こんにちはショウです。前回SPI通信でジャイロセンサのデータを取得できるようになりました。
今回はDMAとバースト転送を使ってジャイロセンサのデータの取得を行うように変更します。DMAとバースト転送を使うことで、CPUの処理と通信時間を減らしてその時間を別の処理に使うことができます。
CubeMX
前回使用したSPI3を使います。
基本の設定はそのままDMAのみ設定をします。
Configurationの中のDMA Settingsタブに移動します。
Addを押すとSPI3_RXとSPI3_TXが設定されます。
ADCの時にDMA1 Channel1が設定しているのでDMA1 Channel2とDMA1 Channel3が設定されます。
ADCの時とは違いModeはNormalで設定します。
LLライブラリを使ったプログラム
ここからDMAを使ってSPI通信をするプログラムを作成していきます。
今回LLライブラリを使った情報が少なかったので、ライブラリの中にあるサンプルを参考にしながら作成しました。サンプルはSTM32CubeのフォルダのSTM32Cube_FW_G4のフォルダの中のプロジェクトに入っていました。
まず、送信データと受信データを保存する配列を用意します。
#define SPI_DATA_BUFFR_SIZE ((uint32_t)3) uint8_t SPIReciveData[SPI_DATA_BUFFR_SIZE]; uint8_t SPITransmitData[SPI_DATA_BUFFR_SIZE]; uint8_t rx_flag = 0; uint8_t tx_flag = 0;
DMAは設定したアドレスにデータを自動的に書き込んでくれるので、事前にデータを保存する配列を定義しておく必要があります。また、DMAの転送完了を確認するフラグを定義しています。
次に、DMAとSPIの設定をします。
void DMA1_Channel2_Init(void) { LL_DMA_EnableIT_TC(DMA1,LL_DMA_CHANNEL_2); LL_DMA_EnableIT_TE(DMA1,LL_DMA_CHANNEL_2); LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_2); } void DMA1_Channel3_Init(void) { LL_DMA_EnableIT_TC(DMA1,LL_DMA_CHANNEL_3); LL_DMA_EnableIT_TE(DMA1,LL_DMA_CHANNEL_3); LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_3); } void SPI3_Start(void) { LL_SPI_SetRxFIFOThreshold(SPI3, LL_SPI_RX_FIFO_TH_QUARTER); DMA1_Channel2_Init(); DMA1_Channel3_Init(); LL_SPI_EnableDMAReq_RX(SPI3); LL_SPI_EnableDMAReq_TX(SPI3); LL_SPI_Enable(SPI3); }
LL_DMA_EnableIT_TCはDMAの転送終了時の割り込み許可です。これを設定しないと転送終了のタイミングが把握できません。LL_DMA_EnableIT_TEはDMAのエラー割り込み許可です。LL_DMA_DisableChannelでDMAを無効化しているのは、この後に行うDMAの設定がDMAが無効化されていないと設定できないからです。
SPI_Start関数では、LL_SPI_EnableDMAReq_RXとLL_SPI_EnableDMAReq_TXを設定しています。これはSPIのCR2レジスタのRXDMAENとTXDMAENを設定しています。この2つのレジスタは送信のデータがなくなった、受信のデータが入ってきた時にDMAを動かす指示を有効化するレジスタです。これを設定しないと、DMAに転送タイミングが指示されないのでDMA転送が始まりません。
SPIとDMAの設定をしたので、データ転送のプログラムを作成していきます。
データシートを確認すると、DMAを使用する通信では、送信時にはTXEがセットされるとDMAリクエストが発行され、DMAはSPIx_DRレジスタに書き込み、受信時にはRXNEがセットされるとDMAリクエストが発行され、DMAはSPIx_DRレジスタを読み出す、と書いてあります。ということはDMAに転送データの配列とDRレジスタ、受信データの配列とDRレジスタのアドレスを渡してあげればTXEとRXNEの値を確認して勝手にデータを転送してくれるようです。ただし、今回DMAをNomalモードで動作させているので、配列のアドレスを毎回セットし直す必要があります。アドレスをセットする動作はDMAが無効化されている時にしかできないので、転送が確認できたらDMAを無効化します。
void SPI_DMA_Communication(SPI_TypeDef *SPIx, uint8_t length, GPIO_TypeDef *GPIOx, uint32_t CS_Pin) { LL_GPIO_ResetOutputPin(GPIOx, CS_Pin); if( LL_SPI_IsActiveFlag_RXNE(SPIx) == SET)LL_SPI_ReceiveData8(SPIx); if( LL_SPI_IsEnabled(SPIx) == RESET) LL_SPI_Enable(SPIx); LL_DMA_ConfigAddresses(DMA1,LL_DMA_CHANNEL_3,(uint32_t)SPITransmitData, LL_SPI_DMA_GetRegAddr(SPI3),LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_3,length); LL_DMA_ConfigAddresses(DMA1,LL_DMA_CHANNEL_2,LL_SPI_DMA_GetRegAddr(SPI3), (uint32_t)SPIReciveData,LL_DMA_DIRECTION_PERIPH_TO_MEMORY); LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_2,length); LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_2); LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_3); while(tx_flag != 1); tx_flag = 0; LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_3); while(rx_flag != 1); rx_flag = 0; LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_2); LL_GPIO_SetOutputPin(GPIOx,CS_Pin); }
LL_DMA_ConfigAddressesは使用するDMAのチャンネルに送受信のアドレスと送受信方向を指示しています。引数は使うDMA、チャンネル、送信元のアドレス、送信先のアドレス、データの送信方向で指定しています。LL_DMA_SetDataLengthは転送するデータのサイズを指定しています。RXとTXで設定してあげてからDMAを有効化します。
転送終了を割り込みで転送終了のフラグを立てて確認し、転送終了したらDMAを無効化してフラグもクリアします。Channel2の割り込みを下のように作成しました。
void DMA1_ReceiveComplete_Callback(void) { rx_flag = 1; }
stm32g4xx_it.c
void DMA1_Channel2_IRQHandler(void) { /* USER CODE BEGIN DMA1_Channel2_IRQn 0 */ if(LL_DMA_IsActiveFlag_TC2(DMA1) == 1){ LL_DMA_ClearFlag_GI2(DMA1); DMA1_ReceiveComplete_Callback(); } /* USER CODE END DMA1_Channel2_IRQn 0 */ /* USER CODE BEGIN DMA1_Channel2_IRQn 1 */ /* USER CODE END DMA1_Channel2_IRQn 1 */ }
これをChannel3にも同じように作成しています。
DMAの動作確認
DMAを使ったデータ転送のプログラムができたのでWho am Iレジスタを読み出してみます。
読み出しの関数は下のように作成しました。
void MPU9250_whoami(void) { SPITransmitData[0] = (0x75 | 0x80); SPITransmitData[1] = 0x00; SPI_DMA_Communication(SPI3, 2,GPIOD,LL_GPIO_PIN_2); printf("who am i = %x\r\n",SPIReciveData[1]); }
main関数にwhoami関数を入れて動作確認をしてみます。
ちゃんと0x71が返って来たので動作していることが確認できました。
バースト転送
バースト転送とは最初のアドレスを指定した後、連続するアドレスのデータをアドレスの指定なしに読み込むことができる転送方法です。ジャイロセンサのデータは上位と下位が並んで保存されているので、この方法を使うとアドレス指定を減らすことができます。
stm32g474でバースト転送をする場合、特別な設定は必要ないようで、必要なクロック数分だけ転送するように配列を増やして、DMAの転送するデータのサイズを変更するだけで動作してくれるようです。
なので、ジャイロセンサの上位ビットと下位ビットを連続で取得させる場合、転送用の配列に開始アドレスと0を2つ入れて、データサイズのlengthを3にするとバースト転送されます。
void MPU9250_ReadByte(uint8_t reg,uint8_t length) { SPITransmitData[0] = ( reg | 0x80 ); SPITransmitData[1] = 0x00; SPITransmitData[2] = 0x00; SPI_DMA_Communication(SPI3, length,GPIOD,LL_GPIO_PIN_2); } void MPU9250_GyroRead_DMA(uint8_t reg){ int16_t tempdata; MPU9250_ReadByte(reg,3); tempdata = (SPIReciveData[1]<<8|SPIReciveData[2]); printf("data = %f\n\r",tempdata/16.4); }
バースト転送の動作確認
バースト転送用にプログラムを作成したので、GyroRead_DMA関数をmain関数に入れて、ジャイロセンサのX軸(レジスタ67,68)のデータを取得してみます。
ジャイロセンサを動かすとデータが動いていることが確認できました。
これでDMAとバースト転送を使ってデータが取得できるようになりました。