CAN协议经过ISO标准化后有ISO11898和ISO11519-2两种标准。其区别仅于物理层不同,ISO11898是CAN闭环高速通信标准,而ISO11519-2是开环低速通信标准,下面仅重点叙述IS011898物理层特性。
为了增强总线抗干扰能力和安全性,我们来看下CAN总线在物理层上做的工作。
在物理层上,CAN总线定义了CAN_High和CAN_Low两条差分信号线,CAN总线的逻辑电平为两条线的电位差(CAN_High - CAN_Low)。那么这样定义会带来什么好处呢?在工业现场中,很多干扰都是共模干扰,如电磁干扰、机械振动等。当外界环境的共模干扰施加到CAN总线上时,CAN采用差分信号线,使得共模干扰得以抵消,不会影响CAN总线逻辑。这就从物理层上提高了总线的稳定性和安全性。
下图为ISO11898标准CAN协议拓扑图
在CAN中采用CAN_High和CAN_Low两线的电位差来表示逻辑0和1,但与一般正逻辑不同,在CAN中采用负逻辑表示"显性电平"和“隐性电平”,总线必须处于二者之中,显性电平为“0”,隐性电平为“1”。在总线上使用“线与"逻辑,即当连接在总线上的n个单元中,只要有一个单元为表现为显性电平0,整个总线上表现为显性电平0;所有的单元表现为隐性电平1时,总线采表现为隐性电平1。这就是“显性电平”和“隐性电平”。
ISO11898标准规定CAN总线拓扑结构为闭环,所有单元以并联的形式连接到网络中,并规定总线两段串联两个85-130Ω电阻。
此外,CAN总线同样为正确逻辑电平的表达提供了一定的容许误差,下表即为ISO11898标准两差分线电位和总线电平关系:
从上表可以看出,电位差为0V时,表现为隐形电平1;电位差为2V时,为显性电平1。
由于ISO11519-2通信标准与ISO11898通信标准类似,就不再赘述,下面提供一个表格表示两种通信标准物理层差异:
物理层 | ISO11898 | ISO11519-2 |
---|---|---|
通信速度 | 125Kbps-1Mbps | 125Kbps |
总线长度 | 40m以下 | 1KM以下 |
连接单元数 | 不超30 | 不超20 |
阻抗 | 120Ω | 120Ω |
总线形式 | 闭环 | 开环 |
终端电阻 | 120Ω | 2.2kΩ |
同样为了提高数据传输的稳定性和安全性,CAN协议在位时序上也做了相应工作,CAN协议规定每一位由下面四段组成:
段名称 | Tq数 |
---|---|
SS | 1 |
PTS | 1~8 |
PBS1 | 1~8 |
PBS2 | 2~8 |
SJW | 1~4 |
总段 | 8~25 |
由于波特率是每秒传输的位数,所以:波特率=1/(8~25)Tq。下图是10Tq的位时序。
CAN协议为了保证通信的稳定性和可靠性,在时序上也做了很多工作保证收发端时序相同。CAN协议采用硬件同步和再同步来对信号进行同步调整。所谓信号同步,就是保证发送单元的时序和接收单元的时序相同,从而保证采样时刻位置不发生变动,进而保证采样的准确性。
硬件同步是接收单元在总线空闲时检测出起始信号(电平跳变)时进行的调整,在该段认为是SS段,即位同步段。注意:硬件同步只是进行了一个起始信号检测并将此段认为是SS段,并没有做同步补偿。硬件同步过程如图所示。
上方的时序为发送单元时序,下方为接收单元时序,接收单元检测到电平变化时,就将此刻作为SS段,从中可见这时由于延时等误差已经造成发送单元时序和接收单元时序有所不同了。
上图中蓝色表示发送端时序,红色表示接收端时序,由于延迟等因素,导致发送端起始信号有2Tq的延迟,绿色为发送端时序对应的采样点,黑色为接收端时序不做调整下对应的采样点。接收端为了保证时序同步,在PBS2段减去2Tq时间段,从而使收发端采样点在同一时刻。
若干个最小时间量子Tq构成了1个位时序,若干个位时序组合又构成了消息帧。
CAN协议规定了以下四种帧
帧 | 作用 |
---|---|
数据帧 | 发送单元向接收单元传送的数据 |
遥控帧 | 接收单元向发送单元请求数据 |
错误帧 | 检测出错误时向其他单元通知错误 |
过载帧 | 接收单元通知发送单元尚未做好接收准备 |
在CAN协议中有标准格式和扩展格式两种数据帧格式,扩展格式在标准格式前增加了一些位来扩展ID,其他区别不大。下图是标准格式时序图。
遥控帧同样有标准格式和扩展格式两种,与数据帧相比,遥控帧仅仅是将RTR位设置为隐性电平,并将数据帧去掉,其余并无差异。下图为遥控帧标准格式时序图,遥控帧的时序不再分析。
错误帧由6位的错误标志和8位的错误界定符组成,错误标志分为主动错误标志和被动错误标志两种,当为主动错误标志时,这6个位均为显性位;反之,均为隐性位。错误界定符由8个隐性位组成。下图为错误帧示意图。
下面来介绍一下主动错误和被动错误和总线关闭。
单元必定处于主动错误、被动错误和总线关闭三种状态之一。
主动错误就是单元可以正常参加总线通信的状态,处于主动错误状态的单元检测到错误时输出主动错误标志。主动错误状态相当于单元不会出错或偶尔出错,即是偶尔出错也能容忍,所以拥有发言权和参会权。
被动错误状态是容易引起错误的状态。处于这种状态的单元也能照常参加总线通信,但为了不妨碍总线正常通信,该单元不能发送错误通知,并且即是检测出错误,但如果其他处于主动错误状态的单元没有检测到错误,仍旧认为整个总线没有错误。也就是说该单元经常性出错,但仍旧能工作,所以为了保证其他单元正常通信,把该单元发言权给剥夺了,仍有参会权。
由于单元一直出错,则达到一定次数后,该单元连参会权都没有,自闭了。
由6位过载标志位和8位过载界定符组成,当过载标志位均为显性位时表示过载,过载界定符由8个隐性位构成。
数据链路层将物理层的信号组织为有一定格式的数据,完成控制数据传输,错误控制等流程,如消息的帧化、仲裁、错误检测、错误报告、应答等。
因为数据链路层的很多东西都已经在前面介绍过了,所以就不再赘述,给出数据链路层的基本参照模型。
要注意的是,CAN协议是一种半双工通信协议,所有单元按照并联方式接入总线,没有主机和从机之分,所有单元均可以竞争总线,总线的竞争结果通过仲裁器仲裁,按照ID号的优先级决定,并且当有一个单元竞争总线成功后,则其他单元均变为接收模式,准备接收数据。
bxCAN是基本扩展CAN(Basic Extended CAN)缩写,其设计目标是以最小的CPU负荷来高效处理大量报文,支持报文发送的优先级要求。
由上面的bxCAN框架可知,bxCAN有3个发送邮箱,bxCAN在发送报文时,只需要设置邮箱格式(标识符,数据长度,待发送数据),然后将寄存器CAN_TIxR中的TXRQ位置1,来请求发送。之后该邮箱进入挂号状态,直到成为最高优先级的邮箱后,待总线空闲,报文则马上就会被发送,发送完毕后则该邮箱变为空邮箱。下图是发送邮箱状态图。
bxCAN有两个接收FIFO,分别为FIFO1和FIFO2,每个FIFO最多可以存放3个报文,报文的存放由硬件自动管理。当接收到第一个报文时,报文会暂存在FIFO中,FIFO变为挂号_1等待软件读取报文,释放邮箱。如果此时又有新的报文到来,则FIFO变为挂号_2。下图为接收FIFO状态转换图。
如果FIFO变为挂号_3状态,则如果有新的报文到来,就会导致该报文或原来报文丢失。究竟哪个报文会丢失取决于CAN_MCR寄存器的RFLM位。
我们前面说过,CAN协议里没有地址区分,也没有主从设备区别,各单元以并联形式连接到总线上,没有高低之分的,信息通过广播的形式发送给总线上的所有接收设备。那么,接收设备如何判断该报文是有效信息还是无效信息呢? 其实,在CAN中,ID既表示了优先级,同样也表示信息身份。在软件上,我们可以通过判断ID来决定是否接收该消息,但这样就会占用CPU,bxCAN设计目的就是用最小的CPU负荷来处理大量数据,所以STM32为我们提供了标识符过滤器。在stm32f103系列中共有14个可配置的过滤器组,每个过滤器由2个32位寄存器组成,分别是CAN_FxR1和CAN_FxR2,而这两个32位的寄存器又可以配置其位宽为16位还是32位,因此每个过滤器可以提供1个32位过滤器或2个16位过滤器。除此之外,每组过滤器工作模式可以配置为掩码模式或标识符列表模式。
所谓“掩码模式”,其实就相当于关键字检索,把带有符合条件的关键字的报文存入FIFO中,其余的过滤掉。“标识符列表模式”就相当于建立了一个数据库,如果该报文在建立的数据库中能全字匹配找到,则接收报文存入FIFO中,否则舍弃。
具体过滤器位宽配置和过滤器组工作模式关系看下图
ID(CAN_FxR1) | 1 0 1 1 1 1 0 0 1······(32位) |
---|---|
掩码(CAN_FxR2) | 1 0 1 0 0 1 1 1 1······(32位) |
要筛选的ID | 1 x 1 x x 1 0 0 1······(32位) |
FSCx=0,FBMx=1为例介绍标识符列表模式
ID(CAN_FxR1) | 1 0 |
---|---|
ID(CAN_FxR1) | 0x1234 |
ID(CAN_FxR2) | 0x2345 |
ID(CAN_FxR3) | 0x3456 |
ID(CAN_FxR4) | 0x4567 |
要筛选的ID | 须是0x1234、0x2345、0x3456、0x4567中的一个 |
bxCAN有四种测试模式,分别为静默模式、回环模式、回环静默模式和调试模式。一般在调试的时候,先用CAN的测试模式调试,调试完毕后,在更改为正常模式。
模式框图
静默模式就是CAN能够接收总线消息,但是不能向总线发送消息,因为bxCAN要发送显性位时,显性位可以被CAN内部自己检测到,但不会传输到总线上,也就相当于CAN“哑巴”了。这种模式常被用来分析CAN总线活动。
回环模式框图
回环模式可用于自测,在该模式下bxCAN在内部把Tx输出回馈到Rx上。相当于“聋了”
模式框图
这种模式用于热自测,即完全与总线断开通信,但可以接收到自己发送的信号。
bxCAN的位时序与CAN协议规定的位时序稍有不同,下图为bxCAN位时序。
bxCAN将CAN标准的4段位时序简化为3段,bxCAN把PTS段和PBS1段合并为BS1段,其余没有变化。
则
寄存器我们需要注意的一点是关于位时序配置的,stm32给出的时间计算公式为如下所示
注意这里TS1、TS2、BRP这些位均在后面有加一操作,在利用库函数配置时需要注意。
实验平台:野火stm32f103霸道开发板(同系列最小系统板也可以,因为是自测模式)
实验目的:利用回环测试模式实现CAN收发,使用CAN中断进行数据的读取
GPIO部分配置较简单,CAN引脚配置根据官方手册,CAN_Tx引脚配置为复用推挽输出,CAN_Rx引脚配置为浮空输入。这里对引脚进行了重映射,将其映射到PB8和PB9上。注意开启相应时钟。代码如下
void CAN_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB,ENABLE); //开启引脚时钟
RCC_APB2PeriphClockCmd (RCC_APB2Periph_AFIO,ENABLE);//开始复位时钟
RCC_APB1PeriphClockCmd (RCC_APB1Periph_CAN1,ENABLE);//开启CAN1时钟 CAN1挂载在APB1上
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;//CAN_Rx配置为浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_Init (GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP ;//Tx配置为复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_Init (GPIOB,&GPIO_InitStruct);
GPIO_PinRemapConfig (GPIO_Remap1_CAN1,ENABLE);//引脚重映射
}
CAN初始化结构体:
typedef struct
{
uint8_t CAN_Mode;
FunctionalState CAN_TTCM;
FunctionalState CAN_ABOM;
FunctionalState CAN_AWUM;
FunctionalState CAN_NART;
FunctionalState CAN_RFLM;
FunctionalState CAN_TXFP;
uint8_t CAN_BS1;
uint8_t CAN_BS2;
uint8_t CAN_SJW;
uint16_t CAN_Prescaler;
} CAN_InitTypeDef;
波特率配置(1Mbps):
经过上述分析,该部分代码如下:
void CAN_Mode_Config(void)
{
CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_ABOM = ENABLE; //自动离线管理共能
CAN_InitStruct.CAN_AWUM = ENABLE; //自动唤醒功能
CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack;//测试时使用回环模式
CAN_InitStruct.CAN_NART = DISABLE; //NART不自动重传功能
CAN_InitStruct.CAN_RFLM = ENABLE; // FIFO接收锁定模式,即如果FIFO信箱接收满了,则新的邮件不再接收
CAN_InitStruct.CAN_TTCM = DISABLE;//不使用时间触发模式
CAN_InitStruct.CAN_TXFP = DISABLE;//FIFO发送优先级,ENABLE按照时间顺序发送,DISABLE按照ID顺序发送
/*配置通信速率为1Mbps*/
CAN_InitStruct.CAN_BS1 = CAN_BS1_5tq;
CAN_InitStruct.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStruct.CAN_SJW = CAN_SJW_2tq;
CAN_InitStruct.CAN_Prescaler = 4;
CAN_Init (CAN1,&CAN_InitStruct);
}
typedef struct
{
FunctionalState CAN_FilterActivation;
uint8_t CAN_FilterMode;
uint8_t CAN_FilterScale;
uint8_t CAN_FilterNumber;
uint16_t CAN_FilterFIFOAssignment;
uint16_t CAN_FilterIdHigh;
uint16_t CAN_FilterIdLow;
uint16_t CAN_FilterMaskIdHigh;
uint16_t CAN_FilterMaskIdLow;
} CAN_FilterInitTypeDef;
过滤器FxRx配置方法:
以FxR1为例,假设过滤器配置为32位,ID为扩展模式,FxR1映像如下图所示:
所以对于扩展ID模式而言,FxR1[31:3]与ID[28:0]一 一对应,所要过滤的ID需要先向左移3位,即
IDE用于选择ID是标准格式还是扩展格式,RTR用于区别信息是遥控帧还是数据帧。这里直接使用官方定义的宏。
所以
因为FxR1由FilterIdHigh和FilterIdLow两个16位寄存器组成,所以
这样FxR1就配置完成了,要想配置FxR2则按照同样的方法配置CAN_FilterMaskIdHigh和CAN_FilterMaskIdLow即可。其他模式的配置同样如此
当CAN接收到一个消息帧存入FIFO中的时候,产生中断,在中断中读取FIFO中的数据。当满足下列条件时可以产生中断:
则因为当消息存入FIFO中时,FIFO将会处于挂号状态,等待消息被读取,所以这里我们使用的中断产生条件为CAN_IT_FMP0。
在官方提供的库函数中,定义接收数据结构体和发送数据结构体,这两个结构体均是按照CAN标准帧格式定义的。
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
uint8_t FMI;
} CanRxMsg;
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
} CanTxMsg;
在发送数据时只需要调用函数 CAN_Transmit (),就能完成自动发送,并将数据定义的结构体变量地址传入。 同样接收数据调CAN_Receive()即可。
其余配置较为简单不再赘述。附代码如下:
void CAN_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;//CAN接收中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority =2;
CAN_ITConfig (CAN1,CAN_IT_FMP0,ENABLE);//FIF0消息挂号中断
NVIC_Init (&NVIC_InitStruct);
}
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CAN_Receive (CAN1,CAN_FIFO0,&CAN_Recivee_Buffer);//接收数据
CAN_Flag = 1;
CAN_ClearFlag (CAN1,CAN_FLAG_FMP0);//清除标志位
}
主函数完成的功能是,初始化按键和CAN,当按下KEY1时,发送一帧数据,并打印第一位数据,按下KEY2时发送另一帧数据,并打印第一位数据。代码逻辑较简单,不在赘述,附代码如下
int main(void)
{
u8 box;
SystemInit();//初始化RCC 设置系统主频为72MHZ
delay_init(); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_GPIO_Config();
Key_GPIO_Config();
uart_init(115200);
CAN1_Init();
while(1)
{
if (Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
CAN_Transmit_Buffer.StdId = 0;
CAN_Transmit_Buffer.ExtId = PASS_ID;
CAN_Transmit_Buffer.Data[0] = 111;
CAN_Transmit_Buffer.IDE = CAN_Id_Extended;
CAN_Transmit_Buffer.RTR = CAN_RTR_Data;
CAN_Transmit_Buffer.DLC = 1;
box = CAN_Transmit (CAN1,&CAN_Transmit_Buffer);
while(CAN_TransmitStatus (CAN1,box) != CAN_TxStatus_Ok);
}
if (Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON)
{
CAN_Transmit_Buffer.StdId = 0;
CAN_Transmit_Buffer.ExtId = 121;
CAN_Transmit_Buffer.Data[0] = 121;
CAN_Transmit_Buffer.IDE = CAN_Id_Extended;
CAN_Transmit_Buffer.RTR = CAN_RTR_Data;
CAN_Transmit_Buffer.DLC = 1;
box = CAN_Transmit (CAN1,&CAN_Transmit_Buffer);
while(CAN_TransmitStatus (CAN1,box) != CAN_TxStatus_Ok);
}
if (CAN_Flag == 1)
{
printf("CAN_Receive = %d\n",CAN_Recivee_Buffer.Data[0]);
CAN_Flag = 0;
}
}
}
https://download.csdn.net/download/dhejsb/21410062?spm=1001.2014.3001.5501