02-freertos移植
一丶多任务
创建任务的配置
其中优先级多种:
osPriorityNormal(正常优先级)用于大多情况
osPriorityRealtime(实时优先级)用于中断响应
CubeMX代码生成Progect步骤
代码里:任务回调函数实现业务需求
二丶小实验:手动创建和删除任务
一个按键控制两种状态
- 按键按下实现状态的翻转本质:
1. 要么是自带的标志位
2. 要么是My自定义flag的标志位
创建与删除
osThreadDef(taskLED1, StartTaskLED1, osPriorityNormal, 0, 128);
taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);
printf("任务1创建完成\r\n");
osThreadTerminate(taskLED1Handle);
taskLED1Handle = NULL;
printf("删除任务1\r\n");
暂停与恢复
osThreadSuspend(taskLED2Handle);
printf("任务2已暂停\r\n");
osThreadResume(taskLED2Handle);
printf("任务2已恢复\r\n");
实验避坑:无实验现象
- 重新烧录完程序,一定要重新按下单片机的复位键
- 使用printf调试信息时,一定要注意重定向printf
1. CubeMX开启串口功能
2. printf重定向uart1:勾选MicroLib,添加下面内容
#include "stdio.h"
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
三丶任务相关概念
任务状态
任务四种状态
- 任务的四种状态:就绪态 运行态 阻塞态 挂起态
- 挂起状态:
- 相当于4399小游戏里的"暂停键",此时你可以跑去上厕所
- 你手动暂停,也需要手动重新开启
- 挂起与恢复:vTaskSuspend() 是挂起 ,xTaskResume()恢复
任务调度
- 开启任务调度,其本质是vTaskStartScheduler()函数
任务遵循两个调度规则
- 多任务系统下:每个任务都有优先级,当优先级不同时优先执行最紧急的事情(不同等级,等级高先执行)
- 如果两个任务优先级相同:每个任务依次分配一个时间片,随机的轮流执行每一个任务((相同等级,轮流执行))
四丶多任务常见问题
动态创建与静态创建任务有什么区别?
- 静态创建需要手动分配栈空间和TCB任务块
// 静态分配任务栈
static StackType_t xStack[configMINIMAL_STACK_SIZE];
// 静态分配任务控制块
static StaticTask_t xTaskBuffer;
- 任务名: 任务回调函数+传入参数+句柄
- 其他:栈+ 优先级+ 任务栈 +任务控制块(句柄)
xTaskCreateStatic(
vTaskFunction, // 任务函数
"Task Name", // 任务名称
configMINIMAL_STACK_SIZE, // 栈大小
NULL, // 参数
tskIDLE_PRIORITY+1, // 优先级
xStack, // 任务栈
&xTaskBuffer // 任务控制块
);
推挽输出和开漏输出区别?
- 推挽输出,内部有一个MOS管,驱动能力强,有大电流:用于驱动蜂鸣器
- 开漏输出,用于总线通信,例如IIC通信协议.
最多可以创建4个任务,否则报错?
为什么不用接线-GND模拟按键按下和解决方案?
-
按键检测:检测 按下 松开的过程
当使用数据线GND模拟时,松开过程可能会错误,导致程序现象错乱,思维混乱 -
解决方法:
采用外部中断,检测下降沿的方法
五丶开启EXTI外部中断的步骤
- 在CubeMX里:开启中断和设置中断模式
- 在代码里:重构HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//delay_ms(20);
if (GPIO_Pin == GPIO_PIN_12) //检测到B键按下
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET)
printf("A按下\r\n");
buttonA_flag = true;
}
else if (GPIO_Pin == GPIO_PIN_13) //检测到A键按下
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_RESET)
printf("B按下\r\n");
buttonB_flag = true;
}
中断回调函数里:尽量不添加延时和循环
- 中断回调里最好不要添加延时和循环函数
while( HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_R); 这个作用类似于按键不松手场景
六丶开启Uart中断的步骤
步骤
- 在CubeMX:开启串口和串口中断enable
- 在代码里:重构串口中断回调函数(可以区分不同的串口中断,可区分串口的发送与接收)
不同的串口中断,使用同一个中断回调函数在这个函数里亦可区分是发送还是接收
// UART1接收完成中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
第一步,区分是哪个串口中断
if (huart->Instance == USART1)
{
第二步,区分串口是发送中断 还是 接收中断
}
else if (huart->Instance == USART2)
{
}
else if (huart->Instance == USART3)
{
}
场景:接收中断下buffer[2]不同,执行不同动作
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
if (Uart1_Rx_Cnt >= 255) // 溢出判断
{
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff, 0x00, sizeof(Uart1_RxBuff));
HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出(大于256)\r\n", strlen("数据溢出(大于256)\r\n"), 0xFFFF);
}
else
{
Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer_Uart1; // 接收数据转存
//
//
//
//这个else里实现:串口接收中断的业务逻辑
//
//
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer_Uart1, 1); // 再开启接收中断
}
else if (huart->Instance == USART3)
{
// 如果不需要对UART3做特殊处理,可保持默认的接收处理逻辑或者添加类似的溢出等简单处理
if (Uart3_Rx_Cnt >= 255)
{
Uart3_Rx_Cnt = 0;
memset(Uart3_RxBuff, 0x00, sizeof(Uart3_RxBuff));
HAL_UART_Transmit(&huart3, (uint8_t *)"数据溢出(大于256)\r\n", strlen("数据溢出(大于256)\r\n"), 0xFFFF);
}
else
{
Uart3_RxBuff[Uart3_Rx_Cnt++] = aRxBuffer_Uart3;
// 可根据实际需求添加更多针对UART3接收数据的处理逻辑
}
HAL_UART_Receive_IT(&huart3, (uint8_t *)&aRxBuffer_Uart3, 1);
}
}