Design the primary firmware & features for remote tank control system
With regards to the primary description, three identical devices bind together remotely to create an automatic tank control system, these parts includea liquid level gauge as a sensor, an electrical pump as an actuator, and an LCD unit as a main control node.
That information reveals , the sensor and actuator are not adjacent to each other and connected via wireless link.
Each sensor can provide false data and wireless connection may be disconnected unexpectedly and make this system arrangement less reliable compare to a coupled close-loop sensor-actuator system, hence alternative fail-safe procedure should be considered for such a situations
Following picture shows the schematic of this control systems
According to the description, the Motor has two modes (auto, manual) and two states (on, off). in each mode, the operator can override the action and turn off the motor, but turnig on the motor is only available in manual mode and in automatic mode, turning the pump on only perform by LCD controller.
On user action, status message will be sent back to LCD controller and when the motor turned off, the mode will be changed to manual mode until LCD controller set a new mode
For safety and reliability, many industrial applications use the RTOS for their controller framework and in this case we use FreeRTOS (CMSIS_V2)as middleware to abstract hardware layer.
STM32M0 & M3 cortex products show many robustness with a lot of feature for industrial application, thus we use STMF10xx MCU alongside the cube IDE for programing.
Three tasks(Threads) are considered for this operation.
Since the actuator does not receive the sensor data directly and the safety of the actuator(Pump) is very critical in every industrial operation, the default task(idle task) set to connection heart bit with frequency(pooling) 3000ms requesting the sensor level gouge data from LCD controller
if new data has not received in that period, the pump will be turned off and mode set to manual waiting for the user operation
Another safety feature would be setting maximum operation time for the pump, so in case of false sensor data, motor shut down automatically. In actual application maximum operation time set for time needed to fill the %60 of tank
void RequestData_Task(void *argument)
The other Threads are the interrupt handler in event of receiving new command from LCD controller or when user press the buttons
void On_Off_button_Task(void *argument)
void LCD_CommandHandle(void *argument)
2 pin of MCU assigned for buttons and connected to one line of the Interrupt controller
void EXTI0_IRQHandler(void)
2 pin of MCU assigned for lights with respect to the status of Pump mode(On, Off)
USART2 initialized for debug report
SPI1 alongside the RF_Pin initialized for interrupt RF data handling
void EXTI9_5_IRQHandler(void)
Following code include the user code and auto generated code in STM32CubeIDE, Version: 1.6.1
#include "main.h"
#include "cmsis_os.h"
#include "usb_host.h"
#include "stdint.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#include "inttypes.h"
#define Print(s) sprintf(str,s);\
HAL_UART_Transmit(&huart2, (uint8_t *)str, sizeof(str), 10);
#define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/
#define TurnOff 0
#define TurnOn 1
#define Manual 1
#define maximum_time_operation 14000
/* Interrupt handller */
osThreadId_t LCD_CommandHandle;
const osThreadAttr_t LCD_Command_attributes = { .name = "LCD_Command", .priority = (osPriority_t) osPriorityHigh, .stack_size = 128 * 4};
osThreadId_t On_Off_buttonHandle;
const osThreadAttr_t On_Off_button_attributes = { .name = "On_Off_button", .priority = (osPriority_t) osPriorityHigh, .stack_size = 128 * 4};
/* pooling Handle (sensor data heart bit) */
osThreadId_t RequestDataHandle;
const osThreadAttr_t RequestData_attributes = { .name = "RequestData", .priority = (osPriority_t) osPriorityNormal, .stack_size = 128 * 4};
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
static void MX_USART2_UART_Init(void);
//void LED_SHOW_Task(void *argument);
void On_Off_button_Task(void *argument);
void LCD_Command_Task(void *argument);
void RequestData_Task(void *argument);
SPI_HandleTypeDef hspi1;
UART_HandleTypeDef huart2;
unsigned char button,Mode,operation;
uint32_t last_sending,pump_operational_time;
void rtos_ini(void);
void rtos_ini(void)
{
osKernelInitialize();
On_Off_buttonHandle = osThreadNew(On_Off_button_Task, NULL, &On_Off_button_attributes);
LCD_CommandHandle = osThreadNew( LCD_Command_Task, NULL, &LCD_Command_attributes);
RequestDataHandle = osThreadNew(RequestData_Task, NULL, &RequestData_attributes);
}
//////////////////////////////////////////////Auto generated code/////////////////////////////////////////////////////////////
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_I2C1_Init();
MX_SPI1_Init();
MX_USART2_UART_Init();
rtos_ini();
osKernelStart();
while(1){}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) Error_Handler();
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) Error_Handler();
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) Error_Handler();
}
static void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK) Error_Handler();
}
static void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK) Error_Handler();
}
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, CS_Ethe_Pin|Off_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, pump_Pin|On_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : CS_Ethe_Pin Off_Pin */
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = CS_Ethe_Pin|pump_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pins : Off_Pin Off_Pin */
GPIO_InitStruct.Pin = Off_Pin|On_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : RF_Pin */
GPIO_InitStruct.Pin = RF_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(RF_GPIO_Port,&GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ( EXTI9_5_IRQn);
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ( EXTI0_IRQn);
}
///////////////////////////////////////////////user code//////////////////////////////////////////////////////////////////////
void EXTI9_5_IRQHandler(void)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(RF_Pin) != 0x00u)
{
HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
__HAL_GPIO_EXTI_CLEAR_IT(RF_Pin); // Clears The Interrupt Flag
osThreadResume (LCD_Command_Task); // Calls The ISR Handler CallBack Function
}
}
void EXTI0_IRQHandler(void)
{
/* EXTI line interrupt detected */
if ((__HAL_GPIO_EXTI_GET_IT(Off_Pin) != 0x00u) && (__HAL_GPIO_EXTI_GET_IT(On_Pin) != 0x00u))
{
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
__HAL_GPIO_EXTI_CLEAR_IT(Off_Pin); // Clears The Interrupt Flag
__HAL_GPIO_EXTI_CLEAR_IT(On_Pin); // Clears The Interrupt Flag
osThreadResume (On_Off_button_Task); // Calls The ISR Handler CallBack Function
}
}
void Pump(bool status)
{
button=status;
HAL_GPIO_WritePin(pump_GPIO_Port,pump_Pin,status);
HAL_GPIO_WritePin(lightOn_GPIO_Port,lightOn_Pin,status);
HAL_GPIO_WritePin(lightOff_GPIO_Port,lightOff_Pin,!status);
if (status && (xTaskGetTickCount()-pump_operational_time)>maximum_time_operation)
{
status=TurnOff;
}
else pump_operational_time=0;
}
void LCD_Command_Task(void *argument)
{
if(!RF_Link(&hspi1)) return;
unsigned short int command;
RF_Buffer(command,sizeof(command));
for(;;)
{
Mode =BitVal(command,1) ;
operation =BitVal(command,2) ;
level =command>>8&0x8;
Pump(operation);
last_sending=xTaskGetTickCount();
}
}
void RequestData_Task(void *argument)
{
if(!RF_Link(&hspi1)) return;
unsigned char DATA;
RF_Buffer(DATA,sizeof(DATA));
/* good to go*/
for(;;)
{
if(xTaskGetTickCount()-last_sending>3000)
{
Pump(TurnOff);
Mode=Manual;
}
else {
DATA=(Mode|operation)&0x8;
RF_Transmit(&hspi1, (uint8_t *)DATA, 1, 10);
Print("sending last status")
last_sending=xTaskGetTickCount();
}
osDelay(3000);
}
}
void On_Off_button_Task(void *argument)
{
for(;;)
{
button = HAL_GPIO_ReadPin(Botton_GPIO_Port,Botton_Pin);
if(Mode==Manual){
Pump(button);
}
else if(button==TurnOff){
Pump(button);
Mode=Manual
}
DATA=(Mode|button)&0x8;
RF_Transmit(&hspi1, (uint8_t *)DATA, 1, 10);
Print("sending status")
osThreadId_t this=osThreadGetId ();
osThreadSuspend (this);
}
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM1 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
void Error_Handler(void)
{
Print("dumping data log.....");
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
Print("code failed");
}
#endif /* USE_FULL_ASSERT */