最近学习了RT-Thread操作系统,学习了其中的内核,包括线程调度,时钟管理,线程通信等,理解了其中的运行机制,最大的优点就是可以使线程并行,实时性高,大大提高运行效率。在学习完后,觉得用RT-Thread做一个项目。
项目介绍:
这个小车之前用裸机跑过程序,于是就在此代码的基础上进行修改
以下是裸机的主函数代码,可以看出所有的任务都在一个线程里面,包括通信,驱动电机,驱动舵机,并且其中还加了些延迟,程序运行效率不高。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Timer.h"
#include "Serial.h"
#include "Motor.h"
#include "Servo.h"
#define UNIT_PWM 3
int main(void)
{
u8 flat,RxData;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Timer_Init(71,9);
Serial_Init();
Motor_Init();
Servo_Init();
while(1)
{
if(Serial_GetRxFlag() == 1)
{
RxData = Serial_GetRxData();
//遥控
if(RxData == 'A')
flat = 1;
}
//遥控
if(flat == 1)
{
if(RxData == 'D')//直线
{
Car_Forward(80,80);
}
else if(RxData == 'E')//左转
{
Car_TurnLeft(0,80);
}
else if(RxData == 'F')//右转
{
Car_TurnRight(80,0);
}
else if(RxData == 'G')//倒车
{
Car_Backward(-80,-80);
}
else if(RxData == 'O')//旋转
{
Car_Turnaround(80,-80);
}
else if(RxData == 'H')
{
Set_Up_Servo_Forward(UNIT_PWM);
}
else if(RxData == 'I')
{
Set_Up_Servo_Backward(UNIT_PWM);
}
else if(RxData == 'J')
{
Set_Below_Servo_Forward(UNIT_PWM);
}
else if(RxData == 'K')
{
Set_Below_Servo_Backward(UNIT_PWM);
}
else if(RxData == 'S')
{
Car_Stop();
}
}
Delay_ms(20);//避免频繁查询按键信息,防止返回过来的信息还没有完全告知完的时候, MCU又发起了新一轮的查询
}
}
因为这个小车有驱动,有舵机,有通信,所以准备分成3个线程
首先是三个线程的创建
//创建电机驱动的线程
th_motor = rt_thread_create("th_motor",th_motor_entry,RT_NULL,512,2,1000);
if(th_motor == RT_NULL)
{
rt_kprintf("th_motor create failed...\n");
return -RT_ENOMEM;
}
else
{
rt_kprintf("th_motor create successfully...\n");
rt_thread_startup(th_motor); //开启电机驱动的线程
}
//创建舵机云台的线程
th_servo = rt_thread_create("th_servo",th_servo_entry,RT_NULL,512,2,1000);
if(th_servo == RT_NULL)
{
rt_kprintf("th_servo create failed...\n");
return -RT_ENOMEM;
}
else
{
rt_kprintf("th_servo create successfully...\n");
rt_thread_startup(th_servo); //开启舵机云台的线程
}
//创建USART的线程
th_serial = rt_thread_create("th_serial",th_serial_entry,RT_NULL,512,1,500);
if(th_serial == RT_NULL)
{
rt_kprintf("th_serial create failed...\n");
return -RT_ENOMEM;
}
else
{
rt_kprintf("th_serial create successfully...\n");
rt_thread_startup(th_serial); //开启USART的线程
}
在运行了线程的创建函数后,程序会开始运行线程函数,该函数即rt_thread_create()的第二个参数,在该函数内执行相应的动作。
//电机驱动线程
void th_motor_entry(void *parameter)
{
while(1)
{
if(rt_mb_recv(mail,(rt_uint32_t *)&RxData,RT_WAITING_FOREVER) == RT_EOK)//rt_mb_recv()函数的第二个参数是rt_uint32_t*类型的指针
{
rt_kprintf("rt_mb_recv successfully! the value is %c\n",*RxData);//str是一个指向字符的指针,而*str表示该指针指向的字符。如果去掉*则输出的是str指针的地址,而不是指向的具体字符
}
//遥控
if(*RxData == 'A')
{
flat = 1;
rt_kprintf("enter control mode\n");
}
if(flat == 1)
{
if(*RxData == 'D')//直线
{
Car_Forward(80,80);
}
else if(*RxData == 'E')//左转
{
Car_TurnLeft(0,80);
}
else if(*RxData == 'F')//右转
{
Car_TurnRight(80,0);
}
else if(*RxData == 'G')//倒车
{
Car_Backward(-80,-80);
}
else if(*RxData == 'O')//旋转
{
Car_Turnaround(80,-80);
}
else if(*RxData == 'S')
{
Car_Stop();
}
}
rt_thread_delay(1000);
}
}
//舵机云台线程
void th_servo_entry(void *parameter)
{
while(1)
{
if(flat == 1)
{
if(*RxData == 'H')
{
Set_Up_Servo_Forward(UNIT_PWM);
}
else if(*RxData == 'I')
{
Set_Up_Servo_Backward(UNIT_PWM);
}
else if(*RxData == 'J')
{
Set_Below_Servo_Forward(UNIT_PWM);
}
else if(*RxData == 'K')
{
Set_Below_Servo_Backward(UNIT_PWM);
}
}
rt_thread_delay(1500);
}
}
//USART线程
void th_serial_entry(void *parameter)
{
u8 str;
while(1)
{
if(Serial_GetRxFlag() == 1)
{
str = Serial_GetRxData();
if(rt_mb_send(mail,(rt_uint32_t)&str) == RT_EOK) //rt_mb_send()函数的第二个参数是rt_uint32_t类型的数据,如果去掉地址符号 &,那么传递给 rt_mb_send() 函数的就是一个字面量字符 ‘A’,而不是 mb_str1 所在的地址了
{
rt_kprintf("rt_mb_send successfully!\n");
}
else
{
rt_kprintf("rt_mb_send failed...\n");
}
}
rt_thread_delay(1000);
}
}
三个线程每n*tick(n为rt_thread_create()函数的最后一个参数)时间内会切换一次,由于切换的速度非常快,所以可以看作三个线程在同时运行。
在其中也加入了邮箱,用于线程之间的通信
//创建动态邮箱
mail = rt_mb_create("mail",32,RT_IPC_FLAG_FIFO);
if(mail == RT_NULL)
{
rt_kprintf("mail create failed...\n");
return -RT_ENOMEM;
}
else
{
rt_kprintf("mail create successfully...\n");
}
通过rt_mb_send()函数和rt_mb_recv()函数来进行邮件的收发,发送和接收的数据是4字节32位的整形数据,发送时要发送数据的地址,接收时通过指针来解引用
以上就是该项目的部分内容,因全写在主函数不太美观,所以我把线程的创建和调度用在了各个.c文件,以下是修改后的主函数代码,非常的简洁明了,主函数中仅创建了3个线程即可实现全部的功能,由此可见RT_Thread的强大。
#include "stm32f10x.h"
#include "Delay.h"
#include "Timer.h"
#include "Serial.h"
#include "Motor.h"
#include "Servo.h"
#include "rtthread.h"
#include "rthw.h"
int main(void)
{
mb_Init(); //邮箱初始化
Motor_Init(); //电机驱动初始化
Servo_Init(); //舵机云台初始化
}
效果展示
代码链接
链接:https://pan.baidu.com/s/1NpKJyW4xF8Wk1-7WtKE3rw?pwd=oxon
提取码:oxon
项目所遇问题(已解决)
- .h文件声明错误,编译下载能通过,但会导致功能无法实现
- 引脚重复定义导致功能无法实现
- PWM(定时器通道)、USART、IIC、SPI等引脚的是在单片机上定义好了的,不能随便配置
- 机械臂的舵机设置了几个参数,如pwm,middle,left_limit,因为这几个参数都属于舵机,所以可以用一个结构体封装起来,然后直接引用成员变量即可,又因为有多个舵机,所以再用这个结构体类型定义几个对象,每个对象内的结构体成员参数都不一样
- 舵机如果电压给大了,可能会出现一直抖动的情况
- 使用任何的GPIO口前都要进行初始化
- 串口初始化要放在rt_hw_board_init()函数里才能使用rt_kprintf
- 修改时钟节拍在rtconfig.h文件里22行RT_TICK_PER_SECOND,1tick = 1/RT_TICK_PER_SECOND秒