本项目是基于兆易创新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音频接口;

1.2 引脚分布图

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

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

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 基本结构

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);

1.5.3 GPIO测试

代码
#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 原理图

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

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

使能rtt自带的调试宏定义
rt-thread\include\rtdbg.h
通过使能这个发现了内核工作流程和日志打印,内核名称太短导致 norflash挂载是名字太长,无法挂载成功
打开rtt的调试日志


打开文件系统fatfs

配置fatfs文件系统

打开sfud组件
这里的名称改了,串行flash驱动


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

查看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

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 开发原理图
注意短路帽的设置

4.2 使能驱动

4.3 修改驱动
修改gpio_config,修改gpio速度
libraries\gd32_drivers\drv_sdio.c

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卡

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 驱动使能

5.2 硬件
开发版的引脚和驱动引脚一致

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

500K报错

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

修改can的反初始化can_deinit位置 放在这里
函数:int rt_hw_can_init(void)

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 问题现象




汇编分析
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

再次分析看看有无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

5.5 时间戳的实现
使能RTC驱动程序

测试驱动
注意时间存在一个时区问题
查看时间: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,帧类型,数据长度,数据内容

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

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