Control PineCone BL602 RGB LED with GPIO and PWM

PineCone BL602 RISC-V Evaluation Board connected to Pinebook Pro

PineCone BL602 RISC-V Evaluation Board connected to Pinebook Pro

đź“ť 6 Jan 2021

Today we shall take control of PineCone’s Onboard RGB LED in two ways…

  1. GPIO

  2. Pulse Width Modulation (PWM)

We’ll do this with the GPIO and PWM Demo Firmware from the BL602 IoT SDK.

Through the Demo Firmware we shall learn to call BL602’s Hardware Abstraction Layer in C to perform GPIO and PWM Functions.

If you’re new to PineCone BL602, check out my article…

PineCone RGB LED Schematic

PineCone RGB LED Schematic

§1 Control RGB LED with GPIO

According to the PineCone Schematics, the onboard RGB LED is connected to these GPIO Pins…

LEDGPIO Pin
BlueGPIO 11
RedGPIO 17
GreenGPIO 14

Let’s flash the GPIO Demo from the BL602 IoT SDK and interact with the above GPIO Pins…

  1. Download the BL602 Demo Firmware Binaries…

  2. Unzip customer_app.zip. Look for the file…

    sdk_app_gpio/build_out/sdk_app_gpio.bin
    
  3. Flash sdk_app_gpio.bin to PineCone. Follow the instructions in the article…

    After flashing, flip the PineCone Jumper (IO 8) to the L Position (Like this)

    Press the Reset Button.

  4. Connect to PineCone…

    For Linux:

    screen /dev/ttyUSB0 2000000
    

    For macOS: Use CoolTerm (See this)

    For Windows: Use putty (See this)

    Alternatively: Use the Web Serial Terminal (See this)

    More details on connecting to BL602

  5. Press the RST Button on PineCone to restart the firmware.

    We should see this…

    BL602 GPIO Demo

  6. Press Enter to reveal the command prompt.

    Enter help to see the commands…

    BL602 GPIO Demo Commands

  7. Enter these commands to set GPIO 11 (Blue), 14 (Green), 17 (Red) to output (no pullup, no pulldown)…

    gpio-func 11 0 0 0
    gpio-func 14 0 0 0
    gpio-func 17 0 0 0
    
  8. Switch off the 3 LEDs (1 = Off)…

    gpio-set 11 1
    gpio-set 14 1
    gpio-set 17 1
    
  9. Switch on and off each of the 3 LEDs: Blue, Green, Red (0 = On, 1 = Off)…

    gpio-set 11 0
    gpio-set 11 1
    
    gpio-set 14 0
    gpio-set 14 1
    
    gpio-set 17 0
    gpio-set 17 1
    
  10. To exit screen, press Ctrl-A then k then y

Watch the GPIO Demo Video on YouTube

PineCone Jumper Schematic

PineCone Jumper Schematic

§2 GPIO Exercise for The Reader

According to the PineCone Schematics, the onboard jumper is connected to GPIO 8.

Can we use this command to read the jumper?

gpio-get 8

Flip the jumper and check whether the value changes.

Remember to use this command to configure GPIO 8…

gpio-func 8 1 PULLUP PULLDOWN

Please lemme know!

§3 How It Works: BL602 GPIO

The GPIO Demo Firmware calls the GPIO Functions provided by the BL602 Hardware Abstraction Layer (HAL).

Let’s look at the BL602 GPIO Functions called by the GPIO Demo Firmware: sdk_app_gpio.bin

§3.1 Enable GPIO

To designate a GPIO Pin for input or output, we call these GPIO HAL Functions: bl_gpio.h

int bl_gpio_enable_output(uint8_t pin, uint8_t pullup, uint8_t pulldown);
int bl_gpio_enable_input( uint8_t pin, uint8_t pullup, uint8_t pulldown);

Check out this sample code for GPIO Output…

§3.2 Read and Write GPIO

To read or write a GPIO Pin, we call these GPIO HAL Functions: bl_gpio.h

int bl_gpio_output_set(uint8_t pin, uint8_t value);
int bl_gpio_input_get( uint8_t pin, uint8_t *value);
int bl_gpio_input_get_value(uint8_t pin);

Check out this sample code for writing to GPIO…

§3.3 GPIO Interrupts

To allow a GPIO Pin to trigger interrupts (like when a button is pressed), we call these GPIO HAL Functions: bl_gpio.h

int  bl_gpio_int_clear( uint8_t gpioPin, uint8_t intClear);
void bl_gpio_intmask(   uint8_t gpiopin, uint8_t mask);
void bl_set_gpio_intmod(uint8_t gpioPin, uint8_t intCtrlMod, uint8_t intTrgMod);
void bl_gpio_register(gpio_ctx_t *pstnode);

Check the following for details on GPIO Interrupts…

To see the above GPIO HAL Functions in action, check out the GPIO Demo Source Code…

§3.4 GPIO Device Tree

There is an alternative set of functions for controlling GPIO…

These functions are meant to be used with the BL602 Device Tree.

More about BL602 Device Tree

§4 From GPIO to Pulse Width Modulation (PWM)

How many colours can we show on the RGB LED through GPIO?

Each GPIO Pin is binary… Either On or Off. Let’s flip each LED and count the colours…

RedGreenBlueColour
OffOffOffBlack
ONOffOffRed
OffONOffGreen
ONONOffYellow
OffOffONBlue
ONOffONMagenta
OffONONCyan
ONONONWhite

Only 8 colours?! That’s not a Full Colour RGB LED!

GPIO Pins are binary (not analogue)… So are LEDs. This will let us switch each LED On and Off, nothing in between (no 50 shades of grey)…

Switching LED on and off with GPIO

But what if we strobe or blink the LEDs very quickly (a thousand times a second)…

Blink the LED very quickly

Aha! We’ll see something that’s neither On nor Off… It’s halfway between Light and Dark!

Now what if we tweak the spacing between the On and Off parts (keeping the same blinking frequency)…

Blink the LED with spacing

We’ll get many, many shades of grey! (>50 yes!)

And if we apply this nifty trick to each of the RGB LEDs, we’ll get our Full Colour RGB LED!

How shall we program the rapid blinking? Call the GPIO Functions in a loop?

Not a good idea, because our microcontroller will become very busy blinking the LEDs. No time for reading sensors or transmitting data!

Thankfully we have Pulse Width Modulation (PWM)… Our BL602 Microcontroller (and many others) will happily strobe the LED pins for us, without coding any loops.

Here’s the schematic for PineCone’s RGB LED…

PineCone RGB LED Schematic

What are CH1, CH2 and CH4?

CH1, CH2 and CH4 are PWM Channels. Each PWM Channel will let us strobe the output on one pin. (Hence we need 3 PWM Channels)

Let’s match the 3 GPIO Pins and 3 PWM Channels to the Pin Mapping Table: BL602 Reference Manual (Page 27)

BL602 Pin Mapping

The table says that GPIO 11, 17 and 14 may be mapped to PWM Channels 1, 2 and 4 (by calling the PWM HAL Functions). Perfect!

Remember that we tweaked the spacing of the blinking to get many levels of brightness?

We call this the Duty Cycle in PWM.

Let’s experiment with the RGB LED on PWM…

§5 Control RGB LED with PWM

Now we’ll switch PineCone to the Modified PWM Demo from the BL602 IoT SDK.

(The firmware was modified to run without a Device Tree. More details)

  1. Download the BL602 Demo Firmware Binaries…

  2. Unzip customer_app.zip. Look for the file…

    sdk_app_pwm/build_out/sdk_app_pwm.bin
    
  3. Flash sdk_app_pwm.bin to PineCone. Follow the instructions in the article…

    After flashing, flip the PineCone Jumper (IO 8) to the L Position (Like this)

    Press the Reset Button.

  4. Connect to PineCone…

    For Linux:

    screen /dev/ttyUSB0 2000000
    

    For macOS: Use CoolTerm (See this)

    For Windows: Use putty (See this)

    Alternatively: Use the Web Serial Terminal (See this)

    More details on connecting to BL602

  5. Press the RST Button on PineCone to restart the firmware. Ignore the errors.

  6. Press Enter to reveal the command prompt.

  7. Assign GPIO 11 (Blue), 17 (Red), 14 (Green) to PWM Channels 1, 2 and 4.

    Set the PWM Frequency to 2 kHz. (Each LED will blink at 2,000 cycles per second)

    pwm_init 1 11 2000
    pwm_init 2 17 2000
    pwm_init 4 14 2000
    
  8. Set PWM Duty Cycle for all 3 PWM Channels to 100%.

    Which means that 100% of the time, the 3 PWM Channels will be set to 1 (High).

    Which means total darkness: All 3 LEDs will be switched off 100% of the time.

    pwm_duty_set 1 100
    pwm_duty_set 2 100
    pwm_duty_set 4 100
    
  9. Start the PWM Output for all 3 PWM Channels…

    pwm_start 1
    pwm_start 2
    pwm_start 4
    
  10. Gradually decrease the PWM Duty Cycle for PWM Channel 1 (Blue) from 100% to 0%.

    This means the Blue LED will gradually get brighter.

    pwm_duty_set 1 75
    pwm_duty_set 1 50
    pwm_duty_set 1 25
    pwm_duty_set 1 0
    
  11. To exit screen, press Ctrl-A then k then y

Watch the PWM Demo Video on YouTube

§6 How It Works: BL602 PWM

Now we look at the BL602 PWM HAL Functions called by the PWM Demo Firmware: sdk_app_pwm.bin

§6.1 Initialise PWM

To designate a GPIO PIN as a PWM Channel, we call this PWM HAL Function: bl_pwm.h

int32_t bl_pwm_init(uint8_t id, uint8_t pin, uint32_t freq);

Not all GPIO Pins may be assigned to a PWM Channel. Check “Table 3.1: Pin description” (Page 27) in BL602 Reference Manual.

§6.2 PWM Frequency and Duty Cycle

We set the Frequency and Duty Cycle on a PWM Channel by calling these PWM HAL Functions: bl_pwm.h

int32_t bl_pwm_set_freq(uint8_t id, uint32_t freq);
int32_t bl_pwm_set_duty(uint8_t id, float duty);

To get the Duty Cycle for a PWM Channel, we call this function…

int32_t bl_pwm_get_duty(uint8_t id, float *p_duty);

§6.3 PWM Operation

We start and stop a PWM Channel by calling these PWM HAL Functions: bl_pwm.h

int32_t bl_pwm_start(uint8_t id);
int32_t bl_pwm_stop( uint8_t id);

The above PWM HAL Functions are defined here…

To see the above PWM HAL Functions in action, check out the PWM Demo Source Code…

§6.4 PWM Device Tree

There is an alternative set of functions for controlling PWM…

These functions are meant to be used with the BL602 Device Tree.

More about BL602 Device Tree

§7 BL602 PWM Internals

This helpful diagram from the BL602 Reference Manual (Page 158) explains the internals of BL602’s PWM…

BL602 Pulse Width Modulation

BL602 Pulse Width Modulation

  1. BL602’s PWM uses an Internal Counter to generate a Sawtooth Wave

  2. Each cycle of the Sawtooth Wave has a duration (PWM Period) that’s determined by the PWM Frequency (PWM Period = 1 / PWM Frequency)

  3. The PWM Channel outputs 0 or 1 by comparing the Internal Counter with two values: PWM Threshold1 (the lower limit) and PWM Threshold2 (the upper limit)

  4. We assume that PWM Threshold1 (the lower limit) is always 0. That’s because the BL602 PWM HAL Function bl_pwm_set_duty always sets Threshold1 to 0.

  5. What’s the value of PWM Threshold2 (the upper limit)? That’s computed based on the PWM Period and PWM Duty Cycle: bl_pwm_set_duty

    //  The Duty Cycle `duty` is between 0 to 100
    threshold2 = ( period / 100 ) * duty;
    

    So when we increase the Duty Cycle, Threshold2 gets higher.

  6. Here’s the PWM Output logic…

  7. What happens when we increase the Duty Cycle?

    Threshold2 gets higher, hence the PWM Channel outputs 1 more often.

  8. That’s precisely the definition of Duty Cycle…

    Duty Cycle is the percentage of time (0 to 100) within a Cycle that’s spent Working. (“Working” means Output=1)

    Outside of the Duty Cycle, our PWM Channel is Idle. (Output=0)

  9. Note that the Working vs Idle definition is flipped for our LED…

  10. Which explains this odd behaviour we’ve seen earlier…

    (Yep the Duty Cycle is Inversely Proportional to the LED Brightness)

§8 What’s Next

Today we have we have explored the GPIO and PWM HAL Functions through the BL602 IoT SDK.

Alternatively we may access BL602 GPIO Functions through another embedded operating system: Apache NuttX…

Stay tuned for more NuttX!

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

lupyuen.github.io/src/led.md

§9 Appendix: Fix BL602 Demo Firmware for macOS

On macOS, why doesn’t screen work for accessing the BL602 Demo Firmware?

BL602 Demo Firmware configures the UART Port for 2 Mbps. (Which is not a standard POSIX baud rate)

This causes problems for POSIX serial apps (like screen) that don’t call macOS IOKit. See this

To fix this, use a newer serial app like CoolTerm…

  1. Download CoolTerm

  2. Click Options

  3. Set Port to usbserial-1420

  4. Set Baudrate to 2000000 (2 Mbps)

  5. Click Connect

CoolTerm Options

(Many thanks to @Kongduino)

What if we really really want to use POSIX serial apps like screen?

This is NOT recommended… But to support POSIX serial apps with macOS, we need to lower the UART baud rate from 2 Mbps to 230.4 kbps. (Which is a POSIX baud rate)

  1. In the BL602 Demo Firmware, edit the main.c source file, like…

  2. Look for this line that configures the UART port for 2 Mbps…

    bl_uart_init(0, 16, 7, 255, 255, 2 * 1000 * 1000);
    

    Change it to 230.4 kbps…

    bl_uart_init(0, 16, 7, 255, 255, 230400);
    
  3. Rebuild the firmware.

  4. Edit the BL602 Device Tree: bl_factory_params_IoTKitA_40M.dts

    Look for…

    uart {
        #address-cells = <1>;
        #size-cells = <1>;
        uart@4000A000 {
            status = "okay";
            id = <0>;
            compatible = "bl602_uart";
            path = "/dev/ttyS0";
            baudrate = <2000000>;
    

    Change baudrate to…

            baudrate = <230400>;
    
  5. Compile the Device Tree with BLOpenFlasher.

    Copy the compiled Device Tree ro_params.dtb to blflash

    Flash the firmware to PineCone with blflash

    More details

  6. After flashing, set the PineCone Jumper IO8 to L Position. Press the Reset Button.

    We should be able to access the Demo Firmware at 230.4 kbps…

    screen /dev/tty.usbserial-1420 230400