赞
踩
MCU:STM32F407VET6
开发环境:CLion
说明:
1,名字太长的... ...,我会打省略号
2,自带的截屏工具,故图片上的提示信息均为纯手工【涂鸦】
3,本篇基于CLion开发的,也可供其他开发软件如IAR、Keil等参考
4,相关问题放在最下面
首先在CLion中创建STM32CubeMX项目,此处名称为MyRTOS_Cpp, 创建完后如下
在CLion中打开CubeMX并更换芯片
配置SYS,选择Serial Wire和TIM7(SysTick被FreeRTOS占用)
配置RCC,选择Crystal/Cera ... ...
配置时钟树,先查看自己板子的原理图,看看外部晶振是多少,此处为12MHz。
填好外部晶振频率后,连接通路,本篇使用的MCU主板最大频率为168MHz,故有
下载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
回到CLion后,会默认弹出一个窗口(会弹两次),跳过即可
打开CMakeLists,把FPU打开(CubeMX生成时是默认注释的)
取消注释后如下
重新加载CMake,然后再点击个锤子,没有问题后再进行下一步
【说明】:本篇采用的开发架构是硬件驱动层+功能模块层+应用层,可自行按需构建适合自己的架构,本篇仅做引导
右键左栏项目下的MyRTOS_Cpp,再点击如下选项,打开资源管理器,并进入该工程目录里
创建两个目录Application(应用层)和FunctionModuleLayer(功能模块层),并在其下创建子目录inc和src
在Drivers(硬件驱动层)目录下创建User(不重要,可以不创建),并也在其下添加inc和src
把下面两个头文件及其源文件移至FunctionModuleLayer(功能模块层)对应的子目录
把main.c移至Application(应用层)下的src中,并更改后缀名
把下面文件移至Drivers(硬件驱动层)下User里的src中
把刚刚填的文件包含起来,并重新加载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/*.*" )
在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)
- {
- }
- }
把gpio.h里的头文件包含(原为main.h)改为user_init.h,到资源管理器中把gpio.c改为gpio.cpp(纯粹为了统一,但stm32f4xx_it.c不能这样做,不然启动文件识别不出来)
创建文件时,把所有勾选去掉
在此目录下创建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,用于代替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();
- }
- }
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,用于取代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()
- {
- }
其一,开发重心改变。开发的主体部分不再是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); // 快进
- }
可以参考CLion + STM32CubeMX【嵌入式开发 _环境搭建_C++】,目录【四-3】
接下来不会有什么太大问题,需要注意的是C++调用C和C调用C++
官方其实集成了RTOS分析工具
可能是被编译器优化掉了。试了一下,跟是否有临界区无关
- void StartDefaultTask(void *argument)
- {
- /****创建信号量的过程不要放在这!!!应该放在MX_FREERTOS_Init()里****/
- //myBinarySem01Handle = xSemaphoreCreateBinary();
- for (;;)
- {
- xSemaphoreGive(myBinarySem01Handle);
- osDelay(500);
- }
- }
这是因为C++有更为严格的类型检查,使用static_cast就可以解决问题
- void FreqTask(void *argument)
- {
- for (;;)
- {
- xSemaphoreTake(static_cast<QueueHandle_t>(myBinarySem01Handle), portMAX_DELAY);
- osDelay(100);
- }
- }
本来一直找不到原因,直至重新建一个工程后才发现,不使用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,也仍可继续使用。
如果你一直卡在
prvCheckTasksWaitingTermination、portTASK_FUNCTION之类的函数
可能是你把某个任务的栈大小分配得太小了,调大一点就可以了
在等待函数里加个临界区保护代码就可以了
- 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();
- }
这是相当神奇的一幕,那就是理论上还没有执行到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)
- ;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。