【17】STM32·HAL库·CAN

目录

一、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.0ACAN 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
  • 32
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HZU_Puzzle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值