概要
上記で受信バッファが無いことがわかりましたので、受信割り込みを使って受信してみようとしました。しかし情報が少ないのでGPIO割り込みから実験してみました。
GPIO割り込み
void key_int(){
Serial.println("key_int");
}
void setup() {
Serial.begin(115200);
pinMode(PA0, INPUT);
attachInterrupt(PA0, GPIO_Mode_IN_FLOATING, key_int, EXTI_Mode_Interrupt, EXTI_Trigger_Falling);
}
void loop() {
Serial.println(digitalRead(PA0));
delay(100);
}
手元の環境だと上記で動きました。Arduinoなのである程度共通処理なのですがattachInterrupt()関数の引数がちょっと違います。割り込みコールバック関数のkey_int()関数になにかアトリビュートを設定しないといけないのかはわかりませんでした。おそらく必要なさそうです。
attachInterrupt()関数の定義
void attachInterrupt(uint32_t pin, GPIOMode_TypeDef io_mode, void (*callback)(void), EXTIMode_TypeDef it_mode, EXTITrigger_TypeDef trigger_mode)
{
PinName p = digitalPinToPinName(pin);
GPIO_TypeDef* port = set_GPIO_Port_Clock(CH_PORT(p));
if (!port) return ;
pinV32_DisconnectDebug(p);
ch32_interrupt_enable(port, io_mode, CH_GPIO_PIN(p), callback, it_mode, trigger_mode);
}
ドキュメントや利用方法の情報がないので引数の定義を調べつつ、処理を追っていく必要があります。
GPIOMode_TypeDef
/* Configuration Mode enumeration */
typedef enum
{
GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
} GPIOMode_TypeDef;
pinModeとの違いがわかりませんが、GPIOの初期化から再設定しているような感じの設定値です。今回はプルアップされているボタンを接続したのでGPIO_Mode_IN_FLOATINGを指定してみました。
EXTIMode_TypeDef
/* EXTI mode enumeration */
typedef enum
{
EXTI_Mode_Interrupt = 0x00,
EXTI_Mode_Event = 0x04
} EXTIMode_TypeDef;
イベントか割り込みかの指定です。割り込みを選んでみましたがEXTI_Mode_Eventにしたほうが細かい割り込み制御をしなくてもよいのかもしれません。
EXTITrigger_TypeDef
/* EXTI Trigger enumeration */
typedef enum
{
EXTI_Trigger_Rising = 0x08,
EXTI_Trigger_Falling = 0x0C,
EXTI_Trigger_Rising_Falling = 0x10
} EXTITrigger_TypeDef;
トリガーの設定です。プルアップのボタンだったので、押したときのLOWに落ちるFallingを指定しました。
ch32_interrupt_enable
void ch32_interrupt_enable(GPIO_TypeDef *port, GPIOMode_TypeDef io_mode,uint16_t pin, void (*callback)(void), EXTIMode_TypeDef it_mode, EXTITrigger_TypeDef trigger_mode)
{
GPIO_InitTypeDef GPIO_InitStruct={0};
EXTI_InitTypeDef EXTI_InitStruct={0};
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
uint8_t id = get_pin_id(pin);
uint8_t gpio_port_souce=0;
GPIO_InitStruct.GPIO_Pin = pin;
GPIO_InitStruct.GPIO_Mode = io_mode;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(port, &GPIO_InitStruct);
#if defined(GPIOA_BASE)
if(port == GPIOA) gpio_port_souce = GPIO_PortSourceGPIOA;
#endif
#if defined(GPIOB_BASE)
if(port == GPIOB) gpio_port_souce = GPIO_PortSourceGPIOB;
#endif
#if defined(GPIOC_BASE)
if(port == GPIOC) gpio_port_souce = GPIO_PortSourceGPIOC;
#endif
#if defined(GPIOD_BASE)
if(port == GPIOD) gpio_port_souce = GPIO_PortSourceGPIOD;
#endif
#if defined(GPIOE_BASE)
if(port == GPIOE) gpio_port_souce = GPIO_PortSourceGPIOE;
#endif
#if defined(GPIOF_BASE)
if(port == GPIOF) gpio_port_souce = GPIO_PortSourceGPIOF;
#endif
#if defined(GPIOG_BASE)
if(port == GPIOG) gpio_port_souce = GPIO_PortSourceGPIOG;
#endif
#if defined(GPIOH_BASE)
if(port == GPIOH) gpio_port_souce = GPIO_PortSourceGPIOH;
#endif
GPIO_EXTILineConfig(gpio_port_souce, id);
EXTI_InitStruct.EXTI_Line = exti_lines[id];
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = it_mode;
EXTI_InitStruct.EXTI_Trigger = trigger_mode;
EXTI_Init(&EXTI_InitStruct);
gpio_irq_conf[id].callback = callback;
NVIC_SetPriority(gpio_irq_conf[id].irqnb, EXTI_IRQ_PRIO);
NVIC_EnableIRQ(gpio_irq_conf[id].irqnb);
}
ちょっと長いですがGPIOとEXTIの設定をしたあとにNVICで割り込み設定をしているようでした。実はWCHのサンプルコード集があるのですがそこと処理が違っていました。
/*********************************************************************
* @fn EXTI0_INT_INIT
*
* @brief Initializes EXTI0 collection.
*
* @return none
*/
void EXTI0_INT_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
EXTI_InitTypeDef EXTI_InitStructure = {0};
NVIC_InitTypeDef NVIC_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOA ----> EXTI_Line0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
上記がCH32V103EVT.ZIPのEXTI0の初期化例なのですがNVIC_Initで設定しています。最初はEVTのコードを持ってきて動かしたのですがどうも割り込みが正常に動かなかったのでNVIC_SetPriority()関数とNVIC_EnableIRQ()関数で初期化する必要がありそうでした。
割り込み発生時に呼び出される関数はどこ?
ch32_interrupt_enable()関数でコールバック関数を渡していますが、直接その関数が呼び出されるわけではありません。
/**
* @brief This function his called by the HAL if the IRQ is valid
* @param GPIO_Pin : one of the gpio pin
* @retval None
*/
void _gpio_exti_callback(uint16_t GPIO_Pin)
{
uint8_t irq_id = get_pin_id(GPIO_Pin);
if (gpio_irq_conf[irq_id].callback != NULL) {
gpio_irq_conf[irq_id].callback();
}
}
設定したコールバック関数は上記で呼び出されていました。そしてこの関数を呼び出している関数がさらにありました。
データシートを確認

ここまで来てデータシートをやっと確認します。CH32F103と、今回利用しているCH32V103は同じデータシートになっています。そしてCH32F103はCortex-M3ベースと書いてあります。
実際のところ、STM32F103をベースに設計されています。上記が開発ボードです。
そして、今回利用しているCH32V103の開発ボードです。命令セットはCortex-M3からRISC-Vに変わっていますが、基本的な作りはSTM32に非常に似ています。そしてArduino環境もSTM32のArduino環境をベースにして作られているのでSTM32を触ったことがある人はCH32も比較的触りやすいはずです。私はSTM32はほとんど触ったことがないので手探りで調べています。

割り込みテーブルを確認したところEXTI0から4までありました。

もっと探してみると、下の方にEXTI9_5とEXTI15_10がありました。
{.irqnb = EXTI0_IRQn, .callback = NULL}, //GPIO_PIN_0
{.irqnb = EXTI1_IRQn, .callback = NULL}, //GPIO_PIN_1
{.irqnb = EXTI2_IRQn, .callback = NULL}, //GPIO_PIN_2
{.irqnb = EXTI3_IRQn, .callback = NULL}, //GPIO_PIN_3
{.irqnb = EXTI4_IRQn, .callback = NULL}, //GPIO_PIN_4
{.irqnb = EXTI9_5_IRQn, .callback = NULL}, //GPIO_PIN_5
{.irqnb = EXTI9_5_IRQn, .callback = NULL}, //GPIO_PIN_6
{.irqnb = EXTI9_5_IRQn, .callback = NULL}, //GPIO_PIN_7
{.irqnb = EXTI9_5_IRQn, .callback = NULL}, //GPIO_PIN_8
{.irqnb = EXTI9_5_IRQn, .callback = NULL}
#ifndef CH32V10x
, //GPIO_PIN_9
{.irqnb = EXTI15_10_IRQn, .callback = NULL}, //GPIO_PIN_10
{.irqnb = EXTI15_10_IRQn, .callback = NULL}, //GPIO_PIN_11
{.irqnb = EXTI15_10_IRQn, .callback = NULL}, //GPIO_PIN_12
{.irqnb = EXTI15_10_IRQn, .callback = NULL}, //GPIO_PIN_13
{.irqnb = EXTI15_10_IRQn, .callback = NULL}, //GPIO_PIN_14
{.irqnb = EXTI15_10_IRQn, .callback = NULL} //GPIO_PIN_15
#endif
定義をさがしてみたところ、上記の設定があります。CH32V103だと16個のGPIO割り込みが使えそうですね。
/* Private_Defines */
#ifdef CH32V00x
#define NB_EXTI (8)
#elif defined(CH32X035)
#define NB_EXTI (26)
#else
#define NB_EXTI (16)
#endif
数は少し上にこんな宣言がありました。X035はちょっと特殊な感じですね。

つづいてEXTIとGPIOの関連を確認してみます。数字の0から15はPx0-Px15に対応するようです。xはPA、PB、PC、PDの4つあって、すべてのIOに割り込みが設定できると書いてあります。

上記に説明がありましたが、EXTI1はPA1、PB1、PC1、PD1、PE1から選択する必要があるようです。つまり16個のGPIO割り込みが設定可能ですが、PA1とPB1の組み合わせはできないみたいですね。番号を変えればPA1とPB2の組み合わせはおそらく可能だと思われます。

設定自体はEXTI0から15まで個別にPAからPDのどれを利用するかを選択可能でした。
EXTI0のコールバック関数
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
/**
* @brief This function handles external line 0 interrupt request.
* @param None
* @retval None
*/
void EXTI0_IRQHandler(void)
{
EXTI_ClearITPendingBit(EXTI_Line0);
_gpio_exti_callback(EXTI_Line0);
}
上記の関数が割り込み時のコールバック関数になります。EXTI_ClearITPendingBit関数で割り込みフラグをクリアして、_gpio_exti_callback関数で設定したコールバック関数を呼び出しています。
EXTI0_IRQHandlerを呼び出しているコードはインラインアセンブラで書いてあって、関数テーブルから割り込み番号に応じた関数を呼び出しています。
.weak EXTI0_IRQHandler
.weak EXTI1_IRQHandler
.weak EXTI2_IRQHandler
.weak EXTI3_IRQHandler
上記のようにweakで定義してあるので、自分で定義していない場合でもエラーにならない設定になっています。ちなみに未定義の割り込みを実行するとハングアップします。何もしない関数が定義されているのではなく、ハングアップという動作でしたので注意してください。
バグ
/**
* @brief This function handles external line 5 to 9 interrupt request.
* @param None
* @retval None
*/
void EXTI9_5_IRQHandler(void)
{
uint32_t pin;
for (pin = GPIO_Pin_5; pin <= GPIO_Pin_9; pin = pin << 1) {
EXTI_ClearITPendingBit(pin);
_gpio_exti_callback(pin);
}
}
複数のピンで共通の割り込みの場合、すべてのPINに対してコールバックが実行されています。
修正のプルリクエスが投げられていますが取り込まれていません。。。WCHはArduinoで使わない方がいいのかな。。。
まとめ
STM32を触ったことがある人の場合には問題ないかもしれませんが、事前知識なしで触ると情報が少ないので大変でした。WCHの資料とともにキーワードで検索してSTM32の資料も参考にしつつ理解を深めていく必要がありそうです。
そしてSTM32の仕様をそのまま引きずっているので、CH32V00xとCH32X035、そしてそれ以外で細かい動きが違うのでArduinoライブラリ側のコードをみてもifdefがかなりはいっていて、対応が面倒な感じでした。
コメント