当前位置:   article > 正文

【嵌入式操作系统】FreeRTOS_C++_创建及LVGL相关问题_c++开发rtos

c++开发rtos

MCUSTM32F407VET6

开发环境:CLion

说明

        1,名字太长的... ...,我会打省略号

        2,自带的截屏工具,故图片上的提示信息均为纯手工【涂鸦】

        3,本篇基于CLion开发的,也可供其他开发软件如IARKeil等参考

        4,相关问题放在最下面

一、创建基本工程

1,CLion中创建项目

首先在CLion中创建STM32CubeMX项目,此处名称为MyRTOS_Cpp, 创建完后如下

2,配置CubeMX

①基本配置

CLion中打开CubeMX并更换芯片

配置SYS,选择Serial WireTIM7(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

  1. include_directories(
  2. Application/inc
  3. Drivers/USER/inc
  4. FunctionalModuleLayer/inc
  5. Core/Inc Drivers/STM32F4xx_HAL_Driver/Inc
  6. Drivers/STM32F4xx_HAL_Driver/Inc/Legacy
  7. Middlewares/Third_Party/FreeRTOS/Source/include
  8. Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2
  9. Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F
  10. Drivers/CMSIS/Device/ST/STM32F4xx/Include Drivers/CMSIS/Include)
  11. file(GLOB_RECURSE SOURCES "Core/*.*" "Middlewares/*.*" "Drivers/*.*" "Application/*.*" "FunctionalModuleLayer/*.*" )

2,Drivers(硬件驱动层)

Drivers下的User中,分别在对应位置创建user_init.huser.init.c,用于代替main.h

在把main文件中的代码复制到user_init中(头文件对头文件,源文件对源文件),再删除无用的内容

user_init.h如下

  1. #ifndef RTOS_Cpp_USER_INIT_H
  2. #define RTOS_Cpp_USER_INIT_H
  3. #ifdef __cplusplus
  4. extern "C" {
  5. #endif
  6. #include "stm32f4xx.h"
  7. #include "stm32f4xx_hal.h"
  8. void Error_Handler(void);
  9. #ifdef __cplusplus
  10. }
  11. #endif
  12. #endif

user_init.c如下

  1. #include "user_init.h"
  2. void Error_Handler(void)
  3. {
  4. __disable_irq();
  5. while (1)
  6. {
  7. }
  8. }

3,FunctionModuleLayer(功能模块层)

把gpio.h里的头文件包含(原为main.h)改为user_init.h,到资源管理器中把gpio.c改为gpio.cpp(纯粹为了统一,但stm32f4xx_it.c这样做,不然启动文件识别不出来)

创建文件时,把所有勾选去掉

①MyTask

在此目录下创建MyTask,用于代替先前的FreeRTOS.c,同时以后的任务函数开发就可以放在此处

MyTask.h

  1. #ifndef RTOS_Cpp_MYTASK_H
  2. #define RTOS_Cpp_MYTASK_H
  3. #include "cmsis_os.h"
  4. #include "FreeRTOS.h"
  5. #include "task.h"
  6. /*用户头文件*/
  7. #include "user_init.h"
  8. /*任务函数句柄*/
  9. extern osThreadId_t defaultTaskHandle;
  10. const osThreadAttr_t defaultTask_attributes = {
  11. .name = "defaultTask",
  12. .stack_size = 128 * 4,
  13. .priority = (osPriority_t) osPriorityNormal,
  14. };
  15. /*任务函数声明*/
  16. void StartDefaultTask(void *argument);
  17. #endif

MyTask.cpp

  1. #include "MyTask.h"
  2. uint16_t temp = 0x0010;
  3. /*句柄定义*/
  4. osThreadId_t defaultTaskHandle;
  5. /*任务函数*/
  6. void StartDefaultTask(void *argument)
  7. {
  8. for (;;)
  9. {
  10. osDelay(1);
  11. }
  12. }

②RCC

在目录下创建RCC,用于代替main.cpp里的时钟配置函数

RCC.h

  1. #ifndef RTOS_Cpp_RCC_H
  2. #define RTOS_Cpp_RCC_H
  3. #include "user_init.h"
  4. void SystemClock_Config(void);
  5. #endif

RCC.cpp        我们的时钟配置可能不一样,前面的文件也是如此

  1. #include "RCC.h"
  2. void SystemClock_Config(void)
  3. {
  4. RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  5. RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  6. /** Configure the main internal regulator output voltage
  7. */
  8. __HAL_RCC_PWR_CLK_ENABLE();
  9. __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  10. /** Initializes the RCC Oscillators according to the specified parameters
  11. * in the RCC_OscInitTypeDef structure.
  12. */
  13. RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  14. RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  15. RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  16. RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  17. RCC_OscInitStruct.PLL.PLLM = 6;
  18. RCC_OscInitStruct.PLL.PLLN = 168;
  19. RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  20. RCC_OscInitStruct.PLL.PLLQ = 4;
  21. if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  22. {
  23. Error_Handler();
  24. }
  25. /** Initializes the CPU, AHB and APB buses clocks
  26. */
  27. RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  28. RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  29. RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  30. RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  31. RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
  32. if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  33. {
  34. Error_Handler();
  35. }
  36. }

4,Application(应用层)

①main.cpp

main不再是函数编写的主体了,仅仅用于写中断回调。main.h可以删掉了

  1. #include "MyRTOS.h"
  2. Sys *sys;
  3. int main(void)
  4. {
  5. sys = new Sys;
  6. sys->Peripheral_Init();//外设初始化
  7. sys->OS_Init();
  8. }
  9. /*中断回调函数*/
  10. extern "C" {
  11. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  12. {
  13. if (htim->Instance == TIM7)
  14. {
  15. HAL_IncTick();
  16. }
  17. }
  18. }
  19. #ifdef USE_FULL_ASSERT
  20. /**
  21. * @brief Reports the name of the source file and the source line number
  22. * where the assert_param error has occurred.
  23. * @param file: pointer to the source file name
  24. * @param line: assert_param error line source number
  25. * @retval None
  26. */
  27. void assert_failed(uint8_t *file, uint32_t line)
  28. {
  29. /* USER CODE BEGIN 6 */
  30. /* User can add his own implementation to report the file name and line number,
  31. ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  32. /* USER CODE END 6 */
  33. }
  34. #endif /* USE_FULL_ASSERT */

②MyRTOS

添加MyRTOS,用于取代FreeRTOS和原main.c

MyRTOS.h

  1. #ifndef RTOS_Cpp_MYRTOS_H
  2. #define RTOS_Cpp_MYRTOS_H
  3. #include "MyTask.h"
  4. #include "RCC.h"
  5. #include "gpio.h"
  6. /*系统类*/
  7. class Sys
  8. {
  9. public:
  10. Sys();//默认初始化
  11. ~Sys();
  12. void Peripheral_Init();//外设初始化
  13. void OS_Init();//RTOS初始化
  14. public:
  15. };
  16. #endif

MyRTOS.cpp

  1. #include "MyRTOS.h"
  2. Sys::Sys()
  3. {
  4. HAL_Init();
  5. SystemClock_Config();
  6. GPIO_Init();
  7. }
  8. /*外设初始化函数*/
  9. void Sys::Peripheral_Init()
  10. {
  11. /*
  12. DAC_Init();
  13. HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
  14. */
  15. }
  16. void Sys::OS_Init()
  17. {
  18. /*用户任务初始化*/
  19. defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
  20. /*操作系统初始化*/
  21. osKernelInitialize();
  22. osKernelStart();
  23. }
  24. Sys::~Sys()
  25. {
  26. }

5,说明

        其一,开发重心改变。开发的主体部分不再是main.c或FreeRTOS.c了,而是MyTaskMyRTOS,前者侧重开发任务函数,后者侧重任务的初始化任务调度以外设配置

        其二,层次更加分明。本架构源于无际大佬

        其三,面向对象开发。下面是以前裸机开发时创建的System类,希望能提供些许思路。不过,如果请谨慎使用System类(把所有初始化封装在一个类里面),如果你的程序比较大,如使用了LVGL,那么运行时极有可能申请不到空间。

  1. #ifndef _SYSTEM_H
  2. #define _SYSTEM_H
  3. #include "stm32f4xx.h" //必须放在最上面,你也不想它突然报几百个错吧
  4. #include "stm32f4xx_hal_conf.h"
  5. #include "stm32f4xx_it.h"
  6. #include <iostream>
  7. /*DATA*/
  8. #include "logo.h"
  9. #include "FONT.h"
  10. #include "WAVEDAT.h"
  11. /*USER*/
  12. #include "AD-DA.h"
  13. #include "flash.h"
  14. #include "FSMC.h"
  15. #include "LCD.h"
  16. #include "spi.h"
  17. #include "spi_flash.h"
  18. #include "tools.h"
  19. #include "timer.h"
  20. #include "tools.h"
  21. #include "UI.h"
  22. #include "usart.h"
  23. #include "key.h"
  24. #include "FPGA.h"
  25. /*指针类*/
  26. #define KEY_RAM (*((volatile unsigned short *)0x6006000C)) // 键盘接口地址
  27. #define IO_CS (*((volatile unsigned short *)0x60020000)) // MCU-IO扩展模块中并行IO片选地址
  28. /*函数类*/
  29. #define CK1_LOW() GPIO_ResetBits(GPIOC, GPIO_Pin_4) // 继电器1置低
  30. #define CK1_HIGH() GPIO_SetBits(GPIOC, GPIO_Pin_4) // 继电器1置高
  31. #define CK2_LOW() GPIO_ResetBits(GPIOC, GPIO_Pin_5) // 继电器2置低
  32. #define CK2_HIGH() GPIO_SetBits(GPIOC, GPIO_Pin_5) // 继电器2置高
  33. class System
  34. {
  35. public:
  36. System(); // 系统初始化
  37. ~System();
  38. /*初始化*/
  39. void KEY_EXTI_init(void); // 键盘外部中断配置
  40. void function_init(void);
  41. void SystemClock_Config(void);
  42. /*功能模块设计*/
  43. void keybond(void); // 按键绑定
  44. /*键区*/
  45. void k0open(); // 擦除
  46. void k0close();
  47. void k1open(); // 录音
  48. void k1close();
  49. void k2open(); // 放音
  50. void k2close();
  51. void k3open(); // 快进
  52. void k3close();
  53. void k4open(); // 慢放
  54. void k4close();
  55. public:
  56. /*基本类成员*/
  57. Key *key;
  58. /*用户类成员*/
  59. public:
  60. /*句柄*/
  61. public:
  62. /*标志类*/
  63. /*数值类*/
  64. uint8_t min, csec; // 分钟、秒、百分秒
  65. uint16_t sec;
  66. uint8_t recordcsec;
  67. uint16_t recordsec;
  68. /*计数类*/
  69. /*指针型*/
  70. uint32_t recordaddr; // 录音地址
  71. uint32_t playaddr; // 放音地址
  72. // uint32_t startaddr;
  73. uint16_t offset;
  74. /*debugger*/
  75. };
  76. #endif
  1. #include "system.h"
  2. /*系统功能模块初始化*/
  3. void System::function_init(void) {
  4. /*用户变量初始化*/
  5. recordaddr = 0;
  6. playaddr = 0;
  7. sec = 0;
  8. csec = 0; // 注:在这里不是百分秒,而是十分秒
  9. recordcsec = 0;
  10. recordsec = 0;
  11. offset = 0; // 缓存偏移初始化
  12. // startaddr = 0;
  13. /*用户类的实例化*/
  14. /*用户功能初始化*/
  15. MX_SPI3_Init();
  16. HAL_SPI_MspInit(&hspi3);
  17. MX_ADC1_Init();
  18. MX_DAC_Init();
  19. HAL_ADC_MspInit(&hadc1); // 方便可移植
  20. HAL_DAC_MspInit(&hdac);
  21. MX_TIM2_Init();
  22. MX_TIM6_Init(); // 10Hz
  23. MX_TIM7_Init();
  24. HAL_TIM_Base_MspInit(&htim2);
  25. HAL_TIM_Base_MspInit(&htim6);
  26. HAL_TIM_Base_MspInit(&htim7);
  27. HAL_TIM_Base_Start(&htim2);
  28. HAL_DAC_Start(&hdac, DAC1_CHANNEL_1);
  29. SPI_FLASH_Init();
  30. __HAL_TIM_CLEAR_IT(
  31. &htim6,
  32. TIM_IT_UPDATE); // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
  33. __HAL_TIM_CLEAR_IT(
  34. &htim7,
  35. TIM_IT_UPDATE); // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
  36. }
  37. // 按键绑定
  38. void System::keybond() {
  39. key->sign = 0; // 重置键效
  40. key->reverseflag(key->code); // 键标取反
  41. switch (key->code) {
  42. case 0x0: // 按键K0
  43. if (key->operateotherkey(1, keyk1 | keyk2 | keyk3 | keyk4, 0)) {
  44. k1close();
  45. k2close();
  46. HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
  47. }
  48. /*再打开本键*/
  49. k0open();
  50. break;
  51. case 0x1: // 按键K1
  52. if (key->flag & keyk1) {
  53. /*先启闭其他键,如果需要的话*/
  54. if (key->operateotherkey(1, keyk2 | keyk3 | keyk4, 0)) {
  55. k2close();
  56. k3close();
  57. k4close();
  58. }
  59. k1open();
  60. } else {
  61. k1close();
  62. }
  63. break;
  64. case 0x2: // 按键K2
  65. if (key->flag & keyk2) {
  66. if (key->operateotherkey(1, keyk1, 0)) {
  67. k1close(); // 只需关闭录音
  68. playaddr = 0;
  69. sec = 0, csec = 0;
  70. }
  71. k2open();
  72. } else {
  73. k2close();
  74. }
  75. break;
  76. case 0x3: // 按键K3
  77. if (!key->iskeyopen(keyk1)) // 如果录音开启,那么就不执行慢放
  78. {
  79. if (key->flag & keyk3) {
  80. if (key->operateotherkey(1, keyk4, 0)) {
  81. LCD_ShowChineseStringBig(307, 180, 76, 2, YELLOW); // 关闭快进
  82. }
  83. k3open();
  84. } else {
  85. k3close();
  86. }
  87. }
  88. break;
  89. case 0x4: // 按键K4
  90. if (!key->iskeyopen(keyk1)) // 如果录音开启,那么就不执行快进
  91. {
  92. if (key->flag & keyk4) {
  93. if (key->operateotherkey(1, keyk3, 0)) {
  94. LCD_ShowChineseStringBig(307, 220, 78, 2, YELLOW); // 关闭慢放
  95. }
  96. k4open();
  97. } else {
  98. k4close();
  99. }
  100. }
  101. break;
  102. case 0x5: // 按键K5
  103. break;
  104. case 0x6: // 按键K6
  105. break;
  106. case 0x7: // 按键K7
  107. break;
  108. case 0x8: // 按键K8
  109. break;
  110. case 0x9: // 按键K9
  111. break;
  112. case 0xA: // 按键KA
  113. break;
  114. case 0xB: // 按键KB
  115. break;
  116. case 0xC: // 按键KC
  117. break;
  118. case 0xD: // 按键KD
  119. break;
  120. case 0xE: // 按键KE
  121. break;
  122. case 0xF: // 按键KF
  123. break;
  124. default: // 异常状态
  125. break;
  126. }
  127. }
  128. /*系统初始化*/
  129. System::System() {
  130. /*基本全局初始化*/
  131. FSMC_init(); // 灵活静态存储初始化——必不可少
  132. GPIO_Configuration(); // GPIO初始化
  133. SystemClock_Config(); // 系统时钟初始化
  134. LCD_Init9488(); // 液晶初始化
  135. KEY_EXTI_init(); // 全局中断初始化
  136. /*基本初始化*/
  137. TFTLED = 0x01; // 背光寄存器初始化
  138. key = new Key;
  139. /*用户基本初始化*/
  140. UI_init(); // 显示Logo
  141. tools.delay_ms(2500); // 延时一坤秒左右
  142. LCD_Clear1(0x0000); // 清屏
  143. userUI(); // 显示用户界面
  144. }
  145. System::~System() {
  146. delete key;
  147. key = nullptr;
  148. }
  149. // 全局中断配置
  150. // 全局中断配置
  151. void System::KEY_EXTI_init(void) {
  152. GPIO_InitTypeDef GPIO_InitStructure;
  153. __HAL_RCC_GPIOB_CLK_ENABLE();
  154. GPIO_InitStructure.Mode = MODE_INPUT;
  155. GPIO_InitStructure.Pull = GPIO_NOPULL;
  156. GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;
  157. GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
  158. GPIO_InitStructure.Pin = GPIO_PIN_0;
  159. HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
  160. // 外部中断1初始化
  161. GPIO_InitStructure.Pin = GPIO_PIN_1;
  162. HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
  163. HAL_NVIC_SetPriority(EXTI0_IRQn, 0x01, 0x02);
  164. HAL_NVIC_EnableIRQ(EXTI0_IRQn);
  165. HAL_NVIC_SetPriority(EXTI1_IRQn, 0x01, 0x02);
  166. HAL_NVIC_EnableIRQ(EXTI1_IRQn);
  167. }
  168. /*系统时钟配置*/
  169. void System::SystemClock_Config(void) {
  170. /*系统时钟168MHz*/
  171. RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  172. RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  173. __HAL_RCC_PWR_CLK_ENABLE();
  174. __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  175. RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  176. RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  177. RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  178. RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  179. RCC_OscInitStruct.PLL.PLLM = 12;
  180. RCC_OscInitStruct.PLL.PLLN = 336;
  181. RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  182. RCC_OscInitStruct.PLL.PLLQ = 4;
  183. if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  184. while (1)
  185. ;
  186. RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
  187. RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  188. RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  189. RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  190. RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  191. RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
  192. if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  193. while (1)
  194. ;
  195. }
  196. /************************************************************/
  197. /* 按键功能设计 */
  198. /************************************************************/
  199. /*开启键0*/
  200. void System::k0open() {
  201. uint16_t oldflag = key->flag; // 存储旧键值,为了防止在擦除死循环中乱按
  202. sec = 0, csec = 0; // 计时器清零
  203. recordaddr = 0; // 录音地址
  204. recordcsec = 0, recordsec = 0;
  205. playaddr = 0;
  206. tools.dispsec(0);
  207. HAL_TIM_Base_Start_IT(&htim6); // 开启定时器
  208. LCD_ShowChineseStringBig(161, 220, 70, 2, LIGHTBLUE); // 显示擦除画面
  209. SPI_FLASH_ChipErase();
  210. LCD_ShowChineseStringBig(161, 220, 70, 2, YELLOW); // 关闭擦除画面
  211. HAL_TIM_Base_Stop_IT(&htim6);
  212. key->flag = oldflag; // 把按键清零
  213. key->sign = 0; // 把置键有效也清零
  214. }
  215. /*关闭键0*/
  216. void System::k0close() {
  217. // 擦除不能取消,所以没有重复开关的功能
  218. }
  219. void System::k1open() {
  220. if (recordaddr == 0) /*判断有没有擦除的必要*/
  221. {
  222. uint8_t i = 0;
  223. uint8_t arr[16];
  224. SPI_FLASH_BufferRead(arr, 0, 16);
  225. for (; i < 16; i++) /*只要有数据就清空*/
  226. if (arr[i] != Dummy_Byte) {
  227. k0open();
  228. break;
  229. }
  230. }
  231. LCD_ShowChineseStringBig(161, 180, 72, 2, LIGHTBLUE); // 显示录音
  232. sec = recordsec, csec = recordcsec;
  233. tools.dispsec(sec);
  234. HAL_TIM_Base_Start_IT(&htim6);
  235. HAL_ADC_Start_IT(&hadc1); // 开启ADC
  236. }
  237. void System::k1close() {
  238. HAL_ADC_Stop_IT(&hadc1); // 关闭ADC
  239. HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
  240. recordcsec = csec;
  241. recordsec = sec;
  242. csec = 0; // 为了把放音清除
  243. sec = 0;
  244. playaddr = 0;
  245. LCD_ShowChineseStringBig(161, 180, 72, 2, YELLOW); // 显示录音
  246. }
  247. void System::k2open() {
  248. if (recordaddr == playaddr)
  249. playaddr = 0, sec = 0, csec = 0; // 置零
  250. tools.dispsec(sec);
  251. LCD_ShowChineseStringBig(161, 140, 74, 2, LIGHTBLUE); // 蓝为开启
  252. HAL_TIM_Base_Start_IT(&htim6); // 打开计时器
  253. HAL_TIM_Base_Start_IT(&htim7); // 打开放音用的中断
  254. }
  255. void System::k2close() {
  256. HAL_TIM_Base_Stop_IT(&htim6); // 关闭计时器
  257. HAL_TIM_Base_Stop_IT(&htim7);
  258. LCD_ShowChineseStringBig(161, 140, 74, 2, YELLOW); // 黄为关闭
  259. }
  260. void System::k3open() {
  261. __HAL_TIM_SetAutoreload(&htim6, 12599); // 慢放2/3
  262. __HAL_TIM_SetAutoreload(&htim7, 125);
  263. LCD_ShowChineseStringBig(307, 220, 78, 2, LIGHTBLUE); // 慢放
  264. }
  265. void System::k3close() {
  266. __HAL_TIM_SetAutoreload(&htim6, 8399); // 恢复
  267. __HAL_TIM_SetAutoreload(&htim7, 83);
  268. LCD_ShowChineseStringBig(307, 220, 78, 2, YELLOW); // 慢放
  269. }
  270. void System::k4open() {
  271. __HAL_TIM_SetAutoreload(&htim6,
  272. 4799); // 快进 1.75,两倍速会卡住,因为HAL库太占资源
  273. __HAL_TIM_SetAutoreload(&htim7, 47);
  274. LCD_ShowChineseStringBig(307, 180, 76, 2, LIGHTBLUE); // 快进
  275. }
  276. void System::k4close() {
  277. __HAL_TIM_SetAutoreload(&htim6, 8399); // 恢复
  278. __HAL_TIM_SetAutoreload(&htim7, 83);
  279. LCD_ShowChineseStringBig(307, 180, 76, 2, YELLOW); // 快进
  280. }

四、验证

1,创建OpenCD

可以参考CLion + STM32CubeMX【嵌入式开发 _环境搭建_C++】,目录【四-3

2,构建

        接下来不会有什么太大问题,需要注意的是C++调用C和C调用C++

3,分析

        官方其实集成了RTOS分析工具

五、BUG

1,使用信号量传递,但永远阻塞

可能是被编译器优化掉了。试了一下,跟是否有临界区无关

  1. void StartDefaultTask(void *argument)
  2. {
  3. /****创建信号量的过程不要放在这!!!应该放在MX_FREERTOS_Init()里****/
  4. //myBinarySem01Handle = xSemaphoreCreateBinary();
  5. for (;;)
  6. {
  7. xSemaphoreGive(myBinarySem01Handle);
  8. osDelay(500);
  9. }
  10. }

2,使用 xSemaphoreTake报错

这是因为C++有更为严格的类型检查,使用static_cast就可以解决问题

  1. void FreqTask(void *argument)
  2. {
  3. for (;;)
  4. {
  5. xSemaphoreTake(static_cast<QueueHandle_t>(myBinarySem01Handle), portMAX_DELAY);
  6. osDelay(100);
  7. }
  8. }

3,无法使用信号量,卡在configASSERT( pxQueue );

        ①lvgl

        本来一直找不到原因,直至重新建一个工程后才发现,不使用lvgl可以正常使用信号量,但是一使用就会卡住,所以推断是lvgl出现了问题。后来受这篇博客启发STM32 FreeRTOS处理LVGL+串口双任务相关问题总结,最初推断是堆栈不够了。

  1. /*LVGL初始化*/
  2. lv_init();
  3. 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初始化之前调用的

  1. int main()
  2. {
  3. HAL_Init();
  4. SystemClock_Config();
  5. Base_GPIO_Init();
  6. PeripheralInit();
  7. /*LVGL初始化*/
  8. lv_init();
  9. lv_port_disp_init();
  10. /*FreeRTOS初始化*/
  11. osKernelInitialize();
  12. My_FreeRTOS_Init();
  13. osKernelStart();
  14. }

        结果居然可以正常工作了!?

连lv_disp_drv_register(&disp_drv)都不需要注释掉了

  1. #include "cmsis_os.h"
  2. void lv_port_disp_init(void)
  3. {
  4. taskENTER_CRITICAL();
  5. LCD_Init();
  6. taskEXIT_CRITICAL();
  7. /* Example for 1) */
  8. static lv_disp_draw_buf_t draw_buf_dsc_1;
  9. static lv_color_t buf_1[MY_DISP_HOR_RES * BufferRows]; /*A buffer for 10 rows*/
  10. lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * BufferRows);
  11. static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
  12. lv_disp_drv_init(&disp_drv); /*Basic initialization*/
  13. disp_drv.hor_res = MY_DISP_HOR_RES;
  14. disp_drv.ver_res = MY_DISP_VER_RES;
  15. disp_drv.flush_cb = disp_flush;
  16. disp_drv.draw_buf = &draw_buf_dsc_1;
  17. lv_disp_drv_register(&disp_drv);
  18. }

        当然不能像上面那样直接在lv_port_disp_init里加临界区代码,那样不利于后续移植,最好在你调用这个函数的地方加上临界保护区

  1. /*LVGL初始化*/
  2. lv_init();
  3. taskENTER_CRITICAL();
  4. lv_port_disp_init();
  5. taskEXIT_CRITICAL();

②任务栈

lv_task_handler分配的栈不要太节省,否则也会出现卡死的现象。经测试,最少得分配栈大小为277,当然由于未考虑过多的因素,所以这个数值仅供参考。建议还是分配512

  1. osThreadId_t GUITaskHandle;
  2. const osThreadAttr_t GUITask_attributes = {
  3. .name = "GUITask",
  4. .stack_size = 512 * 4,
  5. .priority = (osPriority_t) osPriorityNormal,
  6. };
  1. void GUITask(void *argument)
  2. {
  3. while (1)
  4. {
  5. lv_task_handler();
  6. osDelay(5);
  7. }
  8. }

        后来加载了界面再测试,结果直接进入了硬件中断。去掉lv_scr_load(ui->EPMscreen);就不会进入硬件中断。其根本原因还是刚才分配给lv_task_handler的任务栈太小,改成512就不会出现问题。

        下一次如果出现类似问题,可以考虑将lv_task_handler的任务栈调大一点

  1. void setup_ui(lv_ui *ui)
  2. {
  3. init_scr_del_flag(ui);
  4. setup_scr_EPMscreen(ui);
  5. lv_scr_load(ui->EPMscreen);
  6. }

③信号量创建

        属实没想到创建位置也能引发这个报错,下面这个是错误例子(我还没有排除两种体系的API造成的影响)

  1. /*基本任务、信号量创建*/
  2. keyBinarySemHandle = xSemaphoreCreateBinary();//放在任务里的前面会出现问题
  3. GUITaskHandle = osThreadNew(GUITask, nullptr, &GUITask_attributes);
  4. KeyTaskHandle = osThreadNew(KeyTask, nullptr, &KeyTask_attributes);
  5. /*信号量创建*/
  6. FPGABinarySemHandle = xSemaphoreCreateBinary();
  7. /*任务创建*/
  8. FreqTaskHandle = osThreadNew(FreqTask, nullptr, &FreqTask_attributes);

正确创建应该为下面这样

  1. /*信号量创建*/
  2. keyBinarySemHandle = xSemaphoreCreateBinary();//放在任务里的前面会出现问题
  3. FPGABinarySemHandle = xSemaphoreCreateBinary();
  4. /*任务创建*/
  5. FreqTaskHandle = osThreadNew(FreqTask, nullptr, &FreqTask_attributes);
  6. GUITaskHandle = osThreadNew(GUITask, nullptr, &GUITask_attributes);
  7. KeyTaskHandle = osThreadNew(KeyTask, nullptr, &KeyTask_attributes);

提示:建议使用CMSIS-RTOS或CMSIS-RTOS2的API,而非FreeRTOS自带的API,因为前者是统一的标准,即便后续你换了RTOS,也仍可继续使用。

4,进不去任何任务包括中断

如果你一直卡在

prvCheckTasksWaitingTermination、portTASK_FUNCTION之类的函数

        可能是你把某个任务的栈大小分配得太小了,调大一点就可以了

5,SPI_Flash卡死在等待

在等待函数里加个临界区保护代码就可以了

  1. inline void SPI_FLASH_WaitForWriteEnd() // 等待写完成
  2. {
  3. uint8_t FLASH_Status;
  4. SPI_FLASH_CS_LOW();
  5. SPI_FLASH_SendByte(ReadStatusReg); /* 发送 读状态寄存器 命令 */
  6. do
  7. {
  8. taskENTER_CRITICAL();
  9. FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
  10. taskEXIT_CRITICAL();
  11. } while (FLASH_Status & WIP_Flag);// 检测BUSY位是否为0,0表示已完成不忙
  12. SPI_FLASH_CS_HIGH();
  13. }

6,初始化卡住

        这是相当神奇的一幕,那就是理论上还没有执行到osKernelInitialize();,可却已经以时间片轮询的方式进行了。所以会发生一些十分诡异的情况,比如在前面的一些基础GPIO的初始化,可能会与后面的App_Init中如IIC的GPIO初始化产生冲突,有时候HAL_Delay也会卡住。

        为了避免这种情况发生,可以在需要提前做的部分加上临界区加以保护

  1. #include "Application_Logic.h"
  2. #include "FreeRTOS.h"
  3. #include "baseInit.h"
  4. #include "cmsis_os2.h"
  5. #include "task.h"
  6. int main() {
  7. taskENTER_CRITICAL();//临界区
  8. HAL_Init();
  9. SystemClock_Config();
  10. Base_Founction_Init();
  11. taskEXIT_CRITICAL();//临界区
  12. /*FreeRTOS初始化*/
  13. osKernelInitialize();
  14. App_Init();
  15. osKernelStart();
  16. while (1)
  17. ;
  18. }

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号