通讯基本的概念
串行通讯与并行通讯
按数据传送的方式,通讯可分为串行通讯与并行通讯,串行通讯是指按数据位形式一位一位地传输数据的通讯方式。并行通讯一般是指以同时传输多个数据位的数据通讯方式。
全双工、半双工及单工通讯
通讯方式 | 说明 |
---|---|
全双工 | 在同一时刻,两个设备之间可以同时收发数据 |
半双工 | 两个设备之间可以收发数据,但不能在同一个时刻进行 |
单工 | 在任何时刻都只能进行一个方向的通讯,即一个固定为发送设备,另一个固定为接收设备 |
同步通讯与异步通讯
根据通讯的数据同步方式,又分为同步和异步两种,可以根据通讯过程中是否有使用到时钟信号进行简单的区分。
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 进行数据收发。