CH32VのオレオレArduino環境を作ろう その3 Arduino Coreの組み込み

概要

前回までで、なんとなく動くものができましたが、実際にArduino Coreに対応をしていきたいと思います。

バージョンの構成

今回はちょっと特殊なのでEVTがそのまま動くnoneOSとArduino Coreが動くものは別のボードマネージャーとして作っています。CH32V003とかだとArduino Coreを動かすとメモリがなりなくなったりとシンプルな構成を残したほうがよいと思ったからです。

バージョン番号の付け方で対応を取っています。ベースとなるnoneOSは1.1などメジャーとマイナーバージョンのみで構成しています。基本はEVTが更新したところでバージョン番号をアップします。

Arduino Core版に関してはベースとなるnoneOSのバージョン番号+リビジョン番号をつけることにして、1.1.0と一番下の桁のみ更新します。上位2つのバージョンでベースとなるEVTを特定できるようにしています。

Arduino Coreとは?

GitHub - arduino/ArduinoCore-API: Hardware independent layer of the Arduino cores defining the official API
Hardware independent layer of the Arduino cores defining the official API - arduino/ArduinoCore-API

上記にあるAPI群のことです。APIフォルダに基本的になAPIがまとまっており、StreamやStringクラスなどが定義されています。これによってStringクラスなどはボードが違っても同じ実装が利用可能です。

また、Common.hでArduinoの基本的な関数や、LOWやHIGHなどの定数が定義されています。

void pinMode(pin_size_t pinNumber, PinMode pinMode);
void digitalWrite(pin_size_t pinNumber, PinStatus status);
PinStatus digitalRead(pin_size_t pinNumber);
int analogRead(pin_size_t pinNumber);
void analogReference(uint8_t mode);
void analogWrite(pin_size_t pinNumber, int value);
unsigned long millis(void);
unsigned long micros(void);
void delay(unsigned long);
void delayMicroseconds(unsigned int us);
unsigned long pulseIn(pin_size_t pin, uint8_t state, unsigned long timeout);
unsigned long pulseInLong(pin_size_t pin, uint8_t state, unsigned long timeout);
void shiftOut(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder, uint8_t val);
uint8_t shiftIn(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder);
void attachInterrupt(pin_size_t interruptNumber, voidFuncPtr callback, PinStatus mode);
void attachInterruptParam(pin_size_t interruptNumber, voidFuncPtrParam callback, PinStatus mode, void* param);
void detachInterrupt(pin_size_t interruptNumber);
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0);
void noTone(uint8_t _pin);
long random(long);
long random(long, long);
void randomSeed(unsigned long);
long map(long, long, long, long, long);

関数は上記が宣言されており、内部実装はボードごとに自分で作る必要があります。まずはここの関数が動くようになるのが目標だと思います。

フォルダ構成

フォルダ1フォルダ2ファイル概要
coresCH32L103Arduino.hボード別のGPIO定義などをしているファイル
main.c全ボード共通のmain関数
port.c実際のボード別処理を定義するファイル
CH32V003同上
・・・同上
CH32X035同上
librariesArduinoCoreAPI/srcapiarduino/ArduinoCore-API/apiをそのまま配置
ArduinoCoreAPI.hArduino.hから読み込まれ、api/ArduinoAPI.hを読む
common.cpp全ボード共通のArduino Core関連の実装

上記のフォルダ構成にしました。cores配下にはEVT別のフォルダがあり、その中にボード個別のSDK相当のファイルが入っています。自動的に読み込まれるArduino.hでは基本的なボード別の定義の他に、Arduino Core APIを読み込んでいます。その他に全ボード共通なので個別にあるのが無駄なのですがmain.cがあります。そしてボード別の個別実装はport.cに分離してあります。新しいボードが増えた場合にはnoneOSでEVTを取り込み、Arduino.hとport.cをすることで追加可能な構成にしました。

coresだとEVT別に同じファイルを入れる必要があるので全ボード共通なArduinoCoreAPIはlibrariesに配置しています。

GPIO設定などはvariantsでボード別に読み込むフォルダでも設定可能ですが、現在variantsフォルダは利用していません。

main関数

#include <Arduino.h>
#include <debug.h>

int main(void)
{
    board_init();

    setup();

    for (;;)
    {
        loop();
    }
}

main関数は上記のようにしました。noneOSと比べるとboard_init関数の呼び出しが増えています。これはボードごとに最初に最低限初期化する処理が違うためです。

処理はEVTのGPIO_Toggleから持ってきています。CH32V103の場合には以下のコードとなります。

int main(void)
{
    u8 i = 0;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
    printf("GPIO Toggle TEST\r\n");
    GPIO_Toggle_INIT();

    while(1)
    {
        Delay_Ms(250);
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET));
    }
}

上記から初期化で必要な部分を抜き出すと以下になります。

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);

NVIC_PriorityGroupConfigの部分はボードによって違います。

ボード初期化
CH32V003NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1)
CH32V103NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1)
CH32V20xNVIC_PriorityGroupConfig(NVIC_PriorityGroup_1)
CH32V307NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)
CH32X035NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1)
CH32L103NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1)

ボートによって違うというよりは、CH32V307だけ違います。アプローチとしてはボードごとにifdefをする方法もありますが、泥沼になるので完全にボードごとに処理を分離しました。

pinMode関数の実装

CH32V103/EVT/EXAM/GPIO/GPIO_Toggle/User/main.c at main · ch32-riscv-ug/CH32V103
Contribute to ch32-riscv-ug/CH32V103 development by creating an account on GitHub.

さいどEVTのGPIO_Toggleを確認すると、GPIOの初期化は以下のコードです。

void GPIO_Toggle_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

RCC_APB2PeriphClockCmdの部分で、GPIOのユニットごとにクロックの供給をして有効化しています。CH32は初期状態は省電力のためすべて無効で、個別に利用設定をしていく感じの動きとなっていmす。

ボード初期化
CH32V003RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
CH32V103RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
CH32V20xRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
CH32V307RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
CH32X035RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
CH32L103RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);

初期化部分を比べるとCH32L103だけ関数名も、GPIOのバス名も違います。ここもボードごとにわけて実装したいと思います。

ボードクロック
CH32V003GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
CH32V103GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
CH32V20xGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
CH32V307GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
CH32X035GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
CH32L103GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

あとGPIOのクロックもCH32V003だけ違いました。ここもボードごとに変更可能に作ります。

arduino_core_ch32_riscv_arduino/copy/libraries/ArduinoCoreAPI/src/common.cpp at main · ch32-riscv-ug/arduino_core_ch32_riscv_arduino
CH32 Risc-V for Arduino IDE. Contribute to ch32-riscv-ug/arduino_core_ch32_riscv_arduino development by creating an acco...

実際に実装したpinMode関数は上記にあります。

クロック供給

各ボードごとに個別関数を呼び出しています。PINがPA0の場合にはGPIOAを有効化するなどユニットが変わってくるので地道に分岐させます。

void gpion_enable(uint8_t gpion)
{
    if (gpion == CH32_GPIO_A)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    }
    else if (gpion == CH32_GPIO_B)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    }
    else if (gpion == CH32_GPIO_C)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    }
    else if (gpion == CH32_GPIO_D)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
    }
}

ボードによってPCまでのものや、PEまであったりするので微妙に変わっています。

pin番号

PA0の場合には0、PC15だと15などのように後ろの番号だけ設定します。

#define PB1 (CH32_GPIO_B | (1))

pin自体が8ビットで上位3ビットでAからEまでのGPIOユニットの指定。下位5ビットで0から23までの番号を合成しています。

GPIO_InitStructure.GPIO_Pin = 1 << (pinNumber & CH32_PIN_MASK);

上記のようにビットの場所を指定してボード共通で設定できました。

ピンモード

    // pinMode
    if (pinMode == INPUT)
    {
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    }
    else if (pinMode == OUTPUT)
    {
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    }
    else if (pinMode == INPUT_PULLUP)
    {
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    }
    else if (pinMode == INPUT_PULLDOWN)
    {
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    }
    else if (pinMode == OUTPUT_OPENDRAIN)
    {
#if defined(GPIO_Mode_Out_OD)
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
#else
        GPIO_InitStructure.GPIO_Mode = GPIOMode_TypeDef(GPIO_Mode_IN_FLOATING | GPIO_Mode_Out_PP);
#endif
    }

Arduinoのモードと、CH32のモードを設定変更してあげています。なぜかCH32X035はGPIO_Mode_Out_ODの定義がないので、GPIO_Mode_IN_FLOATINGとGPIO_Mode_Out_PPを組み合わせて指定しています。

GPIOクロック

    GPIO_InitStructure.GPIO_Speed = CH32_GPIO_SPEED;

上記のように共通設定で指定しています。

#define CH32_GPIO_SPEED GPIO_Speed_50MHz

実際の速度についてはボードごとにdefineしています。

設定反映

    // GPIO
    if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_A)
    {
        gpion_enable(CH32_GPIO_A);
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    }
#if defined(CH32_GPIO_B)
    else if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_B)
    {
        gpion_enable(CH32_GPIO_B);
        GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
#endif
    else if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_C)
    {
        gpion_enable(CH32_GPIO_C);
        GPIO_Init(GPIOC, &GPIO_InitStructure);
    }
#if defined(CH32_GPIO_D)
    else if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_D)
    {
        gpion_enable(CH32_GPIO_D);
        GPIO_Init(GPIOD, &GPIO_InitStructure);
    }
#endif
#if defined(CH32_GPIO_E)
    else if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_E)
    {
        gpion_enable(CH32_GPIO_E);
        GPIO_Init(GPIOE, &GPIO_InitStructure);
    }
#endif
#if defined(CH32_GPIO_F)
    else if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_F)
    {
        gpion_enable(CH32_GPIO_F);
        GPIO_Init(GPIOF, &GPIO_InitStructure);
    }
#endif
#if defined(CH32_GPIO_G)
    else if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_G)
    {
        gpion_enable(CH32_GPIO_G);
        GPIO_Init(GPIOG, &GPIO_InitStructure);
    }
#endif

非常に泥臭いですが、全ボード共通設定になっています。

digitalWrite関数

GPIO_WriteBit(GPIOA, GPIO_Pin_0, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET));

EVTだと上記の処理になります。この関数は全ボード共通で、GPIOのユニットなどを個別に切り替える必要があります。

void digitalWrite(pin_size_t pinNumber, PinStatus status)
{
    uint16_t pin = 1 << (pinNumber & CH32_PIN_MASK);

    // GPIO
    if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_A)
    {
        GPIO_WriteBit(GPIOA, pin, (status == HIGH) ? Bit_SET : Bit_RESET);
    }

上記のように愚直にユニットごとに値を設定する感じになりました。

digitalRead関数

GPIO_ReadInputDataBit(GPIOA, pin);

キー入力などで上記の関数で取得できることがわかりました。こちらも全ボード共通です。

PinStatus digitalRead(pin_size_t pinNumber)
{
    uint16_t pin = 1 << (pinNumber & CH32_PIN_MASK);
    PinStatus ret = LOW;

    // GPIO
    if ((pinNumber & CH32_GPIO_MASK) == CH32_GPIO_A)
    {
        ret = (PinStatus)GPIO_ReadInputDataBit(GPIOA, pin);
    }

上記のように愚直にユニットごとに値を設定する感じになりました。

ここまででできたもの

#if defined(CH32V003)
#define OUTPUT_PIN PC1
#define INPUT_PIN PC2
#else
#define OUTPUT_PIN PA1
#define INPUT_PIN PA2
#endif

void setup() {
  printf("setup\n");
  pinMode(OUTPUT_PIN, OUTPUT);
  pinMode(INPUT_PIN, INPUT_PULLUP);
}

void loop() {
  static PinStatus output = LOW;
  output = (output == LOW) ? HIGH : LOW;
  printf("loop\n");
  digitalWrite(OUTPUT_PIN, output);
  printf(" output = %d\n", output);
  printf(" input = %d\n", digitalRead(INPUT_PIN));
  delay(1000);
}

上記の処理が動くようになりました。ちょっとCH32V003はピンが少ないのと他のボードと共通化が難しいのでdefineがありますがそれ以外は一般的なArduinoっぽいコードでLチカするコードが動くようになりました。

まとめ

なんとなくArduinoっぽい動くにはなりましたが、まだまだ関数が足りていません。今後徐々に関数を実装していきたいと思います。

コメント