Wiki:知识&笔记 Wiki:知识&笔记
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
  • 基础

  • 底层&寄存器

  • HAL库外设

    • HAL库函数
    • HAL结构
    • GPIO各种点亮LED
    • 定时器
    • PWM控制LED颜色
    • 按键外部中断
    • ADC使用
    • 串口与DMA
    • FLASH 读写
    • IIC通信
    • SPI 通信
    • CAN
      • 硬件与协议
        • 接线
        • 仲裁
        • 波特率
      • 代码层面
        • 滤波器
        • 接收信号
        • 发送信号
  • STM32
  • HAL库外设
2020-10-22
硬件与协议
接线
仲裁
波特率
代码层面
滤波器
接收信号
发送信号

CAN

这次粗线条的聊一聊 CAN 通讯,因为想要详细的讲通它,实在是太长了!

# 硬件与协议

# 接线

CAN 通讯只使用了两根线:CAN_H 与 CAN_L ,通过差分信号的方式区分显性与隐形电平,从而表达逻辑0与逻辑1

# 仲裁

由于 CAN 通常是以“一主多从”的形式连接的,所以通常需要对来自不同设备的 CAN 信号进行优先级的仲裁。

而CAN 的仲裁机制正好利用了差分信号的特性,即显性电平覆盖隐形电平的特性,如果出现多个设备同时发送的情况,则先输出隐形电平的设备会失去对总线的占有权。下图中 D 为显性电平,R 为隐形电平,通过该图可以很容易地理解 CAN 的仲裁机制:

# 波特率

对于波特率的计算,目前不需要知道详细的原理,只需要知道怎么计算即可,例如对于下图, APB1 总线上的时钟频率是:

波特率就是:

在 CubeMX 中的配置计算方法如下,对于更一般的,请参考这篇文章 (opens new window)。

# 代码层面

# 滤波器

CAN 总线上的信息由不同的设备发出,如果不进行任何手段的过滤、判别信号是否为我们需要的则将会接到很多的混杂的,所以有必要设置对信息的过滤规则以接收到我们需要的信息

这里依然引用一下这篇文章 (opens new window)的对于滤波器的形象解释:

重点介绍一下掩码模式的原理:掩码模式的思路很容易理解,举个例子,某所学校的学号构成方式为 [4 位 10 进制 入学年份]+[4 位 10 进制 学生序号],比如一个 2016 年入学的学生,其学号可以是 20161234,那么假如要开一个 2016 年毕业生的庆祝会,会场门口要检查每一个人的学号,只有 2016 级的才可以进入,这里应该使用什么样的判断方法呢?

首先,我们需要设置屏蔽码,屏蔽掉后四位的学生序号,因为他们和本次检测无关,反而增大了计算量。

然后设置检验法 2016,如果屏蔽后的结果等于 2016,则可以放行。

如下表所示,第一行为原码,第二行为掩码,将第一行表格中的数与掩码相乘,即得到第三行的屏蔽码,最后一行是验证码,屏蔽码和验证码比较确定一致后,就接收该学号:

2 0 1 6 1 2 3 4
1 1 1 1 0 0 0 0
2 0 1 6 0 0 0 0
2 0 1 6 0 0 0 0

对于 RM 官方的电调,我们对滤波器的设置与使能如下:

filter1.FilterIdHigh = 0x0000;                // 设置验证码高低各16位
filter1.FilterIdLow = 0x0000;
filter1.FilterMaskIdHigh = 0x0000;            // 设置屏蔽码高低各16位
filter1.FilterMaskIdLow = 0x0000;
filter1.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 通过CAN的信息放入0号FIFO
filter1.FilterBank = 0;
filter1.SlaveStartFilterBank=14;
filter1.FilterMode = CAN_FILTERMODE_IDMASK;   // 使用掩码模式
filter1.FilterScale = CAN_FILTERSCALE_32BIT;  // 设置32位宽
filter1.FilterActivation = ENABLE;            // 使能滤波器

HAL_CAN_ConfigFilter(&hcan, &filter1);
1
2
3
4
5
6
7
8
9
10
11
12

以上代码对于滤波器的使能就是:

  • 使用屏蔽码 0x00000000 将所有的信号均变为 0x00000000
  • 随后使用验证码 0x00000000 检测经屏蔽码过滤后的信号是否与此符合

所以,就是全部都接受,因为对于这个 CAN,其实只挂载了电调,而电调上的电机信号我们一定是要接收的。所以就产生了这个看似怪异的滤波器

# 接收信号

我们通过 CAN 的接收中断来接收来自 CAN 的信号,对于接收消息,我们需要额外使用 CAN_RxHeaderTypeDef 来定义一个消息接受句柄。对于消息接收句柄,我们只需要定义它而无需配置相关参数即可

首先我们需要开启 CAN 的接收中断:

HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING)
1

滤波器使能等等不在此处阐述,随后就是接受中断函数。在中断接收函数中,由于电机电调发送来的报文格式是拆为高8位与低8位。我们主要对数据进行拼接转换为我们需要的格式:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *Target_hcan)
{
    s_CAN_Message can1_rx_message;
    CAN_RxHeaderTypeDef Can1RxHeader;
    if(Target_hcan -> Instance == CAN1)
    {
        HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &Can1RxHeader, can1_rx_message.Data);
        switch (Can1RxHeader.StdId)
        {
            case 0x201:
            {
            	printf("CAN receives successfully.\n\r");
                motor.esc_back_position = can1_rx_message.Data[0]<<8 | can1_rx_message.Data[1];
                motor.esc_back_speed = can1_rx_message.Data[2]<<8 | can1_rx_message.Data[3];
                break;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

关于 RoboMaster 使用的电调格式等,依然请参考上面那篇文章。

# 发送信号

发送信号时,依然需要自己定义一个 CAN_TxHeaderTypeDef 类型的消息发送句柄,不同的是,消息发送句柄需要我们设置一些参数以表明 CAN 信号中的标识符,让别人正确的识别到这是从我们这里发出的信息。

void CANTx_SendCurrent(CAN_HandleTypeDef *Target_hcan, uint32_t id, int16_t current1, int16_t current2, int16_t current3, int16_t current4)
{
    s_CAN_Message tx_message;
    CAN_TxHeaderTypeDef CanTxHeader;

    uint32_t TX_MailBOX = CAN_TX_MAILBOX0;
	 // 设置发送消息句柄
    CanTxHeader.StdId = id;
    CanTxHeader.IDE = CAN_ID_STD;
    CanTxHeader.RTR = CAN_RTR_DATA;
    CanTxHeader.DLC = 0x08;

    tx_message.Data[0] = (unsigned char)(current1>>8);
    tx_message.Data[1] = (unsigned char)current1;
    tx_message.Data[2] = (unsigned char)(current2>>8);
    tx_message.Data[3] = (unsigned char)current2;
    tx_message.Data[4] = (unsigned char)(current3>>8);
    tx_message.Data[5] = (unsigned char)current3;
    tx_message.Data[6] = (unsigned char)(current4>>8);
    tx_message.Data[7] = (unsigned char)current4;

    if(HAL_CAN_AddTxMessage(Target_hcan, &CanTxHeader, tx_message.Data, &TX_MailBOX) != HAL_OK)
    {
        printf("CAN send failed!\r\n");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

我们发送信息主要使用了 HAL 库函数 HAL_CAN_AddTxMessage ,他还有一个参数是发送信箱的地址,也需要自行定义一下

最简单的方式就是这样,如需参考稍微高级一点的还请参考:

  • Robomaster电控入门(3)RM系列电机控制 (opens new window)
  • 基于STM32F429和HAL库的CAN收发例程 (opens new window)
  • CAN的过滤器的4种工作模式以及使用方法总结 (opens new window)
编辑 (opens new window)
上次更新: 2020/11/18, 13:11:00
SPI 通信

← SPI 通信

Theme by Vdoing | Copyright © 2020-2025 Jack :) | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式