main函数方式点亮
接口函数
RT-Thread工程在制作BSP时已开启了对gpio的驱动
RTT操作系统通过I/O设备模型框架管理
GPIO通过PIN设备驱动框架管理,应用程序需要通过PIN设备管理接口访问GPIO
接口函数如图
使用流程
获取引脚编号
RT-Thread 提供的引脚编号需要和芯片的引脚号区分开来,它们并不是同一个概念,引脚编号由 PIN 设备驱动程序定义,和具体的芯片相关。
有2种方式可以获取引脚编号:使用宏定义或者查看PIN 驱动文件。
- 使用宏定义
GET_PIN(port, pin)
即可自动获取
- 查看驱动文件
static const rt_uint16_t pins[] =
{
__STM32_PIN_DEFAULT,
__STM32_PIN_DEFAULT,
__STM32_PIN(2, A, 15),
__STM32_PIN(3, B, 5),
__STM32_PIN(4, B, 8),
__STM32_PIN_DEFAULT,
__STM32_PIN_DEFAULT,
__STM32_PIN_DEFAULT,
__STM32_PIN(8, A, 14),
__STM32_PIN(9, B, 6),
... ...
}
引脚A15的引脚号为2
设置引脚模式
void rt_pin_mode(rt_base_t pin, rt_base_t mode);
参数 | 描述 |
---|---|
pin | 引脚编号 |
mode | 引脚工作模式 |
目前 RT-Thread 支持的引脚工作模式可取如所示的 5 种宏定义值之一,每种模式对应的芯片实际支持的模式需参考 PIN 设备驱动程序的具体实现:
#define PIN_MODE_OUTPUT 0x00 /* 输出 */
#define PIN_MODE_INPUT 0x01 /* 输入 */
#define PIN_MODE_INPUT_PULLUP 0x02 /* 上拉输入 */
#define PIN_MODE_INPUT_PULLDOWN 0x03 /* 下拉输入 */
#define PIN_MODE_OUTPUT_OD 0x04 /* 开漏输出 */
设置引脚电平
void rt_pin_write(rt_base_t pin, rt_base_t value);
参数 | 描述 |
---|---|
pin | 引脚编号 |
value | 电平逻辑值,可取 2 种宏定义值之一:PIN_LOW 低电平,PIN_HIGH 高电平 |
上机实验
#define GPIONum_LED GET_PIN(C, 2)
int main(void)
{
rt_pin_mode( GPIONum_LED, PIN_MODE_OUTPUT );
while(1)
{
rt_pin_write( GPIONum_LED, PIN_LOW );
}
return RT_EOK;
}
thread线程方式点亮
在用main函数点亮led时,存在一个问题
在RTT操作系统中,认为main函数是主线程的一个用户程序,在其中用直接用while循环,会导致低优先级的线程无法分配到资源
而且main函数往往放置用户程序的初始化函数,一次性执行
为使操作系统使用更加规范化,使用线程来点亮led
接口函数
创建线程
一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。
可以通过如下的接口创建一个动态线程:
rt_thread_t rt_thread_create(const char* name,
void (*entry)(void* parameter),
void* parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick);
调用这个函数时,系统会从动态堆内存中分配一个线程句柄以及按照参数中指定的栈大小从动态堆内存中分配相应的空间。分配出来的栈空间是按照 rtconfig.h 中配置的 RT_ALIGN_SIZE 方式对齐。线程创建 rt_thread_create() 的参数和返回值见下表:
参数 | 描述 |
---|---|
name | 线程的名称;线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分会被自动截掉 |
entry | 线程入口函数 |
parameter | 线程入口函数参数 |
stack_size | 线程栈大小,单位是字节 |
priority | 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0\~255,数值越小优先级越高,0 代表最高优先级 |
tick | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 |
返回 | —— |
thread | 线程创建成功,返回线程句柄 |
RT_NULL | 线程创建失败 |
线程入口
线程控制块中的 entry 是线程的入口函数,它是线程实现预期功能的函数。
线程的入口函数由用户设计实现,一般有以下两种代码形式:
-无限循环模式:
在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务:
void thread_entry(void* paramenter)
{
while (1)
{
/* 等待事件的发生 */
/* 对事件进行服务、进行处理 */
}
}
线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU 使用权的动作,如循环中调用延时函数或者主动挂起。用户设计这种无线循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。
-顺序执行或有限次循环模式:
如简单的顺序语句、do whlie() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。
static void thread_entry(void* parameter)
{
/* 处理事务 #1 */
…
/* 处理事务 #2 */
…
/* 处理事务 #3 */
}
启动线程
创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化 / 创建成功后调用下面的函数接口让该线程进入就绪态:
rt_err_t rt_thread_startup(rt_thread_t thread);
当调用这个函数时,将把线程的状态更改为就绪状态,并放到相应优先级队列中等待调度。如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。线程启动接口 rt_thread_startup() 的参数和返回值见下表:
参数 | 描述 |
---|---|
thread | 线程句柄 |
返回 | —— |
RT_EOK | 线程启动成功 |
-RT_ERROR | 线程起动失败 |
上机实验
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
//LED点亮线程的参数定义
#define THREAD_PRIORITY 25 //线程优先级(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义优先级范围)
#define THREAD_STACK_SIZE 512 //线程栈大小,单位是字节
#define THREAD_TIMESLICE 5 //同优先级线程时间片大小
#define GPIONum_LED GET_PIN(C, 2) //获取C2引脚号
/* 线程 1 的入口函数 */
static void thread_App_LED_entry(void *parameter)
{
rt_kprintf("LED is On\n"); //串口命令行打印
rt_pin_write( GPIONum_LED, PIN_LOW ); //设置引脚电平
}
/*调度器钩子函数,在发生线程跳转时被调用*/
static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to)
{
//打印线程从哪里跳到了哪里
rt_kprintf("\n from: %s --> to: %s \n", from->name , to->name);
}
static rt_thread_t tid1 = RT_NULL;//用来接受线程控制块
int thread_LED_Init(void)
{
//设置调度器钩子
rt_scheduler_sethook(hook_of_scheduler);
//设置GPIO引脚模式
rt_pin_mode( GPIONum_LED, PIN_MODE_OUTPUT );
//创建线程
tid1 = rt_thread_create("thr_LED",
thread_App_LED_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
return 0;
}
//导出到 msh 命令列表中,可以在串口命令行中执行LED线程的初始化,进而启动线程,点亮LED
MSH_CMD_EXPORT(thread_LED_Init, thread LED On);
打印线程跳转情况(tidle0为空闲线程)
同理,这里的LED线程的线程(thr_LED)入口函数中没有循环,因此该线程为一次性线程,执行过一次后即会被操作系统自动删除并释放空间
若想要线程永远保留,可在线程内添加死循环与延时函数结合。
在延时中,线程进入睡眠模式,即可为低优先级线程让出资源。