单片机
返回首页

STM32CubeMX学习笔记(34)——FreeRTOS实时操作系统使用(任务通知)

2025-02-12 来源:jianshu

一、FreeRTOS简介

FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。

FreeRTOS 是用 C 和汇编来写的,其中绝大部分都是用 C 语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,FreeRTOS 结构简洁,可读性很强!最主要的是非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。

最新版本 V9.0.0(2016年),尽管现在 FreeRTOS 的版本已经更新到 V10.4.1 了,但是我们还是选择 V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版本之后是亚马逊收购了FreeRTOS之后才出来的版本,主要添加了一些云端组件,一般采用 V9.0.0 版本足以。

  • FreeRTOS官网:http://www.freertos.org/

  • 代码托管网站:https://sourceforge.net/projects/freertos/files/FreeRTOS/

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”


2. 选择 MCU 和封装


3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)


选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置


4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire


三、SYS Timebase Source

在 System Core 中选择 SYS ,对 Timebase Source 进行设置,选择 TIM1 作为HAL库的时基(除了 SysTick 外都可以)。


在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:

  1. HAL的时基,SYS Timebase Source

  2. OS的时基(仅在使用OS的情况下才考虑)

而这些 “时基” 该去如何维护,主要分为两种情况考虑:

  • 裸机运行:
    可以通过 SysTick(滴答定时器)或 (TIMx)定时器 的方式来维护 SYS Timebase Source,也就是HAL库中的 uwTick,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler() 中断服务函数中来维护。

  • 带OS运行:
    前面提到的 SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。

    在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。

    如果共用SysTick,当我们在CubeMX中选择启用FreeRTOS之后,在生成代码时,CubeMX一定会报如下提示:


强烈建议用户在使用FreeRTOS的时候,不要使用 SysTick(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用,最好是要换一个!!!如果共用,潜在一定风险。

四、FreeRTOS

4.1 参数配置

在 Middleware 中选择 FREERTOS 设置,并选择 CMSIS_V1 接口版本



CMSIS是一种接口标准,目的是屏蔽软硬件差异以提高软件的兼容性。RTOS v1使得软件能够在不同的实时操作系统下运行(屏蔽不同RTOS提供的API的差别),而RTOS v2则是拓展了RTOS v1,兼容更多的CPU架构和实时操作系统。因此我们在使用时可以根据实际情况选择,如果学习过程中使用STM32F1、F4等单片机时没必要选择RTOS v2,更高的兼容性背后时更加冗余的代码,理解起来比较困难。

在 Config parameters 进行具体参数配置。

Kernel settings:

  • USE_PREEMPTION: Enabled:RTOS使用抢占式调度器;Disabled:RTOS使用协作式调度器(时间片)。

  • TICK_RATE_HZ: 值设置为1000,即周期就是1ms。RTOS系统节拍中断的频率,单位为HZ。

  • MAX_PRIORITIES: 可使用的最大优先级数量。设置好以后任务就可以使用从0到(MAX_PRIORITIES - 1)的优先级,其中0位最低优先级,(MAX_PRIORITIES - 1)为最高优先级。

  • MINIMAL_STACK_SIZE: 设置空闲任务的最小任务堆栈大小,以字为单位,而不是字节。如该值设置为128 Words,那么真正的堆栈大小就是 128*4 = 512 Byte。

  • MAX_TASK_NAME_LEN: 设置任务名最大长度。

  • IDLE_SHOULD_YIELD: Enabled 空闲任务放弃CPU使用权给其他同优先级的用户任务。

  • USE_MUTEXES: 为1时使用互斥信号量,相关的API函数会被编译。

  • USE_RECURSIVE_MUTEXES: 为1时使用递归互斥信号量,相关的API函数会被编译。

  • USE_COUNTING_SEMAPHORES: 为1时启用计数型信号量, 相关的API函数会被编译。

  • QUEUE_REGISTRY_SIZE: 设置可以注册的队列和信号量的最大数量,在使用内核调试器查看信号量和队列的时候需要设置此宏,而且要先将消息队列和信号量进行注册,只有注册了的队列和信号量才会在内核调试器中看到,如果不使用内核调试器的话次宏设置为0即可。

  • USE_APPLICATION_TASK_TAG: 为1时可以使用vTaskSetApplicationTaskTag函数。

  • ENABLE_BACKWARD_COMPATIBILITY: 为1时可以使V8.0.0之前的FreeRTOS用户代码直接升级到V8.0.0之后,而不需要做任何修改。

  • USE_PORT_OPTIMISED_TASK_SELECTION: FreeRTOS有两种方法来选择下一个要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU自带的硬件指令来实现。STM32有计算前导零指令吗,所以这里强制置1。

  • USE_TICKLESS_IDLE: 置1:使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行。假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用ISP下载办法解决。

  • USE_TASK_NOTIFICATIONS: 为1时使用任务通知功能,相关的API函数会被编译。开启了此功能,每个任务会多消耗8个字节。

  • RECORD_STACK_HIGH_ADDRESS: 为1时栈开始地址会被保存到每个任务的TCB中(假如栈是向下生长的)。

Memory management settings:

  • Memory Allocation: Dynamic/Static 支持动态/静态内存申请

  • TOTAL_HEAP_SIZE: 设置堆大小,如果使用了动态内存管理,FreeRTOS在创建 task, queue, mutex, software timer or semaphore的时候就会使用heap_x.c(x为1~5)中的内存申请函数来申请内存。这些内存就是从堆ucHeap[configTOTAL_HEAP_SIZE]中申请的。

  • Memory Management scheme: 内存管理策略 heap_4。

Hook function related definitions:

  • USE_IDLE_HOOK: 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子。

  • USE_TICK_HOOK: 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子。

  • USE_MALLOC_FAILED_HOOK: 使用内存申请失败钩子函数。

  • CHECK_FOR_STACK_OVERFLOW: 大于0时启用堆栈溢出检测功能,如果使用此功能用户必须提供一个栈溢出钩子函数,如果使用的话此值可以为1或者2,因为有两种栈溢出检测方法。

Run time and task stats gathering related definitions:

  • GENERATE_RUN_TIME_STATS: 启用运行时间统计功能。

  • USE_TRACE_FACILITY: 启用可视化跟踪调试。

  • USE_STATS_FORMATTING_FUNCTIONS: 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数prvWriteNameToBuffer()、vTaskList()、vTaskGetRunTimeStats()。

Co-routine related definitions:

  • USE_CO_ROUTINES: 启用协程。

  • MAX_CO_ROUTINE_PRIORITIES: 协程的有效优先级数目。

Software timer definitions:

  • USE_TIMERS: 启用软件定时器。

Interrupt nesting behaviour configuration:

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中断最低优先级。

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系统可管理的最高中断优先级。

4.2 创建任务Task

要想使用任务通知必须在 Config parameters 中把 USE_TASK_NOTIFICATIONS 选择 Enabled 来使能。


我们创建三个任务,两个接收任务,一个发送任务。



  • Task Name: 任务名称

  • Priority: 优先级,在 FreeRTOS 中,数值越大优先级越高,0 代表最低优先级

  • Stack Size (Words): 堆栈大小,单位为字,在32位处理器(STM32),一个字等于4字节,如果传入512那么任务大小为512*4字节

  • Entry Function: 入口函数

  • Code Generation Option: 代码生成选项

  • Parameter: 任务入口函数形参,不用的时候配置为0或NULL即可

  • Allocation: 分配方式:Dynamic 动态内存创建

  • Buffer Name: 缓冲区名称

  • Conrol Block Name: 控制块名称

五、KEY

5.1 参数配置

在 System Core 中选择 GPIO 设置。


在右边图中找到按键对应引脚,选择 GPIO_Input。


六、UART串口打印

查看 STM32CubeMX学习笔记(6)——USART串口使用

七、生成代码

输入项目名和项目路径


选择应用的 IDE 开发环境 MDK-ARM V5


每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。


点击 GENERATE CODE 生成代码


八、任务通知

8.1 基本概念

FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有 一个 32 位 的通知值,在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)。

相对于以前使用 FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列。 想要使用任务通知,必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1,其实FreeRTOS 默认是为 1 的,所以任务通知是默认使能的。

FreeRTOS 提供以下几种方式发送通知给任务 :

  • 发送通知给任务, 如果有通知未读,不覆盖通知值。

  • 发送通知给任务,直接覆盖通知值。

  • 发送通知给任务,设置通知值的一个或者多个位 ,可以当做事件组来使用。

  • 发送通知给任务,递增通知值,可以当做计数信号量使用。
    通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量,队列、事件组等。

当然,凡是都有利弊,不然的话 FreeRTOS 还要内核的 IPC 通信机制干嘛,消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :

  • 只能有一个任务接收通知消息,因为必须指定接收通知的任务。

  • 只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。

8.2 运作机制

由于任务通知的数据结构包含在任务控制块中,只要任务存在,任务通知数据结构就已经创建完毕,可以直接使用,所以使用的时候很是方便。

任务通知可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知,FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue 就是这个通知值。只有在任务中可以等待通知,而不允许在中断中等待通知。如果任务在等待的通知暂时无效,任务会根据用户指定的阻塞超时时间进入阻塞状态,我们可以将等待通知的任务看作是消费者;其它任务和中断可以向等待通知的任务发送通知,发送通知的任务和中断服务函数可以看作是生产者,当其他任务或者中断向这个任务发送任务通知,任务获得通知以后,该任务就会从阻塞态中解除,这与 FreeRTOS 中内核的其他通信机制一致。

九、相关API说明

9.1 osSignalSet

向指定的任务发送一个任务通知,带有通知值并且用户可以指定通知值的发送方式。该函数可以在中断函数中使用。

函数int32_t osSignalSet (osThreadId thread_id, int32_t signal)
参数thread_id: 接收通知的任务ID

signal: 任务通知值,一般按位操作数字,一个事件用一个类似这样的值表示(即0x0001,0x0002,0x0004,0x0008,0x0010......)
返回值错误码

9.2 osSignalWait

用于实现等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待。函数不允许在中断函数中使用。

函数osEvent osSignalWait (int32_t signals, uint32_t millisec)
参数signals: 接收完成后等待被清零的数据位,比如一个任务状态切换,由四个不同的事件分别影响着,通知osSignalSet中的signal分别为0x0001,0x0002,0x0004,0x0008,那么接收端osSignalWait 中的signals应该为0x000F(0x0001|0x0002|0x0004|0x0008)

millisec: 等待超时时间,单位为系统节拍周期。宏 pdMS_TO_TICKS 用于将单位毫秒转化为系统节拍数
返回值错误码

十、示例

按下按键1触发任务通知到接收任务1Receive1Task,按下按键2触发任务通知到接收任务2Receive2Task。

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/

#include 'main.h'

#include 'cmsis_os.h'


/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

#include

#include

/* USER CODE END Includes */


/* Private typedef -----------------------------------------------------------*/

/* USER CODE BEGIN PTD */


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

UART_HandleTypeDef huart1;

DMA_HandleTypeDef hdma_usart1_rx;

DMA_HandleTypeDef hdma_usart1_tx;


osThreadId defaultTaskHandle;

osThreadId SendHandle;

osThreadId Receive1Handle;

osThreadId Receive2Handle;

/* USER CODE BEGIN PV */

#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0 

#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1

/* USER CODE END PV */


/* Private function prototypes -----------------------------------------------*/

void SystemClock_Config(void);

static void MX_GPIO_Init(void);

static void MX_DMA_Init(void);

static void MX_USART1_UART_Init(void);

void StartDefaultTask(void const * argument);

void SendTask(void const * argument);

void Receive1Task(void const * argument);

void Receive2Task(void const * argument);


/* USER CODE BEGIN PFP */


/* USER CODE END PFP */


/* Private user code ---------------------------------------------------------*/

/* USER CODE BEGIN 0 */


/* USER CODE END 0 */


/**

  * @brief  The application entry point.

  * @retval int

  */

int main(void)

{

  /* USER CODE BEGIN 1 */

    

  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */

  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */

  MX_GPIO_Init();

  MX_DMA_Init();

  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */

    

  /* USER CODE END 2 */


  /* USER CODE BEGIN RTOS_MUTEX */

  /* add mutexes, ... */

  /* USER CODE END RTOS_MUTEX */


  /* USER CODE BEGIN RTOS_SEMAPHORES */

  /* add semaphores, ... */

  /* USER CODE END RTOS_SEMAPHORES */


  /* USER CODE BEGIN RTOS_TIMERS */

  /* start timers, add new ones, ... */

  /* USER CODE END RTOS_TIMERS */


  /* USER CODE BEGIN RTOS_QUEUES */

  /* add queues, ... */

  /* USER CODE END RTOS_QUEUES */


  /* Create the thread(s) */

  /* definition and creation of defaultTask */

  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);

  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);


  /* definition and creation of Send */

  osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);

  SendHandle = osThreadCreate(osThread(Send), NULL);


  /* definition and creation of Receive1 */

  osThreadDef(Receive1, Receive1Task, osPriorityIdle, 0, 128);

  Receive1Handle = osThreadCreate(osThread(Receive1), NULL);


  /* definition and creation of Receive2 */

  osThreadDef(Receive2, Receive2Task, osPriorityIdle, 0, 128);

  Receive2Handle = osThreadCreate(osThread(Receive2), NULL);


  /* USER CODE BEGIN RTOS_THREADS */

  /* add threads, ... */

  /* USER CODE END RTOS_THREADS */


  /* Start scheduler */

  osKernelStart();


  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

    /* USER CODE END WHILE */


    /* USER CODE BEGIN 3 */

  }

  /* USER CODE END 3 */

}


/**

  * @brief System Clock Configuration

  * @retval None

  */

void SystemClock_Config(void)

{

  RCC_OscInitTypeDef RCC_OscInitStruct = {0};

  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};


  /** Initializes the RCC Oscillators according to the specified parameters

  * in the RCC_OscInitTypeDef structure.

  */

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;

  RCC_OscInitStruct.HSEState = RCC_HSE_ON;

  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;

  RCC_OscInitStruct.HSIState = RCC_HSI_ON;

  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;

  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;

  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)

  {

    Error_Handler();

  }

  /** Initializes the CPU, AHB and APB buses clocks

  */

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK

                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;

  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;

  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;

  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;

  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;


  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)

  {

    Error_Handler();

  }

}


/**

  * @brief USART1 Initialization Function

  * @param None

  * @retval None

  */

static void MX_USART1_UART_Init(void)

{


  /* USER CODE BEGIN USART1_Init 0 */


  /* USER CODE END USART1_Init 0 */


  /* USER CODE BEGIN USART1_Init 1 */


  /* USER CODE END USART1_Init 1 */

  huart1.Instance = USART1;

  huart1.Init.BaudRate = 115200;

  huart1.Init.WordLength = UART_WORDLENGTH_8B;

  huart1.Init.StopBits = UART_STOPBITS_1;

  huart1.Init.Parity = UART_PARITY_NONE;

  huart1.Init.Mode = UART_MODE_TX_RX;

  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;

  huart1.Init.OverSampling = UART_OVERSAMPLING_16;

  if (HAL_UART_Init(&huart1) != HAL_OK)

  {

    Error_Handler();

  }

  /* USER CODE BEGIN USART1_Init 2 */


  /* USER CODE END USART1_Init 2 */


}


/**

  * Enable DMA controller clock

  */

static void MX_DMA_Init(void)

{


  /* DMA controller clock enable */

  __HAL_RCC_DMA1_CLK_ENABLE();


  /* DMA interrupt init */

  /* DMA1_Channel4_IRQn interrupt configuration */

  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 5, 0);

  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);

  /* DMA1_Channel5_IRQn interrupt configuration */

  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 5, 0);

  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);


}


/**

  * @brief GPIO Initialization Function

  * @param None

  * @retval None

  */

static void MX_GPIO_Init(void)

{

  GPIO_InitTypeDef GPIO_InitStruct = {0};


  /* GPIO Ports Clock Enable */

  __HAL_RCC_GPIOC_CLK_ENABLE();

  __HAL_RCC_GPIOA_CLK_ENABLE();

  __HAL_RCC_GPIOB_CLK_ENABLE();


  /*Configure GPIO pin Output Level */

  HAL_GPIO_WritePin(GPIOB, LED_G_Pin|LED_B_Pin|LED_R_Pin, GPIO_PIN_SET);


  /*Configure GPIO pin : KEY2_Pin */

  GPIO_InitStruct.Pin = KEY2_Pin;

  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;

  GPIO_InitStruct.Pull = GPIO_NOPULL;

  HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);


  /*Configure GPIO pin : KEY1_Pin */

  GPIO_InitStruct.Pin = KEY1_Pin;

  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;

  GPIO_InitStruct.Pull = GPIO_NOPULL;

  HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);


  /*Configure GPIO pins : LED_G_Pin LED_B_Pin LED_R_Pin */

  GPIO_InitStruct.Pin = LED_G_Pin|LED_B_Pin|LED_R_Pin;

  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

  GPIO_InitStruct.Pull = GPIO_NOPULL;

  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


}


/* USER CODE BEGIN 4 */

/**

  * @brief 重定向c库函数printf到USARTx

  * @retval None

  */

int fputc(int ch, FILE *f)

{

  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);

  return ch;

}

 

/**

  * @brief 重定向c库函数getchar,scanf到USARTx

  * @retval None

  */

int fgetc(FILE *f)

{

  uint8_t ch = 0;

  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);

  return ch;

}

/* USER CODE END 4 */


/* 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 const * argument)

{

  /* USER CODE BEGIN 5 */

  /* Infinite loop */

  for(;;)

  {

    osDelay(1);

  }

  /* USER CODE END 5 */

}


/* USER CODE BEGIN Header_SendTask */

/**

* @brief Function implementing the Send thread.

* @param argument: Not used

* @retval None

*/

/* USER CODE END Header_SendTask */

void SendTask(void const * argument)

{

  /* USER CODE BEGIN SendTask */

  /* Infinite loop */

  for(;;)

  {

    //如果 KEY1 被按下 

    if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_SET) 

    { 

        printf('KEY1 downn'); 

        /* 触发一个事件 1 */ 

        osSignalSet(Receive1Handle, KEY1_EVENT); 

    } 

    //如果 KEY2 被按下 

    if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_SET) 

    { 

        printf('KEY2 downn'); 

        /* 触发一个事件 2 */ 

        osSignalSet(Receive2Handle, KEY2_EVENT); 

    } 

    osDelay(100);

  }

  /* USER CODE END SendTask */

}


/* USER CODE BEGIN Header_Receive1Task */

/**

* @brief Function implementing the Receive1 thread.

* @param argument: Not used

* @retval None

*/

/* USER CODE END Header_Receive1Task */

void Receive1Task(void const * argument)

{

  /* USER CODE BEGIN Receive1Task */

  osEvent event; 

  /* Infinite loop */

  for(;;)

  {

    event = osSignalWait(KEY1_EVENT,        /* 接收任务感兴趣的事件 */ 

                         osWaitForever);    /* 指定超时事件,一直等 */ 

    if(event.status == osEventSignal)       //如果接收到通知

    {

        if(event.value.signals & KEY1_EVENT)//接收的通知为KEY1_EVENT

        {

            printf('Receive1Taskn'); 

        }

    }

  }

  /* USER CODE END Receive1Task */

}


/* USER CODE BEGIN Header_Receive2Task */

/**

* @brief Function implementing the Receive2 thread.

* @param argument: Not used

* @retval None

*/

/* USER CODE END Header_Receive2Task */

void Receive2Task(void const * argument)

{

  /* USER CODE BEGIN Receive2Task */

  osEvent event; 

  /* Infinite loop */

  for(;;)

  {

    event = osSignalWait(KEY2_EVENT,        /* 接收任务感兴趣的事件 */ 

                         osWaitForever);    /* 指定超时事件,一直等 */ 

    if(event.status == osEventSignal)       //如果接收到通知

    {

        if(event.value.signals & KEY2_EVENT)//接收的通知为KEY2_EVENT

        {

            printf('Receive2Taskn'); 

        }

    }

  }

  /* USER CODE END Receive2Task */

}


/**

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

}


/**

  * @brief  This function is executed in case of error occurrence.

  * @retval None

  */

void Error_Handler(void)

{

  /* USER CODE BEGIN Error_Handler_Debug */

  /* User can add his own implementation to report the HAL error return state */


  /* USER CODE END Error_Handler_Debug */

}


#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)

{

  /* USER CODE BEGIN 6 */

  /* User can add his own implementation to report the file name and line number,

     tex: printf('Wrong parameters value: file %s on line %drn', file, line) */

  /* USER CODE END 6 */

}

#endif /* USE_FULL_ASSERT */


十一、工程代码

链接:https://pan.baidu.com/s/1qS0uYFJ1YgMi2G0nAVaB8g  提取码:r53a


十二、注意事项

用户代码要加在 USER CODE BEGIN N 和 USER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。

进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦四级调频发射机

  • 500W MOS场效应管电源逆变器,12V转110V/220V

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • 12V转110V/220V 500W逆变器

  • DS1669数字电位器

    相关电子头条文章