一、项目需求
一直想在一些小产品上用单片机替代通用的控制器提高性价比,当时申请的这块开发板也是看中了这个板子采用RISC-V的架构,资源丰富,并且采用国产的软件。
在上一篇试用笔记中实现了多串口的应用,这一篇主要是参考《RT-THREAD 编程指南》学习多线程的应用,和大家一起分享一下测试心得。
二、RT-Tthread 概述
RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。
事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在 RT-Thread 系统中,任务通过线程实现的,RT-Thread 中的线程调度器也就是以上提到的任务调度器。
三、主要功能
对于设备的控制,主要是接收操作面板的控制开关、旋钮等的输入、驱动继电器触点动作、与其他的模块如显示屏等通讯。主要涉及GPIO、ADC、UART等。 希望能通过这些功能的实现对嵌入式实时多线程操作系统有所理解。
对于设备的控制比较相似,所以想做一个通用的模板工程,对于不同的设备做最小的修改方便今后的应用。所以就将输入(input.c)、输出(output.c)、串口1(ser_uart1.c)、串口2(ser_uart2.c)、串口3(ser_uart3.c)、串口4(ser_uart4.c)分成不同的c源码文件,并分别创建不同优先级别的线程。
为了演示方便,还增加了RTC的功能,需要打开模拟RTC组件。
四、代码分析
4.1主线程
在系统启动时,系统会创建 main 线程,它的入口函数为 main_thread_entry(),用户的应用入口函数 main() 就是从这里真正开始的,系统调度器启动后,main 线程就开始运行,过程如下图,用户可以在main() 函数里添加自己的应用程序初始化代码。
主线程调用过程:
我这里的主线程主要是更新显示输入、输出状态以及RTC的实时时间。为了直观一些,本来想沿用模板中的闪灯功能,但是发现,PB4上的蓝色灯一直发出微弱的光,用表测量始终是2.4V,不知道什么原因。
/* defined the LED1 pin: PB4 */
#define LED1_PIN 90
extern int D_IN_1,D_IN_2,D_IN_3,D_IN_4;
extern int D_OUT_1,D_OUT_2,D_OUT_3,D_OUT_4;
extern char str1;
int main(void)
{
char * str;
char buf = 0;
uint32_t Speed = 100;
/ set LED1 pin mode to output */
rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
while (1)
{
rt_pin_write(LED1_PIN, PIN_LOW);
rt_thread_mdelay(Speed);
rt_pin_write(LED1_PIN, PIN_HIGH);
rt_thread_mdelay(Speed);
rt_kprintf("main----\n");
rt_kprintf("D_IN_1:%d,D_IN_2:%d\n",D_IN_1,D_IN_2);
rt_kprintf("D_OUT_1:%d,D_OUT_2:%d\n",D_OUT_1,D_OUT_2);
rt_kprintf("RTC:%s\n",str1);
}
}
4.2输入线程
输入用开发板上的按键来演示,KEY1,KEY2,KEY3分别连接PA4,PA5,PA6。测试中发现,KEY3按键无输入,后来发现是按键本身的问题,直接从PA6引脚输入是有效的。
#include <stdint.h>
#include <rtthread.h>
#include <rtdevice.h>
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 5
/* defined the KEY1 pin: PA4 PA5 PA6 */
#define KEY1_PIN 29
#define KEY2_PIN 30
#define KEY3_PIN 31
int D_IN_1,D_IN_2,D_IN_3,D_IN_4;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void thread1_entry(void *parameter)
{
uint32_t Speed = 200;
rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(KEY2_PIN, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(KEY3_PIN, PIN_MODE_INPUT_PULLUP);
while (1)
{
if (rt_pin_read(KEY1_PIN)==PIN_LOW )
{
//rt_kprintf("INPUT_1:---on\n");
D_IN_1=1;
}
else {
D_IN_1=0;
}
if (rt_pin_read(KEY2_PIN)==PIN_LOW)
{
//rt_kprintf("INPUT_2:---on\n");
D_IN_2=1;
}
else {
D_IN_2=0;
}
if (rt_pin_read(KEY3_PIN)==PIN_LOW)
rt_kprintf("k_KEY3--on\n");
}
}
int key_sample(void)
{
rt_thread_init(&thread1,
"thread1",
thread1_entry,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
rt_thread_startup(&thread1);
return 0;
}
INIT_APP_EXPORT(key_sample);
4.3输出线程
输出通过开发板上的红色和绿色led灯来演示。分别是PB5和PA8。
#include <stdint.h>
#include <rtthread.h>
#include <rtdevice.h>
#define THREAD_PRIORITY 11
#define THREAD_TIMESLICE 5
/* defined the LED1 pin: PA8 /
#define LED1_PIN 67
/ defined the LED1 pin: PB5 /
#define LED2_PIN 91
/ defined the LED1 pin: PA4 /
#define LED3_PIN 90
int D_OUT_1,D_OUT_2,D_OUT_3,D_OUT_4;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/ 线 程 1 入 口 */
static void thread1_entry(void parameter)
{
uint32_t Speed = 50;
/ set LED1 pin mode to output */
rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LED3_PIN, PIN_MODE_OUTPUT);
while (1)
{
D_OUT_1=PIN_LOW;
D_OUT_2= PIN_HIGH;
rt_pin_write(LED1_PIN, PIN_LOW);
rt_pin_write(LED2_PIN, PIN_HIGH);
rt_pin_write(LED3_PIN, PIN_HIGH);
rt_thread_mdelay(Speed);
D_OUT_1=PIN_HIGH;
D_OUT_2= PIN_LOW;
rt_pin_write(LED1_PIN, PIN_HIGH);
rt_pin_write(LED2_PIN, PIN_LOW);
rt_pin_write(LED3_PIN, PIN_LOW);
rt_thread_mdelay(Speed*10);
}
}
int out_sample(void)
{
rt_thread_init(&thread1,
"thread1",
thread1_entry,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
rt_thread_startup(&thread1);
return 0;
}
INIT_APP_EXPORT(out_sample);
4.4串口通讯
串口通信的线程是一样的,这里只把uart2的代码贴出来,其他的可以参考我的上一篇多串口的笔记。
#include <ser_uart2.h>
struct serial_configure config2 = RT_SERIAL_CONFIG_DEFAULT;
static struct rt_semaphore rx_sem;
rt_device_t serial2;
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
while (rt_device_read(serial2, -1, &ch, 1) != 1)
{
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
ch = ch + 1;
rt_device_write(serial2, 0, &ch, 1);
// rt_kprintf("%c",ch);
}
}
int thread_serial2(void)
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread_2!\r\n";
rt_strncpy(uart_name, SAMPLE_UART_NAME2, RT_NAME_MAX);
serial2 = rt_device_find(uart_name);
if (!serial2)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
config2.baud_rate = BAUD_RATE_115200;
config2.data_bits = DATA_BITS_8;
config2.stop_bits = STOP_BITS_1;
config2.bufsz = 64;
config2.parity = PARITY_NONE;
rt_device_control(serial2, RT_DEVICE_CTRL_CONFIG, &config2);
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
rt_device_open(serial2, RT_DEVICE_FLAG_INT_RX);
rt_device_set_rx_indicate(serial2, uart_input);
rt_device_write(serial2, 0, str, (sizeof(str) - 1));
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 4, 10);
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
INIT_APP_EXPORT(thread_serial2);
五、运行效果
5.1编译、下载后可以在终端看到下图的信息。在main函数中定时更新显示输入、输出、以及RTC的状态:按KEY1,KEY2按钮可以看到D_IN_1,D_IN_2的数据变化,D_OUT_1,D_OUT_2的数据也是变化的,同时可以看到红、绿两个LED的闪动。RTC显示实时时钟。
5.2串口通讯
因uart1用于msh通讯,这里使用uart2,uart3,uart4实现与其他设备的通讯,通过串口调试助手,可以实现数据收发。从代码中可以看到,串口收到数据后会,将每个字符的ascii码值加一,再发送回去。
六、总结
6.1 线程的优先级
在测试中发现,线程的优先级设置会影响程序的运行,但是具体的影响机理以及如何设置还没有学会,只是发现:输出线程的优先级低于输入线程时,输出线程就不运行了(红、绿led不闪烁),串口的优先级设置太低,串口就不能收发数据。
6.2 邮箱和信息队列
按照指南上的说明,邮箱以及信息队列,更适合于线程间的信息传递,后续还要继续学习。
6.3 ADC和PWM的功能
ADC和PWM是设备控制中常用的功能,但是,对照指南一直没有测试成功。
我不是专业学习嵌入式的,更侧重于应用,希望厂家能在完善产品上多下功夫,并提供更多的参考例程,让大家能通过更多的应用来测试产品。
原作者:xxxx