製作モジュール一覧

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

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/