add DAC/IIR example

main
Clyne 1 year ago
parent 51ef859f60
commit de6256dd45

@ -0,0 +1,5 @@
COMMON_DIR := ../common
CFILES += main.c
include $(COMMON_DIR)/rules.mk

@ -0,0 +1,71 @@
# 2. DAC output with an IIR filter
## DAC initialization
This example uses the DAC with minimal configuration.
Enable the DAC's clock through the RCC:
```cpp
RCC->APB1ENR1 |= RCC_APB1ENR1_DAC1EN;
```
Then, just enable DAC channel one. This will output over pin PA4:
```cpp
DAC1->CR = DAC_CR_EN1;
```
We rely on the default/reset configuration of the other DAC registers. In this
configuration, the DAC is running without a hardware or software trigger;
instead, the DAC will update its output whenever we provide a new output value.
There is a variety of output registers available for different bit-sizes and
alignments. We will stick to the basic 12-bit, right-aligned register
`DHR12R1`.
## IIR filter
The filter will run in a `while` loop, reading samples from the ADC and using
them to produce new DAC output data.
The first-order recursive equation is `y[n] = a*y[n-1] + b*x[n]`. Constants `a`
and `b` set the weights for the previous output sample and current input
sample; their sum should not be greater than one.
```cpp
const float a = 0.5f;
const float b = 0.5f;
```
In the `while` loop, we read our input sample from the ADC (`x[n]`) and
calculate the new output value `y`:
```cpp
unsigned int yprev = 0; // y[n-1]
while (1) {
unsigned int x = adc_read();
unsigned int y = a * yprev + b * x;
```
The new output value is then sent out over the DAC and stored in `yprev` for
the next calculation/iteration.
```cpp
DAC1->DHR12R1 = y;
yprev = y;
```
Finally, we add a small delay to have some basic control over the sampling
rate. Increasing the loop's max value may make the filter's effect more
visible, while decreasing it will minimize the observed effect.
Ideally, the sampling rate would instead be controlled by setting the speed of
the clock given to the ADC and configuring the registers responsible for the
conversion and sampling timings.
```cpp
for (int i = 0; i < 100; ++i);
}
```

@ -0,0 +1,87 @@
#include <stm32l476xx.h>
// Initializes the ADC for reading from channel one.
void adc_init(void);
// Does an ADC conversion on channel one and returns the result.
unsigned int adc_read(void);
int main(void)
{
adc_init();
// DAC initialization:
// Enables DAC channel one on PA4
// Does not configure a hardware or software trigger, so DAC updates
// when its data register is written.
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
RCC->APB1ENR1 |= RCC_APB1ENR1_DAC1EN;
DAC1->CR = DAC_CR_EN1;
// Execute a first-order IIR filter in a loop: y[n] = a*y[n-1] + b*x[n]
const float a = 0.5f;
const float b = 0.5f;
unsigned int yprev = 0; // y[n-1]
while (1) {
unsigned int x = adc_read();
unsigned int y = a * yprev + b * x;
DAC1->DHR12R1 = y;
yprev = y;
// Add some delay between samples
for (int i = 0; i < 100; ++i);
}
}
void adc_init(void)
{
// Use the CCIPR register to select the system clock as a source for the
// ADC clock, then enable the ADC clock through the AHB.
RCC->CCIPR &= ~(RCC_CCIPR_ADCSEL_Msk);
RCC->CCIPR |= 3 << RCC_CCIPR_ADCSEL_Pos;
RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;
// Enable the ADC's internal voltage regulator, with a delay for its
// startup time.
// ADVREGEN requires some bits in the CR register to be zero before being
// set, so we clear CR to zero first.
ADC1->CR = 0;
ADC1->CR = ADC_CR_ADVREGEN;
for (int i = 0; i < 100; ++i);
// Clear DIFSEL to do single-ended conversions.
ADC1->DIFSEL = 0;
// Begin a single-ended calibration and wait for it to complete.
// If the clock source for the ADC is not correctly configured, the
// processor may get stuck in the while loop.
ADC1->CR &= ~(ADC_CR_ADCALDIF); // Select single-ended calibration
ADC1->CR |= ADC_CR_ADCAL;
while ((ADC1->CR & ADC_CR_ADCAL) != 0);
// Enable the ADC and wait for it to be ready.
ADC1->CR |= ADC_CR_ADEN;
while ((ADC1->ISR & ADC_ISR_ADRDY) == 0);
// GPIO pin PC3 will be used as our ADC input.
// In order: enable the GPIOC clock, set PC3 to analog input mode,
// connect ADC channel one to its GPIO pin (PC3).
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;
GPIOC->MODER |= 3 << GPIO_MODER_MODE0_Pos;
GPIOC->ASCR |= 1 << 0;
// Make channel one the first in the ADC's read sequence.
// Using '=' here also clears the L field of the register, giving the
// sequence a length of one.
ADC1->SQR1 = 1 << ADC_SQR1_SQ1_Pos;
}
unsigned int adc_read(void)
{
ADC1->CFGR &= ~(ADC_CFGR_CONT); // Single conversion
ADC1->CR |= ADC_CR_ADSTART; // Start converting
while ((ADC1->ISR & ADC_ISR_EOC) == 0); // Wait for end-of-conversion
ADC1->ISR &= ~(ADC_ISR_EOC); // Clear the status flag
return ADC1->DR; // Read the result
}
Loading…
Cancel
Save