打印
[研电赛技术支持]

CAN总线入门教程——实现数据收发

[复制链接]
2182|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-7-9 11:33 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1. 简介
        CAN 总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以 CAN 为底层协议专为大型货车和重工机械车辆设计的 J1939 协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。

        CAN 总线控制器作为 CAN 网络接口,遵循 CAN 总线协议 2.0A 和 2.0B 。 CAN 总线控制器 可以处理总线上的数据收发,非 GD32130x CL 系列产品中,具有 14 个过滤器,在 GD32F10xCL 系列产品中, CAN 具有 28 个过滤器,过滤器用于筛选并接收用户需要的消息。用户可以通过 3 个发送邮箱将待发送数据传输至总线,邮箱发送的顺序由发送调度器决定。并通过 2 个深度为 3 的接收 FIFO 获取总线上的数据,接收 FIFO 的管理完全由硬件控制。同时 CAN 总线控制器硬件支持时间触发通信(Time-trigger communication )功能。
2.GD32的CAN外设介绍
MCU:GD32F103VET6

2.1 CAN初始化结构体
API:ErrStatus can_init(uint32_t can_periph, can_parameter_struct* can_parameter_init)

can_periph:CANx(x=0,1), CAN1 仅适用于 GD32F10x_CL

can_parameter_struct:初始化结构体

ErrStatus: SUCCESS or ERROR

/* CAN initiliaze parameters structure */
typedef struct
{
    uint8_t working_mode;                         /*!< 配置CAN的工作模式 */
    uint8_t resync_jump_width;                    /*!< 重新同步跳跃宽度 */
    uint8_t time_segment_1;                       /*!< 配置 BS1 段长度 */
    uint8_t time_segment_2;                       /*!< 配置 BS2 段长度 */
    ControlStatus time_triggered;                 /*!< 时间触发通信方式 */
    ControlStatus auto_bus_off_recovery;          /*!< 自动离线管理 */
    ControlStatus auto_wake_up;                   /*!< 自动唤醒功能 */
    ControlStatus auto_retrans;                   /*!< 自动重传模式 */
    ControlStatus rec_fifo_overwrite;             /*!< 配置接收 FIFO 锁定 */
    ControlStatus trans_fifo_order;               /*!< 配置 FIFO 优先级 */
    uint16_t prescaler;                           /*!< 配置CAN外设的时钟频率 */
}can_parameter_struct;
(1)working_mode

        设置CAN的工作模式,可设置为正常通信模式(CAN_NORMAL_MODE),回环通信模式(CAN_LOOPBACK_MODE),静默通信模式(CAN_SILENT_MODE)以及回环静默通信模式(CAN_SILENT_LOOPBACK_MODE)。

         CAN 总线控制器通常工作在正常通信模式下,可以从 CAN 总线接收数据,也可以向 CAN 总线发送数据。(详情请查看GD32F10x用户手册)

(2)resync_jump_width

        设置CAN的再同步补偿宽度SJW,对CAN网络节点同步误差进行补偿占1~4个时间单元。(CAN_BT_SJW_1/2/3/4TQ)

(3)time_segment_1

        设置CAN位时序中BS1段长度,可以配置为1~16个时间单元。(CAN_BT_BS1_1~16TQ)

(4)time_segment_2

        设置CAN位时序中BS2段长度,可以配置为1~8个时间单元。(CAN_BT_BS2_1~8TQ)

(5)time_triggered

        用于配置是否使用时间触发功能(ENABLE / DISABLE)。在这种通信模式下,自动重发功能是禁止的。

(6)auto_bus_off_recovery

        用于配置是否使用自动离线管理功能(ENABLE / DISABLE),使用自动离线管理可以在节点出错离线后适应自动恢复,不需要软件干预。

(7)auto_wake_up

        用于配置是否使用自动唤醒功能(ENABLE / DISABLE),使用自动唤醒功能后会在检测到总线活动后自动唤醒。

(8)auto_retrans

        用于配置是否使用自动重传功能(ENABLE / DISABLE),使用自动重传功能时,会一直发送报文直到成功为止,否则只会发送一次报文。

(9)rec_fifo_overwrite

        用于配置是否使用接收FIFO锁定功能(ENABLE / DISABLE),锁定接收FIFO后,FIFO溢出时会丢弃新数据,否则在FIFO溢出时新数据覆盖旧数据。

(10)trans_fifo_order

        用于设置是否使用发送报文的优先级判定方法(ENABLE / DISABLE),使能后,以报文存入FIFO的先后顺序来发送,否则按照报文ID的优先级来发送。

(11)prescaler

        设置CAN外设的时钟分频,写入的值即为分频值。可控制时间片的时间长度。

波特率计算:



快速计算:BaudRate = APB1_LCK / ( 1 + time_segment_1 +time_segment_2 ) / prescaler

例:APB1总线时钟频率为54MHZ,BS1=5,BS2=3,prescaler=12,实际波特率为500Kbps。

2.2 CAN过滤器结构体
API:void can_filter_init(can_filter_parameter_struct* can_filter_parameter_init)

can_filter_parameter_struct:过滤器结构体

/* CAN filter parameters structure */
typedef struct
{
    uint16_t filter_list_high;                 /*!< 过滤器列表高位数 */
    uint16_t filter_list_low;                  /*!< 过滤器列表低位数 */
    uint16_t filter_mask_high;                 /*!< 滤波掩码数高位数 */
    uint16_t filter_mask_low;                  /*!< 滤波掩码数低位数 */
    uint16_t filter_fifo_number;               /*!< 接收与过滤器相关联的FIFO */
    uint16_t filter_number;                    /*!< 筛选器编号 */
    uint16_t filter_mode;                      /*!< 列表或掩码模式 */
    uint16_t filter_bits;                      /*!< 筛选器位宽 */
    ControlStatus filter_enable;               /*!< 是否使能改筛选器 */
}can_filter_parameter_struct;




注:SFID为标准帧ID,长度为11bit;EFID为扩展帧ID,长度为18bit。

         FF为帧格式(0:标准帧 / 1:扩展帧),FT为帧类型(0:数据帧 / 1:遥控帧)

(1)filter_list_high

        用于存储要过滤的ID,若过滤器工作在32位模式,他存储的是所过滤ID的高16位;若过滤器工作在16位模式,它存储的就是一个完整的要过滤的ID。

(2)filter_list_low

        用于存储要过滤的ID,若过滤器工作在32位模式,他存储的是所过滤ID的低16位;若过滤器工作在16位模式,它存储的就是一个完整的要过滤的ID。

(3)filter_mask_high

        filter_mask_high的存储的内容分两种情况,当过滤器工作在列表模式时,他的功能与filter_list_high相同,都是存储要过滤的ID;当工作在掩码模式时,它存储的是与filter_list_high成员对应的掩码。

(4)filter_mask_low

        filter_mask_low的存储的内容分两种情况,当过滤器工作在列表模式时,他的功能与filter_list_low相同,都是存储要过滤的ID;当工作在掩码模式时,它存储的是与filter_list_low成员对应的掩码。



在掩码模式下,filter_mask_high与filter_mask_low填入的是要筛选的掩码,掩码为1时,筛选的ID必须与ID相同。

列表模式下,筛选的ID需与ID相同。

(5)filter_fifo_number

        用于设置当报文通过过滤器匹配后,该报文会被存储到哪个接收FIFO中(CAN_FIFO0/1)。

(6)filter_number

        用于设计过滤器的编号。可以通过设置多个过滤器对特定的ID进行过滤。



(7)filter_mode

        设置过滤器的工作模式,可以设置为列表模式(CAN_FILTERMODE_LIST),也可以设置为掩码模式(CAN_FILTERMODE_MASK)。

(8)filter_bits

        设置过滤器位宽(32位或16位)。

(9)filter_enable

        用于设置是否使能这个过滤器(ENABLE/DISABLE)。

2.3 CAN发送及接收结构体
API:

uint8_t can_message_transmit(uint32_t can_periph, can_trasnmit_message_struct* transmit_message);//发送CAN报文

void can_message_receive(uint32_t can_periph, uint8_t fifo_number, can_receive_message_struct* receive_message);//接收CAN报文

/* CAN 发送报文结构 */
typedef struct
{
    uint32_t tx_sfid;                      /*!< 标准帧ID */
    uint32_t tx_efid;                      /*!< 扩展帧ID */
    uint8_t tx_ff;                         /*!< 帧格式,标准帧或数据帧 */
    uint8_t tx_ft;                         /*!< 帧类型,数据帧或遥控帧 */
    uint8_t tx_dlen;                       /*!< 数据长度 */
    uint8_t tx_data[8];                    /*!< 传输的数据 */
}can_trasnmit_message_struct;

/* CAN 接收报文结构 */
typedef struct
{
    uint32_t rx_sfid;                       /*!< 标准帧ID */
    uint32_t rx_efid;                       /*!< 扩展帧ID */
    uint8_t rx_ff;                          /*!< 帧格式,标准帧或数据帧 */
    uint8_t rx_ft;                          /*!< 帧类型,数据帧或遥控帧 */
    uint8_t rx_dlen;                        /*!< 数据长度 */
    uint8_t rx_data[8];                     /*!< 接收的数据 */
    uint8_t rx_fi;                          /*!< 过滤器编号 */
} can_receive_message_struct;

3. 硬件与引脚连接
MCU:GD32F103VET6;

CAN模块;//CAN收发器,GD32的CAN只是控制器



RS232_CAN模块;//用于单片机与电脑通信



3.1 MCU引脚选择:



        在这3组引脚中可任意选择一组作为CAN0的通信引脚。使用PB8、PB9或PD0、PD1时,需分别打开GPIO的部分重映射和完全重映射功能,本文中使用PB8、PB9两个引脚。

API:void gpio_pin_remap_config(uint32_t remap, ControlStatus newvalue)

GPIO_CAN_PARTIAL_REMAP:CAN部分重映射

GPIO_CAN_FULL_REMAP:CAN全重映射

3.2 MCU与CAN模块连接



        在数据通信引脚连接时,不需要反接,MCU的RX接CAN收发器RX即可。

3.3 CAN模块与RS232_CAN模块连接
没有RS232_CAN模块也可使用双机通信,RS232_CAN模块实际也是一个单片机与CAN收发器连接与电脑进行通信。



4. 驱动代码
4.1 CAN初始化配置
/*
        \brief:        CAN0初始化配置 波特率500Kbps
        引脚连接:        CAN_TX   PB9
                                CAN_RX   PB8
        \param:        none
        \retval:        none
*/

void CAN_Init(void)
{
        can_parameter_struct can0_param_struct;
        can_filter_parameter_struct can0_filter_param_struct;        //筛选器结构体
        //1.引脚初始化配置
        gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP,ENABLE);        //开启CAN部分重映射
        gpio_init(GPIOB,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,GPIO_PIN_8);
        gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_9);
       
        //2.初始化CNA0
        can_deinit(CAN0);                                                                                //复位CAN0
        can0_param_struct.working_mode = CAN_NORMAL_MODE;                //正常通信模式
        can0_param_struct.time_triggered = DISABLE;                                //静止时间触发通信
        can0_param_struct.auto_bus_off_recovery = DISABLE;                //静止离线恢复
        can0_param_struct.auto_wake_up = DISABLE;                                //禁止自动唤醒
        can0_param_struct.auto_retrans = ENABLE;                                //使能自动重传
        can0_param_struct.rec_fifo_overwrite = DISABLE;                        //禁用接收FIFO锁定
        can0_param_struct.trans_fifo_order = DISABLE;                        //禁用FIFO优先级
        //波特率 54M/(1+3+5)/12
        can0_param_struct.resync_jump_width = CAN_BT_SJW_1TQ;        //再同步宽度补偿 1TQ
        can0_param_struct.time_segment_1 = CAN_BT_BS1_3TQ;                //位段1长度
        can0_param_struct.time_segment_2 = CAN_BT_BS2_5TQ;                //位段2长度
        can0_param_struct.prescaler = 12;                                                //预分频系数
        if(can_init(CAN0,&can0_param_struct)==ERROR)
        {
                return ;
        }
        //3.配置CAN过滤器
        can0_filter_param_struct.filter_list_high = (((uint32_t)0x000<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;        //过滤器高字节
        can0_filter_param_struct.filter_list_low =  (((uint32_t)0x000<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);        //过滤器低字节
        can0_filter_param_struct.filter_mask_high = 0x0000;                        //过滤器掩码数高位
        can0_filter_param_struct.filter_mask_low =         0x0000;                        //过滤器掩码数低位
        can0_filter_param_struct.filter_fifo_number = CAN_FIFO0;        //滤过器关联FIFO0
        can0_filter_param_struct.filter_number = 0;
        can0_filter_param_struct.filter_mode = CAN_FILTERMODE_MASK;        //掩码模式
        can0_filter_param_struct.filter_bits = CAN_FILTERBITS_32BIT;//32位
        can0_filter_param_struct.filter_enable = ENABLE;
        can_filter_init(&can0_filter_param_struct);
        //4.配置中断
        can_interrupt_enable(CAN0,CAN_INT_RFNE0);
}

(1)配置GPIO工作模式。配置CAN部分重映射,将PB8配置为上拉输入模式,PB9配置为复用推挽输出模式。

(2)初始化CAN0。该结构体重要的是将CAN配置为正常通信模式,并配置波特率,其他功能配置可根据实际需求配置。值得注意的是,在配置波特率时需要配置准确,否则将无法进行通信。

(3)配置过滤器。在这里选择了32位掩码模式,关联FIFO0。过滤器掩码高低位都设置为0x0000000,接收所有ID的数据。这里先确保可以接收到数据。



filter_list_high和filter_list_low中需要填入需要筛选的ID,由上图可知,32位过滤器中,

[2:0]位保存的是数据帧格式和帧类型。此处设置的帧格式为标准帧CAN_FF_STANDARD(也可设置为扩展帧CAN_FF_EXTENDED)。帧类型设置为数据帧CAN_FT_DATA(也可设置为数据帧)。

[20:3]保存扩展帧ID。

[31:21]保存标准帧ID。此处将标准帧ID(0x000)左移21位。

filter_mask_high和filter_mask_low保存过滤器掩码数,用于筛选需要的ID。在此设置为0,表示接收所有ID的数据。

(4)使能FIFO不为空中断。

4.2 发送CAN报文
/*
        \brief:        发送CAN报文
        \param:        id:标准帧ID
                                data:要发送的数据
                                len:数据长度 0~8byte
        \retval:        none
*/
uint8_t CAN_Send_Message(uint32_t id,uint8_t *data,uint8_t len)
{
        uint8_t i,mbox;
        can0_Tx_Data.tx_efid = 0x00;
        can0_Tx_Data.tx_sfid = id;
        can0_Tx_Data.tx_ff = CAN_FF_STANDARD;//标准帧
        can0_Tx_Data.tx_ft = CAN_FT_DATA;//数据帧
        can0_Tx_Data.tx_dlen = len;
        for(i=0;i<len;i++)
        {
                can0_Tx_Data.tx_data = data;
        }
        mbox = can_message_transmit(CAN0,&can0_Tx_Data);                                //发送CAN报文
        while(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_PENDING);        //等待发送完成
        if(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_OK)
        {
                return 0;
        }
        return 1;
}

发送CAN报文主要是填充can_trasnmit_message_struct 结构体,调用can_message_transmit函数发送报文即可。在等待发送完成中可加入超时重发或者强制退出,避免函数卡死。

4.3 接收CAN报文
//中断服务函数,接收CAN报文
void USBD_LP_CAN0_RX0_IRQHandler(void)
{
        if(can_interrupt_flag_get(CAN0,CAN_INT_FLAG_RFL0))
        {
                memset(&can0_Rx_Data,0,sizeof(can0_Rx_Data));//清空接收结构体
        can_message_receive(CAN0,CAN_FIFO0,&can0_Rx_Data);//获取信息
        }
}
        FIFO非空是会触发CAN非空中断,首先初始化接收结构体,然后读取FIFO中的数据。在GD32F103的CAN中断中没有清除非空标志位这个函数,can_message_receive接收CAN报文函数中不仅将数据保存至结构体中,还释放了一次数据。



观用户手册可知,FIFO中可保存3帧报文。FIFO0帧的数量保存在CAN_RFIFO0寄存器的RFL0[1:0]中,置位RFD0将释放一次FIFO中帧的数据即RFL0[1:0] - 1。



4.4 main函数
#include "main.h"


void rcu_config(void);
void nvic_config(void);


/*!
    \brief      main function
    \param[in]  none
    \param[out] none
    \retval     none
*/

int main(void)
{
    //gpio_afio_deinit();
        /* system clocks configuration */
    rcu_config();
        nvic_config();
    /* systick configuration */
    systick_config();
        UART_init();
    /* Initialize the peripheral module */
        CAN_Init();
        uint8_t t_data[8]={0,99,3,4,5,55,7,110};
    while(1){
                CAN_Send_Message(0x0000,t_data,8);
                delay_1ms(1000);
        }
}

/*!
    \brief      configure the different system clocks
    \param[in]  none
    \param[out] none
    \retval     none
*/
void rcu_config(void)
{
    /* enable GPIO clock */
        rcu_periph_clock_enable(RCU_AF);
        rcu_periph_clock_enable(RCU_GPIOA);
        rcu_periph_clock_enable(RCU_GPIOB);
        rcu_periph_clock_enable(RCU_GPIOC);
    rcu_periph_clock_enable(RCU_GPIOD);
        rcu_periph_clock_enable(RCU_GPIOE);
    /* enable periph clock */
    rcu_periph_clock_enable(RCU_CAN0);
        rcu_periph_clock_enable(RCU_DMA0);
        rcu_periph_clock_enable(RCU_USART2);
        rcu_periph_clock_enable(RCU_UART3);
}

/*!
    \brief      enable the different NVIC request
    \param[in]  none
    \param[out] none
    \retval     none
*/
void nvic_config(void)
{
        nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
        nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn,1,0);
}

main.c中还是一如既往的配置外设时钟,配置中断优先级和初始化外设。

(1)时钟配置:在使用过程中只需使能AFIO、GPIOB和CAN0时钟即可。

(2)配置中断优先级分组和中断优先级。

(3)初始化外设。

(4)每秒钟向外发送一次报文。

4.5 实验结果
电脑接收报文:软件配置波特率为500Kbps



         在RS232_CAN软件上,发送和读取的数据为16进制。MCU发送的数据与电脑上接收的数据一致。

MCU接收报文:



         使用KEIL软件上的调试功能,发送数据后,代码运行至打断点位置,单步运行至函数最下方,读取到的数据保存至接收CAN报文结构体中。数据长度为8个字节,数据与发送的数据一致。RS232_CAN软件上的标准帧ID设置为0x000,所以MCU中读取到的ID也为0x000。使用双机进行通信的代码配置和操作基本相同。

5. 使用筛选功能
        上述测试中将过滤器掩码设置为0,即接收所有ID报文,无法体现CAN的筛选器功能。以下将修改ID和掩码,测试是否能筛选特定ID。

5.1 筛选固定ID数据
本次测试只接收一个ID的数据,其他ID数据不接收。

RS232_CAN帧模式



标准帧ID范围为0x000~0x7FF,共11为数据。此次测试只接收ID为0x7FF的标准数据帧,其他数据帧一律不接收。

过滤器中写入的ID:(32位宽)

过滤器  1111 1110 0000 0000 0000 0000 0000 0000    //[31:21]位为标准帧ID

掩码      1111 1110 0000 0000 0000 0000 0000 0110     //[2:1]筛选标准数据帧,[31:21]筛选ID

掩码中填入的数据即:0xFE00 0006;      //扩展ID位也可全填入1

void CAN_Init(void)
{
        can_parameter_struct can0_param_struct;
        can_filter_parameter_struct can0_filter_param_struct;        //筛选器结构体
        //1.引脚初始化配置
        gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP,ENABLE);        //开启CAN部分重映射
        gpio_init(GPIOB,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,GPIO_PIN_8);
        gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_9);
       
        //2.初始化CNA0
        can_deinit(CAN0);                                                                                //复位CAN0
        can0_param_struct.working_mode = CAN_NORMAL_MODE;                //正常通信模式
        can0_param_struct.time_triggered = DISABLE;                                //静止时间触发通信
        can0_param_struct.auto_bus_off_recovery = DISABLE;                //静止离线恢复
        can0_param_struct.auto_wake_up = DISABLE;                                //禁止自动唤醒
        can0_param_struct.auto_retrans = ENABLE;                                //使能自动重传
        can0_param_struct.rec_fifo_overwrite = DISABLE;                        //禁用接收FIFO锁定
        can0_param_struct.trans_fifo_order = DISABLE;                        //禁用FIFO优先级
        //波特率 54M/(1+3+5)/12
        can0_param_struct.resync_jump_width = CAN_BT_SJW_1TQ;        //再同步宽度补偿 1TQ
        can0_param_struct.time_segment_1 = CAN_BT_BS1_3TQ;                //位段1长度
        can0_param_struct.time_segment_2 = CAN_BT_BS2_5TQ;                //位段2长度
        can0_param_struct.prescaler = 12;                                                //预分频系数
        if(can_init(CAN0,&can0_param_struct)==ERROR)
        {
                return ;
        }
        //3.配置CAN过滤器
        can0_filter_param_struct.filter_list_high = (((uint32_t)0x7FF<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;        //过滤器高字节
        can0_filter_param_struct.filter_list_low =  (((uint32_t)0x7FF<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);        //过滤器低字节
        can0_filter_param_struct.filter_mask_high = 0xFFE0;                        //过滤器掩码数高位
        can0_filter_param_struct.filter_mask_low =         0x0006;                        //过滤器掩码数低位
        can0_filter_param_struct.filter_fifo_number = CAN_FIFO0;        //滤过器关联FIFO0
        can0_filter_param_struct.filter_number = 0;
        can0_filter_param_struct.filter_mode = CAN_FILTERMODE_MASK;        //掩码模式
        can0_filter_param_struct.filter_bits = CAN_FILTERBITS_32BIT;//32位
        can0_filter_param_struct.filter_enable = ENABLE;
        can_filter_init(&can0_filter_param_struct);
        //4.配置中断
        can_interrupt_enable(CAN0,CAN_INT_RFNE0);
}

代码只修改了以下几处。



测试1:ID号与帧格式相同

发送:00 00 00 07 FF 08 11 22 33 44 55 66 77 88

接收:ID:0x7FF(标准数据帧)

数据长度:8

数据: 11 22 33 44 55 66 77 88



测试2:帧格式相同,ID不同

发送:00 00 00 06 FF 08 11 22 33 44 55 66 77 88

接收:无接收,程序未进入中断。



测试3:ID相同,数据帧不同

发送:01 00 00 07 FF 08 11 22 33 44 55 66 77 88

接收:无接收,未程序未进入中断。



测试成功!

5.2 接收多个ID数据
在具体使用过程中,会存在接收多个ID数据的情况,本次测试只接收多个固定ID的数据。

例如:标准帧ID:0x000~0x7FF.

只接收标准帧ID为:0x200~0x2FF的数据。

过滤器    0010 0000 0000 0000 0000 0000 0000 0000

掩码        1110 0000 0000 0000 0000 0000 0000 0110

掩码中填入0xE000 0006。

由于只接收ID为0x200~0x2FF的数据,只需将掩码的[31:29]位都置一。

测试1:写入范围内的标准数据帧ID

写入:00 00 00 02 00 08 11 22 33 44 55 66 77 88

接收:ID:0x200(标准数据帧)

数据长度:8

数据:11 22 33 44 55 66 77 88



写入:00 00 00 02 19 08 11 22 33 44 55 66 77 99

接收:ID:0x219(标准数据帧)

数据长度:8

数据:11 22 33 44 55 66 77 99



测试2:写入范围外的ID

发送:00 00 00 03 19 08 11 22 33 44 55 66 77 99

接收:无接收(程序未进入中断)

测试成功!



6. 参考程序
can.c


#include "can.h"
#include <string.h>
can_receive_message_struct can0_Rx_Data;
can_trasnmit_message_struct can0_Tx_Data;
/*
        \brief:        CAN0初始化配置 波特率500Kbps
        引脚连接:        CAN_TX   PB9
                                CAN_RX   PB8
        \param:        none
        \retval:        none
*/

void CAN_Init(void)
{
        can_parameter_struct can0_param_struct;
        can_filter_parameter_struct can0_filter_param_struct;        //筛选器结构体
        //1.引脚初始化配置
        gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP,ENABLE);        //开启CAN部分重映射
        gpio_init(GPIOB,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,GPIO_PIN_8);
        gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_9);
       
        //2.初始化CNA0
        can_deinit(CAN0);                                                                                //复位CAN0
        can0_param_struct.working_mode = CAN_NORMAL_MODE;                //正常通信模式
        can0_param_struct.time_triggered = DISABLE;                                //静止时间触发通信
        can0_param_struct.auto_bus_off_recovery = DISABLE;                //静止离线恢复
        can0_param_struct.auto_wake_up = DISABLE;                                //禁止自动唤醒
        can0_param_struct.auto_retrans = ENABLE;                                //使能自动重传
        can0_param_struct.rec_fifo_overwrite = DISABLE;                        //禁用接收FIFO锁定
        can0_param_struct.trans_fifo_order = DISABLE;                        //禁用FIFO优先级
        //波特率 54M/(1+3+5)/12
        can0_param_struct.resync_jump_width = CAN_BT_SJW_1TQ;        //再同步宽度补偿 1TQ
        can0_param_struct.time_segment_1 = CAN_BT_BS1_3TQ;                //位段1长度
        can0_param_struct.time_segment_2 = CAN_BT_BS2_5TQ;                //位段2长度
        can0_param_struct.prescaler = 12;                                                //预分频系数
        if(can_init(CAN0,&can0_param_struct)==ERROR)
        {
                return ;
        }
        //3.配置CAN过滤器
        can0_filter_param_struct.filter_list_high = (((uint32_t)0x200<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;        //过滤器高字节
        can0_filter_param_struct.filter_list_low =  (((uint32_t)0x200<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);        //过滤器低字节
        can0_filter_param_struct.filter_mask_high = 0xE000;                        //过滤器掩码数高位
        can0_filter_param_struct.filter_mask_low =         0x0006;                        //过滤器掩码数低位
        can0_filter_param_struct.filter_fifo_number = CAN_FIFO0;        //滤过器关联FIFO0
        can0_filter_param_struct.filter_number = 0;
        can0_filter_param_struct.filter_mode = CAN_FILTERMODE_MASK;        //掩码模式
        can0_filter_param_struct.filter_bits = CAN_FILTERBITS_32BIT;//32位
        can0_filter_param_struct.filter_enable = ENABLE;
        can_filter_init(&can0_filter_param_struct);
        //4.配置中断
        can_interrupt_enable(CAN0,CAN_INT_RFNE0);
}
/*
        \brief:        发送CAN报文
        \param:        id:标准帧ID
                                data:要发送的数据
                                len:数据长度 0~8byte
        \retval:        none
*/
uint8_t CAN_Send_Message(uint32_t id,uint8_t *data,uint8_t len)
{
        uint8_t i,mbox;
        can0_Tx_Data.tx_efid = 0x00;
        can0_Tx_Data.tx_sfid = id;
        can0_Tx_Data.tx_ff = CAN_FF_STANDARD;//标准帧
        can0_Tx_Data.tx_ft = CAN_FT_DATA;//数据帧
        can0_Tx_Data.tx_dlen = len;
        for(i=0;i<len;i++)
        {
                can0_Tx_Data.tx_data = data;
        }
        mbox = can_message_transmit(CAN0,&can0_Tx_Data);                                //发送CAN报文
        while(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_PENDING);        //等待发送完成
        if(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_OK)
        {
                return 0;
        }
        return 1;
}
//中断服务函数,接收CAN报文
void USBD_LP_CAN0_RX0_IRQHandler(void)
{
        if(can_interrupt_flag_get(CAN0,CAN_INT_FLAG_RFL0))
        {
                memset(&can0_Rx_Data,0,sizeof(can0_Rx_Data));//清空接收结构体
                can_message_receive(CAN0,CAN_FIFO0,&can0_Rx_Data);//获取信息
        }
}

can.h

#ifndef _CAN_H_
#define _CAN_H_

#include "gd32f10x.h"

#define CAN0_ID         0x200        //ID号

extern can_receive_message_struct can0_Rx_Data;//读取信息结构体
extern can_trasnmit_message_struct can0_Tx_Data;//发送信息结构体

void CAN_Init(void);
uint8_t CAN_Send_Message(uint32_t id,uint8_t *data,uint8_t len);

#endif
mian.c


#include "main.h"


void rcu_config(void);
void nvic_config(void);


/*!
    \brief      main function
    \param[in]  none
    \param[out] none
    \retval     none
*/

int main(void)
{
    //gpio_afio_deinit();
        /* system clocks configuration */
    rcu_config();
        nvic_config();
    /* systick configuration */
    systick_config();
        //UART_init();
    /* Initialize the peripheral module */
        CAN_Init();
        uint8_t t_data[8]={0,99,3,4,5,55,7,110};
    while(1){
                CAN_Send_Message(0x0000,t_data,8);
                delay_1ms(1000);
        }
}

/*!
    \brief      configure the different system clocks
    \param[in]  none
    \param[out] none
    \retval     none
*/
void rcu_config(void)
{
    /* enable GPIO clock */
        rcu_periph_clock_enable(RCU_AF);
        rcu_periph_clock_enable(RCU_GPIOB);
    /* enable periph clock */
    rcu_periph_clock_enable(RCU_CAN0);
}

/*!
    \brief      enable the different NVIC request
    \param[in]  none
    \param[out] none
    \retval     none
*/
void nvic_config(void)
{
        nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
        nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn,1,0);
}

最后
文章写的不好,希望能对初学CAN总线通信的友友们有所帮助!文章有错误的或者有更容易理解的方法,希望能写到评论区供大家学习。

非常感谢!
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/we_long/article/details/131429380

使用特权

评论回复
沙发
kzlzqi| | 2024-8-30 16:19 | 只看该作者
CAN 总线控制器作为 CAN 网络接口,遵循 CAN 总线协议 2.0A 和 2.0B 。

使用特权

评论回复
板凳
kzlzqi| | 2024-8-30 16:19 | 只看该作者
CAN协议通过有效的错误检测和恢复机制保证通信的稳定性,适用于需要高安全性和高性能的场景。

使用特权

评论回复
地板
发货后已经wi| | 2024-9-30 22:03 | 只看该作者
近年来,它具有的高可靠性和良好的错误检测能力受到重视

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1975

主题

15764

帖子

12

粉丝