做这个实验的时候,因为之前有STM32定时器的经验,所以基本上只是复习一下。
但还是发现不懂的地方很多,感觉以前学的一点也不扎实。
定时器+消息队列的思路就是,将定时器的每一次中断所产生的信息打印出来,然后放到任务里去处理,这样可以很好的解决中断的阻塞问题。
那么定时器到底跟延时有什么区别呢?
- 延时函数需要占用CPU的使用权,正在延时的时候其他任务没有CPU的使用权就会拖慢执行效率。
- 定时器是不需要占用CPU的使用权的,它是独立自己运行的。
这里参考了这篇文章:http://t.csdn.cn/e8EIb
做实验的过程中,基本就是参考ESP的编程手册,以及FreeRTOS编程手册。
查看ESP32的数据手册。
定时器有两组,每组有两个。
由定时器+消息队列简单写一个测试的框架。
代码如下:(都有注释!)
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "esp_log.h"
#include "ds_timer.h"
#define TIMER_DIVIDER 16 //分频器,具体看芯片手册
#define TIMER_SCALE (TIMER_BASE_CLK/ TIMER_DIVIDER/1000) //将计数值转换为毫秒
#define TIMER_INTERVAL0_SEC (10) //每10ms触发一次中断
#define TEST_WITH_RELOAD 1 //设置超时自动重装载
static const char *TAG = "MAIN APP";
/*
*定义一个要传递的结构体,主要是传递一下每次中断发出的消息
*/
typedef struct {
uint64_t timer_minute_count;
uint64_t timer_second_count;
}timer_event_t;
timer_event_t g_timer_event;//结构体实例化
xQueueHandle timer_queue;
void IRAM_ATTR timer_group0_isr(void *para)
{
timer_spinlock_take(TIMER_GROUP_0);//中断函数里用自旋锁
int timer_idx = (int) para;//获取是定时器0,还是定时器1
//准备中断中要发的事件数据
//简单定义一个事件
timer_event_t evt;
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0,timer_idx);//获取定时器0组定时器的中断状态
/*
*这里可以进行一些操作
*对结构体进行复制等等
*/
timer_group_enable_alarm_in_isr(TIMER_GROUP_0,timer_idx);//使能报警器,下一次继续触发
//发送事件数据
xQueueSendFromISR(timer_queue,&evt,NULL);
timer_spinlock_give(TIMER_GROUP_0);//释放自旋锁
}
static void example_tg0_timer_init(int timer_idx,bool auto_reload,double timer_interval_sec)//初始化
{
//配置定时器
timer_config_t config ={
.divider = TIMER_DIVIDER, //分频
.counter_dir = TIMER_COUNT_UP, //加计数器
.counter_en = TIMER_PAUSE, //计数器使能
.alarm_en = TIMER_ALARM_EN, //报警器使能
.auto_reload = auto_reload, //自动重载首个计数值
};
timer_init(TIMER_GROUP_0,timer_idx,&config);//定时器初始化
timer_set_counter_value(TIMER_GROUP_0,timer_idx,0x00000000ULL);//设置初始值为0
//设置报警
timer_set_alarm_value(TIMER_GROUP_0,timer_idx,timer_interval_sec * TIMER_SCALE);//10ms一次报警
timer_enable_intr(TIMER_GROUP_0,timer_idx);//使能定时器中断
timer_isr_register(TIMER_GROUP_0,timer_idx,timer_group0_isr,
(void*)timer_idx,ESP_INTR_FLAG_IRAM,NULL); //注册中断函数
timer_start(TIMER_GROUP_0,timer_idx);//开启定时器
}
static void timer_example_evt_task(void *arg)//任务函数
{
while (1) {
timer_event_t evt;
xQueueReceive(timer_queue, &evt, portMAX_DELAY);//接受消息
g_timer_event.timer_minute_count ++;//接受一次加1
//60s 计算一次 刷新时间
if(g_timer_event.timer_minute_count >= 6000){
g_timer_event.timer_minute_count = 0;
}
g_timer_event.timer_second_count ++;
//1s计算一次,10ms*100
if(g_timer_event.timer_second_count >= 100){
g_timer_event.timer_second_count = 0;
ESP_LOGI(TAG,"timer is run");
}
}
}
/*
例程实现
*/
void ds_timer_init(void){
g_timer_event.timer_minute_count = 0;
g_timer_event.timer_second_count = 0;
timer_queue = xQueueCreate(10,sizeof(timer_event_t));//动态创建消息队列,记得freertos.h里配置一下
example_tg0_timer_init(TIMER_0,TEST_WITH_RELOAD,TIMER_INTERVAL0_SEC);//初始化定时器
xTaskCreate(timer_example_evt_task,"timer_evt_task",2048,NULL,5,NULL);//创建任务
}
这样,我们定时器+消息队列的初始化就完成了,之后可以在其他函数里进行调用,因为最后都封装到ds_timer_init里面了。
然后我就放在我的板子上测试了一下。
设置的是1000ms也就是1s打印一下“timer is run”这句话,可以看到是成功的。
虽然说的比较简单,但是我搞了一天....
把定时器,消息队列以及其他各种知识复习一遍花的时间比较多,总体来说成功了。