当前位置: 代码网 > 服务器>网络>网络协议 > 【17】STM32·HAL库·CAN

【17】STM32·HAL库·CAN

2024年08月01日 网络协议 我要评论
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基础知识介绍

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 fd 通信速率可达 5mbps,并且兼容经典 can,遵循 iso 11898-1 做数据收发

can总线拓扑图

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

can总线特点

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

1.2、can物理层

1.3、can协议层

1.3.1、can帧种类介绍

1.3.2、can数据帧介绍

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

1.3.3、can位时序介绍

can 总线以“位同步”机制,实现对电平的正确采样。位数据都由四段组成:同步段(ss)传播时间段(pts)相位缓冲段1(pbs1)相位缓冲段2(pbs2),每段又由多个位时序 tq 组成

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

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

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

硬件同步

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

再同步

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

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

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

二、stm32 can控制器介绍

2.1、can控制器介绍

stm32 can 控制器(bxcan),支持 can 2.0acan 2.0b active 版本协议

bxcan 主要特点

2.2、can控制器模式

2.2.1、can控制器工作模式

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

2.2.2、can控制器测试模式

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

2.3、can控制器框图

发送处理

接收处理

2.4、can控制器接收过滤器

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

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

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

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

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

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

2.5、can控制器位时序

stm32的can外设位时序分为三段:

在 stm32f407,设 ts1=6、ts2=5、brp=5,波特率 = 42000 / [( 7 + 6 + 1 ) * 6] = 500kbps。

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

三、can相关寄存器介绍

3.1、can主控制寄存器(can_mcr)

3.2、can位时序寄存器(can_btr)

3.3、can标识符寄存器(can_(t/r)ixr)

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

3.4、数据长度和时间戳寄存器(can_(t/r)dtxr)

注意: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)

3.10、can过滤器激活寄存器(can_fa1r)

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;

需要结合映射去赋值

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
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com