Sonntag, 4. September 2016

STM32 microcontroller #4: HAL ADC test

Setup

I will present the code to read analog values from a pin on the STM32 and my setup to test the code. I'll use the most simple program that I could write to do the following: reading a single value from the ADC peripheral and display it on 12 LEDs (it is a 12-bit value).
There are more sophisticated methods of using the ADC, e.g. direct memory access (DMA), and I plan to use these later. First, a picture of the setup

Sampling frequency

One interesting aspect is the sampling frequency of the setup. I believe that one can calculate it based on information that is available in the datasheet, but I'm too lazy to do that now and will just measure it roughly: after each sample, a LED is toggled (too fast for the human eye). On the oscilloscope, it is possible to see the toggling frequency. The timescale in the picture is 10 us / div, so I have ~25 us / sample, which corresponds to ~40 kHz sampling rate.

Firmware

The firmware code is based on the STM32F103RB-Nucleo examples that are part of the STM32Cube_FW_F1_V1.3.0 package from where I copy/pasted it together. I'm running the chip with 72 MHz, generated from the 16MHz crystal on my test board. The code is all in the following main.c. It compiles with the makefile shown in my previous post.


/**
  ******************************************************************************
  * based on: 
  *   Projects/STM32F103RB-Nucleo/Examples/GPIO/GPIO_IOToggle/Src/main.c
  *   from the STM32Cube_FW_F1_V1.3.0 software package package
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; COPYRIGHT(c) 2015 STMicroelectronics</center></h2>
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */

#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"

void SysTick_Handler(void)
{
 HAL_IncTick();
}

void SystemClock_Config(void)
{
 // Configure PLL with HSE_VALUE = 16000000UL = 16 MHz
 // PLL configuration: 
 //   PLLCLK = (HSE / 2) * PLLMUL = (16 / 2) * 9 = 72 MHz 
 RCC_OscInitTypeDef oscinitstruct = {0};
 oscinitstruct.OscillatorType      = RCC_OSCILLATORTYPE_HSE;
 oscinitstruct.HSEState            = RCC_HSE_ON;
 oscinitstruct.LSEState            = RCC_LSE_OFF;
 oscinitstruct.HSIState            = RCC_HSI_OFF;
 oscinitstruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
 oscinitstruct.HSEPredivValue      = RCC_HSE_PREDIV_DIV2;
 oscinitstruct.PLL.PLLState        = RCC_PLL_ON;
 oscinitstruct.PLL.PLLSource       = RCC_PLLSOURCE_HSE;
 oscinitstruct.PLL.PLLMUL          = RCC_PLL_MUL9;
 HAL_RCC_OscConfig(&oscinitstruct);
 
 // Select PLL as system clock source and configure 
 // the HCLK, PCLK1 and PCLK2 clocks dividers 
 RCC_ClkInitTypeDef clkinitstruct = {0};
 clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK 
                             | RCC_CLOCKTYPE_HCLK 
                             | RCC_CLOCKTYPE_PCLK1 
                             | RCC_CLOCKTYPE_PCLK2);
 clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
 clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
 clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV2;
 clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV1;  
 HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2);
}

void ConfigureADC(ADC_HandleTypeDef *AdcHandle)
{
    GPIO_InitTypeDef gpioInit;
 
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_ADC1_CLK_ENABLE();

    gpioInit.Pin = GPIO_PIN_1;
    gpioInit.Mode = GPIO_MODE_ANALOG;
    gpioInit.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOC, &gpioInit);
 
 
 
    AdcHandle->Instance = ADC1;
 
    AdcHandle->Init.ScanConvMode = DISABLE;
    AdcHandle->Init.ContinuousConvMode = ENABLE;
    AdcHandle->Init.DiscontinuousConvMode = DISABLE;
    AdcHandle->Init.NbrOfDiscConversion = 0;
    AdcHandle->Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_CC1;
    AdcHandle->Init.DataAlign = ADC_DATAALIGN_RIGHT;
    AdcHandle->Init.NbrOfConversion = 1;
 
    HAL_ADC_Init(AdcHandle);
 
    ADC_ChannelConfTypeDef adcChannel;
    adcChannel.Channel = ADC_CHANNEL_1;
    adcChannel.Rank = 1;
    adcChannel.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
    //adcChannel.Offset = 0;
 
    if (HAL_ADC_ConfigChannel(AdcHandle, &adcChannel) != HAL_OK)
    {
        //asm("bkpt 255");
    }
}


void display(uint32_t value)
{
 if (value & (1<<0))  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET); 
 if (value & (1<<1))  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET); 
 if (value & (1<<2))  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_RESET); 
 if (value & (1<<3))  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET); 
 if (value & (1<<4))  HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_6,GPIO_PIN_RESET); 
 if (value & (1<<5))  HAL_GPIO_WritePin(GPIOC,GPIO_PIN_7,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_7,GPIO_PIN_RESET); 
 if (value & (1<<6))  HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); 
 if (value & (1<<7))  HAL_GPIO_WritePin(GPIOC,GPIO_PIN_9,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_9,GPIO_PIN_RESET); 
 if (value & (1<<8))  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET); 
 if (value & (1<<9))  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_RESET); 
 if (value & (1<<10)) HAL_GPIO_WritePin(GPIOA,GPIO_PIN_10,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_10,GPIO_PIN_RESET); 
 if (value & (1<<11)) HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_SET);
 else                 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_RESET); 
}

int main(void)
{
 //  STM32F103xB HAL library initialization:
 // - Configure the Flash prefetch
 // - Systick timer is configured by default as source of time base, but user 
 //  can eventually implement his proper time base source (a general purpose 
 //  timer for example or other time source), keeping in mind that Time base 
 //  duration should be kept 1ms since PPP_TIMEOUT_VALUEs are defined and 
 //  handled in milliseconds basis.
 // - Set NVIC Group Priority to 4
 // - Low Level Initialization
 HAL_Init();
 
 // Configure the system clock to 72 MHz 
 SystemClock_Config();

 // -1- Enable GPIO Clock (to be able to program the configuration registers) 
 __HAL_RCC_GPIOA_CLK_ENABLE();
 __HAL_RCC_GPIOB_CLK_ENABLE();
 __HAL_RCC_GPIOC_CLK_ENABLE();
 
 // -2- Configure IO in output push-pull mode to drive external LEDs
 GPIO_InitTypeDef  GPIO_InitStruct;
 GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull  = GPIO_PULLUP;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
 
 GPIO_InitStruct.Pin   = GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
 HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
 GPIO_InitStruct.Pin   = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
 GPIO_InitStruct.Pin   = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

 // Configure the ADC peripheral
 ADC_HandleTypeDef    AdcHandle;
 ConfigureADC(&AdcHandle);

 for (;;)
 {
  // ADC measurment & display value
  HAL_ADC_Start(&AdcHandle);
  HAL_ADC_PollForConversion(&AdcHandle, 1000); // 1000 is timeout in miliseconds
  display(HAL_ADC_GetValue(&AdcHandle));
  HAL_ADC_Stop(&AdcHandle);

  // flash an additional LED to measure the sampling rate
  HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_4);
 }
}