小ピンマイコンの酷暑(5) CH32Vのモデル?STM32のHALレイヤの使用 その2

Joseph Halfmoon

前回書いたとおり、WCHのRISC-V搭載マイコンCH32Vは、STM32をリスペクトしているのか、HALレイヤの関数がクリソツです。先にSTM32のHALレイヤで予習しておいたらCH32VでHALレイヤを使うのも楽なんでないかと。学習教材としてはタイマからPWM出力するのをHALレベルでやってみようと。メンドイけど。

Arduino環境からHALレベルのAPIを使えることはCH32VでもSTM32でも確かめましたが、直接使うのはメンドイです。しかし、STM32では「ラッパ」レイヤが準備されているので、そいつらを呼び出してやれば、ArduinoのAPIほどお手軽ではないけれど、結構簡単に制御が可能でした。今回は、STM32の「ラッパ」レイヤの関数を「毟って」、HALレベルのAPIもしくは、直接ハードウエアレジスタを読み書きするレベルでPWM出力してみたいと思います。使用するハードウエアは以下のとおりです。

    • タイマ=TIM2
    • コンペア・キャプチャ・チャネル=CH1
    • CH1出力先=PA_5端子(STM32名。)Arduino的にはD13端子。ここにオンボードのLEDが接続されている

ここに対して、LEDの点滅が分かる比較的遅い周期(10Hzくらい)で、デューティ10%のPWM波形をPA_5に出力するのが当座の目標です。

実験に使用したソースコード

STM32の「ラッパ」レイヤのソースコードをカンニングさせていただきながら書いてみたものです。HALもしくは直接のハードウエアの読み書きをするマクロなどで書いてみたソースが以下に。

#if defined(LED_BUILTIN)
#define pin  LED_BUILTIN
#else
#define pin  D2
#endif

int counter = 0;
uint32_t channel;

timerObj_t _timerObj;

void sprin(const char *msg, uint16_t v) {
  Serial.print(msg);
  Serial.println(v, HEX);
}

void setupTimer(TIM_TypeDef *instance)
{
  _timerObj.handle.Instance = nullptr;

  _timerObj.handle.Instance = instance;
  _timerObj.handle.Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
  _timerObj.handle.hdma[0] = NULL;
  _timerObj.handle.hdma[1] = NULL;
  _timerObj.handle.hdma[2] = NULL;
  _timerObj.handle.hdma[3] = NULL;
  _timerObj.handle.hdma[4] = NULL;
  _timerObj.handle.hdma[5] = NULL;
  _timerObj.handle.hdma[6] = NULL;
  _timerObj.handle.Lock = HAL_UNLOCKED;
  _timerObj.handle.State = HAL_TIM_STATE_RESET;

  _timerObj.preemptPriority = TIM_IRQ_PRIO;
  _timerObj.subPriority = TIM_IRQ_SUBPRIO;

  __HAL_RCC_TIM2_CLK_ENABLE();

  _timerObj.handle.Init.Prescaler = 0;
  _timerObj.handle.Init.Period = 0xFFFF;
  _timerObj.handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  _timerObj.handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  _timerObj.handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  HAL_TIM_Base_Init(&(_timerObj.handle));
}

void setMode(PinName pinx)
{
  int timChannel = TIM_CHANNEL_1;
  int timAssociatedInputChannel;
  TIM_OC_InitTypeDef channelOC;
  TIM_IC_InitTypeDef channelIC;

  channelOC.OCMode = TIMER_NOT_USED;
  channelOC.Pulse = __HAL_TIM_GET_COMPARE(&(_timerObj.handle), timChannel);
  channelOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  channelOC.OCFastMode = TIM_OCFAST_DISABLE;
  channelIC.ICPolarity = TIM_ICPOLARITY_RISING;
  channelIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  channelIC.ICPrescaler = TIM_ICPSC_DIV1;
  channelIC.ICFilter = 0;

  channelOC.OCMode = TIM_OCMODE_PWM1;
  HAL_TIM_PWM_ConfigChannel(&(_timerObj.handle), &channelOC, timChannel);

  if (pinx != NC) {
    if ((int)getTimerChannel(pinx) == timChannel) {
      pinmap_pinout(pinx, PinMap_TIM);
    }
  }
}

void setCaptureCompare(uint32_t compare)
{
  int timChannel = TIM_CHANNEL_1;
  uint32_t Prescalerfactor = LL_TIM_GetPrescaler(_timerObj.handle.Instance) + 1;
  uint32_t CCR_RegisterValue;

  CCR_RegisterValue = ((__HAL_TIM_GET_AUTORELOAD(&(_timerObj.handle)) + 1) * compare) / 100;
  if ((__HAL_TIM_GET_AUTORELOAD(&(_timerObj.handle)) == 0xFFFF)
      && (CCR_RegisterValue == 0xFFFF + 1)) {
    CCR_RegisterValue = 0xFFFF;
  }
  __HAL_TIM_SET_COMPARE(&(_timerObj.handle), timChannel, CCR_RegisterValue);
}

void setPWM_T2C1(uint32_t frequency, uint32_t dutycycle)
{
  int timChannel = TIM_CHANNEL_1;
  setMode(PA_5);
  LL_TIM_SetPrescaler(_timerObj.handle.Instance, 63);
  __HAL_TIM_SET_AUTORELOAD(&_timerObj.handle, 0xFF19);
  setCaptureCompare(dutycycle);
  HAL_TIM_PWM_Start(&(_timerObj.handle), timChannel);
}

void setup() {
  Serial.begin(9600);
  while (!Serial) { }
  Serial.println("STM32, PWM output settings check <DIRECT>.");
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
  setupTimer(Instance);
  setPWM_T2C1(5, 10);
}

// the loop function runs over and over again forever
void loop() {
  Serial.println(counter++);
  sprin("Chan : ", channel);
  sprin("CNT  : ", TIM2->CNT);
  sprin("ARR  : ", TIM2->ARR);
  sprin("CR1  : ", TIM2->CR1);
  sprin("CCMR1: ", TIM2->CCMR1);
  sprin("CCR1 : ", TIM2->CCR1);
  sprin("CCER : ", TIM2->CCER);
  delay(10000);
}

メインループの中では、TIM2の主要なレジスタを直接読み出して表示してます。

実験結果

上記のソースをArduino IDE環境でビルド後、ターゲット機 Nucleo-F072RB(STM32F072RBマイコン搭載)に書き込みました。

シリアルモニタに出力されたレジスタ値は以下のようです。CNTみているとちゃんとカウントアップされているみたいデス。

ResultSTDIO

一方、ArduinoのD13端子出力をオシロで観察した結果が以下に。約11.5Hz、デューティ約10%の波形が観察できます。やったね。ResultWaveForm

やったね、といって今回は動いて当然のSTM32機。次回こそは小ピンマイコンCH32V003に戻ってタイマを操作してみたいと思います。先は長いな。

小ピンマイコンの酷暑(4) CH32Vのモデル?STM32のタイマを使ってみる に戻る