2019年3月30日土曜日

STM32: 内蔵DACを使う(Nucleo-F446RE)

Nucleo-F446REの内蔵DACからSIN波を出力しました。

参考
<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32446E_EVAL\Examples\DAC\DAC_SimpleConversion

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

実行環境

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

STM32CubeMXの設定



STM32CubeMXに[Report]という機能があったので出力してみました。

https://github.com/ryood/STM32Cube_Test/blob/master/SW4STM32/Nucleo-F446_DAC_Test5/Nucleo-F446_DAC_Test5.pdf

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

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

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  double phi = 0.0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    uint16_t sinv = 4095 * ((sin(phi) + 1.0) / 2.0);
    if (HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sinv) != HAL_OK)
    {
      Error_Handler();
    }
    if (HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_12B_R, sinv) != HAL_OK)
    {
      Error_Handler();
    }
    phi += 0.01;
  }
  /* USER CODE END 3 */
}

位相角を0.01ずつ増やしてsinの値を求め、12bit値に変換して2chある内蔵DACから出力しています。

出力結果


ch1:DAC1(PA4) ch2:DAC2(PA5)

DAC2はNucleoボード上でLEDに接続されているため、上側がクリップしています。DAC1も上下の最大振幅付近で若干クリップしています。

2019年3月26日火曜日

STM32: SPI ADCのMCP3208を使う(Nucleo-F446RE)

SPIのマスター側が読み取る必要があるA-DコンバーターのMCP3208を動作させました。

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

実行環境

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

配線


STM32CubeMXの設定


CLOCK Confiiguration

[HCLK]: 128

プリスケーラ-で分周しやすいようにHCLKを128MHz。

[SPI1]-[Parameter Settings]

Clock Prameters
  Prescaler(for Baud Rate): 64

MCP3208はVdd=2.7Vのときクロック周波数は最大1.0MHzなのでそれに合わせてプリスケーラ-を設定。

GPIOの設定

SPI1のNSS(CS)をソフトウェア制御するために、GPIOで[SPI1_CS]を割り当て。

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


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define MCP3208_START_BIT   0x04
#define MCP3208_MODE_SINGLE 0x02    // Single-ended mode
#define MCP3208_MODE_DIFF   0x00    // Differential mode
/* USER CODE END PD */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
 return ch;
}

uint16_t MCP3208_read_u16(uint8_t channel)
{
  uint8_t aTxBuffer[3];
  uint8_t aRxBuffer[3];

  aTxBuffer[0] = MCP3208_START_BIT | MCP3208_MODE_SINGLE | ((channel & 0x04) >> 2);
  aTxBuffer[1] = (channel & 0x03) << 6;
  aTxBuffer[2] = 0;

  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);

  if(HAL_SPI_TransmitReceive(&hspi1, aTxBuffer, aRxBuffer, 3, 100) != HAL_OK)
  {
    /* Transfer error in transmission process */
    Error_Handler();
  }

  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

  // RXデータの2バイト目の上位4ビットは値が不定のため0x0fでマスキング
  uint16_t conv_result = ((uint16_t)(aRxBuffer[1] & 0x0f) << 8) | (uint16_t)aRxBuffer[2];

  return conv_result;
}

/* USER CODE END 0 */

MCP3208のデータシートを見ると、SPIで8bit単位で送受信すると、RXデータ(12bit)の上位3ビットはHI-Zで不定になり、4ビット目は0になるようです。このためRXデータ(aRxBuffer[])の2バイト目の上位4ビットはプログラムで読み捨てています。


↑DOUTがNucleo-F446側から見るとRXデータになります。


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

    /* USER CODE BEGIN 3 */
    uint16_t adcv[3] = {0};
    for (int i = 0; i < 3; i++) {
      adcv[i] = MCP3208_read_u16(i);
    }
    printf("Read VAL:\t%d\t%d\t%d\r\n", adcv[0], adcv[1], adcv[2]);
  }
  /* USER CODE END 3 */

メインループでMCP3208をポーリングして、値をprintf(UART出力)しています。

SPI信号波形

MISO

ch1:MISO ch2:SCK

MOSI

ch1:MOSI ch2:SCK

<追記:2019.03.29>

MOSIの3バイト目(aTxBuffer[2])は「0」のハズなのに、値が出力されてますね(@@? バグがありそうです。

</追記>

CS

ch1:CS ch2:SCK

Puttyで受信しているようす

メモ

3連POTのch0(黄色)が動作不良。

2019年3月23日土曜日

STM32: SPI DACのMCP4922を使う(Nucleo-F446RE)

SPI DACのMCP4922でノコギリ波を2ch出力しました。

実行環境

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

配線

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

STM32CubeMXの設定


MCP4922はMISOが必要ないのでSPIは「Transmit Only Master」でOKです。


GPIOは、SPIのNSS(CS)をソフトウェアで制御するのに1つ、2ch分ラッチして出力するのでLDAC1つで計2本です。


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


/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

// param
//   channel: 0, 1
//   val: 0 .. 4095
void MCP4922_Write(uint8_t channel, uint16_t val)
{
 channel = channel & 0x01;
 val = val & 0x0fff;

 uint8_t cmd[2];
 cmd[0] = (channel << 7) | (val >> 8) | 0x30;
 cmd[1] = val & 0xff;

 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, cmd, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
}

/* USER CODE END 0 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(LDAC_GPIO_Port, LDAC_Pin, GPIO_PIN_SET);
 MCP4922_Write(0, cnt);
 MCP4922_Write(1, 4095 - cnt);
 HAL_GPIO_WritePin(LDAC_GPIO_Port, LDAC_Pin, GPIO_PIN_RESET);

 cnt++;
 if (cnt > 4095) {
  cnt = 0;
 }
  }
  /* USER CODE END 3 */

ノコギリ波の非反転、反転を出力しました。

出力結果


ch1:VOUTA ch2:VOUTB

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で受信しているようす


2019年3月19日火曜日

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

ADCを1チャネル、割り込みでテストしました。

参考にした記事
STM32CubeのExample「ADC_RegularConversion_Interrupt」
<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32446E_EVAL\Examples\ADC\ADC_RegularConversion_Interrupt
「EMCU」さんの「How to use ADC in Interrupt mode

実行環境

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

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

STM32CubeMXの設定



[Analog]-[ADC1]で[IN0]を有効化。

[IN0]の[Parameter Settings]で以下の通り設定

  • Continuous Conversion Mode: Enable
  • End Of Conversion Selection: EOC flag at the end of all conversions


[NVIC Settings]で[Enable]にチェック

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

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

/* USER CODE BEGIN PV */
/* Variable used to get converted value */
__IO uint16_t uhADCxConvertedValue = 0;
/* USER CODE END PV */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
 return ch;
}
/* USER CODE END 0 */

/* USER CODE BEGIN 4 */
/**
  * @brief  Conversion complete callback in non blocking mode
  * @param  AdcHandle : AdcHandle handle
  * @note   This example shows a simple way to report end of conversion, and
  *         you can add your own implementation.
  * @retval None
  */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  /* Get the converted value of regular channel */
  uhADCxConvertedValue = HAL_ADC_GetValue(AdcHandle);
  printf("ADC Value: %d\r\n", uhADCxConvertedValue);
}
/* USER CODE END 4 */

HAL_ADC_ConvCpltCallback()は割り込みハンドラで名前は固定です。printf()を使っていますが、ブロッキングされるので本当は割り込みハンドラ内で使ってはいけません。

メモ:


「End of Conversion Slection」(変数名はEOCSelection)は、STM32CubeのExampleでは
  AdcHandle.Init.EOCSelection          = DISABLE;
となっています。DISABLEは、stm32f4xx.hで「0U」と定義されています。

STM32Cube_FW_F4_V1.24.0では、stm32f4xx_hall_adc.hで
  /** @defgroup ADC_EOCSelection ADC EOC Selection
  * @{
  */
#define ADC_EOC_SEQ_CONV              0x00000000U
#define ADC_EOC_SINGLE_CONV           0x00000001U
#define ADC_EOC_SINGLE_SEQ_CONV       0x00000002U  /*!< reserved for future use */
と定義されています。よって「ADC_EOC_SEQ_CONV」(0U)を指定しました。

「ADC_EOC_SINGLE_CONV」(1U)の場合は、ポーリングの場合と同様に、起動時に1回割り込みがかかってそれっきりで、連続変換になりません。これはADC_CR2レジスタのEOCSビットの仕様だと思います。

2019年3月16日土曜日

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

STM32のADCを一番簡単そうな、1チャネルのポーリングで使ってみました。(簡単そうでしたがハマったところがあります。)

STM32のADCはポーリングの他に、割り込みやDMAを使う事もでき、こちらのほうがCPU時間を節約できそうです。

参考にした記事

STM32CubeのExample「ADC_RegularConversion_Polling」
<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32446E_EVAL\Examples\ADC\ADC_RegularConversion_Polling
「ガレスタさんのDIY日記」さんの「STM32でADCをやってみる1(レギュラ変換)
「CCWO」さんの「STM32F303K8 ADC 1ch レギュラー変換

実行環境

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

シングル変換


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

STM32CubeのExample「ADC_RegularConversion_Polling」をもとに「シングル変換(Continuous Conversion Mode: DISALBE)」で動作させました。「ガレスタさんのDIY日記」さんや「CCWO」さんの例では「連続変換(Continuous Conversion Mode: Enable)」である点が異なります。

STM32CubeMXの設定


ADC1のIN0を有効化します。PinoutビューでPA0がADC入力に割り当てられます。PA0はNucleo-F446REのArduino HeaderのA0につながっています。


Configurationはデフォルトのままです。


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


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

<stdio.h>をインクルード。

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
 return ch;
}
/* USER CODE END 0 */

printf()を使えるようにする。

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

    /* USER CODE BEGIN 3 */
   /*##-3- Start the conversion process #######################################*/
   if (HAL_ADC_Start(&hadc1) != HAL_OK)
   {
  /* Start Conversation Error */
  Error_Handler();
   }

   /*##-4- Wait for the end of conversion #####################################*/
   /*  Before starting a new conversion, you need to check the current state of
     the peripheral; if it痴 busy you need to wait for the end of current
     conversion before starting a new one.
     For simplicity reasons, this example is just waiting till the end of the
     conversion, but application may perform other tasks while conversion
     operation is ongoing. */
   if (HAL_ADC_PollForConversion(&hadc1, 10) != HAL_OK)
   {
  /* End Of Conversion flag not set on time */
  Error_Handler();
   }

 /* Check if the continuous conversion of regular channel is finished */
   if ((HAL_ADC_GetState(&hadc1) & HAL_ADC_STATE_EOC_REG) == HAL_ADC_STATE_EOC_REG)
   {
  /*##-5- Get the converted value of regular channel  ########################*/
  uhADCxConvertedValue = HAL_ADC_GetValue(&hadc1);
   }
   printf("ADC Value: %d\r\n", uhADCxConvertedValue);
   HAL_Delay(100);
  }
  /* USER CODE END 3 */

メインループでADCの読み取りを行う。シングル変換のため、毎回HAL_ADC_Start()を呼んだあと、ADCの値を読み取っています。

なお、ADCの初期化コードはSTM32CubeMXによって生成されています。

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}


POTを GND - A0 - 3V3 に接続しています。

Puttyで受信

連続変換


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

シングル変換と異なる点は、「ContinuousConvMode = ENABLE」とすることですが、Nucleo-F446REの場合は、EOCSelectionを「EOC flag at the end of all conversion」と指定する必要がありました。


参考「https://electronics.stackexchange.com/questions/202938/stm32-adc-conversion-using-hal

調べるとUM1725の6.2.7の「HAL_ADC_PollForConversion」の項に以下の記述がありました。こんなのマニュアル読み込まないとわからない・・・。
This function cannot be used in a particular setup: ADC
configured in DMA mode and polling for end of each
conversion (ADC init parameter "EOCSelection" set to
ADC_EOC_SINGLE_CONV). In this case, DMA resets the
flag EOC and polling cannot be performed on each
conversion. Nevertheless, polling can still be performed on
the complete sequence.
「EOC flag at the end of single channel conversion」のままだと、起動後1回だけADC変換が行われそれ以降は同じ値が返ってくるようです。

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

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  /*##-3- Start the conversion process #######################################*/
  if (HAL_ADC_Start(&hadc1) != HAL_OK)
  {
    /* Start Conversation Error */
    Error_Handler();
  }
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
   /*##-4- Wait for the end of conversion #####################################*/
   /*  Before starting a new conversion, you need to check the current state of
        the peripheral; if it痴 busy you need to wait for the end of current
        conversion before starting a new one.
        For simplicity reasons, this example is just waiting till the end of the
        conversion, but application may perform other tasks while conversion
        operation is ongoing. */
   if (HAL_ADC_PollForConversion(&hadc1, 10) != HAL_OK)
   {
     /* End Of Conversion flag not set on time */
     Error_Handler();
   }

 /* Check if the continuous conversion of regular channel is finished */
   if ((HAL_ADC_GetState(&hadc1) & HAL_ADC_STATE_EOC_REG) == HAL_ADC_STATE_EOC_REG)
   {
     /*##-5- Get the converted value of regular channel  ########################*/
     uhADCxConvertedValue = HAL_ADC_GetValue(&hadc1);
   }
   printf("ADC Value: %d\r\n", uhADCxConvertedValue);
   HAL_Delay(100);
  }
  /* USER CODE END 3 */

連続変換モードにしてHAL_ADC_Start()をメインループの外に追い出せました。

メモ:


STM32CubeF4はSTM32CubeF3とはADCの仕様が結構異なり、「HAL_ADCEx_Calibration」がない模様。その他Dual channel(差動入力)がなかったり、Clock Prescalerで「ADC Asycrhronus clock mode」がなかったり・・・

参考になりそうな記事
「STM32F0 ADC - Tutorial 6」https://letanphuc.net/2016/07/stm32f0-adc/
「How to use 3 channels of the ADC in DMA mode using CUBE-MX and ATOLLIC」http://www.emcu.eu/how-to-use-3-channels-of-the-adc-in-dma-mode-using-cube-mx-and-atollic/
「How to use ADC in Interrupt mode」http://www.emcu.eu/how-to-use-adc-in-interrupt-mode/
「A detailed tutorial on STM32 ADC」https://visualgdb.com/tutorials/arm/stm32/adc/

2019年3月13日水曜日

STM32: UARTでprintfを使う(浮動小数点型あり)

STM32のUARTでprintf関数を使ってみました。

参考にした記事
「ガレスタさんのDIY日記」さんの「STM32でUARTをやってみる6(float型printfをUART経由で出力)
「@take-iwiw」さんの」「STM32F4 Discovery BoardとCube MXの環境構築、Lチカからprintfポーティングまで
「滴了庵日録」さんの「CubeMX環境でprintfを使う

実行環境

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

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

UART自体の基本的な使い方は「STM32CubeとTrueSTUDIOでUART通信」にあります。

ソースコード(一部)

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

<stdio.h>をインクルードします。

/* USER CODE BEGIN PV */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
 return ch;
}
/* USER CODE END PV */

int __io_putchar()で、HAL_UART_Transmit()をラッピングするコードです。これによってprintf()内部でHAL_UART_Transmit()が呼ばれるようになります。


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  const char msg[] = "CNT:";
  int cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */
 float fv = cnt * 0.01f;
 printf("%s\t%d\t%f\r\n", msg, cnt, fv);
 cnt++;
 fv += 0.01;
 HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

メインループ内で、printf()を呼び出しています。文字列、整数、浮動小数点数を使っています。

<追記:2019.03.13>

ソースコードの「fv += 0.01;」は無駄な演算です。

</追記>


Puttyで受信している様子


メモ:


printf()で浮動少数点数を使うと、「-u _printf_float」せよと、IDEでアラートが表示されます。[MCU GCC Linker]-[Miscellaneous]の[Linder flags]で「-u _printf_float」を指定します。


STM32CubeMX Ver.5.1.0では、Generate Codeすると「syscalls.c」は<projectフォルダ>/Srcに入っています。

2019年3月11日月曜日

STM32: CMSIS-DSPのFast Math Functionsの処理速度

CMSIS-DSPのFast Math Functionsの処理速度を測定しました。

CMSIS-DSP Fast Math Functions
http://www.keil.com/pack/doc/CMSIS/DSP/html/group__groupFastMath.html

CMSIS-DSPのFast Math Functionsは

  • Square Root
  • Cosine
  • Sine

の固定小数点数と単精度浮動小数点数の関数が用意されています。

使い方は「ガレスタさんのDIY日記」の「STM32でCMSIS DSPを使ってみる その1」を参考にしました。

実行環境

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

プロジェクト:
https://github.com/ryood/Nucleo_F446_FloatingPoint/tree/master/SW4STM32/floating_point_vsCMSIS_DSP_test1

CMSIS-DSPのライブラリファイルの準備


「<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Drivers\CMSIS\Lib\GCC\libarm_cortexM4lf_math.a」を「<workspace>\CMSIS_DSP_test1\」(プロジェクトのルート)にコピー。


ファイル名の"l"はリトル・エンディアン、"f"はfpuの意味のようです。

インクルードファイルの準備


「<STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Drivers\CMSIS\DSP\Include\arm_math.h」を「<プロジェクトフォルダ>\Inc」にコピー

SW4STM32でビルドオプションを指定


GCC Linkerの設定で[Libraries (-l)]と[Library search path(-L)」を設定

Libraries(-l)
arm_cortexM4lf_math

Library search path(-L)
"${workspace_loc:/${ProjName}}"


Libraryの指定は、接頭辞の"lib"及び接尾辞の".a"を抜きます。(参考「Tutorial: Using the ARM CMSIS Library」の「Library」の項)

GCC Compiler - Preprocessorで「ARM_MATH_CM4」を指定。


「__FPU_PRESENT」はもともと定義されているようで、Preprocessorで指定すると「redefined」のアラートが出ます。

コード


"arm_math.h"をインクルード

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "math.h"
#include "arm_math.h"
/* USER CODE END Includes */

メインループで関数を使う

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

    /* USER CODE BEGIN 3 */
 volatile float fv = 0.0f;
 LL_GPIO_SetOutputPin(CHK1_GPIO_Port, CHK1_Pin);
 for (int i = 0; i < LOOP_N; i++) {
  fv += 0.01f;
  //buffer[i] = sqrtf(fv);
  //buffer[i] = sinf(fv);
  //buffer[i] = cosf(fv);
  //arm_sqrt_f32(fv, buffer + i);
  buffer[i] = arm_sin_f32(fv);
  //buffer[i] = arm_cos_f32(fv);
 }
 LL_GPIO_ResetOutputPin(CHK1_GPIO_Port, CHK1_Pin);
  }
  /* USER CODE END 3 */
}

math Libraryの関数(sinf()など)と、CMSIS-DSPの関数(arm_sqrt_f32()など)をコメントアウトしたり外したりしてビルドし直して計測しました。GPIOをH/Lさせてオシロで処理時間を測定しました。GPIO操作には低レベルAPIのLL APIを使用しました。(参考「GPIO出力の速度比較 mbed OS5 vs HAL API vs LL API」)

コンパイラ・オプション

-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -DUSE_FULL_LL_DRIVER -DARM_MATH_CM4 -D__weak=__attribute__((weak)) -D__packed=__attribute__((__packed__)) -DUSE_HAL_DRIVER -DSTM32F446xx -I../Inc -I../Drivers/STM32F4xx_HAL_Driver/Inc -I../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32F4xx/Include -I../Drivers/CMSIS/Include -O2 -g3 -Wall -fmessage-length=0 -ffunction-sections -c -fmessage-length=0

リンカ・オプション

-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -L"D:\Users\gizmo\Documents\workspace\floating_point_vsCMSIS_DSP_test1" -specs=nosys.specs -specs=nano.specs -T"../STM32F446RETx_FLASH.ld" -Wl,-Map=output.map -Wl,--gc-sections -lm

ビルドすると、「arm_sqrt_f32(fv, buffer + i);」でvolatile修飾に対して警告が出ましたが無視しています。

警告メッセージ

Description Resource Path Location Type
passing argument 2 of 'arm_sqrt_f32' discards 'volatile' qualifier from pointer target type [-Wdiscarded-qualifiers] main.c /floating_point_vsCMSIS_DSP_test1/Src line 112 C/C++ Problem

Debugパースペクティブの式の評価(arm_sin_f32())


測定結果


測定データ

op math(us) CMSIS-DSP(us)
sqrt 178 194
sine 1038 345
cosine 1002 345

sine, cosineはCMSIS-DSPを使うとかなり高速化できるようです。

メモ:


STM32CubeMXとSW4STM32を行ったり来たりすると、「STM32F446RETx_FLASH.ld」が空(0Byte)になってビルドできなくなることがあります。「STM32F446RETx_FLASH.ld」を削除してSTM32CubeMXでGenerate Codeし直せば良いようです。