can是总线性架构,有双绞线的存在,抗干扰性极强。有非破坏性仲裁,自带纠错。
can总线没有时钟线,异步通信,需要靠位同步来约束数据收发。让接收方的采样点,永远死死咬住发送方数据位的正中心,保证不读错数据。

数据帧

RTR 作为远程发送请求位,为0时是显性电平,代表数据帧(CAN_RTR_DATA)。
为1时是隐性电平,代表遥控帧(CAN_RTR_REMOTE)。
IDE 作为标识符拓展位,为0是显性电平,代表标准帧(CAN_ID_STD)
为1是隐性电平,代表拓展帧(CAN_ID_EXT)
DLC表示数据有多少个字节
r0,r1为保留位,目前没有具体定义的用途
接收节点发送一个显性电平表示应答,随后发送七个隐性电平表示结束

can活动内核
主要负责:

can的位选取



三个要点:

关于can的邮箱机制
3个独立的发送邮箱,发送邮箱本质上是影子寄存器。总共有三组影子寄存器,每组寄存器有4个32位寄存器。位于can外设的寄存器空间,不在SRAM里,所以不能用memset,memcpy操作,只能通过HAL API或直接读写寄存器来操作。
以邮箱0为例

在外设寄存器,跟fifo相比是一个深度为3,不按照先后顺序,每格带独立状态的发送缓冲区,它存在的首要目的不是存数据,而是为了让CPU不要等待总线。
多个邮箱都待发时,先按id优先级来发,id数值小的先发。id相同时,邮箱号小的先发。
can的内部回环测试
先定义一些变量
CAN_TxHeaderTypeDef TxHeader; // 发送帧头
CAN_RxHeaderTypeDef RxHeader; // 接收帧头
uint8_t TxData[8]; // 发送数据
uint8_t RxData[8]; // 接收数据
uint8_t SentData[8]; // 发送快照(比对用)
uint32_t TxMailbox; // 使用的邮箱号(0~2)
/* ---------- 统计 ---------- */
uint32_t FrameCnt = 0; // 帧序号(改 32 位,防回绕)
uint32_t PassCnt = 0; // 通过数
uint32_t FailCnt = 0; // 失败数
/* ---------- 循环变量 ---------- */
uint32_t i; // 收发共用的临时下标
配置滤波器和启动
/* ============ CAN 验收滤波器 + 启动 ============ */
CAN_FilterTypeDef sFilterConfig; // 建议改成 = {0}:该结构体有 10 个字段,
// HAL 会把它们全部读入寄存器,未赋值的字段是栈上垃圾值
/* ---- 第 1 组:用哪一组滤波器(F103CB 共有 14 组,编号 0~13)---- */
sFilterConfig.FilterBank = 0; // 只用组 0,其余 13 组保持未激活(不参与匹配)
/* ---- 第 2 组:匹配方式 ---- */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式:写 "ID + 掩码",掩码位=1必须相同,=0不关心
// 另一种是 IDLIST(列表模式),逐个精确匹配
/* ---- 第 3 组:位宽 ---- */
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32 位宽:一组 = 1 个 ID + 1 个掩码
// 16 位宽则一组能放 2 个 ID + 2 个掩码,但只能筛标准帧
/* ---- 第 4 组:ID 和掩码,全 0 = 全通 ---- */
sFilterConfig.FilterIdHigh = 0x0000; // 期望 ID 的高 16 位(标准帧 ID 放在 bit[31:21])
sFilterConfig.FilterIdLow = 0x0000; // 期望 ID 的低 16 位
sFilterConfig.FilterMaskIdHigh = 0x0000; // 掩码高 16 位:0x0000 = 这些位"全部不关心"
sFilterConfig.FilterMaskIdLow = 0x0000; // 掩码低 16 位
// 判据:(收到的ID & 掩码) == (期望ID & 掩码)
// 掩码为 0 → 0 == 0 → 恒成立 → 任何帧都放行
/* ---- 第 5 组:放行后送哪个 FIFO ---- */
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 送 FIFO0(也可以选 FIFO1,两个独立队列)
/* ---- 第 6 组:是否启用这一组 ---- */
sFilterConfig.FilterActivation = ENABLE; // 置 FA1R 的 bit0 = 1
// ⚠️ 复位默认所有组都是"关闭"状态 = 任何帧都不收
/* ---- 写入硬件 ---- */
if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
printf("ConfigFilter error\r\n"); // ⚠️ 只报告不处理,失败后程序继续往下跑
}
/* ---- 启动 CAN(退出初始化模式,允许收发)---- */
if (HAL_CAN_Start(&hcan) != HAL_OK)
{
printf("CAN START error\r\n"); // ⚠️ 同上,失败后接着发帧,会一直 mailboxes full
}
printf("CAN init OK, filter all-pass, mode=LOOPBACK\r\n"); // 检查点 1:能打出这行说明前面都过了
组建帧头部
TxHeader.StdId = 0x123;
TxHeader.ExtId = 0;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.DLC = 8;
TxHeader.TransmitGlobalTime = DISABLE;
把数据发到邮箱
for(i=0;i<8;i++) // 填满 8 字节数据域
{
TxData[i]=(uint8_t)(FrameCnt+i); // 第 i 字节 = 帧号低 8 位 + i → 递增图案
}
memcpy(SentData,TxData,8); // 发前留快照,收回来后拿它比对
if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan)>0) // 先查 3 个发送邮箱有没有空的
{ // (0 = 全满,再发也是失败)
if(HAL_CAN_AddTxMessage(&hcan,&TxHeader,TxData,&TxMailbox)==HAL_OK)
{ // 入队成功:数据已复制进硬件寄存器
FrameCnt++; // 只有真正入队才计数
printf("TX: %-3u ",FrameCnt); // 打印帧号,十进制,左对齐
for(i=0;i<8;i++)
{
printf("%02x",TxData[i]); // 8 字节十六进制,补齐两位
}
printf("\r\n");
}
}
else // ← 这个 else 对应的是外层 if
{
printf("TX mailboxes full\r\n"); // 三个邮箱全满:总线忙 / 收不到 ACK
}
把数据从FIFO中取出
uint32_t start = HAL_GetTick(); // 记下等待起点(ms 时间戳)
while(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0) // FIFO0 里 0 帧就继续轮询
{
if (HAL_GetTick() - start > 20) break; // 超过 20ms 放弃,防止卡死
}
if (HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) > 0) // 再确认一次:真有帧才往下走
{
HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, RxData); // 出队,FMP 自动 −1
if (RxHeader.StdId == 0x123 && // ① ID 对不对
RxHeader.DLC == 8 && // ② 长度对不对(防 memcmp 越界)
memcmp(SentData, RxData, 8) == 0) // ③ 8 字节逐字节相同
{
PassCnt++; // 通过计数
printf("RX ID=0x%03X DLC=%d ", RxHeader.StdId, RxHeader.DLC);
for (i = 0; i < 8; i++) printf("%02X ", RxData[i]); // 8 字节,大写,带空格
printf("PASS\r\n");
}
else // 收到帧,但 ID/长度/内容有一项不符
{
FailCnt++; // 失败计数
printf("RX ID=0x%03X DLC=%d ", RxHeader.StdId, RxHeader.DLC);
for (i = 0; i < 8; i++) printf("%02X ", RxData[i]);
printf("FAIL\r\n");
}
}
数据先发到邮箱,硬件等待总线空闲,由can内核进行组帧同时回环给接收端,CRC校验,经由滤波器检查对照id,发到fifo中,等待数据被取走
遇到一个问题
程序烧录进去串口助手收不到信息,之前的程序可以
通过debug找到原因,断点卡在main入口处。芯片里存在一个硬件断点,位置在main的第一条指令,CPU一进入main就停住,根本没有运行。

解决办法,断电清除断点,下一次运行不要勾选run to main,这个选项会给main入口处打上断点