Analog input

Some pins have the possibility to measure the applied voltage dynamically, and assign a discrete number to the measured value, using an analog to digital signal converter, or ADC. This is very useful to acquire data from a wide range of sensors, capable of conveying the information as output voltage, or simply using a variable resistor.

The configuration of the ADC subsystem may vary significantly across different platforms. ADCs on modern microcontrollers offer a wide range of configuration options. The reference microcontroller equips three separate ADC controllers, sharing 16 input channels, and each one with a resolution of 12 bits. Multiple features are available, such as DMA transfer of the acquired data, and monitoring the signals in between two watchdog thresholds.

The simplest case consist in the implementation a one-shot read operation for a single conversion. Associating a specific pin to the controller is possible by checking how channels are mapped on the controllers, if the pin supports it and it is connected through a channel to one of the configured as analog input, and reading out the value which results from the conversion of the analog signal. In this example, the pin B1 is used as analog input, and can be connected to the ADB1 controller through channel 9. The following constants and registers are defined for the configuration of the ADB1 controller:

#define APB2_CLOCK_ER (*(volatile uint32_t *)(0x40023844))
#define ADC1_APB2_CLOCK_ER_VAL (1 << 8)
#define ADC1_BASE (0x40012000)
#define ADC1_SR (*(volatile uint32_t *)(ADC1_BASE + 0x00))
#define ADC1_CR1 (*(volatile uint32_t *)(ADC1_BASE + 0x04))
#define ADC1_CR2 (*(volatile uint32_t *)(ADC1_BASE + 0x08))
#define ADC1_SMPR1 (*(volatile uint32_t *)(ADC1_BASE + 0x0c))
#define ADC1_SMPR2 (*(volatile uint32_t *)(ADC1_BASE + 0x10))
#define ADC1_SQR3 (*(volatile uint32_t *)(ADC1_BASE + 0x34))
#define ADC1_DR (*(volatile uint32_t *)(ADC1_BASE + 0x4c))
#define ADC_CR1_SCAN (1 << 8)
#define ADC_CR2_EN (1 << 0)
#define ADC_CR2_CONT (1 << 1)
#define ADC_CR2_SWSTART (1 << 30)
#define ADC_SR_EOC (1 << 1)
#define ADC_SMPR_SMP_480CYC (0x7)

These are the definitions to configure GPIO as usual, this time mapped for GPIOB:

#define AHB1_CLOCK_ER (*(volatile uint32_t *)(0x40023830))
#define GPIOB_AHB1_CLOCK_ER (1 << 1)
#define GPIOB_BASE (0x40020400)
#define GPIOB_MODE (*(volatile uint32_t *)(GPIOB_BASE + 0x00))
#define ADC_PIN (1)
#define ADC_PIN_CHANNEL (9)

The three ADCs share a few registers for common settings, such as the clock prescale factor, so they will all operate at the same frequency. The prescale factor for the ADC must be set within the working range of the converter recommended by the datasheet. In the target platform, halving the frequency of the APB2 clock through the common prescaler. The common ADC configuration registers start at port 0x40012300:

#define ADC_COM_BASE (0x40012300)
#define ADC_COM_CCR (*(volatile uint32_t *)(ADC_COM_BASE + 0x04))

Based on these definitions, the initialization function can be written as follows. First, we enable the clock gating for both the ADC controller and the GPIO group:

int adc_init(void)
{
APB2_CLOCK_ER |= ADC1_APB2_CLOCK_ER_VAL;
AHB1_CLOCK_ER |= GPIOB_AHB1_CLOCK_ER;

PB1 is set to analog input mode, corresponding to the value 3 in the mode register:

    GPIOB_MODE |= 0x03 << (ADC_PIN * 2);

The ADC1 is temporarily switched off to set the desired configuration. The common clock prescaler is set to 0, meaning a divisor of 2 from the input clock. This ensures that the frequency fed to the ADC controller is within its operational range. Scan mode is disabled, and so is continuous mode, as we are not using these features in this example:

    ADC1_CR2 &= ~(ADC_CR2_EN);
ADC_COM_CCR &= ~(0x03 << 16);
ADC1_CR1 &= ~(ADC_CR1_SCAN);
ADC1_CR2 &= ~(ADC_CR2_CONT);

The sampling frequency can be set using the two registers SMPR1 and SMPR2, depending on the channel in use. Each register represents one channel sample rate using three bits per register, so the channels 0 to 9 are configurable using SMPR1, and all the others through SMPR2. The channel for PB1 is set to 9, so in this case the SMPR1 is used, but to remind about this the generic mechanism to set the sample rate on any channel is provided:

if (ADC_PIN_CHANNEL > 9) {
uint32_t val = ADC1_SMPR2;
val = ADC_SMPR_SMP_480CYC << ((ADC_PIN_CHANNEL - 10) * 3);
ADC1_SMPR2 = val;
} else {
uint32_t val = ADC1_SMPR1;
val = ADC_SMPR_SMP_480CYC << (ADC_PIN_CHANNEL * 3);
ADC1_SMPR1 = val;
}

Finally, the channel is enabled in the conversion sequence of the ADC controller using the sequence registers (SQR). The mechanisms foresee that multiple channels can be added to the same sequence on the controller, by populating the registers in inverse order, from SQR3 to SQR1. Each source channel is represented in five bits, so each register contains up to six sources, except SQR1, which stores five, and reserves the higher bits to indicate the length of the stack stored in the registers, minus one. In our case, there is no need to set the length-minus-one field, as it would be zero for a single source in SQR1:

ADC1_SQR3 |= (ADC_PIN_CHANNEL);

Finally, the ADC1 is enabled again by setting the enable bit in the CR2, and the initialization function successfully returns:

ADC1_CR2 |= ADC_CR2_EN;
return 0;
}

After the ADC has been initialized and configured to convert the analog signal on PB1, the A/D conversion can be started at any time. A simple blocking read function would initiate the conversion, wait for the conversion to be successfully started, then wait until the conversion is completed by looking at the end of conversion (EOC) bit in the status register:

int adc_read(void)
{
ADC1_CR2 |= ADC_CR2_SWSTART;
while (ADC1_CR2 & ADC_CR2_SWSTART)
;
while ((ADC1_SR & ADC_SR_EOC) == 0)
;

When the conversion is completed, the corresponding discrete value is available on the lowest 12 bits of the data register, and can be returned to the caller:

    return (int)(ADC1_DR);
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.200.211