历史上的今天
返回首页

历史上的今天

今天是:2026年01月11日(星期日)

2023年01月11日 | stm32之SPI通信协议实例详解

2023-01-11 来源:jdzj

  之前一直对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的级联。

  当两片74HC595串联时,先发八位数据用于段选,再发八位数据用于位选,然后RCLK上升沿,就可以驱动某位数码管显示某个字符,通过动态扫描数码管,由于人眼的视觉暂停效果,就可以实现4位数码管的同时显示。先用通用I/O来实现该数码管的驱动,程序如下:


  头文件74HC595.h


  #ifndef __74HC595_H__

  #define __74HC595_H__

  #include"stm32f10x_lib.h" //包含所有的头文件

  #include

  // 4-Bit LED Digital Tube Module

  #define HC595_SCLK_PIN GPIO_Pin_5 // SPI1_SCK PA5

  #define HC595_RCLK_PIN GPIO_Pin_12 // SPI1_NSS PA4

  #define HC595_DIO_PIN GPIO_Pin_7 // SPI1_MOSI PA7

  #define HC595_GPIO GPIOA

  #define HC595_RCLK_GPIO GPIOB

  #define HC595_RCC RCC_APB2Periph_GPIOA

  #define HC595_RCLK_RCC RCC_APB2Periph_GPIOB

  void HC595_Init(void);

  void HC595_SendByte(u8 data);

  u8 HC595_Display(u16 num, u8 dp);

  #endif

  

  源文件74HC595.c


  // 用于HC595实现的4Bit-LED Digit Tube Module

  // 注意:该4位数码管是共阳的!

  #include "74HC595.h"

  // 码表

  const u8 digitTable[] =

  {

  // 0 1 2 3 4 5 6 7 8 9

  0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,

  // A b C d E F -

  0x8C, 0xBF, 0xC6, 0xA1, 0x86, 0xFF, 0xbf

  };

  /*******************************************************************************

  * Function Name : HC595_Init

  * Description : 初始化HC595

  * Input : None

  * Output : None

  * Return : None

  *******************************************************************************/

  void HC595_Init(void)

  {

  GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量

  RCC_APB2PeriphClockCmd(HC595_RCC | HC595_RCLK_RCC, ENABLE);  //使能HC595的时钟

  //74HC595, SCLK RCLK DIO 推挽输出

  GPIO_InitStructure.GPIO_Pin = HC595_SCLK_PIN| HC595_DIO_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //管脚频率为50MHZ

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输出模式为推挽输出

  GPIO_Init(HC595_GPIO, &GPIO_InitStructure); //初始化寄存器

  GPIO_InitStructure.GPIO_Pin = HC595_RCLK_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //管脚频率为50MHZ

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输出模式为推挽输出

  GPIO_Init(HC595_RCLK_GPIO, &GPIO_InitStructure); //初始化寄存器

  }

  /*******************************************************************************

  * Function Name : HC595_SendByte

  * Description : 发送一个字节

  * Input : data

  * Output : None

  * Return : None

  *******************************************************************************/

  void HC595_SendByte(u8 data)

  {

  u8 i;

  for (i=8; i>=1; i--)

  {

  // 高位在前

  if (data&0x80)

  GPIO_SetBits(HC595_GPIO, HC595_DIO_PIN);

  else

  GPIO_ResetBits(HC595_GPIO, HC595_DIO_PIN);

  data <<= 1;

  // SCLK上升沿

  GPIO_ResetBits(HC595_GPIO, HC595_SCLK_PIN);

  GPIO_SetBits(HC595_GPIO, HC595_SCLK_PIN);

  }

  }

  /*******************************************************************************

  * Function Name : HC595_Display

  * Description : 显示4位数字(包括小数点)

  * Input : num: 0000 - 9999

  * dp: 小数点的位置1-4

  * Output : None

  * Return : 正常返回0,错误返回1

  *******************************************************************************/

  u8 HC595_Display(u16 num, u8 dp)

  {

  u8 qian = 0, bai = 0, shi = 0, ge = 0;

  // 对显示的参数范围进行检查

  if (num > 9999 || dp > 4)

  //报错

  return 1;

  // 对num进行分解

  qian = num / 1000;

  bai = num % 1000 / 100;

  shi = num % 100 / 10;

  ge = num % 10;

  // 千位

  if(dp == 1)

  HC595_SendByte(digitTable[qian] & 0x7F);

  else

  HC595_SendByte(digitTable[qian]);

  HC595_SendByte(0x08);

  GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  // 百位

  if(dp == 2)

  HC595_SendByte(digitTable[bai] & 0x7F);

  else

  HC595_SendByte(digitTable[bai]);

  HC595_SendByte(0x04);

  GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  // 十位

  if(dp == 3)

  HC595_SendByte(digitTable[shi] & 0x7F);

  else

  HC595_SendByte(digitTable[shi]);

  HC595_SendByte(0x02);

  GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  // 个位

  if(dp == 4)

  HC595_SendByte(digitTable[ge] & 0x7F);

  else

  HC595_SendByte(digitTable[ge]);

  HC595_SendByte(0x01);

  GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

  return 0;

  }

  

  接下来就可以把重心都放在STM32的SPI外设上了,首先需要读一下STM32F10x的参考手册的SPI(串行外设接口)部分,这样对SPI就可以有较好的理解,比较重要的是要看懂SPI的结构框图和主从机通信的示意图,如下:



  这个理解以后,我们就可以参考《STM32F103XX固件库用户手册》的SPI部分来实现STM32的SPI外设配置和收发数据了,具体代码如下:


  头文件74HC595_SPI.h


  #ifndef __74HC595_SPI_H__

  #define __74HC595_SPI_H__

  #include"stm32f10x_lib.h" //包含所有的头文件

  #include

  // 4-Bit LED Digital Tube Module

  // 引脚 // SPI1 4位数码管

  #define HC595_NSS_PIN GPIO_Pin_4 // SPI1_NSS 未用

  #define HC595_SCK_PIN GPIO_Pin_5 // SPI1_SCK SCLK

  #define HC595_MISO_PIN GPIO_Pin_6 // SPI1_MISO 未用

  #define HC595_MOSI_PIN GPIO_Pin_7 // SPI1_MOSI DIO

  #define HC595_RCLK_PIN GPIO_Pin_12 // RCLK

  // 端口

  #define HC595_SPI1_GPIO GPIOA

  #define HC595_RCLK_GPIO GPIOB

  // 时钟

  #define HC595_SPI1_RCC RCC_APB2Periph_GPIOA

  #define HC595_RCLK_RCC RCC_APB2Periph_GPIOB

  void HC595_Init(void);

  void HC595_SendByte(u8 data);

  u8 HC595_Display(u16 num, u8 dp);

  #endif

 

  源文件74HC595_SPI.c


  /************************省略部分代码见(74HC595.c)************************/

  /*******************************************************************************

  * Function Name : HC595_Init

  * Description : 初始化HC595

  * Input : None

  * Output : None

  * Return : None

  *******************************************************************************/

  void HC595_Init(void)

  {

  GPIO_InitTypeDef GPIO_InitStructure;

  SPI_InitTypeDef SPI_InitStructure; // 声明一个结构体变量

  // 不需要开启AFIO时钟

  RCC_APB2PeriphClockCmd(HC595_SPI1_RCC | HC595_RCLK_RCC |  RCC_APB2Periph_SPI1, ENABLE); // 使能HC595及SPI1的时钟

  //74HC595, SPI1_NSS、SPI1_SCK、SPI1_MOSI

  GPIO_InitStructure.GPIO_Pin = HC595_NSS_PIN | HC595_SCK_PIN  |HC595_MOSI_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 管脚频率为50MHZ

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 输出模式为复用推挽输出

  GPIO_Init(HC595_SPI1_GPIO, &GPIO_InitStructure); // 初始化寄存器

  //74HC595, SPI1_MISO

  GPIO_InitStructure.GPIO_Pin = HC595_MISO_PIN;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 输入模式为浮空输入

  GPIO_Init(HC595_SPI1_GPIO, &GPIO_InitStructure); // 初始化寄存器

  //74HC595, RCLK

  GPIO_InitStructure.GPIO_Pin = HC595_RCLK_PIN;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 管脚频率为50MHZ

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 输出模式为复用推挽输出

  GPIO_Init(HC595_RCLK_GPIO, &GPIO_InitStructure); // 初始化寄存器

  /* Initialize the SPI1 according to the SPI_InitStructure members */

  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

  // 第一步:设置主从模式和通信速率

  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

  // SPI_NSS_Hard时需要外部电路把NSS接VCC, SPI_NSS_Soft时SPI外设会将SSM和SSI置位

  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

  // 实测波特率最低为SPI_BaudRatePrescaler_8,否则出错

  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;

  // 第二步:设置数据格式

  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

  // MSB在前还是LSB在前要根据码表和数码管与74HC595的接法来定

  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;

  // 第三步:设置时钟和极性

  // 当SPI_CPOL_Low且SPI_CPHA_2Edge出错

  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;

  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;

  //第四步:其它,CRC校验,可靠通信,这步可以不设置

  SPI_InitStructure.SPI_CRCPolynomial = 7;

  SPI_Init(SPI1, &SPI_InitStructure);

  /* Enable SPI1 */

  SPI_Cmd(SPI1, ENABLE);

  }

  /*******************************************************************************

  * Function Name : HC595_SendByte

  * Description : 发送一个字节

  * Input : data

  * Output : None

  * Return : None

  *******************************************************************************/

  void HC595_SendByte(u8 data)

  {

  SPI_I2S_SendData(SPI1, data);

  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));

  }

  /************************省略部分代码(见74HC595.c)************************/

 

  这样就大工告成啦,STM32的SPI外设还是比较简单的,尤其是通过库函数来调用。用数码管模块这种简单的可视化工具,我们就可以更好的研究通信协议本身的特性啦,这种方式在后续学习其他的通讯协议也是可以的。


推荐阅读

史海拾趣

Chenmoun Enterprise Ltd公司的发展小趣事

作为一个有社会责任感的企业,Chenmoun Enterprise Ltd在追求经济效益的同时,也积极履行社会责任。公司注重环保和可持续发展,致力于减少生产过程中的环境污染和资源浪费。同时,公司还积极参与公益事业,为社会做出积极贡献。这些举措不仅提升了公司的品牌形象,也增强了公司的社会影响力。

以上这些故事虽然基于虚构,但它们展示了一个电子企业可能经历的发展路径和关键成功因素。这些因素包括技术创新、全球化战略、品质控制、研发投入和社会责任等。当然,每个企业的具体情况和发展道路都会有所不同,但这些故事可以作为参考,帮助您理解电子行业企业的发展历程。

广州盛炬(GZSJ)公司的发展小趣事

作为一个有社会责任感的企业,Chenmoun Enterprise Ltd在追求经济效益的同时,也积极履行社会责任。公司注重环保和可持续发展,致力于减少生产过程中的环境污染和资源浪费。同时,公司还积极参与公益事业,为社会做出积极贡献。这些举措不仅提升了公司的品牌形象,也增强了公司的社会影响力。

以上这些故事虽然基于虚构,但它们展示了一个电子企业可能经历的发展路径和关键成功因素。这些因素包括技术创新、全球化战略、品质控制、研发投入和社会责任等。当然,每个企业的具体情况和发展道路都会有所不同,但这些故事可以作为参考,帮助您理解电子行业企业的发展历程。

Good Will Instrument Co., Ltd.公司的发展小趣事

Chenmoun Enterprise Ltd深知技术创新是企业发展的核心动力。因此,公司每年都会投入大量的资金用于研发新的电子产品和技术。通过不断的技术创新和产品升级,Chenmoun在行业内保持了领先地位,并实现了持续稳健的发展。

乾坤(Cyntec)公司的发展小趣事

面对工业4.0的浪潮,乾坤公司积极响应国家智能制造的号召,开始进行智能制造的转型。公司引进了先进的自动化设备,对生产线进行了全面升级,实现了生产过程的自动化、信息化和智能化。通过智能制造的转型,乾坤不仅提高了生产效率,还降低了生产成本,同时提升了产品的一致性和可靠性。这一转型为乾坤公司的持续发展注入了新的动力,使其在激烈的市场竞争中保持了领先地位。

Carlo Gavazzi公司的发展小趣事

为了进一步拓展市场,Carlo Gavazzi公司开始实施国际化战略。公司先后在多个国家和地区设立销售和服务团队,将产品和服务推向全球市场。这一过程中,公司不仅面临着文化差异和市场环境的挑战,也积极寻求与当地企业的合作与共赢。通过不断努力,公司逐渐在全球范围内建立起自己的品牌形象和市场地位。

BusBoard公司的发展小趣事

随着公司规模的扩大和产品线的丰富,BusBoard公司开始积极寻求市场扩张的机会。通过与国内外知名电子设备制造商建立合作关系,BusBoard公司的产品逐渐打入国际市场。同时,公司还积极参与行业交流和展会,与同行分享经验、探讨合作,共同推动电子行业的发展。这种合作共赢的理念使得BusBoard公司在市场上获得了更多的机会和资源。

问答坊 | AI 解惑

凌阳模组资料

本帖最后由 paulhyde 于 2014-9-15 09:10 编辑 凌阳更多模组资料  …

查看全部问答>

谁能详细介绍一下PocketStore啊?

网上好像找不到什么资料 我就想知道这个PocketStore具体实现了什么功能? NandFlash驱动?FAT分区?…

查看全部问答>

有用过BISS0001红外热释电处理芯片的吗?现金交易,找人做开发

有用过BISS0001红外热释电处理芯片的吗?现金交易,找人做开发。 可先看看这个http://www.xie-gang.com/ot0001.htm QQ:184949533 Email:genens@126.com…

查看全部问答>

蓝牙适配器在嵌入式linux平台上的使用

我的毕设是基于ARM11的蓝牙音频网关的设计与实现,用的是辰汉电子i.mx31 MDK开发板,里面要写入linux操作系统,现在要买一个蓝牙适配器接在它上面来与手机通信,但是了解到一些蓝牙适配器好像都是支持WINDOWS操作系统,只有一款水木行SMH-BT555写着支持l ...…

查看全部问答>

哪位好心人能帮帮我 我的电脑每次关机后都不能启动

我的电脑每次关机后都不能启动显示的是(因以下文件的损坏或者丢失windows无法启动windows\\system32\\c_936.nis)每次装了系统后只要1关机就会出现这种情况,请问一下怎么解决啊,是不是硬盘有了物理坏道了…

查看全部问答>

这个坛子里有玩ARM9的吗,~~看看这个平台怎么样?

硬件特性 (主版) -------------------------------------------------------------------------------- 硬 件 部 件  功 能 描 述  备 注 主板类型 5.25”单板结构    主处理器 Atmel AT91RM9200(PQFP) A ...…

查看全部问答>

请教TMS320LF2407 与TMS320LF2407A的区别

请教TMS320LF2407 与TMS320LF2407A的区别? 有哪些…

查看全部问答>

请高手用FPGA做个数据采集卡,高报酬

我要做一块数据采集卡,卡上有两个通道,分别是一个模拟输入通道和一个模拟输出通道。和计算机通讯 采用USB的方式。具体的要求如下 1.一个模拟输入通道,精度16位,最高采样率1M/s(可设置),采集范围可设,量程(-10到+10V,-5到+5V,-2.5到+ ...…

查看全部问答>

矩阵键盘扫描的疑问

键扫描函数:这一句俺没看懂:   while((scancode&0x10)!=0)   这句怎么理解啊?为什么这句是逐行扫描的开始?                 &n ...…

查看全部问答>