## STM32F4: Generating a sine wave

Now that the basic peripherals of the STM32F4 have been configured, we can start to make them do useful things. The aim of this “mini-project” is to generate PSK31 modulated data. PSK31 is a very narrowband technique for sending data. Typically, it is used at VHF by ham radio operators, but we have adapted it in our recent IPSN2012 paper for use in underground mines using Magneto-Induction. At the moment, we are using the PWM to output analog values (after filtering), but it would be just as easy to use a DAC or the onboard CODEC.

We want to generate a sine wave at an arbitrary carrier frequency. The simplest way of doing this is to use a software Direct Digital Synthesis (DDS) technique. The basic concept is just to compute the wrapped phase at each sampling instant. In practice, what this means is that we use a free running sampling clock, say running at 100kHz to make life easy. Again, to make life easy, lets assume we wanted to generate a sine-wave with frequency 1kHz. 1kHz is equivalent to an angular frequency $\omega=2000\pi$ radians per second. Our sampling period is 10 us, so in each discrete time step, the phase is advancing by $\phi = 2000\pi \times 10^{-6} = 20 \pi \times 10^{-3}$ radians.

In general, for an arbitrary sampling frequency $f_s$ and required frequency $f_o$, the phase must increase by $R=2\pi \frac{f_o}{f_s}$ at each sampling instant. Because we are using fixed point numbers, and not reals (floating point), we let $2^{32} = 2\pi$. If we were using a 16 bit phase accumulator, then obviously change $2^{32}$ to $2^{16}$.  So all we need to calculate is: $R=2^{32}\frac{f_o}{f_s}$

The overall procedure is quite simple:

1. We keep a phase accumulator, which typically has a much higher resolution than the output DAC.
2. At each sampling instant, we increment the phase accumulator by $R$.
3. Sine of the current phase accumulator is then sent to the DAC.

The only “tricky” part in reality is scaling numbers from real (double) to integers to make things easier to compute. For speed, typically the angle-to-sine(angle) conversion is precalculated, in a lookup table (LUT). If you are really smart, you will note that only the first quadrant of the sine lookup needs to be specified, as the rest can be calculated through reflection or inversion. I am lazy, and the STM32 has tons of memory (it could actually easily calculate the true sine value on the fly), so I just precalculated the whole table.

The phase accumulator is stored as a 32 bit long. To get the 8 bit DAC value, the phase accumulator is shifted to the right by 24. This is then used as the index to the LUT.

The entire meat of the code lives in the Timer 2 ISR, which is called at a rate of 100 kHz. Code takes 300ns to execute, so it is taking about 3% of the STM’s brain power (clocked at 84MHz) to update this. This is obviously complete overkill just to generate a lowly sinewave, but it does it pretty well. Here is a screenshot of PD13 showing how long the processor takes in the ISR:

The code itself is simplicity itself, with the magic phase accumulator parameter R just being added on at each interval.

void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
GPIO_SetBits(GPIOD, GPIO_Pin_13);
PWM_SetDC(1,pulse_width);
// Calculate a new pulse width
phase_accumulator+=R;
angle=(uint8_t)(phase_accumulator>>24);
pulse_width = sinetable[angle];
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
GPIO_ResetBits(GPIOD, GPIO_Pin_13);
}
}


Before filtering, the PWM looks nothing like a sine wave! PWM output – you can see the pulse width reducing towards the right. PWM frequency is ~ 325kHz.

After filtering with a single pole RC (R = 22k, C = 2.2nF), things look a whole lot better, and it looks like a pretty decent attempt at a sinewave. In this case, we are generating a 2500 Hz sinewave, so the magic number R = 107374182.

If we zoom in, we can see that the sinewave is not perfect. This is due to the RC filter not rejecting the high frequency PWM, leading to a slightly jagged waveform. This can be addressed using a higher order filter, such as cascaded LC sections. However, for our application of generating a low frequency magnetic field (where the coil itself acts as an LC tank), this is adequate for now. For a £10 dev board and a few hours work, I would say that this is quite successful!

## STM32F4: USART

The nice thing about using the STLINK/V2 debugger inside IAR is that you can print values straight to the terminal watch screen, just by writing

printf("Ok\n");

. This works really well as a method to find out what is going on inside the mystery black box. To interface to the real world however, we need to use the USART. Again, after googling a bit and reading the very thick manual, its not too difficult to figure out. The code below

void USART_Configuration(void){
// sort out clocks
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
/* Configure USART2 Tx (PA.02) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Map USART2 to A.02
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
// Initialize USART
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
/* Configure USART */
USART_Init(USART2, &USART_InitStructure);
/* Enable the USART */
USART_Cmd(USART2, ENABLE);
}


sets up USART2 to output data at 9600 bps. To make life easy, we can “overload” the printf primitive to redirect stdio to USART2:

/**
* @brief Function that printf uses to push characters to serial port
* @param ch: ascii character
* @retval character
*/
int putcharx(int ch)
{
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, (uint8_t)ch);
return ch;
}


Now, when we type “printf(“Hello World\n”);”, it appears on the serial port instead of the watch terminal. Simple 🙂

Get the main listing.

## STM32F4: PWM

PWM output is great for things like motor control, dimming or in my case, a poor-man’s DAC. As long as the PWM frequency is significantly larger than the fundamental to be generated, a suitably filtered output is often good enough for many purposes. Here, we show how to implement a four channel PWM, driven from a common timebase provided by Timer 3.

First, the GPIOs are configured and assigned to the PWM OC (output compare) alternative functions. PWM is output on PC6, PC7, PB0 and PB1. The clock signal is also routed to Timer 3.

void TIM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/* TIM3 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

/* GPIOC and GPIOB clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOB, ENABLE);

/* GPIOC Configuration: TIM3 CH1 (PC6) and TIM3 CH2 (PC7) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOC, &GPIO_InitStructure);

/* GPIOB Configuration:  TIM3 CH3 (PB0) and TIM3 CH4 (PB1) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOB, &GPIO_InitStructure);

/* Connect TIM3 pins to AF2 */
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_TIM3);
}


Next, the PWM timer itself is configured and the duty cycle registers for each OC channel setup. The integer period passed to the function sets the overall period of the timer. The dutycycle is given as $\frac{CCRn}{Period}$.

void PWM_Config(int period)
{
uint16_t PrescalerValue = 0;
/* Compute the prescaler value */
PrescalerValue = (uint16_t) ((SystemCoreClock /2) / 28000000) - 1;
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = period;
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
/* PWM1 Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
/* PWM1 Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
/* PWM1 Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
/* TIM3 enable counter */
TIM_Cmd(TIM3, ENABLE);
}


Now the timer is setup and happily. To provide an easy way to modify the duty cycle for each channel, the following helper function is used. I am sure it could be made a lot cleaner, but as a first start, it works ok:

void PWM_SetDC(uint16_t channel,uint16_t dutycycle)
{
if (channel == 1)
{
TIM3->CCR1 = dutycycle;
}
else if (channel == 2)
{
TIM3->CCR2 = dutycycle;
}
else if (channel == 3)
{
TIM3->CCR3 = dutycycle;
}
else
{
TIM3->CCR4 = dutycycle;
}
}


Fire it up and the duty cycle on Channel 1 ramps up from 0% to 100%, producing a triangular waveform.

## STM32F4: Timer

This shows how to make a simple interval timer that toggles an LED (the orange LED on the discovery board) everytime the counter overflows. It uses Timer 2 and busy waits to see if the overflow flag has been set. If set, then it toggles the LED. Thus the frequency of the LED is half the frequency of overflow.

The main loop is straightforward: the timer is first started, then the LEDs on the GPIO pins. It then enters an infinite loop, toggling PD13 whenever the flag is raised.

int main(void)
{
INTTIM_Config();
/* LED Configuration */
LED_Config();
while (1)
{
if (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) != RESET)
{
TIM_ClearFlag(TIM2, TIM_IT_Update);
GPIO_ToggleBits(GPIOD, GPIO_Pin_13);
}
}
}


Once you have had a go at reading the insanely long specification documents, it is quite easy to set up Timer2. The STM32 timers are incredibly flexible in terms of their prescaling – a lot of microcontrollers only give you a few options, whereas here you can divide by any integer (16bit?). You then set the period to dictate how often it rolls over – here we first divide down to 1MHz using the prescaler and then by using a period of 100, we get down to 10kHz. If you want to visibly see the LED flash, change this from 100 to 10000 or something similar. I was on the scope, so no issue with watching flashing lights.

void INTTIM_Config(void)
{
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 100 - 1; // 1 MHz down to 10 KHz (0.1 ms)
TIM_TimeBaseStructure.TIM_Prescaler = 84 - 1; // Down to 1 MHz (adjust per your clock)
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}


## STM32F4: Interrupt Timer

Rather than using a busy-wait approach to timing, as shown in a previous post here, it is much more efficient to make this interrupt driven. Adding an interrupt involves configuring the NVIC (Nested Vectored Interrupt Controller). In addition, the flags used are different – the IT flags are used instead. This is different to a lot of other micros, where the same flag is used to trigger and clear an interrupt.

The interrupt handler is straightforward – it checks for the interrupt source (TIM_IT_Update) and if that is SET, then it toggles the LED. Unlike other micros, the flag is not automatically cleared, so must be cleared manually (at least, this is my current view of things).

void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
GPIO_ToggleBits(GPIOD, GPIO_Pin_13);
}
}


The main loop is even simpler than before, it just configures the timer and ports and then spins around in an infinite loop. Configuring the timer is similar to before, but has a couple of extra steps to setup the interrupts and tie the timer to an interrupt:

void INTTIM_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the TIM2 gloabal Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 1 MHz down to 1 KHz (1 ms)
TIM_TimeBaseStructure.TIM_Prescaler = 84 - 1; // 24 MHz Clock down to 1 MHz (adjust per your clock)
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* TIM IT enable */
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}