ロボトレース競技のマシンを作ってみる その7 STM32の周辺機能を実装する(MA700とSPI通信)

こんにちは。そらです。

ロボトレマシンのマイコンのペリフェラルの実装をしていきたいと思います。実装を行うのは、SPI、ADC(DMA,Single)、TIM(PWM,Interrupt),GPIO(Input,Output)にしました。このマシンのファームウェアはすべてLLAPIを使用してプログラムを書いています。

今回の周辺機能の実装はSTM32の今まで書いた記事の内容でほとんどカバーすることができています。ファームウェアの実装については、Githubのリポジトリを確認していただければと思います。また、STM32関連の記事についてはSTM32記事まとめを参考にしていただければと思います。

記事一覧のまとめ、ハードウェア、ファームウェアのデータはこちら。

STM32記事まとめはこちら。

周辺機能のプログラムの実装で少しはまったMA700のSPI通信を書きたいと思います。MA700の通信方式はSPI通信でも少し癖があるのでちょうどいいかなと思っています。




データシートを確認する

MA700のSPIの設定について

データシートから抜粋していきます。

MA700データシートより引用

Table2よりSPIのモードは3で、CPOL,CPHAはSTM32CubeMXでそれぞれHigh,2edgeに設定する必要があるということがわかりました。

通信速度はTable3をみれば最大値がわかると思います。興味をもった方は計算をしてみてください。左上のOPERATIONに13ページにデータのやり取りの詳細があるよと書いてあるので、次は13ページを確認していきます。

MA700の通信方式について

13ページに移動する途中にレジスタマップがあったので確認をしておこうと思います。

MA700データシートより引用

レジスタの0x04に12ビットのデータの内上位8ビットがあり、0x05の下位4ビットにあとのデータが格納されていることがわかりました。

13ページのReading Back the Register Contentをみていくと、レジスタを指定してそのレジスタの値の読み方が書いてあります。今回は角度情報が欲しいだけなので他のレジスタの値を確認する必要はありません。AS5047PやMPU6500等のセンサでは欲しいデータがあるときは、その情報が保管されているレジスタにアクセスして値を取得する必要があります。このセンサーでは、角度情報が通信を始めたら上位8Bitが送られてきてから下位4bitのデータが入っているレジスタの値が勝手に送られてくると書いてあります。

したがって、このセンサで角度情報を取得することだけを行う場合はMOSI常に0x00に設定していることになるため、MOSIをGNDに繋げても通信はできそうだということがわかりました。1

実装をしてみる

STM32CubeMXの設定

今回は、rokisiのリポジトリに公開してあるiocファイルの設定でやっていきます。

使用するAPIはLLAPIなのでLLAPIでソースコードの出力に設定をしてください。

プログラムの実装

Githubのリポジトリにもありますが、ここにソースコードをはっておきたいと思います。以下のソースコードは右側のMA700との通信に使用したソフトです。

/**
 * @brief SPI1 Enable
 * @details
 *  enable SPI1 and set SPI1 enc right cs high.
 */
void SPI1_Starat(void)
{
  LL_SPI_Enable(SPI1);
  LL_GPIO_SetOutputPin(enc_right_cs_GPIO_Port, enc_right_cs_Pin);
}

/**
 * @brief Performs SPI1 communication
 * @param tx_data write data array 
 * @param rx_data read data array 
 * @param length communication length
 * @details 
 *  Tx_data and rx_data arrays need the same size as length,
 *  because SPI communication is full duplex communication.
 */
void SPI1_Communication(uint8_t *tx_data, uint8_t *rx_data, uint8_t length)
{
  uint8_t count = length;

  LL_GPIO_ResetOutputPin(enc_right_cs_GPIO_Port, enc_right_cs_Pin);

  if(LL_SPI_IsActiveFlag_RXNE(SPI1) == SET) LL_SPI_ReceiveData8(SPI1);

  if(LL_SPI_IsEnabled(SPI1) == RESET) LL_SPI_Enable(SPI1);

  while(count > 0){
    LL_SPI_TransmitData8(SPI1, *tx_data++);
    while(LL_SPI_IsActiveFlag_TXE(SPI1) == RESET);
    while(LL_SPI_IsActiveFlag_RXNE(SPI1) == RESET);
    *rx_data++ = LL_SPI_ReceiveData8(SPI1);
    count--;
  }

  LL_GPIO_SetOutputPin(enc_right_cs_GPIO_Port, enc_right_cs_Pin);
  
}

/**
 * @brief get encoder value for right MA700 
 * @return 12bit encoder data
 */
uint16_t MA700Right_getEncoderData(void)
{
  uint8_t tx_data[2];
  uint8_t rx_data[2];
  uint16_t data = 0;

  tx_data[0] = 0x00;
  tx_data[1] = 0x00;

  SPI1_Communication(tx_data, rx_data, 2);

  data = (uint16_t)( (uint16_t)(rx_data[0] << 4) | (uint16_t)(rx_data[1] & 0x0f) );

  return data;
}

エンコーダのデータを取得するにあたって、上位8bitのデータは4bitシフトして下位の4bitデータは角度情報のデータのみ値を取得できるようにしています。下位4bitのデータが保管されているレジスタの上位4bitの値が随時変化するためです。

ただし、ここで紹介しているエンコーダのデータ取得用関数はエンコーダの絶対角のデータを返しているだけなので、車体の速度への変換は別に行う必要があります。絶対角度から速度への変換の仕方は考えてみてください。0→4095, 4095→0の値が大きく変化するタイミングの処理をしっかりと書ければ、一つ前の値と今の値の変化量をみることでインクリメンタル型のエンコーダのデータと同様に扱うことができると思います。

さいごに

私がMA700との通信にはまった理由は、データシートやプログラムの問題ではなく、プログラムの確認に使用したハードウェアのはんだづけがミスっていたことによるものでした。ソフトを疑っていてもわからなかったので、オシロスコープで確認をしたら一瞬で解決しました。SPI通信やI2C通信のエラーが起きたときはオシロスコープで当たってみるというのも一つの解決法ですね。

問題の切り分け作業に無駄に時間を使いました。はんだづけはミスっていないだろう等の思い込みはだめってことを再度学びました。

参考文献

MA700 データシート

  1. 実際にGNDに繋げていても通信はできました。