Skip to main content

Pulse Width Modulation is a technique to vary the duty cycle of a signal while keeping it's frequency constant. For example:

duty cycles from https://learn.sparkfun.com/tutorials/pulse-width-modulation

This turns out to be a very handy thing to be able to do. So much so that it's a basic feature in most, if not all, MCUs.

LED brightness

The most basic use is to control the brightness of an LED. Here is a clip of the four onboard LEDs being controlled by PWMs. They're all doing the same thing, so it's not the most exciting. But it proves the point.

The requisite constants

(define pin-mode-alternate 2)
(define otype-pushpull  0)
(define ospd-highspeed 3)
(define pupd-none     0)
(define gpio-af-tim4 2)
(define timer-reg-cr1 #x00)
(define timer-reg-ccmr1 #x18)
(define timer-reg-ccmr2 #x1C)
(define timer-reg-ccer #x20)
(define timer-reg-psc #x28)
(define timer-reg-arr #x2C)
(define timer-reg-ccr1 #x34)
(define timer-reg-ccr2 #x38)
(define timer-reg-ccr3 #x3C)
(define timer-reg-ccr4 #x40)
(define rcc-reg-apb1enr (* -1 #x40))

Not that these registers are 16-bit so we can just use integer values and don't have to use vectors (as you may recall, Armpit uses 30-bit integers so if we need the top two bits we need to use byte vectors).

Configuration

(define (init-gpio-pins)
  (config-pin giod 12 pin-mode-alternate otype-pushpull ospd-highspeed pupd-none gpio-af-tim4)
  (config-pin giod 13 pin-mode-alternate otype-pushpull ospd-highspeed pupd-none gpio-af-tim4)
  (config-pin giod 14 pin-mode-alternate otype-pushpull ospd-highspeed pupd-none gpio-af-tim4)
  (config-pin giod 15 pin-mode-alternate otype-pushpull ospd-highspeed pupd-none gpio-af-tim4))


(define (config-pwm)
  (write #vu8(#x04 #x00 #x00 #x00) rcc rcc-reg-apb1enr)
  (write #x0000 tmr4 timer-reg-psc)
  (write 255 tmr4 timer-reg-arr)
  (write #x0080 tmr4 timer-reg-cr1)
  (write #x6868 tmr4 timer-reg-ccmr1)
  (write #x6868 tmr4 timer-reg-ccmr2)
  (write #x1111 tmr4 timer-reg-ccer)
  (write #x0081 tmr4 timer-reg-cr1))

GPIO PD12-15 are configured to be connected to the output of timer 4's comparitors.

Timer 4 is configured to receive a clock signal from the RCC, repeatedly count from 0 to 255, and drive the output high when the count exceeds the comparison value.

Main loop

(define (take-time n)
  (if (> n 0)
      (begin (* 3 5 7)
             (take-time (- n 1)))))

(define (pwm)
  (config-pwm)
  (init-gpio-pins)
  (let loop ((brightness 0)
             (step 1))
    (write brightness tmr4 timer-reg-ccr1)
    (write brightness tmr4 timer-reg-ccr2)
    (write brightness tmr4 timer-reg-ccr3)
    (write brightness tmr4 timer-reg-ccr4)
    (take-time 1000)
    (cond ((eq? brightness 255)
           (loop (+ brightness -1) -1))
          ((eq? brightness 0)
           (loop (+ brightness 1) 1))
          (else
           (loop (+ brightness step) step)))))

The main function initializes the GPIO pins and timer 4, then enters an endless loop: the current brightness value is written to the 4 channels of timer 4, there's a delay so the effect is slow enough to be visible * the brightness value is checked against each bound (0 & 255), if it's hit the bound the direction is reveresed, otherwise it continues to the next value in the same direction.

RGB

LEDs are available in a variety of shapes and sizes, but most relevant to this post: color. Of particular are red, green, and blue LEDs. Why? because of how light works. These are the tree primary colors of light and can be combined is various proportions to create any other color. If we take an LED of each of these three colors and put them close together our eyes will blend the three into a single color. Very handy.

All we need to do is drive each of the three with a PWM and we can create any color we desire. Well, almost any. The actual number is the product of the number of possible brightnesses of each LED. If we use 8 bit PWMs then each has 256 possible brightness levels. That means we can make 256 * 256 * 256 = 16,777,216 possible colors. In theory. In reality our eyes can't differentiate that level of nuance. Still, plenty of colors.

Here's an RGB LED being driven through a variety of colors. The LED is in the keyswitch.

The ideas behind this code are much the same. You configure the GPIO pins and the timer. In this example I'm using PWM channels 1, 2, & 3 of timer 3, connected to PB4, PB5, and PB0, respectively.

The LED circuit is pretty much identical to the one in the ADC post other than the specific GPIO pins being used.

I'll spare you the configuration details, as they are almost identical to the previous example. You can check the repo for details. I wanted to control the three colors independently rather than in sync like the previous example so the main loop is quite different:

(define steps '((1 0 0)
                (0 1 0)
                (0 0 1)
                (-1 0 0)
                (0 -1 0)
                (0 0 -1)))

(define (rgb)
  (config-pwm)
  (init-gpio-pins)
  (write 0 tmr3 timer-reg-ccr1)
  (write 0 tmr3 timer-reg-ccr2)
  (write 0 tmr3 timer-reg-ccr3)
  (let loop ((red 0)
             (green 0)
             (blue 0)
             (step-list steps)
             (step-count 0))
    (write red tmr3 timer-reg-ccr1)
    (write green tmr3 timer-reg-ccr2)
    (write blue tmr3 timer-reg-ccr3)
    (take-time 500)

    (if (eq? step-count 255)
        (let ((remaining-steps (cdr step-list)))
          (if (null? remaining-steps)
              (loop red green blue steps 0)
              (loop red green blue remaining-steps 0)))
        (let ((step-deltas (car step-list)))
          (loop (+ red (car step-deltas))
                (+ green (cadr step-deltas))
                (+ blue (caddr step-deltas))
                step-list
                (+ step-count 1))))))

The steps constant contains a list of 3-tuples that contain delta values for red, green, and blue. For example, the first one will increment the red value while leaving green and blue unchanged. Each tuple takes 255 times through the loop. Once that's happened it moves to the next tuple. Once all tuples have been used, it starts again at the beginning.

Code is available on Bitbucket.

Twitter Facebook Pinterest LinkedIn

By now I'd controlled an LED and read a digital input. The next step was to read analog values. The STM32F407 on the Disco board has 3 analog to digital convertors (ADCs), which can be used to read from 19 different sources: 16 external sources, two internal sources, and the battery voltage. I just used 1, so configuration is straight forward.

For an anlog source I used a variable resistor with its ends connected to ground and +5 volts. The tap gave a voltage between those extremes that was then connected to an analog input. I used PA0 which connects to source 0. I configured ADC1 to read that source.

Here are the constants. Some have been seen before, and there are new ones for the ADC:

(define pin-mode-in        0)
(define pin-mode-out       1)
(define pin-mode-alternate 2)
(define pin-mode-analog    3)

(define otype-pushpull  0)
(define otype-opendrain 1)

(define ospd-low       0)
(define ospd-medium    1)
(define ospd-fast      2)
(define ospd-highspeed 3)

(define pupd-none     0)
(define pupd-pullup   1)
(define pupd-pulldown 2)

(define adc-reg-sr   #x00)
(define adc-bits-eoc #x00000002)
(define adc-reg-cr2  (* -1 #x08))
(define adc-reg-dr   #x4C)
(define adc-reg-ccr  (* -1 #x0304))

(define rcc-reg-apb2enr (* -1 #x44))

Notice that some of the reg constants (which are register offsets from the base address of adc1) are negative. This denotes to the read and write function that the value read or written should be a byte array (of 4 bytes) instead of a 30-bit integer.

(define (init-adc channel-base)
  (config-pin gioa 0 pin-mode-analog otype-pushpull ospd-medium pupd-pulldown)
  (write #vu8(#x00 #x01 #x00 #x00) rcc rcc-reg-apb2enr)
  (write #vu8(#x00 #x03 #x01 #x00) adc1 adc-reg-ccr)
  (write #vu8(#x01 #x00 #x00 #x00) channel-base adc-reg-cr2))

First the PA0 pin to configured for ADC use. Then the ADC1 clock is enabled. Next ADC1 is configured. Notably DMA is disabled, it's set to independent channels mode, and set to use a prescaler divisor of 4 and a sampling delay 0 8 cycles and 12 bits samples. Most of the settings I needed were defaults, so the configuration actually required was pretty limited. Finally ADC1 is enabled.

To take a reading I wrote another function, along with a couple helpers:

(define (check-bit base register bit)
  (not (zero? (bitwise-and (read base register) bit))))

(define (conversion-done? channel-base)
  (check-bit channel-base adc-reg-sr adc-bits-eoc))

(define (take-reading channel-base)
  (write #vu8(#x01 #x00 #x00 #x40) channel-base adc-reg-cr2)
  (let loop ((time-remaining #xfff))
    (cond ((zero? time-remaining)
           (display "Reading timed out")
           (newline)
           -1)
          ((conversion-done? channel-base)
           (read channel-base adc-reg-dr))
          (else
           (loop (- time-remaining 1))))))

This is straight forward: initiate the conversion by setting the swstart bit in the cr2 register, then loop until the end of conversion bit flips to a high state. I also added a timeout so that it will give up if it doesn't see a conversion in a reasonable amount of time. When a conversion completes successfully, the value is read from the dr register (i.e. data register) and returned.

To show that the ADC code is working, I initially just output the converted value to the console. However, that's not very interesting so I hooked up 4 LEDs and controlled them based on the value. Here's the circuit:

I had to initialize those GPIO pins so I put that in a function:

(define (init-leds)
  (config-pin giod 0 pin-mode-out)
  (config-pin giod 1 pin-mode-out)
  (config-pin giod 2 pin-mode-out)
  (config-pin giod 3 pin-mode-out))

Here's the main function:

(define (monitor-adc)
  (let ((channel-base adc1))
    (init-adc channel-base)
    (init-leds)
    (let loop ()
      (take-time 10000)
      (let ((result (take-reading channel-base)))
        (if (> result -1)
            (new-adc-result result)))
      (loop))))

This function initializes the system, then goes into an infinite loop which takes a reading and, if it is valid, passes it to the new-adc-result function:

(define (new-adc-result val)
  (display "ADC reading: ")
  (display val)
  (newline)

  (let ((percent (/ (* 100 val) 4095)))
    (pin-clear giod 0)
    (pin-clear giod 1)
    (pin-clear giod 2)
    (pin-clear giod 3)
   (cond ((< percent 25) (pin-set giod 0))
          ((and (>= percent 25) (< percent 50)) (pin-set giod 1))
          ((and (>= percent 50) (< percent 75)) (pin-set giod 2))
          ((>= percent 75) (pin-set giod 3)))))

First, the reading is output to the console. Then it's used to compute a percentage (12 bits of resolution gives a maximum value of 4095). All LEDs are turned off and one is turned on based upon the range into which the percentage value falls.

Here's what it looks like.

Code is available on Bitbucket.

Twitter Facebook Pinterest LinkedIn

In the last posts I showed how to control LEDs with the STM32F4-Disco board using ArmPit Scheme. In this port, I read some input.

The circuit

Specifically, I hooked a switch to a GPIO pin and read its state. Then I used that to control a LED hooked to another GPIO pin. To keep is simple, I used the same LED as in the last post, adding a switch.

The code

For the input line, I wanted an internal pullup resister, so I needed a few more constants. Here's all the options for the config-pin function

(define pin-mode-in 0)
(define pin-mode-out 1)
(define otype-pushpull 0)
(define otype-opendrain 1)
(define ospd-low 0)
(define ospd-medium 1)
(define ospd-fast 2)
(define ospd-highspeed 3)
(define pupd-none 0)
(define pupd-pullup 1)
(define pupd-pulldown 2)

I didn't need any utility functions as the logic is simple. So here's the main function:

(define (light-switch)
  (let ((port giod)
        (led-pin 10)
        (switch-pin 9))
    (config-pin port led-pin pin-mode-out)
    (config-pin port switch-pin pin-mode-in 0 0 pupd-pullup)
    (let loop ()
      ((if (pin-set? port switch-pin) pin-clear pin-set) port led-pin)
      (loop))))

The main loop continuously reads the switch and turns the LED on or off based on whether the switch is pressed (indicated by a low signal and thus a false value).

Code is available on Bitbucket.

Twitter Facebook Pinterest LinkedIn

Once ArmPit Scheme was in place and running, and manipulating the onboard LEDs, it was time to grab some components, wires, and a breadboard and get hacking.

The wiring

My first project was simple: blink an external LED. Unlike Arduino boards, the Disco board has male pin headers, not female. This means that you can't just use the usual wire jumpers to hook to it. You need a female end. The usual hardware hacking supply shops carry jumper wires in male-male, male-female, and female-female varieties. Make-female would have been ideal for connecting to a bredboard, bit I just have male-male and female-female.

female-female jumpers

Fortunately, I also have some long pin header strips that are great for connecting these jumpers to a breadboard.

The circuit

Connecting a LED is, as mentioned above, simple. One resister connected to a GPIO pin (I used PD10) then a LED connected from the other end of the LED to ground. Be sure to get the LED right way round or it won't light... being a diode and all.

The code

First some utility constants and functions

(define pin-mode-out 1)

(define (toggle port pin)
  ((if (pin-set? port pin) pin-clear pin-set) port pin))

(define (take-time n)
  (if (> n 0)
      (begin (* 3 5 7)
             (take-time (- n 1)))))

And the main function which toggles PD10 and waits a bit before looping. I added a count to it would do this 10 times and exit.

(define (blink)
  (let ((port giod)
        (pin 10))
    (config-pin port pin pin-mode-out)
    (let loop ((c 10))
      (toggle port pin)
      (take-time 100000)
      (if (zero? c)
          #f
          (loop (- c 1))))))

Code is available on Bitbucket.

Twitter Facebook Pinterest LinkedIn

In my new position as Director of Innovation at SteelSeries I've been doing plenty of hardware hacking and prototyping. I've done some work with Arduino, and more recently Teensy3.1. Now I'm playing around with The STM32F4 Discovery board from ST Micro.

The STM32F4Disco is centered around an STM32F407VGT6 MCU that has an ARM Cortex-M4 core with a wealth of peripherals along with 1M of flash and 192K of ram. That's plenty of room for doing some interesting things... like run a Lisp. Specifically ArmPit Scheme.

ArmPit is a fairly complete R6RS implementation of Scheme, with extensions for low level access and Flash/SD files. It supports a wide variety of ARM based boards.

First you have to get ArmPit on your Arm board. Go to the ArmPit site and follow the links to download the latest version. Have a look at the HowTo page for instructions on uploading Armpit to your board. For the STM32F4-Disco this means using the onboard ST-Link interface. After flashing ArmPit onto the board you will get a REPL prompt on the user USB port. You'll need some sort of terminal emulator for this. I'm using Serial.app.

Without doing any hardware hacking, here's an example that manipulates the on-board LEDs.

Magic numbers are bad, so here are some GPIO configuration constants:

(define pin-mode-in 0)
(define pin-mode-out 1)
(define pin-mode-alternate 2)
(define pin-mode-analog    3)

(define otype-pushpull 0)
(define otype-opendrain 1)

(define ospd-low 0)
(define ospd-medium 1)
(define ospd-fast 2)
(define ospd-highspeed 3)

(define pupd-none 0)
(define pupd-pullup 1)
(define pupd-pulldown 2)

The M4 also has a true random number generator, which is incredibly handy. For this demo I'll be using it:

(define (make-frndu seed)
  (lambda ()
    (/ (bytevector-s32-native-ref (ash (RNG seed) -2) 0) 5.36870911e8)))

(define r (make-frndu #vu8(1 0 0 0)))

And a couple functions to toggle a output pin and do a bit of a delay:

(define (toggle port pin)
  ((if (pin-set? port pin) pin-clear pin-set) port pin))

(define (take-time n)
  (if (> n 0)
      (begin (* 3 5 7)
             (take-time (- n 1)))))

Here's the main function. It configures the GPIO pins connected to the onboard LEDs, then loops infinitely (well, until you press control C) generating a random number between 0 & 100 and toggles LEDs based on that value.

(define (blink)
  (config-pin giod 12 pin-mode-out)
  (config-pin giod 13 pin-mode-out)
  (config-pin giod 14 pin-mode-out)
  (config-pin giod 15 pin-mode-out)
  (let loop ((n (* 100 (r))))
    (if (> n 25) (toggle giod 12))
    (if (> n 50) (toggle giod 13))
    (if (> n 75) (toggle giod 14))
    (if (> n 90) (toggle giod 15))
    (take-time 10000)
    (loop (* 100 (r)))))

And here's what that looks like.

Code is available on Bitbucket.

Twitter Facebook Pinterest LinkedIn