Bootstrap

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

MCU:STM32F407VET6

开发环境: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

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.huser.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了,而是MyTaskMyRTOS,前者侧重开发任务函数,后者侧重任务的初始化任务调度以外设配置

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

        其三,面向对象开发。下面是以前裸机开发时创建的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)
        ;
}

;