2016年10月23日 星期日

作業系統之前的程式 for stm32f4discovery (16) - spi

少年辛苦終身事,莫向光陰惰寸功

相關文章:
fig 0 金字塔門檻

fig 0 的金字塔圖表現出所需要的知識, 得從最下層一一突破, 才能完成最上層的目標。

spi 的用法真的難倒我, 從《20150117 成功大學 UniDEMO 期末聯合 DEMO 計畫》開始我就沒成功過, 我真的很懷疑書上/網路上的範例程式是真的可以運作嗎? 20161013 我再次挑戰, 這次我補充了需要的知識以及武器, 可以來對付 spi 了。

fig 1 spi1 可以使用的 pin

一樣要有以下的知識以及武器:
  • 邏輯分析儀
  • 搞懂 stm32f4 clock source tree
  • 那些 pin 是可以拿來當 spi 的那 4 個 pin
fig 2
還是參考《精通STM32F4(库函数版)》SPI 的實驗, 但我稍微簡化它, 因為 stm32f4discovery 並沒有接上的 SPI flash, 我無法照原樣套用。書上的範例程式我搞不定, 最後找到另外的版本。

我打算用 SPI1 送出資料, 所以得先找那個 pin 可以用來當作 SPI1, 參考 fig 1 的 datasheet, 我本來找了:

NSS: PA15
SCK: PB3
MISO: PB4
MOSI: PB4

來當 spi1 使用, 不過失敗了。最後找到可運作的程式碼是使用

NSS: PA4
SCK: PA5
MISO: PA6
MOSI: PA7

來當 spi1。

由於用到了 gpio A, 所以還要開啟 gpio A 相關的時脈, GPIO 接在 AHB1, 這是為什麼要呼叫 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE)。

PA4, 5, 6, 7 需要用 GPIO_PinAFConfig 來設定其對應的 spi1 功能。

spi 相關設定就不解釋, 《精通STM32F4(库函数版)》(可以從https://pan.baidu.com/s/1dEQxWDN 下載) 或是相關的書籍都會提到, 但都不是很詳細, 我看了幾本書中的介紹以及網路文章, 還是不太理解 spi, 但也算略懂。

而 spi1 接在 APB2, 所以需要呼叫 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE) 開啟 spi1 的時脈。

這就是為什麼要搞懂 stm32f407 clock soure tree, 除了系統時脈, 我們還得知道所要設定的週邊接在那個 bus 上。這些細節請參考《精通STM32F4(库函数版)》, 這本書讓我一舉突破 clock source tree 的關卡。

程式在初始化 spi1 之後, 很單純的從 spi MOSI 送出 0xaa, 0x12 而已, 怎麼驗證有正確送出呢? 用 LA, fig 2 中間上方便是接上 LA channel 0, 1, 2, 3, fig 3 則是結果。

一開始我以為設定好 spi1 enable 之後就可以在 LA 看到 SCK 的波形, 不過事與願違, spi1.c L736 SPI1->DR = data; 這之後, spi 才會開始運作, 也才會送出 spi clock。

fig 3 的 01 是 SCK, 也就是 clock, 326.5k, 而 spi1 是接在 APB2, 而 APB2 是 84 MHz, 而我用 SPI_BaudRatePrescaler_256 讓 spi1 除以 256, 84000000/256 = 328125, 好像很接近 325.5k, 我不確定是不是對的。03 是 spi 1 的 MOSI, 也就是 master output, 送出 170 (0xaa), 18 (0x12) 看來也是正確的。

fig 3

spi1.c 是將 spi1 設定為 master, soft nss, 也就是 cs 這個 pin 可以不用接, 用軟體的方式來實作。

spi1.c
  1 #include "stm32.h"
  2 #include "stm32f4xx_usart.h"
  3 #include "stm32f4xx_rcc.h"
  4 #include "stm32f4xx_spi.h"
  5 #include "stm32f4xx_gpio.h"
  6 
663 void init_SPI1(void)
664 {
665  
666  GPIO_InitTypeDef GPIO_InitStruct;
667  SPI_InitTypeDef SPI_InitStruct;
668  
669  // enable clock for used IO pins
670  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
671  
672  /* configure pins used by SPI1
673   * PA5 = SCK
674   * PA6 = MISO
675   * PA7 = MOSI
676   */
677  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
678  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
679  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
680  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
681  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
682  GPIO_Init(GPIOA, &GPIO_InitStruct);
683  
684  // connect SPI1 pins to SPI alternate function
685  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
686  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
687  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
688  
689  // enable clock for used IO pins
690  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
691  
692  /* Configure the chip select pin
693     in this case we will use PA4 */
694  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
695  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
696  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
697  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
698  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
699  GPIO_Init(GPIOA, &GPIO_InitStruct);
700  
701  GPIOA->BSRRL |= GPIO_Pin_4; // set PA4 high
702 #if 0
703  /* Configure the chip select pin
704     in this case we will use PE7 */
705  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
706  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
707  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
708  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
709  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
710  GPIO_Init(GPIOE, &GPIO_InitStruct);
711  
712  GPIOE->BSRRL |= GPIO_Pin_7; // set PE7 high
713 #endif 
714  // enable peripheral clock
715  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
716  
717  /* configure SPI1 in Mode 0 
718   * CPOL = 0 --> clock is low when idle
719   * CPHA = 0 --> data is sampled at the first edge
720   */
721  SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO lines
722  SPI_InitStruct.SPI_Mode = SPI_Mode_Master;     // transmit in master mode, NSS pin has to be always high
723  SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
724  SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;        // clock is low when idle
725  SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;      // data sampled at first edge
726  SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set; // set the NSS management to internal and pull internal NSS high
727  SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // SPI frequency is APB2 frequency / 4
728  SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
729  SPI_Init(SPI1, &SPI_InitStruct); 
730  
731  SPI_Cmd(SPI1, ENABLE); // enable SPI1
732 }
733 
734 uint8_t SPI1_send(uint8_t data){
735 
736  SPI1->DR = data; // write data to be transmitted to the SPI data register
737  while( !(SPI1->SR & SPI_I2S_FLAG_TXE) ); // wait until transmit complete
738  while( !(SPI1->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
739  while( SPI1->SR & SPI_I2S_FLAG_BSY ); // wait until SPI is not busy anymore
740  return SPI1->DR; // return received data from SPI data register
741 }
742 
743 /**
744  * @brief  Main program.
745  * @param  None
746  * @retval None
747  */
748 int main(void)
749 {
750 #ifdef SET_CPU_CLOCK
751   SystemInit();
752 #endif
753   init_SPI1();
754   uint8_t received_val = 0;
755 
756 #if 1
757   while(1)
758   {
759     //GPIOE->BSRRH |= GPIO_Pin_7; // set PE7 (CS) low
760     GPIOA->BSRRH |= GPIO_Pin_4; // set PE7 (CS) low
761     #if 1
762     SPI1_send(0xAA);  // transmit data
763     received_val = SPI1_send(0x12); // transmit dummy byte and receive data
764     #endif
765     //GPIOE->BSRRL |= GPIO_Pin_7; // set PE7 (CS) high
766     GPIOA->BSRRL |= GPIO_Pin_4; // set PE7 (CS) high
767   }
768 #endif
810 
811 }

ref:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。