CH32VのオレオレArduino環境を作ろう その7 タイマー処理とGitHub Copilotでの実装

概要

前回はI2Cをやりましたが、今回USB PDにしようとしたのですがちょっと時間がかかりそうなので、内部で利用しそうなタイマーを作成してみました。

CH32Vのタイマー

上記はCH32V303の機能一覧ですが左上が時間関係の機能になっています。

上記の型番別のものがわかりやすいのですがタイマーには5種類あります。

SysTick

すべてのチップで搭載されているタイマーで、システムの一番基本的なカウンターとして動いています。オレオレArduino Coreの場合にはSysTickを1ミリ秒に設定し、経過時間のカウントアップに利用しています。

EVTの場合にはdelay用に利用しており、指定時間が経過するかを確認する用途でした。

WDOG

ウォッチドッグタイマ(WDT)はハングアップなどを確認するためのタイマーで、一定時間以上更新をしないと自動リセットをする動きになっています。

TIM

チップによって違うのですがTIM1から最大TIM10までのタイマーがあります。こちらが一般的に利用できるタイマーなのですがチップごとに精度の違うタイマーが複数搭載されており、番号と精度が紐づいていません。

作成するタイマークラスについて

TIMを使うと特定の機能とバッティングしたときに回避が難しいので、なるべくArduino Core側からは使わない方針にしたいと思います。そこでSysTickの割り込みを利用して、最速で1ミリ秒間隔で呼び出されるような簡易的なタイマークラスとします。

void SysTick_Handler(void)
{
    SysTick->SR = 0;
    _millis++;
    sysTickUpdate();
}

SysTick_Handlerの割り込みで上記のように割り込みフラグのクリア、起動経過時間のカウントアップ、そして今回利用する処理などを呼び出すsysTickUpdate関数を呼び出すようにしました。

void sysTickUpdate()
{
    Timer.update();
}

sysTickUpdateの中身は今回作成するタイマークラスを更新する処理になっています。

タイマークラスの設計

class TimerClass
{
public:
    TimerClass() {}

    ~TimerClass()
    {
        stopAll();
    }

    void update();

    uint8_t addOnce(int intervalMs, void (*func)());
    uint8_t addOnce(int intervalMs, void (*func)(void *), void *arg);
    uint8_t addRepeat(int intervalMs, void (*func)());
    uint8_t addRepeat(int intervalMs, void (*func)(void *), void *arg);

    void stop(uint8_t timerId);
    void start(uint8_t timerId);
    void stopAll();

    uint8_t timerCount = 0;
    std::list<TimerItem> _timers;
};

上記のような設計にしました。updateでタイマーを更新して、addOnceでワンショットタイマーの追加、addRepeatでリピートタイマーも追加になります。追加するときにタイマーIDが戻ってくるのでstopとstartなどもあります。

タイマー自体はstd::listに保存して極力未使用時のメモリ利用量を抑える方針です。また、コールバックを登録する方式にしており、引数なしのものとvoid*の2種類を登録できるようにしました。

  Timer.addRepeat(3000, []() {
    Serial.println("timer 3000");
  });

上記だと3000ミリ秒(3秒)間隔で”timer 3000″が出力されるタイマー処理になります。

  Timer.addRepeat(10000, timer10000, (void *)&Serial);

上記のように引数にクラスへのポインタを渡すことでクラスなどの処理も可能になります。

GitHub Copilotで実装

これぐらいのクラスだと汎用的な処理になるので、ほぼ自分で組まなくてもGitHub Copilotにある程度組んでもらうことができました。

ここ数ヶ月プライベートのコーディングはGitHub Copilotを利用してみているのですが、どうも特殊なコードばかり組んでいるのであまり便利ではなかったです。しかしながらこれぐらいよくある処理だとクラスの設計をして、実際のcpp部分はほぼ出てきたコードを確認する作業ですみました。

すこし気に入らない処理の修正や、255個までにしたかったので特殊処理をしていますが、ベースとしては非常に便利に使えました。

仕事で使えるかと言われると微妙なところで、定型的な処理は他の人に任せる事が多いですし、そもそも仕事であまりコードを書いていないので金額の元が取れるかは微妙なところです。定型処理などを組むことがある場合には非常に便利そうでした。とくにエラー処理など似たようなコードが大量に必要な場合には誰が組んでも時間かかるのでよさそう。

プライベートだと他に情報がないものを調べて書いている記事が多いので、実はGitHub Copilotはあまり役にたっていません。あると便利なところな場合がありますが、なくても困らないケースも多いです。ただ使っているとvscodeの自動補完なのか、GitHub Copilotなのかがどんどんわからなくなってきます。

#include "Timer.h"

void TimerClass::update()
{
    unsigned int now = millis();
    for (std::list<TimerItem>::iterator it = _timers.begin(); it != _timers.end(); it++)
    {
        if (it->running && now >= it->nextRun)
        {
            if (it->repeat)
            {
                it->nextRun = now + it->interval;
            }
            else
            {
                it->running = false;
            }
            if (it->arg)
            {
                ((void (*)(void *))it->func)(it->arg);
            }
            else
            {
                ((void (*)())it->func)();
            }
        }
    }
}

uint8_t TimerClass::addOnce(int intervalMs, void (*func)())
{
    if (timerCount == 255)
    {
        TimerItem &item = _timers.back();
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = NULL;
        item.repeat = false;
        item.running = true;
        return item.timerId;
    }
    else
    {
        TimerItem item;
        item.timerId = timerCount++;
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = NULL;
        item.repeat = false;
        item.running = true;
        _timers.push_back(item);
        return item.timerId;
    }
}

uint8_t TimerClass::addOnce(int intervalMs, void (*func)(void *), void *arg)
{
    if (timerCount == 255)
    {
        TimerItem &item = _timers.back();
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = arg;
        item.repeat = false;
        item.running = true;
        return item.timerId;
    }
    else
    {
        TimerItem item;
        item.timerId = timerCount++;
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = arg;
        item.repeat = false;
        item.running = true;
        return item.timerId;
    }
}

uint8_t TimerClass::addRepeat(int intervalMs, void (*func)())
{
    if (timerCount == 255)
    {
        TimerItem &item = _timers.back();
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = NULL;
        item.repeat = true;
        item.running = true;
        return item.timerId;
    }
    else
    {
        TimerItem item;
        item.timerId = timerCount++;
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = NULL;
        item.repeat = true;
        item.running = true;
        _timers.push_back(item);
        return item.timerId;
    }
}

uint8_t TimerClass::addRepeat(int intervalMs, void (*func)(void *), void *arg)
{
    if (timerCount == 255)
    {
        TimerItem &item = _timers.back();
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = arg;
        item.repeat = true;
        item.running = true;
        return item.timerId;
    }
    else
    {
        TimerItem item;
        item.timerId = timerCount++;
        item.interval = intervalMs;
        item.nextRun = millis() + intervalMs;
        item.func = (void *)func;
        item.arg = arg;
        item.repeat = true;
        item.running = true;
        _timers.push_back(item);
        return item.timerId;
    }
}

void TimerClass::stop(uint8_t timerId)
{
    for (std::list<TimerItem>::iterator it = _timers.begin(); it != _timers.end(); it++)
    {
        if (it->timerId == timerId)
        {
            it->running = false;
            return;
        }
    }
}

void TimerClass::start(uint8_t timerId)
{
    for (std::list<TimerItem>::iterator it = _timers.begin(); it != _timers.end(); it++)
    {
        if (it->timerId == timerId)
        {
            it->running = true;
            it->nextRun = millis() + it->interval;
            return;
        }
    }
}

void TimerClass::stopAll()
{
    for (std::list<TimerItem>::iterator it = _timers.begin(); it != _timers.end(); it++)
    {
        it->running = false;
    }
}

TimerClass Timer;

できたコードが上記になります。あっさりと作ってみましたのでGitHub Copilotを使わなくてもほぼ同じコードは組めたと思います。こんな組み方があったのかみたいなコードは出てこないので、個人的には好感度は高いです。

おまけにピン配置検索ツールを作った

CH32 RISC-V Pin Alternate Functions

上記のページにあるのですが、ピン配置を調べるためのページを作ってみました。

GitHub - ch32-riscv-ug/ch32_riscv_tools
Contribute to ch32-riscv-ug/ch32_riscv_tools development by creating an account on GitHub.

上記のリポジトリにコードが入っていますが、データシートからPin Alternate Functionsの部分を抜き出してCSV化したものを準備しています。そのデータを読み込んで静的HTMLを作ってjQueryで絞り込み処理を追加しています。

setTimeout(function() {
  const params = new URLSearchParams(window.location.search);
  filter = [];
  if(params.get("chip")) filter[0] = params.get("chip");
  if(params.get("pin")) filter[1] = params.get("pin");
  if(params.get("functions")) filter[2] = params.get("functions");
  if(params.get("features")) filter[3] = params.get("features");
  $('table').trigger('search', [ filter ]);
  return false;
}, 1000);

中身は過去に作ったことがある組み合わせだったのですが、GETパラメータでフィルターを更新する処理がかなり面倒でした。絞り込みフィルタのテキストボックス自体がjQueryで作成していて、起動直後にテキストボックスに流し込んでも変な動きになりました。最終的にはタイムアウト処理で1秒後のある程度初期化が終わってからフィルターを設定することで意図している動きになりました。

https://github.com/ch32-riscv-ug/CH32X035

上記の様に各種チップ画面にリンクを追加しています。

CH32 RISC-V Pin Alternate Functions

上記のようにチップと機能名で絞り込むことができるようになりました。

オリジナルのデータシートだと上記のような表があり、リマップが多いUARTなどのピンを探すのが非常に面倒な感じでしたので絞り込みとソートがある画面ができて少しは便利になりました。

まとめ

よくある処理は最初にクラス設計をすれば、実装はGitHub Copilotが便利そうなことがわかりました。クラス設計の部分はGitHub CopilotよりはChatGPTとかの方が微調整がしやすいかもしれません。

ただ関数ポインタをvoid*にするかstd::functionにするかなどの好みで変わる部分があるので、ある程度判断がつく知識が必要そうでした。

とりあえずはあまりメモリとフラッシュを使わない軽めのタイマークラスができたので良かったです。

コメント