マイクロマウス研修(kora編)[24] STM32マイコンでSPI通信


こんにちは。koraです。

今回は、新型マウスのSPI通信を設定します。

CubeMXの設定

今回製作したマイクロマウスは、STM32F732マイコンに搭載されている3つのSPIのうち2つを使用します。左右のエンコーダがSPI1に、ジャイロセンサがSPI2に接続してあります。

Pinoutの設定

ピンの設定画面でSPI1とSPI2の設定を行います。

  • MISO (Master In Slave Out)
  • MOSI (Master Out Slave In)
  • SCK (Serial Clock)

SPI通信では、マスタが通信相手を切り替えるためのCSピン (Chip Select あるいは SS: Slave Select) が必要なので、GPIOピンを割り当てます。同時に、System Core → GPIO → Configurationを開いて、GPIOにわかりやすいラベルを付けます。

  • CS_ENCR (右側のエンコーダ)
  • CS_ENCL (左側のエンコーダ)
  • CS_GYRO (ジャイロセンサ)

このときGPIO output level (GPIOピンの初期状態) はHighにしておきます。CSピンはアイドル状態がHigh、通信中がLowとなるためです。

SPI Modeの設定

ピンの設定が済んだら、SPI1とSPI2のModeを設定します。

  • Mode: Full-Duplex Master (受信と送信を同時に行えるモード)
  • Hardware NSS Signal: Disable (ソフト的にCSピンを使う)

SPI1 Configurationの設定

エンコーダMA702のデータシートを読むと、次のような仕様になっています。

  • Data order: MSB first
  • Clock rate: 最大25MHz
  • SPI mode: Mode0とMode3をサポート※

そこで、SPI1のConfigurationを次のように設定します。

  • First Bit: MSB first
  • Baud Rate: 13.5 MBits/s
  • Clock Polarity: High
  • Clock Phase: 2 Edge

SPI2 Configurationの設定

ジャイロセンサICM20648の仕様に合わせます。データシートによると、

  • Data order: MSB first
  • Clock rate: 最大7MHz
  • Clock Phase: クロックの立ち上がりでデータ受信

となっていますので、次のように設定します。

  • First Bit: MSB first
  • Baud Rate: 6.75 MBits/s
  • Clock Polarity: High
  • Clock Phase: 2 Edge

※SPI通信は、クロックの極性 (クロックのアイドル状態) とクロックの位相 (データ受信のタイミング) について4つのモードが定義されています。
参考:ANALOG DEVICES AN-1248 アプリケーション・ノート

クロックの極性 クロックの位相
Mode 0 アイドル状態がLOW クロックの立ち上がり(1番目のエッジ)にデータ受信
Mode 1 アイドル状態がLOW クロックの立ち下がり(2番目のエッジ)にデータ受信
Mode 2 アイドル状態がHIGH クロックの立ち下がり(1番目のエッジ)にデータ受信
Mode 3 アイドル状態がHIGH クロックの立ち上がり(2番目のエッジ)にデータ受信

ジャイロセンサの動作確認

SPI通信用の関数

CubeMXのGENERATE CODEボタンを押すと、今回設定したSPI関連のコードが生成されます。

SPI通信でマイコンからデバイスへ送信するには、HAL_SPI_Transmit()関数を使います。第一引数がSPIハンドラ、第二引数がデータバッファ、第三引数がデータバッファのサイズ(バイト)、第四引数がタイムアウトの時間(ms)です。反対にデバイスからデータを受信するとき、HAL_SPI_Receive()関数を使用します。引数はさきほどと同様です。
なお、通信を開始する前にHAL_GPIO_WritePin()関数で、通信したいデバイスのCSピンをLowにして、通信を終えた後Highに戻します。

ジャイロセンサ用のモジュール

これを踏まえたうえで、ジャイロセンサのモジュールを作ります。まず、Srcフォルダに以下のようなgyro.cというファイルを作ります。

#include "gyro.h"
#include "spi.h"
#include "usart.h"
#include <stdio.h>

int16_t  gyro_raw;
float gyro_ang_vel;

static uint8_t read_byte( uint8_t reg )
{
    uint8_t val = 0x00;

    reg = reg | 0x80;   // read bit 1--- ----

    HAL_GPIO_WritePin(CS_GYRO_GPIO_Port, CS_GYRO_Pin, GPIO_PIN_RESET);   // CS = 0

    HAL_SPI_Transmit(&hspi2, &reg, 1, 100);
    HAL_SPI_Receive(&hspi2, &val, 1, 100);

    HAL_GPIO_WritePin(CS_GYRO_GPIO_Port, CS_GYRO_Pin, GPIO_PIN_SET);     // CS = 1

    return val;
}

static void write_byte( uint8_t reg, uint8_t val)
{
    reg = reg & 0x7F;   // write bit 0--- ----

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);   // CS = 0

    HAL_SPI_Transmit(&hspi2, &reg, 1, 100);
    HAL_SPI_Transmit(&hspi2, &val, 1, 100);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);     // CS = 1
}

void gyro_init(void)
{
    gyro_raw = 0;

    // who am i
    uint8_t who_am_i;
    who_am_i = read_byte(0x00);
    printf("WHO_AM_I = 0x%02x\n\r", who_am_i);

    // ICM-20648 settings
    write_byte(0x06, 0x81); // 1000 0001 wake from sleep
    HAL_Delay(50);
    write_byte(0x06, 0x01); // 0000 0001 turn off low power mode
    HAL_Delay(50);

    write_byte(0x7f, 0x20); // 0010 0000 User Bank2に変更
    HAL_Delay(50);
    write_byte(0x01, 0x07); // 0000 0111 GYRO_FS_SEL=3(Range 2000dps)に変更
    HAL_Delay(50);          //           Digital Low Pass Filterを有効化
                            // このときgyro_sensitivityは 32768/2000=16.4 LSB/dps
    write_byte(0x7f, 0x00); // 0000 0000 User Bank0に変更
    HAL_Delay(50);

    write_byte(0x06, 0x21); // 0010 0001 turn on low power mode
    HAL_Delay(50);
}

void gyro_update(void)
{
    uint8_t zout_h, zout_l;

    zout_h = read_byte(0x37);
    zout_l = read_byte(0x38);

    gyro_raw = ((zout_h << 8) & 0xff00) | (zout_l & 0x00ff);
    gyro_ang_vel = (float)gyro_raw / 16.4;
}

gyro_init()関数を呼び出せばジャイロセンサの初期設定がまとめて行われ、gyro_update()関数を呼び出せばジャイロの角速度情報をまとめて読み込めます。

次に、Incフォルダにgyro.hというファイル名でヘッダーファイルを作ります。他のソースファイルでこのヘッダーファイルをインクルードすれば、gyro_init()関数とgyro_upate関数を呼び出せるようになります。

#ifndef GYRO_H_
#define GYRO_H_

#include <inttypes.h>

extern int16_t gyro_raw;
extern float gyro_ang_vel;

void gyro_init(void);
void gyro_update(void);

#endif /* GYRO_H_ */

最後に、main.cに次のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "gyro.h"
#include <inttypes.h>
#include <stdio.h>
/* USER CODE END Includes */
  /* USER CODE BEGIN 2 */
  uint8_t state;
  printf("hello world!\r\n");
  HAL_Delay(100);
  gyro_init();
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    while (1) {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    // Gyro
    gyro_update();
    printf("GYRO = %d, %d\n\r", gyro_raw, (int)(gyro_ang_vel*100));

    HAL_Delay(5);
  }
  /* USER CODE END 3 */
ジャイロセンサ動作確認プログラムの実行

ビルドしてCubeProgrammerでマウスに書き込みます。
TeraTermに次のような角速度が表示されると成功です。

次回

ジャイロの値は取れたので、次回はエンコーダを試してみたいと思います。モータマウント・タイヤ・磁石などを取り付けて、エンコーダの動作を確認します。


Posted in DCマウス研修