/* USER CODE BEGIN Header */ /** ****************************************************************************** * File Name : app_freertos.c * Description : Code for freertos applications ****************************************************************************** * @attention * * Copyright (c) 2021 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "FreeRTOS.h" #include "task.h" #include "main.h" #include "cmsis_os.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "BME280_STM32.h" #include "pid.h" #include "icons.h" #include "ssd1306.h" #include "string.h" #include "stdio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ typedef struct { char text[6]; uint8_t drytemp; uint32_t drytime; } MenuItem; /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN Variables */ float aimTemperature, Temperature, Pressure, aimHumidity, Humidity; //BME280 results and aim values uint8_t powerFan = 0, powerHeater = 0; //Set fan/heater power 0 - 100 //PID settings PIDController pid = { .Kp = 5.0f, .Ki = 2.0f, .Kd = 1.0f, .T = 0.25f, //4 times per second .tau = 0.2f, //Low-pass filter (0 - no filter) .limMin = 0.0f, .limMax = 100.0f }; MenuItem menuProg[] = { // text temp time in seconds {"PLA", 45, 4*3600}, {"ABS", 60, 2*3600}, {"PETG", 65, 2*3600}, {"TPU", 50, 4*3600}, {"NYLON", 70, 8*3600}, {"PVA", 45, 4*3600}, {"ASA", 60, 4*3600}, {"PP", 55, 6*3600}, {"SILIC", 65, 3*3600}, {"TEST", 27, 30} }; uint8_t menuMax = 9; // Max menu index uint8_t prog = 0; // Selected program uint8_t menuFrame = 0; // Menu list frame index uint8_t mode = 0; // Current mode: 0 - Idle menu; 1 - Drying; 2 - Drying stop question; 3 - Keeping; // 4 - Keeping stop question; 98 - Thermal runout; 99 - sensor error; uint8_t qConfirm = 0; // Question menu (Yes/No) selected answer uint32_t time_cnt = 0; // Time counter for Drying / Keeping action /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { .name = "defaultTask", .priority = (osPriority_t) osPriorityNormal, .stack_size = 128 * 4 }; /* Definitions for svcDisplay */ osThreadId_t svcDisplayHandle; const osThreadAttr_t svcDisplay_attributes = { .name = "svcDisplay", .priority = (osPriority_t) osPriorityLow, .stack_size = 128 * 4 }; /* Definitions for svcSensors */ osThreadId_t svcSensorsHandle; const osThreadAttr_t svcSensors_attributes = { .name = "svcSensors", .priority = (osPriority_t) osPriorityLow, .stack_size = 128 * 4 }; /* Definitions for svcKeys */ osThreadId_t svcKeysHandle; const osThreadAttr_t svcKeys_attributes = { .name = "svcKeys", .priority = (osPriority_t) osPriorityLow, .stack_size = 128 * 4 }; /* Definitions for qKeysPressed */ osMessageQueueId_t qKeysPressedHandle; const osMessageQueueAttr_t qKeysPressed_attributes = { .name = "qKeysPressed" }; /* Definitions for qDisplay */ osMessageQueueId_t qDisplayHandle; const osMessageQueueAttr_t qDisplay_attributes = { .name = "qDisplay" }; /* Definitions for timeCounter */ osTimerId_t timeCounterHandle; const osTimerAttr_t timeCounter_attributes = { .name = "timeCounter" }; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ void update_ui(void); //update display main function void draw_info_frame(void); //draw frame and pics for info frame void draw_info(void); //update drying/keeping info void draw_time_info(void); //update drying/keeping timers void draw_header(void); //update display header lines void draw_menu(void); //Draw program select menu void draw_question(void); //Draw STOP? question void draw_error(void); //Draw sensor error message void setPWMs(void); //set Timer values according to requested settings /* USER CODE END FunctionPrototypes */ void StartDefaultTask(void *argument); void svc_display(void *argument); void svc_sensors(void *argument); void svc_keys(void *argument); void clock_tick(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /** * @brief FreeRTOS initialization * @param None * @retval None */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* 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 */ /* Create the timer(s) */ /* creation of timeCounter */ timeCounterHandle = osTimerNew(clock_tick, osTimerPeriodic, NULL, &timeCounter_attributes); /* USER CODE BEGIN RTOS_TIMERS */ /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ /* Create the queue(s) */ /* creation of qKeysPressed */ qKeysPressedHandle = osMessageQueueNew (16, sizeof(uint8_t), &qKeysPressed_attributes); /* creation of qDisplay */ qDisplayHandle = osMessageQueueNew (16, sizeof(uint8_t), &qDisplay_attributes); /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ /* Create the thread(s) */ /* creation of defaultTask */ defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* creation of svcDisplay */ svcDisplayHandle = osThreadNew(svc_display, NULL, &svcDisplay_attributes); /* creation of svcSensors */ svcSensorsHandle = osThreadNew(svc_sensors, NULL, &svcSensors_attributes); /* creation of svcKeys */ svcKeysHandle = osThreadNew(svc_keys, NULL, &svcKeys_attributes); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ /* USER CODE END RTOS_THREADS */ /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */ } /* USER CODE BEGIN Header_StartDefaultTask */ /** * @brief Function implementing the defaultTask thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartDefaultTask */ void StartDefaultTask(void *argument) { /* USER CODE BEGIN StartDefaultTask */ osTimerStart(timeCounterHandle, 1000); /* Infinite loop */ uint8_t key; //pressed key id for(;;) { //Check if there is a key pressed if (osMessageQueueGetCount(qKeysPressedHandle) > 0) { osMessageQueueGet(qKeysPressedHandle, &key, NULL, 50); switch (key) { case 0: // Down button switch (mode) { case 0: if (prog < menuMax) prog++; if (prog > menuFrame + 2) menuFrame = prog - 2; break; case 2: case 4: qConfirm = 0; break; } break; case 1: // OK button switch (mode) { case 0: //Program has been chosen, lets start heating time_cnt = menuProg[prog].drytime; aimTemperature = menuProg[prog].drytemp; pid.integrator = 0; powerFan = 100; mode = 1; break; case 1: //Ask for confirmation to stop drying mode = 2; qConfirm = 0; break; case 3: //Ask for confirmation to stop keeping mode = 4; qConfirm = 0; break; case 2: //resume heating or stop and go to idle mode if (qConfirm) { aimTemperature = 0; mode = 0; } else { mode = 1; } break; case 4: //resume keeping or stop and go to idle mode if (qConfirm) { aimTemperature = 0; aimHumidity = 0; mode = 0; } else { mode = 3; } break; } break; case 2: // Up button switch (mode) { case 0: if (prog > 0) prog--; if (prog < menuFrame) menuFrame = prog; break; case 2: case 4: qConfirm = 1; break; } break; } //switch key update_ui(); } //key queue check osDelay(1); } /* USER CODE END StartDefaultTask */ } /* USER CODE BEGIN Header_svc_display */ /** * @brief Function implementing the svcDisplay thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_svc_display */ void svc_display(void *argument) { /* USER CODE BEGIN svc_display */ /* Infinite loop */ for(;;) { if (mode == 1 || mode == 3) { draw_info(); } osDelay(500); } /* USER CODE END svc_display */ } /* USER CODE BEGIN Header_svc_sensors */ /** * @brief Function implementing the svcSensors thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_svc_sensors */ void svc_sensors(void *argument) { /* USER CODE BEGIN svc_sensors */ uint16_t tro = 0; //counter for thermal runout protection aimTemperature = Temperature = Pressure = aimHumidity = Humidity = 0.0f; PID_Init(&pid); /* Infinite loop */ for(;;) { if (mode < 98) { BME280_Measure(); if (Temperature == 0.0f && Humidity == 0.0f) { //Some sensor malfunction mode = 99; aimTemperature = 0; aimHumidity = 0; powerFan = 100; powerHeater = 0; update_ui(); } else { switch (mode) { //update fan/heater settings here. case 0: if (powerHeater > 0) powerHeater = 0; if (Temperature >= 35) powerFan = 100; else powerFan = 0; break; case 1: case 2: powerHeater = PID_Update(&pid, aimTemperature, Temperature); if (powerHeater > 0) powerFan = 100; else powerFan = 85; break; case 3: case 4: if (Humidity > 12) powerHeater = PID_Update(&pid, aimTemperature, Temperature); else powerHeater = 0; break; } //switch (mode) } //else //Simple thermal runout protection if (Temperature > aimTemperature && Temperature > 45) tro++; else if (tro != 0) tro = 0; if (tro > 480) { //480 - 2 minutes (120 sec * 4 as this task runs 4 times per second) mode = 98; aimTemperature = 0; aimHumidity = 0; powerFan = 100; powerHeater = 0; update_ui(); } } //if (mode < 98) setPWMs(); osDelay(250); } /* USER CODE END svc_sensors */ } /* USER CODE BEGIN Header_svc_keys */ /** * @brief Function implementing the svcKeys thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_svc_keys */ void svc_keys(void *argument) { /* USER CODE BEGIN svc_keys */ uint8_t bBuff[3] = {0xFF}; // buttons are grounded when pressed uint8_t bState[3] = {0xFF}; //last reported state. We need this to prevent repeated reports uint8_t read, i; /* Infinite loop */ for(;;) { read = KEY_DN_GPIO_Port->IDR & 0x00000007; //Read 3 bits of buttons port (PB0, PB1, PB2) // check every button and shift its values to the array for (i=0; i<3; i++) { bBuff[i] = (bBuff[i]<<1) | ((read >> i) & 1); //Shift button buffer left and assign a button bit from the read data if ( ((bBuff[i] & 0x0F) == 0b00000000) && (bState[i] == 1) ) { //if we read 0 last 4 times and last report was 1 then the button has been pressed bState[i] = 0; osMessageQueuePut(qKeysPressedHandle, &i, 0, 50); //Put the pressed button id to the key queue } if ( ((bBuff[i] & 0x0F) == 0b00001111) && (bState[i] == 0) ) { //if we read 1 last 4 times and last report was 0 then the button has been released bState[i] = 1; } } osDelay(4); } /* USER CODE END svc_keys */ } /* clock_tick function */ void clock_tick(void *argument) { /* USER CODE BEGIN clock_tick */ //time counters here. Happens every second switch (mode) { case 1: case 2: time_cnt--; if (time_cnt == 0) { aimTemperature = 35; pid.integrator = 0; powerFan = 85; mode = 3; update_ui(); } break; case 3: case 4: time_cnt++; break; } draw_time_info(); /* USER CODE END clock_tick */ } /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ //update display main function void update_ui(void) { ssd1306_Fill(Black); draw_header(); switch (mode) { case 0: draw_menu(); break; case 1: case 3: draw_info_frame(); break; case 2: case 4: draw_question(); break; case 98: case 99: draw_error(); break; } //switch (mode) ssd1306_UpdateScreen(); } //draw frame and pics for info frame void draw_info_frame(void) { ssd1306_DrawBitmap(4, 16, humidity16, 16, 16, White); ssd1306_DrawBitmap(0, 32, temperature24, 24, 24, White); ssd1306_DrawBitmap(26, 54, heat_icon, 18, 8, White); ssd1306_DrawBitmap(80, 54, fan_icon, 16, 8, White); } //update drying/keeping info void draw_info(void) { char num[8]; //Humidity ssd1306_DrawRectangle(26, 16, 61, 26, Black, 1); sprintf(num, "%.1f%%", Humidity); ssd1306_SetCursor(26, 16); ssd1306_WriteString(num, Font_7x10, White); //Temperature ssd1306_DrawRectangle(26, 32, 122, 58, Black, 1); sprintf(num, "%.1f*C", Temperature); ssd1306_SetCursor(26, 32); ssd1306_WriteString(num, Font_16x26, White); //PWM Heater ssd1306_DrawRectangle(47, 55, 65, 63, Black, 1); sprintf(num, "%u", powerHeater); ssd1306_SetCursor(47, 55); ssd1306_WriteString(num, Font_6x8, White); //PWM Fan ssd1306_DrawRectangle(98, 55, 116, 63, Black, 1); sprintf(num, "%u", powerFan); ssd1306_SetCursor(98, 55); ssd1306_WriteString(num, Font_6x8, White); ssd1306_UpdateScreen(); } //update drying/keeping timers void draw_time_info(void) { if (mode < 98) { ssd1306_SetCursor(78, 8); if (mode > 0) { uint8_t hrs, mins, secs; uint32_t rem; hrs = time_cnt / 3600; rem = time_cnt % 3600; mins = rem / 60; secs = rem % 60; char ts[9]; sprintf(ts, "%02d:%02d:%02d", hrs, mins, secs); ssd1306_DrawRectangle(78, 8, 128, 16, Black, 1); ssd1306_WriteString(ts, Font_6x8, White); } else { ssd1306_WriteString("00:00:00", Font_6x8, White); } ssd1306_UpdateScreen(); }// if mode < 98 } //update display header lines void draw_header(void) { char header[6], action[8]; switch (mode) { case 0: strcpy(header, "MENU"); strcpy(action, "Idle"); break; case 1: strcpy(header, menuProg[prog].text); strcpy(action, "Drying"); break; case 2: strcpy(header, "STOP?"); strcpy(action, "Drying"); break; case 3: strcpy(header, "DONE!"); strcpy(action, "Keeping"); break; case 4: strcpy(header, "STOP?"); strcpy(action, "Keeping"); break; case 98: case 99: strcpy(header, "ERROR"); break; } //Switch (mode) ssd1306_SetCursor(0, 0); ssd1306_WriteString(header, Font_7x10, White); if (mode < 98) { ssd1306_SetCursor(78, 0); ssd1306_WriteString(action, Font_6x8, White); }// if mode < 98 } //Draw program select menu void draw_menu(void) { char str[7]; for (uint8_t i=0; i<3; i++) { if (prog == i + menuFrame) { ssd1306_Line(0, i*15 + 16, 127, i*15 + 16, White); ssd1306_Line(0, i*15 + 31, 127, i*15 + 31, White); } ssd1306_SetCursor(0, i*15 + 17); ssd1306_WriteString(menuProg[i + menuFrame].text, Font_7x10, White); ssd1306_SetCursor(78, i*15 + 16); sprintf(str, "%u *c", menuProg[i + menuFrame].drytemp); ssd1306_WriteString(str, Font_6x8, White); ssd1306_SetCursor(78, i*15 + 24); sprintf(str, "%luh%lum", menuProg[i + menuFrame].drytime/3600, menuProg[i + menuFrame].drytime%3600/60); ssd1306_WriteString(str, Font_6x8, White); } //for } //Draw STOP? question void draw_question(void) { ssd1306_DrawRoundRectangle(4, 20, 123, 60, White); ssd1306_SetCursor(20, 33); ssd1306_WriteString("YES NO", Font_7x10, White); if (qConfirm) { ssd1306_DrawRoundRectangle(17, 30, 56, 49, White); } else { ssd1306_DrawRoundRectangle(77, 30, 104, 49, White); } } //Draw sensor error message void draw_error(void) { char str1[8], str2[7]; if (mode == 99) { strcpy(str1, "SENSOR"); strcpy(str2, "FAIL"); } if (mode == 98) { strcpy(str1, "THERMAL"); strcpy(str2, "RUNOUT"); } ssd1306_DrawBitmap(111, 0, error_icon, 14, 16, White); ssd1306_SetCursor(26, 24); ssd1306_WriteString(str1, Font_7x10, White); ssd1306_SetCursor(38, 40); ssd1306_WriteString(str2, Font_7x10, White); } //set Timer values according to requested settings void setPWMs(void) { TIM3->CCR1 = powerHeater; TIM3->CCR2 = powerFan; if (powerHeater < powerFan) { TIM3->ARR = powerHeater - 10; } else { TIM3->ARR = powerFan - 10; } } /* USER CODE END Application */