RT-Thread studio上创建一个STM32F103的CAN通讯功能

前言

(1)如果有嵌入式企业需要招聘湖南区域日常实习生,任何区域的暑假Linux驱动实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效
(2)学习本文之前,建议先在裸机上实现CAN通讯,这样测试一下通讯是否硬件是否正常。

前期准备

创建RT-Thread标准版工程

(1)打开项目资源管理器。

在这里插入图片描述

(2)在项目资源管理器中右键 —> 新建 —> 项目。

在这里插入图片描述

(3)选择RT-Thread项目 —> 下一步。

在这里插入图片描述

(4)选择所需要的芯片型号

在这里插入图片描述

(5)选择这个项目,进行编译。

在这里插入图片描述

进行STM32CubeMX适配

(1)打开STM32CubeMX

在这里插入图片描述

(2)因为我是STlink下载器,所以进行如下配置

在这里插入图片描述

(3)我开发板上是使用的外部无源晶振(陶瓷晶振),因此RCC的外部高速时钟配置为Crystal/Ceramic Resonator

在这里插入图片描述

(4)因为RT-ThreadFinSH是默认使用的串口1,因此,我们需要打开串口1。

在这里插入图片描述

(5)官方推荐STM32F103系统主频为72MHZ,因此这里设置为72MHZ

在这里插入图片描述

(6)对外设初始化的文件单独生成.c/.h文件,最终生成代码,并且关闭STM32CubeMX

在这里插入图片描述

(7)打开RT-Thread studio,出现如下弹框,点击确定。
注意:上面的STM32CubeMX必须关闭,否则这个弹窗将不会出现。

在这里插入图片描述

(8)此时编译烧录即可

在这里插入图片描述

(9)此时上机打开串口工具测试,波特率115200。即可看到如下打印信息。

在这里插入图片描述

(10)在某些教程中,可能会需要你找到cubemx/Src/main.c,将生成的SystemClock_Config()函数复制到drivers/drv_clk.c中。但是我当前版本的RT-Thread studio成功的避免了这个问题,工程中存在两个main.c也可以正常编译,cubemx/Src/main.c中的main()函数也被__WEAK因此不会产生冲突。

增加板级LED支持

(1)因为我开发板上有两个LED,一个是PB5引脚,一个是PE5引脚。为了知道程序在正常运行,同时打印信息太多影响FinSH使用,因此我加上LED闪烁程序,把Hello RT-Thread!打印信息删除。

/** Copyright (c) 2006-2024, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date           Author       Notes* 2024-02-21     RT-Thread    first version*/#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include <rtdevice.h>
#include <drv_common.h>#define LED1_PIN        GET_PIN(B, 5)
#define LED2_PIN        GET_PIN(E, 5)int main(void)
{int count = 1;rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);while (count++){rt_pin_write(LED1_PIN, PIN_LOW);rt_pin_write(LED2_PIN, PIN_HIGH);rt_thread_mdelay(1000);rt_pin_write(LED1_PIN, PIN_HIGH);rt_pin_write(LED2_PIN, PIN_LOW);rt_thread_mdelay(1000);}return RT_EOK;
}

适配CAN通讯功能

(1)通过上面的步骤,我们已经成功的创建了一个基础模板工程了。现在再增加CAN通讯的驱动部分。

增加CAN驱动

使能CAN驱动

(1)按照如下步骤使能RT-Thread中的CAN驱动。

在这里插入图片描述

(2)配置完成之后,按Ctrl+S保存,出现一下弹框,等待完成。

在这里插入图片描述

(3)进入board.h文件,添加如下代码。

/*-------------------------- CAN CONFIG BEGIN --------------------------*/#define BSP_USING_CAN
#define BSP_USING_CAN1
/*#define BSP_USING_CAN2*//*-------------------------- CAN CONFIG END --------------------------*/

在这里插入图片描述

STM32CubeMX使能CAN设备

(1)双击打开STM32CubeMX,按照下图使能CAN设备。
切记,配置完STM32CubeMX之后,一定一定记得将STM32CubeMX关闭!

在这里插入图片描述

(2)找到cubemx/Src/can.c文件,按照如下步骤添加构建。
<1>

在这里插入图片描述

<2>

在这里插入图片描述

<3>

在这里插入图片描述

增加CAN驱动代码

(1)此时我们需要找到RT-Thread官方的驱动代码。首先按照如下方式找到RT-Thread Studio的安装路径。

在这里插入图片描述

(2)例如,现在我们RT-Thread Studio的安装路径为D:\RT-Thread_Studio\soft\RT-ThreadStudio,那么我们只需要再这个路径后面加上\repo\Extract\RT-Thread_Source_Code\RT-Thread\4.0.3\bsp\stm32\libraries\HAL_Drivers。在这个路径中找到drv_can.c文件,然后复制到当前的工程drivers目录下。

在这里插入图片描述

(3)然后再在\repo\Extract\RT-Thread_Source_Code\RT-Thread\4.0.3\bsp\stm32\libraries\HAL_Drivers路径中找到drv_can.h文件,然后复制到当前的工程drivers\include目录下。

在这里插入图片描述

(4)此时进入RT-Thread studio,点击当前工程,右键,选择刷新,即可出现刚刚复制过来的两个驱动文件。

在这里插入图片描述

上机实操

(1)编译烧录,上机测试。输入list_device命令,出现can1设备。表面CAN驱动已经成功编译进入。

在这里插入图片描述

增加CAN命令使用例程

(1)通过上面的步骤,我们成功将CAN驱动编译进入了RT-Thread中。但是还没有完成真正意义上的CAN设备使用。此时我们可以增加一个CAN设备使用命令,用于测试。

添加CAN命令测试代码

(1)我们先在applications目录下增加一个can_test.c的文件
<1>

在这里插入图片描述

<2>

在这里插入图片描述

(2)将如下代码编写进入can_test.c文件中。

在这里插入图片描述

/** 程序清单:这是一个 CAN 设备使用例程* 例程导出了 can_test 命令到控制终端* 命令调用格式:can_test can1* 命令解释:命令第二个参数是要使用的 CAN 设备名称,为空则使用默认的 CAN 设备* 程序功能:通过 CAN 设备发送一帧,并创建一个线程接收数据然后打印输出。
*/#include <rtthread.h>
#include "rtdevice.h"#define CAN_DEV_NAME       "can1"      /* CAN 设备名称 */static struct rt_semaphore rx_sem;     /* 用于接收消息的信号量 */
static rt_device_t can_dev;            /* CAN 设备句柄 *//* 接收数据回调函数 */
static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size)
{/* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */rt_sem_release(&rx_sem);return RT_EOK;
}static void can_rx_thread(void *parameter)
{int i;rt_err_t res;struct rt_can_msg rxmsg = {0};/* 设置接收回调函数 */rt_device_set_rx_indicate(can_dev, can_rx_call);#ifdef RT_CAN_USING_HDRstruct rt_can_filter_item items[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 号过滤表 */};struct rt_can_filter_config cfg = {5, 1, items}; /* 一共有 5 个过滤表 *//* 设置硬件过滤表 */res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);RT_ASSERT(res == RT_EOK);
#endifwhile (1){/* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */rxmsg.hdr = -1;/* 阻塞等待接收信号量 */rt_sem_take(&rx_sem, RT_WAITING_FOREVER);/* 从 CAN 读取一帧数据 */rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));/* 打印数据 ID 及内容 */rt_kprintf("ID:%x", rxmsg.id);for (i = 0; i < 8; i++){rt_kprintf("%2x", rxmsg.data[i]);}rt_kprintf("\n");}
}int can_test(int argc, char *argv[])
{struct rt_can_msg msg = {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);/* 创建数据接收线程 */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");}// 更改后再发送十次for(rt_uint8_t send_ind = 0; send_ind < 10; send_ind++){rt_thread_mdelay(1000);msg.data[0] = msg.data[0] + 0x01;msg.data[1] = msg.data[1] + 0x01;msg.data[2] = msg.data[2] + 0x01;msg.data[3] = msg.data[3] + 0x01;msg.data[4] = msg.data[4] + 0x01;msg.data[5] = msg.data[5] + 0x01;msg.data[6] = msg.data[6] + 0x01;msg.data[7] = msg.data[7] + 0x01;/* 发送一帧 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_test, can device sample);

上机测试

(1)打开CAN分析仪,将波特率设置为500Kbps

在这里插入图片描述

(2)输入can_test命令,CAN分析仪上出现11次数据打印。

在这里插入图片描述

可能会遇到的问题

To initialize device:can1 failed

(1)在测试过程中,有可能会出现如下问题。

在这里插入图片描述

(2)处理办法很简单,将cubemx/Src/can.c加入构建。

在这里插入图片描述

can dev write data failed!

(1)如果测试出现这个错误,就说明你的CAN分析仪没有打开,或者是CAN分析仪波特率设置错误。

在这里插入图片描述

(2)处理办法很简单,打开CAN分析仪,设置正确的波特率,然后板子复位即可。

在这里插入图片描述

assertion failed at function:rt_object_init

(1)注意,每次can_test命令只能执行一次,因为第二次执行can_test命令就会出现这个报错,之后程序卡死。

在这里插入图片描述

(2)处理办法很简单,板子重新复位启动。

参考

(1)C站:RT-Thread系列08——CAN设备(CAN收发)
(2)C站:RT-Thread studio 添加CAN通信功能
(3)C站:STM32CubeMX创建CAN通讯工程+图莫斯UTA0403使用
(4)RT-Thread官方文档:CAN 设备
(5)C站:RT-Thread studio创建一个STM32F103ZE的RT-Thread标准版模板工程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/499142.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】MVCC机制

引入问题 首先看下面这张图&#xff0c;假如说一条数据经过了事务 2、3、4&#xff0c;到事务 5 的时候&#xff0c;进行两次查询&#xff0c;那这两次查询分别查询的是哪个事务版本的记录呢&#xff1f; 这就是我们要解决的问题&#xff0c;那么MVCC机制也就是为了解决这个问…

力扣hot100题解(python版29-32题)

29、删除链表的倒数第N个结点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&a…

金三银四,自动化测试面试题精选【美团二面】

面试一般分为技术面和hr面&#xff0c;形式的话很少有群面&#xff0c;少部分企业可能会有一个交叉面&#xff0c;不过总的来说&#xff0c;技术面基本就是考察你的专业技术水平的&#xff0c;hr面的话主要是看这个人的综合素质以及家庭情况符不符合公司要求&#xff0c;一般来…

Decision Transformer

DT个人理解 emmm, 这里的Transformer 就和最近接触到的whisper一样,比起传统Transformer,自己还设计了针对特殊情况的tokens。比如whisper里对SOT,起始时间,语言种类等都指定了特殊tokens去做Decoder的输入和输出。 DT这里的作为输入的Tokens由RL里喜闻乐见的历史数据:…

安秉源代码加密,不仅可以正常加密,对编译调试无任何影响

源代码防泄密对于很多企业来讲都在使用&#xff0c;特别是在广东一些做智能制造的企业&#xff0c;这些企业在很早就意识到源代码防泄密的重要性&#xff0c;很多企业采用加密的方式对企业的源代码进行加密&#xff0c;也采用了相对应的加密软件&#xff0c;但是在使用一些加密…

nodejs配置环境变量后不生效(‘node‘ 不是内部或外部命令,也不是可运行的程序或批处理文件)

一、在我们安装Node.js后&#xff0c;有时候会遇到node命令不管用的情况&#xff0c;关键是在安装时候已经添加配置了环境变量&#xff0c;向下面这样 但是还是不管用&#xff0c;这是因为环境变量配置不正确&#xff0c;权重不够&#xff0c;或者是命令冲突导致&#xff0c;解…

存内计算技术大幅提升机器学习算法的性能—挑战与解决方案探讨

一.存内计算技术大幅机器学习算法的性能 1.1背景 人工智能技术的迅速发展使人工智能芯片成为备受关注的关键组成部分。在人工智能的构建中&#xff0c;算力是三个支柱之一&#xff0c;包括数据、算法和算力。目前&#xff0c;人工智能芯片的发展主要集中在两个方向&#xff1…

nginx使用详解--缓存使用

Nginx 是一个功能强大的 Web 服务器和反向代理服务器&#xff0c;它可以用于实现静态内容的缓存&#xff0c;缓存可以分为客户端缓存和服务端缓存。 客户端缓存 客户端缓存指的是浏览器缓存, 浏览器缓存是最快的缓存, 因为它直接从本地获取(但有可能需要发送一个协商缓存的请…

【程序员的金三银四求职宝典】《春风拂面,代码在手:程序员的金三银四求职指南》

《春风拂面&#xff0c;代码在手&#xff1a;程序员的金三银四求职指南》 随着春风的轻拂&#xff0c;大地复苏&#xff0c;万物更新。在这个生机勃勃的季节&#xff0c;不仅自然界在迎接新生&#xff0c;对于广大的程序员朋友们而言&#xff0c;这也是一个全新的开始——金三…

数据库JSON类型到映射JAVA上

Mysql存放JSON数据如何映射JAVA实体类 概述&#xff1a;最近写在写SKU模块中&#xff0c;需要表中字段存放JSON类型数据&#xff0c;mybatis-plus在查询的时候如何跟JSON类型所匹配呢&#xff1f;再次记录一下。 直接上代码&#xff0c;后面有解释到底如何映射上的。 Mysql表…

推荐几款优秀免费开源的导航网站

&#x1f9a9;van-nav 项目地址&#xff1a;van-nav项目介绍&#xff1a;一个轻量导航站&#xff0c;汇总你的所有服务。项目亮点&#xff1a;全平台支持&#xff0c;单文件部署&#xff0c;有配套浏览器插件。效果预览 &#x1f9a9;发现导航 项目地址&#xff1a;nav项目…

Spring Cloud2022之OpenFeign使用以及部分源码分析

OpenFeign使用 Feign和OpenFeign Feign是Netflix开发的⼀个轻量级RESTful的HTTP服务客户端&#xff0c;可以使用⽤它来发起请求&#xff0c;进行远程调用。Fegin是以Java接口注解的⽅式调⽤Http请求&#xff0c;而不是像RestTemplate那样&#xff0c;在Java中通过封装HTTP请求…