2019年2月21日木曜日

PWMのduty比設定をDMAで行う(STM32F4)



 絶賛ひきこもり生活中の中の人です。ひきこもっている間何してたかといえばGT6、Twitter巡回、Youtube再生業といった時間の浪費とてもアクティブなことです。はい。まあ、それ以外だとSTM32をいじったり、nnablaってライブラリを触ってました。今回はSTM32の話です。(nnablaはそのうちブログ書くかも)


 STM32で複数のPWMを出そうとする時(唐突)、大体は
 
if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK) {
    Error_Handler();
}

if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2) != HAL_OK) {
    Error_Handler();
}

if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3) != HAL_OK) {
    Error_Handler();
}

while (1) {
     for (i = 0; i < 100; i++) {
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i);
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 100 - i);
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i);
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 100 - i);
    }
}

みたいにTIMの初期設定→PWMのduty比をテキトーに変えていく感じになりますよね。
まあこれでもいいんですが、CubeMXで設定欄を見ていたら

?

何なんだと、こいつもDMA設定あるのかよ...。
というわけで本題。(今までの前振りの時間を返せ!)
DMAを使ってPWMを吐き出してみましょう。まずはCubeMX側の設定で、TIM本体の設定は同じ。今回はTIM1を使用します。



TIM1のDMA設定。この辺よくわかってないんで、ワタシエスティーエムチョットデキル方がいたら教えてくだせぇ。
次はプログラム側での話。どうやらdutyを設定する関数がなくて、PWMスタートとduty比設定が一緒って感じです。(求ム、有識者。コノ辺、ワカラン。)

while (1) {
    for (i = 0; i < 100; i += 2) {
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *) &i, 1);
        htim1.State = HAL_TIM_STATE_READY;
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_2, (uint32_t *) &j, 1);
        htim1.State = HAL_TIM_STATE_READY;
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_3, (uint32_t *) &j, 1);
        htim1.State = HAL_TIM_STATE_READY;
    }
}

TIM1のStateをいじっているのは、duty比設定用の関数がHAL_TIM_STATE_READYの時のみduty比のセットを行えるため...らしい。イーノック、こんな解説で大丈夫か?
これで動くはず...です...。少なくとも僕のNucleoは動いたんだ...。





おまけ:
CeleronG3900 + 4GB RAM + GTX1060(6GB)って変態構成の自作PCをどうにかしたい(後赤く光るファンを追加したい)


©2018 shts All Right Reserved.

2019年2月15日金曜日

STM32F411でDMAを使ってSPI通信する話(MPU9250使用)



 卒論発表を偵察しに行ったら、他学部の学生がずっと質問している修羅場場面に遭遇した中の人です。
 前回はポーリングでMPU9250とSPIでお話ししていました。実はHALでは割り込みとDMAによるSPI通信が実装されており、今回はDMAを使ってみたいと思います。

 DMAを使ったSPI通信に関して基本的にポーリングの時と同じですが、細々とした設定があるので注意が必要です。

1.CubeMXの設定


 CubeMXでは、SPI通信をDMAで使うための設定が必要です。SPIのDMA SettingsにSPIx_RXとSPIx_TXを登録しておきましょう。

2.コード

基本的にポーリングと同じなのですが、お話しに使う関数が

        HAL_SPI_Transmit_DMA()
        HAL_SPI_Receive_DMA()

になります。関数名がhoge_DMA()となるのでわかりやすいです。
 MPU9250でお話しする場合、読み込みたい(書き込みたい)レジスタを送り、そのあとでデータを読みだす(書き出す)形になるので、書き込みx1と読み込みx1の組み合わせまたは書き込みx2を行う必要があります。ポーリングの場合読み出しを行う時は
 
uint8_t read_MPU9250(uint8_t addr) {
 uint8_t reg, val;
 reg = addr | 0x80;

 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);//CS
 HAL_SPI_Transmit(&hspi2, &reg, 1, 100);//Select reg
 HAL_SPI_Receive(&hspi2, &val, 1, 100);//Read data
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);//CS

 return val;
}

みたいな書き方になります。これをDMAを使ったものにすると、
 
uint8_t read_MPU9250_DMA(uint8_t addr) {
 uint8_t reg, val;
 reg = addr | 0x80;

 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);//CS
    HAL_SPI_Transmit_DMA(&hspi2, &reg, 1);//Select reg
 while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
 HAL_SPI_Receive_DMA(&hspi2, &val, 1);//Read data
 while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);//CS
 return val;
}

となります。...なんか追加されてますね。

HAL_SPI_GetState(&hspi2)

はSPIの使用状況を確認する関数で、これを入れることでSPIの通信待ちをしています。
頭悪い実装ですね。
 ポーリングの時とは異なり、完了待ちを入れておかないと読み出し先のレジスタを指定しながらデータを受信することになります。MPU9250の場合、先にレジスタを指定しないといけないので、正常な読み出しを行うことができません。

ここまで書けば大体動く...はずです。はい。動かなかったら教えてくだせぇ

3.その他

SPI通信が終了するとコールバック関数が呼ばれます。

HAL_SPI_RxCpltCallback()
HAL_SPI_TxCpltCallback()

これらのコールバック関数は弱参照されているので、別のファイルで別途中身を定義するのが望ましいです。こいつらにグローバル変数を仕込んで、終了フラグにしてもいいと思います。(別に使わなくても動きます。)
©2018 shts All Right Reserved.

2019年2月12日火曜日

STM32F411REでMPU9250を動かす(SPI)



学校に行こうとしても体がついていかず、結局ひきこもる生活をしてる中の人です。STM32の勉強用としてお家に生えてた落ちてたMPU9250をいじりだしたのですが、MPU9250をSPIで使った例がなかったのでかきかきしてみようかと思います。

今回はSTM32F411RE(Nucleo)とMPU9250(中華拡張ボードなんで詳細不明)を使っていきます。MPU9250を動かす流れは、大まかにこんな感じ。
  • WHO_AM_Iの読み出し
  • 起動
  • 出力設定
  • ジャイロ・加速度の設定
  • 角速度、加速度の読み出し
MPU9250は3軸加速度・3軸ジャイロのMPU6500と3軸地磁気のAK8963がセットになった9軸IMUなので、MPU6500と同じような使い方ができます(レジスタとかが違う)。

MPU9250をSPIで使うとき気を付けたいのはボーレートで、レジスタの読み取りのみであれば20MHzまで行けるようですが、書き込みまで行う場合、1MHz程度が限界となっています。

今回使ったNucleoボードの場合、

 hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;

と設定することで(CubeMXであればSPIのプリスケーラを32にする)ことで大体1.3MHzになります。(これでも一応動くけど、値の読み出しに失敗することがありそう)
この設定が終わったらread関数とwrite関数を実装するのですが、それはここを参考にしてください。

で、ここまで出来たら次はWHO_AM_Iを読み出す作業です。MPU9250は0x75のレジスタを読みに行くと、0x71が返ってくるはずです。

ここまでできれば、データの読出しはもうすぐです。とりあえずこの後の流れをまとめると、
0x6B(PWR_MGMT_1)のレジスタに0x00を書き込む

0x1A(CONFIG)のレジスタに0x00を書き込む

0x1B()のレジスタに0x18を書き込む(フルレンジを2000dpsに設定)

0x1C()のレジスタに0x18を書き込む(フルレンジを16gに設定)

 参考:MPU9250 Register Map
    9軸加速度センサー MPU9250をArduinoで制御する その1(I2C)
ここまでで設定が終了して、実際にデータの読み出しとなります。
データの読出しは、上位8bitのデータが入っているレジスタと下位8bitのデータが入っているレジスタの両方を読み出すことで完了します。read関数を2回実行し、上位8bitをシフトして下位8bitとのorを取ることで16bit分のデータが取得できます。やったね。


©2018 shts All Right Reserved.

2019年2月8日金曜日

Raspberry Pi向け拡張基板を作った(n回目)







 マウスそっちのけでいろいろやってる中の人です。突然ですが、Raspberry Piでモータを回したりLEDをPWM点灯させたいと思ったことがある人は僕だけではないはず(ないと思いたい)。RPiから高精度のPWMを出すのも結構大変ですよね(最近ではRPiのGPIOすべてから高精度のPWMを吐き出せるライブラリもあるみたいですが)。ADCとかモタドラとなるともはや拡張しないとだめですよね。
 そこで今回はI2Cで制御できるモータドライバICやLEDドライバ、おまけでADCをまとめて載せたてんこ盛り拡張ボードを作成してみました。(n=1の時の奴はこちら)
今回作成したのは、こんなやつ


裏面はこんな感じ


この拡張ボードに載せた(載せる予定の)ICはこんな感じ
すべてのICがI2C対応のICでRPiでの動作例が結構あることから今回これらを選びました。あとADC用のADS1015以外はアキバで手に入るやつなのではんだ付けに失敗しても大丈夫だったのが良かった点ですね。(ADS1015はアキバだとブレークアウトボードでしか手に入らない...)
 回路図はこんな感じ


 今回はPCA9685の動作確認用にAdafruitのライブラリを使い、DRV8830はI2Cライブラリ(python-smbus)を使ってデータを投げてます。DRV8830はもしかしたら自作ライブラリにするかも。









 P.S.:IKEAのサメさんみたいにまとまった休みが欲しい


©2018 shts All Right Reserved.