Skip to content

Instantly share code, notes, and snippets.

@mkeyno
Last active April 13, 2021 19:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mkeyno/f06ab4fd2dd480df3685bbf44a13ce57 to your computer and use it in GitHub Desktop.
Save mkeyno/f06ab4fd2dd480df3685bbf44a13ce57 to your computer and use it in GitHub Desktop.
control description

Objective

Design the primary firmware & features  for remote tank control system

Presumptions

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

Primary Proposal Code

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)

Hardware initialization

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)

source code

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


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment