介绍

前几天有幸收到C站的训练营学习邀请,了解到这两天即将举行的开放原子 RTT 训练营。博主算是一名嵌入式方向的小白,主要还是在裸机上进行开发,但对嵌入式的操作系统和实时系统很感兴趣。在这次学习训练营中借助一些学习示例,我对RTThread这款国产实时操作系统有了深入的了解。因此我的这篇总结博文也是主要围绕讲解的几个示例而展开的,至于 RTT 的详细介绍博主现在确实了解不多,因此可能要等日后学习再加以补充~

RT-Thread 是什么?

RT-Thread是一个C语言编写的实时操作系统(RTOS),针对嵌入式系统而设计,可以运行于多种架构。它具有实时性、微内核、轻量级、可裁剪等特点,支持多线程和时间片轮转调度等特性。相较于一般的裸机开发,即使使用了中断一次也只能先执行一个任务。RT-Thread 的引入允许分时等操作,同时进行操作。

环境搭建

教程来自:RT-Thread 环境搭建 https://atomgit.com/joyce/train-note
rtthread-nano atomGit 源码仓库:https://atomgit.com/OpenAtomFoundation/rtthread-nano

下载 RT-Thread Studio 后,安装对应包。

一个是 rt-thread 4.1.0 源码,一个是 st 固件包 stm32f411-st-nucleo。这次使用的开发板是 stm32f411,把 stm32f103c8t6 作为调试器。

然后双击 dpinst_amd64.exe 安装 stlink 驱动。

新建工程

本次培训讲解到的几个示例分别为:LED、按键、自动初始化机制、自定义msh命令、多线程、线程间通信、定时器、消息队列以及结合前面功能的摩斯电码包。因此我们需要新建一个项目,并在项目中下载一个现成的摩斯电码代码包以便开发时使用。

#mermaid-svg-ExCTs705DOg0WsHo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ExCTs705DOg0WsHo .error-icon{fill:#552222;}#mermaid-svg-ExCTs705DOg0WsHo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ExCTs705DOg0WsHo .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ExCTs705DOg0WsHo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ExCTs705DOg0WsHo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ExCTs705DOg0WsHo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ExCTs705DOg0WsHo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ExCTs705DOg0WsHo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ExCTs705DOg0WsHo .marker.cross{stroke:#333333;}#mermaid-svg-ExCTs705DOg0WsHo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ExCTs705DOg0WsHo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ExCTs705DOg0WsHo .cluster-label text{fill:#333;}#mermaid-svg-ExCTs705DOg0WsHo .cluster-label span{color:#333;}#mermaid-svg-ExCTs705DOg0WsHo .label text,#mermaid-svg-ExCTs705DOg0WsHo span{fill:#333;color:#333;}#mermaid-svg-ExCTs705DOg0WsHo .node rect,#mermaid-svg-ExCTs705DOg0WsHo .node circle,#mermaid-svg-ExCTs705DOg0WsHo .node ellipse,#mermaid-svg-ExCTs705DOg0WsHo .node polygon,#mermaid-svg-ExCTs705DOg0WsHo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ExCTs705DOg0WsHo .node .label{text-align:center;}#mermaid-svg-ExCTs705DOg0WsHo .node.clickable{cursor:pointer;}#mermaid-svg-ExCTs705DOg0WsHo .arrowheadPath{fill:#333333;}#mermaid-svg-ExCTs705DOg0WsHo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ExCTs705DOg0WsHo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ExCTs705DOg0WsHo .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-ExCTs705DOg0WsHo .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-ExCTs705DOg0WsHo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ExCTs705DOg0WsHo .cluster text{fill:#333;}#mermaid-svg-ExCTs705DOg0WsHo .cluster span{color:#333;}#mermaid-svg-ExCTs705DOg0WsHo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ExCTs705DOg0WsHo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

LED
morse
按键
自动初始化机制
自定义msh命令
多线程
线程间通信
定时器
消息队列

新建 RTT 项目,基于开发板:stm32f411-st-nucleo。

安装好后双击 rt-thread-settings,可以搜索软件包并给当前项目安装。搜索 morse 并添加。

如果安装错误是很常见的问题,我们只需要手动下载对应的包进行安装,再重新添加即可。

示例1:LED 间歇闪烁

嵌入式少不了点灯环节。这里主要是叫我们熟悉在 RTThread Studio 上的代码编写。

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>/* defined the LED0 pin: PA5 */
#define USER_LED_PIN               GET_PIN(A, 5)int main(void)
{int count = 1;/* set LED0 pin mode to output */rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);//输出模式while (count++){rt_pin_write(LED0_PIN, PIN_HIGH);rt_thread_mdelay(500);rt_pin_write(LED0_PIN, PIN_LOW);rt_thread_mdelay(500);}return RT_EOK;    //代表线程挂起成功
}

编译构建代码:点击红色框里的按钮。

下载代码:点击黄色框里的按钮。

打开对应终端:点击蓝色框里的按钮。

示例2:按键

按键功能类似,定义按键引脚,设置上拉输入模式,下降沿触发函数在终端输出,并启用。

#define USER_KEY GET_PIN(C, 13) // GET_PIN(H,4)void irq_callback()
{rt_kprintf("RT-Thread!\r\n");
}int main(void)
{rt_pin_mode(USER_KEY, PIN_MODE_INPUT_PULLUP);rt_pin_attach_irq(USER_KEY, PIN_IRQ_MODE_RISING_FALLING, irq_callback, RT_NULL);rt_pin_irq_enable(USER_KEY, PIN_IRQ_ENABLE);return 0;
}

示例3:自动初始化机制

板子上电时会触发一些函数。

初始化顺序 宏接口 描述
1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动
2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数
3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备
4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP
5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统
6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用

其中 INIT_APP_EXPORT 里挂载我们自己想在板子上电时运行的函数。

#include <rtthread.h>int export_app(void)
{rt_kprintf("export_app RT-Thread!\r\n");//板子启动时在终端输出这句话return 0;
}
INIT_APP_EXPORT(export_app);//挂载函数int main(void){return 0;
}

正确运行后,启动板子时就会输出 export_app RT-Thread!。

示例4:自定义 msh 命令

rt-thread 系统的终端叫做 FinalSH,也支持用户写入一些自己的 shell 命令。比如我们写一个命令,用户输入 hello 终端输出 hello RT-Thread! 。

void hello(void)
{rt_kprintf("hello RT-Thread!\n");
}MSH_CMD_EXPORT(hello , say hello to RT-Thread);//挂载函数

第二个参数是 description 命令的描述。比如我们打开 win cmd 输入 help,可以看到很多命令和他们的描述:

我们在 FinalSH 里输入 help,也可以看到我们自己定义的这个 msh 命令机器描述:hello say hello to RT-Thread

示例5:线程管理

这里要求我们写一个双线程的代码,优先级2>1,看看两者执行情况。导出到 msh 命令中。

#include <rtthread.h>#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5static rt_thread_t tid1 = RT_NULL;/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter)
{rt_uint32_t count = 0;while (1){/* 线程 1 采用低优先级运行,一直打印计数值 */rt_kprintf("thread1 count: %d\n", count ++);rt_thread_mdelay(500);}
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程 2 入口 */
static void thread2_entry(void *param)
{rt_uint32_t count = 0;/* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */for (count = 0; count < 10 ; count++){/* 线程 2 打印计数值 */rt_kprintf("thread2 count: %d\n", count);}rt_kprintf("thread2 exit\n");/* 线程 2 运行结束后也将自动被系统脱离 */
}/* 线程示例 */
int thread_sample(void)
{/* 创建线程 1,名称是 thread1,入口是 thread1_entry*/tid1 = rt_thread_create("thread1",thread1_entry, RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY, THREAD_TIMESLICE);/* 如果获得线程控制块,启动这个线程 */if (tid1 != RT_NULL)rt_thread_startup(tid1);/* 初始化线程 2,名称是 thread2,入口是 thread2_entry */rt_thread_init(&thread2,"thread2",thread2_entry,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY - 1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);

如上,线程1的代码是循环无限输出 1,2,3,4,5……线程2是输出0-9.

先创建并启动线程1,再创建并启动线程2,会发现虽然线程2晚启动,但是由于 PRIORITY 值比1小,优先级高,所以终端输出是先输出2的0-9,再开始输出1.

如果用延时函数延时创建线程2,可以看到1先输出了一段时间后被2打断,2执行完后继续输出1.

示例6:定时器

单片机很重要的一个内容:中断定时器。

#include <rtthread.h>/* 定时器的控制块 */
static rt_timer_t timer1;
static rt_timer_t timer2;
static int cnt = 0;/* 定时器 1 超时函数 */
static void timeout1(void *parameter)
{rt_kprintf("periodic timer is timeout %d\n", cnt);/* 运行第 10 次,停止周期定时器 */if (cnt++ >= 9){rt_timer_stop(timer1);rt_kprintf("periodic timer was stopped! \n");}
}/* 定时器 2 超时函数 */
static void timeout2(void *parameter)
{rt_kprintf("one shot timer is timeout\n");
}int timer_sample(void)
{/* 创建定时器 1  周期定时器 */timer1 = rt_timer_create("timer1", timeout1,RT_NULL, 10,RT_TIMER_FLAG_PERIODIC);//10ms调用timeout1一次。周期定时就是一段时间后停止/* 启动定时器 1 */if (timer1 != RT_NULL)rt_timer_start(timer1);/* 创建定时器 2 单次定时器 */timer2 = rt_timer_create("timer2", timeout2,RT_NULL, 30,RT_TIMER_FLAG_ONE_SHOT);//30ms调用timeout2一次/* 启动定时器 2 */if (timer2 != RT_NULL)rt_timer_start(timer2);return 0;
}/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(timer_sample, timer sample);

示例7:消息队列

线程2给线程1发消息,发20次,第八次是紧急消息,其他都是普通消息。线程1接收20次后停止接收。线程2发送20次后停止发送。

#include <rtthread.h>/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程 1 入口函数 */
static void thread1_entry(void *parameter)
{char buf = 0;rt_uint8_t cnt = 0;while (1){/* 从消息队列中接收消息 */if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK){rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf);if (cnt == 19){break;}}/* 延时 50ms */cnt++;rt_thread_mdelay(50);}rt_kprintf("thread1: detach mq \n");rt_mq_detach(&mq);
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{int result;char buf = 'A';rt_uint8_t cnt = 0;while (1){if (cnt == 8){/* 发送紧急消息到消息队列中 */result = rt_mq_urgent(&mq, &buf, 1);if (result != RT_EOK){rt_kprintf("rt_mq_urgent ERR\n");}else{rt_kprintf("thread2: send urgent message - %c\n", buf);}}else if (cnt >= 20) /* 发送 20 次消息之后退出 */{rt_kprintf("message queue stop send, thread2 quit\n");break;}else{/* 发送消息到消息队列中 */result = rt_mq_send(&mq, &buf, 1);if (result != RT_EOK){rt_kprintf("rt_mq_send ERR\n");}rt_kprintf("thread2: send message - %c\n", buf);}buf++;cnt++;/* 延时 5ms */rt_thread_mdelay(5);}
}/* 消息队列示例的初始化 */
int msgq_sample(void)
{rt_err_t result;/* 初始化消息队列 */result = rt_mq_init(&mq,"mqt",&msg_pool[0],     /* 内存池指向 msg_pool */1,                /* 每个消息的大小是 1 字节 */sizeof(msg_pool), /* 内存池的大小是 msg_pool 的大小 */RT_IPC_FLAG_PRIO  /* 如果有多个线程等待,优先级大小的方法分配消息 */);if (result != RT_EOK){rt_kprintf("init message queue failed.\n");return -1;}rt_thread_init(&thread1,"thread1",thread1_entry,RT_NULL,&thread1_stack[0],sizeof(thread1_stack), 25, 5);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",thread2_entry,RT_NULL,&thread2_stack[0],sizeof(thread2_stack), 25, 5);rt_thread_startup(&thread2);return 0;
}/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(msgq_sample, msgq sample);

示例9:morse 摩斯电码

这里只需要使用下载好的摩斯电码包即可。

代码使用方式:输入 morse_shell_example 可以查看 morse 码示例。根据摩斯电码表按下按键(如:快按一下是e,短按+长按是a)可以输入对应的字母,实际上是程序打印在终端的效果。

代码包下载地址:https://github.com/zhkag/morse

其中 inc 是头文件,src 是 .c 文件,samples 文件夹里是 FinalSH 对应的提示信息。

正确运行后,就可以通过按键输入 morse 码啦!

心得体会

在此之前博主的开发一直是基于裸机的,只是后台的死循环+前台的中断,并没有怎么接触操作系统的层面。因此接触 RT-Thread 的初次学习后最主要的感觉就是把课内纸上谈兵的操作系统拉到了现实的实践中,也能更真实地踏入这个领域。这也很大程度上归功于这款系统的轻量,安装方便,不需要大量资源即可运行的特点。

因为最近报名的嵌入式芯片系统大赛也需要使用 RTT 系统,所以博主还会利用手头的板子进行后续的深入学习,先尝试理解内核的基本概念和操作流程,到掌握线程、信号量、定时器等基本API的使用方法,以及亲自实现线程调度、通信、互斥、同步等代码,加深对嵌入式操作系统的理解。

开放原子训练营(第二季)RT-Thread Nano学习营学习有感相关推荐

  1. 开放原子训练营(第一季)铜锁探密 基于铜锁构建Web在线加密工具库(Go + React)

    简介 本文记录我参加开放原子训练营(第一季)铜锁探密活动,学习铜锁密码库和国密加密算法后,完成的小作品. 先简单介绍一下作品内容: Web在线加密工具库是一种提供加密算法在线使用的工具库,可以帮助用户 ...

  2. 开放原子训练营(第一季)铜锁探密,SM3杂凑算法加强至pro版

    目录 前言: 一.初遇铜锁 自我总结: 1.环境搭建,一次难忘的经历 2.键盘敲出的每一个命令,都是最美的音符 二.SM3杂凑算法增强改造 三.艺术源于生活 四.对开放原子开源基金会的感受 前言: 说 ...

  3. 开放原子训练营(第三季)inBuilder低代码开发实验室:货运单的开发

    开放原子开源基金会(OpenAtom Foundation),是一个非盈利组织,致力于推广开源技术和开放创新.其宗旨是倡导合作伙伴间的信息共享和资源共享,鼓励开源社区的创造性和互助精神.该基金会提供资 ...

  4. 基于GD32F103C8T6添加RT Thread nano设备框架并添加串口设备(以控制台console( uart0 )为例)

    最近没事琢磨了一下使用设备框架的问题.因为将串口注册到设备框架可以应用十分丰富的软件包. 于是就整理了一下手上的工程,重新将工程梳理了一遍. 像这样是十分清爽了,其中RTOS是操作系统源代码 并且学习 ...

  5. 开放原子训练营(第一季)铜锁探密:基于铜锁,在前端对登录密码进行加密,实现隐私数据保密性

    本文将基于 铜锁(tongsuo)开源基础密码库实现前端对用户登录密码的加密,从而实现前端隐私数据的保密性. 首先,铜锁密码库是一个提供现代密码学算法和安全通信协议的开源基础密码库,在中国商用密码算法 ...

  6. 正点原子STM32 H743完成RT Thread下的LAN8720 网卡驱动 LWIP跑起来

    ,目前RT官网对H743的支持力度还不理想,本想按照F407的搞定网卡的套路来搞定H743的网卡(因为phy也是LAN 8720),以为会很轻松,没想到却是一条遍布荆棘的路... 好在已经有不少大佬做 ...

  7. RT Thread根据开发板制作BSP方法

    之前一直不懂怎么使用RT Thread的软件包,感谢网上的大神,看了你们的博客后大概了解一些,在此做下记录.用RT Thread软件包需要RT Thread的系统,但是RT Thread和RT Thr ...

  8. 【涵子来信python大全】——第二季——opencv第二篇

    各位亲爱的读者,博主: 大家好,我是涵子.今天我们继续讲下去,如果不清楚上一章的内容,请从链接或者主页回去先读一遍之前的文章,否则今天的内容很难理解. (2条消息) [涵子来信&python大 ...

  9. Tongsuo/铜锁|「开放原子开源基金会」拜访篇

    文|杨洋(花名:凯申 ) 铜锁开源密码库创始人 蚂蚁集团高级技术专家 本文 500 字 预计阅读时间 3 分钟 2023 年 3 月,铜锁密码学开源项目的研发和运营团队拜访了位于北京的开放原子开源基金 ...

最新文章

  1. 网络存储导论第七章:重要系统灾备方法
  2. 解决cookie跨域访问
  3. 为AI学术小白铺平道路,NeurIPS 2019推出New In ML2019特别会议
  4. 单链表之无头和有头--逆序
  5. freeredius3.0 mysql_EDIUS非线性编辑系统价格,4k视频编辑系统
  6. AsciidocFX相关
  7. C++中include头文件使用与的区别
  8. pojCashier Employment
  9. sql server 查询当前月份日期列表数据
  10. 世界哲学日2600年西方哲学思想发展史谱系图和哲学50命题(公号回复“西方哲学”下载PDF彩标典藏版,欢迎转发、赞赏、支持科教)
  11. linux设置双屏拼接_Linux 与Windows(A卡、N卡)下折腾双屏、3屏拼接
  12. php随机生成6个数字,php随机产生六位数密码的实例代码
  13. 河北单招2021计算机类,2021河北省单招十大类专业
  14. Python 爬虫监控女神的QQ空间新的说说,实现秒赞,并发送说说内容到你的邮箱
  15. No Matter What
  16. 「Computer Vision」Note on Seamless Nudity Censorship(裸体审查)
  17. 一个简单有效的兼容IE7浏览器的办法
  18. 中国特种腈行业市场供需与战略研究报告
  19. C1认证学习二十六(基础选择器)
  20. SQLyog连接linux数据库问题

热门文章

  1. c#foreach循环用法详解
  2. IWebBrowser2登陆网站(无验证码的)
  3. DVWA登录出现csrf token incorrect
  4. Gitlab CI 拉取 submodules
  5. 《Madame Curie》
  6. 微博,微信,微信朋友圈,QQ分享工具类ShareUtil
  7. 简单的sql server查询
  8. #1623 : 有歧义的号码
  9. inline函数返回值_C++ inline关键字详解
  10. pta c++ 填空题