状态码表
Enumerator | |
---|---|
osOK 0x0 | 函数完成;没有发生错误或事件. |
osEventSignal 0x08 | 函数完成;信号事件发生. |
osEventMessage 0x10 | 函数完成;消息事件发生. |
osEventMail 0x20 | 函数完成;邮件事件发生. |
osEventTimeout 0x40 | 函数完成;超时事件发生. |
osErrorParameter 0x80 | 参数错误:缺少强制参数或指定了错误的对象. |
osErrorResource 0x81 | 资源不可用:指定的资源不可用. |
osErrorTimeoutResource 0xC1 | 指定的资源在超时时间内不可用. |
osErrorISR 0x82 | 该函数不能从中断服务例程调用. |
osErrorISRRecursive 0x83 | 函数多次从ISR调用同一对象. |
osErrorPriority 0x84 | 系统无法确定优先级或线程具有非法优先级. |
osErrorNoMemory 0x85 | 系统内存不足:无法为操作分配或预留内存. |
osErrorValue 0x86 | 参数值超出范围. |
osErrorOS 0xFF | 未指定的RTOS错误:运行时错误,但没有其他错误消息适合. |
os_status_reserved 0x7FFFFFFF | 防止枚举缩小编译器优化. |
cubeMX配置
USE_PREEMPTION
Enabled设置为抢占式RTOS调度器,Disabled使用协作式RTOS调度器。
TICK_RATE_HZ
设置tick中断频率。
MAX_PRIORITIES
设置可分配给任务的最大优先级
MINIMAL_STACK_SIZE
设置分配给空闲任务的堆栈大小。该值是用字(这里是32位)而不是字节指定的。
任务管理
delay函数
-
osDelay(uint32_t millisec);
通过调用2实现 -
vTaskDelay( const TickType_t xTicksToDelay )
Timebase Source时钟实现。
/**
* @brief 延时任务到指定的时间
* @param PreviousWakeTime 指向任务上次就绪的时间变量,该变量使用前,徐初始化为当前时间 (PreviousWakeTime =osKernelSysTick() )
* @param millisec 延时的时间值
* @retval 指示函数的执行状态
*/
osStatus osDelayUntil (uint32_t *PreviousWakeTime, uint32_t millisec)
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
TickType_t tasktime_stamp;
TickType_t delaytimemsec=500;
tasktime_stamp= xTaskGetTickCount();
for(;;)
{
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);
osDelayUntil(&tasktime_stamp,delaytimemsec);
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);
osDelayUntil(&tasktime_stamp,delaytimemsec);
}
/* USER CODE END StartDefaultTask */
}
-
1~3函数仅仅只是把任务挂起,其他任务仍可以执行。如果用
hal_delay()
的话,是算在任务中的,所以会造成系统堵塞,应尽量少使用。 -
vTaskDelay()
指定的延时时间是从调用vTaskDelay()
之后(执行完该函数)开始算起的,而vTaskDelayUntil()
指定一个绝对时间,每当时间到达,则解除任务阻塞
空闲任务及其钩子函数
-
任务后的清理工作在哪执行?分两类:
-
自杀的任务:在空闲任务中完成清理工作,比如释放内存(都自杀了,怎么清理自己的尸体? 由别人来做)
-
非自杀的任务:在vTaskDelete内部完成清理工作(凶手执行清理工作)
CMSIS API:
osThreadTerminate()
-
调度策略
- 抢占式调度
configUSE_PREEMPTION
- 时间片调度 ``
- 空闲让步
访问竞争问题分析
- 当任务共享项目时,存在不同任务同时访问的可能性。比如用同一个串口发送数据。
- 解决争用问题的唯一安全、可靠的方法是设计代码是避免同时访问。
- 在使用全局变量时,可能也会有类似的问题
使用挂起调度器消除资源竞争
引入FreeRTOS函数
taskENTER_CRITICAL(); //禁用中断
taskEXIT_CRITICAL(); //启用中断
在需要保护的代码段前禁用中断,后开启中断。
这样做的优点是简单、有效;但缺点也十分明显,停止调度会停止并发单元的执行,会暂停多任务的处理,并且中断时间越长,对系统的影响越大。而且在要使用这个方法的代码段中若存在中断则无法使用。
所以黄金法则是:如果使用关中断的方式保护共享资源,进入和退出临界段的时间必须非常快
信号量机制
信号量使用
信号量是一帧任务流控制机制,信号量有以下两种状态
- 发布态(released,同义词free、available、unlocked)
- 锁定态(locked,同义词taken、unavailable、acquired)
当信号量锁定时,申请信号量的任务将停止运行;信号量处于发布状态时,允许申请信号量的任务继续执行。
任务可以通过查询信号量的状态,确定是否停止或继续执行,使用如下API:
osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
参数 millisec
决定任务继续执行代码前等待的时间。如果只想查询信号量当前状态,设置为0即可。
如果任务需要释放信号量,可以调用下列API像信号量发送消息:
osSemaphoreRelease(osSemaphoreId semaphore_id)
用法示例:
//申请信号量:等待信号量就绪
osSemaphoreWait (resource_semaphoreHandle, timeout);
{
//访问共享资源
}
osSemaphoreRelease(resource_semaphoreHandle);
互斥信号量使用
API:
osMutexWait (osSemaphoreId semaphore_id, uint32_t millisec);
osMutexRelease(osSemaphoreId semaphore_id);
用法与信号量完全相同,除此之外,互斥信号量的使用还包括以下特性:
- 只有锁定互斥信号量的任务才能释放它。(FreeRTOS仍不支持,存在被其他任务释放的可能,所以要成对使用)
- FreeRTOS互斥信号量支持优先级继承机制。
封装机制提升系统安全
-
信号量不会自动与特定的受保护对象关联。
-
信号量等待和释放操作必须成对使用。
-
没有机制阻止等待信号量之前,先调用发信号操作,可能导致奇怪的系统行为。
-
信号量必须对共享受保护资源的所有任务可见。
-
资源与信号量的关联不能保证安全,如果存在绕过保护区的“后门”,就可以绕过安全措施,如全局变量
做法
将受保护对象与保护机制(信号量与互斥信号量)封装成函数.
优先级继承机制
在使用信号量来控制访问共享资源时,应始终使用互斥信号量,而非信号量。
任务间交互
任务间的单向同步
主要用于触发任务执行
-
使用信号标志
cmsis信号管理API(调用任务通知)
/**
* @brief 设置信号标志
* @param thread_id 线程ID
* @param signals 信号标志
* @return
* @author muyue
*/
int32_t osSignalSet (osThreadId thread_id, int32_t signals)
/**
* @brief 等待信号标志
* @param signals
* @param millisec
* @return
* @author muyue
*/
osEvent osSignalWait (int32_t signals, uint32_t millisec)
/**
* @brief
* @param thread_id
* @param signals
* @return
* @author muyue
*/
int32_t osSignalClear (osThreadId thread_id, int32_t signals)
任务间双向同步
用于同步任务之间的活动,确保它们以受控方式协同工作。
使用单个信号量同步多个任务
uint8_t TaskCount=1;
/**
* @brief 同步任务函数
* @return
* @author muyue
*/
void SynchronizeTasks()
{
uint8_t TaskMaxNum=3;//任务数
if (TaskCount<TaskMaxNum)
{
TaskCount++;
osSemaphoreWait(SemaSyncl1Handle,osWaitForever);
}
else if(TaskCount == TaskMaxNum)
{
for (uint8_t i = 1; i < TaskMaxNum; i++)
{
osSemaphoreRelease(SemaSyncl1Handle);
osDelay(10);
TaskCount=1;
}
}
}
-
会合过程:
- 在最后一个任务(taskmax)到达同步点之前,每个任务挂起在信号量FIFO队列中。
- 当taskmax任务到达时,此队列有(TaskMaxNum-1)个任务。每到达一个任务,计数器就加1,计数器值的范围为1~(TaskMaxNum-1)。
- 计数器被复位,释放(TaskMaxNum-1)次信号量。
- 这样做是为了释放所有排队的任务,但使信号量处于锁定状态(准备下一个会合操作)。
-
信号量必须初始化为阻塞态
- cubeMX创建信号量用的API是
osSemaphoreCreate()
调用的是vSemaphoreCreateBinary()
,默认的状态是发布态。 - 用
xSemaphoreCreateBinary()
创建的信号量默认为阻塞态。
- cubeMX创建信号量用的API是
通过内存池共享数据
- 通过函数封装时可以很好的隔离临界资源,但信号量仍是一个全局对象。
- 所以可以通过创建独立的文件来模拟模块化,从而隔离信号量
- .c文件实现任务细节
- .h文件导出可供外部使用的接口
使用队列传输数据(Queue)
- 使用消息队列可以有效解决中断服务程序与任务之间消息传递的问题.
- FreeRTOS消息存取采用FIFO方式.环形缓冲区
- 队列采用传值方式,即复制要传输的值,而不是传输地址
CMSIS-API
osMessageCreate()
/**
* @brief 创建和初始化消息队列
* @param queue_def osMessageQ(队列名).
* @param thread_id 线程ID(通过osThreadCreate或osThreadGetId获取)或NULL
* @retval 消息队列ID供其他函数引用,错误时为NULL
*/
osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)
//-------------使用示例-----------
osMessageQDef(QS2A, 1, uint16_t); /*(队列名,队列长度,数据类型)*/
QS2AHandle = osMessageCreate(osMessageQ(QS2A), NULL);
osMessageGet()
/**
* @brief 获取消息或从队列中等待消息
* @param queue_id 队列ID
* @param millisec 超时时间
* @retval 包含状态码的事件信息
*/
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec);
//-------osEvent结构体---------
typedef struct
{
osStatus status; ///< 状态值:事件或错误码
union
{
uint32_t v; ///< 32位消息
void *p; ///< void类型的消息或邮箱指针
int32_t signals; ///< 信号标志
} value; ///<
union
{
osMailQId mail_id; ///< osMailCreate返回的邮箱句柄
osMessageQId message_id; ///< osMessageCreate返回的消息句柄
} def;
} osEvent;
//-----------使用示例------------
osReturn=osMessageGet(QS2AHandle,100);
if (osReturn.status==osEventMessage)
{
usb_printf("%d\r\n",osReturn.value.v);
}
osMessagePut()
/**
* @brief 将消息放入队列
* @param queue_id 队列ID
* @param info message information.
* @param millisec timeout value or 0 in case of no time-out.
* @retval 包含状态码的事件信息
*/
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
//使用示例
uint16_t key1=1;
osMessagePut(QS2AHandle,key1,0);
FreeRTOS原生API
xQueueCreate ()
xQueueSend ()
xQueueSendFromISR ()
xQueueReceive ()
队列集 (https://freertos.org/xQueueCreateSet.html)
xQueueCreateSet
xQueueAddToSet
xQueueRemoveFromSet
xQueueSelectFromSet
xQueueSelectFromSetFromISR
邮箱
CMSIS API
osMailQId osMailCreate (const osMailQDef_t *queue_def, osThreadId thread_id)
// Create and Initialize mail queue. More...
void * osMailAlloc (osMailQId queue_id, uint32_t millisec)
//Allocate a memory block from a mail. More...
void * osMailCAlloc (osMailQId queue_id, uint32_t millisec)
//Allocate a memory block from a mail and set memory block to zero. More...
osStatus osMailPut (osMailQId queue_id, void *mail)
// Put a mail to a queue. More...
osEvent osMailGet (osMailQId queue_id, uint32_t millisec)
//Get a mail from a queue. More...
osStatus osMailFree (osMailQId queue_id, void *mail)
//Free a memory block from a mail. More...
使用步骤
-
创建邮箱ID
osMailQId myMailHandle;
-
创建邮箱,设置参数,初始化
osMailQDef(myMail, 1, T_MEAS); myMailHandle = osMailCreate(osMailQ(myMail), NULL);
-
发送
举例,传输结构体指针
T_MEAS *mail_val; mail_val = osMailAlloc(myMailHandle, osWaitForever); mail_val->counter = 1111; mail_val->voltage = 33.33; mail_val->current = 11.11; osMailPut(myMailHandle, mail_val);
-
接收
osEvent osReturn; //事件状态型变量 osReturn = osMailGet(myMailHandle, osWaitForever); if (osReturn.status == osEventMail) { //处理数据 }
手搓邮箱
创建独立的.c和.h文件
在里面使用互斥信号量 , 信号量 , 队列 传输数据
对外只保留 发送和接受接口
事件组
用一个二进制数来表示事件是否发生,在stm32中这个数是32位的,但是最多只能表示24个事件