目录
1. SPI
SPI,串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线,并且在芯片管脚上只占用四根线,节约了芯片的管脚。常作为单片机外设芯片串行扩展接口,主要应用于EEPROM、FLASH、实时时钟、AD转换器、数字信号处理器和数字信号解码器之间。
SPI主要以主从方式通信。这种模式通常只有一个主机和一个或者多个从机,标准的SPI是4根线,分别是:
- MISO(SDI):主设备数据输入,从设备数据输出;
- MOSI(SDO):主设备数据输出,从设备数据输入;
- SCLK:时钟信号,由主设备产生,只能由主设备控制;
- SSEL(CS、SS):从设备片选使能信号,由主设备控制,通常是低电平有效信号。
CS是用来控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。例如,如果从设备是低电平使能的话,当拉低这个引脚后,从设备会被选中,主机和这个被选中的从机进行通信。
SPI的四种工作模式:
SPI通信的主机就是单片机,在读写数据时序的过程中,有四种模式。首先,先介绍一下CPOL和CPHA。
CPOL:clock polarity,时钟极性。
通信的整个过程分为空闲时刻和通信时刻,如果SCLK在数据发送之前和之后的空闲状态是高电平,那么CPOL=1;如果空闲状态SCLK是低电平,那么CPOL=0。
CPHA:clock phase,时钟相位。
主机和从机要交换数据,牵涉到一个问题:主机在什么时刻输出数据到MOSI上而从机在什么时刻采集这个数据?
在同步通信中,所有数据的变化和采样都是伴随着时钟沿进行的,即数据总是在时钟的边沿附近变化或被采样。一个时钟周期包含一个上升沿和一个下降沿,由于数据从产生到稳定需要一定时间,因此如果主机在上升沿输出数据到MOSI上,从机只能在下降沿采样这个数据,反之同理。
CPHA=1,表示数据的输出是在一个时钟周期的第一个沿上,至于这个沿是上升沿还是下降沿,要视CPOL的值而定:CPOL=1为下降沿,CPOL=0为上升沿。然后,数据的采样就是在第二个沿上了。
CPHA=0,表示数据的采样是在一个时钟周期的第一个沿上,至于这个沿是上升沿还是下降沿,要视CPOL的值而定:CPOL=1为下降沿,CPOL=0为上升沿。然后,数据的输出就是在第二个沿上了。
四种工作模式:
SPI通信有4种不同的模式,不同的从设备可能在出厂时就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以可以对主机的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制主机的通信模式,具体如下:
- Mode0(00):CPOL=0,CPHA=0;
- Mode1(01):CPOL=0,CPHA=1;
- Mode2(10):CPOL=1,CPHA=0;
- Mode3(11):CPOL=1,CPHA=1。
例如:
- CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
- CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据发送是在上升沿,数据采样是在下降沿。
- CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采样是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
- CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据发送是在下降沿,数据采样是在上升沿。
注意:
- 片选引脚通常用来决定是哪个从机和主机进行通信。
- 主设备能够控制时钟,SPI协议能够通过控制时钟信号线,当没有数据交流时,时钟线要么保持高电平要么保持低电平。
- 从设备的模式决定主设备,所以要先去搞懂从设备的SPI是何种模式,然后再将主设备的SPI的模式,设置和从设备相同的模式,即可正常通讯。
2. CAN
参考:
CAN的特点:
CAN协议有两个标准:高速通信标准ISO11898,针对通信速率为125kbps~1Mbps;低速通信标准ISO11519-2,针对通信速率为125kbps以下的低速通信。
① 多主控制:总线空闲时,所有单元都可发送消息,而两个以上的单元同时发送消息时,根据标识符(ID, 非地址)决定优先级。两个以上的单元同时开始发送消息时,对各消息ID的每个位进行逐个仲裁比较。仲裁获胜(优先级最高)的单元可继续发送消息,仲裁失利的单元则立即停止发送而进行接收工作。
② CAN物理层的形式主要分为闭环总线和开环总线。
闭环通讯网络是一种高速、短距离网络,它的总线最大长度为40m,通信速度最高1Mbps,总线的两端各需要一个120Ω的电阻;
开环总线网络是低速、远距离网络,它的最大传输距离1km,最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各需要串联一个2.2kΩ的电阻。
③ 具有错误检测/错误通知和错误恢复功能。
④ 故障封闭功能。CAN可以判断出错误的类型是总线上数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生次序数据错误时,可将引起此故障的单元从总线上隔离出去。
⑤ 连接节点多。CAN总线可同时可同时连接多个单元。
物理层特征:
CAN是一种异步通信,CAN_High和CAN_Low两条信号线构成一组差分信号线,以差分信号的形式进行通讯。
CAN控制器根据CAN_L和CAN_H的电位差来判断总线电平。总线电平分为显性电平和隐性电平。
- 显性电平:逻辑0,用于表示CAN总线上的某个节点正在发送数据,当总线上有一个或多个节点发送显性电平时,总线状态被置为显性电平。 CAN_High的电平通常为3.5V,CAN_Low的电平通常为1.5V,相差2V左右;
- 隐性电平:逻辑1,用于表示CAN总线处于空闲状态,没有节点发送数据。当总线上所有节点都停止发送数据或发送隐性电平时,总线状态被置为隐性电平。CAN_High和CAN_Low线上的电压均为2.5V,相差0V。
显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。
通讯节点:
CAN总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。
CAN(Controller Area Network)通信协议是一种基于广播的通信方式,它采用多主结构,即网络中的每个节点都可以在任何时候主动地向网络上其他节点发送信息,而不分主从,通信方式灵活。
由于CAN通讯协议不对节点进行地址编码,而是对数据内容进行编码,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。
CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器(电平转换)之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连。
当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器,然后由收发器把这个普通的逻辑电平转化为差分信号,通过差分线CAN_High和CAN_Low线输出到CAN总线网络。
而通过收发器接收总线上的数据到控制器时,则是相反的过程,收发器把总线上收到的CAN_High及CAN_Low信号转化为普通的逻辑电平信号,通过CAN_Rx输出到控制器中。
由于CAN总线协议的物理层只有1对差分线,在一个时刻只能表示一个信号,所以对通讯节点来说,CAN通讯是半双工的,收发数据需要分时进行。在CAN的通讯网络中,因为共用总线,在整个网络中同一时刻只能有一个通讯节点发送信号,其余的节点在该时刻都只能接收。
CAN通信报文:
有五种类型的帧:
- 数据帧:用于通讯节点向外传送数据。
- 远程帧:用于向远端节点请求数据。
- 错误帧:用于向远端节点通知校验错误,请求重新发送上一个数据。
- 过载帧:用于通知远端节点,本节点尚未做好接收准备。
- 间隔帧:用于将数据帧、远程帧与前面的帧分开。
数据帧介绍:
如下图所示是报文的数据帧结构。
根据标识符长度的不同,可把数据帧格式分为两种:具有11 位标识符的帧称为标准格式,而具有29位标识符的帧为扩展格式。
标准格式报文中的数据帧:
扩展格式报文中的数据帧:
① 帧起始:
帧起始信号只有1bit,是一个显性电平逻辑0 ,用于通知各个节点将有数据传输。
② 仲裁段:
- 对于标准帧(Standard Frame):
- 标识符(ID):11位
- 远程传输请求位(RTR):1位
- 总计:12位
- 对于扩展帧(Extended Frame):
- 标识符(ID.28-ID.21):8位
- 标识符扩展位(IDE):1位
- 标识符(ID.20-ID.0):11位
- 远程传输请求位(RTR):1位
- 总计:21位
- 描述:用于确定报文的优先级以及是否为远程帧。ID越小,优先级越高。RTR位为1时表示远程帧,为0时表示数据帧。
表示该帧优先级的段,当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输。报文的优先级数值即ID越小,优先级越高。该仲裁机制是利用“如果总线上同时出现显性电平和隐形电平,总线的状态会被置为显性电平”这个特性进行仲裁。
在CAN网络中,节点之间通过发送和接收报文(Message)来进行通信。每个报文都包含一个标识符(ID),用于标识报文的优先级和类型。当多个节点同时发送报文时,CAN的仲裁机制会根据报文的ID来决定哪个报文可以优先发送。
③控制场:
- 长度:6位
- 描述:包含数据长度码(DLC,指示数据域中数据的字节数,0-8字节),以及保留位(用于将来扩展)。
④数据场:
- 长度:0-8字节(标准CAN)或0-64字节(CAN FD)
- 描述:包含实际要传输的数据,长度由DLC指定。
⑤CRC段:
检查帧的传输错误的段,16bit(标准CAN)或20bit(CAN FD)一旦接收节点算出的CRC码跟接收到的CRC码不同,则它会向发送节点反馈错误信息,利用错误帧请求它重新发送。CRC码一般由CAN控制器硬件计算得出,出错时的处理则由软件控制最大重发数。
⑥应答场:
- 长度:2位,应答位(ACK Slot)和应答界定符(ACK Delimiter)。
- 描述:包含应答位和应答分隔符。接收站使用应答位来表示是否已正确接收报文。
- 当接收器正确地接收到有效的报文时,它会在应答位期间发送一个显性位来覆盖原本应该是隐性的位,以此表示应答。如果没有任何接收器应答,那么发送器会检测到这个位仍然是隐性的,这可能意味着传输出现了问题。
-
应答界定符:这是一个隐性的位,用于分隔应答位和随后的帧结束。
⑦ 帧结束:帧结束段由发送节点发送的7个隐性位表示结束。
远程帧:
如果需要CAN上某个节点向你发送数据,你可以用这个节点的ID,发送一个Remote frame(远程帧),这样节点接收到这个Remote frame之后会自动发送数据给你。发送的数据就是数据帧。
主要用来请求某个指定节点发送数据,而且避免总线冲突。
由于CAN总线发送帧时,仲裁方法只依靠帧ID号,当有两个相同ID号的帧同时竞争总线时,总线就无法判别出让哪个设备先发送帧,于是就造成总线冲突。
为了总线访问安全,每个发送器必须用独属于自己的ID号往外发送帧。
错误帧
是某节点发现帧错误时用来向其他节点通知的帧;
过载帧
是接收节点用来向发送节点告知自身接收能力的帧,用于将数据帧、远程帧与前面帧隔离的帧。
总线冲突问题:
CAN总线控制器在发送数据的同时监控总线电平,如果电平不同,则停止发送并做其他处理。如果该位位于仲裁段,则退出总线竞争;如果位于其他段,则产生错误事件。
一个数据帧传输的数据量为0~8个字节,这种短帧结构使得CAN-bus实时性很高。
通过波形了解CAN网络通信:
eg:参考《如何通过波形解析can总线数据》
这里的数据使用的是标准的can设备产生的can信号(扩展帧发送数据ID=0x11121181 Data=0x06 0x08)。
信号的波形如图1所示,这里示波器的探头接的是CAN_H,探头的夹子接的是CAN_L:
如果波特率为500kbps,那么一个位的时间就是1/500=2us。
使用简单的公式,人们可以计算出在CAN 总线上传输一个帧所需的大概时间。
举例来说,1个CAN 数据帧大约包含125比特(包含填充位)。
假设比特率=250kbps
位时间=1/位速率=1/(250*1000)s=4μs
因此,在使用250kbps时,1位在总线上的传输时间为4μs
因此,传输1帧的大致时间是(4 µs/bit * 125 bit)=500 µs
依据波特率和上边的波形可以读出具体的数据:
10111011101100011110111001111101011111001111101001111100111101111011011101
这里先将高电平编码为1,低电平编码为0。后续要根据协议将1和0反转。
标准的CAN2.0扩展协议如下:
协议规定,当连续出现5个高电平时,就需要插入一个低电平,如下所示的红线删除的数据。(这里仍然假定高电平为1,低电平为0 )
删除之后,根据协议的格式就能将这些数据分段表示了。
解析出来得到:(此时可以把高电平置为0,低电平置为1)
29位ID:
0 1110 1110 1101 1110 1110 0111 1110 ---> 1 0001 0001 0010 0001 0001 1000 0001
解析出来ID=0x11121181,与我们发送的数据是相符合的。
DLC:1101 --> 0010,为2,表示这一帧有两个数据,即接下来的16个字节,同理。
总结:
- 在can的协议中当连续出现5个高电平时就需要插入一个低电平;
解释:
在CAN总线上,数据的传输是通过位(bit)的形式进行的。每个位都有特定的时间长度,这个时间长度是由CAN控制器的位定时参数决定的。位定时参数通常包括位时间(Bit Time)、位速率(Bit Rate)和同步跳转宽度(Synchronization Jump Width, SJW)等。
当CAN总线上的信号连续出现多个相同电平(例如,连续的高电平)时,可能会出现一些问题,如信号失真、位同步错误等。为了避免这些问题,CAN协议规定在连续的高电平(或低电平,具体取决于协议的实现)之间插入一个或多个跳变(transition),以确保接收节点能够正确地同步和检测每个位。
具体来说,当发送节点在发送数据时,如果检测到连续的高电平超过了某个预定的阈值(例如,5个连续的高电平),它就会在下一个位之前插入一个低电平跳变。这个跳变被称为“位填充”(Bit Stuffing)。类似地,如果发送节点在发送数据时检测到连续的低电平超过了某个阈值,它也会相应地插入一个高电平跳变。
- 在can协议中将CAN_H和CAN_L的差值为高电平时定义为显性,逻辑上表示为0,为低电平时定义为隐形,逻辑上表示为1。
CAN信号的传输:
发送过程:
CAN控制器将CPU传来的信号转换为逻辑电平(0或1),CAN发射器接收到逻辑电平后,再将其转换为差分电平输出到CAN总线上。
接收过程:
CAN接收器将CAN_H和CAN_L线上传来的差分电平转换为逻辑电平输出到CAN控制器,CAN控制器再把该逻辑电平转化为相应的信号发送到CPU上。
3. IIC
通讯步骤概括:
- 主机发出起始信号:在通信开始前,主机首先发出起始信号来占用总线。这通常是在SCL为高电平时,将SDA从高电平拉低来完成的。
- 发送设备地址和读写控制位:主机接着会发送一个字节的数据,其中高7位(或10位,取决于地址的位数)为从设备的地址,最后一位为读写控制位(R/W位)。当R/W位为0时,表示主机要向从设备发送数据(写操作);当R/W位为1时,表示主机要从从设备读取数据(读操作)。
- 等待从机应答:主机发送完设备地址和读写控制位后,会等待从机的应答信号。如果从机的地址与主机发送的地址匹配,并且准备好进行通信,它会发送一个应答信号(ACK)给主机。这个应答信号是在SCL为高电平时,SDA从高电平拉低来表示的。
- 一旦建立了连接,主机就可以通过SDA线向从机发送数据或从从机接收数据。数据在SCL的每个时钟周期内传输一位。
- 通信结束后,主机发送停止信号来释放总线。
I²C总线的SDA和SCL线都是开漏输出,这意味着它们需要外部的上拉电阻才能保持在高电平状态。当设备需要发送低电平时,它会将其对应的引脚拉低;当需要发送高电平时,它会释放该引脚,允许上拉电阻将其拉高。
一般来说,对于标准模式(100kHz)的I²C总线,上拉电阻可以选择4.7kΩ或10kΩ。对于快速模式(400kHz)或更高速率的I²C总线,可能需要更小的上拉电阻。
1. I2C有两根双向信号线,SDA和SCL。总线上可以挂很多设备,多个主设备,多个从设备。
2. 起始信号和终止信号都是由主机发送的。
- 起始信号:SCL为高电平时,SDA由高向低跳变。
- 终止信号:SCL为高电平时,SDA由低向高跳变。
3. 数据位的有效性规定:
在I2C总线进行数据传送时,SCL为高电平期间,SDA线上的数据必须保持稳定;只有在SCL为低时,SDA上的高电平或低电平状态才允许变化。
4. 数据传送格式:主机发给从机
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
5. 应答信号
- 应答信号包括ACK和NACK。
- 作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;
- 若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。
- 传输时主机产生时钟,由于每个周期传输一位01数据,每个数据包包括8位,所以在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
6. 总线寻址:
主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/W位将自己确定为发送器还是接收器。
7. 每次数据传送总是由主机产生终止信号来结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
8. 在总线的一次数据传输中,可以有以下几种组合方式:
(1)主机向从机发送数据,数据传送方向在整个过程中不变。
(2)主机在第一个字节后,立即从从机读取数据(传输方向不变)。
(3)传送过程中,当需要改变传递方向时,起始信号和从机地址都被重复产生一次,但两次读写方向位正好相反。
IIC驱动代码
首先看下iic.h中的定义:
#ifndef __IIC_H
#define __IIC_H
//IO方向设置
//先把bit12,13,14,15清零,然后bit15置1,表示PB3为输入模式,上拉/下拉输入模式
#define SDA_IN() {GPIOB->CRL&=0xFFFF0FFF;GPIOB->CRL|=8<<12;}
//先把bit12,13,14,15清零,然后bit13,12置1,表示PB3为通用推挽输出,最大速度50MHz
#define SDA_OUT() {GPIOB->CRL&=0xFFFF0FFF;GPIOB->CRL|=3<<12;}
//IO操作函数
#define IIC_SCL PBout(4) //SCL
#define IIC_SDA PBout(3) //SDA
#define READ_SDA PBin(3) //SDA
#endif
下面是iic.c:
//初始化 IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//产生 IIC 起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda 线输出
IIC_SDA=1;
IIC_SCL=1;
//开始信号:SCL为高电平时,SDA由高向低跳变。
delay_us(4);
IIC_SDA=0;
delay_us(4);
IIC_SCL=0; //钳住 I2C 总线,准备发送或接收数据
}
//产生 IIC 停止信号
void IIC_Stop(void)
{
SDA_OUT(); //sda 线输出
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
//结束信号:SCL为高电平时,SDA由低向高跳变。
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA 设置为输入
IIC_SDA=1;
delay_us(1);
IIC_SCL=1;
delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出 0
return 0;
}
//产生 ACK 应答
void IIC_Ack(void)
{
IIC_SCL=0; //只有SCL为低,SDA上的高电平或低电平状态才允许变化
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生 ACK 应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC 发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读 1 个字节:ack=1 时,发送 ACK;ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA 设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)
{
receive++;
}
delay_us(1);
}
if (!ack)
IIC_NAck(); //发送 nACK
else
IIC_Ack(); //发送 ACK
return receive;
}