写点什么

开源一夏 | 一个裸机工程转 FreeRTOS 的实例

作者:矜辰所致
  • 2022 年 8 月 22 日
    江苏
  • 本文字数:24592 字

    阅读完需:约 81 分钟

开源一夏 | 一个裸机工程转FreeRTOS的实例
分享一下一个实际项目由裸机程序改成FreeRTOS,以前产品的平台还是C8051单片机上面的程序,硬件平台改成了STM32L051,同时使用STM32CubeMX生成的工程,使用FreeRTOS系统项目开源     ...... by 矜辰所致
复制代码


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



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

一、裸机程序到 FreeRTOS 概述

裸机到 FreeRTOS 的转变:


  • 该写的驱动还是要写,如果平台一样是可以直接用裸机中的。

比如工程中的按键驱动mod_button.c

按键驱动程序源码请参考我的另一篇博文:几个实用的按键驱动https://xie.infoq.cn/article/e6bc8e57f11b929430823c5c3


  • 以前 驱动 或 函数 中的 ”干等“”的延时函数,不是中断中调用的情况下是可以直接改成·osDelay(ms 延时函数),us 的延时函数(I2C 协议中使用的),可以沿用以前的。(STM32CubeMX 下并没有现成的 us 延时函数,可以自己写一个简单的);

上图为温湿度读取的函数,可以看到修改了多种不同的延时函数,因为用在了不同的平台上面;


上图为 32Mhz 主频下面的 不准确 us 延时函数。

  • 以前 驱动 或 函数 中有些也是 轮询方式设计的,如果使用了 FreeRTOS 的一些信号量,任务通知,消息队列等可以实现 唤醒触发任务方式的机制,需要稍作修改。


上图中判断是否接收到报文的语句if(Read_pt != Enocean_Data)是裸机中用来判断串口是否开始接收到了数据,接收到了,立即 干等 一段时间HAL_Delay(7);但是在 FreeRTOS 中不需要这样的干等。

  • 以前的全局变量,为了修改少,移植简单,可以沿用以前的全局变量,包括一些标志位


  • 以前的中断 ,不直接涉及到任务的,该怎样还是怎样:

如果直接和任务有关的,需要做一定的修改, 比如 定时器定时任务 与 事件组有关:


再比如串口接收中断 使用 消息队列接收数据:

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

二、移植过程

2.1 基本框架搭建

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


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


在window下使用VScode搭建ARM开发环境——手把手教学详细版



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


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


FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析).



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



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 <string.h>/* 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 */}
复制代码


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


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 空间:



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


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



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



函数没有问题,是在调用的时候,调用出错,正确方式应该是如下方式:


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信号量、事件标志组、邮箱和消息队列、任务通知的关系)


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


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


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



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


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 中设置:




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



完善一下 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 */}
复制代码


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


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

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


if(Switch_Flag == 1){  SendConfigInfoTelegram();   Switch_Flag = 0;}
复制代码


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



但是最终以这种方式测试下来,有 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 */}
复制代码


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



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



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


2.2.8 延时函数!!!

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


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


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


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



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



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


/*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 样例最终情况

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



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



最终剩余情况:



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


最终修改下来,实际上我只用了 3 个任务,如果不是为了适配以前的代码框架,重新构建逻辑,可能可以更加合理的分配任务:



3.2 一些体会

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


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


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


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


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


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


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


3.3 源码链接


最后附上源码链接:https://gitee.com/qzh_projects/Demos/tree/master/

发布于: 刚刚阅读数: 4
用户头像

矜辰所致

关注

不浮夸,不将就,认真对待学知识的我们! 2022.08.02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论 (1 条评论)

发布
用户头像
老样子,打开目录看吧,直接拉可能有点长
刚刚 · 江苏
回复
没有更多了
开源一夏 | 一个裸机工程转FreeRTOS的实例_开源_矜辰所致_InfoQ写作社区