|
|
|
@ -74,6 +74,14 @@
|
|
|
|
|
- [代码](#代码)
|
|
|
|
|
- [运行效果和代码分析](#运行效果和代码分析)
|
|
|
|
|
- [3.6. 软件定时器](#36-软件定时器)
|
|
|
|
|
- [函数接口](#函数接口)
|
|
|
|
|
- [软件定时器创建函数(osTimerNew)](#软件定时器创建函数ostimernew)
|
|
|
|
|
- [软件定时器启动函数(osTimerStart)](#软件定时器启动函数ostimerstart)
|
|
|
|
|
- [软件定时器停止函数(osTimerStop)](#软件定时器停止函数ostimerstop)
|
|
|
|
|
- [应用示例](#应用示例)
|
|
|
|
|
- [配置](#配置-1)
|
|
|
|
|
- [代码编写](#代码编写)
|
|
|
|
|
- [代码分析](#代码分析)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 概述
|
|
|
|
@ -655,6 +663,15 @@ FreeRTOS提供了事件标志组的功能,每一个事件标志组最多具有
|
|
|
|
|
|
|
|
|
|
#### 3.2.2.2. 代码编写
|
|
|
|
|
|
|
|
|
|
在 USER CODE BEGIN 4 部分加入以下代码:
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
/* USER CODE BEGIN 4 */
|
|
|
|
|
#define EVENT1 0x01
|
|
|
|
|
#define EVENT2 0x02
|
|
|
|
|
/* USER CODE END 4 */
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
补充 StartEventTask 代码:
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
@ -969,3 +986,118 @@ osMessageQueuePut(ComQueueHandle, RxBuffer, 0, 0);
|
|
|
|
|
为什么?无解。
|
|
|
|
|
|
|
|
|
|
## 3.6. 软件定时器
|
|
|
|
|
|
|
|
|
|
在嵌入式系统设计中,常常有这样的功能需求:一个任务周期性地运行或者在将来某个时刻运行,以实现定时或者延时控制,这个功能可以利用定时器实现。
|
|
|
|
|
|
|
|
|
|
从定时器的实现角度来看,可以分为硬件定时器和软件定时器。硬件定时器数量有限,往往可以实现除了简单定时而外的其他功能;作为简单的定时而言,软件定时器可能更适合。
|
|
|
|
|
|
|
|
|
|
软件定时器的工作方式和硬件定时器类似:设置一个定时时间后,启动定时器运行预设的定时时间到达后,可以调用一个回调函数来执行相应的操作。
|
|
|
|
|
|
|
|
|
|
软件定时器可以配置为单次触发或者周期触发:单次触发定时器执行一次回调函数后将不再运行;周期触发定时器则重复执行回调函数,直到用户停止或删除该定时器为止。所有软件定时器都可以启动、停止或重新启动。
|
|
|
|
|
|
|
|
|
|
### 函数接口
|
|
|
|
|
|
|
|
|
|
#### 软件定时器创建函数(osTimerNew)
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
#### 软件定时器启动函数(osTimerStart)
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
#### 软件定时器停止函数(osTimerStop)
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
### 应用示例
|
|
|
|
|
|
|
|
|
|
建立两个软件定时器:一个是周期触发定时器,每隔100ms通过串口向PC发送数据;一个是单次触发定时器,控制指示灯在系统启动5s后再开启。
|
|
|
|
|
|
|
|
|
|
#### 配置
|
|
|
|
|
|
|
|
|
|
1. 新建一个工程,取名OsTimer;注意:需要设置SYS中的Debug为 Serial Wire;另外固件版本可能需要调整,1.8.6的有问题;
|
|
|
|
|
2. 同样在 SYS 中,配置Timebase Source 为一个没有使用的定时器,如TIM4;
|
|
|
|
|
3. 设置PA7为输出,并设置Label为LED;
|
|
|
|
|
4. 打开串口2,不用打开中断;
|
|
|
|
|
5. 配置中间件,打开FREERTOS,Interface设置成 CMSIS_V2;
|
|
|
|
|
6. FreeRTOS 的 Advanced settings 中打开 NEWLIB 的选项,否则要提示警告;
|
|
|
|
|
7. 建立一个任务
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 代码编写
|
|
|
|
|
|
|
|
|
|
在 USER CODE BEGIN Includes 代码段补充以下代码:
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
/* USER CODE BEGIN Includes */
|
|
|
|
|
#include <string.h>
|
|
|
|
|
/* USER CODE END Includes */
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在 USER CODE BEGIN 0 补充以下代码
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
/* USER CODE BEGIN 0 */
|
|
|
|
|
osTimerId_t ComTimerHandle;
|
|
|
|
|
osTimerId_t LedTimerHandle;
|
|
|
|
|
|
|
|
|
|
char *MSG = "Periodic Virtual Timer:run every 1s.\n";
|
|
|
|
|
void ComCallback(void *argument) {
|
|
|
|
|
HAL_UART_Transmit(&huart2, (uint8_t*) MSG, strlen(MSG), 100); // 发送接收到的数据
|
|
|
|
|
}
|
|
|
|
|
void LedCallback(void *argument) {
|
|
|
|
|
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
|
|
|
|
|
osDelay(200);
|
|
|
|
|
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
|
|
|
|
|
}
|
|
|
|
|
/* USER CODE END 0 */
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
完善 StartInitTimer 任务函数
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
/* USER CODE END Header_StartInitTimer */
|
|
|
|
|
void StartInitTimer(void *argument) {
|
|
|
|
|
/* USER CODE BEGIN 5 */
|
|
|
|
|
ComTimerHandle = osTimerNew(ComCallback, osTimerPeriodic, NULL, NULL);
|
|
|
|
|
LedTimerHandle = osTimerNew(LedCallback, osTimerOnce, NULL, NULL);
|
|
|
|
|
osTimerStart(ComTimerHandle, 1000);
|
|
|
|
|
osTimerStart(LedTimerHandle, 5000);
|
|
|
|
|
for (;;) {
|
|
|
|
|
osDelay(1000);
|
|
|
|
|
}
|
|
|
|
|
/* USER CODE END 5 */
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 代码分析
|
|
|
|
|
|
|
|
|
|
按照书上的方法,启动定时器不能简单的在main 函数中完成。首先分析一下main函数的流程:
|
|
|
|
|
|
|
|
|
|
```c
|
|
|
|
|
int main(void) {
|
|
|
|
|
HAL_Init(); // 1. 硬件代码初始化
|
|
|
|
|
SystemClock_Config(); // 2. 系统时钟配置
|
|
|
|
|
MX_GPIO_Init(); // 3. GPIO初始化
|
|
|
|
|
MX_USART2_UART_Init(); // 4. 串口初始化
|
|
|
|
|
osKernelInitialize(); // 5. 操作系统初始化
|
|
|
|
|
InitTimerHandle = osThreadNew(StartInitTimer, NULL, &InitTimer_attributes); // 6. 建立一个任务
|
|
|
|
|
osKernelStart(); // 7. 操作系统正式运行
|
|
|
|
|
while (1) {
|
|
|
|
|
// 8. 非操作系统的循环代码
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
1. 正常情况下,当运行到7的位置时,操作系统进行自己的任务调度循环,这时,7之后的代码永远不能运行;
|
|
|
|
|
2. 在7之前的代码都是初始化的代码,操作系统也没有运行;
|
|
|
|
|
3. 因此启动定时器的代码放在7之前是不合适的,因为操作系统还没有运行,无法启动定时器;
|
|
|
|
|
4. 启动定时器的代码放在7之后也是不合适的,因为7之后的代码正常情况下永远不会运行;
|
|
|
|
|
5. 因此,我们需要一个独立的任务,在操作系统运行后对定时器进行启动,因此有了 StartInitTimer 这个任务;这个任务的目的就是在操作系统运行的时候启动定时器;
|
|
|
|
|
6. 按理说,这个任务可以是一次性任务,不需要后面的死循环;但是删除后面的死循环后,代码无法正常运行;
|
|
|
|
|
7. 有猜测操作系统至少应该有一个周期性任务,因此另外添加一个任务(保留该任务中的死循环);并删除 StartInitTimer 任务中的死循环,让 StartInitTimer 成为一次性任务,依旧不能正常运行。具体原因还需要进一步分析。
|
|
|
|
|