[关闭]
@richey 2020-10-21T02:33:16.000000Z 字数 57308 阅读 1732

物联网综合应用讲义04-基于rtthread的嵌入式软件开发

物联网 嵌入式软件 rt-thread


1 rt-thread嵌入式实时操作系统简介

image.png-44kB

2 内核

2.1 RT-Thread内核基础

image.png-33.4kB

2.1.1 RT-Thread内核介绍

2.1.1.1 线程调度

2.1.1.2 时钟管理

2.1.1.3 线程间同步

RT-Thread 采用信号量、互斥量与事件集实现线程间同步。线程通过对信号量、互斥量的获取与释放进行同步;互斥量采用优先级继承的方式解决了实时系统常见的优先级翻转问题。线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。线程通过对事件的发送与接收进行同步;事件集支持多事件的 “或触发” 和“与触发”,适合于线程等待多个事件的情况。

2.1.1.4 线程间通信

RT-Thread 支持邮箱和消息队列等通信机制。邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。邮箱效率较消息队列更为高效。邮箱和消息队列的发送动作可安全用于中断服务例程中。通信机制支持线程按优先级等待或按先进先出方式获取。

2.1.1.5 内存管理

RT-Thread 支持静态内存池管理及动态内存堆管理。当静态内存池具有可用内存时,系统对内存块分配的时间将是恒定的;当静态内存池为空时,系统将申请内存块的线程挂起或阻塞掉 (即线程等待一段时间后仍未获得内存块就放弃申请并返回,或者立刻返回。等待的时间取决于申请内存块时设置的等待时间参数),当其他线程释放内存块到内存池时,如果有挂起的待分配内存块的线程存在的话,则系统会将这个线程唤醒。

动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 SLAB 内存管理算法。

还有一种动态内存堆管理叫做 memheap,适用于系统含有多个地址可不连续的内存堆。使用 memheap 可以将多个内存堆 “粘贴” 在一起,让用户操作起来像是在操作一个内存堆。

2.1.1.6 I/O 设备管理

RT-Thread 将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。实现了按名称访问的设备管理子系统,可按照统一的 API 界面访问硬件设备。在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。

2.1.2 RT-Thread 启动流程

image.png-92kB

image.png-128.8kB

image.png-110.9kB

  1. int rtthread_startup(void)
  2. {
  3. rt_hw_interrupt_disable();
  4. /* board level initialization
  5. * NOTE: please initialize heap inside board initialization.
  6. */
  7. rt_hw_board_init();
  8. /* show RT-Thread version */
  9. rt_show_version();
  10. /* timer system initialization */
  11. rt_system_timer_init();
  12. /* scheduler system initialization */
  13. rt_system_scheduler_init();
  14. #ifdef RT_USING_SIGNALS
  15. /* signal system initialization */
  16. rt_system_signal_init();
  17. #endif
  18. /* create init_thread */
  19. rt_application_init();
  20. /* timer thread initialization */
  21. rt_system_timer_thread_init();
  22. /* idle thread initialization */
  23. rt_thread_idle_init();
  24. #ifdef RT_USING_SMP
  25. rt_hw_spin_lock(&_cpus_lock);
  26. #endif /*RT_USING_SMP*/
  27. /* start scheduler */
  28. rt_system_scheduler_start();
  29. /* never reach here */
  30. return 0;
  31. }

(1)初始化与系统相关的硬件;

(2)初始化系统内核对象,例如定时器、调度器、信号;

(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化;

(4)初始化定时器线程、空闲线程,并启动调度器。

image.png-104.3kB

2.1.3 RT-Thread 程序内存分布

image.png-17.6kB

1)Code:代码段,存放程序的代码部分;

2)RO-data:只读数据段,存放程序中定义的常量;

3)RW-data:读写数据段,存放初始化为非 0 值的全局变量;

4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;

如下面的例子,代码中的 msg_ptr 指针指向的 128 字节内存空间位于动态内存堆空间中。

  1. rt_uint8_t* msg_ptr;
  2. msg_ptr = (rt_uint8_t*) rt_malloc (128);
  3. rt_memset(msg_ptr, 0, 128);
  1. #include <rtthread.h>
  2. const static rt_uint32_t sensor_enable = 0x000000FE;
  3. rt_uint32_t sensor_value;
  4. rt_bool_t sensor_inited = RT_TRUE;
  5. void sensor_init()
  6. {
  7. /* ... */
  8. }

sensor_value 存放在 ZI 段中,系统启动后会自动初始化成零(由用户程序或编译器提供的一些库函数初始化成零)。sensor_inited 变量则存放在 RW 段中,而 sensor_enable 存放在 RO 段中。

2.1.4 RT-Thread 自动初始化机制

自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。

例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数,代码如下:

  1. int rt_hw_usart_init(void) /* 串口初始化函数 */
  2. {
  3. ... ...
  4. /* 注册串口 1 设备 */
  5. rt_hw_serial_register(&serial1, "uart1",
  6. RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
  7. uart);
  8. return 0;
  9. }
  10. INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使用组件自动初始化机制 */

示例代码最后的 INIT_BOARD_EXPORT(rt_hw_usart_init) 表示使用自动初始化功能,按照这种方式,rt_hw_usart_init()

rt_components_init() 函数会在操作系统运行起来之后创建的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。

2.1.5 RT-Thread 内核对象模型

2.1.5.1 静态对象和动态对象

以下代码是一个关于静态线程和动态线程的例子:

  1. /* 线程 1 的对象和运行时用到的栈 */
  2. static struct rt_thread thread1;
  3. static rt_uint8_t thread1_stack[512];
  4. /* 线程 1 入口 */
  5. void thread1_entry(void* parameter)
  6. {
  7. int i;
  8. while (1)
  9. {
  10. for (i = 0; i < 10; i ++)
  11. {
  12. rt_kprintf("%d\n", i);
  13. /* 延时 100ms */
  14. rt_thread_mdelay(100);
  15. }
  16. }
  17. }
  18. /* 线程 2 入口 */
  19. void thread2_entry(void* parameter)
  20. {
  21. int count = 0;
  22. while (1)
  23. {
  24. rt_kprintf("Thread2 count:%d\n", ++count);
  25. /* 延时 50ms */
  26. rt_thread_mdelay(50);
  27. }
  28. }
  29. /* 线程例程初始化 */
  30. int thread_sample_init()
  31. {
  32. rt_thread_t thread2_ptr;
  33. rt_err_t result;
  34. /* 初始化线程 1 */
  35. /* 线程的入口是 thread1_entry,参数是 RT_NULL
  36. * 线程栈是 thread1_stack
  37. * 优先级是 200,时间片是 10 个 OS Tick
  38. */
  39. result = rt_thread_init(&thread1,
  40. "thread1",
  41. thread1_entry, RT_NULL,
  42. &thread1_stack[0], sizeof(thread1_stack),
  43. 200, 10);
  44. /* 启动线程 */
  45. if (result == RT_EOK) rt_thread_startup(&thread1);
  46. /* 创建线程 2 */
  47. /* 线程的入口是 thread2_entry, 参数是 RT_NULL
  48. * 栈空间是 512,优先级是 250,时间片是 25 个 OS Tick
  49. */
  50. thread2_ptr = rt_thread_create("thread2",
  51. thread2_entry, RT_NULL,
  52. 512, 250, 25);
  53. /* 启动线程 */
  54. if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr);
  55. return 0;
  56. }

静态对象会占用 RAM 空间,不依赖于内存堆管理器,内存分配时间确定。动态对象则依赖于内存堆管理器,运行时申请 RAM 空间,当对象被删除后,占用的 RAM 空间被释放。这两种方式各有利弊,可以根据实际环境需求选择具体使用方式。

2.1.5.2 内核对象管理架构

下图则显示了 RT-Thread 中各类内核对象的派生和继承关系。对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性)。

image.png-19.2kB

上图中由对象控制块 rt_object 派生出来的有:线程对象、内存池对象、定时器对象、设备对象和 IPC 对象(IPC:Inter-Process Communication,进程间通信。在 RT-Thread 实时操作系统中,IPC 对象的作用是进行线程间同步与通信);由 IPC 对象派生出信号量、互斥量、事件、邮箱与消息队列、信号等对象。

2.1.6 RT-Thread 内核配置示例

在实际应用中,系统配置文件 rtconfig.h 是由配置工具自动生成的,无需手动更改。

image.png-99.9kB

image.png-116.8kB

2.2 线程管理

2.2.1 线程的工作机制

2.2.1.1 线程重要属性

image.png-7.4kB

在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务:

  1. void thread_entry(void* paramenter)
  2. {
  3. while (1)
  4. {
  5. /* 等待事件的发生 */
  6. /* 对事件进行服务、进行处理 */
  7. }
  8. }

线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU 使用权的动作,如循环中调用延时函数或者主动挂起。用户设计这种无线循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。

如简单的顺序语句、do whlie() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。

  1. static void thread_entry(void* parameter)
  2. {
  3. /* 处理事务 #1 */
  4. /* 处理事务 #2 */
  5. /* 处理事务 #3 */
  6. }

2.2.1.2 线程状态切换

image.png-27.3kB

2.2.1.3 系统线程

2.2.1 线程的管理方式

image.png-12.6kB

2.2.2.1 创建线程

  1. rt_thread_t rt_thread_create(const char* name,
  2. void (*entry)(void* parameter),
  3. void* parameter,
  4. rt_uint32_t stack_size,
  5. rt_uint8_t priority,
  6. rt_uint32_t tick);

2.2.2.2 初始化和脱离线程

  1. rt_err_t rt_thread_init(struct rt_thread* thread,
  2. const char* name,
  3. void (*entry)(void* parameter), void* parameter,
  4. void* stack_start, rt_uint32_t stack_size,
  5. rt_uint8_t priority, rt_uint32_t tick);

2.2.2.3 启动线程

创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化 / 创建成功后调用下面的函数接口让该线程进入就绪态:

  1. rt_err_t rt_thread_startup(rt_thread_t thread);

2.2.3 线程应用示例

2.2.3.1 创建线程示例

这个例子创建一个动态线程初始化一个静态线程,一个线程在运行完毕后自动被系统删除,另一个线程一直打印计数,如下代码:

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 25
  3. #define THREAD_STACK_SIZE 512
  4. #define THREAD_TIMESLICE 5
  5. static rt_thread_t tid1 = RT_NULL;
  6. /* 线程 1 的入口函数 */
  7. static void thread1_entry(void *parameter)
  8. {
  9. rt_uint32_t count = 0;
  10. while (1)
  11. {
  12. /* 线程 1 采用低优先级运行,一直打印计数值 */
  13. rt_kprintf("thread1 count: %d\n", count ++);
  14. rt_thread_mdelay(500);
  15. }
  16. }
  17. ALIGN(RT_ALIGN_SIZE)
  18. static char thread2_stack[1024];
  19. static struct rt_thread thread2;
  20. /* 线程 2 入口 */
  21. static void thread2_entry(void *param)
  22. {
  23. rt_uint32_t count = 0;
  24. /* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */
  25. for (count = 0; count < 10 ; count++)
  26. {
  27. /* 线程 2 打印计数值 */
  28. rt_kprintf("thread2 count: %d\n", count);
  29. }
  30. rt_kprintf("thread2 exit\n");
  31. /* 线程 2 运行结束后也将自动被系统脱离 */
  32. }
  33. /* 线程示例 */
  34. int thread_sample(void)
  35. {
  36. /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/
  37. tid1 = rt_thread_create("thread1",
  38. thread1_entry, RT_NULL,
  39. THREAD_STACK_SIZE,
  40. THREAD_PRIORITY, THREAD_TIMESLICE);
  41. /* 如果获得线程控制块,启动这个线程 */
  42. if (tid1 != RT_NULL)
  43. rt_thread_startup(tid1);
  44. /* 初始化线程 2,名称是 thread2,入口是 thread2_entry */
  45. rt_thread_init(&thread2,
  46. "thread2",
  47. thread2_entry,
  48. RT_NULL,
  49. &thread2_stack[0],
  50. sizeof(thread2_stack),
  51. THREAD_PRIORITY - 1, THREAD_TIMESLICE);
  52. rt_thread_startup(&thread2);
  53. return 0;
  54. }
  55. /* 导出到 msh 命令列表中 */
  56. MSH_CMD_EXPORT(thread_sample, thread sample);

2.2.3.2 线程时间片轮转调度示例

  1. #include <rtthread.h>
  2. #define THREAD_STACK_SIZE 1024
  3. #define THREAD_PRIORITY 20
  4. #define THREAD_TIMESLICE 10
  5. /* 线程入口 */
  6. static void thread_entry(void* parameter)
  7. {
  8. rt_uint32_t value;
  9. rt_uint32_t count = 0;
  10. value = (rt_uint32_t)parameter;
  11. while (1)
  12. {
  13. if(0 == (count % 5))
  14. {
  15. rt_kprintf("thread %d is running ,thread %d count = %d\n", value , value , count);
  16. if(count> 200)
  17. return;
  18. }
  19. count++;
  20. }
  21. }
  22. int timeslice_sample(void)
  23. {
  24. rt_thread_t tid = RT_NULL;
  25. /* 创建线程 1 */
  26. tid = rt_thread_create("thread1",
  27. thread_entry, (void*)1,
  28. THREAD_STACK_SIZE,
  29. THREAD_PRIORITY, THREAD_TIMESLICE);
  30. if (tid != RT_NULL)
  31. rt_thread_startup(tid);
  32. /* 创建线程 2 */
  33. tid = rt_thread_create("thread2",
  34. thread_entry, (void*)2,
  35. THREAD_STACK_SIZE,
  36. THREAD_PRIORITY, THREAD_TIMESLICE-5);
  37. if (tid != RT_NULL)
  38. rt_thread_startup(tid);
  39. return 0;
  40. }
  41. /* 导出到 msh 命令列表中 */
  42. MSH_CMD_EXPORT(timeslice_sample, timeslice sample);

2.3 时钟管理

2.3.1 时钟节拍

  1. void SysTick_Handler(void)
  2. {
  3. /* 进入中断 */
  4. rt_interrupt_enter();
  5. ……
  6. rt_tick_increase();
  7. /* 退出中断 */
  8. rt_interrupt_leave();
  9. }

在中断函数中调用 rt_tick_increase() 对全局变量 rt_tick 进行自加,代码如下所示:

  1. void rt_tick_increase(void)
  2. {
  3. struct rt_thread *thread;
  4. /* 全局变量 rt_tick 自加 */
  5. ++ rt_tick;
  6. /* 检查时间片 */
  7. thread = rt_thread_self();
  8. -- thread->remaining_tick;
  9. if (thread->remaining_tick == 0)
  10. {
  11. /* 重新赋初值 */
  12. thread->remaining_tick = thread->init_tick;
  13. /* 线程挂起 */
  14. rt_thread_yield();
  15. }
  16. /* 检查定时器 */
  17. rt_timer_check();
  18. }

2.3.2 定时器管理

2.3.3 定时器应用示例

这是一个创建定时器的例子,这个例程会创建两个动态定时器,一个是单次定时,一个是周期性定时并让周期定时器运行一段时间后停止运行,如下所示:

  1. #include <rtthread.h>
  2. /* 定时器的控制块 */
  3. static rt_timer_t timer1;
  4. static rt_timer_t timer2;
  5. static int cnt = 0;
  6. /* 定时器 1 超时函数 */
  7. static void timeout1(void *parameter)
  8. {
  9. rt_kprintf("periodic timer is timeout %d\n", cnt);
  10. /* 运行第 10 次,停止周期定时器 */
  11. if (cnt++>= 9)
  12. {
  13. rt_timer_stop(timer1);
  14. rt_kprintf("periodic timer was stopped! \n");
  15. }
  16. }
  17. /* 定时器 2 超时函数 */
  18. static void timeout2(void *parameter)
  19. {
  20. rt_kprintf("one shot timer is timeout\n");
  21. }
  22. int timer_sample(void)
  23. {
  24. /* 创建定时器 1 周期定时器 */
  25. timer1 = rt_timer_create("timer1", timeout1,
  26. RT_NULL, 10,
  27. RT_TIMER_FLAG_PERIODIC);
  28. /* 启动定时器 1 */
  29. if (timer1 != RT_NULL) rt_timer_start(timer1);
  30. /* 创建定时器 2 单次定时器 */
  31. timer2 = rt_timer_create("timer2", timeout2,
  32. RT_NULL, 30,
  33. RT_TIMER_FLAG_ONE_SHOT);
  34. /* 启动定时器 2 */
  35. if (timer2 != RT_NULL) rt_timer_start(timer2);
  36. return 0;
  37. }
  38. /* 导出到 msh 命令列表中 */
  39. MSH_CMD_EXPORT(timer_sample, timer sample);

2.3.4 高精度延时

高精度延时的例程如下所示:

  1. #include <board.h>
  2. void rt_hw_us_delay(rt_uint32_t us)
  3. {
  4. rt_uint32_t delta;
  5. /* 获得延时经过的 tick 数 */
  6. us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));
  7. /* 获得当前时间 */
  8. delta = SysTick->VAL;
  9. /* 循环获得当前时间,直到达到指定的时间后退出循环 */
  10. while (delta - SysTick->VAL< us);
  11. }

其中入口参数 us 指示出需要延时的微秒数目,这个函数只能支持低于 1 OS Tick 的延时,否则 SysTick 会出现溢出而不能够获得指定的延时时间。

2.4 线程间同步

在多线程实时系统中,一项工作的完成往往可以通过多个线程协调的方式共同来完成,那么多个线程之间如何 “默契” 协作才能使这项工作无差错执行?下面举个例子说明。

例如一项工作中的两个线程:一个线程从传感器中接收数据并且将数据写到共享内存中,同时另一个线程周期性的从共享内存中读取数据并发送去显示,下图描述了两个线程间的数据传递.

image.png-7.8kB

2.4.1 信号量

2.4.1 信号量的管理方式

image.png-12.4kB

2.4.2 信号量应用示例

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 25
  3. #define THREAD_TIMESLICE 5
  4. /* 指向信号量的指针 */
  5. static rt_sem_t dynamic_sem = RT_NULL;
  6. ALIGN(RT_ALIGN_SIZE)
  7. static char thread1_stack[1024];
  8. static struct rt_thread thread1;
  9. static void rt_thread1_entry(void *parameter)
  10. {
  11. static rt_uint8_t count = 0;
  12. while(1)
  13. {
  14. if(count <= 100)
  15. {
  16. count++;
  17. }
  18. else
  19. return;
  20. /* count 每计数 10 次,就释放一次信号量 */
  21. if(0 == (count % 10))
  22. {
  23. rt_kprintf("t1 release a dynamic semaphore.\n");
  24. rt_sem_release(dynamic_sem);
  25. }
  26. }
  27. }
  28. ALIGN(RT_ALIGN_SIZE)
  29. static char thread2_stack[1024];
  30. static struct rt_thread thread2;
  31. static void rt_thread2_entry(void *parameter)
  32. {
  33. static rt_err_t result;
  34. static rt_uint8_t number = 0;
  35. while(1)
  36. {
  37. /* 永久方式等待信号量,获取到信号量,则执行 number 自加的操作 */
  38. result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
  39. if (result != RT_EOK)
  40. {
  41. rt_kprintf("t2 take a dynamic semaphore, failed.\n");
  42. rt_sem_delete(dynamic_sem);
  43. return;
  44. }
  45. else
  46. {
  47. number++;
  48. rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number);
  49. }
  50. }
  51. }
  52. /* 信号量示例的初始化 */
  53. int semaphore_sample(void)
  54. {
  55. /* 创建一个动态信号量,初始值是 0 */
  56. dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO);
  57. if (dynamic_sem == RT_NULL)
  58. {
  59. rt_kprintf("create dynamic semaphore failed.\n");
  60. return -1;
  61. }
  62. else
  63. {
  64. rt_kprintf("create done. dynamic semaphore value = 0.\n");
  65. }
  66. rt_thread_init(&thread1,
  67. "thread1",
  68. rt_thread1_entry,
  69. RT_NULL,
  70. &thread1_stack[0],
  71. sizeof(thread1_stack),
  72. THREAD_PRIORITY, THREAD_TIMESLICE);
  73. rt_thread_startup(&thread1);
  74. rt_thread_init(&thread2,
  75. "thread2",
  76. rt_thread2_entry,
  77. RT_NULL,
  78. &thread2_stack[0],
  79. sizeof(thread2_stack),
  80. THREAD_PRIORITY-1, THREAD_TIMESLICE);
  81. rt_thread_startup(&thread2);
  82. return 0;
  83. }
  84. /* 导出到 msh 命令列表中 */
  85. MSH_CMD_EXPORT(semaphore_sample, semaphore sample);

(1)生产者线程:

1)获取 1 个空位(放产品 number),此时空位减 1;

2)上锁保护;本次的产生的 number 值为 cnt+1,把值循环存入数组 array 中;再开锁;

3)释放 1 个满位(给仓库中放置一个产品,仓库就多一个满位),满位加 1;

(2)消费者线程:

1)获取 1 个满位(取产品 number),此时满位减 1;

2)上锁保护;将本次生产者生产的 number 值从 array 中读出来,并与上次的 number 值相加;再开锁;

3)释放 1 个空位(从仓库上取走一个产品,仓库就多一个空位),空位加 1。

生产者依次产生 10 个 number,消费者依次取走,并将 10 个 number 的值求和。信号量锁 lock 保护 array 临界区资源:保证了消费者每次取 number 值的排他性,实现了线程间同步。

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 6
  3. #define THREAD_STACK_SIZE 512
  4. #define THREAD_TIMESLICE 5
  5. /* 定义最大 5 个元素能够被产生 */
  6. #define MAXSEM 5
  7. /* 用于放置生产的整数数组 */
  8. rt_uint32_t array[MAXSEM];
  9. /* 指向生产者、消费者在 array 数组中的读写位置 */
  10. static rt_uint32_t set, get;
  11. /* 指向线程控制块的指针 */
  12. static rt_thread_t producer_tid = RT_NULL;
  13. static rt_thread_t consumer_tid = RT_NULL;
  14. struct rt_semaphore sem_lock;
  15. struct rt_semaphore sem_empty, sem_full;
  16. /* 生产者线程入口 */
  17. void producer_thread_entry(void *parameter)
  18. {
  19. int cnt = 0;
  20. /* 运行 10 次 */
  21. while (cnt < 10)
  22. {
  23. /* 获取一个空位 */
  24. rt_sem_take(&sem_empty, RT_WAITING_FOREVER);
  25. /* 修改 array 内容,上锁 */
  26. rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
  27. array[set % MAXSEM] = cnt + 1;
  28. rt_kprintf("the producer generates a number: %d\n", array[set % MAXSEM]);
  29. set++;
  30. rt_sem_release(&sem_lock);
  31. /* 发布一个满位 */
  32. rt_sem_release(&sem_full);
  33. cnt++;
  34. /* 暂停一段时间 */
  35. rt_thread_mdelay(20);
  36. }
  37. rt_kprintf("the producer exit!\n");
  38. }
  39. /* 消费者线程入口 */
  40. void consumer_thread_entry(void *parameter)
  41. {
  42. rt_uint32_t sum = 0;
  43. while (1)
  44. {
  45. /* 获取一个满位 */
  46. rt_sem_take(&sem_full, RT_WAITING_FOREVER);
  47. /* 临界区,上锁进行操作 */
  48. rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
  49. sum += array[get % MAXSEM];
  50. rt_kprintf("the consumer[%d] get a number: %d\n", (get % MAXSEM), array[get % MAXSEM]);
  51. get++;
  52. rt_sem_release(&sem_lock);
  53. /* 释放一个空位 */
  54. rt_sem_release(&sem_empty);
  55. /* 生产者生产到 10 个数目,停止,消费者线程相应停止 */
  56. if (get == 10) break;
  57. /* 暂停一小会时间 */
  58. rt_thread_mdelay(50);
  59. }
  60. rt_kprintf("the consumer sum is: %d\n", sum);
  61. rt_kprintf("the consumer exit!\n");
  62. }
  63. int producer_consumer(void)
  64. {
  65. set = 0;
  66. get = 0;
  67. /* 初始化 3 个信号量 */
  68. rt_sem_init(&sem_lock, "lock", 1, RT_IPC_FLAG_FIFO);
  69. rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_FIFO);
  70. rt_sem_init(&sem_full, "full", 0, RT_IPC_FLAG_FIFO);
  71. /* 创建生产者线程 */
  72. producer_tid = rt_thread_create("producer",
  73. producer_thread_entry, RT_NULL,
  74. THREAD_STACK_SIZE,
  75. THREAD_PRIORITY - 1,
  76. THREAD_TIMESLICE);
  77. if (producer_tid != RT_NULL)
  78. {
  79. rt_thread_startup(producer_tid);
  80. }
  81. else
  82. {
  83. rt_kprintf("create thread producer failed");
  84. return -1;
  85. }
  86. /* 创建消费者线程 */
  87. consumer_tid = rt_thread_create("consumer",
  88. consumer_thread_entry, RT_NULL,
  89. THREAD_STACK_SIZE,
  90. THREAD_PRIORITY + 1,
  91. THREAD_TIMESLICE);
  92. if (consumer_tid != RT_NULL)
  93. {
  94. rt_thread_startup(consumer_tid);
  95. }
  96. else
  97. {
  98. rt_kprintf("create thread consumer failed");
  99. return -1;
  100. }
  101. return 0;
  102. }
  103. /* 导出到 msh 命令列表中 */
  104. MSH_CMD_EXPORT(producer_consumer, producer_consumer sample);

2.4.3 信号量的使用场合

当持有信号量的线程完成它处理的工作时,释放这个信号量,可以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:持有信号量的线程完成它自己的工作,然后通知等待该信号量的线程继续下一部分工作。

2.4.2 互斥量

互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。
image.png-7.1kB

image.png-13.2kB

image.png-15.6kB

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 8
  3. #define THREAD_TIMESLICE 5
  4. /* 指向互斥量的指针 */
  5. static rt_mutex_t dynamic_mutex = RT_NULL;
  6. static rt_uint8_t number1,number2 = 0;
  7. ALIGN(RT_ALIGN_SIZE)
  8. static char thread1_stack[1024];
  9. static struct rt_thread thread1;
  10. static void rt_thread_entry1(void *parameter)
  11. {
  12. while(1)
  13. {
  14. /* 线程 1 获取到互斥量后,先后对 number1、number2 进行加 1 操作,然后释放互斥量 */
  15. rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
  16. number1++;
  17. rt_thread_mdelay(10);
  18. number2++;
  19. rt_mutex_release(dynamic_mutex);
  20. }
  21. }
  22. ALIGN(RT_ALIGN_SIZE)
  23. static char thread2_stack[1024];
  24. static struct rt_thread thread2;
  25. static void rt_thread_entry2(void *parameter)
  26. {
  27. while(1)
  28. {
  29. /* 线程 2 获取到互斥量后,检查 number1、number2 的值是否相同,相同则表示 mutex 起到了锁的作用 */
  30. rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
  31. if(number1 != number2)
  32. {
  33. rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2);
  34. }
  35. else
  36. {
  37. rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1);
  38. }
  39. number1++;
  40. number2++;
  41. rt_mutex_release(dynamic_mutex);
  42. if(number1>=50)
  43. return;
  44. }
  45. }
  46. /* 互斥量示例的初始化 */
  47. int mutex_sample(void)
  48. {
  49. /* 创建一个动态互斥量 */
  50. dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO);
  51. if (dynamic_mutex == RT_NULL)
  52. {
  53. rt_kprintf("create dynamic mutex failed.\n");
  54. return -1;
  55. }
  56. rt_thread_init(&thread1,
  57. "thread1",
  58. rt_thread_entry1,
  59. RT_NULL,
  60. &thread1_stack[0],
  61. sizeof(thread1_stack),
  62. THREAD_PRIORITY, THREAD_TIMESLICE);
  63. rt_thread_startup(&thread1);
  64. rt_thread_init(&thread2,
  65. "thread2",
  66. rt_thread_entry2,
  67. RT_NULL,
  68. &thread2_stack[0],
  69. sizeof(thread2_stack),
  70. THREAD_PRIORITY-1, THREAD_TIMESLICE);
  71. rt_thread_startup(&thread2);
  72. return 0;
  73. }
  74. /* 导出到 MSH 命令列表中 */
  75. MSH_CMD_EXPORT(mutex_sample, mutex sample);

(1)线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。

(2)可能会由于多线程同步而造成优先级翻转的情况

2.4.3 事件集

image.png-5.9kB

一个事件集中包含 32 个事件,特定线程只等待、接收它关注的事件。可以是一个线程等待多个事件的到来(线程 1、2 均等待多个事件,事件间可以使用 “与” 或者 “或” 逻辑触发线程),也可以是多个线程等待一个事件的到来(事件 25)。当有它们关注的事件发生时,线程将被唤醒并进行后续的处理动作。

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 9
  3. #define THREAD_TIMESLICE 5
  4. #define EVENT_FLAG3 (1 << 3)
  5. #define EVENT_FLAG5 (1 << 5)
  6. /* 事件控制块 */
  7. static struct rt_event event;
  8. ALIGN(RT_ALIGN_SIZE)
  9. static char thread1_stack[1024];
  10. static struct rt_thread thread1;
  11. /* 线程 1 入口函数 */
  12. static void thread1_recv_event(void *param)
  13. {
  14. rt_uint32_t e;
  15. /* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */
  16. if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
  17. RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
  18. RT_WAITING_FOREVER, &e) == RT_EOK)
  19. {
  20. rt_kprintf("thread1: OR recv event 0x%x\n", e);
  21. }
  22. rt_kprintf("thread1: delay 1s to prepare the second event\n");
  23. rt_thread_mdelay(1000);
  24. /* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */
  25. if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
  26. RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
  27. RT_WAITING_FOREVER, &e) == RT_EOK)
  28. {
  29. rt_kprintf("thread1: AND recv event 0x%x\n", e);
  30. }
  31. rt_kprintf("thread1 leave.\n");
  32. }
  33. ALIGN(RT_ALIGN_SIZE)
  34. static char thread2_stack[1024];
  35. static struct rt_thread thread2;
  36. /* 线程 2 入口 */
  37. static void thread2_send_event(void *param)
  38. {
  39. rt_kprintf("thread2: send event3\n");
  40. rt_event_send(&event, EVENT_FLAG3);
  41. rt_thread_mdelay(200);
  42. rt_kprintf("thread2: send event5\n");
  43. rt_event_send(&event, EVENT_FLAG5);
  44. rt_thread_mdelay(200);
  45. rt_kprintf("thread2: send event3\n");
  46. rt_event_send(&event, EVENT_FLAG3);
  47. rt_kprintf("thread2 leave.\n");
  48. }
  49. int event_sample(void)
  50. {
  51. rt_err_t result;
  52. /* 初始化事件对象 */
  53. result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO);
  54. if (result != RT_EOK)
  55. {
  56. rt_kprintf("init event failed.\n");
  57. return -1;
  58. }
  59. rt_thread_init(&thread1,
  60. "thread1",
  61. thread1_recv_event,
  62. RT_NULL,
  63. &thread1_stack[0],
  64. sizeof(thread1_stack),
  65. THREAD_PRIORITY - 1, THREAD_TIMESLICE);
  66. rt_thread_startup(&thread1);
  67. rt_thread_init(&thread2,
  68. "thread2",
  69. thread2_send_event,
  70. RT_NULL,
  71. &thread2_stack[0],
  72. sizeof(thread2_stack),
  73. THREAD_PRIORITY, THREAD_TIMESLICE);
  74. rt_thread_startup(&thread2);
  75. return 0;
  76. }
  77. /* 导出到 msh 命令列表中 */
  78. MSH_CMD_EXPORT(event_sample, event sample);

运行结果

  1. \ | /
  2. - RT - Thread Operating System
  3. / | \ 3.1.0 build Aug 24 2018
  4. 2006 - 2018 Copyright by rt-thread team
  5. msh >event_sample
  6. thread2: send event3
  7. thread1: OR recv event 0x8
  8. thread1: delay 1s to prepare the second event
  9. msh >thread2: send event5
  10. thread2: send event3
  11. thread2 leave.
  12. thread1: AND recv event 0x28
  13. thread1 leave.

2.5 线程间通信

前面一章讲了线程间同步,提到了信号量、互斥量、事件集等概念;本章接着上一章的内容,讲解线程间通信。在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取,根据读取到的全局变量值执行相应的动作,达到通信协作的目的。RT-Thread 中则提供了更多的工具帮助在不同的线程中间传递信息,本章会详细介绍这些工具。学习完本章,大家将学会如何将邮箱、消息队列、信号用于线程间的通信

2.5.1 邮箱

邮箱服务是实时操作系统中一种典型的线程间通信方法。举一个简单的例子,有两个线程,线程 1 检测按键状态并发送,线程 2 读取按键状态并根据按键的状态相应地改变 LED 的亮灭。这里就可以使用邮箱的方式进行通信,线程 1 将按键的状态作为邮件发送到邮箱,线程 2 在邮箱中读取邮件获得按键状态并对 LED 执行亮灭操作。

这里的线程 1 也可以扩展为多个线程。例如,共有三个线程,线程 1 检测并发送按键状态,线程 2 检测并发送 ADC 采样信息,线程 3 则根据接收的信息类型不同,执行不同的操作。

2.5.1.1 邮箱的工作机制

RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如下图所示,线程或中断服务例程把一封 4 字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。

image.png-14.8kB

非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能由线程进行邮件的收取。

当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回 - RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。

当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 - RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。

2.5.1.2 邮箱控制块

在 RT-Thread 中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体 struct rt_mailbox 表示。另外一种 C 表达方式 rt_mailbox_t,表示的是邮箱的句柄,在 C 语言中的实现是邮箱控制块的指针。邮箱控制块结构的详细定义请见以下代码:

  1. struct rt_mailbox
  2. {
  3. struct rt_ipc_object parent;
  4. rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */
  5. rt_uint16_t size; /* 邮箱缓冲区的大小 */
  6. rt_uint16_t entry; /* 邮箱中邮件的数目 */
  7. rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */
  8. rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
  9. };
  10. typedef struct rt_mailbox* rt_mailbox_t;

2.5.1.3 邮箱的管理方式

邮箱控制块是一个结构体,其中含有事件相关的重要参数,在邮箱的功能实现中起重要的作用。邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。

image.png-12.3kB

2.5.1.3.1 创建和删除邮箱

动态创建一个邮箱对象可以调用如下的函数接口:

  1. rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag);

创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。

flag:邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO

2.5.1.3.2 初始化和脱离邮箱

初始化邮箱跟创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。函数接口如下:

  1. rt_err_t rt_mb_init(rt_mailbox_t mb,
  2. const char* name,
  3. void* msgpool,
  4. rt_size_t size,
  5. rt_uint8_t flag)
2.5.1.3.3 发送邮件

线程或者中断服务程序可以通过邮箱给其他线程发送邮件,发送邮件函数接口如下:

  1. rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);

发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。

2.5.1.3.4 接收邮件

只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK 的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。接收邮件函数接口如下:

  1. rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);

接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回 - RT_ETIMEOUT。

2.5.1.4 邮箱使用示例

这是一个邮箱的应用例程,初始化 2 个静态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,一个线程往邮箱中收取邮件。如下代码所示:

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 10
  3. #define THREAD_TIMESLICE 5
  4. /* 邮箱控制块 */
  5. static struct rt_mailbox mb;
  6. /* 用于放邮件的内存池 */
  7. static char mb_pool[128];
  8. static char mb_str1[] = "I'm a mail!";
  9. static char mb_str2[] = "this is another mail!";
  10. static char mb_str3[] = "over";
  11. ALIGN(RT_ALIGN_SIZE)
  12. static char thread1_stack[1024];
  13. static struct rt_thread thread1;
  14. /* 线程 1 入口 */
  15. static void thread1_entry(void *parameter)
  16. {
  17. char *str;
  18. while (1)
  19. {
  20. rt_kprintf("thread1: try to recv a mail\n");
  21. /* 从邮箱中收取邮件 */
  22. if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
  23. {
  24. rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str);
  25. if (str == mb_str3)
  26. break;
  27. /* 延时 100ms */
  28. rt_thread_mdelay(100);
  29. }
  30. }
  31. /* 执行邮箱对象脱离 */
  32. rt_mb_detach(&mb);
  33. }
  34. ALIGN(RT_ALIGN_SIZE)
  35. static char thread2_stack[1024];
  36. static struct rt_thread thread2;
  37. /* 线程 2 入口 */
  38. static void thread2_entry(void *parameter)
  39. {
  40. rt_uint8_t count;
  41. count = 0;
  42. while (count < 10)
  43. {
  44. count ++;
  45. if (count & 0x1)
  46. {
  47. /* 发送 mb_str1 地址到邮箱中 */
  48. rt_mb_send(&mb, (rt_uint32_t)&mb_str1);
  49. }
  50. else
  51. {
  52. /* 发送 mb_str2 地址到邮箱中 */
  53. rt_mb_send(&mb, (rt_uint32_t)&mb_str2);
  54. }
  55. /* 延时 200ms */
  56. rt_thread_mdelay(200);
  57. }
  58. /* 发送邮件告诉线程 1,线程 2 已经运行结束 */
  59. rt_mb_send(&mb, (rt_uint32_t)&mb_str3);
  60. }
  61. int mailbox_sample(void)
  62. {
  63. rt_err_t result;
  64. /* 初始化一个 mailbox */
  65. result = rt_mb_init(&mb,
  66. "mbt", /* 名称是 mbt */
  67. &mb_pool[0], /* 邮箱用到的内存池是 mb_pool */
  68. sizeof(mb_pool) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */
  69. RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */
  70. if (result != RT_EOK)
  71. {
  72. rt_kprintf("init mailbox failed.\n");
  73. return -1;
  74. }
  75. rt_thread_init(&thread1,
  76. "thread1",
  77. thread1_entry,
  78. RT_NULL,
  79. &thread1_stack[0],
  80. sizeof(thread1_stack),
  81. THREAD_PRIORITY, THREAD_TIMESLICE);
  82. rt_thread_startup(&thread1);
  83. rt_thread_init(&thread2,
  84. "thread2",
  85. thread2_entry,
  86. RT_NULL,
  87. &thread2_stack[0],
  88. sizeof(thread2_stack),
  89. THREAD_PRIORITY, THREAD_TIMESLICE);
  90. rt_thread_startup(&thread2);
  91. return 0;
  92. }
  93. /* 导出到 msh 命令列表中 */
  94. MSH_CMD_EXPORT(mailbox_sample, mailbox sample);

执行结果:

  1. \ | /
  2. - RT - Thread Operating System
  3. / | \ 3.1.0 build Aug 27 2018
  4. 2006 - 2018 Copyright by rt-thread team
  5. msh >mailbox_sample
  6. thread1: try to recv a mail
  7. thread1: get a mail from mailbox, the content:I'm a mail!
  8. msh >thread1: try to recv a mail
  9. thread1: get a mail from mailbox, the content:this is another mail!
  10. thread1: try to recv a mail
  11. thread1: get a mail from mailbox, the content:this is another mail!
  12. thread1: try to recv a mail
  13. thread1: get a mail from mailbox, the content:over

2.5.1.5 邮箱的使用场合

邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 (邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是 4 字节,所以邮箱能够用于不超过 4 字节的消息传递。由于在 32 系统上 4 字节的内容恰好可以放置一个指针,因此当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中,即邮箱也可以传递指针,例如:

  1. struct msg
  2. {
  3. rt_uint8_t *data_ptr;
  4. rt_uint32_t data_size;
  5. };

对于这样一个消息结构体,其中包含了指向数据的指针 data_ptr 和数据块长度的变量 data_size。当一个线程需要把这个消息发送给另外一个线程时,可以采用如下的操作:

  1. struct msg* msg_ptr;
  2. msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));
  3. msg_ptr->data_ptr = ...; /* 指向相应的数据块地址 */
  4. msg_ptr->data_size = len; /* 数据块的长度 */
  5. /* 发送这个消息指针给 mb 邮箱 */
  6. rt_mb_send(mb, (rt_uint32_t)msg_ptr);

而在接收线程中,因为收取过来的是指针,而 msg_ptr 是一个新分配出来的内存块,所以在接收线程处理完毕后,需要释放相应的内存块:

  1. struct msg* msg_ptr;
  2. if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK)
  3. {
  4. /* 在接收线程处理完毕后,需要释放相应的内存块 */
  5. rt_free(msg_ptr);
  6. }

2.5.2 消息队列

消息队列是另一种常用的线程间通讯方式,是邮箱的扩展。可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。

2.5.2.1 消息队列的工作机制

消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。

如下图所示,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO)。

image.png-15.3kB

2.5.2.2 消息队列控制块

  1. struct rt_messagequeue
  2. {
  3. struct rt_ipc_object parent;
  4. void* msg_pool; /* 指向存放消息的缓冲区的指针 */
  5. rt_uint16_t msg_size; /* 每个消息的长度 */
  6. rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */
  7. rt_uint16_t entry; /* 队列中已有的消息数 */
  8. void* msg_queue_head; /* 消息链表头 */
  9. void* msg_queue_tail; /* 消息链表尾 */
  10. void* msg_queue_free; /* 空闲消息链表 */
  11. rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
  12. };
  13. typedef struct rt_messagequeue* rt_mq_t;

2.5.2.3 消息队列的管理方式

image.png-12.7kB

2.5.2.3.1 创建和删除消息队列
  1. rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size,
  2. rt_size_t max_msgs, rt_uint8_t flag);

创建消息队列时先从对象管理器中分配一个消息队列对象,然后给消息队列对象分配一块内存空间,组织成空闲消息链表,这块内存的大小 =[消息大小 + 消息头(用于链表连接)的大小]X 消息队列最大个数,接着再初始化消息队列,此时消息队列为空.

2.5.2.3.2 初始化和脱离消息队列
  1. rt_err_t rt_mq_init(rt_mq_t mq, const char* name,
  2. void *msgpool, rt_size_t msg_size,
  3. rt_size_t pool_size, rt_uint8_t flag);

初始化消息队列时,该接口需要用户已经申请获得的消息队列对象的句柄(即指向消息队列对象控制块的指针)、消息队列名、消息缓冲区指针、消息大小以及消息队列缓冲区大小。如下图所示,消息队列初始化后所有消息都挂在空闲消息链表上,消息队列为空。

2.5.2.3.3 发送消息
  1. rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);
2.5.2.3.4 接收消息
  1. rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer,
  2. rt_size_t size, rt_int32_t timeout);

2.5.2.4 消息队列应用示例

  1. #include <rtthread.h>
  2. /* 消息队列控制块 */
  3. static struct rt_messagequeue mq;
  4. /* 消息队列中用到的放置消息的内存池 */
  5. static rt_uint8_t msg_pool[2048];
  6. ALIGN(RT_ALIGN_SIZE)
  7. static char thread1_stack[1024];
  8. static struct rt_thread thread1;
  9. /* 线程 1 入口函数 */
  10. static void thread1_entry(void *parameter)
  11. {
  12. char buf = 0;
  13. rt_uint8_t cnt = 0;
  14. while (1)
  15. {
  16. /* 从消息队列中接收消息 */
  17. if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK)
  18. {
  19. rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf);
  20. if (cnt == 19)
  21. {
  22. break;
  23. }
  24. }
  25. /* 延时 50ms */
  26. cnt++;
  27. rt_thread_mdelay(50);
  28. }
  29. rt_kprintf("thread1: detach mq \n");
  30. rt_mq_detach(&mq);
  31. }
  32. ALIGN(RT_ALIGN_SIZE)
  33. static char thread2_stack[1024];
  34. static struct rt_thread thread2;
  35. /* 线程 2 入口 */
  36. static void thread2_entry(void *parameter)
  37. {
  38. int result;
  39. char buf = 'A';
  40. rt_uint8_t cnt = 0;
  41. while (1)
  42. {
  43. if (cnt == 8)
  44. {
  45. /* 发送紧急消息到消息队列中 */
  46. result = rt_mq_urgent(&mq, &buf, 1);
  47. if (result != RT_EOK)
  48. {
  49. rt_kprintf("rt_mq_urgent ERR\n");
  50. }
  51. else
  52. {
  53. rt_kprintf("thread2: send urgent message - %c\n", buf);
  54. }
  55. }
  56. else if (cnt>= 20)/* 发送 20 次消息之后退出 */
  57. {
  58. rt_kprintf("message queue stop send, thread2 quit\n");
  59. break;
  60. }
  61. else
  62. {
  63. /* 发送消息到消息队列中 */
  64. result = rt_mq_send(&mq, &buf, 1);
  65. if (result != RT_EOK)
  66. {
  67. rt_kprintf("rt_mq_send ERR\n");
  68. }
  69. rt_kprintf("thread2: send message - %c\n", buf);
  70. }
  71. buf++;
  72. cnt++;
  73. /* 延时 5ms */
  74. rt_thread_mdelay(5);
  75. }
  76. }
  77. /* 消息队列示例的初始化 */
  78. int msgq_sample(void)
  79. {
  80. rt_err_t result;
  81. /* 初始化消息队列 */
  82. result = rt_mq_init(&mq,
  83. "mqt",
  84. &msg_pool[0], /* 内存池指向 msg_pool */
  85. 1, /* 每个消息的大小是 1 字节 */
  86. sizeof(msg_pool), /* 内存池的大小是 msg_pool 的大小 */
  87. RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
  88. if (result != RT_EOK)
  89. {
  90. rt_kprintf("init message queue failed.\n");
  91. return -1;
  92. }
  93. rt_thread_init(&thread1,
  94. "thread1",
  95. thread1_entry,
  96. RT_NULL,
  97. &thread1_stack[0],
  98. sizeof(thread1_stack), 25, 5);
  99. rt_thread_startup(&thread1);
  100. rt_thread_init(&thread2,
  101. "thread2",
  102. thread2_entry,
  103. RT_NULL,
  104. &thread2_stack[0],
  105. sizeof(thread2_stack), 25, 5);
  106. rt_thread_startup(&thread2);
  107. return 0;
  108. }
  109. /* 导出到 msh 命令列表中 */
  110. MSH_CMD_EXPORT(msgq_sample, msgq sample);

执行结果

  1. msh > msgq_sample
  2. msh >thread2: send message - A
  3. thread1: recv msg from msg queue, the content:A
  4. thread2: send message - B
  5. thread2: send message - C
  6. thread2: send message - D
  7. thread2: send message - E
  8. thread1: recv msg from msg queue, the content:B
  9. thread2: send message - F
  10. thread2: send message - G
  11. thread2: send message - H
  12. thread2: send urgent message - I
  13. thread2: send message - J
  14. thread1: recv msg from msg queue, the content:I
  15. thread2: send message - K
  16. thread2: send message - L
  17. thread2: send message - M
  18. thread2: send message - N
  19. thread2: send message - O
  20. thread1: recv msg from msg queue, the content:C
  21. thread2: send message - P
  22. thread2: send message - Q
  23. thread2: send message - R
  24. thread2: send message - S
  25. thread2: send message - T
  26. thread1: recv msg from msg queue, the content:D
  27. message queue stop send, thread2 quit
  28. thread1: recv msg from msg queue, the content:E
  29. thread1: recv msg from msg queue, the content:F
  30. thread1: recv msg from msg queue, the content:G
  31. thread1: recv msg from msg queue, the content:T
  32. thread1: detach mq

2.5.2.5 消息队列的使用场合

  1. struct msg
  2. {
  3. rt_uint8_t *data_ptr; /* 数据块首地址 */
  4. rt_uint32_t data_size; /* 数据块大小 */
  5. };

和邮箱例子相同的消息结构定义,假设依然需要发送这样一个消息给接收线程。在邮箱例子中,这个结构只能够发送指向这个结构的指针(在函数指针被发送过去后,接收线程能够正确的访问指向这个地址的内容,通常这块数据需要留给接收线程来释放)。而使用消息队列的方式则大不相同:

  1. void send_op(void *data, rt_size_t length)
  2. {
  3. struct msg msg_ptr;
  4. msg_ptr.data_ptr = data; /* 指向相应的数据块地址 */
  5. msg_ptr.data_size = length; /* 数据块的长度 */
  6. /* 发送这个消息指针给 mq 消息队列 */
  7. rt_mq_send(mq, (void*)&msg_ptr, sizeof(struct msg));
  8. }

注意,上面的代码中,是把一个局部变量的数据内容发送到了消息队列中。在接收线程中,同样也采用局部变量进行消息接收的结构体:

  1. void message_handler()
  2. {
  3. struct msg msg_ptr; /* 用于放置消息的局部变量 */
  4. /* 从消息队列中接收消息到 msg_ptr 中 */
  5. if (rt_mq_recv(mq, (void*)&msg_ptr, sizeof(struct msg)) == RT_EOK)
  6. {
  7. /* 成功接收到消息,进行相应的数据处理 */
  8. }
  9. }

因为消息队列是直接的数据内容复制,所以在上面的例子中,都采用了局部变量的方式保存消息结构体,这样也就免去动态内存分配的烦恼了(也就不用担心,接收线程在接收到消息时,消息内存空间已经被释放)。

image.png-7.2kB

  1. struct msg
  2. {
  3. /* 消息结构其他成员 */
  4. struct rt_mailbox ack;
  5. };
  6. /* 或者 */
  7. struct msg
  8. {
  9. /* 消息结构其他成员 */
  10. struct rt_semaphore ack;
  11. };

2.5.3 信号(略)

2.6 内存管理

  1. #include <rtthread.h>
  2. #define THREAD_PRIORITY 25
  3. #define THREAD_STACK_SIZE 512
  4. #define THREAD_TIMESLICE 5
  5. /* 线程入口 */
  6. void thread1_entry(void *parameter)
  7. {
  8. int i;
  9. char *ptr = RT_NULL; /* 内存块的指针 */
  10. for (i = 0; ; i++)
  11. {
  12. /* 每次分配 (1 << i) 大小字节数的内存空间 */
  13. ptr = rt_malloc(1 << i);
  14. /* 如果分配成功 */
  15. if (ptr != RT_NULL)
  16. {
  17. rt_kprintf("get memory :%d byte\n", (1 << i));
  18. /* 释放内存块 */
  19. rt_free(ptr);
  20. rt_kprintf("free memory :%d byte\n", (1 << i));
  21. ptr = RT_NULL;
  22. }
  23. else
  24. {
  25. rt_kprintf("try to get %d byte memory failed!\n", (1 << i));
  26. return;
  27. }
  28. }
  29. }
  30. int dynmem_sample(void)
  31. {
  32. rt_thread_t tid = RT_NULL;
  33. /* 创建线程 1 */
  34. tid = rt_thread_create("thread1",
  35. thread1_entry, RT_NULL,
  36. THREAD_STACK_SIZE,
  37. THREAD_PRIORITY,
  38. THREAD_TIMESLICE);
  39. if (tid != RT_NULL)
  40. rt_thread_startup(tid);
  41. return 0;
  42. }
  43. /* 导出到 msh 命令列表中 */
  44. MSH_CMD_EXPORT(dynmem_sample, dynmem sample);

2.7 中断管理

2.7.1 RT-Thread 中断工作机制

2.7.1.1 中断向量表

image.png-12.9kB

image.png-31.4kB

  1. __Vectors DCD __initial_sp ; Top of Stack
  2. DCD Reset_Handler ; Reset 处理函数
  3. DCD NMI_Handler ; NMI 处理函数
  4. DCD HardFault_Handler ; Hard Fault 处理函数
  5. DCD MemManage_Handler ; MPU Fault 处理函数
  6. DCD BusFault_Handler ; Bus Fault 处理函数
  7. DCD UsageFault_Handler ; Usage Fault 处理函数
  8. DCD 0 ; 保留
  9. DCD 0 ; 保留
  10. DCD 0 ; 保留
  11. DCD 0 ; 保留
  12. DCD SVC_Handler ; SVCall 处理函数
  13. DCD DebugMon_Handler ; Debug Monitor 处理函数
  14. DCD 0 ; 保留
  15. DCD PendSV_Handler ; PendSV 处理函数
  16. DCD SysTick_Handler ; SysTick 处理函数
  17. NMI_Handler PROC
  18. EXPORT NMI_Handler [WEAK]
  19. B .
  20. ENDP
  21. HardFault_Handler PROC
  22. EXPORT HardFault_Handler [WEAK]
  23. B .
  24. ENDP

以 SysTick 中断为例,在系统启动代码中,需要填上 SysTick_Handler 中断入口函数,然后实现该函数即可对 SysTick 中断进行响应,中断处理函数示例程序如下所示:

  1. void SysTick_Handler(void)
  2. {
  3. /* enter interrupt */
  4. rt_interrupt_enter();
  5. rt_tick_increase();
  6. /* leave interrupt */
  7. rt_interrupt_leave();
  8. }

2.7.1.2 中断处理过程

RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如下图:

image.png-13.9kB

2.7.2 RT-Thread 中断管理接口

image.png-21.3kB

2.7.3 中断与轮询

当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。因为轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理。所以轮询模式从实现上来说,相对简单清晰。例如往串口中写入数据,仅当串口控制器写完一个数据时,程序代码才写入下一个数据(否则这个数据丢弃掉)。相应的代码可以是这样的:

  1. /* 轮询模式向串口写入数据 */
  2. while (size)
  3. {
  4. /* 判断 UART 外设中数据是否发送完毕 */
  5. while (!(uart->uart_device->SR & USART_FLAG_TXE));
  6. /* 当所有数据发送完毕后,才发送下一个数据 */
  7. uart->uart_device->DR = (*ptr & 0x1FF);
  8. ++ptr;
  9. --size;
  10. }

所以通常情况下,实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。例如一些携带 FIFO(包含一定数据量的先进先出队列)的串口外设,其写入过程可以是这样的,如下图所示:

image.png-5.2kB

线程先向串口的 FIFO 中写入数据,当 FIFO 满时,线程主动挂起。串口控制器持续地从 FIFO 中取出数据并以配置的波特率(例如 115200bps)发送出去。当 FIFO 中所有数据都发送完成时,将向处理器触发一个中断;当中断服务程序得到执行时,可以唤醒这个线程。这里举例的是 FIFO 类型的设备,在现实中也有 DMA 类型的设备,原理类似。

2.8 内核移植(略)

3 设备与驱动

3.1 I/O设备模型

3.1.1 I/O 设备介绍

3.1.1.1 I/O 设备模型框架

RT-Thread 提供了一套简单的 I/O 设备模型框架,如下图所示,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。

image.png-36.1kB

3.1.1.2 I/O 设备模型

image.png-21.1kB

3.1.1.3 I/O 设备类型

  1. RT_Device_Class_Char /* 字符设备 */
  2. RT_Device_Class_Block /* 块设备 */
  3. RT_Device_Class_NetIf /* 网络接口设备 */
  4. RT_Device_Class_MTD /* 内存设备 */
  5. RT_Device_Class_RTC /* RTC 设备 */
  6. RT_Device_Class_Sound /* 声音设备 */
  7. RT_Device_Class_Graphic /* 图形设备 */
  8. RT_Device_Class_I2CBUS /* I2C 总线设备 */
  9. RT_Device_Class_USBDevice /* USB device 设备 */
  10. RT_Device_Class_USBHost /* USB host 设备 */
  11. RT_Device_Class_SPIBUS /* SPI 总线设备 */
  12. RT_Device_Class_SPIDevice /* SPI 设备 */
  13. RT_Device_Class_SDIO /* SDIO 设备 */
  14. RT_Device_Class_Miscellaneous /* 杂类设备 */

3.1.2 创建和注册 I/O 设备

驱动层负责创建设备实例,并注册到 I/O 设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建:

  1. rt_device_t rt_device_create(int type, int attach_size);

设备被创建后,需要注册到 I/O 设备管理器中,应用程序才能够访问,注册设备的函数如下所示:

  1. rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);

flags 参数支持下列参数 (可以采用或的方式支持多种参数):

  1. #define RT_DEVICE_FLAG_RDONLY 0x001 /* 只读 */
  2. #define RT_DEVICE_FLAG_WRONLY 0x002 /* 只写 */
  3. #define RT_DEVICE_FLAG_RDWR 0x003 /* 读写 */
  4. #define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除 */
  5. #define RT_DEVICE_FLAG_STANDALONE 0x008 /* 独立 */
  6. #define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 挂起 */
  7. #define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */
  8. #define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收 */
  9. #define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收 */
  10. #define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送 */
  11. #define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送 */

下面代码为看门狗设备的注册示例,调用 rt_hw_watchdog_register() 接口后,设备通过 rt_device_register() 接口被注册到 I/O 设备管理器中。

  1. const static struct rt_device_ops wdt_ops =
  2. {
  3. rt_watchdog_init,
  4. rt_watchdog_open,
  5. rt_watchdog_close,
  6. RT_NULL,
  7. RT_NULL,
  8. rt_watchdog_control,
  9. };
  10. rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd,
  11. const char *name,
  12. rt_uint32_t flag,
  13. void *data)
  14. {
  15. struct rt_device *device;
  16. RT_ASSERT(wtd != RT_NULL);
  17. device = &(wtd->parent);
  18. device->type = RT_Device_Class_Miscellaneous;
  19. device->rx_indicate = RT_NULL;
  20. device->tx_complete = RT_NULL;
  21. device->ops = &wdt_ops;
  22. device->user_data = data;
  23. /* register a character device */
  24. return rt_device_register(device, name, flag);
  25. }

3.1.2 访问 I/O 设备

应用程序通过 I/O 设备管理接口来访问硬件设备,当设备驱动实现后,应用程序就可以访问该硬件。I/O 设备管理接口与 I/O 设备的操作方法的映射关系下图所示:

image.png-22.8kB

  1. #include <rtthread.h>
  2. #include <rtdevice.h>
  3. #define IWDG_DEVICE_NAME "iwg"
  4. static rt_device_t wdg_dev;
  5. static void idle_hook(void)
  6. {
  7. /* 在空闲线程的回调函数里喂狗 */
  8. rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
  9. rt_kprintf("feed the dog!\n ");
  10. }
  11. int main(void)
  12. {
  13. rt_err_t res = RT_EOK;
  14. rt_uint32_t timeout = 1000; /* 溢出时间 */
  15. /* 根据设备名称查找看门狗设备,获取设备句柄 */
  16. wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
  17. if (!wdg_dev)
  18. {
  19. rt_kprintf("find %s failed!\n", IWDG_DEVICE_NAME);
  20. return RT_ERROR;
  21. }
  22. /* 初始化设备 */
  23. res = rt_device_init(wdg_dev);
  24. if (res != RT_EOK)
  25. {
  26. rt_kprintf("initialize %s failed!\n", IWDG_DEVICE_NAME);
  27. return res;
  28. }
  29. /* 设置看门狗溢出时间 */
  30. res = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
  31. if (res != RT_EOK)
  32. {
  33. rt_kprintf("set %s timeout failed!\n", IWDG_DEVICE_NAME);
  34. return res;
  35. }
  36. /* 设置空闲线程回调函数 */
  37. rt_thread_idle_sethook(idle_hook);
  38. return res;
  39. }

3.2 UART 设备

image.png-7.6kB

3.2.1 访问串口设备

函数 描述
rt_device_find() 查找设备
rt_device_open() 打开设备
rt_device_read() 读取数据
rt_device_write() 写入数据
rt_device_control() 控制设备
rt_device_set_rx_indicate() 设置接收回调函数
rt_device_set_tx_complete() 设置发送完成回调函数
rt_device_close() 关闭设备

3.2.2 串口设备使用示例

3.2.2.1 中断接收及轮询发送

例代码的主要步骤如下所示:

此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。

运行序列图如下图所示:
image.png-46.6kB

  1. /*
  2. * 程序清单:这是一个 串口 设备使用例程
  3. * 例程导出了 uart_sample 命令到控制终端
  4. * 命令调用格式:uart_sample uart2
  5. * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
  6. * 程序功能:通过串口输出字符串"hello RT-Thread!",然后错位输出输入的字符
  7. */
  8. #include <rtthread.h>
  9. #define SAMPLE_UART_NAME "uart2"
  10. /* 用于接收消息的信号量 */
  11. static struct rt_semaphore rx_sem;
  12. static rt_device_t serial;
  13. /* 接收数据回调函数 */
  14. static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
  15. {
  16. /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
  17. rt_sem_release(&rx_sem);
  18. return RT_EOK;
  19. }
  20. static void serial_thread_entry(void *parameter)
  21. {
  22. char ch;
  23. while (1)
  24. {
  25. /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
  26. while (rt_device_read(serial, -1, &ch, 1) != 1)
  27. {
  28. /* 阻塞等待接收信号量,等到信号量后再次读取数据 */
  29. rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
  30. }
  31. /* 读取到的数据通过串口错位输出 */
  32. ch = ch + 1;
  33. rt_device_write(serial, 0, &ch, 1);
  34. }
  35. }
  36. static int uart_sample(int argc, char *argv[])
  37. {
  38. rt_err_t ret = RT_EOK;
  39. char uart_name[RT_NAME_MAX];
  40. char str[] = "hello RT-Thread!\r\n";
  41. if (argc == 2)
  42. {
  43. rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
  44. }
  45. else
  46. {
  47. rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
  48. }
  49. /* 查找系统中的串口设备 */
  50. serial = rt_device_find(uart_name);
  51. if (!serial)
  52. {
  53. rt_kprintf("find %s failed!\n", uart_name);
  54. return RT_ERROR;
  55. }
  56. /* 初始化信号量 */
  57. rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
  58. /* 以中断接收及轮询发送模式打开串口设备 */
  59. rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
  60. /* 设置接收回调函数 */
  61. rt_device_set_rx_indicate(serial, uart_input);
  62. /* 发送字符串 */
  63. rt_device_write(serial, 0, str, (sizeof(str) - 1));
  64. /* 创建 serial 线程 */
  65. rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
  66. /* 创建成功则启动线程 */
  67. if (thread != RT_NULL)
  68. {
  69. rt_thread_startup(thread);
  70. }
  71. else
  72. {
  73. ret = RT_ERROR;
  74. }
  75. return ret;
  76. }
  77. /* 导出到 msh 命令列表中 */
  78. MSH_CMD_EXPORT(uart_sample, uart device sample);

3.2.2.2 串口接收不定长数据

  1. /*
  2. * 程序清单:这是一个串口设备接收不定长数据的示例代码
  3. * 例程导出了 uart_dma_sample 命令到控制终端
  4. * 命令调用格式:uart_dma_sample uart2
  5. * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
  6. * 程序功能:通过串口 uart2 输出字符串"hello RT-Thread!",并通过串口 uart2 输入一串字符(不定长),再通过数据解析后,使用控制台显示有效数据。
  7. */
  8. #include <rtthread.h>
  9. #define SAMPLE_UART_NAME "uart2"
  10. #define DATA_CMD_END '\r' /* 结束位设置为 \r,即回车符 */
  11. #define ONE_DATA_MAXLEN 20 /* 不定长数据的最大长度 */
  12. /* 用于接收消息的信号量 */
  13. static struct rt_semaphore rx_sem;
  14. static rt_device_t serial;
  15. /* 接收数据回调函数 */
  16. static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
  17. {
  18. /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
  19. if (size > 0)
  20. {
  21. rt_sem_release(&rx_sem);
  22. }
  23. return RT_EOK;
  24. }
  25. static char uart_sample_get_char(void)
  26. {
  27. char ch;
  28. while (rt_device_read(serial, 0, &ch, 1) == 0)
  29. {
  30. rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
  31. rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
  32. }
  33. return ch;
  34. }
  35. /* 数据解析线程 */
  36. static void data_parsing(void)
  37. {
  38. char ch;
  39. char data[ONE_DATA_MAXLEN];
  40. static char i = 0;
  41. while (1)
  42. {
  43. ch = uart_sample_get_char();
  44. rt_device_write(serial, 0, &ch, 1);
  45. if(ch == DATA_CMD_END)
  46. {
  47. data[i++] = '\0';
  48. rt_kprintf("data=%s\r\n",data);
  49. i = 0;
  50. continue;
  51. }
  52. i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i;
  53. data[i++] = ch;
  54. }
  55. }
  56. static int uart_data_sample(int argc, char *argv[])
  57. {
  58. rt_err_t ret = RT_EOK;
  59. char uart_name[RT_NAME_MAX];
  60. char str[] = "hello RT-Thread!\r\n";
  61. if (argc == 2)
  62. {
  63. rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
  64. }
  65. else
  66. {
  67. rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
  68. }
  69. /* 查找系统中的串口设备 */
  70. serial = rt_device_find(uart_name);
  71. if (!serial)
  72. {
  73. rt_kprintf("find %s failed!\n", uart_name);
  74. return RT_ERROR;
  75. }
  76. /* 初始化信号量 */
  77. rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
  78. /* 以中断接收及轮询发送模式打开串口设备 */
  79. rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
  80. /* 设置接收回调函数 */
  81. rt_device_set_rx_indicate(serial, uart_rx_ind);
  82. /* 发送字符串 */
  83. rt_device_write(serial, 0, str, (sizeof(str) - 1));
  84. /* 创建 serial 线程 */
  85. rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 1024, 25, 10);
  86. /* 创建成功则启动线程 */
  87. if (thread != RT_NULL)
  88. {
  89. rt_thread_startup(thread);
  90. }
  91. else
  92. {
  93. ret = RT_ERROR;
  94. }
  95. return ret;
  96. }
  97. /* 导出到 msh 命令列表中 */
  98. MSH_CMD_EXPORT(uart_data_sample, uart device sample);

3.3 PIN 设备

3.3.1 引脚简介

image.png-30kB

3.3.2 访问 PIN 设备

函数 描述
rt_pin_mode() 设置引脚模式
rt_pin_write() 设置引脚电平
rt_pin_read() 读取引脚电平
rt_pin_attach_irq() 绑定引脚中断回调函数
rt_pin_irq_enable() 使能引脚中断
rt_pin_detach_irq() 脱离引脚中断回调函数
  1. /*
  2. * 程序清单:这是一个 PIN 设备使用例程
  3. * 例程导出了 pin_beep_sample 命令到控制终端
  4. * 命令调用格式:pin_beep_sample
  5. * 程序功能:通过按键控制蜂鸣器对应引脚的电平状态控制蜂鸣器
  6. */
  7. #include <rtthread.h>
  8. #include <rtdevice.h>
  9. /* 引脚编号,通过查看设备驱动文件drv_gpio.c确定 */
  10. #ifndef BEEP_PIN_NUM
  11. #define BEEP_PIN_NUM 35 /* PB0 */
  12. #endif
  13. #ifndef KEY0_PIN_NUM
  14. #define KEY0_PIN_NUM 55 /* PD8 */
  15. #endif
  16. #ifndef KEY1_PIN_NUM
  17. #define KEY1_PIN_NUM 56 /* PD9 */
  18. #endif
  19. void beep_on(void *args)
  20. {
  21. rt_kprintf("turn on beep!\n");
  22. rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
  23. }
  24. void beep_off(void *args)
  25. {
  26. rt_kprintf("turn off beep!\n");
  27. rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
  28. }
  29. static void pin_beep_sample(void)
  30. {
  31. /* 蜂鸣器引脚为输出模式 */
  32. rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
  33. /* 默认低电平 */
  34. rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
  35. /* 按键0引脚为输入模式 */
  36. rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
  37. /* 绑定中断,下降沿模式,回调函数名为beep_on */
  38. rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
  39. /* 使能中断 */
  40. rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);
  41. /* 按键1引脚为输入模式 */
  42. rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);
  43. /* 绑定中断,下降沿模式,回调函数名为beep_off */
  44. rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL);
  45. /* 使能中断 */
  46. rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);
  47. }
  48. /* 导出到 msh 命令列表中 */
  49. MSH_CMD_EXPORT(pin_beep_sample, pin beep sample);

3.4 ADC 设备

应用程序通过 RT-Thread 提供的 ADC 设备管理接口来访问 ADC 硬件,相关接口如下所示:

函数 描述
rt_device_find() 根据 ADC 设备名称查找设备获取设备句柄
rt_adc_enable() 使能 ADC 设备
rt_adc_read() 读取 ADC 设备数据
rt_adc_disable() 关闭 ADC 设备

- ADC 设备使用示例
- ADC 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:

  1. /*
  2. * 程序清单: ADC 设备使用例程
  3. * 例程导出了 adc_sample 命令到控制终端
  4. * 命令调用格式:adc_sample
  5. * 程序功能:通过 ADC 设备采样电压值并转换为数值。
  6. * 示例代码参考电压为3.3V,转换位数为12位。
  7. */
  8. #include <rtthread.h>
  9. #include <rtdevice.h>
  10. #define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
  11. #define ADC_DEV_CHANNEL 5 /* ADC 通道 */
  12. #define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/
  13. #define CONVERT_BITS (1 << 12) /* 转换位数为12位 */
  14. static int adc_vol_sample(int argc, char *argv[])
  15. {
  16. rt_adc_device_t adc_dev;
  17. rt_uint32_t value, vol;
  18. rt_err_t ret = RT_EOK;
  19. /* 查找设备 */
  20. adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
  21. if (adc_dev == RT_NULL)
  22. {
  23. rt_kprintf("adc sample run failed! can't find %s device!\n", ADC_DEV_NAME);
  24. return RT_ERROR;
  25. }
  26. /* 使能设备 */
  27. ret = rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
  28. /* 读取采样值 */
  29. value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
  30. rt_kprintf("the value is :%d \n", value);
  31. /* 转换为对应电压值 */
  32. vol = value * REFER_VOLTAGE / CONVERT_BITS;
  33. rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);
  34. /* 关闭通道 */
  35. ret = rt_adc_disable(adc_dev, ADC_DEV_CHANNEL);
  36. return ret;
  37. }
  38. /* 导出到 msh 命令列表中 */
  39. MSH_CMD_EXPORT(adc_vol_sample, adc voltage convert sample);

3.5 SPI 设备

image.png-9.7kB

SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后通过 SCLK 给从设备提供时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。

如下图所示芯片有 2 个 SPI 控制器,SPI 控制器对应 SPI 主设备,每个 SPI 控制器可以连接多个 SPI 从设备。挂载在同一个 SPI 控制器上的从设备共享 3 个信号引脚:SCK、MISO、MOSI,但每个从设备的 CS 引脚是独立的。

image.png-17.5kB

  1. /*
  2. * 程序清单:这是一个 SPI 设备使用例程
  3. * 例程导出了 spi_w25q_sample 命令到控制终端
  4. * 命令调用格式:spi_w25q_sample spi10
  5. * 命令解释:命令第二个参数是要使用的SPI设备名称,为空则使用默认的SPI设备
  6. * 程序功能:通过SPI设备读取 w25q 的 ID 数据
  7. */
  8. #include <rtthread.h>
  9. #include <rtdevice.h>
  10. #define W25Q_SPI_DEVICE_NAME "qspi10"
  11. static void spi_w25q_sample(int argc, char *argv[])
  12. {
  13. struct rt_spi_device *spi_dev_w25q;
  14. char name[RT_NAME_MAX];
  15. rt_uint8_t w25x_read_id = 0x90;
  16. rt_uint8_t id[5] = {0};
  17. if (argc == 2)
  18. {
  19. rt_strncpy(name, argv[1], RT_NAME_MAX);
  20. }
  21. else
  22. {
  23. rt_strncpy(name, W25Q_SPI_DEVICE_NAME, RT_NAME_MAX);
  24. }
  25. /* 查找 spi 设备获取设备句柄 */
  26. spi_dev_w25q = (struct rt_spi_device *)rt_device_find(name);
  27. if (!spi_dev_w25q)
  28. {
  29. rt_kprintf("spi sample run failed! can't find %s device!\n", name);
  30. }
  31. else
  32. {
  33. /* 方式1:使用 rt_spi_send_then_recv()发送命令读取ID */
  34. rt_spi_send_then_recv(spi_dev_w25q, &w25x_read_id, 1, id, 5);
  35. rt_kprintf("use rt_spi_send_then_recv() read w25q ID is:%x%x\n", id[3], id[4]);
  36. /* 方式2:使用 rt_spi_transfer_message()发送命令读取ID */
  37. struct rt_spi_message msg1, msg2;
  38. msg1.send_buf = &w25x_read_id;
  39. msg1.recv_buf = RT_NULL;
  40. msg1.length = 1;
  41. msg1.cs_take = 1;
  42. msg1.cs_release = 0;
  43. msg1.next = &msg2;
  44. msg2.send_buf = RT_NULL;
  45. msg2.recv_buf = id;
  46. msg2.length = 5;
  47. msg2.cs_take = 0;
  48. msg2.cs_release = 1;
  49. msg2.next = RT_NULL;
  50. rt_spi_transfer_message(spi_dev_w25q, &msg1);
  51. rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[3], id[4]);
  52. }
  53. }
  54. /* 导出到 msh 命令列表中 */
  55. MSH_CMD_EXPORT(spi_w25q_sample, spi w25q sample);

3.6 SENSOR 设备

函数 描述
rt_device_find() 根据传感器设备设备名称查找设备获取设备句柄
rt_device_open() 打开传感器设备
rt_device_read() 读取数据
rt_device_control() 控制传感器设备
rt_device_set_rx_indicate() 设置接收回调函数
rt_device_close() 关闭传感器设备
  1. /*
  2. * 程序清单:这是一个 传感器 设备使用例程
  3. * 例程导出了 sensor_sample 命令到控制终端
  4. * 命令调用格式:sensor_sample dev_name
  5. * 命令解释:命令第二个参数是要使用的传感器设备名称
  6. * 程序功能:打开对应的传感器,然后连续读取 5 次数据并打印出来。
  7. */
  8. #include "sensor.h"
  9. static void sensor_show_data(rt_size_t num, rt_sensor_t sensor, struct rt_sensor_data *sensor_data)
  10. {
  11. switch (sensor->info.type)
  12. {
  13. case RT_SENSOR_CLASS_ACCE:
  14. rt_kprintf("num:%3d, x:%5d, y:%5d, z:%5d, timestamp:%5d\n", num, sensor_data->data.acce.x, sensor_data->data.acce.y, sensor_data->data.acce.z, sensor_data->timestamp);
  15. break;
  16. case RT_SENSOR_CLASS_GYRO:
  17. rt_kprintf("num:%3d, x:%8d, y:%8d, z:%8d, timestamp:%5d\n", num, sensor_data->data.gyro.x, sensor_data->data.gyro.y, sensor_data->data.gyro.z, sensor_data->timestamp);
  18. break;
  19. case RT_SENSOR_CLASS_MAG:
  20. rt_kprintf("num:%3d, x:%5d, y:%5d, z:%5d, timestamp:%5d\n", num, sensor_data->data.mag.x, sensor_data->data.mag.y, sensor_data->data.mag.z, sensor_data->timestamp);
  21. break;
  22. case RT_SENSOR_CLASS_HUMI:
  23. rt_kprintf("num:%3d, humi:%3d.%d%%, timestamp:%5d\n", num, sensor_data->data.humi / 10, sensor_data->data.humi % 10, sensor_data->timestamp);
  24. break;
  25. case RT_SENSOR_CLASS_TEMP:
  26. rt_kprintf("num:%3d, temp:%3d.%dC, timestamp:%5d\n", num, sensor_data->data.temp / 10, sensor_data->data.temp % 10, sensor_data->timestamp);
  27. break;
  28. case RT_SENSOR_CLASS_BARO:
  29. rt_kprintf("num:%3d, press:%5d, timestamp:%5d\n", num, sensor_data->data.baro, sensor_data->timestamp);
  30. break;
  31. case RT_SENSOR_CLASS_STEP:
  32. rt_kprintf("num:%3d, step:%5d, timestamp:%5d\n", num, sensor_data->data.step, sensor_data->timestamp);
  33. break;
  34. default:
  35. break;
  36. }
  37. }
  38. static void sensor_sample(int argc, char **argv)
  39. {
  40. rt_device_t dev = RT_NULL;
  41. struct rt_sensor_data data;
  42. rt_size_t res, i;
  43. /* 查找系统中的传感器设备 */
  44. dev = rt_device_find(argv[1]);
  45. if (dev == RT_NULL)
  46. {
  47. rt_kprintf("Can't find device:%s\n", argv[1]);
  48. return;
  49. }
  50. /* 以轮询模式打开传感器设备 */
  51. if (rt_device_open(dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
  52. {
  53. rt_kprintf("open device failed!");
  54. return;
  55. }
  56. for (i = 0; i < 5; i++)
  57. {
  58. /* 从传感器读取一个数据 */
  59. res = rt_device_read(dev, 0, &data, 1);
  60. if (res != 1)
  61. {
  62. rt_kprintf("read data failed!size is %d", res);
  63. }
  64. else
  65. {
  66. sensor_show_data(i, (rt_sensor_t)dev, &data);
  67. }
  68. rt_thread_mdelay(100);
  69. }
  70. /* 关闭传感器设备 */
  71. rt_device_close(dev);
  72. }
  73. MSH_CMD_EXPORT(sensor_sample, sensor device sample);

4 软件包

5 rt-thread studio集成化开发环境

5.1 下载安装

https://www.rt-thread.org/page/studio.html

5.2 新建工程

image.png-36.3kB

image.png-23.6kB

image.png-43.5kB

image.png-59.1kB

5.3 Rt-Rhread setting

image.png-101.8kB

image.png-69.5kB

  1. #include <rtthread.h>
  2. #include <rtdevice.h>
  3. #define DBG_TAG "main"
  4. #define DBG_LVL DBG_LOG
  5. #include <rtdbg.h>
  6. #ifndef LED_PIN
  7. #define LED_PIN 30 /* PB14 */
  8. #endif
  9. int main(void)
  10. {
  11. int count = 1;
  12. rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
  13. while (count++)
  14. {
  15. //LOG_D("Hello RT-Thread!");
  16. rt_pin_write(LED_PIN, count % 2);
  17. rt_thread_mdelay(1000);
  18. }
  19. return RT_EOK;
  20. }
  1. //testsem.c
  2. /*
  3. * Copyright (c) 2006-2020, RT-Thread Development Team
  4. *
  5. * SPDX-License-Identifier: Apache-2.0
  6. *
  7. * Change Logs:
  8. * Date Author Notes
  9. * 2020-10-14 richey the first version
  10. */
  11. #include <rtthread.h>
  12. static rt_timer_t timer1;
  13. /* 指向信号量的指针 */
  14. static rt_sem_t dynamic_sem = RT_NULL;
  15. #define THREAD_PRIORITY 25
  16. #define THREAD_TIMESLICE 5
  17. static void timeout1(void *parameter)
  18. {
  19. rt_kprintf("timer1 release a dynamic semaphore.\n");
  20. rt_sem_release(dynamic_sem);
  21. }
  22. ALIGN(RT_ALIGN_SIZE)
  23. static char thread1_stack[1024];
  24. static struct rt_thread thread1;
  25. static void rt_thread1_entry(void *parameter)
  26. {
  27. static rt_err_t result;
  28. while(1)
  29. {
  30. result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
  31. if (result != RT_EOK)
  32. {
  33. rt_kprintf("t1 take a dynamic semaphore, failed.\n");
  34. rt_sem_delete(dynamic_sem);
  35. return;
  36. }
  37. else
  38. {
  39. rt_kprintf("t1 take a dynamic semaphore. \n");
  40. }
  41. }
  42. }
  43. int my_sample(void)
  44. {
  45. dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO);
  46. if (dynamic_sem == RT_NULL)
  47. {
  48. rt_kprintf("create dynamic semaphore failed.\n");
  49. return -1;
  50. }
  51. else
  52. {
  53. rt_kprintf("create done. dynamic semaphore value = 0.\n");
  54. }
  55. timer1 = rt_timer_create("timer1", timeout1,
  56. RT_NULL, 5000,
  57. RT_TIMER_FLAG_PERIODIC);
  58. /* 启动定时器 1 */
  59. if (timer1 != RT_NULL)
  60. {
  61. rt_timer_start(timer1);
  62. }
  63. rt_thread_init(&thread1,
  64. "thread1",
  65. rt_thread1_entry,
  66. RT_NULL,
  67. &thread1_stack[0],
  68. sizeof(thread1_stack),
  69. THREAD_PRIORITY, THREAD_TIMESLICE);
  70. rt_thread_startup(&thread1);
  71. }
  72. /* 导出到 msh 命令列表中 */
  73. MSH_CMD_EXPORT(my_sample, my_semaphore sample);
  1. //testsem.h
  2. /*
  3. * Copyright (c) 2006-2020, RT-Thread Development Team
  4. *
  5. * SPDX-License-Identifier: Apache-2.0
  6. *
  7. * Change Logs:
  8. * Date Author Notes
  9. * 2020-10-14 richey the first version
  10. */
  11. #ifndef APPLICATIONS_TESTSEM_H_
  12. #define APPLICATIONS_TESTSEM_H_
  13. extern int my_sample(void);
  14. #endif /* APPLICATIONS_TESTSEM_H_ */
  1. //testuart.c
  2. /*
  3. * Copyright (c) 2006-2020, RT-Thread Development Team
  4. *
  5. * SPDX-License-Identifier: Apache-2.0
  6. *
  7. * Change Logs:
  8. * Date Author Notes
  9. * 2020-10-21 richey the first version
  10. */
  11. #include <rtthread.h>
  12. #define SAMPLE_UART_NAME "uart4"
  13. /* 用于接收消息的信号量 */
  14. static struct rt_semaphore rx_sem;
  15. static rt_device_t serial;
  16. /* 接收数据回调函数 */
  17. static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
  18. {
  19. /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
  20. rt_sem_release(&rx_sem);
  21. return RT_EOK;
  22. }
  23. static void serial_thread_entry(void *parameter)
  24. {
  25. char ch;
  26. while (1)
  27. {
  28. /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
  29. while (rt_device_read(serial, -1, &ch, 1) != 1)
  30. {
  31. /* 阻塞等待接收信号量,等到信号量后再次读取数据 */
  32. rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
  33. }
  34. /* 读取到的数据通过串口错位输出 */
  35. ch = ch + 1;
  36. rt_device_write(serial, 0, &ch, 1);
  37. }
  38. }
  39. int uart_sample()
  40. {
  41. rt_err_t ret = RT_EOK;
  42. char str[] = "hello RT-Thread!\r\n";
  43. /* 查找系统中的串口设备 */
  44. serial = rt_device_find(SAMPLE_UART_NAME);
  45. if (!serial)
  46. {
  47. rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME);
  48. return RT_ERROR;
  49. }
  50. /* 初始化信号量 */
  51. rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
  52. /* 以中断接收及轮询发送模式打开串口设备 */
  53. rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
  54. /* 设置接收回调函数 */
  55. rt_device_set_rx_indicate(serial, uart_input);
  56. /* 发送字符串 */
  57. rt_device_write(serial, 0, str, (sizeof(str) - 1));
  58. /* 创建 serial 线程 */
  59. rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
  60. /* 创建成功则启动线程 */
  61. if (thread != RT_NULL)
  62. {
  63. rt_thread_startup(thread);
  64. }
  65. else
  66. {
  67. ret = RT_ERROR;
  68. }
  69. return ret;
  70. }
  71. /* 导出到 msh 命令列表中 */
  72. MSH_CMD_EXPORT(uart_sample, uart device sample);
  1. //testuart.h
  2. #ifndef APPLICATIONS_TESTUART_H_
  3. #define APPLICATIONS_TESTUART_H_
  4. extern int uart_sample();
  5. #endif /* APPLICATIONS_TESTUART_H_ */
  1. //main.c
  2. #include <rtthread.h>
  3. #include <rtdevice.h>
  4. #include "testuart.h"
  5. #define DBG_TAG "main"
  6. #define DBG_LVL DBG_LOG
  7. #include <rtdbg.h>
  8. #ifndef LED_PIN
  9. #define LED_PIN 30 /* PB14 */
  10. #endif
  11. int main(void)
  12. {
  13. int count = 1;
  14. rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
  15. uart_sample();
  16. while (count++)
  17. {
  18. //LOG_D("Hello RT-Thread!");
  19. rt_pin_write(LED_PIN, count % 2);
  20. rt_thread_mdelay(1000);
  21. }
  22. return RT_EOK;
  23. }
  1. //testuart2.c
  2. /*
  3. * Copyright (c) 2006-2020, RT-Thread Development Team
  4. *
  5. * SPDX-License-Identifier: Apache-2.0
  6. *
  7. * Change Logs:
  8. * Date Author Notes
  9. * 2020-10-21 richey the first version
  10. */
  11. #include <rtthread.h>
  12. #define SAMPLE_UART_NAME "uart4"
  13. #define DATA_CMD_END '\r' /* 结束位设置为 \r,即回车符 */
  14. #define ONE_DATA_MAXLEN 50 /* 不定长数据的最大长度 */
  15. /* 用于接收消息的信号量 */
  16. static struct rt_semaphore rx_sem;
  17. static rt_device_t serial;
  18. /* 接收数据回调函数 */
  19. static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
  20. {
  21. /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
  22. if (size > 0)
  23. {
  24. rt_sem_release(&rx_sem);
  25. }
  26. return RT_EOK;
  27. }
  28. static char uart_sample_get_char(void)
  29. {
  30. char ch;
  31. while (rt_device_read(serial, 0, &ch, 1) == 0)
  32. {
  33. rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
  34. rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
  35. }
  36. return ch;
  37. }
  38. /* 数据解析线程 */
  39. static void data_parsing(void)
  40. {
  41. char ch;
  42. char data[ONE_DATA_MAXLEN];
  43. static char i = 0;
  44. while (1)
  45. {
  46. ch = uart_sample_get_char();
  47. rt_device_write(serial, 0, &ch, 1);
  48. if(ch == DATA_CMD_END)
  49. {
  50. data[i++] = '\0';
  51. rt_kprintf("data=%s\r\n",data);
  52. i = 0;
  53. continue;
  54. }
  55. i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i;
  56. data[i++] = ch;
  57. }
  58. }
  59. int uart_data_sample()
  60. {
  61. rt_err_t ret = RT_EOK;
  62. char str[] = "hello RT-Thread!\r\n";
  63. /* 查找系统中的串口设备 */
  64. serial = rt_device_find(SAMPLE_UART_NAME);
  65. if (!serial)
  66. {
  67. rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME);
  68. return RT_ERROR;
  69. }
  70. /* 初始化信号量 */
  71. rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
  72. /* 以中断接收及轮询发送模式打开串口设备 */
  73. rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
  74. /* 设置接收回调函数 */
  75. rt_device_set_rx_indicate(serial, uart_rx_ind);
  76. /* 发送字符串 */
  77. rt_device_write(serial, 0, str, (sizeof(str) - 1));
  78. /* 创建 serial 线程 */
  79. rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 1024, 25, 10);
  80. /* 创建成功则启动线程 */
  81. if (thread != RT_NULL)
  82. {
  83. rt_thread_startup(thread);
  84. }
  85. else
  86. {
  87. ret = RT_ERROR;
  88. }
  89. return ret;
  90. }
  91. /* 导出到 msh 命令列表中 */
  92. MSH_CMD_EXPORT(uart_data_sample, uart device sample);
  1. //testuart2.h
  2. #ifndef APPLICATIONS_TESTUART2_H_
  3. #define APPLICATIONS_TESTUART2_H_
  4. extern int uart_data_sample();
  5. #endif /* APPLICATIONS_TESTUART2_H_ */
  1. //main.c
  2. #include <rtthread.h>
  3. #include <rtdevice.h>
  4. #include "testuart2.h"
  5. #define DBG_TAG "main"
  6. #define DBG_LVL DBG_LOG
  7. #include <rtdbg.h>
  8. #ifndef LED_PIN
  9. #define LED_PIN 30 /* PB14 */
  10. #endif
  11. int main(void)
  12. {
  13. int count = 1;
  14. rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
  15. uart_data_sample();
  16. while (count++)
  17. {
  18. //LOG_D("Hello RT-Thread!");
  19. rt_pin_write(LED_PIN, count % 2);
  20. rt_thread_mdelay(1000);
  21. }
  22. return RT_EOK;
  23. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注