差分信号,又叫差模信号
,通过两根信号线上的电压差来表示逻辑0和逻辑1。
当表示逻辑时,这两根线上的电压信号的振幅相同,相位相反
。见下图:
抗干扰能力强
有效抑制对外EMI
时序定位精确
由于差分信号的这些优点,在 USB 协议、485 协议、以太网协议及 CAN 协议的物理层中,都使用了差分信号传输
控制器局域网络
。最初由德国BOSCH
公司开发。ISO有两个标准ISO11898
和ISO11519
,他们区别在仅在于物理层。ISO11898
后来又被拆分为ISO11898-1
(仅涉及数据链路层)和ISO11898-2
(仅涉及物理层)。J1939
协议是以CAN为底层协议专为大型货车和重工机械车量设计的协议。CAN的物理层是通过差分信号来实现的,和串口一样的是通信的波特率需要通信双方提前约定好。
CAN按通信距离分为以下两种:
闭环网络
也叫高速CAN
,遵循ISO11898标准,适用于高速、短距离网络的通信,传输限制为40M,最高速度为1Mbps。
总线两端要求各并联一个120欧
的电阻,见下图:
逻辑1
叫做隐形电平
,两根信号线的电压值都是2.5V
,它们的差值为0V
。逻辑0
叫做显性电平
,其中高电平信号线的电压值为3.5V
,低电平信号线的电压值1.5V
,它们的差值为2V
。逻辑0的电平将覆盖逻辑1的电平
,因此可以认为逻辑0的优先级大于逻辑1,逻辑0显性电平的名称也是这样来的。上述同时也表明了CAN通信是一个半双工通信。
开环网络
也叫低速CAN
,遵循ISO11519-2标准,适用于低速、远距离网络的通信,传输距离限制为1KM,速率为125kbps。
总线的两根信号线彼此独立,但要求每根总线串联一个2.2k
欧的电阻,见下图:
逻辑1
的电平分别为1.75V和3.75V,它们的差值为-1.5V。逻辑0
的电平分别为4.0V和1.0V,它们的差值为3.0V。CAN总线的节点不同于I2C节点需要为每一个节点分配地址,CAN是直接在总线上进行广播通信,每个CAN节点通过通信报文的ID来决定是否接收。
总线上的节点个数理论上不受限制,可通过中继器增强负载。
CAN协议通过将传输的每一个位的分解成SS段、PTS段、PBS1段、PBS2段
来实现总线电平的正确采样。这样分解采样的方式叫做位同步
,协议中的最小时间单位叫TQ
,一个完整的位由8-25TQ
组成,见下图:
确定各个段的时间,即可确定CAN的波特率,比如1tq=1us,则1s / 20tq = 50kbps
报文就是人为规定在数据前后加上各种标识,用于数据分类的一种方法。
根据标识的值的不同,该报文的种类就不同,CAN报文分为以下五种:
报文类型 | 作用 |
---|---|
数据帧 | 用于节点向外传送数据 |
遥控帧 | 用于向远端节点请求数据 |
错误帧 | 用于向远端节点通知校验错误,请求重新发送上一个数据 |
过载帧 | 用于通知远端节点:本节点尚未做好接收准备 |
帧间隔 | 用于将数据帧及遥控帧与前面的帧分离开来 |
数据帧以显性位作为帧起始,7个连续的隐形位作为帧结束,这之间包含仲裁段、控制段、数据段、CRC段和ACK段,注意仲裁段的RTR位必须为显性电平。
具体见下图:
CAN外设功能
STM32中提供支持2.0A和2.0B
版本CAN协议的bxCAN
控制器,通过配置对应寄存器,可完成自动收发报文的功能,该报文支持标准ID和扩展ID
,通信最大速率为1Mb/s
。
CAN外设有3个发送邮箱
用于缓存待发送的报文,发送邮箱可配置发送优先级、发送时间戳和自动重传等一系列功能。
CAN外设还有2个3级深度的接收FIFO
用于缓存接收到的报文,通过过滤器
可过滤指定ID的报文
CAN外设不支持DMA
来进行缓冲区的读写。
CAN外设支持主从模式
,但只有主模式下可以控制存储器访问控制器,所以当作为从设备的工作的时候要打开主设备的时钟
。
工作模式
STM32提供了4种工作模式,正常模式、静默模式、回环模式、回环静默模式
,通过BTR寄存器的SILM和LBKM位
进行进行配置。
回环就是自发自收数据,不对外接收。
静默就是发送的显性电平将直接送到接收端,隐形电平正常输出。
所以上述四种模式在功能上可以理解为正常、自收收、自收发,仅自收。
位时序段与再同步
CAN标准协议中的位时序段分为4段,但CAN外设位时序段只支持3段
,分别是占用1tq的同步段
、BS1和BS2段,BS1和BS2的延时长度可通过BTR寄存器的TS1和TS2位
进行的配置。
CAN外设还可进行采样点的再同步的配置,也就是根据设置好的位长自动自动调整最佳的采样点,通过BTR寄存器的SJW位
进行配置。
CAN波特率
计算方法在前面已经给出,这里直接复述公式。
公式:(1 + TS1 + TS2) * BRP * PCLK
。
其中 BRP 分频因子,通过BTR寄存器的BRP位进行配置。PCLK是CAN所挂载的总线的频率。
CAN筛选器
CAN外设有28组筛选器,每组有2个32位的寄存器,每个寄存器按地址可以配置为16位或32位模式,按功能可以配置为列表和掩码模式。
掩码模式文字说明可能比较麻烦,以16位+掩码模式给出如下示例,其中x代表任意:
寄存器1高16位:1111 0000 1111 0000
寄存器1低16位:1111 0000 1111 0000
实际过滤的报文:1111 xxxx 1111 xxxx
tips1:这里有个坑,按理说如果配置为16位+掩码模式,那筛选器功能的顺序应该为 地址1、掩码1、地址2、掩码2,但实际测试好像是地址1、地址2、掩码1、掩码2的情况
tips2:尽管手册上表明是28组筛选器是CAN1和CAN2进行公用的,但实际好像是前14组给CAN1用的,后14组给CAN2用的
CAN 其他说明
CAN 的当前缓存情况可通过RFx寄存器进行查看,实际接收的数据存储在RI寄存器中
CAN 报文的发送ID和接收ID存储在 TL 和 RL 寄存器中。
CAN 的时间戳由 TDT 寄存器极性控制,注意TDT寄存器中 FMI 存储的是筛选器的索引号而非筛选器组号。一个筛选器组可以配置为1-4个筛选器,而索引号就是第一个筛选器组开始往后排的筛选器的编号。
CAN收发器 TJA1050的作用是将数据转换成总线通信的电平
CAN电平的线与特性,ID号越小的报文,优先级越高
具体功能简易详细阅读手册,虽然读完后几天不用就会忘记了,┭┮﹏┭┮
固件库提供的接口中使用了四个结构体,详情如下:
typedef struct {
uint32_t Prescaler; /*配置 CAN 外设的时钟分频,可设置为 1-1024*/
uint32_t Mode; /*配置 CAN 的工作模式,回环或正常模式*/
uint32_t SJW; /*配置 SJW 极限值 */
uint32_t BS1; /*配置 BS1 段长度*/
uint32_t BS2; /*配置 BS2 段长度 */
uint32_t TTCM; /*是否使能 TTCM 时间触发功能*/
uint32_t ABOM; /*是否使能 ABOM 自动离线管理功能*/
uint32_t AWUM; /*是否使能 AWUM 自动唤醒功能 */
uint32_t NART; /*是否使能 NART 自动重传功能*/
uint32_t RFLM; /*是否使能 RFLM 锁定 FIFO 功能*/
uint32_t TXFP; /*配置 TXFP 报文优先级的判定方法*/
} CAN_InitTypeDef;
typedef struct {
uint32_t FilterIdHigh; /*CAN_FxR1 寄存器的高 16 位 */
uint32_t FilterIdLow; /*CAN_FxR1 寄存器的低 16 位*/
uint32_t FilterMaskIdHigh; /*CAN_FxR2 寄存器的高 16 位*/
uint32_t FilterMaskIdLow; /*CAN_FxR2 寄存器的低 16 位 */
uint32_t FilterFIFOAssignment; /*设置经过筛选后数据存储到哪个接收 FIFO */
uint32_t FilterNumber; /*筛选器编号,范围 0-27*/
uint32_t FilterMode; /*筛选器模式 */
uint32_t FilterScale; /*设置筛选器的尺度 */
uint32_t FilterActivation; /*是否使能本筛选器*/
uint32_t BankNumber; /*扇区序号*/
} CAN_FilterInitTypeDef;
typedef struct {
uint32_t StdId; /*存储报文的标准标识符 11 位,0-0x7FF. */
uint32_t ExtId; /*存储报文的扩展标识符 29 位,0-0x1FFFFFFF. */
uint8_t IDE; /*存储 IDE 扩展标志 */
uint8_t RTR; /*存储 RTR 远程帧标志*/
uint8_t DLC; /*存储报文数据段的长度,0-8 */
uint8_t Data[8]; /*存储报文数据段的内容 */
} CanTxMsgTypeDef;
typedef struct {
uint32_t StdId; /*存储了报文的标准标识符 11 位,0-0x7FF. */
uint32_t ExtId; /*存储了报文的扩展标识符 29 位,0-0x1FFFFFFF. */
uint8_t IDE; /*存储了 IDE 扩展标志 */
uint8_t RTR; /*存储了 RTR 远程帧标志*/
uint8_t DLC; /*存储了报文数据段的长度,0-8 */
uint8_t Data[8]; /*存储了报文数据段的内容 */
uint8_t FMI; /*存储了 本报文是由经过筛选器存储进 FIFO 的,0-0xFF */
uint8_t FIFONumber; /*配置接收 FIFO 编号,可以是 CAN_FIFO0 或者 CAN_FIFO1 */
} CanRxMsgTypeDef;
固件库提供的常用接口如下:
//复位CAN外设的寄存器,使用前需要开时钟
void CAN_DeInit(void);
// CAN外设初始化
u8 CAN_Init(CAN_InitTypeDef* CAN_InitStruct);
// CAN过滤器初始化
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
//发送CAN数据包
u8 CAN_Transmit(CanTxMsg* TxMessage);
//接收CAN 数据包
void CAN_Receive(u8 FIFONumber, CanRxMsg* RxMessage);
//使 CAN 进入低功耗模式,复位INRQ进入初始化模式,置位SLEEP进入睡眠模式,然后判断INAK和SLAK确认初始化和睡眠状态
//CAN_Init 初始化时的 CAN_AWUM 使能本质就是清除了 MCR 的 SLEEP 位
u8 CAN_Sleep(void);
//将 CAN 唤醒
u8 CAN_WakeUp(void);
// 使能 CAN 的中断
/*
CAN_IT_TME 发送邮箱空中断屏蔽
CAN_IT_FMP0 FIFO0 消息挂号中断屏蔽
CAN_IT_FF0 FIFO0 满中断屏蔽
CAN_IT_FOV0 FIFO0 溢出中断屏蔽
AN_IT_FMP1 FIFO1 消息挂号中断屏蔽
CAN_IT_FF1 FIFO1 满中断屏蔽
CAN_IT_FOV1 FIFO1 溢出中断屏蔽
CAN_IT_EWG 错误警告中断屏蔽
CAN_IT_EPV 错误被动中断屏蔽
CAN_IT_BOF 离线中断屏蔽
CAN_IT_LEC 上次错误号中断屏蔽
CAN_IT_ERR 错误中断屏蔽
CAN_IT_WKU 唤醒中断屏蔽
CAN_IT_SLK 睡眠标志位中断屏蔽
*/
void CAN_ITConfig(u32 CAN_IT, FunctionalState NewState);
//尽管函数名有IT字样
//但我觉得这个API应该解释为查看 CAN 外设当前的状态
/*
CAN_IT_RQCP0 邮箱 1 请求完成
CAN_IT_RQCP1 邮箱 2 请求完成
CAN_IT_RQCP2 邮箱 3 请求完成
CAN_IT_FMP0 FIFO0 消息挂号
CAN_IT_FULL0 FIFO0 已存入 3 消息
CAN_IT_FOVR0 FIFO0 溢出
CAN_IT_FMP1 FIFO1 消息挂号
CAN_IT_FULL1 FIFO1 已存入 3 消息
CAN_IT_FOVR1 FIFO1 溢出
CAN_IT_EWGF 上限到达警告
CAN_IT_EPVF 错误被动上限到达
CAN_IT_BOFF 进入离线状态
CAN_IT_WKUI 睡眠模式下 SOF 侦测
*/
ITStatus CAN_GetITStatus(u32 CAN_IT);
//清除 CAN 外设状态的标志位
void CAN_ClearITPendingBit(u32 CAN_IT);
//判断 CAN 的出错状态
FlagStatus CAN_GetFlagStatus(u32 CAN_FLAG);
//清除 CAN 出错状态标志位
void CAN_ClearFlag(u32 CAN_Flag);
//返回指定邮箱当前的发送状态。
//实际是判断 待发送标志位RQCP、上一次发送结果TXOK、发送邮箱为空TME、三个寄存器位来实现
//如果标志位为待发送且邮箱为空则返回失败,因为如果发送前待发送标志位1且邮箱不为0的话肯定是出错了
//如果标志位为上一次发送成功且待发送且邮箱为空则成功,因为发送后待发送标志位清零,所以等于只判断了邮箱为空且发送成功
u8 CAN_TransmitStatus(u8 TransmitMailbox);
//取消指定邮箱的传输请求,实现是置位ABRQ
//作用是终止邮箱的发送请求,邮箱未挂起等待发送时则无效,当TXRQ置1时,数据开始发送,待发送标志位RQCP会清零,所以猜测,一旦开始发送了,通过这种方法就无法挽回了
void CAN_CancelTransmit(u8 Mailbox);
//释放一个 FIFO,本质置位RFOM寄存器位,移动FIFO指针,访问下一条数据
void CAN_FIFORelease(u8 FIFONumber);
//返回邮件的数量,本质是返回FMP的值
u8 CAN_MessagePending(u8 FIFONumber);
//使用
BB_Can_Init(CANx, BB_Can_Init_Conf_Callback1);
//接口回调,内联回调函数里实现CAN外设的配置
static __inline void BB_Can_Init_Conf_Callback1(CAN_TypeDef* CANx)
{
BB_Can_Set_MCR(CANx, //开启功能
CAN_MCR_NART
);
BB_Can_Reset_MCR(CANx, //关闭功能
CAN_MCR_TTCM
| CAN_MCR_ABOM
| CAN_MCR_AWUM
| CAN_MCR_RFLM
| CAN_MCR_TXFP
);
BB_Can_Set_BTR(CANx, 6, CAN_BS1_4tq, CAN_BS2_2tq, CAN_SJW_1tq, CAN_Mode_LoopBack); //位同步段时间与模式设置
}
//接口实现,通过内联的方式直接操作寄存器,将时间开销降到最低
static __inline uint32_t BB_Can_Init(CAN_TypeDef* CANx, void (*conf_fun)(CAN_TypeDef* CANx))
{
int32_t time_out = 0xffff; //超时默认延时
BB_Can_Reset_MCR(CANx, CAN_MCR_SLEEP); //退出睡眠
BB_Can_Set_MCR(CANx, CAN_MCR_RESET); //进入复位状态
while(!BB_Can_Read_MSR(CANx, CAN_MSR_INAK) && time_out--); //等待初始化
if (!BB_Can_Read_MSR(CANx, CAN_MSR_INAK)) return CAN_InitStatus_Failed; //初始化出错
conf_fun(CANx); //通过指针函数的来实现对不同部分的调用
time_out = 0xffff; //超时默认延时
BB_Can_Reset_MCR(CANx, CAN_MCR_RESET); //退出复位状态
while(BB_Can_Read_MSR(CANx, CAN_MSR_INAK) && time_out--); //退出初始化
if (BB_Can_Read_MSR(CANx, CAN_MSR_INAK)) return CAN_InitStatus_Failed; //退出初始化化出错
return CAN_InitStatus_Success; //返回成功
}
//使用
BB_Can_Filter_Init(0, BB_Can_Filter_Init_Conf_Callback0);
//接口回调
static __inline void BB_Can_Filter_Init_Conf_Callback0(uint8_t Filter_num)
{
BB_Can_FilterScale_32bit(Filter_num); //配置为32位模式
BB_Can_FilterData_32bit(Filter_num, 0x1314, 0x1314); //配置数据寄存器
BB_Can_FilterMode_IdMask(Filter_num); //配置为掩码模式
BB_Can_Filter_FIFO1(Filter_num); //关联到FIFO1
BB_Can_Filter_ENABLE(Filter_num); //打开总使能
}
//接口实现
static __inline void BB_Can_Filter_Init(uint8_t Filter_num, void (*conf_fun)(uint8_t Filter_num))
{
BB_Can_Set_FMR(CAN_FMR_FINIT); //进入初始化状态
conf_fun(Filter_num); //进入配置函数
BB_Can_Reset_FMR(CAN_FMR_FINIT); //退出初始化状态
}
//使用
BB_CAN_ITConfig(CANx);
//接口实现
static __inline void BB_CAN_ITConfig(CAN_TypeDef* CANx)
{
BB_Can_Set_IER(CANx, //中断开启
CAN_IT_FMP0
);
BB_Can_Reset_IER(CANx, //中断关闭
CAN_IT_FF0
);
}