/STM32/STM32L0のADCで、複数チャンネルの入力でハマる

毎度痛感させられるのが、STM32のHAL Driver及びSTM32CubeMXに関する情報の少なさとそれに伴う苦労の多さ。
ホント、参ってしまいます。

更に、HAL DriverはSTM32のハード(あるいはレジスタの設定周り)をうまく隠蔽化して開発者の負担を下げるわけではなく、それは更にその上位レイヤで行う方針だろうということ。
それ故なのか、はたまた単なる設計ミスなのか、(あるいは当方の理解不足なのか)非常に使いにくい部分がある。
USARTがそうだったが、ADCもその傾向があるようだ。

前回1チャンネル分の動作についてサンプル実装と確認を行ったが、その時には何ら苦労はなかった。
今回アナログジョイスティックを購入したので早速試してみたところ、結構はまってしまった。

STM32CubeMXを使用したのだが、もしかしたらコード生成に不具合があるのかもしれない。

試しに以下のようにPB0, PB1 にADCを設定してみた。

Pinout

ADC Configuration は以下のような感じ。

Parameter Settings

DMA Settings にて DMA Channel1 を割り当て。

DMA Settings

NVIC Settings は以下のとおり。

NVIC Settings

これによりアナログ入力を定期的に行い、ADCの初期化時に指定した計測対象のチャンネルが全て計測できたらコールバック
HAL_ADC_ConvCpltCallback() が呼び出される。

注意する点としては、1チャンネルのみ使用する場合はDMAを使用せずとも計測値が得られたが、複数チャンネルの場合はDMAを使用しなければならないとのこと。
確かにSTM32L0のレジスタをみてもめぼしいものは見つからない。

以上からDMAを使用したコードを書いて動作確認を行ったのだが、どうしても1チャンネル分しか取得できなかった。

コードは以下のような感じ。

    :
uint32_t g_ADCValue;
int g_MeasurementNumber;
enum{ ADC_BUFFER_LENGTH = 1024 };
uint16_t g_ADCBuffer[ADC_BUFFER_LENGTH];
    :
int main(void)
{

  /* USER CODE BEGIN 1 */
  uint32_t adcValue = 0;
  uint32_t volt = 0;
  ADC_ChannelConfTypeDef sConfig;
  /* USER CODE END 1 */
    :
  memset(g_ADCBuffer, 0, sizeof(g_ADCBuffer));
  HAL_ADC_Start_DMA(&hadc, g_ADCBuffer, ADC_BUFFER_LENGTH);
    :
}

    :
    :
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  printf("adc[0]:%04x, adc[1]:%04x\n", g_ADCBuffer[0], g_ADCBuffer[1]);
}

ジョイスティックをX/Y方向に倒しても、X方向のみ反映されるという状態に。

生成されたコードを含めて眺めてみる。
すると、不可解なコードが・・・

void MX_ADC_Init(void)
{

  ADC_ChannelConfTypeDef sConfig;
        :
    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_9;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  HAL_ADC_ConfigChannel(&hadc, &sConfig);
}

ADC_IN8/ADC_IN9 の設定を行ったはずなのだが、インスタンスhadcに設定されるのは ADC_CHANNEL_9 のみとなっていた。

STM32のレジスタを確認すると、計測対象にするチャンネルを計測順序という形式で指定でき、同時に変換するチャンネル数を指定するようになっていた。

Reference

試しに以下のように ADC_CHANNEL_8 も設定してみる。

  sConfig.Channel = ADC_CHANNEL_8|ADC_CHANNEL_9;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  HAL_ADC_ConfigChannel(&hadc, &sConfig);

これにより、DMA転送先となる g_ADCBuffer には、X, Y方向の値が交互に格納されることを確認した。

別途HAL Driverのソースを確認する必要があると思われる。
が、力尽きたので今日はここまで。

STM32/USARTを使ってみる

STM32に搭載されているUSARTの機能を使ってみます。
ここまで出来れば、作りたいGPSロガーができそうです。

STM32CubeMX

USART1 を有効にします。
プロジェクトを読み込みます。
Peripherals >> USART1 にある ModeAsynchronous を選択します。

Chips

Configuration タブに移動し、 USART1 をクリックします。

Connectivity

Parameter Settings タブを選択します。 Baud Rate9600 Bits/s に、 Word Length8Bits (including Parity) に設定します。

Parameter Settings

ここまで設定したら、コードを生成します。

ハードウェアについて

Macと接続して通信するためには USBシリアル変換基板 が必要です。
FT232RL USBシリアル変換モジュールキット: 半導体 秋月電子通商 電子部品 ネット通販 あたりが無難と思われます。入手が容易だし安いです。電子工作をされている方でUSBシリアルが必要でこちらを選択している人は多そうです。
なお、当方は今回、過去大阪の日本橋にある共立電子で購入したものを使用しました。
電子ホビー – 赤フグ株式会社 – FTDI Adapter ←こちらだと思います。どのUSBシリアル変換基板を使用するとしても3.3Vに設定しましょう。

Microsoft Windowsの合はデバイスドライバが必要ですが、LinuxやMacの場合は不要です。
/dev/tty.usbserial-◯◯ などというファイル名で見えます。

Eclipse

Eclipseから以下のようなコードを記述します。受信した情報を送信するという単純なものです。

int main(void)
{

  /* USER CODE BEGIN 1 */
  HAL_StatusTypeDef stat;
  uint8_t c;
  /* USER CODE END 1 */
    :
    :
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    stat = HAL_UART_Receive(&huart1, &c, 1, 1);
    if (stat == HAL_OK) {
      stat = HAL_UART_Transmit(&huart1, &c, 1, -1);
    }
  }
  /* USER CODE END 3 */
}

ところで、こちらの動作確認を行う場合はUSBシリアルとの送受信が必要となります。真っ先に思い浮かぶのは通信ソフトを使用することでしょうが、LinuxやMacならターミナルから screen コマンドを使用する方法もあります。

screen  <通信速度>

例えば screen /dev//dev/tty.usbserial-◯◯ 9600 と入力します。
終了は(デフォルトでは) CTRL-a k です。つまり、CTRL-a の後、 k を押すということです。何かと便利なので、知っておいて損はないコマンドだと思います。

閑話休題

USARTもそうだけど、上記のタイムアウト時間を指定して送信/受信する関数以外にタイムアウト時間を指定しない関数、割り込みハンドラを内部を記述するものがあります。上記の場合は受信データが対向側から送信されない場合、 HAL_UART_Receive() は最終の引数で指定した時間(ms)だけ待たされます。今回記述したサンプルだと1msですね。

どのように処理するのが最適なのかについては、情報収集や試行錯誤が必要そうです。

  • 割り込みハンドラを使うのが一番効率的そうだけどたくさんの処理を行っても大丈夫なのか?
  • OS(FreeRTOS等)を使った場合にどれくらいROM/RAMが増えるのか?また消費電力の差は?
  • DMA転送を組み合わせた方が消費電力が減るのか?
  • 省電力(STOPモード)を組み合わせた通信は可能か?またそれを使用しない場合の消費電力の差は?

ともあれ、まずは動くモノを仕立て上げなければ。

更に閑話休題

実際にGPSモジュールからのシリアル送信されたデータをSTM32に入力させたところ、どうもうまく受信できませんでした。
どうも、HALドライバの出来がよろしくないようで。
シリアル送受信時の割り込み時に呼び出されるコールバックでバッファリングするという定石で作らないといけないよう。
HALドライバでUARTに関して動かないという情報はWEB上にいろいろ上がっているようだけど、対策についての記載があまりないみたい。
困ったことに、HALドライバについてくる付属してくるExampleもしょぼい…
ちょっと時間がかかりそうです。

STM32/省電力モードへの遷移とGPIOからの入力による省電力モードの終了

STM32を使って作りたいのはGPSロガーに必要な技術的要素は以下の通り。

  1. デジタル出力
    • 電源の制御
    • LEDの点灯状態の制御
  2. デジタル入力
    • タクトスイッチの押下状態の取得
  3. アナログ入力
    • 電源電圧の監視
  4. SPI
    • SDカードへのNMEAの書き込み
  5. マイコンの省電力制御
  6. EXTI割り込み
    • タクトスイッチ押下により、省電力状態からの復帰
  7. USARTによる受信
    • GPSからのNMEA情報の受信

1〜4までは前回までに動作確認が終わりました。
今回は、5, 6を試します。

回路図

これまで組み上げた回路を使用します。ただ、今回はタクトスイッチにコンデンサーを追加しました。チャタリングを抑止するためです。
今回はSDカード(SPI)やADCは使用しません。タクトスイッチとLED制御のためのGPIOのみ使用します。

Circuit

STM32CubeMX

Chip

PB1を GPIO_Input から GPIO_EXTI1 に変更します。

Configuration

Configuration タブに移動し、 System >> NVIC を選択します。

NVIC

EXTI line 0 and line 1 interrupt にチェックを入れます。

ここまで設定したら、コードを生成します。

Eclipse

以下のようなコードを記述します。

/* USER CODE BEGIN 0 */
uint8_t gToggleSw = 1;
/* USER CODE END 0 */
    :
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    HAL_Delay(500);

    if (gToggleSw == 0) {
      // stop here
      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
      // resume from here
      SystemClock_Config();
    }
  }
  /* USER CODE END 3 */
    :
    :
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  gToggleSw ^= 1;
}
/* USER CODE END 4 */

タクトスイッチを押下するごとに点滅と点滅の一時停止が交互に行われます。

試していて困ったのが、ST-Linkを接続していつものように動作確認をしていたところ、タクトスイッチを押下するとプログラムは一時停止。 Resume により再開してもすぐに停止し、 Program received signal SIGINT, Interrupt. が出力され、以降動作しなくなります。

Temporary breakpoint 2, main () at L0SampleExti/Src/main.c:69
69      {

[Remote target] #1 stopped.
main () at L0SampleExti/Src/main.c:69
69      {
The target is not in the halted nor running stated, stepi/continue ignored.

Program received signal SIGINT, Interrupt.
main () at L0SampleExti/Src/main.c:69
69      {

STM32L053C8T6 に書き込んだ後、ST-Linkを接続せずに単体で起動すると期待通り動作します。追々調べることにしましょう。

STM32/STM32L053C8T6でSDカードにアクセスする

STM32を使って作りたいのはGPSロガーです。パーツとしておそらく以下のようなものを使うはず。

  1. デジタル出力
    • 電源の制御
    • LEDの点灯状態の制御
  2. デジタル入力
    • タクトスイッチの押下状態の取得
  3. アナログ入力
    • 電源電圧の監視
  4. SPI
    • SDカードへのNMEAの書き込み
  5. マイコンの省電力制御
  6. EXTI割り込み
    • タクトスイッチ押下により、省電力状態からの復帰
  7. USARTによる受信
    • GPSからのNMEA情報の受信

今のところ、3.までこなしました。続いて4.の通り、SPIを使用してSDカードへのアクセスを試します。

ところで・・・

無知とは恐ろしいです。
ARMマイコンでGPSロガーを作りたいな→8ビットマイコンの置き換えも狙っているCortex-M0マイコンで作ってみよう→NXPは使いにくそうだし(イメージ先行)STM32にしよう→STM32F0もいいけど、より省電力が期待できるSTM32L0にしよう。みたいな流れです。Arduino(あるいはAVRマイコン)のありがたいところは有志によるライブラリが沢山あり、ハードを動かすソフトウェアを一から作ることはほとんどないところにあります。しかし、STM32はそうはいかないようで、SDカードのアクセスの場合もちょっとだけ苦労しました。

同じSTM32マイコンでもF1やF4シリーズだと有志による実装をすぐに見つけることができたのですが、L0はそうはいきませんでした。STM32CubeMXではFatFsの選択ができるのに…ハードに近い部分の処理は開発者が実装しなければならない状態です。ウムム・・・

でも、気を取り直して STM32CubeL0 Embedded software for STM32L0 series (HAL low level drivers, USB, File system, RTOS, Touch Sensing – coming with examples running on ST boards: STM32 Nucleo, Discovery kits and Evaluation boards) – STMicroelectronics からSTM32Cubeをダウンロードしその内容確認したところ、使えそうなソースがありました。

get software

STM32Cube_FW_L0_V1.4.0/Drivers/BSP/Adafruit_Shieldstm32_adafruit_sd.cstm32_adafruit_sd.h があり、ハードを使いこなす処理をSDカードアクセス処理のレベルで抽象化したAPIを呼び出しています。その呼び出されるAPIを実装しており、使いやすそうなソースが STM32Cube_FW_L0_V1.4.0/Drivers/BSP/STM32L0xx_Nucleostm32l0xx_nucleo.cstm32l0xx_nucleo.h がありました。また、 STM32Cube_FW_L0_V1.4.0/Middlewares/Third_Party/FatFs/src/driverssd_diskio.csd_diskio.h があり、 stm32_adafruit_sd.c/h 提供のAPIを呼び出しており、FatFs本体とを つなぐ ようになっていました。

つまり、以下のような呼び出し関係になってそうです。

アプリ
  ↓
FatFs本体
  ↓
sd_diskio.c/h(FatFs Moduleの一部)
  ↓
stm32_adafruit_sd.c/h
  ↓
stm32l0xx_nucleo.c/h
  ↓
HALライブラリ

sd_diskio.c/h はほとんどそのまま使用できそうであり、stm32_adafruit_sd.c/h、stm32l0xx_nucleo.c/h はSDカードアクセスに必要な部分のみ使用し、他(JoyStick, LCD, LED)に関するソースを無効化することで動かせそうです。(実際動き、ファイルの作成・書き込みと読み込みができました)

回路図

SDカードをSPIによりアクセスするため、SPI1にSDカードを接続します。また、CSの制御のためGPIOを使用します。

Circuit

STM32CubeMX

これまで作成したSTM32CubeMXプロジェクトにSPIを追加します。またCS信号のため、GPIOを一つ割り当てます。SPIやGPIOの初期化コードは上記ソースに含まれており、STM32CubeMXで設定する必要は本来ないのですが、stm32l0xx_nucleo.c/h に #ifdef HAL_SPI_MODULE_ENABLED 〜 #endif というコードがあり、これによりSTM32CubeMXで設定していなければ必要な処理が無効化されます。includeファイル内で定義する、もしくは #ifdef HAL_SPI_MODULE_ENABLED#endif を削除することも考えられますが、一旦STM32CubeMXにて使用を宣言します。

Chip

Peripherals >> SPI1 を開き、 ModeFull-Duplex Master に設定します。

また、 PB5GPIO_Output に設定します。

PB5

SPIについて、BSP側のソースで設定しているため、これ以上の設定は行いません(GPSロガーを作るまでにこのやり方はやめるつもり)。

コードを生成し、以降Eclipse本体で作業を行います。

  • sd_diskio.c/h
  • stm32_adafruit_sd.c/h
  • stm32l0xx_nucleo.c/h

上記ソースをEclipseプロジェクトの適当な場所に格納します。

  • SW4STM32/L0SampleFatFs Configuration/Application/User/BSP
    • stm32_adafruit_sd.c
    • stm32_adafruit_sd.h
    • stm32l0xx_nucleo.c
    • stm32l0xx_nucleo.h
  • SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs
    • sd_diskio.c
    • sd_diskio.h

当方では、上記の通り配置しました。

次にSDカード以外の処理を削除していきます。どれを削除すれば良いかは分かりやすいと思います。定義名の接頭辞や途中にが SD_ がついている定義や関数を残し、他を削除すれば良いのです。

diff --git a/SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs/sd_diskio.c b/SW4STM32/L0SampleFatFs Configuration/Appl
ication/User/fatfs/sd_diskio.c
old mode 100755
new mode 100644
index cb16735..d798cf5
--- a/SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs/sd_diskio.c
+++ b/SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs/sd_diskio.c
@@ -28,6 +28,9 @@
 /* Includes ------------------------------------------------------------------*/
 #include 
 #include "ff_gen_drv.h"
+#ifndef USE_NUCLEO
+#include "../BSP/stm32_adafruit_sd.h"
+#endif /* USE_NUCLEO */

 /* Private typedef -----------------------------------------------------------*/
 /* Private define ------------------------------------------------------------*/
@@ -49,7 +52,11 @@ DRESULT SD_read (BYTE, BYTE*, DWORD, UINT);
   DRESULT SD_ioctl (BYTE, BYTE, void*);
 #endif  /* _USE_IOCTL == 1 */

+#ifdef USE_NUCLEO
 const Diskio_drvTypeDef  SD_Driver =
+#else  /* USE_NUCLEO */
+const Diskio_drvTypeDef  USER_Driver =
+#endif /* USE_NUCLEO */
 {
   SD_initialize,
   SD_status,
diff --git a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.c b/SW4STM32/L0SampleFatFs Configuration
/Application/User/BSP/stm32l0xx_nucleo.c
index 3420c82..71ea981 100755
--- a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.c
+++ b/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.c
@@ -101,12 +101,14 @@
 /** @defgroup STM32L0XX_NUCLEO_LOW_LEVEL_Private_Variables
   * @{
   */
+#ifdef USE_NUCLEO
 GPIO_TypeDef* LED_PORT[LEDn] = {LED2_GPIO_PORT};
 const uint16_t LED_PIN[LEDn] = {LED2_PIN};

 GPIO_TypeDef* BUTTON_PORT[BUTTONn] = {KEY_BUTTON_GPIO_PORT };
 const uint16_t BUTTON_PIN[BUTTONn] = {KEY_BUTTON_PIN };
 const uint8_t BUTTON_IRQn[BUTTONn] = {KEY_BUTTON_EXTI_IRQn };
+#endif // USE_NUCLEO

 /**
  * @brief BUS variables
@@ -118,9 +120,11 @@ static SPI_HandleTypeDef hnucleo_Spi;
 #endif /* HAL_SPI_MODULE_ENABLED */

 #ifdef HAL_ADC_MODULE_ENABLED
+#ifdef USE_NUCLEO
 static ADC_HandleTypeDef hnucleo_Adc;
 /* ADC channel configuration structure declaration */
 static ADC_ChannelConfTypeDef sConfig;
+#endif // USE_NUCLEO
 #endif /* HAL_ADC_MODULE_ENABLED */
 /**
   * @}
@@ -142,16 +146,20 @@ void                      SD_IO_WriteReadData(const uint8_t *DataIn, uint8_t *Da
 uint8_t                   SD_IO_WriteByte(uint8_t Data);

 /* LCD IO functions */
+#ifdef USE_NUCLEO
 void                      LCD_IO_Init(void);
 void                      LCD_IO_WriteData(uint8_t Data);
 void                      LCD_IO_WriteMultipleData(uint8_t *pData, uint32_t Size);
 void                      LCD_IO_WriteReg(uint8_t LCDReg);
 void                      LCD_Delay(uint32_t delay);
+#endif // USE_NUCLEO
 #endif /* HAL_SPI_MODULE_ENABLED */

 #ifdef HAL_ADC_MODULE_ENABLED
+#ifdef USE_NUCLEO
 static void               ADCx_Init(void);
 static void               ADCx_MspInit(ADC_HandleTypeDef *hadc);
+#endif // USE_NUCLEO
 #endif /* HAL_ADC_MODULE_ENABLED */
 /**
   * @}
@@ -171,6 +179,7 @@ uint32_t BSP_GetVersion(void)
   return __STM32L0XX_NUCLEO_BSP_VERSION;
 }

+#ifdef USE_NUCLEO
 /**
   * @brief  Configures LED GPIO.
   * @param  Led: Specifies the Led to be configured.
@@ -284,6 +293,7 @@ uint32_t BSP_PB_GetState(Button_TypeDef Button)
 {
   return HAL_GPIO_ReadPin(BUTTON_PORT[Button], BUTTON_PIN[Button]);
 }
+#endif // USE_NUCLEO


 #ifdef HAL_SPI_MODULE_ENABLED
@@ -444,12 +454,14 @@ void SD_IO_Init(void)


   /* Configure LCD_CS_PIN pin: LCD Card CS pin */
+#ifdef USE_NUCLEO
   GPIO_InitStruct.Pin = LCD_CS_PIN;
   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
   GPIO_InitStruct.Pull = GPIO_NOPULL;
   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
   HAL_GPIO_Init(SD_CS_GPIO_PORT, &GPIO_InitStruct);
   LCD_CS_HIGH();
+#endif // USE_NUCLEO

   /*------------Put SD in SPI mode--------------*/
   /* SD SPI Config */
@@ -507,6 +519,7 @@ uint8_t SD_IO_WriteByte(uint8_t Data)
 }

 /********************************* LINK LCD ***********************************/
+#ifdef USE_NUCLEO
 /**
   * @brief  Initializes the LCD
   * @param  None
@@ -645,10 +658,12 @@ void LCD_Delay(uint32_t Delay)
 {
   HAL_Delay(Delay);
 }
+#endif // USE_NUCLEO
 #endif /* HAL_SPI_MODULE_ENABLED */

 /******************************* LINK JOYSTICK ********************************/
 #ifdef HAL_ADC_MODULE_ENABLED
+#ifdef USE_NUCLEO
 /**
   * @brief  Initializes ADC MSP.
   * @param  None
@@ -789,6 +804,7 @@ JOYState_TypeDef BSP_JOY_GetState(void)
   /* Return the code of the Joystick key pressed */
   return state;
 }
+#endif // USE_NUCLEO
 #endif /* HAL_ADC_MODULE_ENABLED */

 /**
diff --git a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.h b/SW4STM32/L0SampleFatFs Configuration
/Application/User/BSP/stm32l0xx_nucleo.h
index 20912f3..5021c1a 100755
--- a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.h
+++ b/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.h
@@ -66,6 +66,7 @@
 /** @defgroup STM32L0XX_NUCLEO_LOW_LEVEL_Exported_Types
   * @{
   */
+#ifdef USE_NUCLEO
 typedef enum
 {
   LED2 = 0,
@@ -94,6 +95,7 @@ typedef enum
   JOY_RIGHT = 4,
   JOY_UP    = 5
 } JOYState_TypeDef;
+#endif // USE_NUCLEO

 /**
   * @}
@@ -113,6 +115,7 @@ typedef enum
 /** @addtogroup STM32L0XX_NUCLEO_LOW_LEVEL_LED
   * @{
   */
+#ifdef USE_NUCLEO
 #define LEDn                               1

 #define LED2_PIN                           GPIO_PIN_5
@@ -122,6 +125,7 @@ typedef enum

 #define LEDx_GPIO_CLK_ENABLE(__INDEX__)    do {LED2_GPIO_CLK_ENABLE(); } while(0)
 #define LEDx_GPIO_CLK_DISABLE(__INDEX__)   LED2_GPIO_CLK_DISABLE())
+#endif // USE_NUCLEO
 /**
   * @}
   */
@@ -129,6 +133,7 @@ typedef enum
 /** @addtogroup STM32L0XX_NUCLEO_LOW_LEVEL_BUTTON
   * @{
   */
+#ifdef USE_NUCLEO
 #define BUTTONn                            1

 /**
@@ -150,6 +155,7 @@ typedef enum

 #define BUTTONx_GPIO_CLK_ENABLE(__INDEX__)     do {KEY_BUTTON_GPIO_CLK_ENABLE(); } while(0)
 #define BUTTONx_GPIO_CLK_DISABLE(__INDEX__)    (KEY_BUTTON__GPIO_CLK_DISABLE())
+#endif // USE_NUCLEO
 /**
   * @}
   */
@@ -198,10 +204,12 @@ typedef enum
 /**
   * @brief  LCD Control Lines management
   */
+#ifdef USE_NUCLEO
 #define LCD_CS_LOW()      HAL_GPIO_WritePin(LCD_CS_GPIO_PORT, LCD_CS_PIN, GPIO_PIN_RESET)
 #define LCD_CS_HIGH()     HAL_GPIO_WritePin(LCD_CS_GPIO_PORT, LCD_CS_PIN, GPIO_PIN_SET)
 #define LCD_DC_LOW()      HAL_GPIO_WritePin(LCD_DC_GPIO_PORT, LCD_DC_PIN, GPIO_PIN_RESET)
 #define LCD_DC_HIGH()     HAL_GPIO_WritePin(LCD_DC_GPIO_PORT, LCD_DC_PIN, GPIO_PIN_SET)
+#endif // USE_NUCLEO

 /**
   * @brief  SD Control Interface pins
@@ -211,6 +219,7 @@ typedef enum
 #define SD_CS_GPIO_CLK_ENABLE()                 __HAL_RCC_GPIOB_CLK_ENABLE()
 #define SD_CS_GPIO_CLK_DISABLE()                __HAL_RCC_GPIOB_CLK_DISABLE()

+#ifdef USE_NUCLEO
 /**
   * @brief  LCD Control Interface pins
   */
@@ -242,6 +251,7 @@ typedef enum
 #define NUCLEO_ADCx_GPIO_CLK_DISABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()

 #endif /* HAL_ADC_MODULE_ENABLED */
+#endif // USE_NUCLEO

 /**
   * @}
@@ -259,6 +269,7 @@ typedef enum
   * @{
   */
 uint32_t         BSP_GetVersion(void);
+#ifdef USE_NUCLEO
 void             BSP_LED_Init(Led_TypeDef Led);
 void             BSP_LED_On(Led_TypeDef Led);
 void             BSP_LED_Off(Led_TypeDef Led);
@@ -269,6 +280,7 @@ uint32_t         BSP_PB_GetState(Button_TypeDef Button);
 uint8_t          BSP_JOY_Init(void);
 JOYState_TypeDef BSP_JOY_GetState(void);
 #endif /* HAL_ADC_MODULE_ENABLED */
+#endif // USE_NUCLEO
 /**
   * @}
   */

見ての通り、#ifdef USE_NUCLEO〜#endif で括っています。 USE_NUCLEOは定義していないので、結果として括られた部分の処理は無効となります。

/Src/fatfs.c にSDカードのマウント処理を追加します。また、semihostingを使用しているので、 initialise_monitor_handles(); も追加しています。

/* USER CODE BEGIN Variables */
extern void initialise_monitor_handles(void);
FATFS SDFatFs;  /* File system object for SD card logical drive */
/* USER CODE END Variables */    
  :
void MX_FATFS_Init(void) 
{
  /*## FatFS: Link the USER driver ###########################*/
  retUSER = FATFS_LinkDriver(&USER_Driver, USER_Path);

  /* USER CODE BEGIN Init */
  FRESULT res;
  initialise_monitor_handles();
  res = f_mount(&SDFatFs, (TCHAR const*)USER_Path, 0);
  printf("f_mount() res=%d\n", (int)res);
  /* USER CODE END Init */
}

main()関数にサンプルとしてファイルの書き込みを行い、その内容を読み出すソースを記述します。

int main(void)
{

  /* USER CODE BEGIN 1 */
  uint32_t byteswritten, bytesread;                     /* File write/read counts */
  FIL MyFile;     /* File object */
  uint8_t wtext[] = "This is STM32 working with FatFs"; /* File write buffer */
  uint8_t rtext[100];                                   /* File read buffer */
  FRESULT res;                                          /* FatFs function common result code */
  /* USER CODE END 1 */
    :
  /* USER CODE BEGIN 2 */
  res = f_open(&MyFile, "STM32.TXT", FA_CREATE_ALWAYS | FA_WRITE);
  printf("f_open() res=%d\n", (int)res);
  if (res == FR_OK) {
    res = f_write(&MyFile, wtext, sizeof(wtext), (void *)&byteswritten);
    printf("f_write() res=%d, byteswritten=%lu\n", (int)res, byteswritten);
    f_close(&MyFile);
  }

  res = f_open(&MyFile, "STM32.TXT", FA_READ);
  printf("open() res=%d\n", (int)res);
  if (res == FR_OK) {
    res = f_read(&MyFile, rtext, sizeof(rtext), (UINT*)&bytesread);
    printf("f_read() res=%d, bytesread=%lu, rtext=%s\n", (int)res, byteswritten, rtext);
    f_close(&MyFile);
  }
  FATFS_UnLinkDriver(USER_Path);
  /* USER CODE END 2 */

  while (1);
}
Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 300 kHz
adapter_nsrst_delay: 100
  :
semihosting is enabled
f_mount() res=0
f_open() res=0
f_write() res=0, byteswritten=33
open() res=0
f_read() res=0, bytesread=33, rtext=This is STM32 working with FatFs

STM32/アナログ入力を試す

今回は、STM32のADCを使用してみます。

回路について

以下のような回路としました。前回までの回路をベースにしていることもありごちゃごちゃとした印象を受けるかもしれません。しかし、今回やりたいことは単純明快で、ADCを使用して電圧を計測するというものです。これによりLi-Poの電圧を監視し、一定電圧を下回ったらマイコンをSTOP状態に落とし、過放電を防ぐ目的での使用を想定しています。

Circuit

100KΩ接続を計測対象の電源とGNDの間に2つ直列に接続し、その間に PA10 を接続します。抵抗分圧ですね。Li-Poは3.7Vありマイコンには3.3Vを提供し、基準電圧も3.3Vにします。このままでは3.3V以上の計測は出来ないため抵抗分圧で電圧を落としています。この場合は1/2の電圧になるはずです。

STM32CubeMXでの設定

Chip

まずはEclipseを起動し、これまで使用していたプロジェクトファイルを読み込みます。 Configuration >> Peripherals >> ADC にある IN0 にチェックを入れます。

Configuration

ADC をクリックします。

Parameter Settings に移動し、 Clock PrescalerSynchronous clock mode divided by 4 に、 Continuous Conversion ModeEnabled に設定してみます。

Parameter Settings

ここまでできたらコードを生成しましょう。

ソフトウェア

main()関数先頭に変数の定義を追加します。

uint32_t adcValue = 0;
uint32_t volt     = 0;
uint8_t adcCount  = 0;

main()関数にあるwhile()ループ内に以下のようなコードを記述します。

if (adcCount == 0) {
   HAL_ADC_Start(&hadc);
   if (HAL_ADC_PollForConversion(&hadc, 1000000) == HAL_OK) {
    adcValue = HAL_ADC_GetValue(&hadc);
    HAL_ADC_Stop(&hadc);
    volt = (adcValue * 3300 * 2) / 4096; // 4096 => 3.3V
    printf("ADC Value=%lu, Volt=%lu.%2lu\n", adcValue, volt / 1000, volt % 1000);
    adcCount++;
  }
} else {
  adcCount = (adcCount + 1) % 10;
}
HAL_Delay(50);

50ms毎にループさせていますが、10回ループする毎、つまり500ms毎に計測を開始します。電圧が取得できたらその値を表示します。

実行例

Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 300 kHz
adapter_nsrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Info : clock speed 300 kHz
Info : SWD IDCODE 0x0bc11477
Info : stm32l0.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : accepting 'gdb' connection on tcp/3333
Info : STM32L flash size is 64kb, base address is 0x8000000
undefined debug reason 7 - target needs reset
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000
STM32L0: Enabling HSI16
adapter speed: 2500 kHz
Error: stm32l0.cpu -- clearing lockup after double fault
Warn : couldn't use loader, falling back to page memory writes
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000
semihosting is enabled
hello world
ADC Value=2637, Volt=4.249
ADC Value=2641, Volt=4.255
ADC Value=2639, Volt=4.252
ADC Value=2656, Volt=4.279
ADC Value=2645, Volt=4.261
ADC Value=2645, Volt=4.261
ADC Value=2641, Volt=4.255

STM32/セミホスティングを使ってみる

OpenOCDでは、 Semihosting なるものをサポートしているとのこと。

ARM Information Center

これにより、デバッグの選択の余地が増えることを意味し、開発期間をより短くできる可能性を秘めていると思います。

Arduinoを使っていた場合はそのようなものはなく、

  1. USARTを使用して出力する
  2. SDカードにログとして保存する
  3. LCDなどに表示する

といったことをする必要があり、いずれも回路を追加する必要がありました。これはちょっと面倒です。1.はもっとも有力な選択肢になると思いますが他にUSARTを使用する場合はソフトウェアシリアルを追加で入れる必要がありました。2.と3.もできればしたくないです。

ともあれ、過去に構築方法を説明した SWSW4STM32 System Workbench for STM32 の環境でもSemihostingが使用できるので、活用しましょう。

Eclipseの設定

プロジェクトの設定

Project >> Properties を選択します。

Settings

左側のツリーは C/C++ Build >> Settings* を選択します。右側は Tool Settings タブを選択します。その下に表示されるツリーは MCU GCC Linker を選択します。 Linker flags に ** –specs=rdimon.specs -lrdimon** を追加します。

ソースの修正

Semihostingではprintf() などが使用できますが、その前に initialise_monitor_handles() を呼び出す必要があります。main() の先頭に記述しましょう。試しに、続けてprintf()を記述してみます。

/* USER CODE BEGIN 0 */
extern void initialise_monitor_handles(void);
/* USER CODE END 0 */
    :
int main(void)
{
  /* USER CODE BEGIN 1 */
  initialise_monitor_handles();
  printf("hello world\n");
  /* USER CODE END 1 */
    :

そうそう、試した限りでは、改行コード \n を記述しなければEclipseの Console タブに表示されませんでした。

あと、WEBページによっては -nostartfiles を削除する手順を紹介しているところもありましたが、特に実施しなくても文字列の出力ができました。もっとも誤った手順なのかもしれませんが…

ここまでできたら、ビルドします。

デバッグ時のオプション

Run >> Debug Configurations… を選択し、デバッグで使用している設定を選択します。

Commands Tab

Commands タブを選択し、 ‘Initialize’ commands に以下の1行を追加します。

monitor arm semihosting enable

実行

Debug ボタンを押下してみましょう。以下のように Console タブ上に以下のようなログが出力され、printf()で記述した内容も出力されると思います。

Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 300 kHz
adapter_nsrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Info : clock speed 300 kHz
Info : SWD IDCODE 0x0bc11477
Info : stm32l0.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : accepting 'gdb' connection on tcp/3333
Info : STM32L flash size is 64kb, base address is 0x8000000
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000, semihosting
STM32L0: Enabling HSI16
adapter speed: 2500 kHz
Error: stm32l0.cpu -- clearing lockup after double fault
Warn : couldn't use loader, falling back to page memory writes
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000, semihosting
semihosting is enabled
hello world.

これで、よりお手軽にデバッグできます (^^)

STM32/タクトスイッチの状態を取得する

追加回路

PB1 にタクトスイッチを接続します。STM32マイコンはプルアップ抵抗の設定ができるので、抵抗は接続しません。

Circuit

STM32CubeMXでの設定

タクトスイッチの状態を取得するサンプルを取得します。タクトスイッチが押される毎にLEDの点滅・消灯の切り替えを行うサンプルを作ります。前回のタイマ割り込みによるLEDを点滅させるEclipseプロジェクトをベースに作ってみます。

まずはSTMCubeMXを起動し、前回使用したプロジェクトを読み込みます。
マイコンの PB1 をクリックし、 GPIO_Input を選択します。

Chip

Configuration タブから System >> GPIO をクリックします。

Configuration

PB1 をクリックし、 GPIO Pull-up/Pull-down のプルダウンメニューから Pull-up を選択します。

PB1

Project >> Generate Code にてコードを生成します。

Eclipseにてソース修正

main.c を更に修正します。挿入しているコードは以下の通り。

int main(void)
{
  /* USER CODE BEGIN 1 */
  GPIO_PinState state;
  uint8_t swOnCount = 0;
  /* USER CODE END 1 */
      :
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */
      :
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
    if (state == GPIO_PIN_RESET) {
      if (swOnCount < 0xff) {
        swOnCount++;
        if (swOnCount == 4) {
          gSw ^= 0xff;
          swOnCount = 0xff;
        }
      }
    } else {
      swOnCount = 0;
    }
    HAL_Delay(50);
  }
  /* USER CODE END 3 */
}

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == htim2.Instance) {
    if (gSw == 0x00) {
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    } else {
      HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    }
  }
}
/* USER CODE END 4 */

しょぼいコードですみません。
main() では50ms毎にタクトスイッチの状態を読み込みます。4回(つまり200ms)連続で押され続けていたらLEDを点滅←→消灯を切り替えます。これはチャタリング防止に加え、while()ループを止めたくなかったためにこのようにしています。

さて、ここまででGPIOを使用したデジタル値の入出力とタイマ割り込みができるようになりました。

STM32/タイマ割り込みでLEDを点滅させる

前回はメインループ内で点滅させましたが、今度はタイマ割り込み内で処理させてみます。STM32CubeMXを起動し、前回の作ったプロジェクトを Load Project から開きます。

左側にある Peripherals から TIM2 ツリーを開き、 Clock SourceInternal Clock を選択します。

Pinout

Clock Configuration タブに移動し、クロック周波数を確認します。

Clock Configuration

供給されているクロックが2.097MHzでした。この値はタイマ割り込み周期を決め上で必要な数値となるのでメモしましょう。

Configuration タブに移動し、 Control にある TIM2 をクリックします。

Configuration

Parameter Settings タブに移動し、 Prescaler(PSC – 16 bits value))2097 を、 Counter Period(AutoReload Register – 16 bits value)1000 を設定します。これにより1秒毎にタイマ割り込みが入ります。

Parameter Settings

NVIC Settings タブに移動し、 TIM2 global interrupt にある Enabled をチェックします。これによりタイマ割り込みが入るようになります。

NVIC Settings

ここまでできたら Project >> Generate Code を選択して、コードを生成しましょう。

コードを生成したら、Eclipseから Project >> C/C++ Index >> Rebuild を選択します。これをしておかないと、コンパイルエラーが発生することがあります。

前回 main.c に作成したコードはコメントアウトなどし、実行しないようにします。
同じく、 main.c の後半にある「/* USER CODE BEGIN 4 /」と直後の「/ USER CODE END 4 */」の間にタイマ割り込みのコードを記述します。

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == htim2.Instance) {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
  }
}
/* USER CODE END 4 */

タイマ割り込みを使用する場合は、そのための初期化が必要になります。main()内、while()ループには入る前に初期化コードを追加します。

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */

ビルドを行い、実行してみよう。今度はタイマ割り込みによりLEDが点滅します。

STM32/STM32CubeMX + HAL DriversでLEDチカをさせてみる

前回は Standard Peripheral Library を使ったLEDチカをさせてみましたが、今度は STM32CubeMX + HAL DriversでLEDチカをさせてみます。Standard Peripheral Libraryは過去に有志が作成したプログラム + 回路を試すという用途しか想定していないので、深追いはしません。STM32CubeMX + HAL Driversを色々と試してみたいと思います。

回路について

ひとまず、ごく簡単な回路を作りました。

circuit

本来ならパスコンを入れるべきなんだろうけど入れていません。ブレッドボード上に楽して作ったので。最終的にガジェットに仕立て上げる時には入れましょう。

電源ラインについては全て接続しました。AVRマイコンでは接続しないと安定しないことがあったので。この点については追々ちゃんとマニュアルを読んで、どうするべきか把握しておこうと思います。

STM32CubeMXでスケルトンコードを作成する

STM32CubeMXはCPUを選択し、使用する機能やピンを指定することで、それに見合ったイニシャルコードを生成してくれるというものです。少しでもコードを最適化し、使用するCPU資源を減らすことを趣味のようにしている人たちには性に合わないかもしれませんが、限られた時間でモノを作りたいという人たちにはありがたいものです。STM32マイコンは色々と機能が載っていますが、1つのピンに幾つかの機能が割り当てることができるようになっていてそれらの設定が必要になっています。また、もちろんそれぞれの資源を使いこないために必要なレジスタの設定も必要です。とりあえずSTM32マイコンを勉強中出会っても比較的容易にモノづくりができます。ただ、バグに見舞われた場合にはまってしまう危険がありますが…

STM32CubeMXの起動

何はともあれEclipseを起動しましょう。起動したら、 Window >> Show View >> Other… を選択しましょう。 STM32CubeMX が一覧に存在するはずなので、こちらを選択。

STM32CubeMX

上のスクリーンショットのような画面が表示されたら New Project を選択しましょう。

MCU Selector

Board Selector)

タブ MCU Selector / Board Selector のいずれかを選択しましょう。前者ならば、ターゲットとするマイコンを選択し、後者ならばボードを選択します。当方は STM32L053C8T6 を購入したので、 MCU Selector から選択します。

MCU Selector-2

OKを押すと、 Pinout タブが表示されます。

Pinout

PB0 にLEDのアノードを接続することにしたので、PB0にGPIOを割り当てます。PB0のピンを左クリックすると、割り当て可能な機能の一覧が表示されます。

Pinout-PB0

GPIO_Output を選択します。

Pinout-PB0-2

こんな感じの表示になります。
続いて画面左側にあるツリー Peripherals >> SYS をオープンし、 Serial Wire Debug(SWD) を選択します。
最終的には以下のような表示となっているはずです。

Pinout-SWD

続いて、 Configuration タブに移動します。

Configuration

System >> GPIO をクリックしてみましょう。すると、先ほどGPIOに割り当てた PB0 が表示されています。試しにこの項目をクリックしてみましょう。

PB0 Configuration

目的に応じて設定を変更します。LEDチカをするにはデフォルトのままで良いので、何もせずに閉じます。
クロックの修正が必要ならば、 Clock Configuration タブを表示して修正します。当方はデフォルトのままでよしとしたので変更しませんでした。デフォルトでは2MHz程度で動作するようです。

ここまでできたらプロジェクトを保存します。 Project >> Settings… を選択します。まずは Projct タブから。 Project Name , Project Location , * , Toolchain / IDE を設定します。特に重要なのが Toolchain / IDE です。当方がインストールしたのは SW4STM32 System Workbench for STM32 なので、 SW4STM32 を選択します。当方は以下のように設定しました。

Code Generator

Code Generator タブについては、まずはデフォルトで良いと思います。消費電力をより抑えるために Set all free pins as analog(to optimize the power consumption) を選択しても良いでと思います。設定は後から変更できること、意味をきっちり理解しない状態で設定するのは余計なトラブルに見舞われる懸念があるため、当方はデフォルトのままとしました。

ここまでできたら Ok ボタンを押下しましょう。

続いて、いよいよコードを生成します。 Project >> Generate Code を選択します。すると先ほどの設定に応じたコードがプロジェクトフォルダ配下に生成されます。

プロジェクトのインポート

ここまでの作業ではプロジェクトフォルダ配下にプロジェクトが生成されるだけであり、プロジェクトのインポートが必要です。 File >> import… を選択します。

Import

Exsisting Projects into Workspace を選択し、 Next > を選択します。

Import-2

Select root directory にはSTM32CubeMXにて Generate Code にて出力したフォルダを指定しましょう。 Projects: に出力したプロジェクトがありますので、こちらを選択します。最後に Finish ボタンを押下します。

生成されたコードを見てみましょう。STM32CubeMXでの設定に対応するコードは Application >> User 配下に出力されます。
まずは main.c を見てみましょう。main関数があります。ところどころ、 「/* USER CODE BEGIN ○ /」〜「/ USER CODE END ◯ /」という行がありますが、開発者はこの間にソースコードを記述していくことになります。 *STM32CubeMX はこの間に挿入されたコードを消去したりしないようにしてPeripheralの設定をコードに出力します。

stm32l0xx_hal_msp.cstm32l0xx_it.c には、それぞれ HAL_MspInit()SysTick_Handler() がありました。GPIOの初期化コードは MX_GPIO_Init() に含まれていました。

  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

設定した内容に従ってコードが生成されている様子が分かります。

さて、LEDを点滅させるコードを書いてみましょう。
あっ、その前に HAL Drivers の仕様が分からなければコードを書くことができないので、まずは仕様が書かれたPDFファイルをダウンロードしましょう。

Part Number Search ? STMicroelectronics からマイコンに応じたSTM32Cubeを選択。当方の場合は STM32CubeL0 が該当します。 User Manual >> Description にある、 Description of STM32◯◯xx HAL Drivers のリンクをダウンロードします。

User Manual

さて、今回は単純にmain()関数内のwhile(1)内で点滅させる処理を記述してみます(タイマ処理で点滅させる方がスマートですが、まずは手っ取り早くwhile内に記述してみます)。

int main(void)
{
  :
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

}

ハード・ソフト共に不具合を混入していなければ、1秒おきに点灯・消灯を繰り返します。Flashへの書き込み及びデバッグの方法は前回記載した通り。

STM32/Standard Peripheral Libraryを使ったプロジェクトを作ってみる

プロジェクトを作る

STM32の製造・販売を行っているSTMicroelectronicsは以前はStandard Peripheral Libraryを提供していたが、今ではSTM32CubeMXとというツールとHAL driversも提供しており、どうもこちらを推しているらしい。よって当方もSTM32CubeMX & HAL driversを使用するようにしています。しかしながら意外とStandard Peripheral Libraryを使用した記事が多いので、まずはStandard Peripheral Libraryを使用したプロジェクトを作ってみます。

File >> New >> C++ Project を選択します。

C++ Project

Project name: にプロジェクト名を入力し、 Next > を押下します。

Target processor settings

各項目に対し、ターゲットとするCPUにあった設定値を記述します。 STM32F0DISCOVERY の場合、STM32F051R8T6が搭載されています。こちらはFlashが64KB、RAMが8KB搭載されていますので、これに合わせます。 Clock(Hz) は初期値のままとしました。
Content:Blinky (blink a led)Empty (add your own content) から選択できます。手早くLEDがチカチカするところを確認したかったため、前者を選択しました。
Use system calls はデフォルトの Freestanding(no POSIX sysstem calls) で良いと思います。
Trace outputNone(no trace output) としました。デフォルトの Semihosting DEBUG channel などの場合、Semihosting としての設定が必要となるようです。設定できていなければ、実行時にTRAPが発生するようです。
その他のチェックボックスもデフォルトのままとしました。

Folder Settins

こちらの画面についてもデフォルトのままとし Next > ボタンを押下します。

Select Configurations

この画面でもデフォルトのまま。 Next > を押下します。

Cross GNU ARM Toolchain

Toolchain name はデフォルトの GNU Tools for ARM Embedded Processors (arm-none-ebi-gcc) としました。また、 Toolchain path には、インストールしたToolchainのディレクトリを指定します。
設定できたら、 Finish ボタンを押下します。

ここまでできたらビルドしてみましょう。 Projcec >> Build Project を選択します。
ToolchainのPathに誤りがなければ以下のようなメッセージとともにビルドが終了します。


22:36:39 **** Build of configuration Debug for project Sample ****
make all
Building file: ../system/src/stm32f0-stdperiph/stm32f0xx_gpio.c
:
arm-none-eabi-size --format=berkeley "Sample.elf"
text data bss dec hex filename
4065 176 412 4653 122d Sample.elf
Finished building: Sample.siz

22:36:41 Build Finished (took 2s.500ms)

デバッグについて

まず、OpenOCDを起動します。
Run >> External Tools から設定済みのOpenOCDの起動コマンドを実行します。
成功した場合は、以下のようなメッセージが出力されます。


Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v25 API v2 SWIM v0 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 2.880856
Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints

また、STM32F0DISCOVERYの場合は LD2 が緑と赤とか交互に点滅します。

次に Run >> Debug Configurations… を選択します。

Create, manage, and run configurations

*Zylin Embedded debug(Native)を選択し、新規作成します。

Debugger

Debugger タブでは、 DebuggerEmbedded GDB を選択します。また、 GDB debugger には、インストールしたToolchainに含まれる gdb を選択します。

Commands

‘Initialize’ commands には以下のとおり入力します。


target extended-remote localhost:3333
monitor reset init
load
monitor reset halt

設定できたら、 Apply を押下した後、 Debug ボタンを推してみよう。

うまく動作した場合、 start , main で停止するので、その都度 Resume を行うと LD3 が点滅します。