目录

一、CAN基础知识介绍

1.1、CAN介绍

1.2、CAN物理层

1.3、CAN协议层

1.3.1、CAN帧种类介绍

1.3.2、CAN数据帧介绍

1.3.3、CAN位时序介绍

1.3.4、CAN总线仲裁(优先级)

二、STM32 CAN控制器介绍

2.1、CAN控制器介绍

2.2、CAN控制器模式

2.2.1、CAN控制器工作模式

2.2.2、CAN控制器测试模式

2.3、CAN控制器框图

2.4、CAN控制器接收过滤器

2.5、CAN控制器位时序

三、CAN相关寄存器介绍

3.1、CAN主控制寄存器(CAN_MCR)

3.2、CAN位时序寄存器(CAN_BTR)

3.3、CAN标识符寄存器(CAN_(T/R)IxR)

3.4、数据长度和时间戳寄存器(CAN_(T/R)DTxR)

3.5、CAN低位数据寄存器(CAN_(T/R)DLxR)

3.6、CAN高位数据寄存器(CAN_(T/R)DHxR)

3.7、CAN过滤器模式寄存器(CAN_FM1R)

3.8、CAN过滤器位宽寄存器(CAN_FS1R)

3.9、CAN过滤器FIFO关联寄存器(CAN_FFA1R)

3.10、CAN过滤器激活寄存器(CAN_FA1R)

3.11、CAN过滤器组x寄存器(CAN_FxR(1/2))

四、CAN相关HAL库驱动介绍

五、CAN基本驱动步骤

六、编程实战

使用回环模式实现自发自收


一、CAN基础知识介绍

1.1、CAN介绍

CAN(Controller Area Network),是ISO国际标准化的串行通信协议。

为了满足汽车产业的“减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信”的需求。

低速 CAN(ISO11519) 通信速率 10~125Kbps,总线长度可达 1000 米

高速 CAN(ISO11898) 通信速率 125Kbps~1Mbps,总线长度 ≤40 米(经典CAN)

CAN FD 通信速率可达 5Mbps,并且兼容经典 CAN,遵循 ISO 11898-1 做数据收发

CAN总线拓扑图

终端电阻,用于阻抗匹配,以减少回波反射

CAN 总线由两根线( CANL 和 CANH )组成,允许挂载多个设备节点(低速 CAN:20 高速CAN:30 )

CAN总线特点

1、多主控制,每个设备都可以主动发送数据

2、系统的柔软性,没有类似地址的信息,添加设备不改变原来总线的状态

3、通信速度,速度快,距离远

4、错误检测 & 错误通知 & 错误恢复功能

5、故障封闭,判断故障类型,并且进行隔离

6、连接节点多,速度与数量找个平衡

CAN 总线协议已广泛应用在汽车电子、工业自动化、船舶、医疗设备、工业设备等方面

1.2、CAN物理层

CAN 使用差分信号进行数据传输,根据 CAN_H 和 CAN_L 上的电位差来判断总线电平

总线电平分为显性电平(逻辑0)和隐性电平(逻辑1),二者必居其一

显性电平具有优先权。发送方通过使总线电平发生变化,将消息发送给接收方

1.3、CAN协议层

1.3.1、CAN帧种类介绍

CAN 总线以“帧”形式进行通信。CAN 协议定义了 5 种类型的帧:数据帧遥控帧错误帧过载帧间隔帧,其中数据帧最为常用

1.3.2、CAN数据帧介绍

数据帧由 7 段组成。数据帧又分为标准帧(CAN2.0A)和扩展帧(CAN2.0B),主要体现在仲裁段和控制段:

帧起始:表示数据帧开始的段,显性信号

仲裁段:表示该帧优先级的段,优先级

控制段:表示数据的字节数及保留位的段

数据段:数据的内容,一帧可发送 0~8 字节数据

CRC段:检查帧的传输错误的段

ACK段:表示确认正常接收的段

帧结束:表示数据帧结束的段,7 个隐性信号

1.3.3、CAN位时序介绍

CAN 总线以“位同步”机制,实现对电平的正确采样。位数据都由四段组成:同步段(SS)传播时间段(PTS)相位缓冲段1(PBS1)相位缓冲段2(PBS2),每段又由多个位时序 Tq 组成

注意 : 节点监测到总线上信号的跳变在 SS 段范围内,表示节点与总线的时序是同步,此时采样点的电平即该位的电平

采样点是指读取总线电平,并将读到的电平作为位值的点,根据位时序,就可以计算 CAN 通信的波特率

数据同步过程(时钟频率误差、传输上的相位延迟引起偏差)

CAN为了实现对总线电平信号的正确采样,数据同步分为硬件同步和再同步

硬件同步

节点通过 CAN 总线发送数据,一开始发送帧起始信号。总线上其他节点会检测帧起始信号在不在位数据的 SS 段内,判断内部时序与总线是否同步

假如不在 SS 段内,这种情况下,采样点获得的电平状态是不正确的。所以,节点会使用硬件同步方式调整, 把自己的 SS 段平移到检测到边沿的地方,获得同步,同步情况下,采样点获得的电平状态才是正确的

再同步

再同步利用普通数据位的边沿信号(帧起始信号是特殊的边沿信号)进行同步。再同步的方式分为两种情况:超前滞后,即边沿信号与 SS 段的相对位置

再同步时,PSB1 和 PSB2 中增加或者减少的时间被称为“再同步补偿宽度(SJW)”,其范围:1~4 Tq

限定了 SJW 值后,再同步时,不能增加限定长度的 SJW 值。SJW 值较大时,吸收误差能力更强,但是通讯速度会下降

1.3.4、CAN总线仲裁(优先级)

CAN 总线处于空闲状态,最先开始发送消息的单元获得发送权

多个单元同时开始发送时,从仲裁段(报文ID)的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送,即首先出现隐性电平的单元失去对总线的占有权变为接收

竞争失败单元,会自动检测总线空闲,在第一时间再次尝试发送

二、STM32 CAN控制器介绍

2.1、CAN控制器介绍

STM32 CAN 控制器(bxCAN),支持 CAN 2.0A 和 CAN 2.0B Active 版本协议

CAN 2.0A 只能处理标准数据帧且扩展帧的内容会识别错误

CAN 2.0B Active 可以处理标准数据帧和扩展数据帧

CAN 2.0B Passive 只能处理标准数据帧且扩展帧的内容会忽略

bxCAN 主要特点

波特率最高可达 1M bps

支持时间触发通信(CAN 的硬件内部定时器可以在 TX/RX 的帧起始位的采样点位置生成时间戳)

具有 3 级发送邮箱

具有 3 级深度的 2 个接收 FIFO

可变的过滤器组(最多 28 个)

2.2、CAN控制器模式

2.2.1、CAN控制器工作模式

CAN 控制器的工作模式有三种:初始化模式、正常模式和睡眠模式

2.2.2、CAN控制器测试模式

CAN 控制器的测试模式有三种:静默模式、环回模式和环回静默模式(初始化模式下进行配置)

2.3、CAN控制器框图

1、CAN 内核:包含各种控制/状态/配置寄存器,可以配置模式、波特率等

2、发送邮箱:用来缓存待发送的报文,最多可以缓存 3 个报文

3、接收 FIFO:缓存接收到的有效报文

4、接收过滤器:筛选有效报文

发送处理

发送优先级由邮箱中报文的标识符决定。标识符数值越低有最高优先级。如果标识符值相同,邮箱小的先被发送

接收处理

有效报文指的是(数据帧直到 EOF 段的最后一位都没有错误),且通过过滤器组对标识符过滤

2.4、CAN控制器接收过滤器

当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用 CPU。过滤器的存在,选择性接收有效报文,减轻系统负担

每个过滤器组都有两个 32 位寄存器 CAN_FxR1 和 CAN_FxR2。根据过滤器组的工作模式不同,寄存器的作用不尽相同

位宽可设置 32 位或 16 位,寄存器存储的内容就有所区别:

选择模式可设置屏蔽位模式或标识符列表模式,寄存器内容的功能就有所区别:

屏蔽位模式:可以选择出一组符合条件的报文。寄存器内容功能相当于是否符合条件

标识符列表模式:可以选择出几个特定 ID 的报文。寄存器内容功能就是标识符本身

屏蔽位寄存器中位值为 1,表示与 ID 要必须匹配;位值为 0,表示可不与 ID 匹配

在使能过滤器情况下,总线上广播的报文 ID 与过滤器的配置都不匹配,CAN 控制器会丢弃该报文,不会进入到接收 FIFO 中

注意:标识符选择位 IDE 和帧类型 RTR 需要一致。不同过滤器组的工作模式可以设置为不同

2.5、CAN控制器位时序

STM32的CAN外设位时序分为三段:

同步段 SYNC_SEG

时间段1 BS1(PTS + PBS1)

时间段2 BS2

在 STM32F407,设 TS1=6、TS2=5、BRP=5,波特率 = 42000 / [( 7 + 6 + 1 ) * 6] = 500Kbps。

注意:通信双方波特率需要一致才能通信成功

三、CAN相关寄存器介绍

3.1、CAN主控制寄存器(CAN_MCR)

INRQ 位,用于控制初始化请求

3.2、CAN位时序寄存器(CAN_BTR)

在 STM32F407,设 TS1=6、TS2=5、BRP=5

波特率 = 42000 / [( 7 + 6 + 1 ) * 6] = 500Kbps

3.3、CAN标识符寄存器(CAN_(T/R)IxR)

发送 x 范围:1~3,3 个发送邮箱

接收 x 范围:1~2,2 个接收 FIFO 邮箱

注意:报文使用扩展标识符时,STID[10:0] 等效于 EXID[28:18],与 EXID[17:0] 组成 29 位扩展标识符

3.4、数据长度和时间戳寄存器(CAN_(T/R)DTxR)

发送 x 范围:1~3,3 个发送邮箱

接收 x 范围:1~2,2 个接收 FIFO 邮箱

注意:DLC 是多少,数据内容就有多少字节被发送,并不是每次都发送 8 个字节数据

3.5、CAN低位数据寄存器(CAN_(T/R)DLxR)

3.6、CAN高位数据寄存器(CAN_(T/R)DHxR)

3.7、CAN过滤器模式寄存器(CAN_FM1R)

注意:CAN 外设只能使用的有的过滤器组,不能使用没有的过滤器组

3.8、CAN过滤器位宽寄存器(CAN_FS1R)

3.9、CAN过滤器FIFO关联寄存器(CAN_FFA1R)

该寄存器决定了哪个 FIFO 寄存器有效(即 RIxR、RDTxR、RDLxR、RDHxR 的 ‘x’ )

3.10、CAN过滤器激活寄存器(CAN_FA1R)

使用哪个过滤器组,就在对应位置 1 即可。前提:过滤器组需设置在初始化模式

3.11、CAN过滤器组x寄存器(CAN_FxR(1/2))

四、CAN相关HAL库驱动介绍

相关 HAL 库函数介绍

CAN 外设相关重要结构体

CAN_InitTypeDef

typedef struct 
{
    uint32_t Prescaler			    /* 预分频 */
    uint32_t Mode				    /* 工作模式 */
    uint32_t SyncJumpWidth		    /* 再次同步跳跃宽度 */
    uint32_t TimeSeg1			    /* 时间段1(BS1)长度 */
    uint32_t TimeSeg2			    /* 时间段1(BS1)长度 */
    uint32_t TimeTriggeredMode	    /* 时间触发通信模式 */
    uint32_t AutoBusOff			    /* 总线自动关闭 */
    uint32_t AutoWakeUp			    /* 自动唤醒 */
    uint32_t AutoRetransmission 	/* 自动重传 */
    uint32_t ReceiveFifoLocked		/* 接收FIFO锁定 */
    uint32_t TransmitFifoPriority	/* 传输FIFO优先级 */
}CAN_InitTypeDef;

CAN_FilterTypeDef

typedef struct 
{
    uint32_t FilterIdHigh			/* ID高字节 */
    uint32_t FilterIdLow			/* ID低字节 */
    uint32_t FilterMaskIdHigh	 	/* 掩码高字节 */
    uint32_t FilterMaskIdLow		/* 掩码低字节 */
    uint32_t FilterFIFOAssignment	/* 过滤器关联FIFO */
    uint32_t FilterBank			    /* 选择过滤器组 */
    uint32_t FilterMode			    /* 过滤器模式*/
    uint32_t FilterScale			/* 过滤器位宽 */
    uint32_t FilterActivation		/* 过滤器使能 */
    uint32_t SlaveStartFilterBank 	/* 从CAN选择启动过滤器组 单CAN没有意义*/
}CAN_FilterTypeDef;

需要结合映射去赋值

32位位宽 STID[10:3] STID[2:0] EXID[17:13] EXID[12:5] EXID[4:0] IDE RTR 0

16位位宽 STID[10:3] STID[2:0] RTR IDE EXID[17:15]

CAN_TxHeaderTypeDef

typedef struct 
{
    uint32_t StdId			    /* 标准标识符 */
    uint32_t ExtId			    /* 扩展标识符 */
    uint32_t IDE			    /* 帧格式(标准帧或扩展帧) */
    uint32_t RTR			    /* 帧类型(数据帧或远程帧) */
    uint32_t DLC			    /* 数据长度 */
    uint32_t TransmitGlobalTime	/* 发送时间标记(时间戳) */
}CAN_TxHeaderTypeDef;

CAN_RxHeaderTypeDef

typedef struct 
{
    uint32_t StdId			
    uint32_t ExtId		
    uint32_t IDE	
    uint32_t RTR	
    uint32_t DLC		
    uint32_t Timestamp	         /* 时间戳 */
    uint32_t FilterMatchIndex    /* 过滤器号  */
}CAN_RxHeaderTypeDef;

五、CAN基本驱动步骤

1、CAN 参数初始化:使用 HAL_CAN_Init(),配置工作模式、波特率等

2、使能 CAN 时钟和初始化相关引脚:使用 HAL_CAN_MspInit(),GPIO 设为复用功能模式

3、设置过滤器:使用 HAL_CAN_ConfigFilter(),完成过滤器的初始化

4、CAN 数据接收和发送:使用 HAL_CAN_AddTxMessage() 发送消息,使用 HAL_CAN_GetRxMessage() 接收消息

5、使能 CAN 相关中断/设置NVIC/编写中断服务函数:使用 __HAL_CAN_ENABLE_IT()

六、编程实战

使用回环模式实现自发自收

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/CAN/can.h"

int main(void)
{
    uint8_t key;
    uint8_t i = 0, t = 0;
    uint8_t cnt = 0;
    uint8_t canbuf[8];
    uint8_t rxlen = 0;
    uint8_t res;
    uint8_t mode = 1; /* CAN工作模式: 0,普通模式; 1,环回模式 */

    HAL_Init();                                                            /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);                                    /* 设置时钟,168Mhz */
    delay_init(168);                                                       /* 延时初始化 */
    usart_init(115200);                                                    /* 串口初始化为115200 */
    led_init();                                                            /* 初始化LED */
    key_init();                                                            /* 初始化按键 */
    lcd_init();                                                            /* 初始化LCD */
    can_init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_7TQ, 6, CAN_MODE_LOOPBACK); /* CAN初始化, 环回模式, 波特率500Kbps */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "CAN TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "LoopBack Mode", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY0:Send KEK_UP:Mode", RED); /* 显示提示信息 */

    lcd_show_string(30, 150, 200, 16, 16, "Count:", RED);        /* 显示当前计数值 */
    lcd_show_string(30, 170, 200, 16, 16, "Send Data:", RED);    /* 提示发送的数据 */
    lcd_show_string(30, 230, 200, 16, 16, "Receive Data:", RED); /* 提示接收到的数据 */

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES) /* KEY0按下,发送一次数据 */
        {
            for (i = 0; i < 8; i++)
            {
                canbuf[i] = cnt + i; /* 填充发送缓冲区 */

                if (i < 4)
                {
                    lcd_show_xnum(30 + i * 32, 190, canbuf[i], 3, 16, 0x80, BLUE); /* 显示数据 */
                }
                else
                {
                    lcd_show_xnum(30 + (i - 4) * 32, 210, canbuf[i], 3, 16, 0x80, BLUE); /* 显示数据 */
                }
            }

            res = can_send_msg(0x12, canbuf, 8); /* ID = 0x12, 发送8个字节 */

            if (res)
            {
                lcd_show_string(30 + 80, 170, 200, 16, 16, "Failed", BLUE); /* 提示发送失败 */
            }
            else
            {
                lcd_show_string(30 + 80, 170, 200, 16, 16, "OK    ", BLUE); /* 提示发送成功 */
            }
        }
        else if (key == WKUP_PRES) /* WK_UP按下, 改变CAN的工作模式 */
        {
            mode = !mode;
            /* CAN初始化, 普通(0)/回环(1)模式, 波特率500Kbps */
            can_init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_7TQ, 6, mode ? CAN_MODE_LOOPBACK : CAN_MODE_NORMAL);

            if (mode == 0) /* 普通模式, 需要2个开发板 */
            {
                lcd_show_string(30, 110, 200, 16, 16, "Normal Mode  ", RED);
            }
            else /* 回环模式,一个开发板就可以测试了. */
            {
                lcd_show_string(30, 110, 200, 16, 16, "LoopBack Mode", RED);
            }
        }

        rxlen = can_receive_msg(0x12, canbuf); /* CAN ID = 0x12, 接收数据查询 */

        if (rxlen) /* 接收到有数据 */
        {
            lcd_fill(30, 270, 130, 310, WHITE); /* 清除之前的显示 */

            for (i = 0; i < rxlen; i++)
            {
                if (i < 4)
                {
                    lcd_show_xnum(30 + i * 32, 250, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
                }
                else
                {
                    lcd_show_xnum(30 + (i - 4) * 32, 270, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
                }
            }
        }

        t++;
        delay_ms(10);

        if (t == 20)
        {
            LED0_TOGGLE(); /* 提示系统正在运行 */
            t = 0;
            cnt++;
            lcd_show_xnum(30 + 48, 150, cnt, 3, 16, 0X80, BLUE); /* 显示数据 */
        }
    }
}

can.c

#include "./BSP/CAN/can.h"

CAN_HandleTypeDef g_canx_handler;    /* CANx句柄 */
CAN_TxHeaderTypeDef g_canx_txheader; /* 发送参数句柄 */
CAN_RxHeaderTypeDef g_canx_rxheader; /* 接收参数句柄 */

/**
 * @brief       CAN初始化
 * @param       tsjw    : 重新同步跳跃时间单元.范围: 1~3;
 * @param       tbs2    : 时间段2的时间单元.范围: 1~8;
 * @param       tbs1    : 时间段1的时间单元.范围: 1~16;
 * @param       brp     : 波特率分频器.范围: 1~1024;
 *   @note      以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0
 *              CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 = PCLK1 = 42Mhz
 *              tq     = brp * tpclk1;
 *              波特率 = Fpclk1 / ((tbs1 + tbs2 + 1) * brp);
 *              我们设置 can_init(1, 6, 7, 6, 1), 则CAN波特率为:
 *              42M / ((6 + 7 + 1) * 6) = 500Kbps
 *
 * @param       mode    : CAN_MODE_NORMAL,  普通模式;
                          CAN_MODE_LOOPBACK,回环模式;
 * @retval      0,  初始化成功; 其他, 初始化失败;
 */
uint8_t can_init(uint32_t tsjw, uint32_t tbs2, uint32_t tbs1, uint16_t brp, uint32_t mode)
{
    g_canx_handler.Instance = CAN1;
    g_canx_handler.Init.Prescaler = brp;                /* 分频系数(Fdiv)为brp+1 */
    g_canx_handler.Init.Mode = mode;                    /* 模式设置 */
    g_canx_handler.Init.SyncJumpWidth = tsjw;           /* 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ */
    g_canx_handler.Init.TimeSeg1 = tbs1;                /* tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ */
    g_canx_handler.Init.TimeSeg2 = tbs2;                /* tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ */
    g_canx_handler.Init.TimeTriggeredMode = DISABLE;    /* 非时间触发通信模式 */
    g_canx_handler.Init.AutoBusOff = DISABLE;           /* 软件自动离线管理 */
    g_canx_handler.Init.AutoWakeUp = DISABLE;           /* 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) */
    g_canx_handler.Init.AutoRetransmission = ENABLE;    /* 禁止报文自动传送 */
    g_canx_handler.Init.ReceiveFifoLocked = DISABLE;    /* 报文不锁定,新的覆盖旧的 */
    g_canx_handler.Init.TransmitFifoPriority = DISABLE; /* 优先级由报文标识符决定 */
    if (HAL_CAN_Init(&g_canx_handler) != HAL_OK)
    {
        return 1;
    }

#if CAN_RX0_INT_ENABLE

    /* 使用中断接收 */
    __HAL_CAN_ENABLE_IT(&g_canx_handler, CAN_IT_RX_FIFO0_MSG_PENDING); /* FIFO0消息挂号中断允许 */
    HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);                                 /* 使能CAN中断 */
    HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0);                         /* 抢占优先级1,子优先级0 */
#endif

    CAN_FilterTypeDef sFilterConfig;

    /* 配置CAN过滤器 */
    sFilterConfig.FilterBank = 0; /* 过滤器0 */
    sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
    sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
    sFilterConfig.FilterIdHigh = 0x0000; /* 32位ID */
    sFilterConfig.FilterIdLow = 0x0000;
    sFilterConfig.FilterMaskIdHigh = 0x0000; /* 32位MASK */
    sFilterConfig.FilterMaskIdLow = 0x0000;
    sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; /* 过滤器0关联到FIFO0 */
    sFilterConfig.FilterActivation = CAN_FILTER_ENABLE;    /* 激活滤波器0 */
    sFilterConfig.SlaveStartFilterBank = 14;

    /* 过滤器配置 */
    if (HAL_CAN_ConfigFilter(&g_canx_handler, &sFilterConfig) != HAL_OK)
    {
        return 2;
    }

    /* 启动CAN外围设备 */
    if (HAL_CAN_Start(&g_canx_handler) != HAL_OK)
    {
        return 3;
    }

    return 0;
}

/**
 * @brief       CAN底层驱动,引脚配置,时钟配置,中断配置
                此函数会被HAL_CAN_Init()调用
 * @param       hcan:CAN句柄
 * @retval      无
 */
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
    if (CAN1 == hcan->Instance)
    {
        CAN_RX_GPIO_CLK_ENABLE();    /* CAN_RX脚时钟使能 */
        CAN_TX_GPIO_CLK_ENABLE();    /* CAN_TX脚时钟使能 */
        __HAL_RCC_CAN1_CLK_ENABLE(); /* 使能CAN1时钟 */

        GPIO_InitTypeDef gpio_init_struct;

        gpio_init_struct.Pin = CAN_TX_GPIO_PIN;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;
        gpio_init_struct.Pull = GPIO_PULLUP;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
        gpio_init_struct.Alternate = GPIO_AF9_CAN1;
        HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_init_struct); /* CAN_TX脚 模式设置 */

        gpio_init_struct.Pin = CAN_RX_GPIO_PIN;
        HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_init_struct); /* CAN_RX脚 必须设置成输入模式 */
    }
}

#if CAN_RX0_INT_ENABLE /* 使能RX0中断 */

/**
 * @brief       CAN RX0 中断服务函数
 *   @note      处理CAN FIFO0的接收中断
 * @param       无
 * @retval      无
 */
void USB_LP_CAN1_RX0_IRQHandler(void)
{
    uint8_t rxbuf[8];
    uint32_t id;
    can_receive_msg(id, rxbuf);
    printf("id:%d\r\n", g_canx_rxheader.StdId);
    printf("ide:%d\r\n", g_canx_rxheader.IDE);
    printf("rtr:%d\r\n", g_canx_rxheader.RTR);
    printf("len:%d\r\n", g_canx_rxheader.DLC);

    printf("rxbuf[0]:%d\r\n", rxbuf[0]);
    printf("rxbuf[1]:%d\r\n", rxbuf[1]);
    printf("rxbuf[2]:%d\r\n", rxbuf[2]);
    printf("rxbuf[3]:%d\r\n", rxbuf[3]);
    printf("rxbuf[4]:%d\r\n", rxbuf[4]);
    printf("rxbuf[5]:%d\r\n", rxbuf[5]);
    printf("rxbuf[6]:%d\r\n", rxbuf[6]);
    printf("rxbuf[7]:%d\r\n", rxbuf[7]);
}

#endif

/**
 * @brief       CAN 发送一组数据
 *   @note      发送格式固定为: 标准ID, 数据帧
 * @param       id      : 标准ID(11位)
 * @param       msg     : 数据指针
 * @param       len     : 数据长度
 * @retval      发送状态 0, 成功; 1, 失败;
 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len)
{
    uint16_t t = 0;
    uint32_t TxMailbox = CAN_TX_MAILBOX0;

    g_canx_txheader.StdId = id;         /* 标准标识符 */
    g_canx_txheader.ExtId = id;         /* 扩展标识符(29位) */
    g_canx_txheader.IDE = CAN_ID_STD;   /* 使用标准帧 */
    g_canx_txheader.RTR = CAN_RTR_DATA; /* 数据帧 */
    g_canx_txheader.DLC = len;

    if (HAL_CAN_AddTxMessage(&g_canx_handler, &g_canx_txheader, msg, &TxMailbox) != HAL_OK) /* 发送消息 */
    {
        return 1;
    }

    while (HAL_CAN_GetTxMailboxesFreeLevel(&g_canx_handler) != 3) /* 等待发送完成,所有邮箱为空 */
    {
        t++;

        if (t > 0xFFF)
        {
            HAL_CAN_AbortTxRequest(&g_canx_handler, TxMailbox); /* 超时,直接中止邮箱的发送请求 */
            return 1;
        }
    }

    return 0;
}

/**
 * @brief       CAN 接收数据查询
 *   @note      接收数据格式固定为: 标准ID, 数据帧
 * @param       id      : 要查询的 标准ID(11位)
 * @param       buf     : 数据缓存区
 * @retval      接收结果
 *   @arg       0   , 无数据被接收到;
 *   @arg       其他, 接收的数据长度
 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{
    if (HAL_CAN_GetRxFifoFillLevel(&g_canx_handler, CAN_RX_FIFO0) == 0) /* 没有接收到数据 */
    {
        return 0;
    }

    if (HAL_CAN_GetRxMessage(&g_canx_handler, CAN_RX_FIFO0, &g_canx_rxheader, buf) != HAL_OK) /* 读取数据 */
    {
        return 0;
    }

    if (g_canx_rxheader.StdId != id || g_canx_rxheader.IDE != CAN_ID_STD || g_canx_rxheader.RTR != CAN_RTR_DATA) /* 接收到的ID不对 / 不是标准帧 / 不是数据帧 */
    {
        return 0;
    }

    return g_canx_rxheader.DLC;
}

can.h

#ifndef __CAN_H
#define __CAN_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"

/******************************************************************************************/
/* CAN 引脚 定义 */

#define CAN_RX_GPIO_PORT                GPIOA
#define CAN_RX_GPIO_PIN                 GPIO_PIN_11
#define CAN_RX_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)     /* PA口时钟使能 */

#define CAN_TX_GPIO_PORT                GPIOA
#define CAN_TX_GPIO_PIN                 GPIO_PIN_12
#define CAN_TX_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)     /* PA口时钟使能 */

/******************************************************************************************/

/* CAN接收RX0中断使能 */
#define CAN_RX0_INT_ENABLE              CAN1_RX0_IRQHandler

/* 函数声明 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf);                                     /* CAN接收数据, 查询 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len);                           /* CAN发送数据 */
uint8_t can_init(uint32_t tsjw,uint32_t tbs2,uint32_t tbs1,uint16_t brp,uint32_t mode); /* CAN初始化 */

#endif

作者:HZU_Puzzle

物联沃分享整理
物联沃-IOTWORD物联网 » 【17】STM32·HAL库·CAN

发表回复