F3 freeRTOS示例

1.实现串口任务打印

1.1业务流程

  • USART1初始化

  • 静态创建任务

注意:创建静态任务必须将Memory Allocation设置为Dynamic/static模式,即使能动态/静态内存

  • printf重定向,在usart.c下添加如下代码即可
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *stream)
{
    /* 堵塞判断串口是否发送完成 */
    while((USART1->SR & 0X40) == 0);
    /* 串口发送完成,将该字符发送 */
    USART1->DR = (uint8_t) ch;
    return ch;
}
/* USER CODE END 1 */
  • 任务内循环打印
void Uart_Task(void const * argument)
{
  /* USER CODE BEGIN Uart_Task */
  /* Infinite loop */
  for(;;)
  {
	  printf("Uart_Task is Runing!\r\n");
	  osDelay(1000);
  }
  /* USER CODE END Uart_Task */
}

1.2现象

  • LED灯在闪烁
  • 串口也在打印
Uart_Task is Runing!
Uart_Task is Runing!
Uart_Task is Runing!
Uart_Task is Runing!
Uart_Task is Runing!
Uart_Task is Runing!
...

2.按键检测任务

2.1功能需求

  • 创建按键检测任务
  • 当按键按下时候,挂起LED闪烁任务
  • 当按键松开时,恢复LED闪烁任务

2.2业务流程

  • 按键初始化,将对应按键引脚配置为中断模式并取个别名叫KEY

  • 配置按键的中断触发模式,此处需求应该配置为上升/下降沿触发

  • 使能中断

  • 创建按键检测任务

  • 按键中断服务函数实现1,在stm32f1xx_it.c里面找到EXIT4线的中断入口函数,可以看到里面调用了HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4)函数,进入该函数如下,里面定义了一个回调函数HAL_GPIO_EXTI_Callback(GPIO_Pin),再进入回调函数
void EXTI4_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI4_IRQn 0 */

  /* USER CODE END EXTI4_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
  /* USER CODE BEGIN EXTI4_IRQn 1 */

  /* USER CODE END EXTI4_IRQn 1 */
}
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}
//该函数为弱函数,当用户定义该函数时候会执行用户写的代码段,否则执行以下默认代码段

__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
}
  • 按键中断函数实现2
//在gpio.h里面定义枚举类型,保存按键状态

/* USER CODE BEGIN Private defines */
typedef enum
{
	KEY_UP,
	KEY_DOWN,
	KEY_UNKNOW,
}Key_Status;
/* USER CODE END Private defines */
//在gpio.c里面初始化枚举类型,即初始化按键状态为未知状态

/* USER CODE BEGIN 0 */
Key_Status KeyStatus = KEY_UNKNOW;
/* USER CODE END 0 */
//在gpio.c内部实现中断回调函数

/* USER CODE BEGIN 2 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == KEY_Pin)
	{
		if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == SET)
		{
			/*延时去抖*/
			HAL_Delay(1);
			if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == SET)
			{
				KeyStatus = KEY_UP;
			}
		}
		else if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == RESET)
		{
			HAL_Delay(1);
			if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == RESET)
			{
				KeyStatus = KEY_DOWN;
			}
		}
	}
}
  • 任务内部检测按键状态,根据状态进行挂起/恢复任务
void Key_Task(void const * argument)
{
  /* USER CODE BEGIN Key_Task */
  /* Infinite loop */
  for(;;)
  {
	  if(KeyStatus == KEY_DOWN)
	  {
          /*挂起任务*/
		  vTaskSuspend(LEDTaskHandle);
		  KeyStatus = KEY_UNKNOW;
	  }
	  else if(KeyStatus == KEY_UP)
	  {
          /*恢复任务*/
		  vTaskResume(LEDTaskHandle);
		  KeyStatus = KEY_UNKNOW;
	  }
	  osDelay(1);
  }
  /* USER CODE END Key_Task */
}

3.消息队列

3.1功能需求

  • 通过串口输入字符分别控制2个LED灯

3.2业务流程

  • 配置LED引脚为输出模式
  • 串口中断配置,勾选串口中断选项

  • 创建消息队列,其中设置消息队列长度为20,每个单位占1字节,采用动态创建

  • 编写串口中断回调函数,由于HAL库针对串口的效率比较低,此处采用直接操作寄存器的方式来获取串口数据
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint8_t u8Data;//存储一字节临时变量
	/*判断接收标志位*/
	if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == SET)
	{
		u8Data = huart1.Instance->DR;//读取数据寄存器
		xQueueSendFromISR(CmdQueueHandle, &u8Data, NULL);//在中断中将数据入队
	}
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}
  • 在函数中使能串口中断
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration    
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */
	__HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);//使能接收中断
  /* USER CODE END USART1_MspInit 1 */
  }
}
  • 使用面向对象中的封装特性来编写LED控制代码
  • 先定义要控制的灯的个数
#define LED_NUM 2
  • 将灯对应引脚存在数组中
/*保存指令的数组*/
uint8_t u8CmdBuff[20];
/*定义一个指针数组,保存字符串常量指针,此处存放开指令*/
uint8_t *OpenString[LED_NUM] = {
	"openled0",
	"openled1",
};
/*定义一个指针数组,保存字符串常量指针,此处存放关指令*/
uint8_t *CloseString[LED_NUM] = {
	"closeled0",
	"closeled1",
};
/*定义一个GPIO_TypeDef类型的指针数组,保存GPIO分组信息*/
GPIO_TypeDef *LedPort[LED_NUM] = {
	LED0_GPIO_Port,
	LED1_GPIO_Port,
};
/*定义一个数组,保存具体引脚号*/
uint16_t LedPin[LED_NUM] = {
	LED0_Pin,
	LED1_Pin,
};
  • 串口任务,此处采用面向对象方法,不论加入多少灯,只需要在之前的数组中添加相应元素即可,而不是一味地加switch
void vParseString(uint8_t *buff)
{
	uint8_t i;
    /*解析开灯指令*/
	for(i = 0; i < LED_NUM; i++)
	{
		if(strcmp((char const *)buff, (char const *)OpenString[i]) == 0)
		{
			HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_RESET);
			printf("cmd is %s\n", OpenString[i]);
			return;
		}
	}
    /*解析关灯指令*/
	for(i = 0; i < LED_NUM; i++)
	{
		if(strcmp((char const *)buff, (char const *)CloseString[i]) == 0)
		{
			HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_SET);
			printf("cmd is %s\n", CloseString[i]);
			return;
		}
	}
}
/* USER CODE END Header_Uart_Task */
void Uart_Task(void const * argument)
{
  /* USER CODE BEGIN Uart_Task */
	uint8_t u8Index;
  /* Infinite loop */
  for(;;)
  {
      /*每次读取消息的时候将索引值初始化为0*/
	  u8Index = 0;
      /*一直等待接收消息,第一个消息应该放在消息缓冲区的第一个元素上*/
      /*传入portMAX_DELAY表示函数在该处阻塞,直到有数据*/
	  if(xQueueReceive(CmdQueueHandle, &u8CmdBuff[u8Index++], portMAX_DELAY) == pdPASS)
	  {
          /*一直接收数据,直到消息队列为空*/
		  while(xQueueReceive(CmdQueueHandle, &u8CmdBuff[u8Index++], 50))
		  {
		  }
          /*加入结束标志,保证数据完整性*/
		  u8CmdBuff[u8Index] = '\0';
          /*字符解析函数*/
		  vParseString(u8CmdBuff);
          /*清空缓冲区*/
		  memset(u8CmdBuff, 0, 20);
	  }
  }
  /* USER CODE END Uart_Task */
}

4.软件定时器

4.1功能需求

  • 使用软件定时器功能完成闹钟设计
  • 当闹钟时间到达的时候,可根据执行动作,触发LED亮灭

4.2实现方案

  • 用户通过串口终端设置闹钟参数
  • 内部通过RTC实时时钟来计时
  • 通过GPIO来提示

4.3格式定义

  • 设置实时时钟

参数头:年-月-日,时:分:秒

realtime:2020-9-13,12:00:00

  • 设置闹钟参数

参数头:时:分:秒,是否重复,操作LED动作

alarmtime:12:05:00,0,0

4.4业务流程

  • 实时时钟,RTC功能开发

  • 命令参数配置,串口解析功能开发

  • 软件定时器功能

  • 多任务消息同步

posted @ 2021-08-05 23:17  MHDSG  阅读(306)  评论(0)    收藏  举报