You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.9 KiB

简单的例子

1. 新建带FreeRTOS的项目

1.1. 建立项目

File->New->STM32 Project

image-20231025101713280

image-20231025101816533

1.2. 配置项目

首先启用FreeRTOS支持使用CMSIS_V2接口。

image-20231025102033130

在Advanced settings 中打开 NEWLIB 的支持。

image-20231025102129858

设置系统滴答的时钟源以及调试方式。

image-20231025102501142

因为代码量比较多,最好为每个外设设置独立的文件。

image-20231025102612621

保存后生成代码框架

最后设置使用printf可以使用浮点。

Project->Properties

image-20231025103836426

2. 事件标志组的使用

请参考书上333页的讲解。

这里使用两个按钮都同时按过后才能点亮灯作为例子如果只按了一个按钮不会点亮灯两个按钮的先后顺序没关系也就是说这是一个AND关系。

为了简单,我们并不做太多的操作,只是在一个按钮按下的时候设置信号而已。当点灯的任务执行完点灯后,清除标志。

2.1. 环境配置

这里的配置都在IDE中配置完成并不涉及代码

2.1.1. 按键线程

建立两个线程,定时检查按钮。

image-20231025104626443image-20231025114518323

2.1.2. 按键配置

设置按键方式为输入设置label为 KEY1 和 KEY2。需要在GPIO中设置两个端口的上拉其中有个端口有外部上拉我记不住了就两个都设置了也不影响

image-20231025110118570

2.1.3. LED灯配置

使用红色灯PB0端口设置成输出并设置label为LED。

image-20231025114717324

2.1.4. LED控制线程

建立一个LED控制线程

image-20231025114751740

2.1.5. 事件标志组

书上是通过代码的方式建立事件标志组的,代码有些问题。这里直接使用配置的方式建立事件标志组。

image-20231025114942291

2.2. 代码解析

IDE已经为我们生成了代码框架接下来看看主要的代码块在什么位置。

2.2.1. 代码框架

GPIO的端口常量定义在main.h 的大约 60 行:

#define LED_Pin GPIO_PIN_0
#define LED_GPIO_Port GPIOB
#define KEY1_Pin GPIO_PIN_12
#define KEY1_GPIO_Port GPIOB
#define KEY2_Pin GPIO_PIN_13
#define KEY2_GPIO_Port GPIOB

这里定义了我们的三个IO的port和pin到时候需要使用。

在 freertos.c 的大约67行定义了事件标志组的变量

/* Definitions for ledEvent */
osEventFlagsId_t ledEventHandle;
const osEventFlagsAttr_t ledEvent_attributes = { .name = "ledEvent" };

在大约87行的MX_FREERTOS_Init 初始化函数中对线程以及事件标志进行初始化:

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

	/* 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) */
	/* creation of defaultTask */
	defaultTaskHandle = osThreadNew(StartDefaultTask, NULL,
			&defaultTask_attributes);

	/* creation of taskKey1 */
	taskKey1Handle = osThreadNew(StartTaskKey1, NULL, &taskKey1_attributes);

	/* creation of taskKey2 */
	taskKey2Handle = osThreadNew(StartTaskKey2, NULL, &taskKey2_attributes);

	/* creation of taskLed */
	taskLedHandle = osThreadNew(StartTaskLed, NULL, &taskLed_attributes);

	/* USER CODE BEGIN RTOS_THREADS */
	/* add threads, ... */
	/* USER CODE END RTOS_THREADS */

	/* Create the event(s) */
	/* creation of ledEvent */
	ledEventHandle = osEventFlagsNew(&ledEvent_attributes);

	/* USER CODE BEGIN RTOS_EVENTS */
	/* add events, ... */
	/* USER CODE END RTOS_EVENTS */

}

在freertos.c中有三个自定义的线程可以在IDE的Outline中比较方便的看到

image-20231025115651677

这三个函数就是具体的线程任务,我们看看其中的一个,其他三个都是类似的。

/* USER CODE BEGIN Header_StartTaskKey1 */
/**
 * @brief Function implementing the taskKey1 thread.
 * @param argument: Not used
 * @retval None
 */
/* USER CODE END Header_StartTaskKey1 */
void StartTaskKey1(void *argument) {
	/* USER CODE BEGIN StartTaskKey1 */
	/* Infinite loop */
	for (;;) {
		osDelay(1);
	}
	/* USER CODE END StartTaskKey1 */
}

2.2.2. 添加代码

我们需要在两个按键的线程中读取按键的状态当按钮被按下后设置相应的事件标志位在后在LED线程中等待相应的标志位等待选项设置成 osFlagsWaitAll表示当所有的事件被设置后才相应。

实验中采用第0位和第1位作为事件标志。

两个按键线程的代码分别是:

/* USER CODE BEGIN Header_StartTaskKey1 */
/**
 * @brief Function implementing the taskKey1 thread.
 * @param argument: Not used
 * @retval None
 */
/* USER CODE END Header_StartTaskKey1 */
void StartTaskKey1(void *argument) {
	/* USER CODE BEGIN StartTaskKey1 */
	/* Infinite loop */
	for (;;) {
		osDelay(10);
		if (!HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin))
			osEventFlagsSet(ledEventHandle, 0x01);
	}
	/* USER CODE END StartTaskKey1 */
}
/* USER CODE BEGIN Header_StartTaskKey2 */
/**
 * @brief Function implementing the taskKey2 thread.
 * @param argument: Not used
 * @retval None
 */
/* USER CODE END Header_StartTaskKey2 */
void StartTaskKey2(void *argument) {
	/* USER CODE BEGIN StartTaskKey2 */
	/* Infinite loop */
	for (;;) {
		osDelay(10);
		if (!HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin))
			osEventFlagsSet(ledEventHandle, 0x02);
	}
	/* USER CODE END StartTaskKey2 */
}

使用一个死循环去循环检测按钮每次检测的间隔是10个系统滴答10ms因为系统滴答的频率是1000。注意这里必须使用osDelay函数

如果检测到按键被按下设置事件标志组的标志。按键1设置第0位因此是 0x01按键2设置第1位因此是 0x02。

LED线程等待标志位

/* USER CODE BEGIN Header_StartTaskLed */
/**
 * @brief Function implementing the taskLed thread.
 * @param argument: Not used
 * @retval None
 */
/* USER CODE END Header_StartTaskLed */
void StartTaskLed(void *argument) {
	/* USER CODE BEGIN StartTaskLed */
	/* Infinite loop */
	for (;;) {
		osEventFlagsWait(ledEventHandle, 0x03, osFlagsWaitAll, osWaitForever);
		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
	}
	/* USER CODE END StartTaskLed */
}

因为第0位和第1位都设置后的位掩码对应的数字是 0x03这样当检测到两个标志都设置后才对LED进行翻转。如果osEventFlagsWait等待到事件满足条件后会清空相应的事件标志。

2.2.3. 实验效果

单按一个按钮LED并不会进行状态转换按一个后可以释放再按第二个后LED的状态翻转。

实验3设计

Alt text

注意以下几点:

  1. 从最小的代码量一步步完善。如果一开始就是很多代码,那么一旦错误很难定位是哪里出现问题;从最少的代码逐步完善,那么可以基本保证前面代码的正确性。
  2. 当遇到问题的时候,需要仔细看错误提示;善用跟踪调试工具,并在过程中结合自己的知识,网上的资料和合理的猜想。
  3. 一条路走不通,换条路走,代码的实现方式有很多种。