FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)

记录一下一个实际项目由裸机程序改成FreeRTOS,以前产品的平台还是C8051单片机上面的程序,
硬件平台改成了STM32L051,同时使用STM32CubeMX生成的工程,使用FreeRTOS系统
..EEPROM数据存储读取函数修改更新											2021/12/28
..测试,修改,再测试修改...												2021/12/29

目录

  • 一、裸机程序到FreeRTOS概述
  • 二、移植过程
    • 2.1 基本框架搭建
    • 2.2 代码修改
      • 2.2.1 按键任务(使用状态机)
      • 2.2.3 数据存储(使用STM32L051内置EEPROM保存数据)
      • 2.2.3 报文接收任务(使用消息队列接收)
      • 2.2.4 学习清除任务(使用任务通知接收执行)
      • 2.2.5 数据处理函数
      • 2.2.6 延时执行操作(使用软件定时器)
      • 2.2.7 设备状态上报任务(使用任务通知接收执行)
      • 2.2.8 延时函数!!!
  • 三、总结体会
    • 3.1 样例最终情况
    • 3.2 一些体会

对于裸机向FreeRTOS的转变,简单可以用下图表示:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第1张图片

我们前面的文章介绍过的FreeRTOS的任务原理,调度机制,这篇文章只做移植记录。

一、裸机程序到FreeRTOS概述

裸机到FreeRTOS的转变:

  • 该写的驱动还是要写,如果平台一样是可以直接用裸机中的。
    比如工程中的按键驱动mod_button.c
    按键驱动程序源码请参考我的另一篇博文:几个实用的按键驱动

  • 以前 驱动 或 函数 中的 ”干等“”的延时函数,不是中断中调用的情况下是可以直接改成·osDelay(ms 延时函数),us 的延时函数(I2C协议中使用的),可以沿用以前的。(STM32CubeMX 下并没有现成的 us 延时函数,可以自己写一个简单的);
    .
    FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第2张图片.
    上图为温湿度读取的函数,可以看到修改了多种不同的延时函数,因为用在了不同的平台上面;
    .
    FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第3张图片
    上图为 32Mhz 主频下面的 不准确 us 延时函数。

  • 以前 驱动 或 函数 中有些也是 轮询方式设计的,如果使用了 FreeRTOS 的一些信号量,任务通知,消息队列等可以实现 唤醒触发任务方式的机制,需要稍作修改。
    .
    FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第4张图片
    上图中判断是否接收到报文的语句if(Read_pt != Enocean_Data)是裸机中用来判断串口是否开始接收到了数据,接收到了,立即 干等 一段时间HAL_Delay(7);但是在FreeRTOS 中不需要这样的干等。

  • 以前的全局变量,为了修改少,移植简单,可以沿用以前的全局变量,包括一些标志位
    .
    FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第5张图片

  • 以前的 中断 ,不直接涉及到任务的,该怎样还是怎样:
    .
    FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第6张图片
    如果直接和任务有关的,需要做一定的修改, 比如 定时器定时任务 与 事件组有关:
    .
    FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第7张图片
    再比如串口接收中断 使用 消息队列接收数据:
    .FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第8张图片

  • 考虑到 STM32L051 的 ram 空间只有 8KB,还需要时刻关注着内存使用情况!!!

二、移植过程

2.1 基本框架搭建

1、首先在STM32CubeMX中根据原理图把基础的东西设置好,本测试的芯片是STM32L051C8T6,可以参考博文:

STM32L051测试 (一、使用CubeMX生成工程文件)

在window下使用VScode搭建ARM开发环境——手把手教学详细版
FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第9张图片

2、然后在FreeRTOS配置中,新建3个任务 和 一个消息队列,可参考博文:

FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)

FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)
.
FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第10张图片

3、添加基本的代码,添加以前写好的驱动文件,每个驱动拿过来记得在Makefile中添加一下,然后每一步编译保证没有错误(此部分是最初的源码,后面还需要补充,还会对每个细节进行说明):

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第11张图片

4、添加FreeRTOS相关基本代码,直接上源码(此部分是最初的源码,后面还需要补充,还会对每个细节进行说明):

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */     
#include "stdio.h"
#include "Enocean.h"
#include "mod_button.h"
#include 
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
BTN_STRUCT	LEARN_BUTTON_2S ={ Lrn_Key_GPIO_Port,	
            Lrn_Key_Pin, // Pin
						BTN_ACTIVE,			                  // Active state
						2000,		                          // Time the button has to be in 'Active state' to be recognized as pressed
						100 };

BTN_STRUCT	LEARN_BUTTON_150mS ={ Lrn_Key_GPIO_Port,	
            Lrn_Key_Pin, // Pin
						BTN_ACTIVE,			                  // Active state
						150,		                          // Time the button has to be in 'Active state' to be recognized as pressed
						100 };		

BTN_STRUCT	CLEAR_BUTTON_2S = { Clr_Key_GPIO_Port,	
            Clr_Key_Pin, // Pin
						BTN_ACTIVE,			                  // Active state
						2000,		                          //Time the button has to be in 'Active state' to be recognized as pressed
						100	 };	
BTN_STRUCT	CLEAR_BUTTON_150mS = { Clr_Key_GPIO_Port,	
            Clr_Key_Pin, // Pin
						BTN_ACTIVE,			                  // Active state
						150,		                          //Time the button has to be in 'Active state' to be recognized as pressed
						100	 };	

BTN_STRUCT	CLEAR_BUTTON_5S = { Clr_Key_GPIO_Port,	
            Clr_Key_Pin, // Pin
						BTN_ACTIVE,			                  // Active state
						4500,		                          // Time the button has to be in 'Active state' to be recognized as pressed
						100	 };	

BTN_STRUCT	LEARN_BUTTON_5S = { Lrn_Key_GPIO_Port,	
            Lrn_Key_Pin, // Pin
						BTN_ACTIVE,			                  // Active state
						5000,		                          // Time the button has to be in 'Active state' to be recognized as pressed
						100	 };	
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */

/* USER CODE END Variables */
osThreadId KeyTaskHandle;
osThreadId LEDTaskHandle;
osThreadId enoecanreceivedHandle;
osMessageQId EnoceanQueueHandle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
void Relay_On(uint8_t ch)
{
  if(ch == 1){
    HAL_GPIO_WritePin(Relay1_Control_GPIO_Port,Relay1_Control_Pin,GPIO_PIN_SET);
    osDelay(20);
    HAL_GPIO_WritePin(Relay1_Control_GPIO_Port,Relay1_Control_Pin,GPIO_PIN_RESET);
  }
  else if(ch == 2){
    HAL_GPIO_WritePin(Relay2_Control_GPIO_Port,Relay2_Control_Pin,GPIO_PIN_SET);
    osDelay(20);
    HAL_GPIO_WritePin(Relay2_Control_GPIO_Port,Relay2_Control_Pin,GPIO_PIN_RESET);
  }
}

void Relay_Off(uint8_t ch)
{
  if(ch == 1){
    HAL_GPIO_WritePin(Relay1_Close_GPIO_Port,Relay1_Close_Pin,GPIO_PIN_SET);
    osDelay(20);
    HAL_GPIO_WritePin(Relay1_Close_GPIO_Port,Relay1_Close_Pin,GPIO_PIN_RESET);
  }
  else if(ch == 2){
    HAL_GPIO_WritePin(Relay2_Close_GPIO_Port,Relay2_Close_Pin,GPIO_PIN_SET);
    osDelay(20);
    HAL_GPIO_WritePin(Relay2_Close_GPIO_Port,Relay2_Close_Pin,GPIO_PIN_RESET);
  }
} 

/* USER CODE END FunctionPrototypes */

void StartKeyTask(void const * argument);
void StartLEDTask(void const * argument);
void StartenoecanTask(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );

/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
  
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
  *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
  *ppxIdleTaskStackBuffer = &xIdleStack[0];
  *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
  /* place for user code */
}                   
/* USER CODE END GET_IDLE_TASK_MEMORY */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
  printf("Free RTOS init! \r\n");  //for printf test
  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  
  /* USER CODE END RTOS_TIMERS */

  /* Create the queue(s) */
  /* definition and creation of EnoceanQueue */
  osMessageQDef(EnoceanQueue, 50, uint8_t);
  EnoceanQueueHandle = osMessageCreate(osMessageQ(EnoceanQueue), NULL);

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of KeyTask */
  osThreadDef(KeyTask, StartKeyTask, osPriorityAboveNormal, 0, 300);
  KeyTaskHandle = osThreadCreate(osThread(KeyTask), NULL);

  /* definition and creation of LEDTask */
  osThreadDef(LEDTask, StartLEDTask, osPriorityLow, 0, 64);
  LEDTaskHandle = osThreadCreate(osThread(LEDTask), NULL);

  /* definition and creation of enoecanreceived */
  osThreadDef(enoecanreceived, StartenoecanTask, osPriorityHigh, 0, 192);
  enoecanreceivedHandle = osThreadCreate(osThread(enoecanreceived), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE); //

  COMMAND_GetmoduleID(); 
  /* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartKeyTask */
/**
  * @brief  Function implementing the KeyTask thread.
  * @param  argument: Not used 
  * @retval None
  */
/* USER CODE END Header_StartKeyTask */
void StartKeyTask(void const * argument)
{
  /* USER CODE BEGIN StartKeyTask */
  /* Infinite loop */
  for(;;)
  {
    if(btn_getState(&LEARN_BUTTON_150mS) == BTN_EDGE2){
        taskENTER_CRITICAL();
        printf("K1 kicked !!,BIT_KEY set!\r\n");
        taskEXIT_CRITICAL();

    }

    if((btn_getState(&LEARN_BUTTON_2S) == BTN_PRESSED)){  
        taskENTER_CRITICAL();
        printf("K1 pushed 2S!!,send enocean!\r\n");
        taskEXIT_CRITICAL();
        // osThreadSuspendAll();
        // SendLrnTelegram();
        // osThreadResumeAll();
        while(btn_getState(&LEARN_BUTTON_150mS));
    }

    if(btn_getState(&CLEAR_BUTTON_150mS) == BTN_EDGE2){
        taskENTER_CRITICAL();
        printf("K2 pushed!!\r\n");        
        printf("==================================\r\n");
        printf("任务名          任务状态    优先级     剩余栈   任务序号\r\n");
        taskEXIT_CRITICAL();
        uint8_t mytaskstatebuffer[500];
        osThreadList((uint8_t *)&mytaskstatebuffer);
        taskENTER_CRITICAL();
        printf("%s\r\n",mytaskstatebuffer);
        taskEXIT_CRITICAL();
    }

    if((btn_getState(&CLEAR_BUTTON_5S) == BTN_PRESSED)){  
        taskENTER_CRITICAL();
        printf("Clear pushed 5S!!\r\n");
        taskEXIT_CRITICAL();
        // osThreadSuspendAll();
        // SendLrnTelegram();
        // osThreadResumeAll();
        while(btn_getState(&CLEAR_BUTTON_2S));
        while(btn_getState(&CLEAR_BUTTON_150mS));
    }
    osDelay(1);
  }
  /* USER CODE END StartKeyTask */
}

/* USER CODE BEGIN Header_StartLEDTask */
/**
* @brief Function implementing the LEDTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartLEDTask */
void StartLEDTask(void const * argument)
{
  /* USER CODE BEGIN StartLEDTask */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_TogglePin(Clr_Led_GPIO_Port,Clr_Led_Pin);
    osDelay(500);
    HAL_GPIO_TogglePin(Lrn_Led_GPIO_Port,Lrn_Led_Pin);
    osDelay(500);
  }
  /* USER CODE END StartLEDTask */
}

/* USER CODE BEGIN Header_StartenoecanTask */
/**
* @brief Function implementing the enoecanreceived thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartenoecanTask */
void StartenoecanTask(void const * argument)
{
  /* USER CODE BEGIN StartenoecanTask */
  TEL_RADIO_TYPE	rTel;
  TEL_PARAM_TYPE  pTel;
  /* Infinite loop */
  for(;;)
  {
    if(xQueueReceive(EnoceanQueueHandle,&USART_Enocean_BUF[Enocean_Data++],portMAX_DELAY) == pdPASS){
      while(xQueueReceive(EnoceanQueueHandle,&USART_Enocean_BUF[Enocean_Data++],10));
      Enocean_Data -= 1;
      if(Enocean_Data >= 38){
        Getmodule_ID(&u32MyId);
        taskENTER_CRITICAL();
        printf("my ID is :0x %x\r\n",(unsigned int)(long)u32MyId);
        taskEXIT_CRITICAL();
      }
      // HAL_UART_Transmit(&huart1,USART_Enocean_BUF, Enocean_Data,0xFFFF); //将串口3接收到的数据通过串口1传出 
      else if(radio_getTelegram(&rTel,&pTel) == OK){
          if((rTel.trps.u8Choice == RADIO_CHOICE_RPS)||(rTel.trps.u8Choice == RADIO_CHOICE_1BS)){
            taskENTER_CRITICAL();
            printf("rps/1bs received!!BIT_Radio set!\r\n");
            taskEXIT_CRITICAL();
          }
          else if(rTel.trps.u8Choice == RADIO_CHOICE_4BS){
            taskENTER_CRITICAL();
            printf("4bs received\r\n"); 
            taskEXIT_CRITICAL();
          }      
      }
      memset(USART_Enocean_BUF,0,sizeof(USART_Enocean_BUF));
      Enocean_Data=0;
    }
    osDelay(1);
  }
  /* USER CODE END StartenoecanTask */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
     
/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

2.2 代码修改

在完成上述基本移植的基础上,需要针对实际需要的功能 对 相应的代码进行修改.

2.2.1 按键任务(使用状态机)

首先修改的是按键部分,按键虽然有不同的情况,但我这里都放在同一个任务中,对以前的按键操作,基本上是实现了相同的效果,其中还有一个按键是查看所有任务的运气情况和任务栈使用情况,前面的FreeRTOS记录博文中也有如何实现:

void StartKeyTask(void const * argument)
{
  /* USER CODE BEGIN StartKeyTask */
  uint8 um;
  /* Infinite loop */
  for(;;)
  {      
    if(btn_getState(&LEARN_BUTTON_150mS) == BTN_EDGE2)
    {
      /*in normol work mode,when lrn key kicked,change the work mode (1~3 mode loop )*/
      if((bSettingmodeOn == FALSE)&&(bLearnModeOn == FALSE)&&(bCLEARModeOn == FALSE))
      {
        CurrentOpeartion_Mode++;
        if(CurrentOpeartion_Mode == 4)
          CurrentOpeartion_Mode = 1;
        LED_Flash_Times('L',CurrentOpeartion_Mode,0);
        taskENTER_CRITICAL();
        printf("lrn kicked !!,change the work mode!\r\n");
        taskEXIT_CRITICAL();
        // MyData.MODENUM = CurrentOpeartion_Mode;
        // mem_writeFlash((uint8 xdata *)&MyData,APP_FLASH_USER_TABLE,sizeof(MyData));
        // rTel_State.t4bs.u8Data0 |= 0x08;                                     
        // rTel_State.t4bs.u8Data0 &= 0x8F;
        // rTel_State.t4bs.u8Data0 |= (CurrentOpeartion_Mode<<4);              
        // radio_sendTelegram(&rTel_State, &pTel_State);   
      }
      /*in learn mode,when lrn key kicked,change the learn channal*/
      else if(Learn_Channel1_Flag == 1){       
        for(um=0;um<2;um++)
        {
          LRN_LED_OFF;
          osDelay(100);
          LRN_LED_ON;
          osDelay(100);
        }
        taskENTER_CRITICAL();
        printf("next chanle learn!\r\n");
        taskEXIT_CRITICAL();
        Learn_Channel1_Flag = 0;
        Learn_Channel2_Flag = 1; 
      }
      /*in set mode,when lrn key kicked,change the parameter(1~3 parameter loop)*/
      else if(bSettingmodeOn == TRUE){
        parameter++;
        if(parameter == 4){parameter = 1;}
        LED_Flash_Times('L',parameter,bSettingmodeOn);
        taskENTER_CRITICAL();
        printf("parameter %d set!\r\n",parameter);
        taskEXIT_CRITICAL();
      }
    }

    if((btn_getState(&LEARN_BUTTON_2S) == BTN_PRESSED)){
      /*in normol work mode,when lrn key pressed 2s,get into the learn mode*/ 
      if((clrvalue == GPIO_PIN_RESET)&&(bSettingmodeOn == FALSE)&&(bLearnModeOn == FALSE)&&(bCLEARModeOn == FALSE)){
        bSettingmodeOn = TRUE;
        for(um=0;um<6;um++)
        {
          LRN_LED_ON;CLR_LED_ON;
          osDelay(50);
          LRN_LED_OFF;CLR_LED_OFF;
          osDelay(50);
        }
        LRN_LED_ON;CLR_LED_ON;
        taskENTER_CRITICAL();
        printf("Clr and lrn pushed 5S!!,into set mode!\r\n");
        taskEXIT_CRITICAL();
      }
      /*in normol work mode,when lrn and clr key pressed 2s together,get into the set mode*/ 
      else if((clrvalue == GPIO_PIN_SET)&&(bSettingmodeOn == FALSE)){
        bLearnModeOn = !bLearnModeOn;
        if(bCLEARModeOn == TRUE)
        {
          bCLEARModeOn = FALSE;
          // Clear_Channel1_Flag = 0;  
          // Clear_Channel2_Flag = 0;  
          CLR_LED_OFF;
        }
        if (bLearnModeOn){
          ModePriority_Flag = 0;
          // radio_sendTelegram(&rTel_State, &pTel_State);
          for(um=0;um<8;um++)
          {
            LRN_LED_ON;
            osDelay(50);
            LRN_LED_OFF;
            osDelay(50);
          }
          LRN_LED_ON;
          Learn_Channel1_Flag = 1;
        }
        else
        {
          LRN_LED_OFF;
          Learn_Channel2_Flag = 0;                          
          ModePriority_Flag = 1;
        }
        taskENTER_CRITICAL();
        printf("lrn pushed 2S!!,into or out learn mode!\r\n");
        taskEXIT_CRITICAL();
      }
      while(btn_getState(&LEARN_BUTTON_150mS));
      while(btn_getState(&CLEAR_BUTTON_150mS));
    }

    if(btn_getState(&CLEAR_BUTTON_150mS) == BTN_EDGE2){
      /*in normol work mode,when clr key kicked,send one learn Telegram*/
      if(bSettingmodeOn == FALSE){
        taskENTER_CRITICAL();
        printf("K2 pushed!!,send lrn radio...\r\n");        
        printf("==================================\r\n");
        printf("任务名          任务状态    优先级     剩余栈   任务序号\r\n");
        taskEXIT_CRITICAL();
        uint8_t mytaskstatebuffer[500];
        osThreadList((uint8_t *)&mytaskstatebuffer);
        taskENTER_CRITICAL();
        printf("%s\r\n",mytaskstatebuffer);
        taskEXIT_CRITICAL();
        SendLrnTelegram();
      }
      /*in set mode,when clr key kicked,change the parameter value*/
      else if(bSettingmodeOn == TRUE){
        if(parameter == 1){
          Tcnt++;
            if(Tcnt == 7)
          Tcnt = 1;
          LED_Flash_Times('C',Tcnt,bSettingmodeOn);
          // MyData.DelayTimeParameter = Tcnt;	   //LightParameter;
          // mem_writeFlash((uint8 xdata *)&MyData,APP_FLASH_USER_TABLE,sizeof(MyData));
			
          switch(Tcnt){
          case 1: 
            ONTime_Delay = 60000;	   //delay 1min
            break;
          case 2: 
            ONTime_Delay = 300000;	   //delay 5min
            break;
          case 3: 
            ONTime_Delay = 600000;	   //delay 10min
            break;
          case 4: 
            ONTime_Delay = 900000;	   //delay 15min
            break;
          case 5: 
            ONTime_Delay = 1200000;    //delay 20min
            break;
          case 6: 
            ONTime_Delay = 1800000;    //delay 30min
            break;
          }
        }
        else if(parameter == 2){
          Lcnt++;
            if(Lcnt == 7)
          Lcnt = 1;
          LED_Flash_Times('C',Lcnt,bSettingmodeOn);
          // MyData.LightParameter = Lcnt; 
          // mem_writeFlash((uint8 xdata *)&MyData,APP_FLASH_USER_TABLE,sizeof(MyData));
	
			    switch(Lcnt){
          case 1: 
            Lux_Threshold = 50; 	 //Lighter	_  50lux 
            break;
          case 2: 
            Lux_Threshold = 100;	 //
            break;
          case 3: 
            Lux_Threshold = 150;	 //
            break;
          case 4: 
            Lux_Threshold = 200;	 //
            break;
          case 5: 
            Lux_Threshold = 300;	//
            break;
          case 6: 
            Lux_Threshold = 500;	//
            break;
          }
			  }	
			  else if(parameter == 3){
          Rcnt++;
            if(Rcnt == 4)
          Rcnt = 1;
          LED_Flash_Times('C',Rcnt,bSettingmodeOn);
          // MyData.RepeaterParameter = Rcnt; 
          // mem_writeFlash((uint8 xdata *)&MyData,APP_FLASH_USER_TABLE,sizeof(MyData));

          switch(Rcnt){
          case 1:
            REPEATER_OFF();
            break;
          case 2: 
            REPEATER_ONE_ON();
            break;
          case 3:
            REPEATER_TWO_ON();
            break;   
          }
        }
      }
    }

    if((btn_getState(&CLEAR_BUTTON_5S) == BTN_PRESSED)){
        if(bSettingmodeOn == FALSE){
			    if(bLearnModeOn == TRUE)
			    {
            bLearnModeOn = FALSE;
            LRN_LED_OFF;
			    }
				// ClearALLLearnedID(smSensors1,APP_FLASH_CHANNEL1_TABLE,MAX_SMACK_SENSORS,CHANNEL1);
				// ClearALLLearnedID(smSensors2,APP_FLASH_CHANNEL2_TABLE,MAX_SMACK_SENSORS,CHANNEL2);
          LED_Flash_Times('C',5,0);
          bCLEARModeOn = FALSE;
				}
        else if(bSettingmodeOn == TRUE){  //在参数设置模式下,按CLEAR是退出设置模式
			  	for(um=0;um<6;um++)
          {
            LRN_LED_ON;CLR_LED_ON;
            osDelay(50);
            LRN_LED_OFF;CLR_LED_OFF;
            osDelay(50);
          }
          // CLEAR_UART_Buffer(RX,RX_Buffer);
          //Globle_Int_EN(1);
          bSettingmodeOn = FALSE;
        }
        taskENTER_CRITICAL();
        printf("Clear pushed 5S!!,clear all ID or exit setting mode! ...\r\n");
        taskEXIT_CRITICAL();
        while(btn_getState(&CLEAR_BUTTON_2S));
        while(btn_getState(&CLEAR_BUTTON_150mS));
    }
    osDelay(1);
  }
  /* USER CODE END StartKeyTask */
}

时刻关注着 内存使用情况:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第12张图片

2.2.3 数据存储(使用STM32L051内置EEPROM保存数据)

因为产品在使用过程中,有一些数据是需要掉电保存的,所以需要使用到 EEPROM,当然 内部的 Flash 也是可以用来保存的,只不过对于 STM32L051 而言,有内置的 EEPROM ,使用起来更加方便,如果平台是 STM32F103系列,只能使用内部的Flash 或者外接 EEPROM 了。

对于 STM32L051 的使用说明,可以参考我的另外一篇博文:
STM32L051测试 (四、Flash和EEPROM的读写)

需要保存一些参数数据,和无线设备ID等参数数据,还考考虑2个通道,stml0_flash.h文件中一些宏定义如下:


/*
two relay_control
*/
#define CHANNEL1_ADDR_BASE    0x08080000
#define CH1_CHANNEL1_ADDR     0x08080000 + 0				
#define CH2_CHANNEL1_ADDR     0x08080000 + 10
#define CH3_CHANNEL1_ADDR     0x08080000 + 20
#define CH4_CHANNEL1_ADDR     0x08080000 + 30
#define CH5_CHANNEL1_ADDR     0x08080000 + 40
#define CH6_CHANNEL1_ADDR     0x08080000 + 50
#define CH7_CHANNEL1_ADDR     0x08080000 + 60 
#define CH8_CHANNEL1_ADDR     0x08080000 + 70 

#define CHANNEL2_ADDR_BASE    0x08080000 + EEPROM_PAGE_SIZE
#define CH1_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 0				
#define CH2_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 10
#define CH3_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 20
#define CH4_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 30
#define CH5_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 40
#define CH6_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 50
#define CH7_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 60 
#define CH8_CHANNEL2_ADDR     0x08080000 + EEPROM_PAGE_SIZE + 70 


#define User_Data_ADDR  DATA_EEPROM_END_ADDR - 100

#define CurChannelNums    2 
#define NO_SENSOR_ID	0x00000000  //因为L051 EEPROM 初始化是0
#define DefaultVal      0x00
#define MAX_SMACK_SENSORS  8

/* 4 + 1+ 1+ 1+ 1 = 8 考虑蓝牙 +2  10 */
typedef struct
{
	uint32 	u32SensorId;
	uint8 	RORG;
	uint8 	FUNC;
	uint8 	TYPE;
	uint8	u8LearnSN;	
}__attribute__ ((packed)) LEARNED_SENSORS;

/* 放在EEPROM 最后的地方  留 10 个字节*/
typedef struct                    
{
	uint8 	MODENUM;
	uint8	LightParameter;
	uint8   DelayTimeParameter;
	uint8	RepeaterParameter;
	uint8 	AutoAndManual_MODE[CurChannelNums];
	uint8	IDNUM[CurChannelNums];      		
}__attribute__ ((packed)) User_Data;

extern User_Data MyData;
extern LEARNED_SENSORS smSensors_ch1[8];
extern LEARNED_SENSORS smSensors_ch2[8];

typedef enum                    
{
	CHANNEL1 = 0,
	CHANNEL2,
} CHANNEL_ID;


然后在stml0_flash.c文件中,加入以下相关的函数,下面是清除和读取函数(下面的写ID函数其实有问题,每次写的都是结构体指针的第一个元素的值):

/*
  双路执行器操作
*/
/*
  HAL_FLASHEx_DATAEEPROM_Erase(uint32_t Address)
  0-80
  EEPROM_PAGE_SIZE + 80
  全部清除
*/
void EnoceanID_ErasePage()
{
  u8 i = 0;
	HAL_FLASHEx_DATAEEPROM_Unlock(); 
  for(i=0; i<21; i++){
    HAL_FLASHEx_DATAEEPROM_Erase(CH1_CHANNEL1_ADDR + 4*i); 
    HAL_FLASHEx_DATAEEPROM_Erase(CH1_CHANNEL2_ADDR + 4*i);
  }
	HAL_FLASHEx_DATAEEPROM_Lock();
}

/* 固定地址,少传参数 */
User_Data Set_User_DataRead()
{
	User_Data uservalue;
  uservalue.MODENUM                 = FLASH_Readbyte((uint32_t)User_Data_ADDR);
  uservalue.LightParameter          = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 1));
  uservalue.DelayTimeParameter      = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 2));
  uservalue.RepeaterParameter       = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 3));
  uservalue.AutoAndManual_MODE[0]   = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 4));
  uservalue.AutoAndManual_MODE[1]   = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 5));
  uservalue.IDNUM[0]                = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 6));
  uservalue.IDNUM[1]                = FLASH_Readbyte((uint32_t)(User_Data_ADDR + 7));

  return uservalue;
}

LEARNED_SENSORS channel_DataRead(uint32_t address)
{
  LEARNED_SENSORS channelvalue;
  channelvalue.u32SensorId = FLASH_ReadWord(address);
  channelvalue.RORG        = FLASH_Readbyte(address + 4);
  channelvalue.FUNC        = FLASH_Readbyte(address + 5);
  channelvalue.TYPE        = FLASH_Readbyte(address + 6);
  channelvalue.u8LearnSN   = FLASH_Readbyte(address + 7);
  return channelvalue;
}

void channel_all_dataRead(LEARNED_SENSORS *smSensors,CHANNEL_ID ChannelNum){
  uint8 u8Count;
  for(u8Count=0;u8Count<8;u8Count++){
    if(ChannelNum == 0){
      smSensors[u8Count] = channel_DataRead(CHANNEL1_ADDR_BASE + u8Count*10);
    }
    else if(ChannelNum == 1){
      smSensors[u8Count] = channel_DataRead(CHANNEL2_ADDR_BASE + u8Count*10);
    }
  }
}


void  FLASH_WriteParameter(User_Data * mydata)
{
	u8 i = 0;
  u8 writedata[8]={0};
  writedata[0] = mydata->MODENUM;
  writedata[1] = mydata->LightParameter;
  writedata[2] = mydata->DelayTimeParameter;
  writedata[3] = mydata->RepeaterParameter;
  writedata[4] = mydata->AutoAndManual_MODE[0];
  writedata[5] = mydata->AutoAndManual_MODE[1];
  writedata[6] = mydata->IDNUM[0];
  writedata[7] = mydata->IDNUM[1];
  HAL_FLASHEx_DATAEEPROM_Unlock(); 
  for(i=0; i<8; i++){
    while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, User_Data_ADDR + i, writedata[i]) != HAL_OK);
  }
	HAL_FLASHEx_DATAEEPROM_Lock();
}

/*
void  FLASH_WriteSensorID(LEARNED_SENSORS * mysensors,uint8 num, CHANNEL_ID  ChannelNum)
{
  uint32 sensorid;
  uint8  sensorvalue[4];
  u8 i = 0;
  sensorid        = mysensors->u32SensorId;
  sensorvalue[0]  = mysensors->RORG;
  sensorvalue[1]  = mysensors->FUNC;
  sensorvalue[2]  = mysensors->TYPE;
  sensorvalue[3]  = mysensors->u8LearnSN;
  HAL_FLASHEx_DATAEEPROM_Unlock(); //!!!问题所在!!!
  if(ChannelNum == 0){
    switch (num)
    {
    case 0:
      FLASH_WriteWord(CH1_CHANNEL1_ADDR,sensorid);//!!!问题所在!!!这里会锁EEPROM
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH1_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
   */
      }
void  FLASH_WriteSensorID(LEARNED_SENSORS *mysensors,uint8 num, CHANNEL_ID  ChannelNum)
{

  uint32 sensorid;
  uint8  sensorvalue[4] = {0};
  u8 i = 0;
  sensorid        = mysensors->u32SensorId;
  sensorvalue[0]  = mysensors->RORG;
  sensorvalue[1]  = mysensors->FUNC;
  sensorvalue[2]  = mysensors->TYPE;
  sensorvalue[3]  = mysensors->u8LearnSN;

  if(ChannelNum == 0){
    switch (num)
    {
    case 0:
      FLASH_WriteWord(CH1_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){        
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH1_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 1:
      FLASH_WriteWord(CH2_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH2_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      } 
      break;
    case 2:
      FLASH_WriteWord(CH3_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH3_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      } 
      break;
    case 3:
      FLASH_WriteWord(CH4_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH4_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 4:
      FLASH_WriteWord(CH5_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH5_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 5:
      FLASH_WriteWord(CH6_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH6_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 6:
      FLASH_WriteWord(CH7_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH7_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 7:
      FLASH_WriteWord(CH8_CHANNEL1_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH8_CHANNEL1_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break; 
    default:
      break;
    }
  }
  else if(ChannelNum == 1){
    switch (num)
    {
    case 0:
      FLASH_WriteWord(CH1_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH1_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 1:
      FLASH_WriteWord(CH2_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH2_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 2:
      FLASH_WriteWord(CH3_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH3_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 3:
      FLASH_WriteWord(CH4_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH4_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 4:
      FLASH_WriteWord(CH5_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH5_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 5:
      FLASH_WriteWord(CH6_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH6_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 6:
      FLASH_WriteWord(CH7_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH7_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break;
    case 7:
      FLASH_WriteWord(CH8_CHANNEL2_ADDR,sensorid);
      HAL_FLASHEx_DATAEEPROM_Unlock(); 
      for(i=0; i<4; i++){
        while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, CH8_CHANNEL2_ADDR + 4 + i, sensorvalue[i]) != HAL_OK);
      }
      break; 
    default:
      break;
    }
  }
  HAL_FLASHEx_DATAEEPROM_Lock();
}


需要定义3个全局变量,保存 参数配置 和 ID数据,虽然数据存放在 EEPROM 中,但是并不需要 每次比较都从 EEPROM 中读取(如果是Flash读取相对来说,次数少,时间长,影响效率),
所以只需要上电读取出来一次数据,放到全局变量中,然后比较的时候直接和全局变量比较,如果进行了参数设计,修改了 EERPOM 的数据,随时更新一下 全局变量的数值,这几个全局变量 无法避免的需要占用 RAM 空间:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第13张图片

修改到现在,还是感慨一下,本来是为了减少代码修改量,直接复制以前的代码,尽可能的不修改框架,但是以前是基于 51 单片机,对于数据存储和 STM32 大不一样,所以对于数据存储,包括后面的学习来说和以前代码的兼容性,太费心了,还不如直接按照自己的思路重新设计一下。

时刻关注着 内存使用情况:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第14张图片

对于上面的写ID函数
void FLASH_WriteSensorID(LEARNED_SENSORS * mysensors,uint8 num, CHANNEL_ID ChannelNum)
因为程序中定义的其实是一个结构体数组,所以上面的函数在写的时候永远写的是第一个数组第一个结构体的值:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第15张图片
函数没有问题,是在调用的时候,调用出错,正确方式应该是如下方式:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第16张图片

2.2.3 报文接收任务(使用消息队列接收)

报文接收使用消息队列,具体的框架在另一篇博文有提到:
FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)

void StartenoecanTask(void const * argument)
{
  /* USER CODE BEGIN StartenoecanTask */
  uint8 ACK_Val;
  uint32 u32GatewayID = 0;
  /* Infinite loop */
  for(;;)
  {
    if(xQueueReceive(EnoceanQueueHandle,&USART_Enocean_BUF[Enocean_Data++],portMAX_DELAY) == pdPASS){
      while(xQueueReceive(EnoceanQueueHandle,&USART_Enocean_BUF[Enocean_Data++],7));
      if(Enocean_Data > 1)Enocean_Data -= 1;
        // printf("%d\r\n",Enocean_Data);
      if((Enocean_Data >= 38)&&(u32MyId == 0)){
        Getmodule_ID(&u32MyId);
        taskENTER_CRITICAL();
        printf("my ID is :0x %x\r\n",(unsigned int)(long)u32MyId);
        taskEXIT_CRITICAL();
        if(MyData.RepeaterParameter == 0x00);
        else{
          Rcnt = MyData.RepeaterParameter; 
          switch(Rcnt){
          case 1:
            REPEATER_OFF();
            break;
          case 2: 
            REPEATER_ONE_ON();
            break;
          case 3:
            REPEATER_TWO_ON();
            break;
          }
        }
      }  
      else if ((bLearnModeOn == FALSE)&&(bCLEARModeOn == FALSE)&&(bSettingmodeOn == FALSE)){
        if(radio_getTelegram(&rTel,&pTel) == OK){
          if(pTel.p_rx.u32DestinationId != u32MyId){
            taskENTER_CRITICAL();
            printf("receive radio need analysis!!!\r\n");
            taskEXIT_CRITICAL();
            if(MyData.IDNUM[0]){
              ACK_Val = RadioTelegram_PROCESS(smSensors_ch1,MAX_SMACK_SENSORS);
              if(ACK_Val)
              {
                ChannelAct_Process(ACK_Val,CurrentOpeartion_Mode,CHANNEL1);
                CH1_Find = 1;
              }
            }
            if(MyData.IDNUM[1]){
              ACK_Val = RadioTelegram_PROCESS(smSensors_ch2,MAX_SMACK_SENSORS);
              if(ACK_Val)
              {
                ChannelAct_Process(ACK_Val,CurrentOpeartion_Mode,CHANNEL2);
                CH2_Find = 1;
              }
            }

            if((CH1_Find == 1)&&(CH2_Find == 0))
            {
              LED_Flash_Times('L',CHANNEL1+1,bLearnModeOn);
              CH1_Find = 0;
            }
            else if((CH1_Find == 0)&&(CH2_Find == 1))
            {
              LED_Flash_Times('L',CHANNEL2+1,bLearnModeOn);
              CH2_Find = 0;
            }
            else if((CH1_Find == 1)&&(CH2_Find == 1))
            {
              LRN_LED_ON;CLR_LED_ON;
              osDelay(150);
              LRN_LED_OFF;CLR_LED_OFF;
              CH1_Find = 0;
              CH2_Find = 0;
            }
            else
            {
              CH1_Find = 0;
              CH2_Find = 0;
            }      
          }
          else{ //u32GatewayID = rTel.trps.u32Id; 指定地址发送的报文
            taskENTER_CRITICAL();
            printf("this is a radio send to me!! don't care!\r\n");
            taskEXIT_CRITICAL();
            if((rTel.trps.u8Choice == RADIO_CHOICE_RPS)||(rTel.t1bs.u8Choice == RADIO_CHOICE_1BS)){
              
              u32GatewayID = rTel.trps.u32Id;
              if((u32GatewayID >= 0xFF800000)&&(u32GatewayID <= 0xFFFFFF80)){
                ChannelAct_Process(rTel.trps.u8Data,CurrentOpeartion_Mode,CHANNEL1);
                LED_Flash_Times('L',1,bLearnModeOn);
              }
              //4bs的不考虑
            }
          }
        }
      }
      else if(bLearnModeOn == TRUE){ //into learn mode
          taskENTER_CRITICAL();
          printf("ready to learn!!!\r\n");
          taskEXIT_CRITICAL();
        if(radio_getTelegram(&rTel,&pTel) == OK){
          
          if(pTel.p_rx.u32DestinationId != u32MyId){
            if((rTel.t1bs.u8Choice == RADIO_CHOICE_1BS)||(rTel.trps.u8Choice == RADIO_CHOICE_RPS))
						  u32GatewayID = rTel.trps.u32Id;
					  else if(rTel.t4bs.u8Choice == RADIO_CHOICE_4BS)
						  u32GatewayID = rTel.t4bs.u32Id; 
            else {
              taskENTER_CRITICAL();
              printf("cann't learn!!!\r\n");
              taskEXIT_CRITICAL();
              break;
            }
            if((u32GatewayID < 0xFF800000)&&(bLearnModeOn == TRUE)){
              if(Learn_Channel1_Flag == 1){
                taskENTER_CRITICAL();
                printf("ch111 learn!!!\r\n");
                taskEXIT_CRITICAL();
                // osSignalSet(LearnTaskHandle,channel1_learn);
                executeLearn(smSensors_ch1,MAX_SMACK_SENSORS,CHANNEL1);
              }
              else if(Learn_Channel2_Flag == 1){
                taskENTER_CRITICAL();
                printf("ch222 learn!!!\r\n");
                taskEXIT_CRITICAL();
                // osSignalSet(LearnTaskHandle,channel2_learn);
                executeLearn(smSensors_ch2,MAX_SMACK_SENSORS,CHANNEL2); 
              }
            } 
          }   
        }
      }
      memset(USART_Enocean_BUF,0,sizeof(USART_Enocean_BUF));
      Enocean_Data=0;
      // taskENTER_CRITICAL();
      // printf("end!\r\n");
      // taskEXIT_CRITICAL();
    }
    osDelay(5);
  }
  /* USER CODE END StartenoecanTask */
}

使用消息队列接收 串口信息的任务,最初的时候定义的是 100 的缓存,消息队列也是100的大小,在压力测试下面,可能会使得 任务卡死。
现象就是任务永远处理阻塞状态,但是收不到消息了,按键任务正常,所以能够查看此任务的状态。
当然这与程序测试的时候加了printf有关,最终是改成了 200 的缓存,去掉printf,测试还是正常的。

2.2.4 学习清除任务(使用任务通知接收执行)

学习和清除使用任务通知,虽然清除只留下全部清除,简单一个函数可以实现,为了后期扩展,也使用任务通知实现。

说明:最初是计划用任务通知实现 学习清除操作,可实际上最后还是直接在按键任务,和报文接收任务中 直接调用函数的方式实现了 学习清除操作:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第17张图片
FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第18张图片

如果使用任务通知就得确定一个事情,发送任务通知是否会发生任务调度?(因为任务后续代码可能对缓存进行修改,比如清除之类的)

这个我从任务通知的源码里面好像没有找到(也可能是我没有仔细),但是是可以通过测试来确定是否发送任务通知会有任务调度的,具体的方式可以参照:

FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)

上面博文中 2.2 邮箱测试 ------ 2.21 问题测试,不添加临界区与添加临界区来观察是否发送任务通知会和邮箱一样发生任务调度,当然测试的时候需要注意 任务 与 任务之间的优先级问题。

这里就暂时先不做测试,这个移植已经画了太多时间了= =!

这样的话,我就去掉了准备好的这个任务,只留下了2个任务:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第19张图片

时刻关注着 内存使用情况(去掉一个任务后空间多了):

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第20张图片

2.2.5 数据处理函数

因为前面的所有框架,基本是按照以前逻辑的逻辑,以少修改以前代码为前提的,所以数据处理函数依然可以沿用以前的,基本不变,这里直接放一下代码:

uint8 RadioTelegram_PROCESS(LEARNED_SENSORS *smSensors,uint8 MAX_SENSORS_NUMS){
  uint8 u8Count;
	uint16 luxValue = 0;
	for(u8Count=0;u8Count<MAX_SENSORS_NUMS;u8Count++)
  {
		if((smSensors[u8Count].u32SensorId == rTel.trps.u32Id)||(smSensors[u8Count].u32SensorId == rTel.t4bs.u32Id)){
			if((rTel.trps.u8Choice == RADIO_CHOICE_RPS)||(rTel.t1bs.u8Choice == RADIO_CHOICE_1BS)){
				if((CurrentOpeartion_Mode == 1)||(CurrentOpeartion_Mode == 3))
				{
					if((smSensors[u8Count].u8LearnSN == 0x50)||(smSensors[u8Count].u8LearnSN == 0x70))
						Left_Right_Flag = 0;    
					else if((smSensors[u8Count].u8LearnSN == 0x10)||(smSensors[u8Count].u8LearnSN == 0x30))
						Left_Right_Flag = 1;
				}
				return rTel.trps.u8Data;
			}
			if(rTel.t4bs.u8Choice == RADIO_CHOICE_4BS){
				if((smSensors[u8Count].RORG == 0xA5)&&(smSensors[u8Count].FUNC == 0x07)&&(smSensors[u8Count].TYPE == 0x03))
				{
					luxValue = rTel.t4bs.u8Data2;
			     	luxValue = (luxValue<<2)|(rTel.t4bs.u8Data1>>6);
				 	if((luxValue <= Lux_Threshold)&&(rTel.t4bs.u8Data0 &0x80))
					  return  1; 
					else
						return  0;
				}

				if((smSensors[u8Count].RORG == 0xA5)&&(smSensors[u8Count].FUNC == 0x08)&&(smSensors[u8Count].TYPE == 0x02))//A5-08-02
				{
					//luxValue = rTel.t4bs.u8Data2;
					luxValue = ((uint16)rTel.t4bs.u8Data2)*1020/255;								
					if((luxValue <= Lux_Threshold)&&(!(rTel.t4bs.u8Data0 &0x02)))
						return  1;                 
					else                          
						return  0;
		    	}
				
				if((smSensors[u8Count].RORG == 0xA5)&&(smSensors[u8Count].FUNC == 0x07)&&(smSensors[u8Count].TYPE == 0x02))//A5-07-02
				{
					if(rTel.t4bs.u8Data0 &0x80)
					 	return  1;                 
					else                           
						return  0;
		    	}
				
				if((smSensors[u8Count].RORG == 0xA5)&&(smSensors[u8Count].FUNC == 0x08)&&(smSensors[u8Count].TYPE == 0x01))//A5-08-01
				{
					luxValue = ((uint16)rTel.t4bs.u8Data2)*510/255;
					if((luxValue <= Lux_Threshold)&&(!(rTel.t4bs.u8Data0 &0x02)))
					 	return  1;              
					else                  
						return  0;
		    	}
				
				if((smSensors[u8Count].RORG == 0xA5)&&(smSensors[u8Count].FUNC == 0x08)&&(smSensors[u8Count].TYPE == 0x03))//A5-08-03
				{
					luxValue = ((uint16)rTel.t4bs.u8Data2)*1530/255;
					if((luxValue <= Lux_Threshold)&&(!(rTel.t4bs.u8Data0 &0x02)))
					 	return  1;             
					else                          
						return  0;
		    	}
			}
		}
	}
	return 0;
}

2.2.6 延时执行操作(使用软件定时器)

因为有些操作需要用到定时器,所以最后还得使用一下软件定时器,软件定时器的使用参考博文:

FreeRTOS记录(八、FreeRTOS软件定时器)

在STM32cubeMX 中设置:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第21张图片

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第22张图片

然后在程序中需要的地方开启软件定时器:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第23张图片

完善一下 callback 函数(还是为了兼容以前代码,但是实际上可能需要维护一下):

/* myTimerCallback01 function */
void myTimerCallback01(void const * argument)
{
  /* USER CODE BEGIN myTimerCallback01 */
  uint8_t myTimerID;
  uint8_t CH;
  myTimerID = (uint8_t)pvTimerGetTimerID(argument);

  if (myTimerID == time_one){
    for(CH=0;CH<CurChannelNums;CH++){
      if(FirstStart_Flag[CH] == 1){
        if(CH == 0) {
          Relay_Off(1);
          Switch_Flag = 1;
            t4bsu8Data0 &= ~Relay1_OnState;
        }
        if(CH == 1) {
          Relay_Off(2);
          Switch_Flag = 1;
            t4bsu8Data0 &= ~Relay2_OnState;                                                                                                                                                                                                                                                                   
        }
        FirstStart_Flag[CH] = 0;
      }
    }
  }

  /* USER CODE END myTimerCallback01 */
}

时刻关注着 内存使用情况:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第24张图片

2.2.7 设备状态上报任务(使用任务通知接收执行)

在以前的代码中,有一个全局变量 Switch_Flag ,标志着设备的状态发生了变化,如果状态改变,需要发送一次状态报文:

if(Switch_Flag == 1){
	SendConfigInfoTelegram();
 	Switch_Flag = 0;
}

所以新建了一个任务,单独作为发送状态报文:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第25张图片

但是最终以这种方式测试下来,有bug,就是影响接受任务中间的执行操作。

简单来说就是无线控制本来是很丝滑,很立即的,加了这个全局变量的判断,就变得卡卡的!

期初是觉得 Switch_Flag 改变的地方太多了,加任务通知不太适合,但是如果直接这样判断全局变量,显然没法使用,所以还是改为任务通知的方式。

void StartSendconfigTask(void const * argument)
{
  /* USER CODE BEGIN StartSendconfigTask */
  osEvent gettask;
  /* Infinite loop */
  for(;;)
  {
    gettask = osSignalWait(sendconfig,osWaitForever);
    if(gettask.value.v == sendconfig){
      SendConfigInfoTelegram();
    }
    osDelay(1);
  }
  /* USER CODE END StartSendconfigTask */
}

接着修改数据处理函数,因为里面涉及到设备状态的修改:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第26张图片

最后在接收任务中执行完了数据数据函数以后,再进行任务通知的发送才测试正常:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第27张图片

时刻关注着 内存使用情况:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第28张图片

2.2.8 延时函数!!!

延时函数为什么标红色单独拿出来说呢:

对于使用操作系统来说,延时函数使用操作系统的延时函数,可以避免裸机中一个很大的问题,就是干等,所以,在某些情况下不需要考虑,因为延时时间导致其他程序不能够无及时运行的问题!

但是!! 习惯了裸机的逻辑,操作系统的延时函数带来好处的同时,也使得使用起来必须要充分考虑! 延时的时候是会发生任务调度的,如果在某些想不被打断的操作中,千万不要使用延时函数 和 带有延时函数的函数。

在程序中有一个LED灯闪烁的函数:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第29张图片

其中使用了osDelay,然后在学习函数中
void executeLearn(LEARNED_SENSORS *smSensors,uint8 MAX_SENSORS_NUMS,CHANNEL_ID ChannelNum)

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第30张图片

最后修改了一下,把做指示作用的闪灯放在最后:

/*
freertos 进入学习有个问题,就比如一个开关的连续2个报文,在学习第一条的时候,其实第二条已经过来了,第一条没有来得及学完的话,会直接学习第二条
已经解决,因为调用了闪灯函数,里面有延时函数
*/
void executeLearn(LEARNED_SENSORS *smSensors,uint8 MAX_SENSORS_NUMS,CHANNEL_ID ChannelNum)
{
	uint8 u8Count,TypeNums;	
  uint8 FUNC,TYPE,LrnBit_Flag;
  uint16 Manufacturer_ID; 
  for(u8Count=0;u8Count<MAX_SENSORS_NUMS;u8Count++)
  {
    if((smSensors[u8Count].u32SensorId == rTel.trps.u32Id)||(smSensors[u8Count].u32SensorId == rTel.t4bs.u32Id))
    {	
      // printf("%d learned!\r\n",u8Count);
      return;
    }
  }
	//Sensor ID not found so the Application needs to learn it in, proove if it is a correct result, 
	//search for free place in the APP learn table
  for(u8Count=0;u8Count<MAX_SENSORS_NUMS;u8Count++){
    if(smSensors[u8Count].u32SensorId == NO_SENSOR_ID)
      break;	
  }
		//if no free place in APP learn table, discard the learn request, no response time is needed
  if(u8Count == MAX_SENSORS_NUMS){
    LED_Flash_Times('L',4,bLearnModeOn);
    return;
  }

	//there is a free place, learn in the sensor		
  if(rTel.trps.u8Choice == RADIO_CHOICE_RPS){
    smSensors[u8Count].u32SensorId = rTel.trps.u32Id;
    smSensors[u8Count].u8LearnSN = rTel.trps.u8Data;
    // printf("%x\r\n",smSensors[u8Count].u8LearnSN);
  }
  else if(rTel.t1bs.u8Choice == RADIO_CHOICE_1BS)//if 1BS
  {
    if((rTel.t1bs.u8Data&0x08) == 0)
      smSensors[u8Count].u32SensorId = rTel.t1bs.u32Id;
    else
        return;

  }
	else if(rTel.t4bs.u8Choice == RADIO_CHOICE_4BS)    //if 4BS
  {
    if((CurrentOpeartion_Mode == 1)||(CurrentOpeartion_Mode == 3))
      return;
    else{
      FUNC = (rTel.t4bs.u8Data3&0xFC)>>2;
      TYPE = ((rTel.t4bs.u8Data3&0x03)<<5)|((rTel.t4bs.u8Data2&0xF8)>>3);
      Manufacturer_ID = rTel.t4bs.u8Data2&0x07;
      Manufacturer_ID <<= 8;
      Manufacturer_ID |= rTel.t4bs.u8Data1;
      LrnBit_Flag = rTel.t4bs.u8Data0&0x08;
      for(TypeNums=0;TypeNums<EEP_TYPE_Num;TypeNums++){
        if((FUNC == EEP_Info[TypeNums][0])&&(TYPE == EEP_Info[TypeNums][1])&&(LrnBit_Flag == 0))
        {
          smSensors[u8Count].u32SensorId = rTel.t4bs.u32Id;
          smSensors[u8Count].RORG = RADIO_CHOICE_4BS;
          smSensors[u8Count].FUNC = FUNC;
          smSensors[u8Count].TYPE = TYPE;
          break;
        }
      }
      if(TypeNums == EEP_TYPE_Num)
        return;
    }
  }

  MyData.IDNUM[ChannelNum] += 1;
  if(ChannelNum == CHANNEL1)
     t4bsu8Data1 = MyData.IDNUM[ChannelNum];
  if(ChannelNum == CHANNEL2)
     t4bsu8Data2 = MyData.IDNUM[ChannelNum];

  //保存数据需要进入临界区
  taskENTER_CRITICAL(); 
  FLASH_WriteParameter(&MyData);
  // printf("is %d\r\n",u8Count); 
  FLASH_WriteSensorID(&smSensors[u8Count],u8Count,ChannelNum); //保存数据
  channel_dataRead(&smSensors[u8Count],u8Count,ChannelNum); //学习完了更新一下数据,函数用指针操作可能不需要更新直接已经修改了smSensors_ch1
  taskEXIT_CRITICAL();
  memset(USART_Enocean_BUF,0,sizeof(USART_Enocean_BUF)); //串口缓存清除
  Enocean_Data=0;

  LED_Flash_Times('L',2,bLearnModeOn); //问题在于这句话,闪灯会导致任务调度,因为里面有延时函数 !!!

  // Send4BSDataTelegram(&t4bsu8Data3,&t4bsu8Data2,&t4bsu8Data1,&t4bsu8Data0); 
  // mem_writeFlash((uint8 xdata *)&MyData,APP_FLASH_USER_TABLE,sizeof(MyData));//keep
  // mem_writeFlash((uint8 xdata *)smSensors,u16DstAddress,sizeof(LEARNED_SENSORS)*MAX_SMACK_SENSORS);	
  return;
}	

三、总结体会

3.1 样例最终情况

最后整体测试看来没问题版本,编译结果:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第31张图片

在STM32CubeMX中的配置,给与FreeRTOS的内存空间为 3K:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第32张图片

最终剩余情况:

FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第33张图片

当然,这个内存占用还可以减少差不多 0.5k,因为我 打印了任务运行状态,最终在内存够的情况,没有把打印任务状态去掉。

最终修改下来,实际上我只用了3个任务,如果不是为了适配以前的代码框架,重新构建逻辑,可能可以更加合理的分配任务:
FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)_第34张图片

最后整个源码我上传了一份: Relay_Control.zip

3.2 一些体会

1、每个任务的栈大小要根据实际情况调整,任务调用函数深度和变量的多少都直接影响任务栈需要的大小,所以移植过程需要使用 vTaskList 观察任务运行占用内存情况,以便及时调整。 具体可参考博文:

FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)

2、对于全局变量,有些数组可以使用 const 修饰,使其存放至 flash 上,节约 ram 空间。

3、其实最终修改下来,一味的追求最小程序的修改代码,反而适得其反,还是自己按照现在的思路,重新设计一下会来得简单一些

4、对于 EEPROM 的读写函数设计修改,在 结构体,结构体数组 的运用于处理方面,花了一些时间!

EEPROM的读写操作需要添加临界区保护,有可能在写过程发生任务调度导致写读失败!!

5、延时函数要特别注意!!!除了使用临界区保护不想被打断的代码,延时函数和 带有延时的函数 使用考虑周全!

你可能感兴趣的:(FreeRTOS,freertos,stm32)