STM32のDMA(Direct Memory Access)はメモリー同士での転送も可能で、memcpy(を介したアセンブラ命令)を使うよりも2倍ほど高速です。大きなデータをコピーしたい場合はこの機能を積極的に使っていきましょう。

STM32CubeMXではメモリー間転送に特化したAPIは用意されていないため初期設定の手入力が必要ですが、全体としての作業は多くはありません。

以下はDMA2/Stream0にメモリー間転送を割り当てた場合のプログラム例です。
#define BUFFER_SIZE 256
uint8_t base_buffer[256];    // 転送元バッファ
uint8_t target_buffer[256];    // 転送先バッファ

void InitDMA()
{
    // 優先度の指定
    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
    // DMA割り込みを有効にする
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

void CopyMemory()
{
    // 転送の開始(DMAハンドラ、転送元アドレス、転送先アドレス、転送サイズ)
    HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, base_buffer, target_buffer, BUFFER_SIZE);
}

void DMA2_Stream0_IRQHandler(void)
{
    // メモリー転送完了時に実行される割り込み処理
}
 | 2019年5月20日

STM32CubeMXではHIDやマスストレージなどの一般的なUSBデバイスのひな形を作ることができますが、独自のディスクリプタを用いたデバイスを作ろうとするとどうしても低レベルの開発となってしまいます。

mbedやArduino互換プロジェクトを介してのアプローチという方法もありますが、TeenyUSBというオープンソースプロジェクトが、オリジナルのデバイス開発という点において今のところ最適だったので、実装方法をまとめてみました。

STM32CubeMXで新しいプロジェクトを作成したら、USBデバイス(F4シリーズならUSB_OTG_FS)を追加し、NVIC設定でUSBの割り込みを有効にします。ミドルウェアの指定は不要です。
TeenyUSBよりソースコードを入手したら、この中にある「usb_stack」内のファイル、および「usb_stack/demo/custom_bulk/teeny_usb_init.h」「usb_stack/demo/custom_bulk/teeny_usb_desc.c」「CubeMXF303/Inc/board_config.h」のファイルをプロジェクトにコピーします。
プロジェクトのプロパティを開き、追加した「Inc/teeny_usb」フォルダーをインクルードファイルのパスとして追加し、Symbolsに「NO_HOST」の定義を追加(これでUSBデバイスとしてのみ有効)します。
続いてコードの微調整です。「teeny_usb.c」にある「WEAK」を「__weak」に置換(ARMコンパイラ属性)し、「stm32f4xx_it.c」にある「OTG_FS_IRQHandler」関数を削除します(teeny_usbで定義済みの関数と競合するため)。

そしてmain.cに__weak定義の関数を上書きすることで、コールバックによる適切な処理ができるようにします。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "teeny_usb.h"    // TeenyUSBのインクルード
/* USER CODE END Includes */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// USBエンドポイントとバッファの定義
#define  TX_EP   PCD_ENDP1
#define  RX_EP   PCD_ENDP2
uint8_t buf[4096];
__IO uint32_t data_cnt = 0;

// データ送信完了コールバック
void tusb_on_tx_done(tusb_device_t* dev, uint8_t EPn)
{
  if(EPn == TX_EP){
    tusb_set_rx_valid(dev, RX_EP);
  }
}

// データ受信完了コールバック
int tusb_on_rx_done(tusb_device_t* dev, uint8_t EPn, const void* data, uint16_t len)
{
  if(EPn == RX_EP){
    data_cnt = len;
    return len;
  }
  return 0;
}

// USB初期化時のコールバック
void tusb_reconfig(tusb_device_t* dev)
{
  // call the BULK device init macro
  BULK_TUSB_INIT(dev);
  // setup recv buffer for rx end point
  tusb_set_recv_buffer(dev, RX_EP, buf, sizeof(buf));
  // enable rx ep after buffer set
  tusb_set_rx_valid(dev, RX_EP);
}

// ウェイト処理のコールバック(HALライブラリのウェイト関数を使用)
void tusb_delay_ms(uint32_t ms)
{
   HAL_Delay(ms);
}
/* USER CODE END 0 */

int main(void)
{

  /* USER CODE BEGIN 2 */
  // USBデバイスの作成
  tusb_device_t* dev = tusb_get_device(TEST_APP_USB_CORE);
  tusb_open_device(dev);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  char msg[] = "hello!";
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    // 一定間隔ごとにホストへメッセージを送信
    HAL_Delay(3000);
    tusb_send_data(dev, TX_EP, msg, 6, 0);
  }
  /* USER CODE END 3 */
}
Nucleo-F446REでは、USBケーブルを写真のように接続します。
パソコン側でのlibusbの実装例です。Windows版ではパソコンにつないでいるUSB機器の列挙を行い、ベンダーIDとプロダクトIDの一致するデバイスに接続して、非同期処理でWinUSBのシミュレートを行います。
#include "libusb.h"
#include <iostream>

#define VENDORID 0x0483
#define PRODUCTID 0x0001

#define EP1 0x1
#define EP2 0x2

int count = 0;

bool need_exit = false;

void OnUsbReceived(libusb_transfer * transfer)
{
    if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
        fprintf(stderr, "transfer status %d?\n", transfer->status);
        need_exit = true;
        return;
    }

    printf("%d bytes received.\n", (int)transfer->actual_length);
    count++;
    if (count > 500) need_exit = true;

    libusb_submit_transfer(transfer);
}

void OnUsbSend(libusb_transfer * transfer)
{

}

int main()
{
    int res;
    libusb_device_handle *device = nullptr;
    libusb_transfer *send = nullptr, *recv = nullptr;
    unsigned char send_buffer[32] = { 0x00 }, recv_buffer[32];

    res = libusb_init(NULL);

    libusb_device **devs, *dev = NULL;
    libusb_device *found = NULL;
    ssize_t cnt = libusb_get_device_list(NULL, &devs);

    int i = 0;
    while ((dev = devs[i++]) != NULL) {
        struct libusb_device_descriptor desc;
        int r = libusb_get_device_descriptor(dev, &desc);
        printf("%X/%X\n", desc.idVendor, desc.idProduct);
        if (desc.idVendor == VENDORID && desc.idProduct == PRODUCTID) {
            break;
        }
    }

    if(dev) i = libusb_open(dev, &device);

    libusb_free_device_list(devs, 1);

    if (i) goto EXIT;

    res = libusb_claim_interface(device, 0);
    if (res < 0) {
        fprintf(stderr, "usb_claim_interface error %d\n", res);
        goto EXIT;
    }
    printf("claimed interface\n");

    recv = libusb_alloc_transfer(0);
    libusb_fill_bulk_transfer(recv, device, LIBUSB_ENDPOINT_IN | EP1, recv_buffer, 32, OnUsbReceived, NULL, 0);

    send = libusb_alloc_transfer(0);
    libusb_fill_bulk_transfer(send, device, LIBUSB_ENDPOINT_OUT | EP2, send_buffer, 32, OnUsbSend, NULL, 0);

    res = libusb_submit_transfer(recv);

    strcpy_s((char*)send_buffer, 32, "hello!");
    res = libusb_submit_transfer(send);


    while (need_exit == false) {
        res = libusb_handle_events(NULL);
        if (res < 0) break;
    }


EXIT:
    if (send) {
        libusb_cancel_transfer(send);
        libusb_free_transfer(send);
    }
    if (recv) {
        libusb_cancel_transfer(recv);
        libusb_free_transfer(recv);
    }
    if(device) libusb_release_interface(device, 0);
    libusb_close(device);
    libusb_exit(NULL);

    return 0;
}
 | 2019年3月10日

組み込み機器において、メモリー領域の効率的な転送に欠かせないDMAですが、転送完了時の割り込みではメモリーのコピーや加工など、どうしてもCPUの介入が必要で、そこにわずかな隙が出てきます。

オーディオストリームなどでは2つのバッファを用意して適切なタイミングで参照先を入れ替えるダブルバッファと呼ばれる手法が一般的です。STM32F4などのハイパフォーマンスモデルではDMA1/DMA2でダブルバッファをサポートしていますが、逆に言えばエントリーモデルではハードウェア側でダブルバッファを実装することはできません。そこで、STM32で一般的に使われているダブルバッファを模した機能を実装するための手法を紹介します。

STM32のDMAには、指定したメモリーのアドレスが末端に達したら自動で先頭アドレスに戻り、エラーが発生しない限り続行しつづける、循環モード(Circular mode)と呼ばれる機能が用意されています。また、DMA転送の割り込みには「データの半分の転送が完了」でも発生させることができるので、通常の2倍バッファを確保しておき、割り込みごとに前半と後半のデータを読み書きすれば、ダブルバッファを再現でき、転送を停止させることなく処理をし続けられることになります。
STM32CubeMXでは、DMA設定の「DMA Request Setting」の「Mode」を「Circular」にすることで循環モードを指定します。
こちらは循環モードによるI2Sからのデータを半永久的に受信するプログラム例です。コールバック関数はライブラリー内ですでに定義されているので、関数ポインターの関連付けなどは不要です。
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint32_t buffer[64];    // 必要なバッファサイズの2倍確保

void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
    // 前半のメモリーがアクセス可能
    ConvertI2SData(hi2s, buffer + 0, 32);
}

void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s)
{
    // 後半のメモリーがアクセス可能
    ConvertI2SData(hi2s, buffer + 32, 32);
}
/* USER CODE END 0 */

int main(void)
{
  /* USER CODE BEGIN 2 */
  // 循環モードでは1回呼び出したら基本的に動き続ける
  HAL_I2S_Receive_DMA(&hi2s, buffer, 64);
  /* USER CODE END 2 */
}
 | 2019年3月8日

DMA(Dicrect Memory Access)はCPUを使用せずにメモリーを転送する技術で、オーディオなどの継続的に送受信する必要のある信号のやりとりでは必須のテクニックです。STM32CubeMXでは各種モジュールとを紐付けることで、複雑なプログラムを組むことなくDMAによるデータの送受信が行えます。

試しにUSART2にDMAを割り当てて動かしてみましょう。STM32CubeMXの「USART2→DMA Settings」を開き、USART2_RX(受信)とUSART2_TX(送信)にそれぞれDMAのストリーム(STM32F4では2つのDMAコントローラーそれぞれに8つのストリームを設定できる)を割り当てます。
DMAによるシリアルデータの転送は「HAL_UART_Transmit_DMA」で、受信は「HAL_UART_Receive_DMA」関数を使用します。それぞれの転送が完了すると割り込みが発生しますが、割り込みはUSARTの割り込みではなく、DMA側の割り込み関数(DMA1_Stream6_IRQHandlerなど)で処理します。
void DMA1_Stream5_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream5_IRQn 0 */

  /* USER CODE END DMA1_Stream5_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart2_rx);
  /* USER CODE BEGIN DMA1_Stream5_IRQn 1 */
  OnUart2RxDmaComplete();  // Uart受信バッファの転送が完了
  /* USER CODE END DMA1_Stream5_IRQn 1 */
}

void DMA1_Stream6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream6_IRQn 0 */
  /* USER CODE END DMA1_Stream6_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart2_tx);
  /* USER CODE BEGIN DMA1_Stream6_IRQn 1 */
  OnUart2TxDmaComplete();  // Uart送信バッファの転送が完了
  /* USER CODE END DMA1_Stream6_IRQn 1 */
}
#include "main.h"
#include "app.h"
#include <memory.h>

const char msg1[] = "Button pressed.\n";
const char msg2[] = "Received.\n";
char result[10];

extern UART_HandleTypeDef huart2;

void InitApp()
{
    // データの受信を開始する(main.cでメインループの前で呼び出す)
    HAL_UART_Receive_DMA(&huart2, (uint8_t*)result, 10);
}

void OnUart2RxDmaComplete()
{
    // 転送可能状態なら受信した旨を返す
    if(huart2.gState == HAL_UART_STATE_READY){
        HAL_UART_Transmit_DMA(&huart2, (uint8_t*)msg2, strlen(msg2));
    }

    // 受け入れを再開する
    HAL_UART_Receive_DMA(&huart2, (uint8_t*)result, 10);
}

void OnUart2TxDmaComplete()
{
    // 転送ステートをリセット
    huart2.gState = HAL_UART_STATE_READY;
}

void OnInterruptEXTI15_10()
{
    // スイッチを押したときに転送可能状態ならメッセージを送信
    if(huart2.gState == HAL_UART_STATE_READY){
        HAL_UART_Transmit_DMA(&huart2, (uint8_t*)msg1, strlen(msg1));
    }
}
ではターミナルエミュレータ「Tera Term」のシリアルモニター機能で、送受信のテストをしてみましょう。Tera Termを起動したら「新規接続」より「シリアル→STMicroelectronics STLink...」を選択します。
Nucleoでは、USART2ピン(PA2/PA3)はSTLinkへと繋がっており、ボード上のピンは断線されていて機能していない状態です。写真のように市販のUSBシリアル変換器を接続しても、開発ボードを半田付けするなりして結線しない限り、何も起きないので注意しましょう。
Tera Termでの「端末設定」で「改行コード」を「LF」にし、「ローカルエコー」にチェックを入れます。続いて、「シリアルポート設定」でスピードをSTM32におけるシリアル通信設定を反映させます。
プログラムが正常に機能すれば、青スイッチを押すたびにメッセージが表示されます。なお、DMA割り込みが発生するタイミングは、DMA送信で指定したメモリーが規定量に達したときなので、Tera Termで文字を入力すると、改行を待たずしてメッセージが返されています。
 | 2019年3月6日

STM32のタイマーは、システムやウォッチドッグなどの特殊なものを除くと、モーター向けオプションのあるアドバンスタイマー(TMR1,TMR8)、入出力を持たない基本タイマー(TMR6,TMR7)、PWM出力もできる汎用タイマー(16bit:TMR3,TMR4 / 32bit:TMR2,TMR5)が汎用タイマーとして利用できます(モデルにより未対応や追加もあり)。

タイマーはクロックソースをPrescalerで分割した単位でカウントしていき、この値が設定したPeriodに達するとタイマーイベントを発生させます。

クロックソースの周波数は基本的にAPB1の値(TIM1,8,9,10はAPB2)が適用されます。
このクロックはSPIやI2Sなどのソースにも適用されるため、これらの機能も使いたい場合はそれらを考慮した周波数を設定しなくてはいけません。

(Prescaler + 1)*(Period)=Clockの式が成り立つとき、イベント発生の周期は1秒ごととなります。つまり、APB1が40MHzであれば、Prescaler=19999、Period=1999で値が一致するので、1秒ごとのイベント発生が行えるようになります。また、このPeriod+1の値を半分(Period=999)にすると、カウンタが到達するまでの時間が半分になるため、結果として0.5秒周期のイベント発生になります。
タイマー割り込みコードはNVIC設定で割り込みにチェックを入れることで「Src/stm32f4xx_it.c」に追加されます。
void TIM2_IRQHandler(void)
{
  /* USER CODE BEGIN TIM2_IRQn 0 */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */

  /* USER CODE END TIM2_IRQn 1 */
}
割り込みの受付を始めるための命令をプログラム内に記述しないと、いつまでたっても割り込みが発生しないので、この点にも気をつけましょう。
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);  // Begin timer(TMR2) interrupt
  /* USER CODE END 2 */
 | 2019年3月5日

GPIOピンへの信号の変化など、外部の要因で割り込み処理を発生させる機能はSTM32では「外部割り込みコントローラー(EXTI)」と呼んでいます。STM32ではGPIO向けに7つの受け入れ口(ハンドラ)があり、PA0とPB0のように同じラインに属するピンは同じハンドラに渡されるため、実際にどのピンが切り替わったかは、割り込みルーチン内で調べる必要があります。

では、Nucleoに標準で付いている青いユーザーボタン(B1)を使った割り込み処理のプログラムを作ってみましょう。

STM32CubeMXを開いたら、ピンアウト設定でPC13のGPIOモードを「外部割り込みによるエッジの立ち上がり・立ち下がりを検出(External Interrupt Mode with Rising/Falling edge trigger detection)」を選択し、ボタンを押したときと離したときにイベントが発生するようにします。
通常、マイコンのスイッチ操作では誤動作防止用の抵抗やコンデンサーが必要ですが、Nucleoの回路図を見てわかるように、青いスイッチにはこれらの部品がすでに備わっているので、ソフトウェア側でプルアップ抵抗などを指定する必要は特にありません。
続いてEXTIを有効化します。ピンアウト設定の「GPIO」もしくは「NVIC」カテゴリーにある「EXTI line[15:10] interrupts」にチェックを入れます。この「15:10」というのは10~15ラインを示していて、すべてのポートの10~15番ピンが、同じ割り込み処理で呼び出されることになります。


STM32CubeMXで生成される割り込み処理関連のコードは「Src/stm32f4xx_it.c(STM32F466REボードの場合)」にまとめられています。このファイルから「EXTI15_10_IRQHandler()」関数を探しだし、「HAL_GPIO_EXTI_IRQHandler()」の前にコードを追記します(HAL_GPIO_EXTI_IRQHandlerで割り込みフラグがリセットされるため)。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "app.h"
/* USER CODE END Includes */

void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */
  OnInterruptEXTI15_10();
  /* USER CODE END EXTI15_10_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */
  /* USER CODE END EXTI15_10_IRQn 1 */
}
#ifndef APP_H_
#define APP_H_

void OnInterruptEXTI15_10();

#endif /* APP_H_ */
オンボードのスイッチを押すと回路はGNDに直結される、つまり、スイッチを押している状態だとGPIO_PIN_RESET(0)になるので、スイッチを押してLEDを点灯させたいのであれば、B1の返す値を反転させる必要があります。
#include "main.h"

void OnInterruptEXTI15_10()
{
    auto ps = HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
    HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, !ps);
}
 | 2019年3月5日

前回の記事ではプロジェクトの作成方法を紹介しました。STM32CubeMXでテンプレートプロジェクトを作成したら、試しにオンボードのLEDを点灯させてみましょう。

プログラムのメインループは基本的に「Src/main.c」ファイルに記述します。STM32CubeMXでは既存のプロジェクトに追加でコード生成を行うと「/* USER CODE BEGIN [TAG] */」~「/* USER CODE END [TAG] */」に記述されたコード以外をすべて書き直します。そのため、生成されるコードには逐一このコメントが追加されます。

ライブラリーが増えるにつれてコメントでソースファイルが見にくくなっていくので、MPLAB Harmonyのように「Src/app.c」といった独立したファイルを作り、自前のプログラムをメインループから呼び出すようにするのもひとつの方法でしょう。

STM32CubeMXで生成されるGPIO関連のHALライブラリーAPIはこれらのものがあります。
初期化についてはフレームワークが自動で作成するので、もっぱら下の3つをプログラムで使うことになります。

いずれの関数も、第1引数はGPIOポートを定義した構造体(GPIOA~GPIOK)、第2引数はピンの番号(GPIO_PIN_0~GPIO_PIN_15,GPIO_PIN_ALL)を指定します。CubeMXのピン設定において「User Label」を記入しておくと、そのラベルの名称を使用したポート定義が出力されます。例えば、この画像のプロジェクトにおいては、以下のコードは同義です。
// ラベルが「LD2」のピンを切り替える
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

// ポートAの5番ピンを切り替える
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
ピンの状態はHAL_GPIO_ReadPin()を使ってGPIO_PinState列挙体(通常はGPIO_PIN_SET=1でHigh)の値を取得します。逆にピンの状態を変更するにはHAL_GPIO_WritePin()を使用します。

以上を踏まえたプログラム例がこちらです。「HAL_Delay」で指定したミリ秒ウェイト処理を行い、一定間隔でオンボードのLEDを点滅させています。
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "app.h"
/* USER CODE END Includes */

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

    /* USER CODE BEGIN 3 */
        OnMyTask();
    }
    /* USER CODE END 3 */
}
#ifndef APP_H_
#define APP_H_

void OnMyTask();

#endif /* APP_H_ */
void OnMyTask()
{
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    HAL_Delay(1000);
}
また、Nucreo-F446REのArduino互換D4ピンはPB5に相当するので、以下のようにピンとラベルを設定し、プログラミングすれば、ブレッドボードに設置したLEDを点滅させることができます。
void OnMyTask()
{
    HAL_GPIO_TogglePin(D4_GPIO_Port, D4_Pin);
    HAL_Delay(1000);
}
もし、デバッグを実行するたびにmain()・1行目でブレークするのが煩わしいようであれば、環境設定の「GDB→Stop on start up at」のチェックを外しましょう。
それでもmain()で止まってしまうようであれば、「実行→デバッグの構成」より、デバッグ実行時にブレークポイントを設置するスクリプトが存在するようであれば、その箇所を削除してみましょう。
 | 2019年3月4日

STマイクロエレクトロニクス(以下ST)のマイコン開発ボードに備わっている機能をフルに発揮できるようにするため、ここではEclipseベースの統合開発環境「TrueSTUDIO」とSTM32フレームワーク「STM32CubeMX」による開発環境の構築例を紹介しています。

TrueSTUDIOはAtollic社のサイトよりダウンロードできます。2017年末にSTの関連会社となったことにともない、従来のPro相当の機能がすべて使えるようになりました。

STM32CubeMXはGUIでチップのピン配列やクロック数を指定した情報を元に、必要なライブラリコード一式を生成するフレームワークツールです。公式サイトよりダウンロードしたファイルはEclipseプラグインとしてアーカイブされています。

TrueSTUDIOを起動したら「ヘルプ→新規ソフトウェアのインストール」を選びます。
「作業対象」の右隣にある「追加」ボタンより、ダウンロードしたzipファイルを指定します。
プラグインのチェックを確認して、インストールを続行します。
TrueSTUDIOが再起動されたら、ツールメニューの「ウィンドウ→ビューの表示→その他」より「STM32CubeMX」を選択します。
ホームページが表示されたら「New Project」の下にある「ACCESS TO BOARD SELECTOR」をクリックします。
一覧から手持ちの開発ボードと同じ型番を見つけたら、それを選択し、ウィンドウ右上の「Start Project」をクリック。
ピン配置を標準的なものにするか問われるので、ここでは「はい」を選択。
MCU設定画面が表示されます。まずは「Pinout & Configuration」タブより、割り当てたいピンの機能を再指定します。
標準ではB1はNucleoにある青いプッシュボタン、LD2は同じくボード上のLEDライトにつながっています。
となりの「Clock Configuretion」ではCPUや各種モジュールの周波数を調整できます。標準では内部16MHzクロックが基調となりますが、外部クロックを選ぶこともできます。NucleoではSTLink2にある8MHzクリスタルを共有するか、任意の水晶発振器とコンデンサーをボードの指定位置に半田付けするか、対応番号のピンからケーブルで接続することになります。値をいじりすぎたようであれば「Resolve Clock Issus」ボタンで問題点を解決するか、左にあるリセットボタンを押して初期値に戻します。
「Project Manager」タブではプロジェクト全体の設定を行います。少なくとも「Toolchain / IDE」の項目は「TrueSTUDIO」にしておきます。
このページにある「Advanced Setting」では2種類のドライバーを選択することができます。「LL」は「HAL」に比べてハードウェアとの親和性が高いプログラムコードが生成・使用されるため、最適化という面では優位ですが、プログラムの移植性は低くなります。このブログのプログラム例は主にHALを使用します。
設定が終わったら「GENERATE CODE」ボタンを押してプロジェクトファイル一式を生成させます。「.cproject」拡張子のファイルがすでにTrueSTUDIOと関連付けられているのであれば、この直後のダイアログの「Open project」よりプロジェクトを直接開くことができます。
 | 2019年3月4日

STM32はSTマイクロエレクトロニクス社が開発するプログラマブル32bitマイコンのシリーズです。コアアーキテクチャにARMプロセッサを採用しているため、開発環境の幅が非常に広いのが特徴です。

一昔前までは、有料ライセンスを取得しないとコンパイルできるデータに制限があったりしたのですが、ST社が開発元を買収してTrueStudioを誰でも使えるようにしたり、ARM社ではmbedのオフライン環境「mbed studio」を制限のないコンパイラを同梱して公開したりするなど、規模の大きい開発の敷居も大分低くなりました。

mbedやSTM32duinoはお手軽である一方で、マイコンの持つすべての機能を利用できるかは、それらのライブラリーに依存します。一方で、純正開発ツールではSTM32CubeMXというコード生成ツールによって、マイコンの持つすべての機能を制御できますが、APIをはじめとしたプログラミングの学習にはやや時間がかかります。

TNKソフトウェアではSTM32CubeMXによる開発例をまとめることで、ブログをご覧いただいている方たちが効率的に学習できる記事を提供していく予定です。

ちなみに、多々ある開発ボードの選択ですが、「L(省電力)」よりも「F(ハイパフォーマンス)」の方が使える機能が多く、下位モデルに搭載しているUSBなどのモジュールは上位モデルでもまず利用できるので、開発用途であるならラインナップのちょうど中央に位置する「NUCLEO-F446RE」あたりがおすすめです。拡張性よりも、クロック周波数やSRAM容量の多さを求めるのなら、ESP32などの他社マイコンも検討してみましょう。

 | 2019年3月4日