DMA in the IDE, Part IV

STM32 DMA ADC P2M Demo

This is the fourth example of DMA usage on the STM32F405 Feather board. Programming is from the Arduino IDE. This example demonstrates the peripheral to memory (P2M) DMA Mode. We send data from the ADC peripheral to memory via DMA.

This code sends the internal temperature of the MCU to the serial monitor every second.

The Code

// DMA ADC P2M Demo.
// Adafruit SMT32F405 Feather.
//
// stm32f405 CubeMX Setup:
// ADC1, Temperature Sensor Channel.
// Continuous Conversion Mode: Enabled.
// DMA Continuous Requests: Enabled.
// Number of Conversion: 16.
// DMA Request, Add: ADC1
// [per stm32f405 Ref Man, pg 308, DMA2 Request Mapping Table]
// System Core, DMA2 Request, Channel 0, ADC1 Stream4.
// Increment memory by half-word (12-bit resolution).
//
// Tools > C Runtime Library: Newlib Nano + Float Scanf
//
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

volatile bool adcDataAvailable __attribute__ ((aligned));
volatile uint16_t adcData[16];
constexpr uint32_t INTERVAL = 1000; // ms.
uint32_t t = millis();

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

// Handle DMA2 stream4 global interrupt.
extern "C" void DMA2_Stream0_IRQHandler(void) {
  HAL_DMA_IRQHandler(&hdma_adc1);
}

// ADC conversion complete callback.
extern "C" void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { 
  UNUSED(hadc); 
  adcDataAvailable = true;
}

void adcInit(void) {
  ADC_ChannelConfTypeDef sConfig = {0};

  __HAL_RCC_ADC1_CLK_ENABLE();
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  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 = 16;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
    while(1);
  
  hdma_adc1.Instance = DMA2_Stream0;
  hdma_adc1.Init.Channel = DMA_CHANNEL_0;
  hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_adc1.Init.Mode = DMA_NORMAL;
  hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
  hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    while(1);

  __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  for (uint32_t i=1; i<=16; i++) {
    sConfig.Rank = i;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      while(1);
  }
}

void dmaInit(void) {
  __HAL_RCC_DMA2_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

// Convert sample to temperature.
float calcTemp() {
  // See STM32F405 Reference Manual pg. 413.
  const float AVG_SLOPE = 2.5;
  const float V25 = 0.76;
  const float ADC_TO_VOLT = 3.3 / (4096 - 1);
  float adcValue = 0.0;
 
  for (int i=0; i<16; i++) {
    adcValue += (float)adcData[i];
    adcData[i] = 0;
  }
  adcValue /= 16.0;
  
  float vSense = adcValue * ADC_TO_VOLT;
  float temp = (vSense - V25) / AVG_SLOPE + 25.0f;
  return temp;
}

void setup() {
  dmaInit();
  adcInit();
  Serial.begin(9600);
  while(!Serial);
  adcDataAvailable = false;
}

void loop(void) {
  if (millis() > t) {
    t = millis() + INTERVAL;
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcData, 16);
  }
  
  if (adcDataAvailable) {
    char s[16] = {0};
     
    adcDataAvailable = false;
    sprintf(s, "temp = %3.2f C", calcTemp());
    Serial.println(s);
  }
}

About Jim Eli

µC experimenter
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment