说明:
这次先介绍下HWTIMER设备
(硬件定时器设备)的操作方法,然后一步一步完成HWTIMER设备的BSP过程。
该BSP主要实现使用HWTIMER设备管理接口
来定时(定时器TIM4
),同时使用信号量
传递消息,当定时器时间到时,释放一次信号量;线程获得信号量以后蜂鸣器发出“滴”声,其中蜂鸣器硬件图下图:
应用程序通过RT-Thred提供的I/O设备管理接口操作hwtimer设备
,相关函数接口如下表:
方法名称 | 方法描述 |
---|---|
rt_device_find() | 根据 定时器 设备名称查找设备获取设备句柄 |
rt_device_open() | 根据 以读写方式打开定时器 RT_DEVICE_FLAG_RDWR |
rt_device_set_rx_indicate() | 设置超时回调函数 |
rt_device_control() | 控制定时器设备,可以设置定时模式(单次/周期)/计数频率,或者停止定时器 |
rt_device_write() | 设置定时器超时值,定时器随即启动 |
rt_device_read() | 获取定时器当前值 |
rt_device_close() | 关闭定时器设备 |
在使用HWTIMER设备之前需要先查找HWTIMER设备句柄,通过下面函数完成:
rt_device_t rt_device_find(const char* name);
参数 | 描述 |
---|---|
name | HWTIMER设备名字 |
返回 | 描述 |
设备句柄 | 无查找到正确的设备后返回对应的设备句柄 |
RT_NULL | 没有查找到设备 |
根据 rt-thread 设备注册格式,一般注册到设备块的 adc 设备名字为timer0
、timer1
等等。找到了该设备也就是打开了设备
跟我们前面使用其他设备一样,具体操作之前都得先使能,使能HWTIMER设备用如下函数完成:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数 | 描述 |
---|---|
dev | HWTIMER设备句柄 |
oflags | HWTIMER设备使能方式: RT_DEVICE_OFLAG_RDWR (读写方式) |
返回 | 描述 |
RT_EOK | 使能成功 |
其他值 | 使能失败 |
读取HWTIMER设备超时回调函数使用如下函数完成:
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size))
参数 | 描述 |
---|---|
dev | HWTIMER设备句柄 |
rt_ind | 超时回调函数,由用户提供 |
返回 | 描述 |
RT_EOK | 设置成功 |
定时器设备的计数频率
(定时时间=计数值/计数频率)以及定时模式
(单次定时:HWTIMER_MODE_ONESHOT,还是周期定时:HWTIMER_MODE_PERIOD)由如下函数完成:
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数 | 描述 |
---|---|
dev | HWTIMER设备句柄 |
cmd | 计数频率设置还是定时模式设置命令字 ,见表4 |
arg | 计数频率或者定时模式的参数 |
返回 | 描述 |
RT_EOK | 关闭成功 |
-RT_ENOSYS | 关闭失败,设备操作方法为空 |
其他值 | 关闭失败 |
参数 | 描述 |
---|---|
HWTIMER_CTRL_FREQ_SET | 设置计数频率 |
HWTIMER_CTRL_STOP | 停止定时器 |
HWTIMER_CTRL_INFO_GET | 获取定时器特征信息 |
HWTIMER_CTRL_MODE_SET | 设置定时器模式 |
定时器设备的超时值(即定时时间)由如下函数完成:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
该函数设置完之后,定时器变立即启动。
参数 | 描述 |
---|---|
dev | HWTIMER设备句柄 |
pos | 定时时间偏移量,一般不使用,为0 |
buffer | 指向定时器的超时时间结构体的指针 |
size | 超时时间结构体的大小 |
返回 | 描述 |
RT_EOK | 设置失败 |
写入数据的实际大小 | 写入数据的实际大小 |
超时时间的结构体原型如下:
typedef struct rt_hwtimerval
{
rt_int32_t sec; /* 秒 s */
rt_int32_t usec; /* 微秒 us */
} rt_hwtimerval_t;
定时器设备的当前值由如下函数完成:
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
该函数设置完之后,定时器变立即启动。
参数 | 描述 |
---|---|
dev | HWTIMER设备句柄 |
pos | 定时时间偏移量,一般不使用,为0 |
buffer | 指向定时器的超时时间结构体的指针 |
size | 超时时间结构体的大小 |
返回 | 描述 |
RT_EOK | 获取失败 |
写入数据的实际大小 | 获取成功 |
关闭定时器设备由如下函数完成:
rt_err_t rt_device_close(rt_device_t dev);
该函数设置完之后,定时器变立即启动。
参数 | 描述 |
---|---|
dev | HWTIMER设备句柄 |
返回 | 描述 |
RT_EOK | 关闭成功 |
-RT_ERROR | 关闭失败,或者是重复关闭 |
其他值 | 关闭失败 |
定时器设备定时的时候有可能会出现定时误差,下面进行说明。
根据STM32F4数据手册里面关于定时器特性比较表如下图,我们可以知道不同的定时器的特性。
其中高级定时器TIM1
和TIM8
:计数器为16位(即最大计数值为0xFFFF,也就是65535。如果1us计一个数的话,计数满时为65535us=65.535ms),计数类型可从下往上计数,也可从上往下计数,分频因子为1~65536,最大的计数频率为180MHz。
其中通用定时器TIM2
和TIM5
:计数器为32位(即最大计数值为0xFFFFFFFF,也就是4294967295),计数类型可从下往上计数,也可从上往下计数,分频因子为1~65536,最大的计数频率为180MHz。
现在以TIM1
或者TIM8
为例(TIM2
和TIM5
类似):
如果我们通过定时器的预分频寄存器TIMx_PSC
把定时器的计数频率设置为1MHz(即1us记一次数),那么要定时100ms又5us的话,常规我们一般是让定时器以50000us定时2次完成,那么这样的话就会有5us误差。当然,若是让定时器以20001us定时5次完成的话,那就没有误差。
在HWTIMER设备中,定时器的计数频率是由前面介绍的rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg)
函数完成,其中cmd
命令字为定时器计数频率设置(HWTIMER_CTRL_FREQ_SET
)的话,那么arg
参数将是具体的计数频率值。比如传给arg
的值为10000,即代表1s计10000个数,也就是说计数频率是10000Hz = 10KHz,那么计一个数就是 1 10000 s = 0.1 m s = 100 u s \frac{1}{10000} s = 0.1ms = 100us 100001s=0.1ms=100us 。
如果我们不设置计数频率的话,HWTIMER设备默认的计数频率是1MHz,即1us计一个数。
TIM4
为16位的通用定时器,最大计数值65535,同时设置计数频率为10000Hz,即100us计一个数,那么计满数时为 65535 × 100 u s = 6553.5 m s = 6.5535 s 65535\times100us = 6553.5ms =6.5535s 65535×100us=6553.5ms=6.5535s。所以,如果定时时间不是100us的整数倍,定时将会有误差,误差100us。如果需要减小误差,需要往大更改计数频率。这次的HWTIMER设备
的bsp,与上一节rt-thread:_003STM32F429IGT BSP的ADC设备应用类似,也需要使用STM32CubeMX
先开启HWTIMER片上外设
,才能再通过HWTIMER设备驱动去操作完成应用功能。
下面开始HWTIMER设备的BSP:
打开stm32f429-hlg-v1.0\board\CubeMX_Config
下面的CubeMX_Config.ioc
工程,依次配置如下图所示:
注意: 一定要点击序号4
生成代码,不然配置没起作用。
这样HWTIMER片上外设就配置好了。
这下步骤又跟rt-thread:_003STM32F429IGT BSP的ADC设备应用基本一样了,也是在模板rt-thread:_001STM32F429IGT BSP前期准备的基础上加入新的bsp应用程序即可。
首先新建两个文件bsp_hwtimer.c
和bsp_hwtimer.h
;然后把bsp_hwtimer.c
放入bsp_src
文件夹里,bsp_hwtimer.h
放入bsp_inc
文件夹里。同时在main.h
里面添加bsp_adc的头文件,如下:
#include "bsp_inc/bsp_hwtimer.h"
其中bsp_hwtimer.h
里面的代码如下:
#ifndef __BSP_HWTIMER_H
#define __BSP_HWTIMER_H
#include "applications/main.h"
#define BEEP_PIN GET_PIN(I, 11) /* 定义蜂鸣器引脚 */
#define HWTIMER_DEV_NAME "timer4" /* 定义硬件定时器设备名字 */
/* 定义bsp_sem线程的优先级 */
#define BSP_SEM_PRIORITY 10
/* 声明相关函数 */
static rt_err_t bsp_hwtimer_cb(rt_device_t dev, rt_size_t size);
static rt_uint8_t bsp_hwtimer_sample(int argc, char *argv[]);
static void bsp_sem_thread_entry(void *parameter);
#endif
其中bsp_adc.c
里面的代码如下:
#include "bsp_hwtimer.h"
static rt_sem_t bsp_sem = RT_NULL; /* 定义信号量 */
/***************************************************************
* 函数: static rt_err_t bsp_hwtimer_cb(rt_device_t dev, rt_size_t size)
* 参数: dev 硬件定时器设备句柄
* size 长度
* 返回值:0
* 功能: 硬件定时器超时回调函数,释放信号量
*****************************************************************/
static rt_err_t bsp_hwtimer_cb(rt_device_t dev, rt_size_t size)
{
/* 定时时间到释放信号量 */
if(rt_sem_release(bsp_sem) == RT_EOK)
{
rt_kprintf("this is hwtimer timeout callback function!, semaphore have released!\n"); /* 打印回调信息 */
rt_kprintf("tick is %d !\n", rt_tick_get()); /* 打印系统tick值 */
}
return RT_EOK;
}
/***************************************************************
* 函数: static rt_uint8_t bsp_hwtimer_sample(int argc, char *argv[])
* 参数: int argc, char *argv[]
* 返回值:无
* 功能: 创建信号量,设置蜂鸣器引脚默认模式、查找硬件定时器设备,设置定时器相关信息
*****************************************************************/
static rt_uint8_t bsp_hwtimer_sample(int argc, char *argv[])
{
static rt_device_t bsp_hwtim_dev = RT_NULL; /* 定义硬件定时器设备控制块 */
static rt_hwtimerval_t timeout_s; /* 定义硬件定时器超时时间结构体 */
static rt_thread_t bsp_sem_tid = RT_NULL; /* 定义信号量控制块 */
rt_err_t ret = RT_EOK;
rt_hwtimer_mode_t hwmode = HWTIMER_MODE_PERIOD; /* 定义硬件定时器定时模式:周期定时 */
rt_uint32_t hwfreq = 10000; /* 定义硬件定时器计数频率:10000Hz=10KHz */
/* 设置蜂鸣器引脚默认模式,低电平不响 */
rt_pin_mode(BEEP_PIN, PIN_MODE_OUTPUT);
rt_pin_write(BEEP_PIN, PIN_LOW);
/* 创建信号量,初始值为0,先进先出原则 */
bsp_sem = rt_sem_create("bsp_sem", 0, RT_IPC_FLAG_FIFO);
/* 查找硬件定时器设备 */
bsp_hwtim_dev = rt_device_find(HWTIMER_DEV_NAME);
if (bsp_hwtim_dev == RT_NULL)
{
rt_kprintf("hwtimer device sample run failed! can't find %s device!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
/* 以读写方式打开硬件定时器 */
ret = rt_device_open(bsp_hwtim_dev, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK)
{
rt_kprintf("open %s hwtimer failed!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
/* 设置硬件定时器超时回调函数 */
rt_device_set_rx_indicate(bsp_hwtim_dev, bsp_hwtimer_cb);
/* 设置硬件定时器的定时模式为周期定时 */
ret = rt_device_control(bsp_hwtim_dev, HWTIMER_CTRL_MODE_SET, &hwmode);
if (ret != RT_EOK)
{
rt_kprintf("set %s hwtimer mode failed!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
/* 设置硬件定时器的计数频率为10000Hz */
ret = rt_device_control(bsp_hwtim_dev, HWTIMER_CTRL_FREQ_SET, &hwfreq);
if (ret != RT_EOK)
{
rt_kprintf("set %s hwtimer frequency failed!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
timeout_s.sec = 5;
timeout_s.usec = 0;
/* 设置硬件定时器的超时时间为5s,并启动定时器 */
if(rt_device_write(bsp_hwtim_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
{
rt_kprintf("set %s timeout value failed!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
/* 延时3500ms后获取定时器当前值,并打印出来 */
rt_thread_mdelay(3500);
rt_device_read(bsp_hwtim_dev, 0, &timeout_s, sizeof(timeout_s));
rt_kprintf("READ : Sec = %d, Usec = %d \n", timeout_s.sec, timeout_s.usec);
/* 创建信号量线程 */
bsp_sem_tid = rt_thread_create("bsp_sem_tid", bsp_sem_thread_entry, RT_NULL,
512, BSP_SEM_PRIORITY, 10);
/* 启动信号量线程 */
if (bsp_sem_tid != RT_NULL)
{
rt_thread_startup(bsp_sem_tid);
}
return RT_EOK;
}
/* 导出bsp_hwtimer_sample命令至FinSH终端 */
MSH_CMD_EXPORT(bsp_hwtimer_sample, hwtimer sample);
/* 信号量线程 */
static void bsp_sem_thread_entry(void *parameter)
{
while (1)
{
/* 获取到信号量后蜂鸣器发出“滴”声 */
if(rt_sem_take(bsp_sem, RT_WAITING_FOREVER) == RT_EOK)
{
rt_pin_write(BEEP_PIN, PIN_HIGH);
rt_thread_mdelay(200);
rt_pin_write(BEEP_PIN, PIN_LOW);
}
}
}
打开stm32f429-hlg-v1.0\board
下面的Kconfig
文件,在该文件的 menu "BSP module"
配置模块下面添加如下图内容:
并在menu "On-chip Peripheral Drivers"
配置下面添加下图内容:
有了这两个宏,那么在env
工具里面就可以很方便的开启或者关闭该宏,如果开启该宏,则在rtconfig.h
里面就会生成该宏,同时keil工程
自动增加bsp_hwtimer.c
文件。
打开stm32f429-hlg-v1.0\applications
下面的SConscript
编译链接脚本文件,在里面添加如下图内容:
在env
里输入menuconfig
打开工程配置,开启Enable hwtimer
,如下图:
然后一直按Esc
至保存界面,选Yes
保存退出。
说明: 如果是先去开启TIM4
,然后再开启Enable hwtimer
,那么会发现Enable hwtimer
已经自动开启了。因为这两个使能项的配置宏都是RT_USING_HWTIMER
。
在env
输入scons --target=mdk5
重新生成keil5
工程;输入scons --target=mdk4
重新生成keil4
工程;输入scons --target=iar
重新生成iar
工程;输入scons --target=vsc
更新VSCode
头文件路径。
打开keil5
工程,然后就可以看到现在的工程目录结构如下图:
那么我们去操作系统源码bsp\stm32\libraries\HAL_Drivers\config\f4
路径下找到tim_config.h
文件,在该文件里面模仿其它定时器做如下定义:
#ifdef BSP_USING_TIM4
#ifndef TIM4_CONFIG
#define TIM4_CONFIG \
{ \
.tim_handle.Instance = TIM4, \
.tim_irqn = TIM4_IRQn, \
.name = "timer4", \
}
#endif /* TIM4_CONFIG */
#endif /* BSP_USING_TIM4 */
然后再编译,就发现没有错误也没有警告了。
下载程序到开发板。然后打开终端工具。在中断输入例程的导出命令bsp_hwtimer_sample
,便可以看到定时器的定时情况。
由输出能够看到,程序设置延时3500ms获取定时器当前值,打印出来的是3499ms,误差仅为1ms。
系统tick
时间由超时函数获取输出,每次相邻两个值相减正好是5000ms,即是我们的定时时间5s,没有误差。由前面介绍知,在10000Hz的计数频率下(即100us计一个数),定时5s只需要计数50000个,还没有超出该定时器的最大计数值65535。
再输入查看线程指令list_thread
,便可以看到bsp_sem线程
的运行基本信息,如下图:
再输入查看设备指令list_device
,便可以看到hwtimer设备
的挂载的基本信息,如下图:
到此,一个hwtimer设备
实现精准定时的bsp就彻底完成了。
该bsp同样是在rt-thread:_001STM32F429IGT BSP前期准备的基础上增加hwtimer的bsp。再结合env工具,可以快速实现工程配置。
rt-thread:_001STM32F429IGT BSP前期准备
rt-thread:_002STM32F429IGT BSP的PIN设备应用
rt-thread:_003STM32F429IGT BSP的ADC设备应用
RT-Thread HWTIMER设备管理-官网连接
内核-线程间同步:信号量