Mittwoch, 21. August 2019

Generating sinewaves using PWM - Sine pulse width modulation with an STM32 Cortex M0 device

Often times, harmonic signals, such as sinewaves are needed. However, many microcontrollers do not feature a digital to analog converter (DAC) and therefore lack the ability to output analog voltages directly. However, practically every microcontroller comes with a pulse width modulation (PWM) output, or at least have a hardware timer which can be programmed to create a PWM signal. The idea behind sine pulse width modulation (SPWM) is, that pulses are generated whose duty cycle corresponds to the amplitude of a sinewave at that point in time, with a given frequency. The PWM signal is then smoothed with a capacitor and amplified to a voltage of interest using op-amps or a simple transistor amplifier.

The idea is to have a look-up-table with a predefined number of sine-wave values. The pulse width of the next pulse is then set to the current value according to that look-up-table during the interrupt service routine. The STM32 HAL provides a callback function that can be implemented, called HAL_TIM_PWM_PulseFinishedCallback. This callback is called during an interrupt service routine. The logic for computing the next index is outsourced to another timer. This timer calls the afformentioned interrupt handler every time a period is done.

First, we have to setup the timers. For that, we will use the STM32 CubeIDE, which provides a nice visual interface to do these things. We setup timer 1, channel 1 to PWM Generation and channel 2 to Output Compare. The Prescaler is set to 1 and the Counter Period is set to PERIOD (Remember to choose "No Check" in order to be able to set a non-hex value).

The look-up-table could for example look as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#define PERIOD 32
#define DC_OFFSET (PERIOD >> 1)
#define PERIOD_FRACTION(X) ((uint16_t) ((X) * (PERIOD >> 1)))

const uint16_t sin[] = {
  DC_OFFSET + 0,
  DC_OFFSET + PERIOD_FRACTION(0.500),
  DC_OFFSET + PERIOD_FRACTION(0.866),
  DC_OFFSET + PERIOD_FRACTION(1),
  DC_OFFSET + PERIOD_FRACTION(0.866),
  DC_OFFSET + PERIOD_FRACTION(0.500),
  DC_OFFSET + 0,
  DC_OFFSET - PERIOD_FRACTION(0.500),
  DC_OFFSET - PERIOD_FRACTION(0.866),
  DC_OFFSET - PERIOD_FRACTION(1),
  DC_OFFSET - PERIOD_FRACTION(0.866),
  DC_OFFSET - PERIOD_FRACTION(0.500),
};

The values used will be presented shortly:

- PERIOD: corresponds to the number of ticks the timer counts before it overflows. Having a lower period enables the generation of higher frequencies.

- DC_OFFSET: As we can only generate pulses with a width greater or equal to 0, we have to add half of the period as dc offset.

- PERIOD_FRACTION: A macro to compute the width of the pulse from a decimal value.

- sin[]: The actual look-up-table with the sine-function samples. The number of samples should be as low as possible in order to keep the code-size small. In addition to that, the number of samples also dictates the maximum frequency that can be generated, as every value must be held for at least one period. Finally, a small value decreases the resolution and the reconstructability of the sinusoidal shape. The table shown is the one that I settled for after a lot of trial and error. One could also obviously get away with only using a quarter period, however that would increase the computation time inside of the ISR, so a time-space-trade-off must be made.

The implemented callback functions look as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void updatePWMPulse(uint16_t value){
   htim1.Instance->CCR1 = value;
}

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim){

 if(htim == &htim1 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2){
  updatePWMPulse(sin[step]);
  step = (step + 1) % num_steps;
 }
}

As STM32 Timers only have a single set of these callback functions, we need to manually check if we were called by the one timer and channel of interest. Of so, we simply set the pulse for the PWM signal and increase the step.

Note, that this is channel 2, which corresponds to a simple compare timer, which triggers every time a period ends. There are a number of ways to do this, though.

In our main function, we have to start the two channels of the timer:


1
2
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
  HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_2);

And that's it for basic SPWM generation. Generating different frequencies can be done by either changing the period length, the prescaler or the number of sine-wave samples.

Mittwoch, 14. August 2019

Getting my feet wet with TinyFPGA

In this write-up, I am going to document my first steps using an FPGA.

The device that was used is a TinyFPGA BX, featuring a Lattice iCE40LP8K. This FPGA features 7680 logic elements, so called LUT-4. This means that every programmable logic element is a 4-input look up table that can take on any possible 4-input logic function. This LUTs are the basic logic elements (BLEs) of the FPGA. Several BLEs are combined to form a configurable logic block (CLB), which is at the basis of the configurability of FPGAs. The internal frequency of the FPGA is 16MHz.

A great resource for FPGA architecture can be found under this link: FPGA Architecture.

The first thing was to install everything according to this link: TinyFPGA Setup
After the installation completed successfully, and the first program was synthesized on the FPGA, I could see the LED flashing according to an SOS pattern, just as the tutorial promised.

After reading a bit about Verilog, the language used in the TinyFPGA, I wanted to create my own first program. The idea of the program was a simple counter, that would get incremented whenever a button was pressed. The counter was rather simple:

module counter(
  trigger,
  out
  );

  input trigger;
  output out;

  wire trigger;
  reg out;

  reg [2:0] counter;

  initial begin
    out <= 0;
    counter <= 0;
  end

  always @(posedge trigger) begin
    if (counter == 7) begin
      counter <= 0;
      out <= 1;
    end
    else begin
      counter <= counter + 1;
      out <= 0;
    end
  end
endmodule

It's a small module that contains a 3-bit wide register. This register is incremented on every positive edge of the trigger input. Whenever it hits the maximum value of 7 (3'b111 in Verilog-speak), the counter outputs a 1 and resets the counter to 0. Otherwise, the counter is just incremented and it outputs a 0.

Now I hooked up a button to one of the IO-Pins of the FPGA with a pull-up resistor and hooked the IO-Pin to the trigger of the counter. However, the mindful reader may have noticed that this will obviously not work. The reason for this is, that the button is not debounced.

Looking up a way to debounce a circuit in an FPGA, I came across this site: Debounce

Using the blockdiagram, I implemented the debounce logic in Verilog and tested it with a testbench.
The final result was the following code:

module button(
  in,
  clk,
  out
  );

  input clk;
  input in;
  output out;

  reg step_1;
  reg step_2;
  reg counter_clr;
  reg [19:0] counter;
  reg out;

  initial begin
    step_1 = 1'b0;
    step_2 = 1'b0;
    counter_clr = 1'b0;
    counter = 4'b0;
    out = 1'b0;
  end

  always @(posedge clk)
  begin
    step_1 <= in;
    step_2 <= step_1;
    counter_clr <= step_1 ^ step_2;

    if (counter_clr)
      counter <= 0;
    else if (counter < (1 << 19))
      counter <= counter + 1;
    else begin
      counter <= counter;
      out <= (counter == (1 << 19)) ? step_2 : out;
    end
  end
endmodule

The basic idea behind this logical circuit is, that there is a counter, which is cleared every time the input signal transitions from high to low or vice-versa. The output of the circuit is only set to the input value (or rather the input value one timestep ago), once the timer reaches a certain value.

The value that was needed for this was computed as follows:
First, the button was "clicked" several times, and the signal was measured with a logic analyzer. The minimum value for the time the button signal showed 0V was measured. This value was divided by two for good measure, then multiplied by the clock frequency of 16 MHz, giving the number of cycles. Finally, the closest power of 2 was used as a counter value, which turned out to be 219.

These two modules were bundled together in the top module:


module top(
  LED,
  PIN_2,
  CLK
  );

  output LED;
  input PIN_2;
  input CLK;
  reg OUT;

  button btn2(
    PIN_2,
    CLK,
    OUT
    );

  counter cnt(
    OUT,
    LED
    );
endmodule



The output of the debounced button is directly fed to the trigger input of the counter.
The output of the counter is then used to light an LED.

Now, clicking the button 8 times triggers the onboard LED!

I hope you found this first dabbling in FPGA synthesis interesting.
Please leave a comment and let me know about your thoughts!