目录
一、通讯的基本概念
1.串行通讯
逐位传输,传输线少,适合远距离传输,抗干扰能力强,传输速率慢,可以分为:同步串行,异步串行
2.并行通讯
多位数据同时传输,传输速度快,传输线多,适合近距离传输,抗干扰能力强
3.传输模式(单工、半双工、全双工)
二、常见通讯协议(串口、IIC、SPI)
1.串口
串口通讯是一种全双工的串行通讯方式,主要可以分为UART(universal asynchronous receiver and transmitter通用异步收/发器)和USART(universal synchronous asynchronous receiver and transmitter通用同步/异步/收/发器)
(1)UART和USART的区别是什么?
从名字上看USART比UART增加了同步通信的功能,USART多提供了主动时钟。
后续主要讨论UART(串口异步时钟通讯协议),后续将UART统一简称为串口
(2)UART(TTL、RS232、RS485)
①按照电平标准区分:
TTL电平:3.3/5V表示1,0V表示0V表示0
RS232:-3~-15V表示1,+3~+15V
RS485:两线压差+2~+6表示1,-2~-6表示0(差分信号)
在实际应用中一般只使用到TXD,RXD,GND三根信号线
②协议层
串口发送一个字节(8位)数据的格式,如下图所示,分为有校验位和无校验位,每一个字节都装载在一个数据帧中间,每个数据帧由起始位、数据位、停止位三个部分组成。
无校验位
有校验位
③串口参数
波特率:串口的通信速率(每秒钟传送二进制数据的位数),bit/s(bps)为单位,常用的波特率有:9600、19200、38400、57600和115200,如果波特率是115200,则代表每一位数据在线上持续的时间为1/115200=8.68us
起始位:一个数据帧的开始,固定为低电平。不传输数据时, UART 数据传输线通常保持高电平。开始数据传输,发送UART 会将传输线电压从高拉低,并保持1 位时间。
停止位:一个数据帧的结束,固定为高电平。为了表示数据包结束,发送 UART 将数据传输线电压从低拉高并保持1 到 2 时间。
④校验位
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、 1 校验(mark)以及无校验(noparity)。
奇偶校验:奇校验表示数据中“1”的个数与校验位“1”的个数之和为奇数;偶校验表示数据中“1”的个数与校验位“1”的个数之和为偶数。
代码和校验:发送方将所发数据块求和,产生一个字节的校验字符附加到数据块末尾。接收方采用同样方式进行检测
循环冗余校验:通过某种数学运算实现有效信息与校验位之间的循环校验,常用于磁盘信息的传输、存储区的完整性校验等。
(3)基于STM32的HAL库的串口配置
// UART1 初始化函数
void MX_USART1_UART_Init(void) {
// 配置USART1句柄的实例和初始化参数
huart1.Instance = USART1; // 选择USART1外设
huart1.Init.BaudRate = 9600; // 设置波特率为9600
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 设置数据位为8位
huart1.Init.StopBits = UART_STOPBITS_1; // 设置停止位为1位
huart1.Init.Parity = UART_PARITY_NONE; // 设置无奇偶校验
huart1.Init.Mode = UART_MODE_TX_RX; // 使能发送和接收模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 不使用硬件流控制
huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 设置过采样为16
//过采样率为16的意思为每个比特会被采样 16 次。在每个比特周期内,这些采样值会被用来判断该比特的实际值。
}
2.IIC
IIC(Inter-Integrated Circuit)是一种2线式同步半双工串行通信方式,用以连接控制器和气周围设备,多用于主从之间的通信。适用于小数据量和短距离的场合。
(1)物理层
IIC一共只有两个总线,一条是双向的串行数据线SDA(用来传输数据),一条是串行时钟线SCL(控制数据发送的时序的)。
主从设备的区分:控制时钟线(即控制SCL的电平高低变换)的就是主设备。
为了避免总线信号的混乱,设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出,其可独立输入输出低电平和高阻状态,若要产生高电平,则需要外部上拉电阻,空闲设备被拉到了高阻态,就相当于断路,其他开启了的设备正常通信。
(2)协议层
IIC总线在传送数据的过程中总共有三种类型信号:分别是:开始信号,结束信号,应答信号。如下图所示:
开始信号(START):当SCL为高电平,SDA由高变低,则开始传送数据
结束信号(STOP):当SCL为高电平,SDA由低变高,则结束传送数据
数据位传送(DATA):在SCL保持高电平期间,SDA上的电平保持稳定,低电平为数据0、高电平为数据1
应答信号(ACK):主机SCL拉高,读取从机SDA的电平,若为低电平表示应答。
非应答信号(NACK):主机SCL拉高,读取从机SDA的电平,若为高电平表示非应答。
应答信号出现在一个字节(8bit)传输后的第9位,0为应答,1为非应答
起始信号:
//起始信号
void IIC_Start(void)
{
SDA_H; //拉高SDA线
SCL_H; //拉高SCL线
DelayUs(iicInfo.speed); //延时,速度控制
SDA_L; //当SCL线为高时,SDA线一个下降沿代表开始信号
DelayUs(iicInfo.speed); //延时,速度控制
SCL_L; //钳住SCL线,以便发送数据
}
停止信号
//停止信号
void IIC_Stop(void)
{
SDA_L; //拉低SDA线
SCL_L; //拉低SCL先
DelayUs(iicInfo.speed); //延时,速度控制
SCL_H; //拉高SCL线
SDA_H; //拉高SDA线,当SCL线为高时,SDA线一个上升沿代表停止信号
DelayUs(iicInfo.speed);
}
应答信号
//应答信号
//产生ACK应答,读取从机一字节数据后还要接着读的时候使用
//SCL在SDA一直为低电平期间完成低高电平转换
void IIC_Ack(void)
{
SCL_L; //拉低SCL线
SDA_L; //拉低SDA线<----
DelayUs(iicInfo.speed);
SCL_H; //拉高SCL线
DelayUs(iicInfo.speed);
SCL_L; //拉低SCL线
}
//不产生ACK应答,读取从机一字节数据后不读了的时候使用
//SCL在SDA一直为高电平期间完成低高电平转换
void IIC_NAck(void)
{
SCL_L; //拉低SCL线
SDA_H; //拉高SDA线<----
DelayUs(iicInfo.speed);
SCL_H; //拉高SCL线
DelayUs(iicInfo.speed);
SCL_L; //拉低SCL线
}
//等待ACK应答
//发送完一个字节后(释放SDA)的下一个时钟高电平时期,读取SDA电平,0为收到应答
_Bool IIC_WaitAck(unsigned int timeOut)
{
SDA_H;DelayUs(iicInfo.speed); //拉高SDA线
SCL_H;DelayUs(iicInfo.speed); //拉高SCL线
while(SDA_R) //如果读到SDA线为1,则等待。应答信号应是0
{
if(--timeOut)
{
printf("WaitAck TimeOut\r\n");
IIC_Stop(); //超时未收到应答,则停止总线
return IIC_Err; //返回失败
}
DelayUs(iicInfo.speed);
}
SCL_L; //拉低SCL线,以便继续收发数据
return IIC_OK; //返回成功
}
发送数据
void IIC_Send(unsigned char byte)
{
unsigned char count = 0;
SCL_L; //拉低时钟开始数据传输
for(; count < 8; count++) //循环8次,每次发送一个bit
{
if(byte & 0x80) //【先发送最高位,大端传输】
SDA_H;
else
SDA_L;
byte <<= 1; //byte左移1位
DelayUs(iicInfo.speed);
SCL_H;
DelayUs(iicInfo.speed);
SCL_L;
}
}
接收数据
unsigned char IIC_Read(void)
{
unsigned char count = 0, receive = 0;
SDA_H; //拉高SDA线,开漏状态下,需线拉高以便读取数据
for(; count < 8; count++ ) //循环8次,每次发送一个bit
{
SCL_L; //拉低,从机放数据
DelayUs(iicInfo.speed);
SCL_H; //拉高,主机读数据
receive <<= 1; //左移一位
if(SDA_R) //如果SDA线为1,则receive变量自增,每次自增都是对bit0的+1,然后下一次循环会先左移一次
receive++;
DelayUs(iicInfo.speed);
}
return receive;
}
(3)软件模拟IIC通讯代码
①写数据
- 字节写
void IIC_SEND_BYTE(u8 slaveaddr,u8 registeraddr,u8 byte);//发送到具体从机地址及相关寄存器一个字节
{
IIC_Start(); //通讯开始
IIC_Send(slaveaddr); //先发送从机的地址,寻址从机
IIC_WaitAck(); //得到回应,说明,电路中有这个外设器件
IIC_Send(registeraddr);//寻址这个器件中的相关寄存器
IIC_WaitAck(); //得到回应,说明这个寄存器存在
IIC_Send(byte); //发送一个字节
IIC_WaitAck(); //等待回应
}
- 页写
void IIC_SEND_BYTES(u8 slaveaddr,u8 registeraddr,u8 *pbuffer,u16 num );//发送一串数据给具体从机的相关寄存器
{
IIC_Start(); //开始通讯
IIC_Send(slaveaddr); //寻址从机
IIC_WaitAck(); //等待回应
IIC_Send(registeraddr); //寻址寄存器
IIC_WaitAck(); //等待回应
for(t=0;t<num;t++) //发送数组中的数据
{
IIC_Send(*(pbuffer+t));
IIC_WaitAck(); //每发送一个字节,都需要一个应答
}
IIC_Stop(); //终止通讯
}
②读数据
void I2C_READ_BYTES(u8 slaveaddr,u8 registeraddr,u8 *pbuffer,u16 num );//读取具体从机的相关寄存器一串字节
{
I2C_Start(); //开始通讯
I2C_Send(slaveaddr); //寻址从机
I2C_WaitAck(); //等待回应
I2C_Send(registeraddr);//寻址寄存器
I2C_WaitAck(); //等待回应
I2C_Start(); //重新通讯
I2C_Send(slaveaddr+1); //改为读数据
I2C_WaitAck(); //等待回应
for(t=0;t<num;t++) //存储数据
{
*(pbuffer+t)=I2C_Read();
if (t== num-1) //字节没发完,必须给出应答,发完的给个非应答信号
{
IIC_Ack();
}
else
{
IIC_NAck();
}
}
IIC_Stop(); //通讯结束
}
(4)有关IIC面试的问题
①IIC总线的仲裁了解多少?
I2C总线具有多主控能力,有时会发生两个或多个主器件同时想占用总线的情况,这种情况叫做总线竞争。
I2C可以对发生在SDA线上的总线竞争进行仲裁,I2C总线的仲裁逻辑是建立在线与功能上的,有一个拉低SDA总线就是低。
其仲裁原则是这样的:每一个主器件每次发送一位数据,然后比较总线上所呈现的电平与自己所发送的数据是否一致,如果一致,继续发送下一位数据,否则就退出竞争。
总线竞争的仲裁在两个层次上进行:首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位比较,从而确保竞争仲裁的可靠性。由于是利用IIC总线上的信息进行仲裁,不会造成信息的丢失。
②I2C时钟信号(SCL)的同步问题
在I2C总线上传送信息时的时钟同步信号是由挂接在SCL线上的所有器件的逻辑“与”完成的。SCL低电平时间由时钟低电平期最长的器件确定,而时钟高电平时间由时钟高电平期最短的器件确定。
③当通过IIC配置外设失败时,可以从以下几种角度出发进行排查:
- 硬件连接问题:检查IIC总线的物理连接是否正确。确保SDA(数据线)和SCL(时钟线)正确连接到外设和主控制器,并且没有短路或断开的情况。还要确保上拉电阻的数值和位置正确。
- 电源供电问题:确认外设和主控制器都正常供电,并检查电源电压是否在可接受范围内。低电压可能导致通信错误或不稳定。
- 地址设置问题:检查外设的地址设置是否正确。确保外设的IIC地址与要求的地址匹配。有些外设具有可编程地址,因此需要验证是否正确设置了地址。
- IIC协议参数设置问题:确保主控制器和外设之间的IIC协议参数设置一致,如速率、传输模式等。确认主控制器发送的start bit、stop bit等控制信号正确地驱动外设。
- 软件程序问题:检查软件代码是否正确实现了IIC通信协议。确认发送和接收的数据格式、顺序、时序是否符合规范。还要确保正确处理ACK(应答)信号和超时等异常情况。
- 外设工作状态问题:检查外设自身是否正常工作。对于某些外设,可能需要进行初始化或配置才能正常通信。确保外设处于正确的模式或状态
(5)硬件IIC和软件IIC
软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。软件iic一般可以方便移植到任何设备之下,则可按照上述的代码进行设计
硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。基于STM32的硬件IIC的配置如下:
// I2C 外设的初始化函数
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1; // 选择 I2C1 外设实例
hi2c1.Init.ClockSpeed = 100000; // 设置 I2C 时钟频率为 100 kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 设置占空比为 2 (标准模式下)
hi2c1.Init.OwnAddress1 = 0; // 本设备地址设置为 0(此设备为主机,不使用地址)
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 设置地址模式为 7 位
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 禁用双地址模式
hi2c1.Init.OwnAddress2 = 0; // 双地址模式下的第二个地址设置为 0
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 禁用一般调用模式
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 禁用不拉伸模式
HAL_I2C_Init(&hi2c1); // 初始化 I2C 外设并应用配置
}
3.SPI
串行外设接口(Serial Peripheral Interface,SPI)是一种高速、全双工、同步通信的串行接口。
(1)物理层
MISO ( Master Input Slave Output ) : 主设备数据输入,从设备数据输出;
MOSI ( Master Output Slave Input ) : 主设备数据输出,从设备数据输入;
SCLK ( Serial Clock ) : 时钟信号,由主设备产生;
CS/SS ( Chip Select/Slave Select ) : 从设备片选信号,由主设备控制,通常低电平有效
(2)协议层
①通讯的起始和停止信号
CS/SS信号由高变低,是SPI通讯的起始信号,CS/SS信号由低变高,是SPI通讯的停止信号
②数据有效性
在SCK信号的同步下,一个时钟周期内。数据的输入输出同时进行,如上图所示,在SCK上升沿,MOSI和MISO信号变化输出,在SCK下降沿, MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”,在其它时刻,数据无效。
③CPOL/CPHA 及通讯模式
SPI 一共有四种通讯模式,主要区别为:总线空闲时 SCK 的时钟状态、数据采样时刻
时钟极性 CPOL :是指 SPI 通讯设备处于空闲状态时, CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,SCK 在空闲状态时为高电平。
时钟相位 CPHA :是指数据的采样的时刻,当 CPHA=0 时, MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。
(3)基于STM32的HAL库如何配置
void SPI1_Init(void)
{
// SPI1 外设初始化结构体
hspi1.Instance = SPI1; // 选择 SPI1 外设
hspi1.Init.Mode = SPI_MODE_MASTER; // 设为主模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 双线单工模式(全双工)
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8 位数据宽度
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟极性低
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 时钟相位在第一个边沿采样
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制 NSS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 波特率分频系数
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 数据位先传送最高有效位
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // 禁用 TI 模式
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用 CRC 校验
hspi1.Init.CRCPolynomial = 10; // CRC 多项式,未启用时设置为任意值
}
参考以下内容:
常见的通讯协议总结(USART、IIC、SPI、485、CAN)-CSDN博客
STM32——关于USART的讲解与应用(一)(看完这篇你就懂了)-CSDN博客
【接口协议】04.IIC - 牛客网 (nowcoder.com)
HAL库STM32常用外设教程(八)—— SPI (读写W25Q128)_hal spi-CSDN博客
标签:SCL,通信协议,SPI,Init,SDA,IIC,串口,I2C From: https://blog.csdn.net/weixin_53713973/article/details/141907155