• 第十五篇,STM32的SPI串行通信协议


    1.SPI概念

    spi是摩托罗拉公司设计的用于板间的串行通信方式,应用场景类似于IIC,速度快于IIC。

    spi属于高速,全双工,同步的通信总线;和设备连接占用4根线,比较占用引脚,速度最快达到40Mbps。

    2.硬件连接

    CS(chip select):片选引脚

    主设备控制,用于选择当前通信的从设备 ,  一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access),所以, Master 设备必须首先通过 CS对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.

    SCLK:时钟线

    产生时钟信号,由主设备控制

    MISO(Master Input Slave output)

    主设备输入,从设备输出

    MOSI(Master Output Slave Input)

    主设备输出,从设备输入(MISO和MOSI实现全双工)

    3.spi协议

     

    spi每一位数据的发送和接收是在时钟信号的边沿完成,根据选择的上升沿/下降沿和时钟信号的高低电平顺序,一共有4种情况。


    CPOL表示高低电平顺序,叫做极性;若CPOL = 0,串行同步时钟的空闲状态为低电平;

    若CPOL = 1,串行同步时钟的空闲状态为高电平;

    CPHA表示第一个/第二个边沿,叫做相位。

    SPI输出串行同步时钟极性和相位可以根据外设工作要求进行配置。 SPI 设备间的数据传输又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.

        

    分别是以下四种情况:

    MODE0:CPOL= 0,CPHA=0。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的上升沿采样,下降沿输出;

    MODE1:CPOL= 0,CPHA=1。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的下降沿采样,上升沿输出;

    MODE2:CPOL= 1,CPHA=0。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的下降沿采样,上升沿输出;

    MODE3:CPOL= 1,CPHA=1。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的上升沿采样,下降沿输出;

     

     起始信号

    NSS信号线由高变低,是SPI通讯的起始信号
     结束信号

    NSS信号由低变高,是SPI通讯的停止信号
     数据传输

    SPI使用MOSI以及MISO信号来传输数据,使用SCK信号线进行数据同步。

    MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出时同时进行的。

    SPI每次传输数据可以8位或16位为单位,每次传输的单位数不受限制
     


     

    4.spi flash

    原理图:

     

    spi接口的CS连接到了PB14,SCLK,MISO,MOSI分别连接到了PB3 PB4 PB5,这三个IO口具有SPI的复用功能。

    5.spi通信的实现(两种方法)

    (1)使用GPIO接口模拟SPI的时序,只要是IO口即可以使用。

    (2)使用SPI控制器,只需要配置好SPI的通信参数后,直接进行传输,接口必须有SPI复用功能。

    6.spi控制器的库函数编程

     

    添加spi的库函数源码:

    (1)开启时钟

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

    (2)将GPIO配置为SPI复用功能(CS配置为输出)

    GPIO_Init(...); GPIO_AFPinConfig(...);

    (3)初始化SPI

    1. void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
    2. 参数:
    3. SPIx - 哪个SPI
    4. SPI_InitStruct - SPI初始化结构
    5. typedef struct
    6. {
    7. uint16_t SPI_Direction; /*!< 传输方向 单向/双向 @ref SPI_data_direction */
    8. uint16_t SPI_Mode; /*!< 主/从设备选择 @ref SPI_mode */
    9. uint16_t SPI_DataSize; /*!< 数据位长度 @ref SPI_data_size */
    10. uint16_t SPI_CPOL; /*!< 极性 @ref SPI_Clock_Polarity */
    11. uint16_t SPI_CPHA; /*!< 相位 @ref SPI_Clock_Phase */
    12. uint16_t SPI_NSS; /*!< 片选信号选择 软件/硬件 @ref SPI_Slave_Select_management */
    13. uint16_t SPI_BaudRatePrescaler; /*!< 参考时钟的预分频系数 10M左右*/
    14. uint16_t SPI_FirstBit; /*!< 传输位顺序 高位/低位 @ref SPI_MSB_LSB_transmission */
    15. uint16_t SPI_CRCPolynomial; /*!< CRC校验,无需配置 */
    16. }SPI_InitTypeDef;

    (4)使能SPI

    SPI_Cmd(...);

    (5)使用SPI传输数据

    //发送和接收同时进行 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

    (6)查询SPI传输状态

    1. FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
    2. 参数:
    3. * @arg SPI_I2S_FLAG_TXE: Transmit buffer empty flag.
    4. * @arg SPI_I2S_FLAG_RXNE: Receive buffer not empty flag.
    5. * @arg SPI_I2S_FLAG_BSY: Busy flag.
    6. * @arg SPI_I2S_FLAG_OVR: Overrun flag.
    7. * @arg SPI_FLAG_MODF: Mode Fault flag.
    8. * @arg SPI_FLAG_CRCERR: CRC Error flag.
    9. * @arg SPI_I2S_FLAG_TIFRFE: Format Error.
    10. * @arg I2S_FLAG_UDR: Underrun Error flag.
    11. * @arg I2S_FLAG_CHSIDE: Channel Side flag.

    7.W25Q128芯片

    (1)接口和状态寄存器

    CS WP HOLD低电平有效,WP和HOLD接VCC,功能关闭,片选是低电平选中。

    SPI接口支持CPOL和CPHA为0,0和1,1的模式

     

    (2)操作时序

    在进行任何操作前开启片选,操作完成后关闭片选。

    1)读设备ID

    发送90H ===> 发送24位0地址 ===> 收到厂家ID(0xef)和设备ID(0x17)

    *************************************************************************************************************

    //代码实现W25Q128芯片的SPI通信

    //功能函数

    1. #include
    2. #include
    3. #include
    4. #include
    5. void spi1_init(void)
    6. {
    7. GPIO_InitTypeDef GPIO_InitStruct;
    8. SPI_InitTypeDef SPI_InitStruct;
    9. //1.开始时钟
    10. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
    12. //2.配置GPIO为SPI功能
    13. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//输出模式
    14. GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
    15. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
    16. GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
    17. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;//PB14
    18. GPIO_Init(GPIOB,&GPIO_InitStruct);
    19. //片选关闭
    20. W25Q128_CS = 1;
    21. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用模式
    22. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3 PB4 PB5
    23. GPIO_Init(GPIOB,&GPIO_InitStruct);
    24. GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
    25. GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
    26. GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);
    27. //3.SPI初始化
    28. SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双向全双工
    29. SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主设备
    30. SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据长度8位
    31. SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;//极性 mode 0
    32. SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;//相位 mode 0
    33. SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//软件片选
    34. SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//APB2总线时钟 84M/16分频 = 5.25M
    35. SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位先出
    36. SPI_Init(SPI1,&SPI_InitStruct);
    37. //4.使能SPI1
    38. SPI_Cmd(SPI1,ENABLE);
    39. }
    40. //发送接收数据 ----- 同时进行
    41. //data是要发送的数据,返回接收的数据
    42. u8 spi1_send_recv_byte(u8 data)
    43. {
    44. //等待发送缓冲区为空
    45. while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)!=SET);
    46. //发送数据
    47. SPI_I2S_SendData(SPI1,data);
    48. //等待接收缓冲区非空
    49. while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)!=SET);
    50. //接收数据
    51. return SPI_I2S_ReceiveData(SPI1);
    52. }
    53. //读取W25Q128的ID
    54. u16 w25q128_read_id(void)
    55. {
    56. u16 id = 0;
    57. //片选选中
    58. W25Q128_CS = 0;
    59. //发送90H
    60. spi1_send_recv_byte(0x90);
    61. //发送24位0地址
    62. spi1_send_recv_byte(0x00);
    63. spi1_send_recv_byte(0x00);
    64. spi1_send_recv_byte(0x00);
    65. //接收厂家ID放在高8位
    66. id |= spi1_send_recv_byte(0xff)<<8;
    67. //接收设备ID放在低8位
    68. id |= spi1_send_recv_byte(0xff);
    69. //片选关闭
    70. W25Q128_CS = 1;
    71. return id;
    72. }
    73. //读取W25Q128的数据
    74. void w25q128_read_data(u32 addr,u8 *data,u8 len)
    75. {
    76. //片选选中
    77. W25Q128_CS = 0;
    78. //发送03H
    79. spi1_send_recv_byte(0x03);
    80. //发送24位读的地址,高位先出
    81. spi1_send_recv_byte((addr>>16)&0xff);//16~23位
    82. spi1_send_recv_byte((addr>>8)&0xff);//8~15位
    83. spi1_send_recv_byte((addr>>0)&0xff);//0~7位
    84. //读取数据
    85. while(len--){
    86. *data++ = spi1_send_recv_byte(0xff);
    87. }
    88. //片选关闭
    89. W25Q128_CS = 1;
    90. }
    91. //开启/关闭写使能 enable = 0 开启写使能 enable = 1 关闭写使能
    92. void w25q128_write_enable_disable(u8 enable)
    93. {
    94. //片选选中
    95. W25Q128_CS = 0;
    96. if(enable){
    97. spi1_send_recv_byte(0x04);
    98. }
    99. else{
    100. spi1_send_recv_byte(0x06);
    101. }
    102. //片选关闭
    103. W25Q128_CS = 1;
    104. }
    105. //读状态寄存器1
    106. u8 w25q128_read_status(void)
    107. {
    108. u8 status = 0;
    109. //片选选中
    110. W25Q128_CS = 0;
    111. //发送05H
    112. spi1_send_recv_byte(0x05);
    113. status = spi1_send_recv_byte(0xff);
    114. //片选关闭
    115. W25Q128_CS = 1;
    116. return status;
    117. }
    118. //扇区擦除
    119. void w25q128_sector_erase(u32 addr)
    120. {
    121. //开启写使能
    122. w25q128_write_enable_disable(0);
    123. //延时,让片选信号保持一段时间,使W25Q128感受到
    124. delay_us(50);
    125. //片选选中
    126. W25Q128_CS = 0;
    127. //发送20H
    128. spi1_send_recv_byte(0x20);
    129. //发送24位擦除地址,高位先出
    130. spi1_send_recv_byte((addr>>16)&0xff);//16~23位
    131. spi1_send_recv_byte((addr>>8)&0xff);//8~15位
    132. spi1_send_recv_byte((addr>>0)&0xff);//0~7位
    133. //片选关闭
    134. W25Q128_CS = 1;
    135. delay_us(50);
    136. //等待擦除完成
    137. while(w25q128_read_status()&0x1){
    138. //1ms查询一次
    139. delay_ms(1);
    140. }
    141. delay_us(50);
    142. //关闭写使能
    143. w25q128_write_enable_disable(1);
    144. }
    145. //写数据(按页)
    146. void w25q128_write_page(u32 addr,u8 *data,u8 len)
    147. {
    148. //开启写使能
    149. w25q128_write_enable_disable(0);
    150. //延时,让片选信号保持一段时间,使W25Q128感受到
    151. delay_us(50);
    152. //片选选中
    153. W25Q128_CS = 0;
    154. //发送02H
    155. spi1_send_recv_byte(0x02);
    156. //发送24位写地址,高位先出
    157. spi1_send_recv_byte((addr>>16)&0xff);//16~23位
    158. spi1_send_recv_byte((addr>>8)&0xff);//8~15位
    159. spi1_send_recv_byte((addr>>0)&0xff);//0~7位
    160. //发送写的数据
    161. while(len--){
    162. spi1_send_recv_byte(*data++);
    163. }
    164. //片选关闭
    165. W25Q128_CS = 1;
    166. delay_us(50);
    167. //等待写完成
    168. while(w25q128_read_status()&0x1){
    169. //1ms查询一次
    170. delay_ms(1);
    171. }
    172. delay_us(50);
    173. //关闭写使能
    174. w25q128_write_enable_disable(1);
    175. }
    176. void flash_write_temp(u8 temp)
    177. {
    178. u8 buf[16] = {0},i,data = 0;
    179. u32 addr = 0;
    180. //找到未被写过的块
    181. while(1){
    182. w25q128_read_data(addr,buf,16);
    183. if(buf[0]!=0x55)
    184. break;
    185. addr += 16;
    186. }
    187. data = 0x55;
    188. //写入0x55
    189. w25q128_write_page(addr,&data,1);
    190. //写入temp
    191. w25q128_write_page(addr+1,&temp,1);
    192. data = 0;
    193. //求校验和
    194. w25q128_read_data(addr,buf,16);
    195. for(i=0;i<15;i++){
    196. data = (data + buf[i])&0xff;
    197. }
    198. //写入校验和
    199. w25q128_write_page(addr+15,&data,1);
    200. }
    201. void flash_read_temp(void)
    202. {
    203. u8 buf[16] = {0},i,data;
    204. u32 addr = 0;
    205. //找出所有写入温度的块
    206. while(1){
    207. data = 0;
    208. w25q128_read_data(addr,buf,16);
    209. if(buf[0]!=0x55)
    210. break;
    211. //求校验和
    212. for(i=0;i<15;i++){
    213. data = (data + buf[i])&0xff;
    214. }
    215. if(data!=buf[15]){
    216. printf("checksum error!data = %#x,buf[15] = %#x\r\n",data,buf[15]);
    217. break;
    218. }
    219. printf("0x%x temp = %d\r\n",addr,buf[1]);
    220. addr += 16;
    221. }
    222. }

    //头文件声明

    1. #ifndef _SPI_FLASH_H_
    2. #define _SPI_FLASH_H_
    3. #include
    4. #define W25Q128_CS PBout(14)
    5. void spi1_init(void);
    6. u16 w25q128_read_id(void);
    7. void w25q128_read_data(u32 addr,u8 *data,u8 len);
    8. void w25q128_sector_erase(u32 addr);
    9. void w25q128_write_page(u32 addr,u8 *data,u8 len);
    10. void flash_write_temp(u8 temp);
    11. void flash_read_temp(void);
    12. #endif

  • 相关阅读:
    JAVA 多态
    Spring Security-基于表达式的访问控制和基于注解的访问控制
    C# 向us7ascii编码的oracle数据库插入中文数据????问号乱码的解决方案
    k8s之pod控制器
    Spring web security
    使用gitflow时如何合并hotfix
    QGC 飞行模式控制流程分析
    Elasticsearch 7和Elastic Stack:深入实践
    【硅谷创业公司招聘】openai 亚马逊投资的创业公司招聘远程全栈工程师
    Element UI打开表单自动验证问题的解决
  • 原文地址:https://blog.csdn.net/weixin_44651073/article/details/125832907