stm32之SPI通信--学习笔记(防止自己忘记)

2019-07-20 22:28发布

  之前一直对SPI通信一知半解,所以想抽空把它搞得明白一些。考虑到之前是结合Flash芯片来学的,十分不直观,而且主要把时间和精力都花在Flash芯片的datasheet和驱动上了,SPI通信也没学好。所以这次就考虑用4位数码管显示模块,模块是直接买的现成的,这样可以简化操作,把精力聚焦到学习的核心–SPI通信本身上来。  本次采用的模块是用2片74HC595串联驱动的,一片用来控制数码管的位选(U1),一片用来控制数码管的段选(U2)。接口比较简单,总共5个引脚,2个引脚分别接VCC和GND,DIO用来接收串行数据的输入,SCLK用来接收同步时钟,每个SCLK上升沿74HC595内部的移位寄存器会移一位,RCLK用来控制数据的输出,每个RCLK上升沿74HC595内部的移位寄存器的数据会被放进存储寄存器并输出到外部引脚QA~QH上。而QH’是串行输出引脚,该引脚会接收最高位的溢出,从而实现多片74HC595的级联。 spi1.png    当两片74HC595串联时,先发八位数据用于段选,再发八位数据用于位选,然后RCLK上升沿,就可以驱动某位数码管显示某个字符,通过动态扫描数码管,由于人眼的视觉暂停效果,就可以实现4位数码管的同时显示。先用通用I/O来实现该数码管的驱动,程序如下:  头文件74HC595.h
  1.   #ifndef __74HC595_H__
  2.   #define __74HC595_H__
  3.   #include"stm32f10x_lib.h" //包含所有的头文件
  4.   #include
  5.   // 4-Bit LED Digital Tube Module
  6.   #define HC595_SCLK_PIN GPIO_Pin_5 // SPI1_SCK PA5
  7.   #define HC595_RCLK_PIN GPIO_Pin_12 // SPI1_NSS PA4
  8.   #define HC595_DIO_PIN GPIO_Pin_7 // SPI1_MOSI PA7
  9.   #define HC595_GPIO GPIOA
  10.   #define HC595_RCLK_GPIO GPIOB
  11.   #define HC595_RCC RCC_APB2Periph_GPIOA
  12.   #define HC595_RCLK_RCC RCC_APB2Periph_GPIOB
  13.   void HC595_Init(void);
  14.   void HC595_SendByte(u8 data);
  15.   u8 HC595_Display(u16 num, u8 dp);
  16.   #endif
复制代码
    源文件74HC595.c
  1. // 用于HC595实现的4Bit-LED Digit Tube Module
  2.   // 注意:该4位数码管是共阳的!
  3.   #include "74HC595.h"
  4.   // 码表
  5.   const u8 digitTable[] =
  6.   {
  7.   // 0 1 2 3 4 5 6 7 8 9
  8.   0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,
  9.   // A b C d E F -
  10.   0x8C, 0xBF, 0xC6, 0xA1, 0x86, 0xFF, 0xbf
  11.   };
  12.   /*******************************************************************************
  13.   * Function Name : HC595_Init
  14.   * Description : 初始化HC595
  15.   * Input : None
  16.   * Output : None
  17.   * Return : None
  18.   *******************************************************************************/
  19.   void HC595_Init(void)
  20.   {
  21.   GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量
  22.   RCC_APB2PeriphClockCmd(HC595_RCC | HC595_RCLK_RCC, ENABLE);  //使能HC595的时钟
  23.   //74HC595, SCLK RCLK DIO 推挽输出
  24.   GPIO_InitStructure.GPIO_Pin = HC595_SCLK_PIN| HC595_DIO_PIN;
  25.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //管脚频率为50MHZ
  26.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输出模式为推挽输出
  27.   GPIO_Init(HC595_GPIO, &GPIO_InitStructure); //初始化寄存器
  28.   GPIO_InitStructure.GPIO_Pin = HC595_RCLK_PIN;
  29.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //管脚频率为50MHZ
  30.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输出模式为推挽输出
  31.   GPIO_Init(HC595_RCLK_GPIO, &GPIO_InitStructure); //初始化寄存器
  32.   }
  33.   /*******************************************************************************
  34.   * Function Name : HC595_SendByte
  35.   * Description : 发送一个字节
  36.   * Input : data
  37.   * Output : None
  38.   * Return : None
  39.   *******************************************************************************/
  40.   void HC595_SendByte(u8 data)
  41.   {
  42.   u8 i;
  43.   for (i=8; i>=1; i--)
  44.   {
  45.   // 高位在前
  46.   if (data&0x80)
  47.   GPIO_SetBits(HC595_GPIO, HC595_DIO_PIN);
  48.   else
  49.   GPIO_ResetBits(HC595_GPIO, HC595_DIO_PIN);
  50.   data <<= 1;
  51.   // SCLK上升沿
  52.   GPIO_ResetBits(HC595_GPIO, HC595_SCLK_PIN);
  53.   GPIO_SetBits(HC595_GPIO, HC595_SCLK_PIN);
  54.   }
  55.   }
  56.   /*******************************************************************************
  57.   * Function Name : HC595_Display
  58.   * Description : 显示4位数字(包括小数点)
  59.   * Input : num: 0000 - 9999
  60.   * dp: 小数点的位置1-4
  61.   * Output : None
  62.   * Return : 正常返回0,错误返回1
  63.   *******************************************************************************/
  64.   u8 HC595_Display(u16 num, u8 dp)
  65.   {
  66.   u8 qian = 0, bai = 0, shi = 0, ge = 0;
  67.   // 对显示的参数范围进行检查
  68.   if (num > 9999 || dp > 4)
  69.   //报错
  70.   return 1;
  71.   // 对num进行分解
  72.   qian = num / 1000;
  73.   bai = num % 1000 / 100;
  74.   shi = num % 100 / 10;
  75.   ge = num % 10;
  76.   // 千位
  77.   if(dp == 1)
  78.   HC595_SendByte(digitTable[qian] & 0x7F);
  79.   else
  80.   HC595_SendByte(digitTable[qian]);
  81.   HC595_SendByte(0x08);
  82.   GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  83.   GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  84.   // 百位
  85.   if(dp == 2)
  86.   HC595_SendByte(digitTable[bai] & 0x7F);
  87.   else
  88.   HC595_SendByte(digitTable[bai]);
  89.   HC595_SendByte(0x04);
  90.   GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  91.   GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  92.   // 十位
  93.   if(dp == 3)
  94.   HC595_SendByte(digitTable[shi] & 0x7F);
  95.   else
  96.   HC595_SendByte(digitTable[shi]);
  97.   HC595_SendByte(0x02);
  98.   GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  99.   GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  100.   // 个位
  101.   if(dp == 4)
  102.   HC595_SendByte(digitTable[ge] & 0x7F);
  103.   else
  104.   HC595_SendByte(digitTable[ge]);
  105.   HC595_SendByte(0x01);
  106.   GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  107.   GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);
  108.   return 0;
  109.   }
复制代码
  接下来就可以把重心都放在STM32的SPI外设上了,首先需要读一下STM32F10x的参考手册的SPI(串行外设接口)部分,这样对SPI就可以有较好的理解,比较重要的是要看懂SPI的结构框图和主从机通信的示意图,如下: spi2.png spi3.png   这个理解以后,我们就可以参考《STM32F103XX固件库用户手册》的SPI部分来实现STM32的SPI外设配置和收发数据了,具体代码如下:  头文件74HC595_SPI.h
  1. #ifndef __74HC595_SPI_H__
  2.   #define __74HC595_SPI_H__
  3.   #include"stm32f10x_lib.h" //包含所有的头文件
  4.   #include
  5.   // 4-Bit LED Digital Tube Module
  6.   // 引脚 // SPI1 4位数码管
  7.   #define HC595_NSS_PIN GPIO_Pin_4 // SPI1_NSS 未用
  8.   #define HC595_SCK_PIN GPIO_Pin_5 // SPI1_SCK SCLK
  9.   #define HC595_MISO_PIN GPIO_Pin_6 // SPI1_MISO 未用
  10.   #define HC595_MOSI_PIN GPIO_Pin_7 // SPI1_MOSI DIO
  11.   #define HC595_RCLK_PIN GPIO_Pin_12 // RCLK
  12.   // 端口
  13.   #define HC595_SPI1_GPIO GPIOA
  14.   #define HC595_RCLK_GPIO GPIOB
  15.   // 时钟
  16.   #define HC595_SPI1_RCC RCC_APB2Periph_GPIOA
  17.   #define HC595_RCLK_RCC RCC_APB2Periph_GPIOB
  18.   void HC595_Init(void);
  19.   void HC595_SendByte(u8 data);
  20.   u8 HC595_Display(u16 num, u8 dp);
  21.   #endif
  22.  
复制代码
  源文件74HC595_SPI.c
  1. /************************省略部分代码见(74HC595.c)************************/
  2.   /*******************************************************************************
  3.   * Function Name : HC595_Init
  4.   * Description : 初始化HC595
  5.   * Input : None
  6.   * Output : None
  7.   * Return : None
  8.   *******************************************************************************/
  9.   void HC595_Init(void)
  10.   {
  11.   GPIO_InitTypeDef GPIO_InitStructure;
  12.   SPI_InitTypeDef SPI_InitStructure; // 声明一个结构体变量
  13.   // 不需要开启AFIO时钟
  14.   RCC_APB2PeriphClockCmd(HC595_SPI1_RCC | HC595_RCLK_RCC |  RCC_APB2Periph_SPI1, ENABLE); // 使能HC595及SPI1的时钟
  15.   //74HC595, SPI1_NSS、SPI1_SCK、SPI1_MOSI
  16.   GPIO_InitStructure.GPIO_Pin = HC595_NSS_PIN | HC595_SCK_PIN  |HC595_MOSI_PIN;
  17.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 管脚频率为50MHZ
  18.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 输出模式为复用推挽输出
  19.   GPIO_Init(HC595_SPI1_GPIO, &GPIO_InitStructure); // 初始化寄存器
  20.   //74HC595, SPI1_MISO
  21.   GPIO_InitStructure.GPIO_Pin = HC595_MISO_PIN;
  22.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 输入模式为浮空输入
  23.   GPIO_Init(HC595_SPI1_GPIO, &GPIO_InitStructure); // 初始化寄存器
  24.   //74HC595, RCLK
  25.   GPIO_InitStructure.GPIO_Pin = HC595_RCLK_PIN;
  26.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 管脚频率为50MHZ
  27.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 输出模式为复用推挽输出
  28.   GPIO_Init(HC595_RCLK_GPIO, &GPIO_InitStructure); // 初始化寄存器
  29.   /* Initialize the SPI1 according to the SPI_InitStructure members */
  30.   SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  31.   // 第一步:设置主从模式和通信速率
  32.   SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  33.   // SPI_NSS_Hard时需要外部电路把NSS接VCC, SPI_NSS_Soft时SPI外设会将SSM和SSI置位
  34.   SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  35.   // 实测波特率最低为SPI_BaudRatePrescaler_8,否则出错
  36.   SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
  37.   // 第二步:设置数据格式
  38.   SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  39.   // MSB在前还是LSB在前要根据码表和数码管与74HC595的接法来定
  40.   SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  41.   // 第三步:设置时钟和极性
  42.   // 当SPI_CPOL_Low且SPI_CPHA_2Edge出错
  43.   SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
  44.   SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
  45.   //第四步:其它,CRC校验,可靠通信,这步可以不设置
  46.   SPI_InitStructure.SPI_CRCPolynomial = 7;
  47.   SPI_Init(SPI1, &SPI_InitStructure);
  48.   /* Enable SPI1 */
  49.   SPI_Cmd(SPI1, ENABLE);
  50.   }
  51.   /*******************************************************************************
  52.   * Function Name : HC595_SendByte
  53.   * Description : 发送一个字节
  54.   * Input : data
  55.   * Output : None
  56.   * Return : None
  57.   *******************************************************************************/
  58.   void HC595_SendByte(u8 data)
  59.   {
  60.   SPI_I2S_SendData(SPI1, data);
  61.   while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
  62.   }
  63.   /************************省略部分代码(见74HC595.c)************************/
复制代码
   这样就大工告成啦,STM32的SPI外设还是比较简单的,尤其是通过库函数来调用。用数码管模块这种简单的可视化工具,我们就可以更好的研究通信协议本身的特性啦,这种方式在后续学习其他的通讯协议也是可以的。
还有什么不明白的可以参考这个stm32 spi通信的资料stm32之SPI通信 -
0条回答

一周热门 更多>