MCU:STM32F407VET6
开发环境:CLion
说明:
1,名字太长的... ...,我会打省略号
2,自带的截屏工具,故图片上的提示信息均为纯手工【涂鸦】
3,本篇基于CLion开发的,也可供其他开发软件如IAR、Keil等参考
4,相关问题放在最下面
一、创建基本工程
1,CLion中创建项目
首先在CLion中创建STM32CubeMX项目,此处名称为MyRTOS_Cpp, 创建完后如下
2,配置CubeMX
①基本配置
在CLion中打开CubeMX并更换芯片
配置SYS,选择Serial Wire和TIM7(SysTick被FreeRTOS占用)
配置RCC,选择Crystal/Cera ... ...
配置时钟树,先查看自己板子的原理图,看看外部晶振是多少,此处为12MHz。
填好外部晶振频率后,连接通路,本篇使用的MCU主板最大频率为168MHz,故有
②配置RTOS
下载RTOS软件包,点击图示位置或按下快捷键Alt+O
向下滑,找到后点击旁边的Install(此处已安装过,所以FreeRTOS一栏中未显示Install)
再点击OK
回到主界面后,在左边找到Middlware ... ... ,并点击其下的FREERTOS
开启后,并在下面的Config parameters中使能FPU,再在其上的Advanced settings中,把第一个使能
③生成工程
切换到Project Manger,并选择工具链STM32CubeMX
在左方中选择Code Generator,并勾选生成.c/.h的那个选项
按下快捷键Ctrl+S,会默认给你弹出一个写好名称的窗口,点击保存即可
然后又会弹出一个窗口(删除CLion先前生成的无用文件),点击OK即可
点击右上角的GENERATE CODE
二、整理架构
1,基本整理
回到CLion后,会默认弹出一个窗口(会弹两次),跳过即可
打开CMakeLists,把FPU打开(CubeMX生成时是默认注释的)
取消注释后如下
重新加载CMake,然后再点击个锤子,没有问题后再进行下一步
2,更改结构
【说明】:本篇采用的开发架构是硬件驱动层+功能模块层+应用层,可自行按需构建适合自己的架构,本篇仅做引导
①创建目录
右键左栏项目下的MyRTOS_Cpp,再点击如下选项,打开资源管理器,并进入该工程目录里
创建两个目录Application(应用层)和FunctionModuleLayer(功能模块层),并在其下创建子目录inc和src
在Drivers(硬件驱动层)目录下创建User(不重要,可以不创建),并也在其下添加inc和src
②分配文件
把下面两个头文件及其源文件移至FunctionModuleLayer(功能模块层)对应的子目录
把main.c移至Application(应用层)下的src中,并更改后缀名
把下面文件移至Drivers(硬件驱动层)下User里的src中
三、确立架构
1,修改CMakeLists
把刚刚填的文件包含起来,并重新加载CMake
include_directories(
Application/inc
Drivers/USER/inc
FunctionalModuleLayer/inc
Core/Inc Drivers/STM32F4xx_HAL_Driver/Inc
Drivers/STM32F4xx_HAL_Driver/Inc/Legacy
Middlewares/Third_Party/FreeRTOS/Source/include
Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2
Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F
Drivers/CMSIS/Device/ST/STM32F4xx/Include Drivers/CMSIS/Include)
file(GLOB_RECURSE SOURCES "Core/*.*" "Middlewares/*.*" "Drivers/*.*" "Application/*.*" "FunctionalModuleLayer/*.*" )
2,Drivers(硬件驱动层)
在Drivers下的User中,分别在对应位置创建user_init.h和user.init.c,用于代替main.h
在把main文件中的代码复制到user_init中(头文件对头文件,源文件对源文件),再删除无用的内容
user_init.h如下
#ifndef RTOS_Cpp_USER_INIT_H
#define RTOS_Cpp_USER_INIT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
void Error_Handler(void);
#ifdef __cplusplus
}
#endif
#endif
user_init.c如下
#include "user_init.h"
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
3,FunctionModuleLayer(功能模块层)
把gpio.h里的头文件包含(原为main.h)改为user_init.h,到资源管理器中把gpio.c改为gpio.cpp(纯粹为了统一,但stm32f4xx_it.c不能这样做,不然启动文件识别不出来)
创建文件时,把所有勾选去掉
①MyTask
在此目录下创建MyTask,用于代替先前的FreeRTOS.c,同时以后的任务函数开发就可以放在此处
MyTask.h
#ifndef RTOS_Cpp_MYTASK_H
#define RTOS_Cpp_MYTASK_H
#include "cmsis_os.h"
#include "FreeRTOS.h"
#include "task.h"
/*用户头文件*/
#include "user_init.h"
/*任务函数句柄*/
extern osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/*任务函数声明*/
void StartDefaultTask(void *argument);
#endif
MyTask.cpp
#include "MyTask.h"
uint16_t temp = 0x0010;
/*句柄定义*/
osThreadId_t defaultTaskHandle;
/*任务函数*/
void StartDefaultTask(void *argument)
{
for (;;)
{
osDelay(1);
}
}
②RCC
在目录下创建RCC,用于代替main.cpp里的时钟配置函数
RCC.h
#ifndef RTOS_Cpp_RCC_H
#define RTOS_Cpp_RCC_H
#include "user_init.h"
void SystemClock_Config(void);
#endif
RCC.cpp 我们的时钟配置可能不一样,前面的文件也是如此
#include "RCC.h"
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 6;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
4,Application(应用层)
①main.cpp
main不再是函数编写的主体了,仅仅用于写中断回调。main.h可以删掉了
#include "MyRTOS.h"
Sys *sys;
int main(void)
{
sys = new Sys;
sys->Peripheral_Init();//外设初始化
sys->OS_Init();
}
/*中断回调函数*/
extern "C" {
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM7)
{
HAL_IncTick();
}
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
②MyRTOS
添加MyRTOS,用于取代FreeRTOS和原main.c
MyRTOS.h
#ifndef RTOS_Cpp_MYRTOS_H
#define RTOS_Cpp_MYRTOS_H
#include "MyTask.h"
#include "RCC.h"
#include "gpio.h"
/*系统类*/
class Sys
{
public:
Sys();//默认初始化
~Sys();
void Peripheral_Init();//外设初始化
void OS_Init();//RTOS初始化
public:
};
#endif
MyRTOS.cpp
#include "MyRTOS.h"
Sys::Sys()
{
HAL_Init();
SystemClock_Config();
GPIO_Init();
}
/*外设初始化函数*/
void Sys::Peripheral_Init()
{
/*
DAC_Init();
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
*/
}
void Sys::OS_Init()
{
/*用户任务初始化*/
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/*操作系统初始化*/
osKernelInitialize();
osKernelStart();
}
Sys::~Sys()
{
}
5,说明
其一,开发重心改变。开发的主体部分不再是main.c或FreeRTOS.c了,而是MyTask和MyRTOS,前者侧重开发任务函数,后者侧重任务的初始化和任务调度以及外设配置
其二,层次更加分明。本架构源于无际大佬
其三,面向对象开发。下面是以前裸机开发时创建的System类,希望能提供些许思路。不过,如果请谨慎使用System类(把所有初始化封装在一个类里面),如果你的程序比较大,如使用了LVGL,那么运行时极有可能申请不到空间。
#ifndef _SYSTEM_H
#define _SYSTEM_H
#include "stm32f4xx.h" //必须放在最上面,你也不想它突然报几百个错吧
#include "stm32f4xx_hal_conf.h"
#include "stm32f4xx_it.h"
#include <iostream>
/*DATA*/
#include "logo.h"
#include "FONT.h"
#include "WAVEDAT.h"
/*USER*/
#include "AD-DA.h"
#include "flash.h"
#include "FSMC.h"
#include "LCD.h"
#include "spi.h"
#include "spi_flash.h"
#include "tools.h"
#include "timer.h"
#include "tools.h"
#include "UI.h"
#include "usart.h"
#include "key.h"
#include "FPGA.h"
/*指针类*/
#define KEY_RAM (*((volatile unsigned short *)0x6006000C)) // 键盘接口地址
#define IO_CS (*((volatile unsigned short *)0x60020000)) // MCU-IO扩展模块中并行IO片选地址
/*函数类*/
#define CK1_LOW() GPIO_ResetBits(GPIOC, GPIO_Pin_4) // 继电器1置低
#define CK1_HIGH() GPIO_SetBits(GPIOC, GPIO_Pin_4) // 继电器1置高
#define CK2_LOW() GPIO_ResetBits(GPIOC, GPIO_Pin_5) // 继电器2置低
#define CK2_HIGH() GPIO_SetBits(GPIOC, GPIO_Pin_5) // 继电器2置高
class System
{
public:
System(); // 系统初始化
~System();
/*初始化*/
void KEY_EXTI_init(void); // 键盘外部中断配置
void function_init(void);
void SystemClock_Config(void);
/*功能模块设计*/
void keybond(void); // 按键绑定
/*键区*/
void k0open(); // 擦除
void k0close();
void k1open(); // 录音
void k1close();
void k2open(); // 放音
void k2close();
void k3open(); // 快进
void k3close();
void k4open(); // 慢放
void k4close();
public:
/*基本类成员*/
Key *key;
/*用户类成员*/
public:
/*句柄*/
public:
/*标志类*/
/*数值类*/
uint8_t min, csec; // 分钟、秒、百分秒
uint16_t sec;
uint8_t recordcsec;
uint16_t recordsec;
/*计数类*/
/*指针型*/
uint32_t recordaddr; // 录音地址
uint32_t playaddr; // 放音地址
// uint32_t startaddr;
uint16_t offset;
/*debugger*/
};
#endif
#include "system.h"
/*系统功能模块初始化*/
void System::function_init(void) {
/*用户变量初始化*/
recordaddr = 0;
playaddr = 0;
sec = 0;
csec = 0; // 注:在这里不是百分秒,而是十分秒
recordcsec = 0;
recordsec = 0;
offset = 0; // 缓存偏移初始化
// startaddr = 0;
/*用户类的实例化*/
/*用户功能初始化*/
MX_SPI3_Init();
HAL_SPI_MspInit(&hspi3);
MX_ADC1_Init();
MX_DAC_Init();
HAL_ADC_MspInit(&hadc1); // 方便可移植
HAL_DAC_MspInit(&hdac);
MX_TIM2_Init();
MX_TIM6_Init(); // 10Hz
MX_TIM7_Init();
HAL_TIM_Base_MspInit(&htim2);
HAL_TIM_Base_MspInit(&htim6);
HAL_TIM_Base_MspInit(&htim7);
HAL_TIM_Base_Start(&htim2);
HAL_DAC_Start(&hdac, DAC1_CHANNEL_1);
SPI_FLASH_Init();
__HAL_TIM_CLEAR_IT(
&htim6,
TIM_IT_UPDATE); // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
__HAL_TIM_CLEAR_IT(
&htim7,
TIM_IT_UPDATE); // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
}
// 按键绑定
void System::keybond() {
key->sign = 0; // 重置键效
key->reverseflag(key->code); // 键标取反
switch (key->code) {
case 0x0: // 按键K0
if (key->operateotherkey(1, keyk1 | keyk2 | keyk3 | keyk4, 0)) {
k1close();
k2close();
HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
}
/*再打开本键*/
k0open();
break;
case 0x1: // 按键K1
if (key->flag & keyk1) {
/*先启闭其他键,如果需要的话*/
if (key->operateotherkey(1, keyk2 | keyk3 | keyk4, 0)) {
k2close();
k3close();
k4close();
}
k1open();
} else {
k1close();
}
break;
case 0x2: // 按键K2
if (key->flag & keyk2) {
if (key->operateotherkey(1, keyk1, 0)) {
k1close(); // 只需关闭录音
playaddr = 0;
sec = 0, csec = 0;
}
k2open();
} else {
k2close();
}
break;
case 0x3: // 按键K3
if (!key->iskeyopen(keyk1)) // 如果录音开启,那么就不执行慢放
{
if (key->flag & keyk3) {
if (key->operateotherkey(1, keyk4, 0)) {
LCD_ShowChineseStringBig(307, 180, 76, 2, YELLOW); // 关闭快进
}
k3open();
} else {
k3close();
}
}
break;
case 0x4: // 按键K4
if (!key->iskeyopen(keyk1)) // 如果录音开启,那么就不执行快进
{
if (key->flag & keyk4) {
if (key->operateotherkey(1, keyk3, 0)) {
LCD_ShowChineseStringBig(307, 220, 78, 2, YELLOW); // 关闭慢放
}
k4open();
} else {
k4close();
}
}
break;
case 0x5: // 按键K5
break;
case 0x6: // 按键K6
break;
case 0x7: // 按键K7
break;
case 0x8: // 按键K8
break;
case 0x9: // 按键K9
break;
case 0xA: // 按键KA
break;
case 0xB: // 按键KB
break;
case 0xC: // 按键KC
break;
case 0xD: // 按键KD
break;
case 0xE: // 按键KE
break;
case 0xF: // 按键KF
break;
default: // 异常状态
break;
}
}
/*系统初始化*/
System::System() {
/*基本全局初始化*/
FSMC_init(); // 灵活静态存储初始化——必不可少
GPIO_Configuration(); // GPIO初始化
SystemClock_Config(); // 系统时钟初始化
LCD_Init9488(); // 液晶初始化
KEY_EXTI_init(); // 全局中断初始化
/*基本初始化*/
TFTLED = 0x01; // 背光寄存器初始化
key = new Key;
/*用户基本初始化*/
UI_init(); // 显示Logo
tools.delay_ms(2500); // 延时一坤秒左右
LCD_Clear1(0x0000); // 清屏
userUI(); // 显示用户界面
}
System::~System() {
delete key;
key = nullptr;
}
// 全局中断配置
// 全局中断配置
void System::KEY_EXTI_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStructure.Mode = MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStructure.Pin = GPIO_PIN_0;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
// 外部中断1初始化
GPIO_InitStructure.Pin = GPIO_PIN_1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
HAL_NVIC_SetPriority(EXTI0_IRQn, 0x01, 0x02);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
HAL_NVIC_SetPriority(EXTI1_IRQn, 0x01, 0x02);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
}
/*系统时钟配置*/
void System::SystemClock_Config(void) {
/*系统时钟168MHz*/
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 12;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
while (1)
;
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
while (1)
;
}
/************************************************************/
/* 按键功能设计 */
/************************************************************/
/*开启键0*/
void System::k0open() {
uint16_t oldflag = key->flag; // 存储旧键值,为了防止在擦除死循环中乱按
sec = 0, csec = 0; // 计时器清零
recordaddr = 0; // 录音地址
recordcsec = 0, recordsec = 0;
playaddr = 0;
tools.dispsec(0);
HAL_TIM_Base_Start_IT(&htim6); // 开启定时器
LCD_ShowChineseStringBig(161, 220, 70, 2, LIGHTBLUE); // 显示擦除画面
SPI_FLASH_ChipErase();
LCD_ShowChineseStringBig(161, 220, 70, 2, YELLOW); // 关闭擦除画面
HAL_TIM_Base_Stop_IT(&htim6);
key->flag = oldflag; // 把按键清零
key->sign = 0; // 把置键有效也清零
}
/*关闭键0*/
void System::k0close() {
// 擦除不能取消,所以没有重复开关的功能
}
void System::k1open() {
if (recordaddr == 0) /*判断有没有擦除的必要*/
{
uint8_t i = 0;
uint8_t arr[16];
SPI_FLASH_BufferRead(arr, 0, 16);
for (; i < 16; i++) /*只要有数据就清空*/
if (arr[i] != Dummy_Byte) {
k0open();
break;
}
}
LCD_ShowChineseStringBig(161, 180, 72, 2, LIGHTBLUE); // 显示录音
sec = recordsec, csec = recordcsec;
tools.dispsec(sec);
HAL_TIM_Base_Start_IT(&htim6);
HAL_ADC_Start_IT(&hadc1); // 开启ADC
}
void System::k1close() {
HAL_ADC_Stop_IT(&hadc1); // 关闭ADC
HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
recordcsec = csec;
recordsec = sec;
csec = 0; // 为了把放音清除
sec = 0;
playaddr = 0;
LCD_ShowChineseStringBig(161, 180, 72, 2, YELLOW); // 显示录音
}
void System::k2open() {
if (recordaddr == playaddr)
playaddr = 0, sec = 0, csec = 0; // 置零
tools.dispsec(sec);
LCD_ShowChineseStringBig(161, 140, 74, 2, LIGHTBLUE); // 蓝为开启
HAL_TIM_Base_Start_IT(&htim6); // 打开计时器
HAL_TIM_Base_Start_IT(&htim7); // 打开放音用的中断
}
void System::k2close() {
HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
HAL_TIM_Base_Stop_IT(&htim7);
LCD_ShowChineseStringBig(161, 140, 74, 2, YELLOW); // 黄为关闭
}
void System::k3open() {
__HAL_TIM_SetAutoreload(&htim6, 12599); // 慢放2/3
__HAL_TIM_SetAutoreload(&htim7, 125);
LCD_ShowChineseStringBig(307, 220, 78, 2, LIGHTBLUE); // 慢放
}
void System::k3close() {
__HAL_TIM_SetAutoreload(&htim6, 8399); // 恢复
__HAL_TIM_SetAutoreload(&htim7, 83);
LCD_ShowChineseStringBig(307, 220, 78, 2, YELLOW); // 慢放
}
void System::k4open() {
__HAL_TIM_SetAutoreload(&htim6,
4799); // 快进 1.75,两倍速会卡住,因为HAL库太占资源
__HAL_TIM_SetAutoreload(&htim7, 47);
LCD_ShowChineseStringBig(307, 180, 76, 2, LIGHTBLUE); // 快进
}
void System::k4close() {
__HAL_TIM_SetAutoreload(&htim6, 8399); // 恢复
__HAL_TIM_SetAutoreload(&htim7, 83);
LCD_ShowChineseStringBig(307, 180, 76, 2, YELLOW); // 快进
}
四、验证
1,创建OpenCD
可以参考CLion + STM32CubeMX【嵌入式开发 _环境搭建_C++】,目录【四-3】
2,构建
接下来不会有什么太大问题,需要注意的是C++调用C和C调用C++
3,分析
官方其实集成了RTOS分析工具
五、BUG
1,使用信号量传递,但永远阻塞
可能是被编译器优化掉了。试了一下,跟是否有临界区无关
void StartDefaultTask(void *argument)
{
/****创建信号量的过程不要放在这!!!应该放在MX_FREERTOS_Init()里****/
//myBinarySem01Handle = xSemaphoreCreateBinary();
for (;;)
{
xSemaphoreGive(myBinarySem01Handle);
osDelay(500);
}
}
2,使用 xSemaphoreTake报错
这是因为C++有更为严格的类型检查,使用static_cast就可以解决问题
void FreqTask(void *argument)
{
for (;;)
{
xSemaphoreTake(static_cast<QueueHandle_t>(myBinarySem01Handle), portMAX_DELAY);
osDelay(100);
}
}
3,无法使用信号量,卡在configASSERT( pxQueue );
①lvgl
本来一直找不到原因,直至重新建一个工程后才发现,不使用lvgl可以正常使用信号量,但是一使用就会卡住,所以推断是lvgl出现了问题。后来受这篇博客启发STM32 FreeRTOS处理LVGL+串口双任务相关问题总结,最初推断是堆栈不够了。
/*LVGL初始化*/
lv_init();
lv_port_disp_init();
在后续实测中发现,堆栈分配都是足够的,可仍会卡死。但只要把lv_port_disp_init();注释掉信号量就不会卡死。
由此对 lv_port_disp_init();进行了深入测试,最终发现,无论是LCD_Init();还是lv_disp_drv_register(&disp_drv),只要调用其中一个就会出现信号量卡死的情况
这种结果是相当匪夷所思的,因为理论上LCD初始化驱动怎么也不可能影响到FreeRTOS。能出现这种莫名其妙的错误,必然有着莫名其妙的原因。
于是我开始猜想会不会是LCD初始化需要进入临界区,尽管这种想法挺莫名其妙的,因为我的 lv_port_disp_init();是在FreeRTOS初始化之前调用的
int main()
{
HAL_Init();
SystemClock_Config();
Base_GPIO_Init();
PeripheralInit();
/*LVGL初始化*/
lv_init();
lv_port_disp_init();
/*FreeRTOS初始化*/
osKernelInitialize();
My_FreeRTOS_Init();
osKernelStart();
}
结果居然可以正常工作了!?
连lv_disp_drv_register(&disp_drv)都不需要注释掉了
#include "cmsis_os.h"
void lv_port_disp_init(void)
{
taskENTER_CRITICAL();
LCD_Init();
taskEXIT_CRITICAL();
/* Example for 1) */
static lv_disp_draw_buf_t draw_buf_dsc_1;
static lv_color_t buf_1[MY_DISP_HOR_RES * BufferRows]; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * BufferRows);
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
disp_drv.hor_res = MY_DISP_HOR_RES;
disp_drv.ver_res = MY_DISP_VER_RES;
disp_drv.flush_cb = disp_flush;
disp_drv.draw_buf = &draw_buf_dsc_1;
lv_disp_drv_register(&disp_drv);
}
当然不能像上面那样直接在lv_port_disp_init里加临界区代码,那样不利于后续移植,最好在你调用这个函数的地方加上临界保护区
/*LVGL初始化*/
lv_init();
taskENTER_CRITICAL();
lv_port_disp_init();
taskEXIT_CRITICAL();
②任务栈
为lv_task_handler分配的栈不要太节省,否则也会出现卡死的现象。经测试,最少得分配栈大小为277,当然由于未考虑过多的因素,所以这个数值仅供参考。建议还是分配512
osThreadId_t GUITaskHandle;
const osThreadAttr_t GUITask_attributes = {
.name = "GUITask",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void GUITask(void *argument)
{
while (1)
{
lv_task_handler();
osDelay(5);
}
}
后来加载了界面再测试,结果直接进入了硬件中断。去掉lv_scr_load(ui->EPMscreen);就不会进入硬件中断。其根本原因还是刚才分配给lv_task_handler的任务栈太小,改成512就不会出现问题。
下一次如果出现类似问题,可以考虑将lv_task_handler的任务栈调大一点
void setup_ui(lv_ui *ui)
{
init_scr_del_flag(ui);
setup_scr_EPMscreen(ui);
lv_scr_load(ui->EPMscreen);
}
③信号量创建
属实没想到创建位置也能引发这个报错,下面这个是错误例子(我还没有排除两种体系的API造成的影响)
/*基本任务、信号量创建*/
keyBinarySemHandle = xSemaphoreCreateBinary();//放在任务里的前面会出现问题
GUITaskHandle = osThreadNew(GUITask, nullptr, &GUITask_attributes);
KeyTaskHandle = osThreadNew(KeyTask, nullptr, &KeyTask_attributes);
/*信号量创建*/
FPGABinarySemHandle = xSemaphoreCreateBinary();
/*任务创建*/
FreqTaskHandle = osThreadNew(FreqTask, nullptr, &FreqTask_attributes);
正确创建应该为下面这样
/*信号量创建*/
keyBinarySemHandle = xSemaphoreCreateBinary();//放在任务里的前面会出现问题
FPGABinarySemHandle = xSemaphoreCreateBinary();
/*任务创建*/
FreqTaskHandle = osThreadNew(FreqTask, nullptr, &FreqTask_attributes);
GUITaskHandle = osThreadNew(GUITask, nullptr, &GUITask_attributes);
KeyTaskHandle = osThreadNew(KeyTask, nullptr, &KeyTask_attributes);
提示:建议使用CMSIS-RTOS或CMSIS-RTOS2的API,而非FreeRTOS自带的API,因为前者是统一的标准,即便后续你换了RTOS,也仍可继续使用。
4,进不去任何任务包括中断
如果你一直卡在
prvCheckTasksWaitingTermination、portTASK_FUNCTION之类的函数
可能是你把某个任务的栈大小分配得太小了,调大一点就可以了
5,SPI_Flash卡死在等待
在等待函数里加个临界区保护代码就可以了
inline void SPI_FLASH_WaitForWriteEnd() // 等待写完成
{
uint8_t FLASH_Status;
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(ReadStatusReg); /* 发送 读状态寄存器 命令 */
do
{
taskENTER_CRITICAL();
FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
taskEXIT_CRITICAL();
} while (FLASH_Status & WIP_Flag);// 检测BUSY位是否为0,0表示已完成不忙
SPI_FLASH_CS_HIGH();
}
6,初始化卡住
这是相当神奇的一幕,那就是理论上还没有执行到osKernelInitialize();,可却已经以时间片轮询的方式进行了。所以会发生一些十分诡异的情况,比如在前面的一些基础GPIO的初始化,可能会与后面的App_Init中如IIC的GPIO初始化产生冲突,有时候HAL_Delay也会卡住。
为了避免这种情况发生,可以在需要提前做的部分加上临界区加以保护
#include "Application_Logic.h"
#include "FreeRTOS.h"
#include "baseInit.h"
#include "cmsis_os2.h"
#include "task.h"
int main() {
taskENTER_CRITICAL();//临界区
HAL_Init();
SystemClock_Config();
Base_Founction_Init();
taskEXIT_CRITICAL();//临界区
/*FreeRTOS初始化*/
osKernelInitialize();
App_Init();
osKernelStart();
while (1)
;
}