You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
5.0 KiB
Markdown
174 lines
5.0 KiB
Markdown
2 years ago
|
# 1. Reading ADC inputs through polling
|
||
|
|
||
|
## ADC configuration and initialization
|
||
|
|
||
|
### 1. Starting the ADC clock
|
||
|
|
||
|
First, the ADC clock's source must be selected since it defaults to none. We
|
||
|
will use the system clock as our source; if we were using the PLL, we could use
|
||
|
one of the PLL's "R" clocks instead.
|
||
|
|
||
|
```cpp
|
||
|
RCC->CCIPR &= ~(RCC_CCIPR_ADCSEL_Msk);
|
||
|
RCC->CCIPR |= 3 << RCC_CCIPR_ADCSEL_Pos; // Select system clock for ADC clock
|
||
|
```
|
||
|
|
||
|
Then, we simply enable the ADC clock through the AHB:
|
||
|
```cpp
|
||
|
RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;
|
||
|
```
|
||
|
|
||
|
### 2. Power on the ADC's internal voltage regulator
|
||
|
|
||
|
The ADC's internal regulator must be powered on before enabling the ADC. If the
|
||
|
ADC is disabled later, this regulator can be turned off to save power.
|
||
|
|
||
|
```cpp
|
||
|
ADC1->CR = 0; // The CR register must be zero before setting ADVREGEN
|
||
|
ADC1->CR = ADC_CR_ADVREGEN;
|
||
|
```
|
||
|
|
||
|
The internal regulator should be given time to warm up after turning on so that
|
||
|
its output stabilizes. A delay of around 20 microseconds should be enough; a
|
||
|
very crude delay function is implemented in the example.
|
||
|
|
||
|
### 3. Calibrating the ADC
|
||
|
|
||
|
The ADC should be calibrated when first powered on. The calibration process is
|
||
|
something internal (undocumented) to the microcontroller, so we just tell the
|
||
|
calibration to begin and wait for it to finish.
|
||
|
|
||
|
The microcontroller has the option to do a differential calibration, but we
|
||
|
will skip this since we are only using single-ended channels. This is also a
|
||
|
good time to select single-ended channels for our conversions.
|
||
|
|
||
|
```cpp
|
||
|
ADC1->DIFSEL = 0; // Clear DIFSEL for single-ended conversions.
|
||
|
|
||
|
ADC1->CR &= ~(ADC_CR_ADCALDIF); // Select single-ended calibration
|
||
|
ADC1->CR |= ADC_CR_ADCAL; // Begin calibration
|
||
|
while ((ADC1->CR & ADC_CR_ADCAL) != 0); // Wait for ADCAL to clear
|
||
|
```
|
||
|
|
||
|
If execution gets stuck in the `while` loop, there is a chance that the ADC's
|
||
|
clock was incorrectly configured.
|
||
|
|
||
|
### 4. Enable the ADC
|
||
|
|
||
|
Now, the ADC is ready to be enabled:
|
||
|
|
||
|
```cpp
|
||
|
ADC1->CR |= ADC_CR_ADEN;
|
||
|
while ((ADC1->ISR & ADC_ISR_ADRDY) == 0); // Wait for the ADC to be ready
|
||
|
```
|
||
|
|
||
|
## Prepare GPIO pin for analog reading
|
||
|
|
||
|
We will use pin PC0 since it is wired to the first ADC channel. See the
|
||
|
STM32L476RG datasheet for a table of connections between pins and ADC channels.
|
||
|
|
||
|
Make sure the GPIO port's clock is enabled:
|
||
|
|
||
|
```cpp
|
||
|
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;
|
||
|
```
|
||
|
|
||
|
Then, set the GPIO pin's mode to analog input:
|
||
|
|
||
|
```cpp
|
||
|
GPIOC->MODER |= 3 << GPIO_MODER_MODE0_Pos;
|
||
|
```
|
||
|
|
||
|
Finally, STM32L47x/L48x processors require an additional register setting to
|
||
|
complete the pin's connection to the ADC. We set bit zero for ADC channel one:
|
||
|
|
||
|
```cpp
|
||
|
GPIOC->ASCR |= 1 << 0;
|
||
|
```
|
||
|
|
||
|
## Prepare and execute the analog conversion
|
||
|
|
||
|
### Conversion sequencing
|
||
|
|
||
|
First we need to set up the ADC's conversion sequence. The ADC can take
|
||
|
multiple conversions in a row, with channels arranged in order according to
|
||
|
the `SQR` registers.
|
||
|
|
||
|
We are doing a single conversion of a single channel, channel one. So, we
|
||
|
write a `1` for the first sequence channel. By setting the register with an
|
||
|
`=` instead of just setting bits, we clear the L field of the register to zero
|
||
|
which sets a sequence length of one.
|
||
|
|
||
|
```cpp
|
||
|
ADC1->SQR1 = 1 << ADC_SQR1_SQ1_Pos;
|
||
|
```
|
||
|
|
||
|
### Other configuration
|
||
|
|
||
|
The `CFGR` register can be used to adjust the ADC's resolution (8 to 12 bits).
|
||
|
|
||
|
The `CFGR2` register can be configured if hardware oversampling will be used.
|
||
|
|
||
|
The `SMPR` registers can be configured to select sampling times. We will leave
|
||
|
this unconfigured to use the default (fastest) time.
|
||
|
|
||
|
The `OFR` register can be configured to have an offset subtracted from the
|
||
|
analog reading during conversion.
|
||
|
|
||
|
### Running the conversion
|
||
|
|
||
|
Below are the steps for completing a single channel conversion. In the example,
|
||
|
this code is contained within the `adc_read` function.
|
||
|
|
||
|
Set `CFGR` for single conversion, then initiate the conversion:
|
||
|
|
||
|
```cpp
|
||
|
ADC1->CFGR &= ~(ADC_CFGR_CONT);
|
||
|
|
||
|
ADC1->CR |= ADC_CR_ADSTART;
|
||
|
```
|
||
|
|
||
|
The ADC will set the end-of-conversion (EOC) bit of its status register `ISR`
|
||
|
once the conversion is complete. If we were using interrupts, we could write
|
||
|
and enable an interrupt handler to detect this; instead, we will simply poll
|
||
|
the register.
|
||
|
|
||
|
Once the EOC bit is set, we need to clear it before the next conversion.
|
||
|
|
||
|
```cpp
|
||
|
while ((ADC1->ISR & ADC_ISR_EOC) == 0);
|
||
|
ADC1->ISR &= ~(ADC_ISR_EOC);
|
||
|
```
|
||
|
|
||
|
The conversion result is now available in the data register `DR`.
|
||
|
|
||
|
## Getting feedback with an LED
|
||
|
|
||
|
We will use the Nucleo's on-board LED to produce feedback on our ADC
|
||
|
reading. The below code will turn the LED on or off depending on if the
|
||
|
ADC reading goes above a certain threshold.
|
||
|
|
||
|
Configure pin PA5 for output to control the LED:
|
||
|
|
||
|
```cpp
|
||
|
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
|
||
|
GPIOA->MODER &= ~(GPIO_MODER_MODE5_Msk);
|
||
|
GPIOA->MODER |= 1 << GPIO_MODER_MODE5_Pos;
|
||
|
```
|
||
|
|
||
|
Now, we run a simple loop. Our threshold will be 2048, half of the ADC's 12-
|
||
|
bit range. With the default voltage reference of 3.3V, the threshold will be
|
||
|
equal to `3.3V * 2048 / (2^12 - 1) = 1.65V`.
|
||
|
|
||
|
```cpp
|
||
|
while (1) {
|
||
|
unsigned int reading = adc_read();
|
||
|
|
||
|
if (reading > 2048)
|
||
|
GPIOA->BSRR |= 1 << 5;
|
||
|
else
|
||
|
GPIOA->BRR |= 1 << 5;
|
||
|
}
|
||
|
```
|
||
|
|