add DAC/IIR example
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…
Reference in New Issue