PWM on the USBee with custom firmware

In this article I described how to use the signal generator firmware to send out PWM signals generated on the PC. However, there is a better way. The USBee SX is based on a Cypress FX2lp, a microcontroller for which the firmware must be sent every time the device is connected. This gives an excellent oppertunity to experiment with custom firmwares that do fun stuff, such as driving led effects independently from the host...

The 8051 compatible instruction set can be programmed directly using open source tools (SDCC) with a header file describing the Special Function Registers (SFRs) specific to the platform (fx2regs.h). Warning: some of the fx2regs.h files floating around produce blatantly wrong code when used with SDCC, so be careful. The one that I used is written espcially for SDCC (by Christer Weinigel), and licensed under a permissive (X11/MIT) license.

Another thing that is important to know for the USBee SX is that PORTB is connected to the output pins, bit 0 is connected to pin 0 and so on. All pins can be configured either as input or as output.

In my bitbucket reposity I put a Python script to upload and launch arbitrary firmware: exec.py. The firmware doesn't need to do anything with USB, it can simply go into a loop driving the outputs. I've also included an example firmware that blinks a led slowly using PWM plus a timer interrupt to set the level.

The core of the PWM generation code is a simple infinite loop that outputs a 1 if the counter is smaller than the defined level, and a 0 otherwise. The counter wraps at 255, making sure that level defines the part of the time that a 1 is output. Note that it is impossible to entirely disable the led here. Even if level=0, the led will be on 1/256th of the time.

    BYTE counter;
    while(1)
    {
        if(counter <= level)
            IOB = 0x01;
        else
            IOB = 0x00;
        counter += 1;
    }

Of course, keeping the level the same all the time is boring. If we want to change the value in time, a timer interrupt comes in handy:

void timer_overflow (void) interrupt 1 /* Timer 0 overflow */
{
    TH0   = timer_h;
    TL0   = timer_l;

    level += 1;
}

When the timer interrupt triggers, which happens when the timer counter overflows after 0xFFFF, this code restores the value of the timer counter word, and increases the brightness level. In this way, we can determine the blinking speed with [timer_h, timer_l]-- the closer it is to 0xFFFF, the faster the level will change. Before the timer interrupt will happen, the timer needs to be enabled:

    EA    = 0;              // Enable global interrupt mask
    TR0   = 1;              // Enable timer 0
    ET0   = 1;              // Enable timer 0 interrupt
    TMOD  = 0x01;           // Mode 1: 16 bit counter
    TH0   = timer_h;        // Current value of timer 0 (high)
    TL0   = timer_l;        // Current value of timer 0 (low)
    EA    = 1;              // Disable global interrupt mask

For an explanation of the bits please refer to the data sheet.

Written on May 28, 2010