基于 RT-Thread 和兆易创新GD32F527的CAN总线监视器 | 技术集结

描述

 

本项目是基于兆易创新GD32F527微控制器构建了一款CAN数据记录仪

整个工程的源码下载:https://download.csdn.net/download/u010261063/92196886?spm=1001.2101.3001.9500

该项目实现can总线数据的监视并保存在文件系统的功能,并提供时间戳的功能。

项目的使用环境就是在实际的项目中,可能需要分析can通讯的数据是否满足要求或存在异常,这时可以使用该设备实现数据监视,使用can_replay_log实现数据回显。

 

目录
 

基本外设的使用


 

spi nor flash


 

RAM文件系统


 

SDIO驱动


 

can驱动测试


 

can数据监视回显APP


 

《GD32VW553开发实践指南》贡献名单


 

GD32VW553硬件介绍

1 基本外设的使用

1.1 资源介绍GD32F527IST7

Flash有7M,RAM有512+64K,有8个16bit通用定时器,2个32bit通用定时器;8个串口;6个IIC;8个SPI;1个SDIO;2个CAN;

USBFS全速;USBHS高速;ENET以太网;TLI LCD 接口;DSI摄像头;SAI音频接口;

CAN

1.2  引脚分布图
 

CAN

1.3 系统架构
 

APB1 和 APB2 是连接所有 APB 从机的两条 APB 总线。APB1 最高可达 50MHz,APB2 可以全速运行(最高可到 100MHz)。

CAN

1.4 时钟树
 

系统时钟200M,APB2最大100M,APB1最大50M,

CAN

1.5 GPIO 9组
 

最多可支持 140 个通用 I/O 引脚(GPIO),分别为 PA0 ~ PA15,PB0 ~ PB15,PC0 ~ PC15,PD0 ~ PD15,PE0 ~ PE15,PF0 ~ PF15,PG0 ~ PG15,PH0 ~ PH15 和 PI0 ~ PI11,各片上设备用其来实现逻辑输入 / 输出功能

1.5.1 基本结构
 

CAN

1.5.2 地址
 

GPIOA 基地址:0x4002 0000

GPIOB 基地址:0x4002 0400

GPIOC 基地址:0x4002 0800

GPIOD 基地址:0x4002 0C00

GPIOE 基地址:0x4002 1000

GPIOF 基地址:0x4002 1400

GPIOG 基地址:0x4002 1800

GPIOH 基地址:0x4002 1C00

GPIOI 基地址:0x4002 2000

RTT gpio驱动框架

libraries\gd32_drivers\drv_gpio.h

核心操作结构

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

structrt_pin_ops{    void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode);    void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_uint8_t value);    rt_ssize_t  (*pin_read)(struct rt_device *device, rt_base_t pin);    rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_base_t pin,            rt_uint8_t mode, void (*hdr)(void *args), void *args);    rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_base_t pin);    rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint8_t enabled);    rt_base_t (*pin_get)(constchar *name);    rt_err_t (*pin_debounce)(struct rt_device *device, rt_base_t pin, rt_uint32_t debounce);};

gd32驱动提供的驱动实现

pin_get使用宏来实现引脚的获取#define LED1_PIN GET_PIN(E, 3)

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

conststaticstructrt_pin_opsgd32_pin_ops ={    .pin_mode = gd32_pin_mode,    .pin_write = gd32_pin_write,    .pin_read = gd32_pin_read,    .pin_attach_irq = gd32_pin_attach_irq,    .pin_detach_irq= gd32_pin_detach_irq,    .pin_irq_enable = gd32_pin_irq_enable,    RT_NULL,};

注册了pin驱动

libraries\gd32_drivers\drv_gpio.c 761行-770行

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

intrt_hw_pin_init(void){    int result;    result = rt_device_pin_register("pin", &gd32_pin_ops, RT_NULL);    return result;}INIT_BOARD_EXPORT(rt_hw_pin_init);

 

CAN

1.5.3 GPIO测试
 

CAN

代码

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

#include#include#include#include/* defined the LED0 pin: PE2 */#define LED0_PIN GET_PIN(E, 2)/* defined the LED1 pin: PE3 */#define LED1_PIN GET_PIN(E, 3)intmain(void){    int count = 1;    /* set LED1 pin mode to output */    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);    while (count++)    {        rt_pin_write(LED0_PIN, PIN_HIGH);        rt_pin_write(LED1_PIN, PIN_HIGH);        rt_thread_mdelay(500);        rt_pin_write(LED0_PIN, PIN_LOW);        rt_pin_write(LED1_PIN, PIN_LOW);        rt_thread_mdelay(500);    }    return RT_EOK;}

2 spi nor flash

使用时注意这几个短路帽是否在spi模式下

2.1 原理图
 

CAN

这里需要注意hold引脚和wp引脚的上拉控制

CAN

2.2 rtt内核配置
 

内核对象的长度设置。块设备名称过长导致注册块设备失败!

CAN

使能rtt自带的调试宏定义

rt-thread\include\rtdbg.h

通过使能这个发现了内核工作流程和日志打印,内核名称太短导致 norflash挂载是名字太长,无法挂载成功

打开rtt的调试日志

CANCAN

打开文件系统fatfs

CAN

配置fatfs文件系统

CAN

打开sfud组件

这里的名称改了,串行flash驱动

CANCAN

根据开发板设计,nor flash挂在spi5上,故首先需要使能spi的驱动

CAN

查看spi引脚发现默认的驱动引脚和原理图一致,无需修改

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

#ifdef BSP_USING_SPI5    {        SPI5,        "spi5",        RCU_SPI5,        RCU_GPIOG,        RCU_GPIOG,        RCU_GPIOG,        &spi_bus5,        GPIOG,        GPIOG,        GPIOG,#if defined (SOC_SERIES_GD32F4xx) || defined (SOC_SERIES_GD32H7xx) || (defined SOC_SERIES_GD32F5xx)        GPIO_AF_5,#endif        GPIO_PIN_13,        GPIO_PIN_12,        GPIO_PIN_14,    }#endif/* BSP_USING_SPI5 */};
 

注册spi 设备

注册块设备

官方提供了这个文件,已经完成了部分代码

libraries\gd32_drivers\drv_spi_flash.c

 

/* * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date           Author       Notes * 2021-12-31     BruceOu      first implementation * 2023-06-03     CX           fixed sf probe error bug * 2024-05-30     godmial      refactor driver for multi-SPI bus auto-mount */#include#include"drv_spi.h"#include"dev_spi_flash.h"#ifdef RT_USING_SFUD#include"dev_spi_flash_sfud.h"#endif#include#include#ifdef RT_USING_DFS#include#endifstructspi_flash_config{    constchar *bus_name;    constchar *device_name;    constchar *flash_name;    rt_base_t cs_pin;};staticconststructspi_flash_configflash_configs[] =    {#ifdef BSP_USING_SPI0        {            .bus_name = "spi0",            .device_name = "spi00",            .flash_name = "gd25q_spi0",            .cs_pin = GET_PIN(A, 4),        },#endif#ifdef BSP_USING_SPI1        {            .bus_name = "spi1",            .device_name = "spi10",            .flash_name = "gd25q_spi1",            .cs_pin = GET_PIN(B, 9),        },#endif#ifdef BSP_USING_SPI2        {            .bus_name = "spi2",            .device_name = "spi20",            .flash_name = "gd25q_spi2",            .cs_pin = GET_PIN(B, 12),        },#endif#ifdef BSP_USING_SPI3        {            .bus_name = "spi3",            .device_name = "spi30",            .flash_name = "gd25q_spi3",            .cs_pin = GET_PIN(E, 4),        },#endif#ifdef BSP_USING_SPI4        {            .bus_name = "spi4",            .device_name = "spi40",            .flash_name = "gd25q_spi4",            .cs_pin = GET_PIN(F, 6),        },#endif// 新增spi5的配置#ifdef BSP_USING_SPI5        {            .bus_name = "spi5",            .device_name = "spi50",            .flash_name = FAL_USING_NOR_FLASH_DEV_NAME, /** norflash0 */            .cs_pin = GET_PIN(I, 8),        },#endif};// 配置hold和wp引脚的控制#define NOR_FLASH_HOLD GET_PIN(H, 4)#define NOR_FLASH_WP GET_PIN(G, 10)staticintspi_flash_init(void){    int result = RT_EOK;    rt_pin_mode(NOR_FLASH_HOLD, PIN_MODE_OUTPUT);    rt_pin_mode(NOR_FLASH_WP, PIN_MODE_OUTPUT);    rt_pin_write(NOR_FLASH_HOLD, PIN_HIGH);    rt_pin_write(NOR_FLASH_WP, PIN_HIGH);    for (size_t i = 0; i < sizeof(flash_configs) / sizeof(flash_configs[0]); i++)    {        conststructspi_flash_config *cfg = &flash_configs[i];        // 1.注册一个spi50的spi设备        result = rt_hw_spi_device_attach(cfg->bus_name, cfg->device_name, cfg->cs_pin);        if (result != RT_EOK)        {            rt_kprintf("Failed to attach device %s on bus %s\n", cfg->device_name, cfg->bus_name);            continue;        }#ifdef RT_USING_SFUD        // 注册一个块设备        if (RT_NULL == rt_sfud_flash_probe(cfg->flash_name, cfg->device_name))        {            rt_kprintf("SFUD probe failed: %s\n", cfg->flash_name);            continue;        }        else        {            rt_kprintf("SFUD probe ok: %s\n", cfg->flash_name);        }#endif    }    return result;}INIT_COMPONENT_EXPORT(spi_flash_init);


 

首次使用需要格式化

mkfs -t elm norflash0

挂载

mount norflash0 / elm

创建一个文件测试文件系统

echo 123456abc 123.txt

开机自动挂载

新建文件,开机自动挂载

applications\fs_mount.c

 

#include#include#includeintmnt_init(void){     //将sd挂载在 / 目录    if (dfs_mount(FAL_USING_NOR_FLASH_DEV_NAME, "/", "elm", 0, 0) == 0)    {        LOG_I("%s mount success !\n",FAL_USING_NOR_FLASH_DEV_NAME);    }    else    {        LOG_W("%s mount failed!\n",FAL_USING_NOR_FLASH_DEV_NAME);    }    return0;}INIT_FS_EXPORT(mnt_init);

 

硬件测试时,注意断路冒的设置;JP13,JP15;JP17;JP19

CAN

3 RAM文件系统

顶一个ram空间用于存放文件系统,经测试不可以创建文件夹,只能创建文件

 

#include#include#include#includeintmnt_init(void){    uint8_t ramfs_buff[4096];    if (dfs_mount(RT_NULL, "/", "ram", 0, dfs_ramfs_create(ramfs_buff,4096)) == 0)    {        LOG_I("ram mount success !\n");    }    else    {        LOG_W("ram mount failed!\n");    }    return0;}INIT_FS_EXPORT(mnt_init);

 

4 SDIO驱动

4.1 开发原理图
 

注意短路帽的设置

CAN

4.2 使能驱动
 

CAN

4.3 修改驱动

修改gpio_config,修改gpio速度

libraries\gd32_drivers\drv_sdio.c

CAN

4.4 将sd卡挂载到文件系统
 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

    if (dfs_mount(SD_BLK_NAME, "/", "elm", 0, 0) == 0)    {        LOG_I("%s mount success !\n",SD_BLK_NAME);    }    else    {        LOG_W("%s mount failed!\n",SD_BLK_NAME);    }


 

sd0就是sd卡

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

---------------------------------------------------------------- -------------------- ----------spi50                                                            SPI Device           0sd0                                                              Block Device         1rtc                                                              RTC                  0spi5                                                             SPI Bus              0timer10                                                          Timer Device         0pin                                                              Pin Device           0can0                                                             CAN Device           1uart0                                                            Character Device     2msh />dfdisk free: 1.6 GB [ 3408064 block, 512 bytes per block ]
 

注意短路冒设置,默认时摄像头使用的

我是一个4G的sd卡

CAN

4.5 官方的sdio驱动可能的问题

我对比了现在官方提供的sdio驱动和我在之前gd32f303使用的几乎一样,gd32f303平台会快速出现sd卡读写异常,需要重新初始化才可以使用,但是在GD32F527平台上同样的驱动几乎没有出现。

修改了驱动主要做的修改是,在各种地方重置sdio的状态。

修改后的驱动问题

根据我在GD32F303上的经验,发现sdio的驱动会大量写入数据时,驱动报错的问题,于是修改了GD32F527的sdio驱动,但是引发了ls指令列出文件时越来越慢的问题;经排查这里出现问题。、

5 can驱动测试

can框架分析:

https://blog.csdn.net/u010261063/article/details/148741206?spm=1011.2415.3001.5331

rt thread 基于GD32F303的can驱动编写

https://blog.csdn.net/u010261063/article/details/148784016?spm=1011.2124.3001.6209

5.1 驱动使能
 

CAN

5.2 硬件
 

开发版的引脚和驱动引脚一致

CAN

5.3 drv_can.c驱动问题1

测试代码

 

#include#include"rtdevice.h"#define CAN_DEV_NAME "can0"/* CAN 设备名称 */staticstructrt_semaphorerx_sem;/* 用于接收消息的信号量 */staticrt_device_t can_dev;        /* CAN 设备句柄 *//* 接收数据回调函数 */staticrt_err_tcan_rx_call(rt_device_t dev, rt_size_t size){    /* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */    rt_sem_release(&rx_sem);    return RT_EOK;}staticvoidcan_rx_thread(void *parameter){    int i;    rt_err_t res;    structrt_can_msgrxmsg = {0};    /* 设置接收回调函数 */    rt_device_set_rx_indicate(can_dev, can_rx_call);#ifdef RT_CAN_USING_HDR    structrt_can_filter_itemitems[5] =        {            RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, 0, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */            RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, 0, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x300~0x3ff,hdr 为 - 1 */            RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, 0, 0x7ff, RT_NULL, RT_NULL), /* std,match ID:0x211,hdr 为 - 1 */            RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL),                  /* std,match ID:0x486,hdr 为 - 1 */            {                0x555,                0,                0,                0,                0x7ff,                7,            } /* std,match ID:0x555,hdr 为 7,指定设置 7 号过滤表 */        };    structrt_can_filter_configcfg = {5, 1, items}; /* 一共有 5 个过滤表 */    /* 设置硬件过滤表 */    res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);    RT_ASSERT(res == RT_EOK);#endif    while (1)    {        /* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */        rxmsg.hdr_index = -1;        /* 阻塞等待接收信号量 */        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);        /* 从 CAN 读取一帧数据 */        rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));        /* 打印数据 ID 及内容 */        rt_kprintf("ID:%08x [ ", rxmsg.id);        for (i = 0; i < rxmsg.len; i++)        {            rt_kprintf("%02x ", rxmsg.data[i]);        }        rt_kprintf(" ]\n");    }}intcan_sample(int argc, char *argv[]){    structrt_can_msgmsg = {0};    rt_err_t res;    rt_size_t size;    rt_thread_t thread;    char can_name[RT_NAME_MAX];    if (argc == 2)    {        rt_strncpy(can_name, argv[1], RT_NAME_MAX);    }    else    {        rt_strncpy(can_name, CAN_DEV_NAME, RT_NAME_MAX);    }    /* 查找 CAN 设备 */    can_dev = rt_device_find(can_name);    if (!can_dev)    {        rt_kprintf("find %s failed!\n", can_name);        return RT_ERROR;    }    /* 初始化 CAN 接收信号量 */    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);    /* 以中断接收及发送方式打开 CAN 设备 */    res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);    RT_ASSERT(res == RT_EOK);    /* 设置 CAN 通信的波特率为 500kbit/s*/    // res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);    // RT_ASSERT(res == RT_EOK);    /* 创建数据接收线程 */    thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 1024, 25, 10);    if (thread != RT_NULL)    {        rt_thread_startup(thread);    }    else    {        rt_kprintf("create can_rx thread failed!\n");    }    msg.id = 0x78;          /* ID 为 0x78 */    msg.ide = RT_CAN_STDID; /* 标准格式 */    msg.rtr = RT_CAN_DTR;   /* 数据帧 */    msg.len = 8;            /* 数据长度为 8 */    /* 待发送的 8 字节数据 */    msg.data[0] = 0x00;    msg.data[1] = 0x11;    msg.data[2] = 0x22;    msg.data[3] = 0x33;    msg.data[4] = 0x44;    msg.data[5] = 0x55;    msg.data[6] = 0x66;    msg.data[7] = 0x77;    /* 发送一帧 CAN 数据 */    size = rt_device_write(can_dev, 0, &msg, sizeof(msg));    if (size == 0)    {        rt_kprintf("can dev write data failed!\n");    }    return res;}/* 导出到 msh 命令列表中 */MSH_CMD_EXPORT(can_sample, can device sample);


 

在通讯频率时1M时发送ok

CAN

500K报错

CAN

解决办法

经过调试发现,官方提供的can驱动存在问题;修改波特率导致发送数据失败,经过分析时在配置配置波特率是重新初始化了can外设,导致了open阶段的配置就丢失了,例如中断发送接收功能没有了

libraries\gd32_drivers\drv_can.c

CAN

修改can的反初始化can_deinit位置 放在这里

函数:int rt_hw_can_init(void)

CAN

5.4 优化等级问题2?

本来就想测试下can的自发自收测试,没想到问题出现了

5.4.1 测试代码
 

  •  

/* * Copyright (c) 2006-2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date           Author       Notes * 2021-08-20     BruceOu      first implementation * 2023-03-05     yuanzihao    change the LED pins */#include#include#include#include/* defined the LED1 pin: PE3 */#define LED1_PIN GET_PIN(E, 3)#define LED0_PIN GET_PIN(E, 2)intmain(void){    int count = 1;    /* set LED1 pin mode to output */    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);    while (count++)    {        rt_pin_write(LED0_PIN, PIN_HIGH);        rt_pin_write(LED1_PIN, PIN_HIGH);        rt_thread_mdelay(500);        rt_pin_write(LED0_PIN, PIN_LOW);        rt_pin_write(LED1_PIN, PIN_LOW);        rt_thread_mdelay(500);    }    return RT_EOK;}#include#include"rtdevice.h"#define CAN_DEV_NAME "can0"/* CAN 设备名称 */staticstructrt_semaphorerx_sem;/* 用于接收消息的信号量 */staticrt_device_t can_dev;        /* CAN 设备句柄 *//* 接收数据回调函数 */staticrt_err_tcan_rx_call(rt_device_t dev, rt_size_t size){    /* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */    rt_sem_release(&rx_sem);    return RT_EOK;}staticstructrt_can_msgrxmsg = {0};staticstructrt_can_msgmsg = {0};staticvoidcan_rx_thread(void *parameter){    int i;    rt_err_t res;    /* 设置接收回调函数 */    rt_device_set_rx_indicate(can_dev, can_rx_call);#ifdef RT_CAN_USING_HDR    structrt_can_filter_itemitems[5] =        {            RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, 0, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */            RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, 0, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x300~0x3ff,hdr 为 - 1 */            RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, 0, 0x7ff, RT_NULL, RT_NULL), /* std,match ID:0x211,hdr 为 - 1 */            RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL),                  /* std,match ID:0x486,hdr 为 - 1 */            {                0x555,                0,                0,                0,                0x7ff,                7,            } /* std,match ID:0x555,hdr 为 7,指定设置 7 号过滤表 */        };    structrt_can_filter_configcfg = {5, 1, items}; /* 一共有 5 个过滤表 */    /* 设置硬件过滤表 */    res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);    RT_ASSERT(res == RT_EOK);#endif    while (1)    {        /* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */        rxmsg.hdr_index = -1;        /* 阻塞等待接收信号量 */        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);        /* 从 CAN 读取一帧数据 */        rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));        /* 打印数据 ID 及内容 */        rt_kprintf("ID:%08x [ ", rxmsg.id);        for (i = 0; i < rxmsg.len; i++)        {            rt_kprintf("%02x ", rxmsg.data[i]);        }        rt_kprintf(" ]\n");        //直接结构体对象赋值实现数据回环,竟然不得行?修改了优化等级就可以        msg = rxmsg;        /* 发送一帧 CAN 数据 */        int size = rt_device_write(can_dev, 0, &msg, sizeof(msg));        if (size == 0)        {            rt_kprintf("can dev write data failed!\n");        }    }}intcan_sample(int argc, char *argv[]){    rt_err_t res;    rt_size_t size;    rt_thread_t thread;    char can_name[RT_NAME_MAX];    if (argc == 2)    {        rt_strncpy(can_name, argv[1], RT_NAME_MAX);    }    else    {        rt_strncpy(can_name, CAN_DEV_NAME, RT_NAME_MAX);    }    /* 查找 CAN 设备 */    can_dev = rt_device_find(can_name);    if (!can_dev)    {        rt_kprintf("find %s failed!\n", can_name);        return RT_ERROR;    }    /* 初始化 CAN 接收信号量 */    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);    /* 以中断接收及发送方式打开 CAN 设备 */    res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);    RT_ASSERT(res == RT_EOK);    /* 设置 CAN 通信的波特率为 500kbit/s*/    res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);    RT_ASSERT(res == RT_EOK);    /* 创建数据接收线程 */    thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 2048, 25, 10);    if (thread != RT_NULL)    {        rt_thread_startup(thread);    }    else    {        rt_kprintf("create can_rx thread failed!\n");    }    //这样手动赋值也可以实现收一条发一条的功能    structrt_can_msgmsg = {0};    msg.id = 0x78;          /* ID 为 0x78 */    msg.ide = RT_CAN_STDID; /* 标准格式 */    msg.rtr = RT_CAN_DTR;   /* 数据帧 */    msg.len = 8;            /* 数据长度为 8 */    /* 待发送的 8 字节数据 */    msg.data[0] = 0x00;    msg.data[1] = 0x11;    msg.data[2] = 0x22;    msg.data[3] = 0x33;    msg.data[4] = 0x44;    msg.data[5] = 0x55;    msg.data[6] = 0x66;    msg.data[7] = 0x77;    /* 发送一帧 CAN 数据 */    size = rt_device_write(can_dev, 0, &msg, sizeof(msg));    if (size == 0)    {        rt_kprintf("can dev write data failed!\n");    }    return res;}/* 导出到 msh 命令列表中 */MSH_CMD_EXPORT(can_sample, can device sample);

 

5.4.2 问题现象

CANCANCANCAN

汇编分析

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

staticstructrt_can_msgrxmsg = {0};staticstructrt_can_msgmsg = {0};structrt_can_msg{    rt_uint32_t id  : 29;           /**< CAN ID (Standard or Extended). */    rt_uint32_t ide : 1;            /**< Identifier type: 0=Standard ID, 1=Extended ID. */    rt_uint32_t rtr : 1;            /**< Frame type: 0=Data Frame, 1=Remote Frame. */    rt_uint32_t rsv : 1;            /**< Reserved bit. */    rt_uint32_t len : 8;            /**< Data Length Code (DLC) from 0 to 8. */    rt_uint32_t priv : 8;           /**< Private data, used to specify the hardware mailbox in private mode. */    rt_int32_t hdr_index : 8;       /**< For received messages, the index of the hardware filter that matched the message. */    rt_uint32_t rxfifo : 2;         /**< The RX FIFO where the message was received. */    rt_uint32_t reserved : 5;    rt_uint32_t nonblocking : 1;    /**< Send mode: 0=Blocking (default), 1=Non-blocking. */    rt_uint8_t data[8];             /**< CAN message payload (up to 8 bytes). */};

 

可行

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

   106:         msg = rxmsg; 0x08006CFA F242326C  MOV      r2,#0x236c0x08006CFE F2C20200  MOVT     r2,#0x20000x08006D026810      LDR      r0,[r2,#0]0x08006D046851      LDR      r1,[r2,#4]0x08006D066893      LDR      r3,[r2,#8]0x08006D08 F8D2C00C  LDR      r12,[r2,#0xc]0x08006D0C F64172E8  MOV      r2,#0x1fe80x08006D10 F2C20200  MOVT     r2,#0x20000x08006D14 F8C2C00C  STR      r12,[r2,#0xc]0x08006D186093      STR      r3,[r2,#8]0x08006D1A6051      STR      r1,[r2,#4]0x08006D1C6010      STR      r0,[r2,#0]

 

不可行汇编

  •  
  •  
  •  
  •  
  •  

   106:         msg = rxmsg; 0x080048D8 E894000F  LDM      r4,{r0-r3}0x080048DC60EB      STR      r3,[r5,#0xc]0x080048DE E8850007  STM      r5,{r0-r2}
 

使用AI分析了汇编,他说出现这个问题可能是结构struct rt_can_msg的位定义问题,导致了内存拷贝的异常,经过测试其实不是真实原因

5.4.3 解决办法:又是can驱动的问题

发现了一个问题,优化等级太高,导致输出之收不发,不优化就可以自收发。本以为是优化等级问题,结果不是。

结论:不是优化等级的问题,又是can驱动的问题,在收到can的驱动时需要memset一下struct rt_can_msg消息结构。

在接收函数内部将结构体设置为零后,就可以实现在优化等级3下,实现数据回环收发

libraries\gd32_drivers\drv_can.c

CAN

再次分析看看有无memset的数据有啥不一样

测试代码

  •  
  •  
  •  
  •  
  •  
  •  
  •  

uint8_t *u8 = (uint8_t *)&rxmsg;for (i = 0; i < sizeof(rxmsg); i++){    rt_kprintf("%02x ", u8[i]);}rt_kprintf("\n");
 

在优化等级3的情况下;这里就说明了struct rt_can_msg填充了其他值

有置零:4b 01 00 20 03 00 00 00 01 02 03 00 00 00 00 00

无置零:4b 01 00 20 03 be ad dc 01 02 03 20 3c 2c 00 20

优化等级1:

有置零:4b 01 00 20 03 00 00 00 01 02 03 00 00 00 00 00

无置零:4b 01 00 20 03 0a 00 20 01 02 03 00 01 00 00 00

由此可以知道不是优化等级的问题;在未置零memset的情况下仅仅是rxmsg的某些成员是未定义的,而在发送时这个成员又是需要的。

libraries\gd32_drivers\drv_can.c

CAN

5.5 时间戳的实现
 

使能RTC驱动程序

CAN

测试驱动

注意时间存在一个时区问题

查看时间:date

设置时间:date 2025 10 24 9 21 00

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

msh />datelocal time: Mon Jan  108:00:002024timestamps: 1704067200timezone: UTC+08:00:00msh />date 2025102492100old: Mon Jan  108:00:132024now: Fri Oct 2409:21:002025msh />datelocal time: Fri Oct 2409:21:042025timestamps: 1761268864timezone: UTC+08:00:00msh />
 

6 can数据监视回显APP

6.1 can数据结构定义
 

  •  
  •  
  •  

#ifndef CAN_LOGGER_H_#define CAN_LOGGER_H_#include#include/* CAN 消息结构体 */structcan_message{    rt_uint32_t id : 29; /* CAN ID, 标志格式 11 位,扩展格式 29 位 */    rt_uint32_t ide : 1; /* 扩展帧标识位 */    rt_uint32_t rtr : 1; /* 远程帧标识位 */    rt_uint32_t rsv : 1; /* 保留位 */    uint8_t data[8];     // 数据域    uint8_t len;         // 数据长度    time_t timestamp;    // 时间戳(秒)};/* 发送消息接口(用于测试) */voidsend_can_message(struct can_message *msg);#endif

 

6.2 数据监视并记录为文件
 

applications\can_log_monitor.c

  •  
  •  
  •  
  •  

#include#include#include#include#include#include#include#include#include#include#include#include#define CAN_DEV_NAME "can0"/* CAN 设备名称 */#define LED0_PIN GET_PIN(E, 2)#define LED0_ERR GET_PIN(E, 7)staticstructrt_semaphorerx_sem;/* 用于接收消息的信号量 */staticrt_device_t can_dev;        /* CAN 设备句柄 */staticvoidcan_led_blink(){    staticrt_uint8_t blink = 0;    if (blink)    {        rt_pin_write(LED0_PIN, PIN_HIGH);    }    else    {        rt_pin_write(LED0_PIN, PIN_LOW);    }    blink = !blink;}/* 接收数据回调函数 */staticrt_err_tcan_rx_call(rt_device_t dev, rt_size_t size){    /* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */    rt_sem_release(&rx_sem);    return RT_EOK;}staticvoidcan_rx_thread(void *parameter){    int i;    rt_err_t res;    staticstructrt_can_msgrxmsg = {0};    staticstructcan_messagecan_rx_msg;    /* 设置接收回调函数 */    rt_device_set_rx_indicate(can_dev, can_rx_call);#ifdef RT_CAN_USING_HDR    structrt_can_filter_itemitems[5] =        {            RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, 0, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */            RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, 0, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x300~0x3ff,hdr 为 - 1 */            RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, 0, 0x7ff, RT_NULL, RT_NULL), /* std,match ID:0x211,hdr 为 - 1 */            RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL),                  /* std,match ID:0x486,hdr 为 - 1 */            {                0x555,                0,                0,                0,                0x7ff,                7,            } /* std,match ID:0x555,hdr 为 7,指定设置 7 号过滤表 */        };    structrt_can_filter_configcfg = {5, 1, items}; /* 一共有 5 个过滤表 */    /* 设置硬件过滤表 */    // res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);    // RT_ASSERT(res == RT_EOK);#endif    while (1)    {        /* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */        rxmsg.hdr_index = -1;        /* 阻塞等待接收信号量 */        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);        /* 从 CAN 读取一帧数据 */        rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));        can_rx_msg.id = rxmsg.id;        can_rx_msg.ide = rxmsg.ide;        can_rx_msg.rtr = rxmsg.rtr;        can_rx_msg.rsv = rxmsg.rsv;        can_rx_msg.len = rxmsg.len;        can_rx_msg.timestamp = time(NULL);        rt_memcpy(can_rx_msg.data, rxmsg.data, rxmsg.len);        send_can_message(&can_rx_msg);        can_led_blink();        /* 打印数据 ID 及内容 */        //        rt_kprintf("ID:%x [", rxmsg.id);        //        for (i = 0; i < rxmsg.len; i++)        //        {        //            rt_kprintf("%02x ", rxmsg.data[i]);        //        }        //        rt_kprintf("]\n");        // rt_device_write(can_dev, 0, &rxmsg, sizeof(rxmsg));    }}intcan_app_main(void){    structrt_can_msgmsg = {0};    rt_err_t res;    rt_size_t size;    rt_thread_t thread;    /* 查找 CAN 设备 */    can_dev = rt_device_find(CAN_DEV_NAME);    if (!can_dev)    {        LOG_E("find %s failed!", CAN_DEV_NAME);        return RT_ERROR;    }    /* 初始化 CAN 接收信号量 */    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);    /* 以中断接收及发送方式打开 CAN 设备 */    res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);    RT_ASSERT(res == RT_EOK);    LOG_I("set bate 500K");    res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud);    RT_ASSERT(res == RT_EOK);    /* 创建数据接收线程 */    thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 1024, 25, 10);    if (thread != RT_NULL)    {        rt_thread_startup(thread);    }    else    {        LOG_W("create can_rx thread failed!");    }    msg.id = 0x78;          /* ID 为 0x78 */    msg.ide = RT_CAN_STDID; /* 标准格式 */    msg.rtr = RT_CAN_DTR;   /* 数据帧 */    msg.len = 8;            /* 数据长度为 8 */    /* 待发送的 8 字节数据 */    msg.data[0] = 0x00;    msg.data[1] = 0x11;    msg.data[2] = 0x22;    msg.data[3] = 0x33;    msg.data[4] = 0x44;    msg.data[5] = 0x55;    msg.data[6] = 0x66;    msg.data[7] = 0x77;    /* 发送一帧 CAN 数据 */    size = rt_device_write(can_dev, 0, &msg, sizeof(msg));    if (size == 0)    {        LOG_W("can dev write data failed!");    }    return res;}/* 导出到 msh 命令列表中 */INIT_APP_EXPORT(can_app_main);#define QUEUE_SIZE 10#define AUTO_SYNC_TIME_MS (5 * 1000)/* 全局变量 */staticrt_mq_t can_queue = RT_NULL;staticrt_thread_t log_thread = RT_NULL;staticrt_timer_t timer_auto_save = RT_NULL;staticint fd = -1;/* 当前文件名缓存 */staticchar current_filename[64];/* 生成当前时间的文件夹名(按小时) */voidgenerate_hourly_dirname(char *dirname, size_t len){    time_t now;    structtm *today;    time(&now);    today = localtime(&now);    rt_snprintf(dirname, len, "/can/%04d%02d%02d_%02d",                today->tm_year + 1900,                today->tm_mon + 1,                today->tm_mday,                today->tm_hour);}/* 生成当前时间的文件名(每分钟一个文件) */voidgenerate_minute_filename(char *filename, size_t len){    time_t now;    structtm *today;    time(&now);    today = localtime(&now);    rt_snprintf(filename, len, "%02d.data", today->tm_min);}/* 递归创建目录 */intensure_directory_exists(constchar *path){    char temp[256];    int i, len = rt_strlen(path);    if (len >= sizeof(temp))        return-1;    for (i = 0; i < len; i++)    {        temp[i] = path[i];        if (temp[i] == '/' && i > 0)        {            temp[i] = '\0';            mkdir(temp, 0755);            temp[i] = '/';        }    }    temp[len] = '\0';    return mkdir(temp, 0755);}staticvoidfun_auto_sync_file(void *parameter){    LOG_D("timeout sync file");    structcan_messagemsg;    msg.rsv = 0;    rt_mq_send(can_queue, &msg, sizeof(struct can_message));}/* 发送消息接口(用于测试) */voidsend_can_message(struct can_message *msg){    if (can_queue != RT_NULL)    {        msg->rsv = 1;        rt_mq_send(can_queue, msg, sizeof(struct can_message));        // rt_uint32_t time=AUTO_SYNC_TIME_MS;        // rt_timer_control(timer_auto_save,RT_TIMER_CTRL_SET_TIME,&time);        rt_timer_start(timer_auto_save);    }}/* 线程入口函数 */staticvoidlog_thread_entry(void *parameter){    staticstructcan_messagemsg;    staticchar dirname[64] = "";    staticchar filename[32] = "";    staticchar fullpath[128] = "";    staticchar last_dirname[64] = "";    staticchar last_filename[32] = "";    staticint fd = -1;    // 自动创建目录    LOG_I("crea path=\"/can\" dir %d", mkdir("can", 0x777));    while (1)    {        if (sizeof(struct can_message) == rt_mq_recv(can_queue, &msg, sizeof(struct can_message), RT_WAITING_FOREVER) && msg.rsv)        {            generate_hourly_dirname(dirname, sizeof(dirname));            generate_minute_filename(filename, sizeof(filename));            rt_snprintf(fullpath, sizeof(fullpath), "%s/%s", dirname, filename);            // 新目录,检查是否需要切换目录或文件            if (rt_strcmp(last_dirname, dirname) != 0)            {                // 先关闭旧文件                if (fd >= 0)                {                    fsync(fd);                    close(fd);                    fd = -1;                }                // 目录不存在则创建                int err = mkdir(dirname, 0x777);                if (err)                {                    LOG_E("create dir error=%d path =[%s]", err, dirname);                }                rt_strncpy(last_dirname, dirname, sizeof(last_dirname));            }            // 新文件             if (fd < 0 || rt_strcmp(last_filename, filename) != 0)            {               // 先关闭旧文件                if (fd >= 0)                {                    fsync(fd);                    close(fd);                    fd = -1;                }                // 打开新文件                fd = open(fullpath, O_WRONLY | O_APPEND | O_CREAT, 0);                if (fd < 0)                {                    LOG_W("Failed to open file: %s", fullpath);                    rt_pin_write(LED0_ERR, PIN_HIGH);                    continue;                }                rt_strncpy(last_filename, filename, sizeof(last_filename));            }            // 写入数据            if (fd >= 0)            {                write(fd, &msg, sizeof(msg));            }            rt_pin_write(LED0_ERR, PIN_LOW);        }    }    // 线程退出前关闭文件    if (fd >= 0)    {        fsync(fd);        close(fd);    }}/* 初始化函数:创建队列和线程 */intcan_logger_init(void){    /* 创建消息队列 */    can_queue = rt_mq_create("can_mq", sizeof(struct can_message), QUEUE_SIZE, RT_IPC_FLAG_FIFO);    if (can_queue == RT_NULL)    {        rt_kprintf("Failed to create message queue.\n");        return-1;    }    /* 创建线程 */    log_thread = rt_thread_create("can_log",                                  log_thread_entry,                                  RT_NULL,                                  1024,                                  10,                                  10);    if (log_thread != RT_NULL)    {        rt_thread_startup(log_thread);    }    else    {        rt_kprintf("Failed to create logging thread.\n");        rt_mq_delete(can_queue);        return-1;    }    /**创建一个定时器实现自动保存文件 */    timer_auto_save = rt_timer_create("auto_save", fun_auto_sync_file, 0, AUTO_SYNC_TIME_MS, RT_TIMER_FLAG_ONE_SHOT | RT_TIMER_FLAG_SOFT_TIMER);    RT_ASSERT(timer_auto_save);    // 创建文件夹    mkdir("can", 0x777);    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);    rt_pin_mode(LED0_ERR, PIN_MODE_OUTPUT);    return0;}INIT_APP_EXPORT(can_logger_init); // 自动初始化

 

6.3 数据查看指令can_replay_log

applications\can_log_show.c

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

#include#include#include#include#include#include#include// #include voidcan_replay_log(int argc, char **argv){    if (argc != 2)    {        rt_kprintf("Usage: can_replay_log \n");    }    int fd = open(argv[1], O_RDONLY, 0);    if (fd < 0)    {        rt_kprintf("Failed to open log file: %s\n", argv[1]);        return;    }    structcan_messagemsg;    ssize_t bytes_read;    rt_kprintf("Replaying CAN log from: %s\n", argv[1]);    while ((bytes_read = read(fd, &msg, sizeof(msg))) == sizeof(msg))    {        char ts_str[32];        structtm *tm_info = localtime(&msg.timestamp);        strftime(ts_str, sizeof(ts_str), "%Y-%m-%d %H:%M:%S", tm_info);        /* 判断是标准帧还是扩展帧 */        constchar *frame_type = msg.ide ? "EXT" : "STD";        constchar *rtr_type = msg.rtr ? "RTR" : "DATA";        /* 打印帧信息 */        rt_kprintf("[%s] ID: 0x%0*X (%s), Type: %s, LEN: %u, DATA: ",                   ts_str,                   msg.ide ? 8 : 3, msg.id, frame_type, rtr_type, msg.len);        /* 如果是 RTR 帧,则不打印数据 */        if (!msg.rtr)        {            for (int i = 0; i < msg.len; i++)            {                rt_kprintf("%02X ", msg.data[i]);            }        }        else        {            rt_kprintf("(no data)");        }        rt_kprintf("\n");    }    close(fd);}MSH_CMD_EXPORT(can_replay_log, Replay CAN log from current hour file.);
 

6.4 效果展示
 

数据含有时间,ID,帧类型,数据长度,数据内容

CAN

7 《GD32VW553开发实践指南》贡献名单

RT-Thread社区携手兆易创新联合发起兆易创新GD32VW553 无线MCU评测活动,《GD32VW553开发实践指南》详细列出了各个内容板块及其贡献者。在此,衷心感谢所有小伙伴的支持与贡献!

CAN

8 GD32VW553硬件介绍

GD32VW553系列是兆易创新推出的高集成度无线微控制器,专为物联网应用设计。该芯片采用RISC-V内核,主频高达160MHz,并配备4MB Flash与320KB SRAM,为复杂应用提供了充裕的运算与存储空间。其核心亮点在于集成了Wi-Fi 6 (802.11ax) 与Bluetooth LE 5.2双模无线模块,在提供高速、高容量、低延迟无线连接的同时,也具备优异的射频性能和低功耗特性。此外,芯片还集成了UART、SPI、I2C、USB、以太网等丰富外设接口及硬件安全引擎,全面满足智能家居、工业物联网等场景对连接性、功能性和安全性的需求。

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分