引子
现在是2024年1月18号晚上零点半,电路工数等困难科目已经考完,只是剩一门马原
临近寒假的这一段时间颇为闲暇,于是在工作室寻得一些 M2006无刷电机和 C610电调 ,加上手头上的 C板,试着组一台个人未来比赛用的四驱底盘
依据大疆资料来看,电调需要使用CAN通信来控制,正中知识盲区,于是放下手中的马原教材(其实根本没有拿起来过),学习一下CAN
环境准备
前置知识
- STM32CubeMX的使用
- 一定的C语言使用经验
软件环境
- 代码生成
STM32CubeMX
(以HAL库为基础) - 编译工具
arm-none-eabi工具链
(使用其他编译器亦可) - 编写环境
VSCode
+Embedded IDE
(Keil和CubeIDE亦可) - 调试工具
Ozone
(本篇仅以此方法调试)
硬件环境
- 主控芯片
大疆C板-STM32F407IGH6
- 烧录工具
JLink
- 通讯目标
C610电调
设备文档
CAN的印象
何为CAN?
在查阅了很多资料后,我提取了几个关键词:总线结构
,串行通讯
,标准协议
,只需要两条线,即可解决沿途中设备的通信需求,例如,使用一块主控板加上CAN总线就可以很轻松的控制多个电机,极大缓解了布线带给我们的焦虑

至于书面,准确,乃至于繁缛的官方定义,我便不写入文章里,百度看看就好
CAN的硬件组成
我们可以称一个通讯单元为节点,一个节点一般有三个部分:微控制器, CAN控制器,CAN收发器,总线两端须串上120Ω的电阻,以模拟无限远传输线的特性阻抗,通过开关等手段来选择是否使用这个电阻

STM32芯片会自带CAN外设拓展,名为bxCAN (Basic Extended CAN - 基本拓展CAN)
,详细内容此处不展开
要注意,一般的STM32开发板是不带有CAN收发器的,需要自己另外购买,大疆C板是自带CAN收发器的,所以可以直接使用
CAN的回环测试
姑且暂停理论部分的讲解,繁杂的原理总是令人头大,使人望而却步,我们先启动开发软件,走通一个通讯的流程,再来细细分析其中的缘由,或者跳过理论,只掌握软件层的流程也是可以的
基本步骤:配置STM32CubeMX
> 配置CAN过滤器
> 发送接收报文
配置STM32CubeMX
启动CubeMX,选好芯片类型创建项目,首先把常规设置搞定
关于C板的一些注意点
- C板默认的
CAN1 PIN
不是原理图上的位置, 需要修改一下 - 注意C板的晶振是12MHz,要把输入频率调整成12MHz
- C板的外设电源和swd输入的电源不在一条线路,不能通过swd口供电,需要插上24v电源或者usb口供电,否则CAN的收发器将不工作,无法正常收发数据,当然,回环模式还是可以收到的,因为回环的数据不经过CAN收发器
- 一对一连接C板和电调时,需要将电调上的电阻打开,一对多时,把最远端的电阻打开即可,保持CAN总线两端串着电阻




项目管理类型之类的根据自己使用的开发环境来设置即可
简单写一个点灯测试一下
这是板载灯的连线
TIM5_CH1
-LED_BLUE
TIM5_CH2
-LED_GREEN
TIM5_CH3
-LED_RED
void breath_led()
{
for (int i = 0; i < 100; i++) {
HAL_Delay(10);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, 20000 * i / 100);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, 20000 * i / 100);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, 20000 * i / 100);
}
for (int i = 100; i > 0; i--) {
HAL_Delay(10);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, 20000 * i / 100);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, 20000 * i / 100);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, 20000 * i / 100);
}
}
将其放入主循环中运行,理所应当地成功了

现在开始配置CAN通信
CubeMX界面中,在CAN1
的Parameter Settings我们可以看到
- Bit Timings Parameters - 配置传输速度
- Prescaler (for Time Quantum) - 分频,调整TQ(Time Quantum)大小
- Time Quantum - 最小时间单位
- Time Quanta in Bit Segment 1 - 相位缓冲段1段占几个TQ
- Time Quanta in Bit Segment 2 - 相位缓冲段2段占几个TQ
- Time for one Bit
- Baud Rate - 波特率
- ReSynchronization Jump Width - 再同步补偿宽度
- Basic Parameters - 基本参数
- Time Triggered Communication Mode - 时间触发模式
- Automatic Bus-off Management - 自动离线管理
- Automatic Wake-Up Mode - 自动唤醒
- Automatic Retransmission - 自动重传
- Receive Fifo Locked Mode - 锁定模式
- Transmit Fifo Priority - 报文发送优先级
- Advanced Parameters - 高级参数
- Operating Mode -*运行模式:
正常模式
静默模式
回环模式
回环静默模式
- Operating Mode -*运行模式:
而 NVIC Interrupt Table 中有
- CAN1 TX interrupts
- CAN1 RX0 interrupts
- CAN1 RX1 interrupt
- CAN1 SCE interrupt
这是我们初期需要关注的配置列表
1. 设置波特率
先打开CAN1的Activated
选项, 其他的选项才能显示出来

以我的需求为例,查阅大疆官方资料可以得知
将 CAN 信号线连接到控制板接收 CAN 控制指令,CAN 总线比特率为 1Mbps
所以我们需要将CAN通讯的比特率 baud rate
设置为 1000000 bit/s
根据波特率计算公式 BaudRate = TQ * ( Sync + TBS1 + TBS2) , 我们得到如下设置

根据实际情况计算一下即可,也可以多选几个选项,把正确的波特率尝试出来,灰色的选项就是CubeMX帮我们计算好的数值
别忘了修改CAN1的引脚

2. 打开中断
处理电调发送的电机信息,需要中断来调用回调函数,于是打开接收中断
在接收和发送信息前,我们会遇到一个CAN通信的抽象概念 —— 邮箱
这里我使用的单片机中,CAN外设具有两个用于接收信息的邮箱,我们命其为 FIFO0
和FIFO1
,每个邮箱都有一个过滤器,用于筛选报文,可以存放三条报文,在中断设置中对应 CAN1 RX0 interrupt
和CAN1 RX1 interrupt
,我们打开需要使用的那一个就可以

既然存在接收邮箱,相应的,就有发送邮箱,我们现在只要知道发送邮箱存在发送优先级且每个邮箱只能存放一条报文
现在,我们已经在CubeMX中配置好了CAN,下一步就是要配置CAN过滤器
配置CAN过滤器
前面我们说到,STM32上有两个邮箱用于接收报文,为了接收我们想要的报文,我们需要配置一下过滤器,把不想接受的报文过滤掉,只放行想要的报文
配置过滤器需要我们自己手写,并未提前生成,但HAL库提供了过滤器配置参数的结构体类型,我们只需要给这个结构体赋值,然后调用HAL提供的初始化函数即可完成配置, 下面的代码仅仅是展示, 还不需要写进项目里
// Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_can.h
// 过滤器结构体
typedef struct
{
uint32_t FilterIdHigh;
uint32_t FilterIdLow;
uint32_t FilterMaskIdHigh;
uint32_t FilterMaskIdLow;
uint32_t FilterFIFOAssignment;
uint32_t FilterBank;
uint32_t FilterMode;
uint32_t FilterScale;
uint32_t FilterActivation;
uint32_t SlaveStartFilterBank;
} CAN_FilterTypeDef;
// 配置函数
HAL_StatusTypeDef HAL_CAN_ConfigFilter(
CAN_HandleTypeDef *hcan,
CAN_FilterTypeDef *sFilterConfig
);
具体的使用和结构体的定义随后再讲,我们只需要对这个结构体和函数有一个大概的印象即可
发送接收报文
首先是发送
我预期使用一块主控与四个电机通信,那么在发送报文时,就需要指定发送给哪一个电机,以及其他一些信息,比如发送信息的长度
,信息的类型
,信息ID类型
等等,HAL把这些发送需要的信息定义成了一个结构体 CAN_TxHeaderTypeDef
,我们只需要为每一个电机声明一个 CAN_TxHeaderTypeDef
结构体,再确定好发送的数据内容,就可以将数据发送到指定的电机中
我们回想一下,在设置接收中断时,是不是提到了邮箱的概念?STM32F407IGHx为我们提供了三个发送邮箱,在发送时,HAL库会自动选择空闲的邮箱,然后将实际使用的邮箱返回给我们,这也解释了我们传入函数的是指向邮箱的指针,而非一个邮箱编号的常量
HAL库理所应当地帮我们写好了发送的函数,只要传入can的句柄
,报文头结构体
,数据信息
和邮箱
即可
下面的代码同样不需要写进项目
// Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_can.h
// 发送函数的声明
HAL_StatusTypeDef HAL_CAN_AddTxMessage(
CAN_HandleTypeDef *hcan,
CAN_TxHeaderTypeDef *pHeader,
uint8_t aData[],
uint32_t *pTxMailbox
);
// 邮箱编号的定义
#define CAN_TX_MAILBOX0 (0x00000001U) /*!< Tx Mailbox 0 */
#define CAN_TX_MAILBOX1 (0x00000002U) /*!< Tx Mailbox 1 */
#define CAN_TX_MAILBOX2 (0x00000004U) /*!< Tx Mailbox 2 */
然后是接收
总线上的报文在经过了我们设置的过滤器后,正确的报文会触发我们设置的中断,我们便可以在中断的回调函数中对收到的数据进行处理了
我们只需要找到HAL库为我们提供的中断函数,对其进行覆写即可
以下是示例代码
// 这是一种使用情况
// 回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
if(hcan->Instance ==CAN1)
{
HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, date_CAN1);
return ;
}