CAN 总线架构详细介绍
物理层
物理层决定了 CAN 总线的电气特性、信号传输方式以及节点和总线的连接方式。常见的 CAN 物理层标准有 ISO 11898 - 2(高速 CAN)和 ISO 11898 - 3(低速容错 CAN)。
电气特性
- 高速 CAN(ISO 11898 - 2):
- 显性位(逻辑 0):CAN_H 与 CAN_L 之间的电压差大约是 2V,典型值为 CAN_H = 3.5V,CAN_L = 1.5V。
- 隐性位(逻辑 1):CAN_H 和 CAN_L 之间的电压差约为 0V,通常 CAN_H = CAN_L = 2.5V。
- 位速率:高速 CAN 的位速率最高可达 1Mbps,一般汽车应用中常用 500kbps 或 250kbps。
- 低速容错 CAN(ISO 11898 - 3):
- 显性位(逻辑 0):CAN_H 与 CAN_L 之间的电压差约为 1.5V,例如 CAN_H = 3V,CAN_L = 1.5V。
- 隐性位(逻辑 1):CAN_H 和 CAN_L 之间的电压差约为 0V,通常 CAN_H = CAN_L = 2.25V。
- 位速率:低速容错 CAN 的位速率通常在 125kbps 以下。
拓扑结构
CAN 总线一般采用总线型拓扑,所有节点都连接到同一条总线上。为减少信号反射,需要在总线两端连接终端电阻,高速 CAN 中终端电阻值通常为 120Ω。
节点连接
每个 CAN 节点由 CAN 控制器和 CAN 收发器构成。CAN 控制器负责处理 CAN 协议的高层功能,像数据帧的生成、接收和错误处理;CAN 收发器则把 CAN 控制器输出的数字信号转化为适合在总线上传输的差分信号,同时将总线上的差分信号转换为数字信号输入给 CAN 控制器。
数据链路层
数据链路层是 CAN 总线的核心,负责数据的封装、传输、仲裁和错误检测,主要包含介质访问控制(MAC)子层和逻辑链路控制(LLC)子层。
介质访问控制(MAC)子层
- 帧格式:
- 数据帧:由帧起始、仲裁场、控制场、数据场、CRC 场、ACK 场和帧结束七个部分组成。
- 帧起始:一个显性位,用于表示数据帧的开始。
- 仲裁场:包含标识符(ID)和远程传输请求(RTR)位。ID 用于确定数据帧的优先级,ID 越小,优先级越高。RTR 位用于区分数据帧和远程帧,RTR = 0 表示数据帧,RTR = 1 表示远程帧。
- 控制场:包含数据长度码(DLC)和一些保留位。DLC 用于指示数据场的字节数,取值范围为 0 - 8。
- 数据场:最多可包含 8 个字节的数据。
- CRC 场:包含 15 位的循环冗余校验码,用于检测数据传输过程中的错误。
- ACK 场:包含一个 ACK 槽和一个 ACK 界定符。发送节点在 ACK 槽发送隐性位,若总线上有节点正确接收到数据,则会在 ACK 槽发送显性位进行应答。
- 帧结束:由 7 个隐性位组成,用于表示数据帧的结束。
- 远程帧:用于请求其他节点发送数据。其格式与数据帧类似,但没有数据场,且 RTR 位为 1。
- 错误帧:当节点检测到错误时,会发送错误帧通知其他节点。错误帧由错误标志和错误界定符组成。
- 过载帧:用于在节点处理数据时需要额外时间的情况下,通知其他节点暂停发送数据。过载帧由过载标志和过载界定符组成。
- 数据帧:由帧起始、仲裁场、控制场、数据场、CRC 场、ACK 场和帧结束七个部分组成。
- 仲裁机制:CAN 总线采用非破坏性逐位仲裁机制解决多个节点同时发送数据时的冲突问题。每个数据帧的仲裁场包含一个唯一的标识符(ID),ID 越小,优先级越高。当多个节点同时发送数据时,它们会逐位比较仲裁场的 ID,发送隐性位(逻辑 1)的节点如果检测到总线上出现显性位(逻辑 0),则认为自己的优先级较低,立即停止发送,让出总线使用权,而优先级高的节点则可以继续发送数据。
- 位填充:为保证数据的同步和避免长串的连续相同位导致的同步丢失,CAN 总线采用位填充技术。在发送数据时,如果连续出现 5 个相同的位,发送器会自动插入一个相反的位;在接收数据时,接收器会自动删除这些插入的位。
逻辑链路控制(LLC)子层
LLC 子层主要负责数据帧的过滤、确认和错误恢复。它可以根据标识符过滤接收到的数据帧,只接收那些符合自己需求的数据帧。同时,LLC 子层还会对接收的数据帧进行确认,并在检测到错误时采取相应的错误恢复措施。
CAN 总线协议详细介绍
通信协议
- 数据传输模式:CAN 总线支持单播和广播两种数据传输模式。单播是指一个节点向另一个特定节点发送数据;广播是指一个节点向总线上的所有节点发送数据。
- 通信周期:在实际应用中,CAN 总线通信通常采用周期性通信和事件驱动通信相结合的方式。周期性通信是指节点按照一定的时间间隔周期性地发送数据,适用于需要实时更新的数据;事件驱动通信是指节点在发生特定事件时才发送数据,适用于突发情况的数据传输。
错误处理协议
- 错误检测:CAN 总线采用多种错误检测机制,包括位错误检测、填充错误检测、CRC 错误检测、格式错误检测和 ACK 错误检测等。这些错误检测机制可以及时发现数据传输过程中出现的错误。
- 错误处理:当检测到错误时,CAN 节点会根据错误的类型和严重程度采取不同的错误处理措施。如果是轻微错误,节点会发送错误帧通知其他节点,并尝试重新发送数据;如果是严重错误,节点会进入错误被动状态或总线关闭状态,暂时停止参与总线通信。
网络管理协议
- 节点状态管理:CAN 总线网络管理协议负责管理节点的状态,包括节点的唤醒、休眠、初始化和故障诊断等。节点可以根据自身的工作状态和网络需求进入不同的状态,以节省能源和提高系统的可靠性。
- 通信调度:网络管理协议还可以对 CAN 总线的通信进行调度,确保各个节点按照预定的时间和优先级进行通信,避免通信冲突和数据丢失。
C 语言示例代码
以下是一个简单的基于 STM32 的 C 语言示例代码,用于初始化 CAN 总线并发送和接收 CAN 消息。
#include "stm32f4xx.h"
// 定义CAN初始化结构体和过滤器初始化结构体以及GPIO初始化结构体
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 函数功能:配置CAN总线相关参数
void CAN_Configuration(void)
{
// 使能CAN1和GPIOA的时钟
// CAN1挂载在APB1总线上,因此使用RCC_APB1PeriphClockCmd使能其时钟
// GPIOA挂载在AHB1总线上,使用RCC_AHB1PeriphClockCmd使能其时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 配置CAN引脚(PA11 - CAN_RX, PA12 - CAN_TX)
// 设置引脚为复用功能模式,用于连接CAN外设
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
// 设置引脚速度为50MHz,以满足高速通信需求
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 设置引脚为推挽输出类型
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
// 设置引脚为上拉模式,增强信号稳定性
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
// 将配置应用到GPIOA端口
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 将PA11和PA12连接到CAN1
// 把PA11引脚复用为CAN1的接收引脚
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_CAN1);
// 把PA12引脚复用为CAN1的发送引脚
GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_CAN1);
// 对CAN1寄存器进行复位操作,将其恢复到默认状态
CAN_DeInit(CAN1);
// 用默认值初始化CAN_InitStructure结构体
CAN_StructInit(&CAN_InitStructure);
// CAN工作模式配置
// 关闭时间触发通信模式
CAN_InitStructure.CAN_TTCM = DISABLE;
// 关闭自动离线管理模式
CAN_InitStructure.CAN_ABOM = DISABLE;
// 关闭自动唤醒模式
CAN_InitStructure.CAN_AWUM = DISABLE;
// 禁用非自动重传模式,即允许自动重传
CAN_InitStructure.CAN_NART = DISABLE;
// 关闭接收FIFO锁定模式
CAN_InitStructure.CAN_RFLM = DISABLE;
// 关闭发送FIFO优先级模式
CAN_InitStructure.CAN_TXFP = DISABLE;
// 设置CAN工作在正常模式
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
// 设置同步跳转宽度为1个时间量子
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
// 设置位段1的时间长度为6个时间量子
CAN_InitStructure.CAN_BS1 = CAN_BS1_6tq;
// 设置位段2的时间长度为8个时间量子
CAN_InitStructure.CAN_BS2 = CAN_BS2_8tq;
// 设置CAN总线的预分频系数为5
CAN_InitStructure.CAN_Prescaler = 5;
// 根据上述配置初始化CAN1
CAN_Init(CAN1, &CAN_InitStructure);
// 配置CAN过滤器
// 设置过滤器编号为0
CAN_FilterInitStructure.CAN_FilterNumber = 0;
// 设置过滤器工作在标识符掩码模式
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
// 设置过滤器为32位宽
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
// 设置过滤器标识符高16位为0
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
// 设置过滤器标识符低16位为0
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
// 设置过滤器掩码高16位为0
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
// 设置过滤器掩码低16位为0
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
// 将过滤后的消息放入FIFO0
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;
// 使能该过滤器
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
// 根据上述配置初始化CAN过滤器
CAN_FilterInit(&CAN_FilterInitStructure);
}
// 函数功能:发送CAN消息
void CAN_SendMessage(void)
{
// 定义CAN发送消息结构体
CanTxMsg TxMessage;
// 定义邮箱编号
uint8_t mailbox;
// 设置标准标识符为0x123
TxMessage.StdId = 0x123;
// 设置扩展标识符为0x00
TxMessage.ExtId = 0x00;
// 设置使用标准标识符
TxMessage.IDE = CAN_ID_STD;
// 设置为数据帧
TxMessage.RTR = CAN_RTR_DATA;
// 设置数据长度为8字节
TxMessage.DLC = 8;
// 填充数据
for (uint8_t i = 0; i < 8; i++)
{
TxMessage.Data[i] = i;
}
// 将消息放入CAN1的发送邮箱,并返回邮箱编号
mailbox = CAN_Transmit(CAN1, &TxMessage);
// 等待消息发送成功
while (CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok);
}
// 函数功能:接收CAN消息
void CAN_ReceiveMessage(void)
{
// 定义CAN接收消息结构体
CanRxMsg RxMessage;
// 检查FIFO0中是否有消息待接收
if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
{
// 从FIFO0中接收消息
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
// 处理接收到的数据,这里只是占位,实际使用时可根据需求添加处理逻辑
for (uint8_t i = 0; i < RxMessage.DLC; i++)
{
// 处理接收到的数据
}
}
}
int main(void)
{
// 调用CAN配置函数,对CAN总线进行初始化
CAN_Configuration();
while (1)
{
// 调用发送消息函数,发送CAN消息
CAN_SendMessage();
// 调用接收消息函数,接收CAN消息
CAN_ReceiveMessage();
}
}
代码说明
CAN_Configuration
函数:对 CAN 总线进行初始化,包含使能 CAN 和 GPIO 时钟、配置 CAN 引脚、初始化 CAN 寄存器和配置 CAN 过滤器。CAN_SendMessage
函数:创建一个 CAN 发送消息并发送,等待消息发送成功。CAN_ReceiveMessage
函数:检查是否有 CAN 消息待接收,若有则接收消息并处理数据。main
函数:初始化 CAN 总线,在循环中不断发送和接收 CAN 消息。
此代码基于 STM32F4 系列微控制器,使用标准外设库。不同的硬件平台和开发环境可能需要对代码进行相应的调整。