分享到plurk 分享到twitter 分享到facebook

版本 bd1416edc811b0063cf68b5dbe0d72a4746c9e08

embedded/SPI

Changes from bd1416edc811b0063cf68b5dbe0d72a4746c9e08 to current

---
title: SPI
categories: STM32, Communcation_Protocol, SPI
...

Introduction
============
- SPI是一種4線同步序列資料協定,適用於可攜式裝置平臺系
- 串列外設介面一般是4線,有時亦可為3線 , 可連接memory , RTC , ADC ,DAC …etc
- SPI提供兩種序列傳輸協定,分別是SPI protocol跟I2S audio protocol,預設是用SPI protocol,可藉由software來做轉換

.. image:: /single_slave.png
![](/single_slave.png)

System Overview
===============
- SPI(Serial Peripheral Interface)為主從式同步串列通訊,可分為單工/半雙工/全雙工,

   * 單工:線路上的訊號只能做單向傳送
   * 半雙工:線路上的訊號可以雙向傳送 , 但是不能同時傳送
   * 全雙工:線路上的訊號可以同時雙向傳送
   * 同步:傳送端和接收端共用同一個CLOCK

- 所有的傳輸都會根據一個共同的頻率訊號 , 此頻率訊號產生自”主控裝置(Master端)”, 從屬裝置(Slave端)會用此頻率訊號來對收到的位串流進行同步
- 如果有多個周邊晶片被連到同一個SPI介面 , 主控裝置能透過SS pin腳的電位高低來選擇接收資料的周邊裝置

.. image:: /three_slaves.png
![](/three_slaves.png)

同步非同步之定義:
===================

- 非同步串列傳輸

傳送端與接收端只需約定是以X速率(clock rate)來傳輸,
接收端的接收時脈(Receiver Clock,RxC)產生方式和傳送者的位元傳輸時脈(Transmitter Clock,TxC)是互相獨立無關的
此種傳輸方式是允許傳送與接收時脈的頻率不那麼同步(允許一定程度的誤差)的情況下進行,故稱為非同步傳輸

![](/asynchronization.png)


- 同步串列傳輸

對接收端而言並沒有自己的時脈產生電路,而是依據傳送端送過來的時脈來接收資料
(即傳送端和接收端共用同一個CLOCK),傳送端以一條導線送出資料,同時以另一條導線送出傳送時脈,提供接收端之同步訊號

![](/synchronization.png)


SPI features(STM32)
===================

- Full-duplex synchronous transfers on three lines 
- Simplex synchronous transfers on two lines with or without a bidirectional data line
- 8- or 16-bit transfer frame format selection 
- Multimaster mode capability
- dynamic change of master/slave operations
- 8 master baud rate prescalers
- slave mode frequency
- SPI TI / motorola mode 
- MSB-first or LSB-first
- Hardware CRC
- DMA capability

.. BIDI mode / RXONLY (p. 801)
.. I2S feature

Device overview
===============

.. image:: /stm32f40x_block_diagram.png
STM32F40x block diagram [#]_

.. [#] `DS8626: ARM Cortex-M4 32b MCU+FPU, 210DMIPS, up to 1MB Flash/192+4KB RAM, USB OTG HS/FS, Ethernet, 17 TIMs, 3 ADCs, 15 comm. interfaces & camera <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/DM00037051.pdf>`_ p.18
![](/stm32f40x_block_diagram.png)
STM32F40x block diagram (STM32F407xx datasheet p.18) 

- 3 SPI的傳輸速度
  * SPI1: 42Mbits/s 
  * SPI2 & SPI3: 21 Mbits/s

- AHB (Advanced High-performance Bus) 主要用於高性能模組間的連接 
- APB1 APB2 (Advanced Peripheral Bus) 主要用於低帶寬的周邊模組間的連接   
- AHB/ APB1 , AHB/ APB2 : bridge 用來做AHB協議到APB1/APB2協議的轉換

.. Datasheet: p.32

SPI functional description
==========================

.. image:: /embedded/SPI/SPI block diagram.png
SPI block diagram [#]_
![](/embedded/SPI/SPI block diagram.png)

.. [#] `STM32F405xx, STM32F407xx , STM32F415xx and ... <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/DM00031020.pdf>`_ p.793
SPI block diagram

[STM32F407xx Reference Manual p.661](http://measure.feld.cvut.cz/system/files/files/cs/vyuka/predmety/A4M38KRP/DM00031020.pdf)

general
-------
.. 介紹 pin
-------------
**介紹 pin**

- MISO: SPI設為master,此腳接收從slave傳來的資料。SPI設為slave,此腳傳送資料到master。
- MOSI: SPI設為master,此腳傳送資料到slave。SPI設為slave,此腳接收從master傳來的資料。
- SCK: 由master提供clock輸出給slave當成通訊clock
- NSS: Slave select. 該腳可以被master當做要和那個slave做構通

  * software mode(SSM=1)

    * 透過SPI_CR1中的SSI選擇slave

  * hardware mode(SSM=0)

    * Output enable(SSOE=1):only in master mode
- NSS: Slave select. 該腳可以被master當作要和哪個slave做溝通

    * Output disable(SSOE=0):
  - Software NSS management(SSM=1)
    - 由內部的SPI_CR1 register裡的SSI bit來代表NSS pin電位高低
    - 原本外部的NSS pin則可做為其他用途用
    - master的SSI bit需設成1,如設成0,則會進入master mode fault state,master就會被設成slave
    - slave的SSI bit需設成0才可接收資料

      * master mode:allows multimaster
  - Hardware NSS management(SSM=0)
    - Output enable(SSOE=1):
      * 只用在master mode
      * 當NSS拉低電位時,表示開始向slave傳輸資料

      * slave mode: 低電位時代表被選為slave,其他時候保持高電位。
    - Output disable(SSOE=0):
      * master mode:
        * 可用在multimaster
        * 當NSS被拉低時,則會進入master mode fault state,master就會被設成slave

.. image:: /embedded/SPI/single master _ single slave application.png
SPI single master/single slave application [#]_
      * slave mode:
        * 當NSS低電位時,代表被選為要接收資料的slave

.. [#] `STM32F405xx, STM32F407xx , STM32F415xx and ... <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/DM00031020.pdf>`_ p.794
![](/embedded/SPI/single master _ single slave application.png)

.. baud rate ref menual p.834
SPI single master/single slave application

..
    Slave select(NSS) pin management
    ''''''''''''''''''''''''''''''''
[STM32F407xx Reference Manual  p.662](http://measure.feld.cvut.cz/system/files/files/cs/vyuka/predmety/A4M38KRP/DM00031020.pdf)

    - baud rate ref menual p.834
    - Slave select(NSS) pin management

Clock phase and clock polarity
''''''''''''''''''''''''''''''

.. image:: /embedded/SPI/SPI Dota clock timing diagram.png
Data clock timing diagram[#]_

.. [#] `STM32F405xx, STM32F407xx , STM32F415xx and ... <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/DM00031020.pdf>`_ p.796
----------------------------------

SPI_CR1 中有兩個 bits CPOL 和 CPHA 控制取值的時間關係,總共有4種組合。

- CPOL(clock polarity) 決定閒置時 clock 的電位。

  * CPOL = 0 表閒置時為低電位。

  * CPOL = 1 表閒置時為高電位。

- CPHA(clock phase) 決定在 clock 的哪個 edge 取值。

  * CPHA = 0 表示在第一個 edge (Rising,when CPOL=0.Falling,when CPOL=1.)取值。

  * CPHA = 1 表示在第二個 edge (Rising,when CPOL=1.Falling,when CPOL=0.)取值。
  * CPHA = 1 表示在第二個 edge (Falling,when CPOL=1.Rising,when CPOL=0.)取值。

Data format
'''''''''''
![](/embedded/SPI/SPI Dota clock timing diagram.png)

- LSB-first or MSB first
- 一次傳送的資料量為 8-bit 或 16-bit
Data clock timing diagram

..
    confiurataion in master mode
    -------------------------------
[STM32F407xx Reference Manual p.664](http://measure.feld.cvut.cz/system/files/files/cs/vyuka/predmety/A4M38KRP/DM00031020.pdf) 

..
    confiurataion in slave mode
    -------------------------------
Data frame format
------------------

- LSB-first or MSB first
- 一次傳送的資料量為 8-bit 或 16-bit,可由SPI_CR1 register裡的DFF bit來作設定
    - confiurataion in master mode
    - confiurataion in slave mode

half-duplex communication
--------------------------

- BIDMODE 判斷是否為全雙工
- RXONLY 判斷半雙工時為傳送端或接收端。
- BIDMODE 判斷傳輸是否為雙向傳輸。
- RXONLY 判斷是否為 純接收端。

SPI 可以只做半雙工,在半雙工時,沒用到的另一隻 pin 可作為一般的 GPIO 使用。

.. RXONLY 在 BIDMODE 時有作用否?
 - RXONLY 在 BIDMODE 時有作用否?
    - data transmission and reception procedures
        - Start sequence in master mode
            - In full-duplex (BIDIMODE=0 and RXONLY=0)
                - begins when data are written into the SPI_DR register (Tx buffer)
            - In unidirectional receive-only mode (BIDIMODE=0 and RXONLY=1)
                - begins as soon as SPE=1

..
    data transmission and reception procedures
    ------------------------------------------
    Start sequence in master mode
    - In full-duplex (BIDIMODE=0 and RXONLY=0)::
        begins when data are written into the SPI_DR register (Tx buffer)
    - In unidirectional receive-only mode (BIDIMODE=0 and RXONLY=1)::
        begins as soon as SPE=1
    TODO: not finished
Status flag
--------------
- 寫程式時必須要持續監視的三個 status flag:
    - Tx buffer empty  flag(TXE)
    - Rx buffer not empty(RXNE)
    - BUSY flag:用以指示SPI 正忙於傳輸的 flag

..
    CRC calculation
    ---------------
SPI 通訊過程
--------------

Status flag
-----------
寫程式時必須要持續監視的三個 status flag:
![](/embedded/SPI/TXE_RXNE_BSY behavior in Master full_duplex mode.png)

Tx buffer empty  flag(TXE)
Rx buffer not empty(RXNE)
TXE/RXNE/BSY behavior in Master / full-duplex mode (BIDIMODE=0 and RXONLY=0)
in the case of continuous transfers

BUSY flag:用以指示SPI 正忙於傳輸的 flag
[STM32F407xx Reference Manual p.672](http://measure.feld.cvut.cz/system/files/files/cs/vyuka/predmety/A4M38KRP/DM00031020.pdf) 

.. TODO busy flag
- 傳送步驟:
    - 將第一筆資料寫入SPI_DR register裡(清除TXE bit、BSY flag設為1)
    - 當傳送第一個bit時,同時將剩下的7個bit並行load到shift register,之後hardware將TXE bit設為1
    - 接著將第二筆資料寫入SPI_DR register裡(清除TXE bit)
    - 等待第一筆資料的bit傳送完
    - 傳送第二筆資料的第一個bit,同時將剩下的7個bit並行load到shift register,之後hardware將TXE bit設為1
    - 接著將第三筆資料寫入SPI_DR register裡(清除TXE bit)
    - 等待第二筆資料的bit傳送完
    - 傳送第三筆資料的第一個bit,同時將剩下的7個bit並行load到shift register,之後hardware將TXE bit設為1
    - 等待第三筆資料的bit傳送完
    - 傳輸結束後,清除BSY flag

..
    DMA support
    -----------
- 接收步驟:
    - 第一筆資料的8 bits依序由MISO接收進來,並且存到shift register
    - 將第一筆資料的8 bits並行load到SPI_DR register(此時hardware將RXNE bit設為1)
    - 由software讀資料,並清除RXNE bit
    - 第二筆資料的8 bits依序由MISO接收進來,並且存到shift register
    - 依序重複步驟...

Code section
============
.. image:: /DSC_0448.jpg
SPI communication using DMA 
-------------------------------

完整程式碼:
- 為了能達到最大傳輸速率,cpu必須在TXE flag被升起時,及時將資料寫入SPI_DR regsister,同時接收端也必須在RXNE flag被升起時,及時將資料讀取出來,以避免有Overrun Condition的狀況發生,所以為了能減少cpu負荷並加快傳輸速度,SPI也實做了一種請求/應答的DMA機制

.. code-block:: c
    - 在每次TXE flag被設為1時,發送DMA請求,DMA則將資料寫入SPI_DR register裡,TXE flag因此被清除
    - 在每次RXNE flag被設為1時,發送DMA請求,DMA則將資料讀出SPI_DR register裡,RXNE flag因此被清除

    git clone git://gitcafe.com/EricCheng/stm32_spi_demo.git
    cd stm32_spi_demo/
- 要開啟此DMA request必須將SPI_CR2 register裡的TXDMAEN跟RXDMAEN bit設置為1

.. 自定義變數:
![](/embedded/Transmission_using_DMA.png)

.. code-block:: c
CRC calculation (Cyclic redundancy check)
-----------------------------------------

..    unsigned char readData[32];
..    unsigned char writeData[32];
..    int rxCounter, txCounter;
- 用途:主要是用來驗證資料傳輸的可靠性,避免在傳輸時,因為外在因素導致接收端接收到錯誤的資料,所以有此機制來預防。
- 運作:
    - 有兩種計算的標準,是根據data frame format來決定,如果是8-bit的資料,則用CR8,如果是16-bit的資料,則用CRC16
    - 當將SPI_CR1 register裡的CRCEN bit設為1,將會啟動CRC calculation,並且會將SPI_RXCRCR和SPI_TXCRCR清空,傳輸端會自動在最後一筆資料後面傳輸一個CRC值(SPI_TXCRCR),然後接收端在接收到此CRC值後,會將之與SPI_RXCRCR比較,如果相同的話,表示資料傳輸正確,如果不同的話代表資料有問題,則CRCERR flag升起

初始化 SPI:
Error flags
==============

- Master:
Master mode fault (MODF)
---------------------------

.. code-block:: c
- master mode fault 有以下兩種情形會發生
    - 在NSS hardware mode且SSOE設為0,master的NSS pin電位被拉低(master會被設為slave)
    - 在NSS software mode下,master的SSI bit設為0(進入slave mode)

- Master mode fault發生時,會將MODF bit設為1,也會清除掉SPE bit,關掉SPI功能,另外當EERIE bit設為1時,將會產生中斷

Overrun condition
------------------
- 在RXNE沒有重設為0的情況下,此時又接收到一筆新資料,則會發生溢出錯誤,在RXNE沒有被清除時,接收的資料無法從shift register寫入Rx buffer,導致從Rx buffer讀出來的資料是不正確的。
- OVR flag升起

CRC error
------------
  * 當CRCEN bit設為1時,CRCERR flag是用來檢驗資料的正確性,當此flag升起,代表接收過來的CRC值跟SPI_RXCRCR的值不符合,表示接收的資料不正確

SPI interrupts
-----------------
![](/embedded/SPI_interrupt.png)

I²S functional description
=============================
![](/embedded/I2S_block_diagram.png)

general
--------------
- I²S或I2S(Inter-IC Sound或Integrated Interchip Sound)是IC間傳輸數位音訊資料的一種介面標準,採用序列的方式傳輸2組(左右聲道)資料,I²S常被使用在傳送CD的PCM音訊資料到CD播放器的DAC中
- 在SPI裡可藉由SPI_I2SCFGR register裡的I2SMOD bit轉換成I²S模式,I²S使用的pins、flags和interrupts幾乎都跟SPI模式相同
- 有三個主要的pin腳(SD、WS、CK) :
  * SD (Serial Data) : mapped on the MOSI pin,串列傳輸的資料線,傳遞兩個聲道的數位音源資訊
  * WS (Word Select) : mapped on the NSS pin,字元選擇線,字元(Word)在此所指即是音源聲道(Channel),用來選擇現在是要輸出左聲道音源或右聲道音源,由master輸出,slave輸入
  * CK (Serial Clock) : mapped on the SCK pin,串列傳輸的時脈線,由master輸出,slave輸入,CK的頻率=2(左右聲道)×採樣頻率×採樣位數
  * I2S2ext_SD、I2S3ext_SD : mapped on the MISO pin,也是序列傳輸的資料線,只用在全雙工模式
  * MCK (Master Clock) : mapped separately,MCKOE bit(in SPI_I2SPR)必須設為1,由master輸出一個額外的clock時脈,通常是採樣頻率的256倍

- 除了之前SPI用到的registers外,I²S也用到了兩個額外的registers,分別是SPI_I2SPR、SPI_I2SCFGR。
- I²S mode下沒有使用到SPI_CR1、跟CRC有關的registers、SSOE bit in SPI_CR2、MODF和CRCERR bits in SPI_SR

Supported audio protocols
-----------------------------
- 藉由查看CHSIDE bit(in SPI_SR)得知目前對應傳輸的頻道是right或left channel
- 通常都是先傳輸left channel的資料後,再傳輸right channel的資料
- 傳輸資料時,以MSB first為主
- 有四種資料封包的格式
  * 16-bit data packed in 16-bit frame
  * 16-bit data packed in 32-bit frame
  * 24-bit data packed in 32-bit frame
  * 32-bit data packed in 32-bit frame

- 因為I²S也是在SPI_DR register做讀取的動作,但SPI_DR只有16 bits,所以有時候必須分兩次讀取
- 當使用的是16-bit data packed in 32-bit frame,剩下的16 bits LSB會被硬體強制設為0,而不用再做第二次讀取

Audio standards
-------------------
I²S支援4種音頻標準,可藉由I2SSTD[1:0]跟PCMSYNC bits(in SPI_I2SCFGR)來做設定

I²S Philips standard
------------------------
WS訊號是用來代表現在正在傳輸的channel是哪個(Right or Left),WS訊號會在傳輸第一個bits(MSB)前的一個CK clock cycle作切換

![](/embedded/I2S_Philips_protocol_waveforms_16/32-bit_full_accuracy_CPOL=0.PNG)

- 傳輸端在CK的falling edge時寫入,接收端在CK的rising edge時讀取
- WS signal在falling edge時作切換

![](/embedded/I2S Philips standard waveforms_24-bit_frame_with_CPOL=0.PNG)

- 上面的情況需要對SPI_DR作兩次讀寫的動作,舉例如下圖

![](/embedded/I2S Philips standard waveforms Transmitting 0x8EAA33.PNG)

- 下圖只需對SPI_DR作一次讀寫即可,剩餘的16 bits會被hardware強制設為0x0000

![](/embedded/I2S Philips standard 16-bit extended to 32-bit packet frame with CPOL = 0.PNG)

MSB justified standard
---------------------------
在傳輸第一個bits(MSB)時,WS訊號同時作切換,如下圖

![](/embedded/MSB Justified 16-bit or 32-bit full-accuracy length with CPOL = 0.PNG)

- 傳輸端在CK的falling edge時寫入,接收端在CK的rising edge時讀取

![](/embedded/MSB Justified 24-bit frame length with CPOL = 0_fix.PNG)

LSB justified standard
--------------------------
- 跟MSB justified standard很相似
- 對於16-bit、32-bit full-accuracy frame formats來說,與MSB justified standard相同
- 與MSB justified standard的差別在於24-bit frame length與16-bit extended to 32-bit packet frame

![](/embedded/LSB Justified 24-bit frame length with CPOL = 0.PNG)

![](/embedded/LSB Justified 24-bit frame length with CPOL = 0 transmit 0x3478AE.PNG)

- 上圖,在接收或傳送時,需由software或DMA對SPI_DR作兩次讀寫

![](/embedded/LSB justified 16-bit extended to 32-bit packet frame with CPOL = 0.PNG)

- 舉例如果要傳輸的資料是0x76A3(0x0000 76A3 extended to 32-bit),那只需要對SPI_DR作一次讀寫即可
- 在傳送端,當TXE flag升起,hardware會先將0x0000傳輸過去,再來當TXE flag又升起,由software對SPI_DR寫入0x76A3
- 在接收端,RXNE flag只會在接收到0x76A3時升起

PCM standard
------------------
- 不需用到Left channel和Right channel
- 分成兩種PCM modes(short frame、long frame),可利用PCMSYNC bit(in SPI_I2SCFGR)來作設定

![](/embedded/PCM standard waveforms (16-bit).PNG)

- 在long frame mode時,WS訊號在前13個bits(MSB)都處於升起的狀態
- 在short frame mode時,WS訊號升起的時間只有一個cycle

Code section
====================
![](/embedded/spi_demo_stm32.jpg)

- 左板為slave 右板為master
- (PA5接PA5 -- SCK、PA6接PA6 -- MISO、PA7接PA7 -- MOSI)
- Demo影片:  http://youtu.be/GaduZ8z-KqE

完整程式碼:
```
    git clone https://github.com/Jakgn/stm32F429_SPI_demo
    cd stm32F429_SPI_demo
```

初始化 SPI:

- Master:
```c
        // enable clock for used IO pins
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

        // enable SPI2 peripheral clock
        // enable SPI1 peripheral clock
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

        /* configure pins used by SPI1
         * PE7 = NSS(CS)
         * PA5 = SCK
         * PA6 = MISO
         * PA7 = MOSI
         \*/
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6 | GPIO_Pin_7;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;  //push-pull
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_Init(GPIOA, &GPIO_InitStruct);

        // connect SPI2 pins to SPI alternate function
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);

        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 ;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOE, &GPIO_InitStruct);	
        
        GPIO_SetBits(GPIOE,GPIO_Pin_7); //Pull High

        /* configure SPI1 in Mode 0
        * CPOL = 0 --> clock is low when idle
        * CPHA = 0 --> data is sampled at the first edge
        \*/
        SPI_I2S_DeInit(SPI1);
        SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO lines
        SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // transmit in master mode, NSS pin has to be always high
        SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
        SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // clock is low when idle
        SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // data sampled at first edge
        SPI_InitStruct.SPI_NSS = SPI_NSS_Soft|SPI_NSSInternalSoft_Set ; // set the NSS management to internal and pull internal NSS high
        SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4
        SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
        SPI_Init(SPI1, &SPI_InitStruct);
        SPI_Cmd(SPI1, ENABLE); // enable SPI1	

- Slave mode

.. code-block:: c
```

- Slave:
```c
        // enable clock for used IO pins
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

        // enable SPI2 peripheral clock
        // enable SPI1 peripheral clock
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

        /* configure pins used by SPI2
        * PA15 = NSS(CS)
        /* configure pins used by SPI1
        * PA5 = SCK
        * PA6 = MISO
        * PA7 = MOSI
        \*/
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6 | GPIO_Pin_7 |  GPIO_Pin_15;
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6 | GPIO_Pin_7;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_Init(GPIOA, &GPIO_InitStruct);

        // connect SPI2 pins to SPI alternate function
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_SPI1);

        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOE, &GPIO_InitStruct);	
        

        GPIO_SetBits(GPIOE,GPIO_Pin_3);		//Let the LIS302DL disable

        /* configure SPI1 in Mode 0
        * CPOL = 0 --> clock is low when idle
        * CPHA = 0 --> data is sampled at the first edge
        \*/
        SPI_I2S_DeInit(SPI1);
        SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO            lines
        SPI_InitStruct.SPI_Mode = SPI_Mode_Slave; // transmit in master mode, NSS pin has to be always high
        SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
        SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // clock is low when idle
        SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // data sampled at first edge
        SPI_InitStruct.SPI_NSS = SPI_NSS_Soft ; // set the NSS management to internal and pull internal NSS high
        SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4
        SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
        SPI_Init(SPI1, &SPI_InitStruct);
        SPI_Cmd(SPI1, ENABLE); // enable SPI1	


初始化 LED:
        SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE); // make SPI1 receive interrupt enable	
	
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn; // Configure SPI1 interrupt
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // Set the priority group of SPI1 interrupt
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // Set the subpriority inside the group
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // Globally enable SPI1 interrupt
        NVIC_Init(&NVIC_InitStructure);

.. code-block:: c
        SPI_Cmd(SPI1, ENABLE); // enable SPI1	
```

    /in  libstm/Utilities/STM32F4-Discovery/discoveryf4utils.c
初始化 LED:
```c
   void init_LED()
   {
    STM_EVAL_LEDInit(LED4);
    STM_EVAL_LEDInit(LED3);
    STM_EVAL_LEDInit(LED5);
    STM_EVAL_LEDInit(LED6);  

    STM_EVAL_LEDOff(LED4);
    STM_EVAL_LEDOff(LED3);
    STM_EVAL_LEDOff(LED5);
    STM_EVAL_LEDOff(LED6);
   }
```

初始化按鍵:

.. code-block:: c

```c
   void init_PB()
   {
        GPIO_InitTypeDef GPIO_InitStruct;

        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

        /* Here the GPIOA module is initialized.
        * We want to use PA0 as an input because
        * the USER button on the board is connected
        * between this pin and VCC.
        \*/
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;	// we want to configure PA0
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; // we want it to be an input
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//this sets the GPIO modules clock speed
        GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // this sets the pin type to push / pull (as opposed to open drain)
        GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN; // this enables the pulldown resistor --> we want to detect a high level
        GPIO_Init(GPIOA, &GPIO_InitStruct);	// this passes the configuration to the Init function which takes care of the low level stuff
   }
```

Master 設定
- 傳送訊號

.. code-block:: c

    uint32_t TimeOutLed=10000;
    GPIO_SetBits(GPIOE,GPIO_Pin_7);  //Chip Select

- 傳送訊號
```c
   uint8_t RemoteLED_OnOff(uint8_t action)
   {
    while(SPI_I2S_GetFlagStatus(SPI1,SPI_FLAG_TXE) == RESET);
    if(action == 1)	//Led On
            SPI_I2S_SendData(SPI1,LED_ON);
    else //Led Off
            SPI_I2S_SendData(SPI1,LED_OFF);
    
    GPIO_ResetBits(GPIOE,GPIO_Pin_7);	

    while(SPI_I2S_GetFlagStatus(SPI1,SPI_FLAG_TXE) == RESET);
    return 1;
   }
```

Master 設定

- task:

.. code-block:: c

    void pb_task(void \*pvParameters)
```c
    void pb_task(void *pvParameters)
    {
        uint8_t bNewLedOnOff=0 ,bOldLedOnOff=0,bLedOnOff=0;

        uint8_t LedOnOff = 0;
        uint8_t previous = 0;
        while(1)
        {
            if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)== Bit_SET)  //User Button Pressed
            {
                bNewLedOnOff = 1;
                if(bNewLedOnOff ^ bOldLedOnOff)
                if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)== Bit_SET) //User Button Pressed
                {
                    if(bLedOnOff)
                    {
                        RemoteLED_OnOff(0);
                        bLedOnOff = 0;
                        STM_EVAL_LEDOff(LED3);
                        STM_EVAL_LEDOff(LED4);
                    }
                    else
                    {
                        RemoteLED_OnOff(1);
                        bLedOnOff = 1;
                        STM_EVAL_LEDOn(LED3);
                        STM_EVAL_LEDOn(LED4);
                    }
                    bOldLedOnOff = bNewLedOnOff;
                }			
            }
            else 
                    bOldLedOnOff = 0;
                        if(previous == 0)
                        {
                                if(LedOnOff)
                                        RemoteLED_OnOff(0);
                                else
                                        RemoteLED_OnOff(1);
                                LedOnOff ^= 1;
                                STM_EVAL_LEDToggle(LED3);
                        }
                        previous = 1;
                }
                else
                        previous = 0;
        }

    }
```

Slave  設定:
- 接收訊息

.. code-block:: c

    uint8_t rcv_tmp = 0;
    uint32_t TimeOutLed = 10000;
    while(1)
- 接收訊息 (use SPI1 interrupt)
```c
    void SPI1_IRQHandler(void)
    {
        while(SPI_I2S_GetFlagStatus(SPI1,SPI_FLAG_RXNE)== RESET);
        rcv_tmp = (uint8_t)SPI_I2S_ReceiveData(SPI1);
        if(rcv_tmp == LED_ON)
        {
            STM_EVAL_LEDOff(LED4);
            STM_EVAL_LEDOff(LED3);
            STM_EVAL_LEDOff(LED5);
            STM_EVAL_LEDOff(LED6);
        }
        else if(rcv_tmp == LED_OFF)
        uint8_t rcv_tmp = 0;
        while(1)
        {
            STM_EVAL_LEDOn(LED4);
            STM_EVAL_LEDOn(LED3);
            STM_EVAL_LEDOn(LED5);
            STM_EVAL_LEDOn(LED6);
        }
    }
                while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)== RESET);
                rcv_tmp = (uint8_t)SPI_I2S_ReceiveData(SPI1);
                if(rcv_tmp == LED_ON)
                {
                        STM_EVAL_LEDOff(LED4);
                        STM_EVAL_LEDOff(LED3);
                }
                else if(rcv_tmp == LED_OFF)
                {
                        STM_EVAL_LEDOn(LED4);
                        STM_EVAL_LEDOn(LED3);
                }
         }
    }   
```

main

.. code-block:: c

```c
    int main(void)
    {
            uint8_t  pressed=0,new_button_state,last_button_state;
            init_SPI();
            init_LED();
            init_PB();

    #ifdef MASTER
            STM_EVAL_LEDOff(LED4);
            STM_EVAL_LEDOff(LED3);
            STM_EVAL_LEDOn(LED5);
            STM_EVAL_LEDOff(LED6);
            xTaskCreate(pb_task,
                        (signed portCHAR \*) "Push Button Task",
                        512 /* stack size \*/, NULL, tskIDLE_PRIORITY + 2, NULL);
    #else
            xTaskCreate(spi_recv_msg_task,
                        (signed portCHAR *) "SPI Recv Task",
                        512 /* stack size \*/, NULL, tskIDLE_PRIORITY + 2, NULL);

    #endif
            /* Start running the tasks. \*/
            vTaskStartScheduler();
            STM_EVAL_LEDOff(LED5);
            return 0;
    }
```

示波器圖解
============
![](/embedded/0xFF.png)

藍色線為Master的SCLK,紅色線為Master的output(MOSI),此次訊號是一直傳送0xFF的結果。當SCLK在高電位時,此時的MOSI值即為Master的Output(高電位為1,低電位為0)

![](/embedded/for_0x55.png)

上圖為加入for迴圈製造delay並傳送0x55的結果。我們發現當SPI沒有傳送時,SCLK也一直在低電位!

Question question 
=================

同步非同步之定義:
---------------
為什麼TXE和RXNE 要 set by hardware , reset by software?
------------------------------------------------------
- 和SPI資料傳輸流程有關係,當寫資料到SPI_DR時(clear the TXE flag),等到上一筆資料傳輸完成時TXE=1時再將下一筆要傳的資料寫入SPI_DR。RXNE=1時,讀取SPI_DR中的資料,並且clear RXNE flag。

- 非同步串列傳輸
為什麼要有CPOL,CPHA分高地電位?
----------------------------

傳送端與接收端只需約定是以X速率(clock rate)來傳輸,
接收端的接收時脈(Receiver Clock,RxC)產生方式和傳送者的位元傳輸時脈(Transmitter Clock,TxC)是互相獨立無關的
此種傳輸方式是允許傳送與接收時脈的頻率不那麼同步(允許一定程度的誤差)的情況下進行,故稱為非同步傳輸
- 資料傳輸時,Master與slave需要設定相同的``CPOL``和``CPHA``定義取資料的時間點,確保資料正確的傳輸。

.. image:: /asynchronization.png
I2S
---
![](/2013-12-17_151324.png)

- SD(Serial Data) 與 SPI 的 MOSI 共用腳位
- ext_SD  與 SPI 的 MISO 共用腳位
- WS(Word Select) 與 SPI 的NSS 共用腳位 
- CK(Serial Clock) 與 SPI 的 SCK 共用腳位

- 同步串列傳輸
GPIO clock的設定
----------------
 下列四圖中,黃色線為SPI之MOSI腳位輸出(0x55 -> 01010101),藍色線為SCK。
 由更改GPIO_Speed 與 BaudRatePrescaler 以比較不同之處。

對接收端而言並沒有自己的時脈產生電路,而是依據傳送端送過來的時脈來接收資料
(即傳送端和接收端共用同一個CLOCK),傳送端以一條導線送出資料,同時以另一條導線送出傳送時脈,提供接收端之同步訊號
![](/M50P2.png)
此圖為 GPIO_Speed_50MHz,BaudRatePrescaler除2,通訊正常

.. image:: /synchronization.png
![](/M50P4.png)
此圖為 GPIO_Speed_50MHz,BaudRatePrescaler除4,與上圖比較,可以看出時脈頻率為上圖的1/2,通訊正常

![](/M2P2.png)
此圖為 GPIO_Speed_2MHz,BaudRatePrescaler除2,可以看到時脈輸出跟不上訊號變換,無法正常通訊

![](/M25P2.png)
此圖為 GPIO_Speed_25MHz,BaudRatePrescaler除2,與GPIO_Speed_50MHz,BaudRatePrescaler除2的圖比較,看不出差異,且通訊正常

Bus和prescaler的關係
-------------------

- The maximum allowed frequency of the high-speed ``APB2`` domain is 84 MHz. Themaximum allowed frequency of the low-speed ``APB1`` domain is 42 MHz,在SPI—CR1的BR[2:0]中設定除頻。

shift register 除了shift 資料以外,還有什麼作用?
---------------------------------------------

- 移位暫存器的輸入、輸出都可以是並行(parallel)或串列(serial)的。它們經常被配置成串入並出(serial-in, parallel-out, SIPO)的形式或併入串出(parallel-in, serial-out, PISO),這樣就可以實現並行數據和串列數據的轉換。如果把移位暫存器的串列輸入端,和並行輸出端的最後一位連接起來,還可以構成循環移位暫存器(circular shift register),用來實現循環計數功能。

為什麼 slave的VDD要接地,master的不用?
為什麼 slave的NSS要接地,master的不用?
-----------------------------------

- 因為如果在hardware mode下,master nss被pull low的話, master mode 會發生錯誤而slave mode 的 nss 要保持低電位 slave mode 才會動作 所以要接地

為什麼要有CPOL,CPHA分高地電位?
----------------------------

``cpol``: 

* 1:控制‵`SCK``閒置的時候處於高電位 所以再大量傳送時的狀態 省電
* 0:控制‵`SCK``閒置的時候處於低電位 所以在少量傳送資料時省電
master和slave在設定上有什麼不同?
-----------------------------

- master
  * 要用BR[2:0]來選baud rate
  * hardware 模式下 NSS pin 要保持高電位,software  模式下 ssi=1


- slave

  * 不能選baud rate
  * hardware 模式下 NSS pin 要保持低電位,software  模式下 ssi=0

Error flags
-----------------------

- Master mode fault (MODF)
    - when the master device has its NSS pin pulled low (in NSShardware mode) or SSI bit low (in NSS software mode), this automatically sets the MODF bit.


- Overrun condition
    - An overrun condition occurs when the master device has sent data bytes and the slave device has not cleared the RXNE bit resulting from the previous data byte transmitted.


- CRC error
  * The CRCERR flag in the SPI_SR register is set if the value received in the shift register does not match the receiver SPI_RXCRCR value.



怎麼知道要使用哪些腳位?
---------------------
------------------------

.. image:: /alternate function mapping.png
Alternate function mapping[#]_
![](/alternate function mapping.png)
Alternate function mapping

.. [#] `DS8626: ARM Cortex-M4 32b MCU+FPU, 210DMIPS, up to 1MB Flash/192+4KB RAM, USB OTG HS/FS, Ethernet, 17 TIMs, 3 ADCs, 15 comm. interfaces & camera <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/DM00037051.pdf>`_ p.58
[Datasheet .61](http://www.st.com/web/en/resource/technical/document/datasheet/DM00037051.pdf)

![](/Alternate function mapping continued.png)
由於GPIOE的AF最少,功能最為乾淨(不易佔用其他功能所需之腳位),適合作為IO port 腳。
STM32F407xx Datasheet p.64

為何要定義為low-active?
----------------------

當系統一有電源時,此時所有的Pin腳會由Low Level 轉換成系統設定的High Level,Slave device 會因為這樣而
誤以為它是被觸發而開始動作.為了避免這類的問題發生通常chip select都會設定Low-Active.


在 (最高速 BUS) 上最低速的裝置為何?
--------------------------------

這樣取決於STM32所可以接受的通訊方式,如果以STM32F4來講,最低速的應該是USART。


有沒有 SPI 串接的可能?
---------------------

可參考`SPI - Serial Peripheral Interface<http://www.mct.net/faq/spi.html>`_
可參考[SPI - Serial Peripheral Interface](http://www.mct.net/faq/spi.html)
這種做法是用在Master的Chip Select腳位很少的情況所使用的。這樣也可以節省硬體的線路,不用在Layout 多餘的線路。
但這必須要Master 與 Slave軟體上的搭配才可行。



在設計 LA 時,要怎麼判斷何 MOSI, MISO?
------------------------------------

因為MASTER的MOSI是主動發送訊號線,所以可以設定LA查看哪一條線被先傳送訊號,就可以判定MOSI。


為何不能一次和多個 slave 端通訊?
------------------------------

因為會造成多個Slave會一起傳送資料,這樣會造成匯流排互相搶資源,而造成Master會誤判資料。Master同一時間只能與一個Slave作資料的溝通。

Why we need CRC?
--------------------

CRC(Cyclic redundancy check),是一種錯誤偵測的方法。是防止在傳輸資料或儲存資料過程當中因為意外事件而造成原始資料變動,所設計的方法。
只要在傳輸資料後面加入已經運算出CRC碼,傳送給對方。對方收到後,透過原始資料運算出CRC碼,比對對方所寫入的CRC碼是否一致就可以知道資料有沒
有因為意外事件而造成資料的變動。


Speed rate of Motion sensor
------------------------------
LIS302DL 資料傳送速度是100Hz 或 400Hz。<http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00135460.pdf>

Reference
=========
- `Serial Peripheral Interface Bus - Wikipedia, the free encyclopedia <http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus>`_
- Reference manual: `STM32F405xx, STM32F407xx , STM32F415xx and ... <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/DM00031020.pdf>`_
- Datasheet: `DS8626: ARM Cortex-M4 32b MCU+FPU, 210DMIPS, up to 1MB Flash/192+4KB RAM, USB OTG HS/FS, Ethernet, 17 TIMs, 3 ADCs, 15 comm. interfaces & camera <http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/DM00037051.pdf>`_

.. need improve
.. _`Reference manual`: http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/DM00031020.pdf
.. https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/receiving-data
- [STM32F42xxx Reference Manual](http://www.st.com/web/en/resource/technical/document/reference_manual/DM00031020.pdf)
- [STM32F429xx I/O pin mapping](http://mikrocontroller.bplaced.net/wordpress/wp-content/uploads/2013/10/Pinbelegung_f429_v100.html)
- [序列周邊介面 - Wikipedia](http://zh.wikipedia.org/wiki/%E5%BA%8F%E5%88%97%E5%91%A8%E9%82%8A%E4%BB%8B%E9%9D%A2)