阅读量:4
目录
介绍
一种功能丰富的车用总线标准。被设计用于在不需要主机(Host)的情况下,允许网络上的单片机和仪器相互通信
它基于消息传递协议,设计之初在车辆上复用通信线缆,以降低铜线使用量,后来也被其他行业所使用
CAN拥有了良好的弹性调整能力,可以在现有网络中增加节点而不用在软、硬件上做出调整。除此之外,消息的传递不基于特殊种类的节点,增加了升级网络的便利性
物理层
- 一个CAN控制器
- 一般MCU提供,stm32内部提供了一个can控制器
- 一个CAN收发器
- 收发器一般需要专门芯片提供,例如PD1050S收发器芯片
can
协议层
CAN的 帧/报文 种类
特点:
- CAN总线是广播类型的总线
- 这意味着所有节点都可以侦听到所有传输的报文
- 无法将报文单独发送给指定节点
- 所有节点都将始终捕获所有报文
- 但是CAN硬件能够提供本地过滤功能,让每个节点对报文有选择性地做出响应
- CAN使用短报文 – 最大实用负载是94位
- 可以认为报文是通过内容寻址,也就是说,报文的内容隐式地确定其地址
- CAN总线上有5种不同的报文类型
- 数据帧,远程帧,错误帧,过载帧,帧间隔
数据帧
- 数据帧是最常见的报文类型,用于发送单元向接收单元发送数据
- 有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符
远程帧(遥控帧)
- 远程帧用于接收单元向具有相同id的发送单元请求发送数据
- 有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符
- 与数据帧相比没有数据段
错误帧
- 错误帧当检测出错误时向其他单元通知错误的帧
- 由硬件自动完成的,没有办法用软件来控制
过载帧
- 过载帧并不常用,因为当今的CAN控制器会非常智能化地避免使用过载帧
- 由硬件自动完成的,没有办法用软件来控制
帧间隔
- 用于将数据帧及遥控帧与前面的帧分离开来的帧
- 由硬件自动完成的,没有办法用软件来控制
总线仲裁
发送接收特点:
- CAN总线处于空闲状态的时候,最先发送消息的单元获得发送权
- 多个单元同时开始发送时,从仲裁段(报文id)的第一位开始进行仲裁
- 连续输出显性电平最多的单元可以继续发送,即首先出现隐形电平的单元失去最总线的占有权变为接收。(即报文id小的优先级高)
- 竞争失败,会自动检测总线空闲,在第一时间再次尝试发送
stm32的CAN外设
STM32的芯片中具有bxCAN控制器,它支持CAN协议2.0A 和2.0B Active标准。
- CAN2.0A只能处理标准数据帧且扩展帧的内容会织别错误。
- 而CAN2.0 B Active可以处理标准数据帧和扩展数据帧。
- CAN2.0 B Passive只能处理标准数据帧而扩展帧的内容会被忽略
- 该CAN控制器支持最高的通讯速率为1Mb/s
- 可以自动地接收和发送CAN报文
- 外设中具有3个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间
- 具有2个3级深度的接收FIFO,可使用过滤功能只接收或不接收某些ID号的报文
- 可配置成自动重发;不支持使用DMA进行数据收发
工作模式
CAN控制器有3种工作模式:
- 初始化模式
- 正常模式
- 睡眠模式
上电复位后CAN控制器默认会进入睡眠模式,作用是降低功耗。当需要将进行初始的时候(配置寄存器),会进入初始化模式。当需要通讯的时候,就进入正常模式
测试模式
有3种测试模式:
- 静默模式
- 环回模式
- 环回静默模式
当控制器进入初始化模式的时候才可以配置测试模式
- 静默模式可以用于检测总线的数据流量
- 环回模式可以用于自检(影响总线)
- 环回静默也是用于自检,不会影响到总线
功能框图
- 主动内核
- 含各种控制/状态/配置寄存器,可以配置模式、波特率等。在STM32CubeMx中可以非常方便的配置
- 发送邮件
- 用来缓存待发送的报文,最多可以缓存3个报文。发送调度决定报文的发送顺序
- 接收FIFO
- 共有2个接收FIFO,每个FIFO都可以存放3个完整的报文。它们完全由硬件来管理。从而节省了CPU的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取FIFO中最先收到的报文
- 接收滤波器
- 做用:对接到的报文进行过滤。最后放入FIFO 0或FIFO 1
- 当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的存在,选择性接收有效报文,减轻系统负担
- 有2种过滤模式
- 标识符列表模式
- 它把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理
- 掩码模式(屏蔽位模式)
- 它把可接收报文ID的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO
- 标识符列表模式
时序
标准时序
- 与我们前面解释的 CAN 标准时序有一点区别
- STM32的位时序:把传播时间段和相位缓冲段1做了合并
例子 环回静默模式测试
需求:我们使用环回静默模式测试CAN能否正常工作。把接收到的报文数据发送到串口输出,看是否可以正常工作
寄存器代码
main.c
int main(void) { usart1_init(); printf("尚硅谷 CAN 通讯实验: 静默回环 寄存器版\r\n"); CAN_Init(); printf("CAN 初始化配置完成...\r\n"); uint16_t stdId = 0x066; uint8_t *tData = "abcdefg"; CAN_SendMsg(stdId, tData, strlen((char *)tData)); printf("发送完毕...\r\n"); tData = "123"; CAN_SendMsg(stdId, tData, strlen((char *)tData)); printf("发送完毕...\r\n"); stdId = 0x067; tData = "xyz"; CAN_SendMsg(stdId, tData, strlen((char *)tData)); printf("发送完毕...\r\n"); /* 1. 接收数据 */ RxDataStruct rxDataStruct[8]; uint8_t rxMsgCount; CAN_ReceiveMsg(rxDataStruct, &rxMsgCount); printf("接收完毕 rxMsgCount = %d...\r\n", rxMsgCount); /* 2. 输出消息 */ uint8_t i; for (i = 0; i < rxMsgCount; i++) { RxDataStruct msg = rxDataStruct[i]; printf("stdId = %d, length = %d, msgData = %s\r\n", msg.stdId, msg.length, msg.data); } while (1) { } }
can.h
#ifndef __CAN_H #define __CAN_H #include "stm32f10x.h" #include "usart.h" #include "string.h" /** * @description: 存储接收到的数据 * @return {*} */ typedef struct { uint16_t stdId; uint8_t data[8]; uint8_t length; } RxDataStruct; void CAN_Init(void); void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length); void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount); #endif
can.c
#include "can.h" /** * @description: CAN 通讯初始化 */ void CAN_Init(void) { /* 1. 开启时钟 CAN时钟和GPIO时钟 */ RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; /* 2. 重定向PB8和PB9引脚 10:CAN_RX映像到PB8,CAN_TX映像到PB9 */ AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1; AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0; /* 3. 初始化GPIO: PB9(CAN_Tx):复用推挽输出 mode=11 cnf=10 PB8(CAN_Rx): 浮空输入 mode=00 cnf=01 */ GPIOB->CRH |= GPIO_CRH_MODE9; /* mode = 11 */ GPIOB->CRH |= GPIO_CRH_CNF9_1; /* cnf = 10 */ GPIOB->CRH &= ~GPIO_CRH_CNF9_0; GPIOB->CRH &= ~GPIO_CRH_MODE8; /* mode = 00 */ GPIOB->CRH &= ~GPIO_CRH_CNF8_1; /* cnf = 01 */ GPIOB->CRH |= GPIO_CRH_CNF8_0; /* 4. 初始化 CAN */ /* 4.1 进入初始化模式 */ CAN1->MCR |= CAN_MCR_INRQ; while ((CAN1->MSR & CAN_MSR_INAK) == 0) /* 等待进入初始化模式 */ ; /* 4.2 退出睡眠模式 */ CAN1->MCR &= ~CAN_MCR_SLEEP; while ((CAN1->MSR & CAN_MSR_SLAK) != 0) /* 等待退出睡眠模式 */ ; /* 4.3 自动离线管理。 允许自动退出离线状态 */ CAN1->MCR |= CAN_MCR_ABOM; /* 4.4 自动唤醒管理。 检测到有报文,可以从睡眠模式由硬件自动唤醒。 */ CAN1->MCR |= CAN_MCR_AWUM; /* 4.5 配置位时序寄存器 */ /* 4.5.1 静默模式 用于调试 */ CAN1->BTR |= CAN_BTR_SILM; /* 4.5.2 回环模式 用于调试 */ CAN1->BTR |= CAN_BTR_LBKM; /* 4.5.3 波特率分频器,定义Tq的长度。 配置35表示36分频,则产生波特率的时钟位1MHz。 Tq = 1us */ CAN1->BTR &= ~CAN_BTR_BRP; /* 相应的位均置0 (9:0) */ CAN1->BTR |= 35 << 0; /* 4.5.4 时间段1(3*Tq)和时间段2(6*Tq) */ CAN1->BTR &= ~CAN_BTR_TS1; CAN1->BTR &= ~CAN_BTR_TS2; CAN1->BTR |= (3 << 16); CAN1->BTR |= (6 << 20); /* 4.5.5 再同步跳跃宽度 2*Tq*/ CAN1->BTR &= ~CAN_BTR_SJW; CAN1->BTR |= (2 << 24); /* 4.6 退出初始化模式 */ CAN1->MCR &= ~CAN_MCR_INRQ; while ((CAN1->MSR & CAN_MSR_INAK) != 0) /* 等待退出初始化模式 */ ; /* 4.7 配置过滤器: 接收所有消息 */ /* 4.7.1 进入过滤器初始化模式 */ CAN1->FMR |= CAN_FMR_FINIT; /* 4.7.2 过滤器组0工作模式: 掩码模式 0:掩码模式 1:标识符模式 */ CAN1->FM1R &= ~CAN_FM1R_FBM0; /* 4.7.2 过滤器组0为单个32位配置 0:2给16位 1:单个32位*/ CAN1->FS1R |= CAN_FS1R_FSC0; /* 4.7.3 给过滤器组0分配FIFO 0:FIFO0 1:FIFO1. 通过后的报文会放入这个FIFO中*/ CAN1->FFA1R &= ~CAN_FFA1R_FFA0; /* 4.7.4 设置过滤器组0 标识符寄存器FR1 */ CAN1->sFilterRegister[0].FR1 = 0x00000000; /* id每位都是0 */ /* 4.7.5 设置过滤器组0 屏蔽位寄存器FR2 */ CAN1->sFilterRegister[0].FR2 = 0x00000000; /* 屏蔽位是0,表示不关心ID对应的位。都是0,表示接收所有消息 */ /* 4.7.6 激活过滤器组0 */ CAN1->FA1R |= CAN_FA1R_FACT0; /* 4.7.7 退出过滤器初始化模式 */ CAN1->FMR &= ~CAN_FMR_FINIT; } /** * @description: 发送消息 * @param {uint16_t} stdId 标准帧id * @param {uint8_t} *data 要发送的数据 * @param {uint8_t} length 发送的数据的字节数 */ void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length) { if (length > 8) { printf("数据长度不能超过8个字节\r\n"); return; } /* 1. 等待邮箱0为空 (也可以判断其他邮箱) 0:非空 1:空*/ while ((CAN1->TSR & CAN_TSR_TME0) == 0) ; /* 2. 使用标准标识符 0:标准标识符 1:扩展标识符 */ CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE; /* 3. 0:数据帧 or 1:远程帧 */ CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR; /* 4. 设置标准标识符 */ CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID; CAN1->sTxMailBox[0].TIR |= (stdId << 21); /* 5. 设置数据长度 */ CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC; CAN1->sTxMailBox[0].TDTR |= (length << 0); /* 6. 设置数据 */ uint8_t i; CAN1->sTxMailBox[0].TDLR = 0; /* 低位寄存器 */ CAN1->sTxMailBox[0].TDHR = 0; /* 高位寄存器 */ for (i = 0; i < length; i++) { if (i < 4) { CAN1->sTxMailBox[0].TDLR |= (data[i] << (8 * i)); } else { CAN1->sTxMailBox[0].TDHR |= (data[i] << (8 * (i - 4))); } } /* 7. 请求发送数据 */ CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ; } /** * @description: * @param {uint16_t} *stdId 读取数据的标准id * @param {uint8_t} *data 读取到的数据 * @param {uint8_t} *length 读取到的数据的长度 */ void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount) { /* 1. 获取 FIFO0 中的报文数 */ *msgCount = (CAN1->RF0R & CAN_RF0R_FMP0) >> 0; uint8_t i, j; for (i = 0; i < *msgCount; i++) { RxDataStruct *msg = &rxDataStruct[i]; /* 2. 读取标准标识符id */ msg->stdId = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7FF; /* 3. 读取数据长度 */ msg->length = (CAN1->sFIFOMailBox[0].RDTR >> 0) & 0x0F; /* 4. 读取数据 */ memset(msg->data, 0, sizeof((char *)msg->data)); uint32_t low = CAN1->sFIFOMailBox[0].RDLR; uint32_t high = CAN1->sFIFOMailBox[0].RDHR; for (j = 0; j < msg->length; j++) { if (j < 4) { msg->data[j] = (low >> (8 * j)) & 0xFF; } else { msg->data[j] = (high >> (8 * (j - 4))) & 0xFF; } } /* 5. 释放 FIFO 0. 则报文数减1*/ CAN1->RF0R |= CAN_RF0R_RFOM0; } }
HAL版本
main.c
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ /* 1. 配置过滤器 */ CAN_Filter_Config(); /* 2. 启动CAN总线 */ HAL_CAN_Start(&hcan); /* 3. 发送数据 */ uint16_t stdId = 0x011; uint8_t *tData = "abcdefg"; CAN_SendMsg(stdId, tData, strlen((char *)tData)); printf("发送完毕...\r\n"); tData = "123"; CAN_SendMsg(stdId, tData, strlen((char *)tData)); printf("发送完毕...\r\n"); /* 4. 接收数据 */ RxDataStruct rxDataStruct[8]; uint8_t rxMsgCount; CAN_ReceiveMsg(rxDataStruct, &rxMsgCount); printf("接收完毕 rxMsgCount = %d...\r\n", rxMsgCount); /* 5. 输出消息 */ uint8_t i; for (i = 0; i < rxMsgCount; i++) { RxDataStruct msg = rxDataStruct[i]; printf("stdId = %d, length = %d, msgData = %s\r\n", msg.stdId, msg.length, msg.data); } while (1) { } }
can.h中添加
/* USER CODE BEGIN Prototypes */ typedef struct { uint16_t stdId; uint8_t data[8]; uint8_t length; } RxDataStruct; void CAN_Filter_Config(void); void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length); void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount); /* USER CODE END Prototypes */
can.c中添加
/* USER CODE BEGIN 1 */ /** * @description: 配置过滤器 */ void CAN_Filter_Config() { CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank = 0; // 过滤器编号, CAN1是0-13, CAN2是14-27 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 采用掩码模式 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 设置筛选器的尺度, 采用32位 sFilterConfig.FilterIdHigh = 0X0000; // 过滤器ID高16位,即CAN_FxR1寄存器的高16位 sFilterConfig.FilterIdLow = 0X0000; // 过滤器ID低16位,即CAN_FxR1寄存器的低16位 sFilterConfig.FilterMaskIdHigh = 0X0000; // 过滤器掩码高16位,即CAN_FxR2寄存器的高16位 sFilterConfig.FilterMaskIdLow = 0X0000; // 过滤器掩码低16位,即CAN_FxR2寄存器的低16位 sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 设置经过筛选后数据存储到哪个接收FIFO sFilterConfig.FilterActivation = ENABLE; // 是否使能本筛选器 sFilterConfig.SlaveStartFilterBank = 14; // 指定为CAN1分配多少个滤波器组 HAL_CAN_ConfigFilter(&hcan, &sFilterConfig); } /** * @description: 发送信息 * @param {uint16_t} stdId * @param {uint8_t} *data * @param {uint8_t} length */ void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length) { /* 1. 检测发送邮箱是否可用 */ while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0) ; CAN_TxHeaderTypeDef txHeader; txHeader.IDE = CAN_ID_STD; // 标准帧还是扩展帧 txHeader.RTR = CAN_RTR_DATA; // 帧的类型: 数据帧还是远程帧 txHeader.StdId = stdId; // 标准帧的id txHeader.DLC = length; // 发送的数据长度 单位字节 uint32_t txMailBox; // 会把这次使用的邮箱存入到这个变量 /* 2. 发送消息 */ HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &txMailBox); } /** * @description: 接收消息 * @param {RxDataType} * */ void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount) { /* 1. 检测FIFO0收到的报文个数 */ *msgCount = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0); /* 2. 遍历出所有消息 */ uint8_t i; CAN_RxHeaderTypeDef rxHeader; for (i = 0; i < *msgCount; i++) { HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rxHeader, rxDataStruct[i].data); rxDataStruct[i].stdId = rxHeader.StdId; rxDataStruct[i].length = rxHeader.DLC; } } /* USER CODE END 1 */