stm32四大通讯方式 串口 iic spi can

本文深入探讨了嵌入式系统中的各种通信方式,包括串行、并行、全双工、半双工、同步和异步通讯,以及USART、IIC、SPI和CAN等常见通讯协议的工作原理和编程实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通讯基本的概念

串行通讯与并行通讯

按数据传送的方式,通讯可分为串行通讯与并行通讯,串行通讯是指按数据位形式一位一位地传输数据的通讯方式。并行通讯一般是指以同时传输多个数据位的数据通讯方式。

全双工、半双工及单工通讯

通讯方式说明
全双工在同一时刻,两个设备之间可以同时收发数据
半双工两个设备之间可以收发数据,但不能在同一个时刻进行
单工在任何时刻都只能进行一个方向的通讯,即一个固定为发送设备,另一个固定为接收设备

同步通讯与异步通讯

根据通讯的数据同步方式,又分为同步和异步两种,可以根据通讯过程中是否有使用到时钟信号进行简单的区分。

USART—串口通讯

可以实现两个设备之间的通讯

编程代码

串口初始化函数
void Debug_USART_Config(void)
{
 GPIO_InitTypeDef GPIO_InitStructure;
 USART_InitTypeDef USART_InitStructure;
 /* 使能 USART GPIO 时钟 */
 RCC_AHB1PeriphClockCmd(DEBUG_USART_RX_GPIO_CLK |
 DEBUG_USART_TX_GPIO_CLK,
 ENABLE);

 /* 使能 USART 时钟 */
 RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);
 
 /* 连接 PXx 到 USARTx_Tx*/
 GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,
 DEBUG_USART_RX_SOURCE,
 DEBUG_USART_RX_AF);
 
 /* 连接 PXx 到 USARTx__Rx*/
 GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,
 DEBUG_USART_TX_SOURCE,
 DEBUG_USART_TX_AF);
 
 /* GPIO 初始化 */
 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 
 /* 配置 Tx 引脚为复用功能 */
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN ;
 GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
 
/* 配置 Rx 引脚为复用功能 */
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
 GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
 
 /* 配置串 DEBUG_USART 模式 */
 /* 波特率设置:DEBUG_USART_BAUDRATE */
 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
 /* 字长(数据位+校验位):8 */
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;
 /* 停止位:1 个停止位 */
 USART_InitStructure.USART_StopBits = USART_StopBits_1;
 /* 校验位选择:不使用校验 */
 USART_InitStructure.USART_Parity = USART_Parity_No;
 /* 硬件流控制:不使用硬件流 */
 USART_InitStructure.USART_HardwareFlowControl =
 USART_HardwareFlowControl_None;
 /* USART 模式控制:同时使能接收和发送 */
 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 /* 完成 USART 初始化配置 */
 USART_Init(DEBUG_USART, &USART_InitStructure);
 
 /* 嵌套向量中断控制器 NVIC 配置 */
 NVIC_Configuration();


 /* 使能串口接收中断 */
 USART_ITConfig(DEBUG_USART, USART_IT_RXNE, ENABLE);
 
/* 使能串口 */
USART_Cmd(DEBUG_USART, ENABLE);
 }
发送函数
 /***************** 发送一个字符 **********************/
 void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
 {
 /* 发送一个字节数据到 USART */
 USART_SendData(pUSARTx,ch);

 /* 等待发送数据寄存器为空 */
 while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
 }
 /***************** 发送字符串 **********************/
 void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
 {
 unsigned int k=0;
 do {
 Usart_SendByte( pUSARTx, *(str + k) );
 k++;
 } while (*(str + k)!='\0');
 
 /* 等待发送完成 */
 while (USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET) {
 }
 }
中断服务函数

在中断函数中通过判断标志位来判断是什么中断。

void DEBUG_USART_IRQHandler(void)
{
 uint8_t ucTemp;
 if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET) {
 ucTemp = USART_ReceiveData( DEBUG_USART );
 USART_SendData(DEBUG_USART,ucTemp);
 }
 }

编程总结

使用初始化函数初始化以后就可以直接使用发送函数发送数据,根据需要编写中断函数。

IIC通讯

IIC通讯可以实现多个设备之间的半双工通讯

发送流程

stm32 iic 可以工作在其下四种模式之一:
● 从发送器
● 从接收器
● 主发送器
● 主接收器
默认情况下,它以从模式工作。接口在生成起始位后会自动由从模式切换为主模式,并在出现仲裁丢失或生成停止位时从主模式切换为从模式,从而实现多主模式功能。

主发送器

图中的是“主发送器”流程,即作为 I2C 通讯的主机端时,向外发送数据
时的过程
在这里插入图片描述
主发送器发送流程及事件说明如下:
(1) 控制产生起始信号(S),当发生起始信号后,它产生事件“EV5”,并会对 SR1 寄
存器的“SB”位置 1,表示起始信号已经发送;
(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”及
“EV8”,这时 SR1 寄存器的“ADDR”位及“TXE”位被置 1,ADDR 为 1 表
示地址已经发送,TXE 为 1 表示数据寄存器为空;
(3) 以上步骤正常执行并对 ADDR 位清零后,我们往 I2C 的“数据寄存器 DR”写入
要发送的数据,这时 TXE 位会被重置 0,表示数据寄存器非空,I2C 外设通过
SDA 信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,
重复这个过程,就可以发送多个字节数据了;
(4) 当我们发送数据完成后,控制 I2C 设备产生一个停止信号§,这个时候会产生
EV2 事件,SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。
假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一
个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来了解是哪一个事件。

主接收器

再来分析主接收器过程,即作为 I2C 通讯的主机端时,从外部接收数据的过程,见图

在这里插入图片描述
主接收器接收流程及事件说明如下:
(1) 同主发送流程,起始信号(S)是由主机端产生的,控制发生起始信号后,它产生事
件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”这时
SR1 寄存器的“ADDR”位被置 1,表示地址已经发送。
(3) 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产
生“EV7”事件,SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空,我们
读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控
制 I2C 发送应答信号(ACK)或非应答信号(NACK),若应答,则重复以上步骤接收
数据,若非应答,则停止传输;
(4) 发送非应答信号后,产生停止信号§,结束传输。
在发送和接收过程中,有的事件不只是标志了我们上面提到的状态位,还可能同时标
志主机状态之类的状态位,而且读了之后还需要清除标志位,比较复杂。我们可使用
STM32 标准库函数来直接检测这些事件的复合标志,降低编程难度。

编程代码

gpio初始化函数
 void I2C_GPIO_Config(void)
{
 GPIO_InitTypeDef GPIO_InitStructure;
 
 /*使能 I2C 外设时钟 */
 RCC_APB1PeriphClockCmd(EEPROM_I2C_CLK, ENABLE);
 
 /*使能 I2C 引脚的 GPIO 时钟*/
 RCC_AHB1PeriphClockCmd(EEPROM_I2C_SCL_GPIO_CLK |
 EEPROM_I2C_SDA_GPIO_CLK, ENABLE);
 
 /* 连接引脚源 PXx 到 I2C_SCL*/
 GPIO_PinAFConfig(EEPROM_I2C_SCL_GPIO_PORT, EEPROM_I2C_SCL_SOURCE,
 EEPROM_I2C_SCL_AF);
 /* 连接引脚源 PXx 到 to I2C_SDA*/
 GPIO_PinAFConfig(EEPROM_I2C_SDA_GPIO_PORT, EEPROM_I2C_SDA_SOURCE,
 EEPROM_I2C_SDA_AF);
 
 /*配置 SCL 引脚 */
 GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIO_Init(EEPROM_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
 
 /*配置 SDA 引脚 */
 GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_PIN;
 GPIO_Init(EEPROM_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);
 }
模式配置函数
 I2C_Mode_Config(void)
 {
 I2C_InitTypeDef I2C_InitStructure;
 
 /* I2C 配置 */
 /*I2C 模式*/
 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
 /*占空比*/
 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
 /*I2C 自身地址*/
 I2C_InitStructure.I2C_OwnAddress1 =I2C_OWN_ADDRESS7;
 /*使能响应*/
 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
 /* I2C 的寻址模式 */
 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
 /* 通信速率 */
 I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
 /*写入配置*/
 I2C_Init(EEPROM_I2C, &I2C_InitStructure);
 /* 使能 I2C */
 I2C_Cmd(EEPROM_I2C, ENABLE);
 }

初始化函数
 void I2C_EE_Init(void)
{
 	I2C_GPIO_Config(); 
	I2C_Mode_Config();
}
产生信号函数
/* 产生 I2C 起始信号 */
 I2C_GenerateSTART(EEPROM_I2C, ENABLE);
事件检测
I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)
stm32f4xx_i2c.c源文件中有所有事件的对应参数


发送函数
 /* 发送 EEPROM 设备地址,通过第三个参数确定读写 */
 I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS,
 I2C_Direction_Transmitter);
 /* 发送一个字节 */
 I2C_SendData(EEPROM_I2C, WriteAddr);
发送字节函数
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
{
  /* Send STRAT condition */
  I2C_GenerateSTART(EEPROM_I2C, ENABLE);

  I2CTimeout = I2CT_FLAG_TIMEOUT;

  /* Test on EV5 and clear it */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
  }    

  /* Send EEPROM address for write */
  I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  /* Test on EV6 and clear it */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
  }    
      
  /* Send the EEPROM's internal address to write to */
  I2C_SendData(EEPROM_I2C, WriteAddr);
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;

  /* Test on EV8 and clear it */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))  
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
  } 
  /* Send the byte to be written */
  I2C_SendData(EEPROM_I2C, *pBuffer); 
   
  I2CTimeout = I2CT_FLAG_TIMEOUT;

  /* Test on EV8 and clear it */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
  } 
  
  /* Send STOP condition */
  I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
  
  return 1;
}
编程总结

初始化iic后,可以使用字节发送函数直接发送数据,也可以对照时序图调用固件库函数。

spi通信

是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。

spi 物理层

在这里插入图片描述
SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为 NSS

spi基本通讯过程

在这里插入图片描述
这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

spi的四种通讯模式

在这里插入图片描述

spi相关代码

spi引脚配置

 void SPI_FLASH_Init(void)
 {
 GPIO_InitTypeDef GPIO_InitStructure;
 
 /* 使能 FLASH_SPI 及 GPIO 时钟 */
 /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,
 SPI_FLASH_SPI_MISO_GPIO 和 SPI_FLASH_SPI_SCK_GPIO 时钟使能 */
 RCC_AHB1PeriphClockCmd (FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK|
 FLASH_SPI_MOSI_GPIO_CLK|FLASH_CS_GPIO_CLK, ENABLE);
 
 /*!< SPI_FLASH_SPI 时钟使能 */
 FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE);
 
 //设置引脚复用
 GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT,FLASH_SPI_SCK_PINSOURCE,
 FLASH_SPI_SCK_AF);
 GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT,FLASH_SPI_MISO_PINSOURCE,
 FLASH_SPI_MISO_AF);
 GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT,FLASH_SPI_MOSI_PINSOURCE,
 FLASH_SPI_MOSI_AF);
 
 /*!< 配置 SPI_FLASH_SPI 引脚: SCK */
 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
 
 GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
 
 /*!< 配置 SPI_FLASH_SPI 引脚: MISO */
 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
 GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
 
 /*!< 配置 SPI_FLASH_SPI 引脚: MOSI */
 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
 GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
 
 /*!< 配置 SPI_FLASH_SPI 引脚: CS */
 GPIO_InitStructure.GPIO_Pin = FLASH_CS_PIN;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
 GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStructure);
 
 /* 停止信号 FLASH: CS 引脚高电平*
 /*为方便讲解,以下省略 SPI 模式初始化部分*/
 //......
 }

spi模式配置

void SPI_FLASH_Init(void)
{
 /*为方便讲解,省略了 SPI 的 GPIO 初始化部分*/
 //......
 
 SPI_InitTypeDef SPI_InitStructure;
 /* FLASH_SPI 模式配置 */
 // FLASH 芯片 支持 SPI 模式 0 及模式 3,据此设置 CPOL CPHA
 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
 SPI_InitStructure.SPI_CRCPolynomial = 7;
 SPI_Init(FLASH_SPI, &SPI_InitStructure);
 
 /* 使能 FLASH_SPI */
 SPI_Cmd(FLASH_SPI, ENABLE);
 }

spi发送字节函数

 u8 SPI_FLASH_SendByte(u8 byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
 
 /* 等待发送缓冲区为空,TXE 事件 */
 while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET)
 {
 if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
 }
 
 /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
 SPI_I2S_SendData(FLASH_SPI, byte);
 
 SPITimeout = SPIT_FLAG_TIMEOUT;
 
 /* 等待接收缓冲区非空,RXNE 事件 */
 while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET)
 {
 if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
 }
 
 /* 读取数据寄存器,获取接收缓冲区数据 */
 return SPI_I2S_ReceiveData(FLASH_SPI);
 }

spi接收字节函数

 u8 SPI_FLASH_ReadByte(void)
 {
 return (SPI_FLASH_SendByte(Dummy_Byte));
 }

编程总结

使用初始化函数初始化后,调用发送接收函数即可发送或接收。

can通讯

CAN 是控制器局域网络(Controller Area Network)的简称,是国际上应用
最广泛的现场总线之一。

报文

帧用途
数据帧用于节点向外传送数据
遥控帧用于向远端节点请求数据
错误帧用于向远端节点通知校验错误,请求重新发送上一个数据
过载帧用于通知远端节点:本节点尚未做好接收准备
帧间隔用于将数据帧及遥控帧与前面的帧分离开来
can的仲裁是通过总线的线与功能实现的

stm32 can

不支持使用 DMA 进行数据收发。

工作模式在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值