SPI 全称Serial Peripheral Interface
SPI的最高时钟高达45MHZ
下图表达了SPI的工程原理,通过两根线(MISO和MOSI)进行数据传输,数据的读写同步进行,通过移位寄存器完成数据的交换。
SPI的4条通讯线:
MISO: Master Input Slave Output
MOSI: Master Output Slave Input
CS: Chip Select
SCLK: System Clock
前两根线是数据传输线,最后一跟 是系统时钟线 ,主要看第三根线CS,信号片选:
这是别人写的一个不错的解释 https://blog.csdn.net/STL1634614466/article/details/69375138
总结如下:
CS在也叫NSS(Number Slave Select),分为软件和硬件控制,如果用硬件NSS,只能通过PF6这个口控制,因此只能于一台Slave连接。因此一般用软件NSS,通过普通IO输出数字电平到Slave的片选端口,如果是低电平,则工作,如果是高电平则不连接。
SPI的相关配置如下:
1. 时钟极性:CPOL = 0 -> 时钟空闲为低电平; CPOL = 1 -> 时钟空闲为高电平;
2. 时钟相位:CPHA = 0 -> 时钟第一个跳变沿采集数据; CPHA = 1 -> 时钟第二个跳变沿采集数据;(两种不同的传输协议)
提醒:主从机的时钟极性和时钟相位应保持一致。
3. MODE: 可以配置为Master 或者 Slave,主从模式的区别在于时钟来源于主机
4. 传输方向:只读,双向单线,双向双线
5. SPI波特率分频系数
6. 开始位:MSB 或者 LSB
7. NSS软件控制还是硬件控制
8. 帧格式:SPI Motorola 或者 TI
9. 是否开启CRC校验
10. 如果开启CRC,测需要设置CRC多项式
关于SPI的配置,差不多就这些了,需要配置相关的寄存器。当然,HAL库早就将其封装好了,直接调用就行了。
下面就是STM32中SPI的使用过程了:
STEP1:打开SPI时钟
__HAL_RCC_SPI5_CLK_ENABLE();
STEP2:对SPI进行相关的配置
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
上面的代码中相关的配置很多,具体已经在之前罗列了,大概就10个相关的配置。
STEP3:使能SPI
__HAL_SPI_ENABLE(&SPI5_Handler);
STEP4:数据传输
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,uint16_t Size,uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,uint16_t Size,uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
以上部分大多参考正点原子的资料整理的。下面我们来通过CUBEMX配置自己的SPI,通过SPI连接到一个FLASH,并实现批量数据的传输。
首先打开在CUBEMX上的配置:
选择全双工,并且禁止硬件NSS。接下来就是在Configuration界面配置SPI的一些参数,下面我随便配置了一下,注意主机和从机一些参数要保持一致,DMA和中断我们就先不用了。
配置好了,接下来我们生成工程文件吧!
打开生成好的文件,找到Application/User目录下spi.c,里面有个函数就是用于配置我们在图形界面配置的参数。
void MX_SPI5_Init(void) { hspi5.Instance = SPI5; hspi5.Init.Mode = SPI_MODE_MASTER; hspi5.Init.Direction = SPI_DIRECTION_2LINES; hspi5.Init.DataSize = SPI_DATASIZE_8BIT; hspi5.Init.CLKPolarity = SPI_POLARITY_HIGH; hspi5.Init.CLKPhase = SPI_PHASE_2EDGE; hspi5.Init.NSS = SPI_NSS_SOFT; hspi5.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; hspi5.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi5.Init.TIMode = SPI_TIMODE_DISABLE; hspi5.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi5.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi5) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } }
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) { GPIO_InitTypeDef GPIO_InitStruct; if(spiHandle->Instance==SPI5) { /* USER CODE BEGIN SPI5_MspInit 0 */ /* USER CODE END SPI5_MspInit 0 */ /* SPI5 clock enable */ __HAL_RCC_SPI5_CLK_ENABLE(); /**SPI5 GPIO Configuration PF7 ------> SPI5_SCK PF8 ------> SPI5_MISO PF9 ------> SPI5_MOSI */ GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI5; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); /* USER CODE BEGIN SPI5_MspInit 1 */ /* USER CODE END SPI5_MspInit 1 */ } }
这个函数在main.c里已经帮你调用了,CUBEMX真棒!
接下来我们就用STM32这个主机与一块FLASH从机进行通讯吧!
我们在这里调用一下正点原子整理的两个文件spi.c和w25qxx.c以及他们的头文件。通过一个定时器不断扫描按键状态,并且在while(1)里面写入:
while (1) { switch (SPI_KEY) { case KEY0_PRES: W25QXX_Write((u8*)"showtimewalker", 0x00, 14); LED0 = !LED0; break; case KEY1_PRES: W25QXX_Write((u8*)"ShowTimeWalker", 0x00, 14); LED0 = !LED0; break; case KEY2_PRES: W25QXX_Read(DisPlayString, 0x00, 14); POINT_COLOR = BLUE; LCD_ShowString(10 + DisPlayCutoffX,60 + DisPlayCutoffY,300,16,16,(char*)DisPlayString); DisPlayCutoffY += 18; if (DisPlayCutoffY == 198){ DisPlayCutoffX += 120; DisPlayCutoffY = 0; } LED0 = !LED0; delay_ms(50); break; }
编译通过,然后我们按下KEY0,写入“showtimewalker”,按下KEY1,写入“ShowTimeWalker”,按下KEY2,读出数据,并且显示到显示屏上。看一下实际测试结果吧:
OK,调试成功,值得一提的是,FLASH中的数据是掉电不消失的哦,因此可以用于存储一下特殊数据。
有疑问欢迎大家讨论。谢谢阅读。