Timer(legacy_driver)

今回はTimer(legacy_driver)です。サンプルプログラムはesp-idfがインストールされているパス、~/esp/esp-idf/examples/peripherals/timer_group/legacy_driverを使います。 mainフォルダーの下に、timer_group_example_main.c といるファイルが有ります。

timer_group_example_main.c

/*
 * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/timer.h"
#include "esp_log.h"

#define TIMER_RESOLUTION_HZ   1000000 // 1MHz resolution
#define TIMER_ALARM_PERIOD_S  0.5     // Alarm period 0.5s

static const char *TAG = "example";

/**
 * @brief A sample structure to pass events from the timer ISR to task
 */
typedef struct {
    uint64_t timer_count_value;
} example_timer_event_t;

/**
 * @brief Timer user data, will be pass to timer alarm callback
 */
typedef struct {
    QueueHandle_t user_queue;
    int timer_group;
    int timer_idx;
    int alarm_value;
    bool auto_reload;
} example_timer_user_data_t;

static bool IRAM_ATTR timer_group_isr_callback(void *args)
{
    BaseType_t high_task_awoken = pdFALSE;
    example_timer_user_data_t *user_data = (example_timer_user_data_t *) args;
    // fetch current count value
    uint64_t timer_count_value = timer_group_get_counter_value_in_isr(user_data->timer_group, user_data->timer_idx);
    example_timer_event_t evt = {
        .timer_count_value = timer_count_value,
    };

    // set new alarm value if necessary
    if (!user_data->auto_reload) {
        user_data->alarm_value += TIMER_ALARM_PERIOD_S * TIMER_RESOLUTION_HZ;
        timer_group_set_alarm_value_in_isr(user_data->timer_group, user_data->timer_idx, user_data->alarm_value);
    }

    // Send the event data back to the main program task
    xQueueSendFromISR(user_data->user_queue, &evt, &high_task_awoken);

    return high_task_awoken == pdTRUE; // return whether a task switch is needed
}

static void example_tg_timer_init(example_timer_user_data_t *user_data)
{
    int group = user_data->timer_group;
    int timer = user_data->timer_idx;

    timer_config_t config = {
        .clk_src = TIMER_SRC_CLK_APB,
        .divider = APB_CLK_FREQ / TIMER_RESOLUTION_HZ,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = user_data->auto_reload,
    };
    ESP_ERROR_CHECK(timer_init(group, timer, &config));

    // For the timer counter to a initial value
    ESP_ERROR_CHECK(timer_set_counter_value(group, timer, 0));
    // Set alarm value and enable alarm interrupt
    ESP_ERROR_CHECK(timer_set_alarm_value(group, timer, user_data->alarm_value));
    ESP_ERROR_CHECK(timer_enable_intr(group, timer));
    // Hook interrupt callback
    ESP_ERROR_CHECK(timer_isr_callback_add(group, timer, timer_group_isr_callback, user_data, 0));
    // Start timer
    ESP_ERROR_CHECK(timer_start(group, timer));
}

static void example_tg_timer_deinit(int group, int timer)
{
    ESP_ERROR_CHECK(timer_isr_callback_remove(group, timer));
    ESP_ERROR_CHECK(timer_deinit(group, timer));
}

void app_main(void)
{
    example_timer_user_data_t *user_data = calloc(1, sizeof(example_timer_user_data_t));
    assert(user_data);
    user_data->user_queue = xQueueCreate(10, sizeof(example_timer_event_t));
    assert(user_data->user_queue);
    user_data->timer_group = 0;
    user_data->timer_idx = 0;
    user_data->alarm_value = TIMER_ALARM_PERIOD_S * TIMER_RESOLUTION_HZ;


    ESP_LOGI(TAG, "Init timer with auto-reload");
    user_data->auto_reload = true;
    example_tg_timer_init(user_data);

    example_timer_event_t evt;
    uint32_t test_count = 4;
    while (test_count--) {
        xQueueReceive(user_data->user_queue, &evt, portMAX_DELAY);
        ESP_LOGI(TAG, "Timer auto reloaded, count value in ISR: %llu", evt.timer_count_value);
    }
    example_tg_timer_deinit(user_data->timer_group, user_data->timer_idx);

    ESP_LOGI(TAG, "Init timer without auto-reload");
    user_data->auto_reload = false;
    example_tg_timer_init(user_data);

    test_count = 4;
    while (test_count--) {
        xQueueReceive(user_data->user_queue, &evt, portMAX_DELAY);
        ESP_LOGI(TAG, "Timer alarmed at %llu", evt.timer_count_value);
    }
    example_tg_timer_deinit(user_data->timer_group, user_data->timer_idx);

    vQueueDelete(user_data->user_queue);
    free(user_data);
}

いつもの事ですが、サンプルプログラム難し過ぎます。そこでタイマーに付いて調べて見ました。

  • グループ0,1。各グループにタイマー0,1の計4個のタイマーが有る。
  • 各タイマーは64ビット長で16ビットのプリスケーラを持っている。
  • 基本となるクロックは、TIMER_SRC_CLK_APB(0)とTIMER_SRC_CLK_XTAL(1)の2種類
  • 基本クロックを16ビットのプリスケーラで分周し、64ビットのカウンターでカウントする。
  • カウント方向は、アップ、ダウンの指定が可能。
  • アラーム機能が有り、アラームで割り込みをかける事が可能。

先ずは、グループ0、タイマー0を使ってタイマーのスタートで値を表示するプログラムを書いてみました。

timer_group_example_main.c

#include <stdio.h>
#include "driver/timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TIMER_RESOLUTION_HZ   1000000 // 1MHz resolution

void print_timer()
{
	uint64_t timer_count_value;
	double 	timer_sec;
	int group,timer;
	
	int a;
	
	group = timer = 0;
    timer_start(group, timer);		// Start timer
    for(a = 0; a < 5; a ++)
    {
    	// get_counter_value
    	timer_get_counter_value(group, timer, &timer_count_value);
    	
    	// get_counter_time_sec
    	timer_get_counter_time_sec(group, timer, &timer_sec);
    	
    	printf("Count: %llu, sec: %f\n",timer_count_value, timer_sec);  
        
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
    timer_pause(group, timer);		// Pause timer
    printf("Stop Timer\n");  
}

void app_main(void)
{
	int group,timer;
	
	timer_config_t config = {
        .clk_src = TIMER_SRC_CLK_APB,
        .divider = APB_CLK_FREQ / TIMER_RESOLUTION_HZ,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = true,
    };

	group = timer = 0;
    timer_init(group, timer, &config);
    
    // counter = 0
    printf("Start value is 0\n");  
    timer_set_counter_value(group, timer, 0);
	print_timer();

    // counter = 0xfffffffffff00000
    printf("Start value is %llu\n", 0xfffffffffff00000);  
	timer_set_counter_value(group, timer, 0xfffffffffff00000);
	print_timer();
}

  • 38行:タイマー用パラメータの設定
    • .clk_src: ソースとなるクロックの指定
    • .divider: プリスケーラの指定
    • .counter_dir: カウント方向の指定
    • .counter_en: タイマーの開始
    • .alarm_en: アラームの開始
    • .auto_reload: アラーム値の再設定。
    • この設定で、
      • カウンターの分解能:1usec
      • アップカウンター
      • タイマー停止
      • アラーム有効
      • アラーム停止
  • 48行:timer_init(group, timer, &config); タイマーの設定
    • 第1引数: タイマーのグループ 0,1
    • 第2引数: グループ内のタイマ 0,1
    • 第3引数: タイマーパラメータ
  • 52行:カウンターの値を、0としてタイマースタート
  • 57行:カウンターの値を、0x0xfffffffffff00000 としてタイマー再スタート

プロジェクトの構成も変更しています。


- ~esp/legacy_driver/
      - CMakeLists.txt
      - main/
                   - CMakeLists.txt
                   - timer_group_example_main.c

使用するCPUは、ESP32。モニターから、idf.py -p [port] flash monito を実行して下さい。モニターに下記が表示されます。


I (289) spi_flash: detected chip: generic
I (293) spi_flash: flash io: dio
W (297) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
W (310) timer_group: legacy timer group driver is deprecated, please migrate to use driver/gptimer.h
I (320) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Start value is 0
Count: 45, sec: 0.000079
Count: 493253, sec: 0.493255
Count: 993253, sec: 0.993255
Count: 1493253, sec: 1.493255
Count: 1993253, sec: 1.993255
Stop Timer
Start value is 18446744073708503040
Count: 18446744073708503042, sec: 18446744073708.503906
Count: 18446744073709002862, sec: 18446744073709.000000
Count: 18446744073709502862, sec: 18446744073709.503906
Count: 451246, sec: 0.451247
Count: 951246, sec: 0.951247
Stop Timer

プログラムは、0.5秒に一回カンターの値を呼んで表示するものです。確かタイマーが動いている様です。後半のカウンター値を、0x0xfffffffffff00000にしたのは、64ビットをオーバーフローしたらカウンターの値がどうなるのか確認したかった為です。オーバーフローした0からカウントし直す様です。

今回の設定で、分解能1マイクロ秒、64ビット長までカウントが可能。カウンターがオーバーフローするまで58年以上かかります。ESP32はそのタイマーを4個持っているのです。

Alarmと割り込みの使用

タイマー割り込みは下記の手順で行います。

  1. アラームを許可する。
  2. 割り込みを許可する。
  3. 割り込み関数を指定する。

割り込み関数の指定には、timer_isr_register()  と timer_isr_callback_add()の2つが有り、使い方が若干違います。

  • timer_isr_register()
    • void型で宣言
    • 関数内で下記を行う(続けて割り込みを使用する場合)
      • 割り込みフラグのクリアー
      • タイマーカウンターの設定
      • タイマー割り込みの有効化
  • timer_isr_callback_add()
    • bool型で宣言。
    • 説明には、戻り値とYeldが関係している様な説明が有ったのですが、戻り値がTureでもFaileでも動作は同じでした。
    • この関数は下記を自動で行う。
      • 割り込みフラグのクリアー
      • タイマーカウンターの設定
      • タイマー割り込みの有効化

先ずは、timer_isr_register() を使ったサンプル。プログラムは

  • 1秒間に1回割り込みをかける。それを繰り返す
  • 割り込みにはグループ0のタイマー0を使用
  • 割り込みが正しく行われたか割り込みがかかるまでの時間をタイマー1で測定。

です。

timer_group_example_main.c

#include <stdio.h>
#include "driver/timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TIMER_RESOLUTION_HZ   1000000 // 1MHz resolution

int flg = 0;
uint64_t c_value, t_value;

void IRAM_ATTR timer_group_isr_callback(void *args)
{
   	timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &c_value);
    timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &t_value);
	timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
	timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
	flg = 1;
}

void app_main(void)
{
	
	timer_config_t config = {
        .clk_src = TIMER_SRC_CLK_APB,
        .divider = APB_CLK_FREQ / TIMER_RESOLUTION_HZ,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = true,
    };

    timer_init(TIMER_GROUP_0, TIMER_0, &config);
    timer_init(TIMER_GROUP_0, TIMER_1, &config);
   
    // For the timer counter to a initial value
    timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
    timer_set_counter_value(TIMER_GROUP_0, TIMER_1, 0);

    // Set alarm value and enable alarm interrupt
    timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000000);
    timer_enable_intr(TIMER_GROUP_0, TIMER_0);
    // Hook interrupt callback
	timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group_isr_callback, (void *) TIMER_0, ESP_INTR_FLAG_IRAM, NULL);
    // Start timer
    timer_start(TIMER_GROUP_0, TIMER_0);
    timer_start(TIMER_GROUP_0, TIMER_1);

	int cnt = 1;
	    
    while(1)
    {
    	while(!flg)	vTaskDelay(10 / portTICK_PERIOD_MS);
    	printf("Alarm occure: %d: t0:%llu t1:%llu\n",cnt ++, c_value, t_value);
    	flg = 0;
    }
}

  • 11行:void IRAM_ATTR timer_group_isr_callback(void *args) 割り込み実行関数
    • 13,14行:タイマー0,1の値を読み込む。
    • 繰り返し割り込みをかける為に下記の処理が必要
      • 15行:timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
        • 割り込みフラグのクリアー
      • 16行:timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
        • 割り込みの有効化
      • カウンタ−値の設定は割り込みのコンフィグで .auto_reload = true とセットしているので自動でセットされます。
    • 割り込み検知用の変数 flg を1としてメインループに割り込みを知らせる
  • 33,32行:タイマー0,1の準備
  • 36,37行:タイマー0,1のカウンターを0にセット
  • 40行:タイマー0のアラームカウンターを1000000に設定。これでスタート後1秒でアラームが発生
  • 41行:タイマー0の割り込み有効化
  • 43行:割り込み実行関数の指定
  • 45,46:タイマー0,1スタート
  • 53行:割り込みが有ったら、タイマー0,1の値を表示

実行して見て下さい。モニターに以下が表示されます。


W (310) timer_group: legacy timer group driver is deprecated, please migrate to use driver/gptimer.h
I (320) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Alarm occure: 1: t0:39 t1:1000043
Alarm occure: 2: t0:3 t1:2000003
Alarm occure: 3: t0:3 t1:3000003
Alarm occure: 4: t0:3 t1:4000003
Alarm occure: 5: t0:3 t1:5000003
Alarm occure: 6: t0:3 t1:6000003
Alarm occure: 7: t0:3 t1:7000003
Alarm occure: 8: t0:3 t1:8000003

この表示から以下の事が分かります。

  • 繰り返し割り込みがかかっている。
  • タイマー0は割り込みの度にカウントが0に設定される模様
  • タイマー1は連続でカウントしているので、割り込みの度に1000000づつ増える。

同じ事を、timer_isr_callback_add() を使ってプログラムを書き直しました。

timer_group_example_main.c

#include <stdio.h>
#include "driver/timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TIMER_RESOLUTION_HZ   1000000 // 1MHz resolution

int flg = 0;
uint64_t c_value, t_value;

bool IRAM_ATTR timer_group_isr_callback(void *args)
{
   	timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &c_value);
    timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &t_value);
	flg = 1;
    return pdTRUE; // return whether a task switch is needed
}

void app_main(void)
{
	
	timer_config_t config = {
        .clk_src = TIMER_SRC_CLK_APB,
        .divider = APB_CLK_FREQ / TIMER_RESOLUTION_HZ,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = true,
    };

    timer_init(TIMER_GROUP_0, TIMER_0, &config);
    timer_init(TIMER_GROUP_0, TIMER_1, &config);
   
    // For the timer counter to a initial value
    timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
    timer_set_counter_value(TIMER_GROUP_0, TIMER_1, 0);

    // Set alarm value and enable alarm interrupt
    timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000000);
    timer_enable_intr(TIMER_GROUP_0, TIMER_0);
    // Hook interrupt callback
    timer_isr_callback_add(TIMER_GROUP_0, TIMER_0, timer_group_isr_callback,(void *) TIMER_0 , 0);

    // Start timer
    timer_start(TIMER_GROUP_0, TIMER_0);
    timer_start(TIMER_GROUP_0, TIMER_1);

	int cnt = 1;
	    
    while(1)
    {
    	while(!flg)	vTaskDelay(10 / portTICK_PERIOD_MS);
    	printf("Alarm occure: %d: t0:%llu t1:%llu\n",cnt ++, c_value, t_value);
    	flg = 0;
    }
}
  • 11から17行:bool IRAM_ATTR timer_group_isr_callback(void *args)
    • 書き換えた割り込み関数。
    • タイマーの設定を(フラグのクリア、割り込みの有効化)を行っていない
  • 40行:timer_enable_intr(group, timer); 割り込みを許可
  • 42行:timer_isr_callback_add(group, timer, timer_group_isr_callback,(void *) 0 , 0);
    • 割り込み実行関数を指定。
  • その他は前回と同じ。

プログラムを実行して下さい。モニターに下記の様に表示されます。


I (293) spi_flash: flash io: dio
W (297) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
W (311) timer_group: legacy timer group driver is deprecated, please migrate to use driver/gptimer.h
I (320) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Alarm occure: 1: t0:40 t1:1000044
Alarm occure: 2: t0:4 t1:2000004
Alarm occure: 3: t0:4 t1:3000004
Alarm occure: 4: t0:4 t1:4000004
Alarm occure: 5: t0:4 t1:5000004
Alarm occure: 6: t0:4 t1:6000004
Alarm occure: 7: t0:4 t1:7000004
Alarm occure: 8: t0:4 t1:8000004
Alarm occure: 9: t0:4 t1:9000004


ほとんど同じ結果です。約1秒間隔で割り込みが発生しています。 timer_isr_callback_add() は割り込みを繰り返す時に使用するのでしょうか。

もう一度サンプルプログラムへ

ここまで分かった所でオリジナルのサンプルプログラムを実行して下さい。実行後モニターは以下のようになります。


I (293) spi_flash: flash io: dio
W (297) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
W (310) timer_group: legacy timer group driver is deprecated, please migrate to use driver/gptimer.h
I (320) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (331) example: Init timer with auto-reload
I (841) example: Timer auto reloaded, count value in ISR: 3
I (1341) example: Timer auto reloaded, count value in ISR: 3
I (1841) example: Timer auto reloaded, count value in ISR: 3
I (2341) example: Timer auto reloaded, count value in ISR: 3
I (2341) example: Init timer without auto-reload
I (2841) example: Timer alarmed at 500003
I (3341) example: Timer alarmed at 1000003
I (3841) example: Timer alarmed at 1500003
I (4341) example: Timer alarmed at 2000003