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

版本 cb1f7a426d067a2d882b605067844f096391be79

embedded/SPI

Changes from cb1f7a426d067a2d882b605067844f096391be79 to 7299483b1548e154ae276d0ed954564bdb95f6db

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

Introduction
============
- SPI是一種4線同步序列資料協定,適用於可攜式裝置平臺系
- 串列外設介面一般是4線,有時亦可為3線 , 可連接memory , RTC , ADC ,DAC …etc

.. image:: /single_slave.png

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

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

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

.. image:: /three_slaves.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

- 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 [#]_

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

general
-------
.. 介紹 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

    * Output disable(SSOE=0):

      * master mode:allows multimaster

      * slave mode: 低電位時代表被選為slave,其他時候保持高電位。

.. image:: /embedded/SPI/single master _ single slave application.png
SPI single master/single slave application [#]_

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

.. 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.)取值。

Data format
'''''''''''

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

..
    confiurataion in master mode
    -------------------------------

..
    confiurataion in slave mode
    -------------------------------

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

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

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

.. 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
    TODO: not finished

..
    CRC calculation
    ---------------

Status flag
-----------
寫程式時必須要持續監視的三個 status flag:

Tx buffer empty  flag(TXE)
Rx buffer not empty(RXNE)

BUSY flag:用以指示SPI 正忙於傳輸的 flag

.. TODO busy flag

..
    DMA support
    -----------

Code section
============
.. image:: /DSC_0448.jpg

完整程式碼:

.. code-block:: c

    git clone git://gitcafe.com/EricCheng/stm32_spi_demo.git
    cd stm32_spi_demo/

.. 自定義變數:

.. code-block:: c

..    unsigned char readData[32];
..    unsigned char writeData[32];
..    int rxCounter, txCounter;

初始化 SPI:

- Master:

.. code-block:: c

        // enable clock for used IO pins
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

        // enable SPI2 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

        // enable clock for used IO pins
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

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

        /* configure pins used by SPI2
        * PA15 = NSS(CS)
        * 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_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_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:

.. code-block:: c

    /in  libstm/Utilities/STM32F4-Discovery/discoveryf4utils.c
    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

        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

    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;


- task:

.. code-block:: c

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

        while(1)
        {
            if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)== Bit_SET)  //User Button Pressed
            {
                bNewLedOnOff = 1;
                if(bNewLedOnOff ^ bOldLedOnOff)
                {
                    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;
        }

    }

Slave  設定:
- 接收訊息

.. code-block:: c

    uint8_t rcv_tmp = 0;
    uint32_t TimeOutLed = 10000;
    while(1)
    {
        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)
        {
            STM_EVAL_LEDOn(LED4);
            STM_EVAL_LEDOn(LED3);
            STM_EVAL_LEDOn(LED5);
            STM_EVAL_LEDOn(LED6);
        }
    }

main

.. code-block:: 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;
    }

Questions
=========

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

.. image:: /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


為何要定義為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>`_
這種做法是用在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