rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用

基于RT-THREAD STM32F429IGT6 BSP的HWTIMER设备应用


硬件平台:野火STM32挑战者 MCU:STM32F429IGT6
开发工具:
  1. MDK Version:5.25
  2. VSCode
  3. env
  4. STM32CubeMX Version:5.2.1
  5. rt-thread源码 Version:4.0.1

工具获取

  • MDK获取链接
  • VSCode获取链接
  • env工具获取链接
  • STM32CubeMX获取链接
  • RT-Thread源码获取链接

说明:
这次先介绍下HWTIMER设备(硬件定时器设备)的操作方法,然后一步一步完成HWTIMER设备的BSP过程。

该BSP主要实现使用HWTIMER设备管理接口来定时(定时器TIM4),同时使用信号量传递消息,当定时器时间到时,释放一次信号量;线程获得信号量以后蜂鸣器发出“滴”声,其中蜂鸣器硬件图下图:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第1张图片

图1.蜂鸣器硬件原理图

ADC设备的操作方法

应用程序通过RT-Thred提供的I/O设备管理接口操作hwtimer设备,相关函数接口如下表:

表1.I/O设备管理接口API
方法名称 方法描述
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() 关闭定时器设备

1. 查找HWTIMER设备

在使用HWTIMER设备之前需要先查找HWTIMER设备句柄,通过下面函数完成:

rt_device_t rt_device_find(const char* name);
表2.rt_device_find()的输入参数与返回值
参数 描述
name HWTIMER设备名字
返回 描述
设备句柄 无查找到正确的设备后返回对应的设备句柄
RT_NULL 没有查找到设备

根据 rt-thread 设备注册格式,一般注册到设备块的 adc 设备名字为timer0timer1等等。找到了该设备也就是打开了设备

2. 使能HWTIMER设备

跟我们前面使用其他设备一样,具体操作之前都得先使能,使能HWTIMER设备用如下函数完成:

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
表3.rt_device_open()的输入参数与返回值
参数 描述
dev HWTIMER设备句柄
oflags HWTIMER设备使能方式: RT_DEVICE_OFLAG_RDWR (读写方式)
返回 描述
RT_EOK 使能成功
其他值 使能失败

3. 设置HWTIMER设备超时回调函数

读取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))
表4.rt_device_set_rx_indicate()的输入参数与返回值
参数 描述
dev HWTIMER设备句柄
rt_ind 超时回调函数,由用户提供
返回 描述
RT_EOK 设置成功

4. 控制HWTIMER设备

定时器设备的计数频率(定时时间=计数值/计数频率)以及定时模式(单次定时:HWTIMER_MODE_ONESHOT,还是周期定时:HWTIMER_MODE_PERIOD)由如下函数完成:

rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
表3.rt_device_control()的输入参数与返回值
参数 描述
dev HWTIMER设备句柄
cmd 计数频率设置还是定时模式设置命令字 ,见表4
arg 计数频率或者定时模式的参数
返回 描述
RT_EOK 关闭成功
-RT_ENOSYS 关闭失败,设备操作方法为空
其他值 关闭失败
表4.cmd命令字一览表
参数 描述
HWTIMER_CTRL_FREQ_SET 设置计数频率
HWTIMER_CTRL_STOP 停止定时器
HWTIMER_CTRL_INFO_GET 获取定时器特征信息
HWTIMER_CTRL_MODE_SET 设置定时器模式

5. 设置HWTIMER设备的超时值

定时器设备的超时值(即定时时间)由如下函数完成:

rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);

该函数设置完之后,定时器变立即启动。

表5.rt_device_write()的输入参数与返回值
参数 描述
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;

6. 获取HWTIMER设备的当前值

定时器设备的当前值由如下函数完成:

rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);

该函数设置完之后,定时器变立即启动。

表6.rt_device_read()的输入参数与返回值
参数 描述
dev HWTIMER设备句柄
pos 定时时间偏移量,一般不使用,为0
buffer 指向定时器的超时时间结构体的指针
size 超时时间结构体的大小
返回 描述
RT_EOK 获取失败
写入数据的实际大小 获取成功

7. 关闭HWTIMER设备

关闭定时器设备由如下函数完成:

rt_err_t rt_device_close(rt_device_t dev);

该函数设置完之后,定时器变立即启动。

表7.rt_device_close()的输入参数与返回值
参数 描述
dev HWTIMER设备句柄
返回 描述
RT_EOK 关闭成功
-RT_ERROR 关闭失败,或者是重复关闭
其他值 关闭失败

HWTIMER设备定时误差

定时器设备定时的时候有可能会出现定时误差,下面进行说明。

根据STM32F4数据手册里面关于定时器特性比较表如下图,我们可以知道不同的定时器的特性。rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第2张图片

图2.定时器的特新比较表

其中高级定时器TIM1TIM8:计数器为16位(即最大计数值为0xFFFF,也就是65535。如果1us计一个数的话,计数满时为65535us=65.535ms),计数类型可从下往上计数,也可从上往下计数,分频因子为1~65536,最大的计数频率为180MHz。

其中通用定时器TIM2TIM5:计数器为32位(即最大计数值为0xFFFFFFFF,也就是4294967295),计数类型可从下往上计数,也可从上往下计数,分频因子为1~65536,最大的计数频率为180MHz。

现在以TIM1或者TIM8为例(TIM2TIM5类似):

如果我们通过定时器的预分频寄存器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计一个数。

本次bsp使用的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

这次的HWTIMER设备的bsp,与上一节rt-thread:_003STM32F429IGT BSP的ADC设备应用类似,也需要使用STM32CubeMX先开启HWTIMER片上外设,才能再通过HWTIMER设备驱动去操作完成应用功能。

下面开始HWTIMER设备的BSP:

STM32CubeMX 配置

  • 第一步

打开stm32f429-hlg-v1.0\board\CubeMX_Config下面的CubeMX_Config.ioc工程,依次配置如下图所示:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第3张图片

图3.定时器片上外设配置

注意: 一定要点击序号4生成代码,不然配置没起作用。

这样HWTIMER片上外设就配置好了。

工程配置

这下步骤又跟rt-thread:_003STM32F429IGT BSP的ADC设备应用基本一样了,也是在模板rt-thread:_001STM32F429IGT BSP前期准备的基础上加入新的bsp应用程序即可。

  • 第一步:

首先新建两个文件bsp_hwtimer.cbsp_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"配置模块下面添加如下图内容:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第4张图片

图4.Kconfig 添加 RT_USING_HWTIMER 宏配置

并在menu "On-chip Peripheral Drivers"配置下面添加下图内容:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第5张图片

图5.Kconfig 再添加 BSP_USING_TIM4 宏配置

有了这两个宏,那么在env工具里面就可以很方便的开启或者关闭该宏,如果开启该宏,则在rtconfig.h里面就会生成该宏,同时keil工程自动增加bsp_hwtimer.c文件。

  • 第三步:

打开stm32f429-hlg-v1.0\applications下面的SConscript编译链接脚本文件,在里面添加如下图内容:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第6张图片

图6.SConscript 添加源文件编译链接宏 RT_USING_HWTIMER
  • 第四步:

env里输入menuconfig打开工程配置,开启Enable hwtimer,如下图:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第7张图片

图7.开启 hwtimer

然后再去开启hwtimer设备具体的编号,如下图:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第8张图片

图8.开启 TIM4

然后一直按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工程,然后就可以看到现在的工程目录结构如下图:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第9张图片

图9.keil 工程目录结构

然后编译可看到如下图错误提示:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第10张图片

图10.keil 编译错误说明

那么我们去操作系统源码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,便可以看到定时器的定时情况。
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第11张图片

图11.FinSH 终端

由输出能够看到,程序设置延时3500ms获取定时器当前值,打印出来的是3499ms,误差仅为1ms。

系统tick时间由超时函数获取输出,每次相邻两个值相减正好是5000ms,即是我们的定时时间5s,没有误差。由前面介绍知,在10000Hz的计数频率下(即100us计一个数),定时5s只需要计数50000个,还没有超出该定时器的最大计数值65535。

再输入查看线程指令list_thread,便可以看到bsp_sem线程的运行基本信息,如下图:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第12张图片

图12.FinSH 终端

再输入查看设备指令list_device,便可以看到hwtimer设备的挂载的基本信息,如下图:
rt-thread:_004STM32F429IGT BSP的HWTIMER设备应用_第13张图片

图13.FinSH 终端

到此,一个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设备管理-官网连接
内核-线程间同步:信号量

你可能感兴趣的:(操作系统:RT-Thread)