製作モジュール一覧

ラベル DMA の投稿を表示しています。 すべての投稿を表示
ラベル DMA の投稿を表示しています。 すべての投稿を表示

2019年4月2日火曜日

STM32: 内蔵DACをDMA経由で使う(Nucleo-F446RE)

内蔵DACをDMA経由で使ってみました。データをあらかじめ用意できれば、DMAにデータ転送をまかせて、CPUは他の処理が行えます。

参考
「Elastic Notes」さんの「Usage DMA and DAC in STM32CubeMX

プロジェクト
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_DAC_DMA_Test2

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

4096サンプルのサイン波テーブルを作成して内蔵DACから出力します。

STM32CubeMXの設定


Clock Configuration
  PLL Source Mux: HSE
  HCLK: 180MHz

Pinout & Configuration
  Timers
    TIM6
      Mode: Activatedをチェック
      Counter Period: 220
      Trigger Event Selection: Update Event

180MHz駆動するとAPB1 Timer clocksは90MHzになります。4096サンプルで1波形のサイン波を出力するので、1kHzのサイン波を出力しようとすると
90MHz / prescaler / 4096 = 1kHz
なので
prescaler = 90MHz / (1kHz * 4096) ≒ 22.0
ですが、prescalerを22に設定するとDACから信号が出力されませんでした。100Hzにしようとprescalerを220に設定するとOKでした。

  Analog
    DAC
      Mode: OUT1 Configurationをチェック
      Prameter Settings
        Trigger: Timer 6 Trigger Out event
      NVIC Settings
        Enableをチェック
      DMA Settings
        DMA Request: DAC1を追加
        Mode: Circular
        Increment Address: Memory
        Data Width: Half Word
     

 SW4STM32でコードを追加(一部抜粋)


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <math.h>
/* USER CODE END Includes */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define SINE_TABLE_SIZE (4096)
/* USER CODE END PD */

/* USER CODE BEGIN PV */
uint16_t sine_wave_array[SINE_TABLE_SIZE];
/* USER CODE END PV */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void generateSineWave(void)
{
  for (int i = 0; i < SINE_TABLE_SIZE; i++) {
      float fv = ((float)i / SINE_TABLE_SIZE) * 2.0f - 1.0f;
      float phi = (float)M_PI * fv;
      sine_wave_array[i] = 4095 * ((sinf(phi) + 1.0f) / 2.0f);
  }
}
/* USER CODE END 0 */

  /* USER CODE BEGIN 2 */
  generateSineWave();
  HAL_TIM_Base_Start(&htim6);
  HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave_array, SINE_TABLE_SIZE, DAC_ALIGN_12B_R);
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

Memory→DACのデーター転送はTimer駆動(TIM6)のDMAにまかせているので、プログラム(while(1)ループ)では何もしていません。

出力波形

いつものように、サイン波形の最大振幅付近で若干クリップしています。

99.6Hzのサイン波になりました。水晶がクロック源なので少し誤差が大きいような・・・(@@?

Prescaler Prescler(丸め) 誤差
219.7265625 220 0.1244%

期待周期(ms) 実測周期(ms) 誤差
10 10.04 0.4000%

2019年3月22日金曜日

STM32: SPIをPollingとDMAで使う(Nucleo-F446RE)

SPI通信を、ポーリングとDMA経由でテストしました。

参考にした記事

STM32CubeのExample「SPI_FullDuplex_ComPolling」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling

STM32CubeのExample「SPI_FullDuplex_ComDMA」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComDMA

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

SPI通信(ポーリング)


STM32CubeのExampleはボードを2枚使って互いに通信させていますが、SPI送信波形を見るだけにして一番シンプルな形でテストしました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_SPI_SoftNSS_Test1

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Transmit Only Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

SPI1はデフォルトでは、PB3がSYS_JTDO-SWOに割り当てられていて有効化できませんが、Pinout viewでPB3にSPI1_SCKを割り当ててやれば、有効化できます。

モードは送信のみの「Transmit Only Master」にしました。MISOが割り当てられず、SCKとMOSIの2線です。

CSはソフトウェア制御にしてPinout viewでPB4をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MOSI PB5 D4
CS PB4 D5

クロック設定


SPIクロックをちょうど1MHzにしたかったので、HCLKを2のN乗の128MHzにしました。またクロック源(PLL Source Mux)を内蔵RCのHSIではなく、NucleoボードのST-Link側に乗っている8MHzの水晶から取るために、HSEに切り替えています。

SW4STM32でコードを追加(一部抜粋)

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

    /* USER CODE BEGIN 3 */
 uint8_t data[2] = { 0x55, 0xAA };
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, data, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
  }
  /* USER CODE END 3 */

0x55, 0xAAを送信しています。ビット列にすると「01010101:10101010」になります。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)

SPI通信(DMA)


こちらも送信のみですが、設定は全二重(Full-Duplex Master)にしました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_SPI_DMA_Test1

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Full-Duplex Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

Transmit Only Masterの場合より1ピン増えて、MISOが追加されます。CSはPinout viewでPB10をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MISO PB4 D5
MOSI PB5 D4
CS PB10 D6

DMA Settings


SPIとDMAを紐付ける設定です。

[Add]ボタンを押して、[DMA Request]で[SPI1_TX][SPI1_RX]を追加します。

DMA Request Settings
  Mode: Normal

クロックの設定はポーリングの場合と同じです。

SW4STM32でコードを追加(一部抜粋)

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUFFERSIZE (2)
enum {
 TRANSFER_WAIT,
 TRANSFER_COMPLETE,
 TRANSFER_ERROR
};
/* USER CODE END PD */

/* USER CODE BEGIN PV */
/* transfer state */
__IO uint32_t wTransferState = TRANSFER_WAIT;
/* USER CODE END PV */

DMA転送の状態を示すフラグです。

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

    /* USER CODE BEGIN 3 */
    uint8_t aTxBuffer[BUFFERSIZE] = { 0x55, 0xAA };
 uint8_t aRxBuffer[BUFFERSIZE];
 wTransferState = TRANSFER_WAIT;

    /*##-2- Start the Full Duplex Communication process ########################*/
    /* While the SPI in TransmitReceive process, user can transmit data through
    "aTxBuffer" buffer & receive data through "aRxBuffer" */
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
    if(HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)aTxBuffer, (uint8_t *)aRxBuffer, BUFFERSIZE) != HAL_OK)
    {
   /* Transfer error in transmission process */
   Error_Handler();
    }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

    /*##-3- Wait for the end of the transfer ###################################*/
    /*  Before starting a new communication transfer, you must wait the callback call
     to get the transfer complete confirmation or an error detection.
     For simplicity reasons, this example is just waiting till the end of the
     transfer, but application may perform other tasks while transfer operation
     is ongoing. */
    while (wTransferState == TRANSFER_WAIT)
    {
    }

    printf("SPI RX: %d\t%d\r\n", aRxBuffer[0], aRxBuffer[1]);
  }
  /* USER CODE END 3 */

こちらも0x55、0xAAを送信しています。

ソースのコメントにもありますが、DMA転送の完了をWhileループでフラグを見て待機していますが、本来はうまいことやってDMA転送完了待ち中は他の処理をするべきです。

/* USER CODE BEGIN 4 */
/**
  * @brief  TxRx Transfer completed callback.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report end of DMA TxRx transfer, and
  *         you can add your own implementation.
  * @retval None
  */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_COMPLETE;
}

/**
  * @brief  SPI error callbacks.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report transfer error, and you can
  *         add your own implementation.
  * @retval None
  */
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_ERROR;
}
/* USER CODE END 4 */

DMA転送完了の割り込みハンドラです。wTransferStateに状態を代入しているだけです。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)


ch1:MOSI(D4) ch2:SCK(D3)

printf()でUART通信しているので(ブロッキングされる)、SPI通信ごとの間隔が広がっています。

Puttyで受信しているようす

スレーブがつながっていないので受信データは0:0です。

メモ:


NSS(CS)をハードウェア制御できるようですが、マスター側ではソフトウェアでGPIO Writeしてやれば用は済みます。NSSのハードウェア制御の使い方はしばし保留。

2019年3月21日木曜日

STM32: ADCをDMAで使う(Nucleo-F446RE)

ADCの使い方の本丸、DMA経由でA-D Convertするテストをしました。

参考にした記事
STM32CubeのExample「ADC_RegularConversion_DMA」<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32446E_EVAL\Examples\ADC\ADC_RegularConversion_DMA
「ガレスタさんのDIY日記」さんの「STM32でADCをやってみる2(DMAを使ったレギュラ変換)
「JP7FKFの備忘録」さんの「STM32マイコンでADCを使ってみる話

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

DMAシングルチャネル


プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_ADC_DMA_Test2

DMAを使った1チャネルのみのA-D Convertです。

STM32CubeMXの設定


[Analog]-[ADC1]で、[Mode]の[IN0]をチェックします。PA0がADC1-IN0に割り当てられます。これはNucleoボードのArduinoヘッダのA0に接続されています。


[Parameter Settings]
Continuous Conversion Mode: Enable
DMA Continuous Requests: Enable
End Of Conversion Selection: EOC flag at the end of single channel conversion
ADC_Regular_ConvesionMode
  Rank:1
    Sampling Time: 480Cycles
Sampling Timeはデフォルトでは3 Cyclesになっていますが、3 CyclesのままだとDMAの割り込み処理でCPU時間を消費しつくしてしまい、他の処理が行えません。一番大きい480 Cyclesを指定しました。実用的には、DMAの処理と他の処理との兼ね合いで値を調整することになると思います。


[DMA Settings]
[Add]ボタンを押して[DMA Request]でADC1を選択
Mode: Circular

SW4STM32でコードを追加(一部抜粋)


  /* USER CODE BEGIN 2 */

  printf("before HAL_ADC_Start_DMA()\r\n");

  /*##-3- Start the conversion process #######################################*/
  /* Note: Considering IT occurring after each number of ADC conversions      */
  /*       (IT by DMA end of transfer), select sampling time and ADC clock    */
  /*       with sufficient duration to not create an overhead situation in    */
  /*        IRQHandler. */
  if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }
  printf("after HAL_ADC_Start_DMA()\r\n");
  /* USER CODE END 2 */

前述の[Sampling Time]が3 Cyclesだと、DMA初期化後
    printf("after HAL_ADC_Start_DMA()\r\n");
に処理が移らず、メッセージが表示されません。

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  /* Turn LED2 on: Transfer process is correct */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
/* USER CODE END 4 */

ADC変換後のDMA転送完了時の割り込み処理です。Nucleoボード上のLEDを点滅させています。点滅の間隔が短すぎて視認できないのでオシロで測定しました。


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

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_SET);
 printf("ADC Value: %d\r\n", uhADCxConvertedValue);
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_RESET);
  }
  /* USER CODE END 3 */

メインループ内で、printf()でADCの読み取り値をUART送信し、その前後で処理時間計測用にGPIOをH/Lさせています。


ch1:D13(LD2) ch2:D2(CHK1)

メインループ処理中(黄色)にも非同期にADCのDMA転送割り込み(赤色)が行われています。

Puttyで受信しているようす


マルチチャンネル


2ch分ADC読み取りを行いました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_ADC_DMA_Multi_Test1

STM32CubeMXの設定


[Analog]-[ADC1]で、[Mode]の[IN0]、[IN1]をチェックします。PA0がADC1-IN0、PA1がADC1-IN1に割り当てられます。これはNucleoボードのArduinoヘッダのA0、A1に接続されています。

[Parameter Settings]
Continuous Conversion Mode: Enable
DMA Continuous Requests: Enable
End Of Conversion Selection: EOC flag at the end of single channel conversion
Scanmode: Enable
ADC_Regular_ConversionMode
  Number Of Conversion: 2
Number Of Conversionで2以上を指定すると、[Scan Conversion Mode]は[Enable]しか選べなくなります。
Rank 1
  Channel: Channel 0
  Sampling Time: 480Cycles
Rank 2
  Channel: Channel 1
  Sampling Time: 480Cycles
Sampling Timeはひとまず480Cyclesを指定しました。
DMA Settings
  DMA Request: ADC1
  Mode: Circular

SW4STM32でコードを追加(一部抜粋)

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ADC_CHANNEL_N (2)
/* USER CODE END PD */

/* USER CODE BEGIN PV */
__IO uint16_t uhADCxConvertedValue[ADC_CHANNEL_N];
/* USER CODE END PV *

2ch分のバッファを確保しています。

/* USER CODE BEGIN 2 */
  printf("before HAL_ADC_Start_DMA()\r\n");

  /*##-3- Start the conversion process #######################################*/
  /* Note: Considering IT occurring after each number of ADC conversions      */
  /*       (IT by DMA end of transfer), select sampling time and ADC clock    */
  /*       with sufficient duration to not create an overhead situation in    */
  /*        IRQHandler. */
  if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)uhADCxConvertedValue, ADC_CHANNEL_N) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }

  printf("after HAL_ADC_Start_DMA()\r\n");
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_SET);
 printf("ADC Value[0]: %d\t",   uhADCxConvertedValue[0]);
 printf("ADC Value[1]: %d\r\n", uhADCxConvertedValue[1]);
 HAL_GPIO_WritePin(CHK1_GPIO_Port, CHK1_Pin, GPIO_PIN_RESET);
  }
  /* USER CODE END 3 */

メインループでは、ADCの読み取り値をUART送信しています。


/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  /* Turn LED2 on: Transfer process is correct */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
/* USER CODE END 4 */

DMA転送割り込みハンドラではLEDを点滅させています。


ch1:D13(LD2) ch2:D2(CHK1)

Puttyで受信しているようす