Files
FilaDry2/firmware/Core/Src/app_freertos.c
2023-01-24 20:41:43 +03:00

664 lines
17 KiB
C

/* 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 */