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!

Sonntag, 30. Juni 2019

Exploring the nRF52-DK Part 1 - Timers

This first post dives into one of the most fundamental peripherals of any microprocessor: The timer. Let us examine this part very closely. All the information was gathered from looking through an example file and the datasheet for the nRF52832.

The code with the examples can be found in the following github gist: https://gist.github.com/kschoos/535c6703ddfd06e26c5c1092d8513df4

To start, we need to find an example that makes use of the timer. After some digging I found the peripherals/ppi example to be a good candidate. Reading through the main function and going from there shows the general pattern that we should follow if we were to use this peripheral:

- Generate timer config (This looks interesting so we make a mental note)
- Initialize timer with that config and pass event handler callback
- Convert a period in ms to a number of ticks (A quick glance at the function reveils that it does what we'd expect)
- Configure the compare register (This also looks interesting)
- Enable the timer


The event handler is just a function that is called every time a certain event is triggered by the timer. We will see this in more depth shortly.

Now that we have a broad overview, let's dig deeper into the different bits. The constant NRFX_TIMER_DEFAULT_CONFIG pops into view and it's interesting to see what a "default config" for such a timer contains. We follow its definition and see that it is made up of more constants that are provided by the sdk_config.h.

The first parameter is the frequency. The maximum frequency is 16 MHz  and it can be reduced by setting this parameter. Interesting to note is that, when a frequency below or equal to 1MHz is chosen, the slower CLK PCLK1M (1 MHz internal clock)  is chosen instead of PCLK16M (16 MHz internal clock), in order to reduce power consumption. This should be kept in mind when choosing the right timer values for certain tasks.

Next, mode can be either Timer or Counter. When mode is configured to be "Timer", the module acts like an actual timer. The timer's internal counter register is incremented for every tick. The time between ticks is the period given by the timer frequency. However, when setting the mode to "Counter", the internal counter register is incremented whenever the COUNT-task is triggered. We will see an example of both.

Bit width sets the width of the variable that represents the number of ticks that have passed. This can be 8, 16, 24 or 32 bit wide. We can easily compute the largest timeline that these constraints allow. For example 2^32 / 31.25 kHz = 137439 seconds (38 hours and 10 minutes) is the longest possible timeline when using the slowest frequency (remember, that this puts constraints on the accuracy of the timer). When using the full 16MHz we only get 4 minutes and 28 seconds worth of timeline. We can extend these timelines by keeping track of time itself, updating a variable in software whenever an the timer is about to overrun.

IRQ priority sets the interrupt priority. Nested interrupts are a thing in ARM Cortex-M4, so the priority indicated, by which other interrupts the interrupt service routine for timer interrupts can be ... interrupted.

Now that we know the settings, which describe a timer, let us see what this mysterious nrf_drv_timer_extended_compare function is doing.

Every timer in the nRF52 has multiple capture/compare registers associated with them. Timers 0 to 2 have 4, timers 3 and 4 have 6 so called CC-registers. The concepts of these functionalities is straight-forward and will be briefly explained here:

"Capture" means, that the current value of the timeline is written into the specified CC-register. Capturing can be used for example for time-stamping certain events and interrupts or for precisely measuring the time between two instants, as setting a capture task has such a small amount of overhead. In order to use a certain CC-register in capture mode, simply call the function nrfx_timer_capture with the timer and channel you are interested in. This will trigger the CAPTURE-Task and return the captured value. The function nrfx_timer_capture_get is a wrapper that simply returns the captured value for a certain channel. This function should be used when you do not want to capture the value again, but just want to read from it. Whenever the CAPTURE-task is triggered via the PPI (Programmable Peripheral Interface), this function must be used, as we don't want to trigger the CAPTURE-Task again when reading the value.

"Compare" means, that an event occurs whenever the timeline is equal to the value in that CC-register. The timer counts up to the value specified in the CC-register and triggers an interrupt when it reaches that value. This can be useful when you want periodic things to happen, such as an LED blinking or a sensor measurement being taken. However, when implementing such functionality, make sure to keep your ISRs as short as possible and defer all complex processing to later times. Timer interrupts  are also used by preemptive schedulers: A timer interrupt occurs every X milliseconds and checks which task is supposed to run next. To setup a compare timer interrupt, simply call nrfx_timer_compare or nrfx_timer_extended_compare with a timer instance, an appropriate channel, the value at which the interrupt should be triggered as well as a flag, if interrupts should be enabled or disabled. In addition, nrfx_timer_extended_compare also takes an nrf_timer_short_mask_t can be added, which allows to specify that the CLEAR- and / or the STOP-Task should be executed, whenever the corresponding COMPARE-event is raised.

The index of the capture/compare register is equivalent to what is called "channel" in the example code.

Now that we have a good overview of what these timers are capable of, it's time to play around with them. I created three small examples that showcase what we have learned. The gist of these examples is obviously not that this can't be done differently, but it's fun to tinker with these kind of things to deepen the understanding.

The examples are based on the example project peripherals/bsp in the nRF5 SDK, because it already includes button and LED initialization as well as UART logging. The only thing we need to import is nrfx_timer.h and nrfx_timer.c. In addition, some fields in the sdk_config.h must be added and reconfigured (The part found under "nrfx_timer - TIMER peripheral driver"). After this is done, our playground is ready for action.

There are 3 tasks implemented, which can be cycled through by pressing Button 1 on the nRF52-DK.

Task 1:
The first task creates a single counter. Whenever Button 0 is pressed, the counter is incremented by 1 and the value of the counter is displayed in form of its bits by the LEDs. When the compare value of 16 is reached, the counter is supposed to be resetted. This is achieved by setting the mask to NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK in the call to nrfx_timer_extended_compare. The timer is now cleared every time the compare value of channel 0 is reached.


Task 2:
In the second task, we create another timer. This timer increments the first timer (the counter), every time it reaches a certain compare value. The compare value is computed, such that it takes exactly 1 second between events. The result is the display of a binary number which is incremented every second.


Task 3:
The final task contains two timers. The timer from task 2 enables the new timer every time it hits its compare value. The second timer has several compare events attached to it. The datasheet tells us, that we need to use timer 3 or 4 if we need more than 4 compare registers. Instead of computing the compare values ourselves, we use the function nrfx_timer_ms_to_ticks, which does the computation for us, based on the frequency of the timer. Note that we only set the masking shortcut for the last compare registers: We only want to reset the timer once it cycled to the end. We finally disable the timer inside of its own event handler.


This wraps up this first post on timers in the nRF52-DK. We will come back to timers again and again, as they are vital for all kinds of embedded devices.