多线程-RGB_LED闪烁灯
目录
- 线程
- RTX Thread API
- 实验:RGB灯闪烁
- 准备
- 配置线程
- 编译运行
- 其他
- multiple instances(多个实例)
- joinable Thread(可接合线程)
- 小结
- 参考资料
开始学习线程之前,你可能需要复习:
☞为什么使用RTOS?
☞RTX系统移植
如果准备就绪,那么,进入正题!
线程
线程概念
In CMSIS-RTOS2, the basic unit of execution is a “Thread”. A Thread is very similar to a ‘C’ procedure but has some very fundamental (根本的)differences. An RTOS program is made up of a number of threads, which are controlled by the RTOS scheduler.
线程是程序执行的基本单元。我们可以将程序分解为多个功能相对独立的子任务(类似C函数模块化调用),然后为每个子任务分配一个线程,而RTOS负责子任务之间的调度,从而实现多线程的"并行",提高程序的实时性和效率。
unsigned int procedure(void){ // C function...return (ch);
}void thread(void* arg){ // threadwhile(1){... }
}
线程调度
问题来了,scheduler
是如何进行线程调度的?。其实很简单,scheduler以SysTick
产生的周期性中断作为时基,给每个线程分配一个时间片(相当于分配多少个sysTick),当某个线程的时间片用完了,就阻塞该线程,而调度另一个就绪线程执行。那SysTick
是什么?我们知道微处理器上面有很多时序电路,所以我们经常会用到石英晶振来产生稳定的时钟信号。一个时钟周期就是一个SysTick
,它一般是一个很精准的固定量,比如对于8MHz的晶振,其时钟周期是1/8MHz=0.125us1/8MHz = 0.125us1/8MHz=0.125us,即SysTick = 0.125us
。
线程管理
When a thread is created, it is also allocated its own thread ID. This is a variable which acts as a handle for each thread and is used when we want to manage the activity of the thread.
每个线程都有一个id,我们可以通过这个id来管理线程。
osThreadId id1,id2,id3; // 线程id
线程切换
当线程切换时,kernel会将当前线程的所有变量状态保存到该线程的栈中,同时将该线程的运行信息保存到线程控制块中,然后执行另外一个线程。
线程通常有三种状态:运行态、就绪态、阻塞态。
大致了解这么多吧~,详细可以看操作系统相关的书籍。
RTX Thread API
操作系统的一大优点就是:抽象。它将底层的硬件资源抽象成一组接口,然后用户便可以直接在这些接口上进行开发。这样既提高了开发效率,也降低了开发门槛。CMSIS-RTX5 提供了线程的创建、删除等接口,主要包括如下:
osTreadId_t
:定义线程的ID
osThreadAttr_t
: 定义线程属性结构体
osThreadNew
: 创建线程
osThreadExit
:线程退出
实验:RGB灯闪烁
功能:通过三个线程分别控制RGB LED 灯的三个灯的亮灭,从而实现颜色的合成。
硬件:stm32f103zet6
软件:keil MKD5.23
,CMSIS-RTX5
准备
- (一)系统移植
- (二)修改配置
【系统移植】请参考:RTX系统移植,具体根据硬件平台,比如GPIO等。
【修改配置】配置用户线程数量,keil MDK默认1个用户线程,所以我们需要修改,笔者修改为10。另外,有需要也可以配置线程的栈空间大小。
配置线程
- 创建线程ID
- 创建线程函数和线程属性结构体
- 创建线程
【创建线程ID】
创建三个线程,分别表示红、绿、蓝三个线程ID。
osThreadId_t red_led,green_led,blue_led;
【创建线程函数和线程属性结构体】
这里以线程red_led
为例,其他两个类似。首先,我们来看线程创建函数osThreadNew
,其函数原型为:
osThreadId_t osThreadNew(osThreadFunc_t func,void* argument,const osThreadAttr_t* attr)
该函数有三个参数:
func
: 线程名字argument
: 线程函数的参数attr
: 线程的属性配置,包括线程函数名,栈大小,优先级等等。
返回值
osThreadId_t
: 表示该线程的ID号。
所以,在创建线程之前,我们需要先定义:func
和attr
// 线程函数一:红灯void redLight(void* arg){while(1){LED1(ON); // 声明在bsp_led.h中osDelay(100); // RTX 提供的延时函数,这里延时100个SysTickLED1(OFF);osDelay(100);}}
线程函数就是一般的函数形式,唯一注意的是两点:函数内包含while(1)
死循环,以及参数为void*
。重点说一下线程属性结构体attr
。
类型 | 数据成员 | 描述 |
---|---|---|
const char*
|
name
|
线程名 |
uint32_t
|
attr_bits
|
\ 不关心 ~ |
void*
|
cb_mem
|
线程控制块起始地址,默认NULL 为动态分配
|
uint32_t
|
cb_size
|
线程控制块大小,默认NULL 为0
|
void*
|
stack_mem
|
线程栈起始地址,默认NULL 为使用定长内存池
|
uint32_t
|
stack_size
|
线程栈大小,默认NULL 为0
|
osPriority_t
|
priority
|
线程优先级,默认osPriorityNormal
|
TZ__ModuleId_t
|
tz_module
|
安全区标志,默认0
|
uint32_t
|
reserved
|
保留位,必须是0
|
一共9个,好多参数啊~,其实不必担心,我们常用的可能就name
和Priority
,其余的默认就好。所以,就有如下这种简易表示法。
// 线程一属性结构体static const osThreadAttr_t threadAttr_LED1 = {.name = "redLight",};
不过要使用这种方式,keil必须支持c99
,如下:
这里再谈一下app_main
,这个线程是CMSIS-RTX5
提供的,相当于一个启动线程。它的任务就是创建用户需要的线程,完成使命后就退出。
void app_main (void *arg) {...
}// 线程结构体参数,使用默认
static const osThreadAttr_t threadAttr_app_main = { "app_main",NULL,NULL,NULL,NULL,NULL,osPriorityNormal,NULL,NULL
};
这里的线程属性结构体用的直接初始化的形式,主要是为了知道有这种方式,其实不用写也没关系,创建线程时直接传入NULL
,使用默认值即可。
osThreadNew(app_main, NULL, NULL); // 直接传NULL
【创建线程】
搞定了func
和attr
,就可以创建线程啦~,我们直接在app_main
中创建三个线程。
void app_main (void *arg) {// initializeLED_GPIO_Config();// create three threadsred_led = osThreadNew(redLight,NULL,&threadAttr_LED1); // 也可用NULL默认属性结构体green_led = osThreadNew(greenLight,NULL,&threadAttr_LED2);blue_led = osThreadNew(blueLight,NULL,&threadAttr_LED3);// complete task,exit!osThreadExit();
}
完整代码:
main.c
/*----------------------------------------------------------------------------* CMSIS-RTOS 'main' function template*---------------------------------------------------------------------------*/
#include "RTE_Components.h"
#include CMSIS_device_header
#include "cmsis_os2.h"
#include "bsp_led.h"#ifdef RTE_Compiler_EventRecorder
#include "EventRecorder.h"
#endif// Thread Information
osThreadId_t red_led,green_led,blue_led;
/*----------------------------------------------------------------------------* Task thread*---------------------------------------------------------------------------*/// 红灯void redLight(void* arg){while(1){LED1(ON);osDelay(100);LED1(OFF);osDelay(100);}} static const osThreadAttr_t threadAttr_LED1 = {.name = "redLight",};// 绿灯void greenLight(void* arg){while(1){LED2(ON);osDelay(100);LED2(OFF);osDelay(100);}}static const osThreadAttr_t threadAttr_LED2 = {.name = "greenLight",};// 蓝灯void blueLight(void* arg){while(1){LED3(ON);osDelay(100);LED3(OFF);osDelay(100);}}static const osThreadAttr_t threadAttr_LED3 = {.name = "blueLight",};/*----------------------------------------------------------------------------* main thread*---------------------------------------------------------------------------*/
void app_main (void *arg) {// initializeLED_GPIO_Config();// create three threadsred_led = osThreadNew(redLight,NULL,&threadAttr_LED1); // 也可用NULL默认属性结构体green_led = osThreadNew(greenLight,NULL,&threadAttr_LED2);blue_led = osThreadNew(blueLight,NULL,&threadAttr_LED3);// exitosThreadExit();
}// 线程参数,使用默认
static const osThreadAttr_t threadAttr_app_main = { "app_main",NULL,NULL,NULL,NULL,NULL,osPriorityNormal,NULL,NULL
};int main (void) {// System InitializationSystemCoreClockUpdate();
#ifdef RTE_Compiler_EventRecorder// Initialize and start Event RecorderEventRecorderInitialize(EventRecordError, 1U);
#endifosKernelInitialize(); // Initialize CMSIS-RTOSosThreadNew(app_main, NULL, &threadAttr_app_main); // Create application main threadosKernelStart(); // Start thread executionfor (;;) {}
}
RGB_LED电路图
有些小伙伴,可能不理解RGB_LED,这里贴个图。
其实就是三个LED灯,集成在一起,管脚配置就不需要我介绍了吧,各位这么聪明~
编译运行
编译配置请看:RTX系统移植
我们来预料实现效果:我们创建了三个线程,分别点闪烁RGB_LED等的红绿蓝三个灯,由于线程是“并行”的,那么根据三原色的合成,最后RGB_LED灯应该是白光闪烁。
看看具体效果:
看见那个白闪闪的大灯了嘛,还不错哦,和预期一样。说明操作系统确实跑起来了,且三个线程正常工作~
其他
线程还有其他知识点,比如mutiple instances
和joinable threads
。
multiple instances(多个实例)
我们知道,线程创建函数为:
osThreadId_t osThreadNew(osThreadFunc_t func,void* argument,const osThreadAttr_t* attr);
多个实例的本质就是基于同一个线程函数func
来创建多个线程实例,RTX根据参数argument
来分配不同的线程ID。
比如,上面LED闪烁灯实验,三个线程函数都基本相同,只是LED号不同而已,所以我们可以将LED号传给参数argument,从而只需一个线程函数,便实现三个线程实例。
// base funcvoid Light(void* arg)
{while(1){switch((int)arg){case 1:{LED1(ON);osDelay(100);LED1(OFF);osDelay(100);break;}case 2:{LED2(ON);osDelay(100);LED2(OFF);osDelay(100);break;}case 3:{LED3(ON);osDelay(100);LED3(OFF);osDelay(100);break;}} }}
线程属性结构体不变,这时创建三个线程可以这样:
// 定义参数指针
#define red (void*)1
#define green (void*)2
#define blue (void*)3// create three threads
red_led = osThreadNew(Light,red,&threadAttr_LED1); // 也可用NULL默认属性结构体
green_led = osThreadNew(Light,green,&threadAttr_LED2);
blue_led = osThreadNew(Light,blue,&threadAttr_LED3);
编译后,下载到开发板运行效果是一样的。
joinable Thread(可接合线程)
额,先不管翻译的恰当与否,最重要的是,啥是可接合线程?
a second thread can join it by calling osThreadJoin(). This will cause the second thread to deschedule and remain in a waiting state until the thread which has been joined is terminated.
比如你正在看电视(线程A),这时候你妹来了,说要用你的电脑处理一个word文档(线程B)。由于是你妹,你当然选择接受了(线程B is joinable)。当然,这时候你就不能继续看电视了(线程A等待),直到你妹处理完word(线程B)结束,然后你才可以继续看电视(线程A恢复)。这里可接合线程就是指的线程B。可接合线程的设置在线程属性结构体中设置,也就是之前我们不关心的那个属性attr_bits
。
static const osThreadAttr_t ThreadAttr_thread_joinable = {.attr_bits = osThreadJoinable,
};
当在某个线程中调用osThreadJoin()
后,该线程就会等待,直到可接合线程退出为止。
osThreadJoin(<joinable_thread_handle>); // 函数原型
结合前面的例子,我们可以让red light 变成 joinable thread,然后在light线程中调用osThreadJoin()
。
首先,这里设置red light的属性,使其 joinable
static const osThreadAttr_t threadAttr_LED1 = {.name = "redLight",.attr_bits = osThreadJoinable,};
然后还需要单独对线程函数做一下改变
// 红灯void redLight(void* arg)
{while(1){LED1(ON);osDelay(100);LED1(OFF);osDelay(100);}}
// base funcvoid Light(void* arg)
{while(1){switch((int)arg){default:red_led = osThreadNew(redLight,red,&threadAttr_LED1); osThreadJoin(red_led); // 调用osThreadJoin()case 2:{ // 红 + 绿LED2(ON);osDelay(100);LED2(OFF);osDelay(100);break;}case 3:{ // 红 + 蓝LED3(ON);osDelay(100);LED3(OFF);osDelay(100);break;}} }}
最后修改线程创建函数
// create three threads
green_led = osThreadNew(Light,green,&threadAttr_LED2);
blue_led = osThreadNew(Light,blue,&threadAttr_LED3);
joinable threads主要用于临时创建一个线程,用来处理一些事情,然后结束任务,主线程继续执行。
嗯大致就是这些~
小结
本文主要针对线程的概念,创建和编写做了简要介绍,最后基于一个具体的多线程RGB_LED灯的例子,让大家更直观理解多线程是如何工作和实现的。
重点
- 线程的概念
- 线程的创建,使用,和删除
- 多实例和可接合线程
希望对大家有所帮助,有不懂或者纠错的欢迎留言~,谢谢!
参考资料
☞官方tutorial
多线程-RGB_LED闪烁灯相关推荐
- STM32系统定时器SysTick(只能向下递减)延时闪烁灯
参考:stm32 系统定时器 SysTick 作者:点灯小哥 发布时间: 2021-03-10 13:46:00 网址:https://blog.csdn.net/weixin_46016743/ar ...
- FPGA项目一:1位闪烁灯设计
文章目录 项目一:1位闪烁灯设计 第一节 项目背景 第二节 设计目标 第三节 设计实现 3.1 顶层信号 3.2 信号设计 3.3 信号定义 第四节 综合和上板 4.1 新建工程 4.2 综合 4.3 ...
- ESP32开发二_LED闪烁灯
LED闪烁灯 交流QQ: 1048272975 QQ交流群: 636564526 控制LED灯的亮灭是MCU开发中一个最简单的应用功能,实现这个应用功能包含了MCU开发中工程的 ...
- 单片机八灯交替闪烁c语言代码,单片机闪烁灯汇编语言源代码大全(四款闪烁灯的汇编语言源代码)...
单片机闪烁灯汇编语言源代码大全(一) 1.单片机AT89C51的P2口接8个发光二极管,让这8个发光二极管显示闪烁功能,即八灯亮2S,熄灭3S,如此循环. 参考程序 ORG 0000H start: ...
- 最基础硬件学习 | 简单闪烁灯制作
一.查询资料 1.学习三极管基本工作原理 重点:三极管的放大特性,Ib与Ic的关系 参考:三极管的工作原理(详细.通俗易懂.图文并茂)http://www.360doc.com/content/17/ ...
- 单片机c语言小灯闪烁,单片机c语言闪烁灯程序.doc
单片机c语言闪烁灯程序 1. 闪烁灯 1. 实验任务 如图4.1.1 所示:在P1.0 端口上接一个发光二极管L1,使L1 在不停地一亮 灭,一亮一灭的时间间隔为0.2 秒. 2. 电路原理图 图4. ...
- 单片机实例1——闪烁灯(硬件电路图+汇编程序+C语言程序)
1. 闪烁灯 1. 实验任务 如图4.1.1所示:在P1.0端口上接一个发光二极管L1,使L1在不停地一亮一灭,一亮一灭的时间间隔为0.2秒. 2. 电路原理图 图4.1.1 3. 系统板上硬件连线 ...
- 51单片机------闪烁灯(实验报告)
实验一:LED闪烁灯 一.实验目的 掌握51单片机开发板的使用步骤: 掌握51单片机开发板所需软件的安装过程: 以LED灯闪烁为例子,掌握软件KEIL4的使用方法. 二.实验设备 实验仪器设备: 计算 ...
- 单片机c语言每隔1m闪烁一次,单片机c语言闪烁灯程序
1.闪烁灯 1.实验任务 如图所示:在端口上接一个发光二极管L1,使L1 在不停地一亮 灭,一亮一灭的时间间隔为秒. 2.电路原理图 图系统板上硬件连线把"单片机系统"区域中的端口 ...
最新文章
- Ubuntu14搭建配置青岛大学OJ系统
- 西安交大计算机考研分数线2020院线,西安交通大学2020研究生复试分数线预计4月中旬左右公布...
- SQL Server数据库大型应用解决方案总结【转】
- lamp里php 的升级,lamp架构之升级php版本
- iPhone SE 3 5G版发布在即 旧款将降价到千元出头
- 游标中的static参数
- 洛谷 P1631 序列合并
- linux去除内容重复行,实例详细说明linux下去除重复行命令uniq
- LintCode 1350: Excel Sheet Column Title
- ubuntu 下的限速软件 wondershaper 以及 命令行测试网速
- 《Xenogears》(异度装甲)隐含的原型与密码
- 给控件做数字签名——摘录自阿泰博客
- 程序在单片机里是如何运行的?
- Redis源码学习(13),t_set.c 学习(一),sadd,srem 命令学习
- 打开fiddler 电脑无法上网问题
- 彻底理解numpy中的axis
- Linux内核编程接口函数
- 使用MySQL的聊天室_聊天室phpmysql(一)
- 图书管理系统(C文件读、存)
- 如何用python爬取e-hentai的图片
热门文章
- cad两直线相交画圆弧,CAD 两直线,怎么用圆弧连接?
- 大数据分析师·人才培养·高薪起航
- 《Unity Shader入门精要》彩图版免费分享~~~~~
- OpenVINO之链接库
- 怎样关闭qq位置定位服务器,手机qq怎么关闭定位
- MAC常用进入文件夹剪贴粘贴打开文件夹快捷键
- [竞赛01]2021CCF BDCI新闻摘要自动生成Baseline-T5模型
- 南京工资个税计算机,南京个税计算器_南京税后月薪|工资计算器_南京个人所得税查询 - Tax518...
- 好斗or炒作?甲骨文“撕咬”过的那些对手 - 爱上英语题库系统|郭雄飞
- 微信小程序有哪些优点和价值