Sinusoidal signals are commonly used in signal processing applications and generating these waveforms provides an interesting project that is the focus of this recipe. A common approach is a direct method that stores the sinusoidal waveform samples in a look-up-table (LUT). This recipe is called dacSinusoid_c5v0
.
First, we need to calculate the (12-bit) DAC values that will be stored in the LUT. We'll attempt to generate a 50 Hz sinusoidal signal and use a spreadsheet (for example, Microsoft Excel) to calculate the following values:
Smpl. No |
Theta Rads |
floor((sin(theta)+1)*4095/2) |
---|---|---|
0 |
0 |
2047 |
1 |
0.31415927 |
2680 |
2 |
0.62831853 |
3250 |
3 |
0.9424778 |
3703 |
4 |
1.25663706 |
3994 |
5 |
1.57079633 |
4095 |
6 |
1.88495559 |
3994 |
7 |
2.19911486 |
3703 |
8 |
2.51327412 |
3250 |
9 |
2.82743339 |
2680 |
10 |
3.14159265 |
2047 |
11 |
3.45575192 |
1414 |
12 |
3.76991118 |
844 |
13 |
4.08407045 |
391 |
14 |
4.39822972 |
100 |
15 |
4.71238898 |
0 |
16 |
5.02654825 |
100 |
17 |
5.34070751 |
391 |
18 |
5.65486678 |
844 |
19 |
5.96902604 |
1414 |
Follow the outlined steps to generate a sine wave:
dacSinusoid_c5v0
by cloning timerISR_c5v0
from the Using timers to trigger conversions recipe.timerISR.c
with a file named dacSinusoid.c
and add a declaration for an LUT:uint16_t dacLUT [] = {2047, 2680, 3250, 3703, 3994, 4095, 3994, 3703, 3250, 2680, 2047, 1414, 844, 391, 100, 0, 100, 391, 844, 1414 };
TIM2
:/*------------------------------------------------ TIM2 IRQ Handler *------------------------------------------------*/ void TIM2_IRQHandler (void) { static uint8_t idx = 0; if (TIM2->SR & (1<<0)) { TIM2->SR &= ~(1<<0); /* clear UIR flag */ /* write LUT val to DAC */ DAC->DHR12R1 = dacLUT[idx++]; idx %= 20; LED_Out (idx); /* Write idx to LEDs */ }
Add the following main()
function:
/*--------------------------------------------------- Main function *---------------------------------------------------*/ int main (void) { HAL_Init(); SystemClock_Config(); LED_Initialize (); /* LED Init. */ DAC_Initialize (); /* DAC Init */ TIM2_Initialize (); while (1) { /* empty statement */ ; } }
dacSinusoid.c
to the project.TIM2_Initialize ( )
function (in the timer.c
file) needs to be changed:/*************************************************** * TIM2_Initialize ( ) *************************************************** * Initializes TIM2 generates interrupts every 1ms * SystemCoreClock = 168 MHz - set by SystemInit ( ) * Refer to Figure 134 of STM Reference Manual RM0090 * TIMxCLK = SystemCoreClock/2 * Hence ticks = 0.001 * 168,000,000 / 2 = 84,000 * Prescaler = 84-1; ARR = 1000-1; ***************************************************/ void TIM2_Initialize (void) { const uint16_t PSC_val = 84; const uint16_t ARR_val = 1000; /* En. TIM2 clk */ RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; TIM2->PSC = PSC_val - 1; /* set prescaler */ TIM2->ARR = ARR_val - 1; /* set auto-reload */ TIM2->CR1 = (1UL << 0); /* set command reg. */ TIM2->DIER = (1UL << 0); /* En. TIM2 IRQ */ NVIC_EnableIRQ(TIM2_IRQn); /* En. NVIC TIM2 Int. */ }
The many techniques that could be used to generate a sinusoidal waveform are the subject of the digital signal processing literature. A common approach is a direct method that stores the sinusoidal waveform samples in a look-up-table (LUT). This may seem very crude but if the output is passed through an analog low-pass filter with a cut-off frequency set to the fundamental frequency of the output signal, then the result is a reasonably pure sinusoid. In fact, this approach works equally well for a triangular waveform (which can be generated by the DAC hardware), but the LUT approach will produce something that looks convincing when displayed on an oscilloscope without the need for a filter.
In theory, the minimum number of samples needed is determined by the Nyquist-Shannon Sampling Theorem. This states that we need a minimum of two samples per cycle. At this limit the raw samples describe a 50 Hz square wave that will produce a sinusoid when processed by a suitable low-pass output filter. However, as an ideal square wave contains only components of odd-integer harmonic frequencies (of the form 2π(2k-1)f), the order of the filter will need to be ~12 so that the harmonics are highly attenuated while the fundamental is unaffected. To achieve a satisfactory output with a much simpler second-order filter, the number of samples is usually increased by a factor of ~10.
We store the samples in an array, as follows:
uint32_t dacLUT [] = {2047, 2680, 3250, 3250, 3994, 4095, 3994, 3703, 3250, 2680, 2047, 1414, 844, 391, 100, 0, 100, 391, 844, 1414 };
Then, we use a timer to generate an interrupt every 1 ms (that is, the period of the sinusoid T = 20 ms; 1/20 ms = 50 Hz.). Please note that we could use any timer (in this case, we use TIM2
; reusing code discussed previously but changing the prescaler value):
Uint16_t PSC_val = 84;
We write the sample to the DAC's Data Holding Register in the timer ISR (we postincrement idx
), as follows:
DAC->DHR12R1 = dacLUT[idx++];
To ensure the index is incremented by modulo 20 (because the LUT array stores 20 values), we use the following:
idx %= 20;
We output the idx
variable to the LEDs just to give a visual check that the program is running. A screenshot of an oscilloscope connected to PortA4 is shown as follows:
The lower trace shows the output (Vout) of the low-pass filter. The cut-off frequency for the low-pass filter is set to 50 Hz approximately, (refer to T. Floyd and D. Buchla, Electronics Applications Circuits Devices and Applications (8e), Pearson Education, 2014) which can be seen in the following figure:
3.147.72.74