PineCone BL602 Talks UART to Grove E-Ink Display

📝 19 Feb 2021

Today we shall connect PineCone BL602 RISC-V Board to the Grove Triple Color E-Ink Display 2.13“ with UART Interface.

The Demo Firmware in this article will run on PineCone, Pinenut and Any BL602 Board.

It’s 2021… Why are we learning UART?

UART has been around since 1960… Before I was born!

Many modern peripherals expose UART as a “Managed Interface” instead of the raw underlying interface (like SPI)…

  1. UART coding is simpler than SPI and I2C.

    (Though UART is not recommended for transmitting and receiving data at high speeds… Data may get dropped when there’s no hardware flow control)

  2. UART is still used by all kinds of peripherals: GPS Receivers, E-Ink Displays, LoRa Transceivers, …

    (UART is probably OK for E-Ink Displays because we’re pushing pixels at a leisurely bitrate of 230.4 kbps … And we don’t need to receive much data from the display)

This article shall be Your Best Friend if you ever need to connect BL602 to a UART Peripheral.

PineCone BL602 RISC-V Board rendering an image on Grove Triple Colour E-Ink Display with UART Interface

PineCone BL602 RISC-V Board rendering an image on Grove Triple Colour E-Ink Display with UART Interface

1 BL602 UART Hardware Abstraction Layer: High Level vs Low Level

The BL602 IoT SDK contains a UART Hardware Abstraction Layer (HAL) that we may call in our C programs to access the two UART Ports.

BL602’s UART HAL is packaged as two levels…

  1. Low Level HAL bl_uart.c: This runs on BL602 Bare Metal.

    The Low Level HAL manipulates the BL602 UART Registers directly to perform UART functions.

  2. High Level HAL hal_uart.c: This calls the Low Level HAL, and uses the Device Tree and FreeRTOS.

    The High Level HAL is called by the AliOS Firmware created by the BL602 IoT SDK.

    (AliOS functions are easy to identify… Their function names begin with “aos_”)

Today we shall use the Low Level UART HAL bl_uart.c because…

We shall call the BL602 Low Level UART HAL to control the Grove E-Ink Display with this BL602 Command-Line Firmware: sdk_app_uart_eink

The firmware will work on all BL602 boards, including PineCone and Pinenut.

PineCone BL602 connected to Grove E-Ink Display

PineCone BL602 connected to Grove E-Ink Display

2 Connect BL602 to Grove E-Ink Display

Connect BL602 to Grove E-Ink Display according to the pic above…

BL602 PinE-Ink DisplayWire Colour
GPIO 3TXYellow
GPIO 4RXBlue / White
3V33.3VRed
GNDGNDBlack

Here’s an extreme closeup of the PineCone BL602 pins…

PineCone BL602 connected to Grove E-Ink Display Closeup

The screen works without power! What magic is this?

Remember that E-Ink Displays only need power when we’re updating the display.

Which makes them very useful for Low Power IoT Gadgets.

(But we’re not supposed to update the screen too often)

3 Initialise UART Port

Let’s dive into the code for our Demo Firmware!

We initialise the UART Port like so: demo.c

/// Use UART Port 1 (UART Port 0 is reserved for console)
#define UART_PORT 1

/// Command to display image
static void display_image(char *buf, int len, int argc, char **argv) {
    ...
    //  Init UART Port 1 with Tx Pin 4, Rx Pin 3 at 230.4 kbps
    int rc = bl_uart_init(
        UART_PORT,  //  UART Port 1
        4,          //  Tx Pin (Blue)
        3,          //  Rx Pin (Yellow)
        255,        //  CTS Unused
        255,        //  RTS Unused
        230400      //  Baud Rate
    );
    assert(rc == 0);

Here we define display_image, the command that we’ll be running in our Demo Firmware.

It calls bl_uart_init (from BL602 Low Level UART HAL) to initialise the UART Port with these parameters…

We’ll come back to display_image in a while. First let’s learn to transmit and receive some UART data.

4 Transfer UART Data

Before we send a bitmap to the E-Ink Display for rendering, we do a Start Transfer Handshake to make sure that everybody’s all ready…

  1. We wait until we receive the character 'c' from the E-Ink Display

  2. We transmit the character 'a' to the E-Ink Display

  3. Finally we wait until we receive the character 'b' from the E-Ink Display

Let’s implement this with the BL602 Low Level UART HAL.

4.1 Receive Data

Here’s how we receive one byte of data from the UART Port…

//  Use UART Port 1 (UART Port 0 is reserved for console)
#define UART_PORT 1

//  Read one byte from UART Port 1, returns -1 if nothing read
int ch = bl_uart_data_recv(UART_PORT);

Note that bl_uart_data_recv (from the BL602 Low Level UART HAL) returns -1 if there’s no data to be read.

Thus we usually call bl_uart_data_recv in a loop like so: demo.c

/// Do the Start Transfer Handshake with E-Ink Display:
/// Receive 'c', send 'a', receive 'b'
void send_begin() {
    //  Wait until 'c' is received
    for (;;) {
        //  Read one byte from UART Port, returns -1 if nothing read
        int ch = bl_uart_data_recv(UART_PORT);
        if (ch < 0) { continue; }  //  Loop until we receive something

        //  Stop when we receive 'c'
        if (ch == 'c') { break; }
    }

Here we define the function send_begin that performs the Start Transfer Handshake.

This code loops until the character 'c' has been received from the UART Port. Which is the First Step of our handshake.

4.2 Transmit Data

Second Step of the handshake: Send the character 'a'

    //  Send 'a'
    int rc = bl_uart_data_send(UART_PORT, 'a');
    assert(rc == 0);

Here we call bl_uart_data_send (also from the BL602 Low Level UART HAL) to transmit the character 'a' to the UART Port.

4.3 Receive Again

Finally the Third Step: Wait until 'b' has been received from the UART Port…

    //  Wait until 'b' is received
    for (;;) {
        //  Read one byte from UART Port, returns -1 if nothing read
        int ch = bl_uart_data_recv(UART_PORT);
        if (ch < 0) { continue; }  //  Loop until we receive something

        //  Stop when we receive 'b'
        if (ch == 'b') { break; }
    }
}

(Looks very similar to the First Step)

And we’re done with the Start Transfer Handshake!

Note that we’re polling the UART Port, which is OK because we’re mostly transmitting data, and receiving little data.

If we’re receiving lots of data through polling, we might lose some data. For such cases, we should use UART Interrupts or DMA.

(The E-Ink Display code in this article was ported from Arduino to BL602. See this)

5 Display Image

Let’s head back to display_image, the function in our Demo Firmware that controls the E-Ink Display to render an image: demo.c

/// Command to display image
static void display_image(char *buf, int len, int argc, char **argv) {
    ...
    //  Init UART Port 1 with Tx Pin 4, Rx Pin 3 at 230.4 kbps
    int rc = bl_uart_init( ... );  //  Omitted, we have seen this earlier
    assert(rc == 0);

    //  Sleep for 10 milliseconds
    vTaskDelay(10 / portTICK_PERIOD_MS);

We’ve initialised the UART Port with bl_uart_init (as seen earlier).

To give the E-Ink Display a bit of time to get ready, we call vTaskDelay (from FreeRTOS) to sleep for 10 milliseconds.

    //  Do the Start Transfer Handshake with E-Ink Display
    send_begin();

    //  Sleep for 2 seconds (2000 milliseconds)
    vTaskDelay(2000 / portTICK_PERIOD_MS);

Then we call send_begin to do the Start Transfer Handshake (from the previous section).

We give the E-Ink Display a little more pondering time, by calling vTaskDelay to sleep for 2 seconds.

    //  Send the display data
    write_image_picture();
}

At the end of the function, we call write_image_picture to send the image data.

Let’s look inside write_image_picture

5.1 Send Image Data

To display a image on our E-Ink Display, we shall transmit two bitmaps: Black Bitmap and Red Bitmap.

(More about the Black and Red Bitmaps in a while)

From demo.c

/// Send Black and Red Image Data to display
static void write_image_picture(void) {    
    //  Send Black Pixels to display in 13 chunks of 212 bytes
    for (int i = 0; i < 13; i++) {
        //  Send a chunk of 212 bytes
        send_data(&IMAGE_BLACK[0 + i * 212], 212);

        //  Sleep for 80 milliseconds
        vTaskDelay(80 / portTICK_PERIOD_MS);
    }

Here we define the function write_image_picture that will transmit the two bitmaps to our E-Ink Display.

We start with the Black Bitmap IMAGE_BLACK, calling send_data to transmit 13 chunks of 212 bytes each.

(We’ll see send_data in a while)

Then we give our E-Ink Display display a short rest…

    //  Sleep for 90 milliseconds
    vTaskDelay(90 / portTICK_PERIOD_MS);

And we transmit the Red Bitmap IMAGE_RED the same way…

    //  Send Red Pixels to display in 13 chunks of 212 bytes
    for (int i = 0; i < 13; i++) {
        //  Send a chunk of 212 bytes
        send_data(&IMAGE_RED[0 + i * 212], 212);

        //  Sleep for 80 milliseconds
        vTaskDelay(80 / portTICK_PERIOD_MS);
    }
}

In send_data we transmit a chunk of data to our E-Ink Display like so: demo.c

/// Send data to display over UART. data_len is number of bytes.
static void send_data(const uint8_t* data, uint32_t data_len) {
    for (int i = 0; i < data_len; i++) {
        int rc = bl_uart_data_send(UART_PORT, data[i]);
        assert(rc == 0);
    }
}

send_data calls bl_uart_data_send (from BL602 Low Level UART HAL) to transmit the data to the UART Port, one byte at a time.

(We’ve seen this earlier during the handshake)

That’s all for the UART code that talks to the E-Ink Display!

6 Build and Run the Firmware

Let’s run the E-Ink Display UART Demo Firmware for BL602.

Download the Firmware Binary File sdk_app_uart_eink.bin from…

Alternatively, we may build the Firmware Binary File sdk_app_uart_eink.bin from the source code

# Download the eink branch of lupyuen's bl_iot_sdk
git clone --recursive --branch eink https://github.com/lupyuen/bl_iot_sdk
cd bl_iot_sdk/customer_app/sdk_app_uart_eink

# TODO: Change this to the full path of bl_iot_sdk
export BL60X_SDK_PATH=$HOME/bl_iot_sdk
export CONFIG_CHIP_NAME=BL602
make

# TODO: Change ~/blflash to the full path of blflash
cp build_out/sdk_app_uart_eink.bin ~/blflash

More details on building bl_iot_sdk

(Remember to use the eink branch, not the default master branch)

6.1 Flash the firmware

Follow these steps to install blflash

  1. “Install rustup”

  2. “Download and build blflash”

We assume that our Firmware Binary File sdk_app_st7789.bin has been copied to the blflash folder.

Set BL602 to Flashing Mode and restart the board.

For PineCone:

  1. Set the PineCone Jumper (IO 8) to the H Position (Like this)

  2. Press the Reset Button

For BL10:

  1. Connect BL10 to the USB port

  2. Press and hold the D8 Button (GPIO 8)

  3. Press and release the EN Button (Reset)

  4. Release the D8 Button

For Pinenut and MagicHome BL602:

  1. Disconnect the board from the USB Port

  2. Connect GPIO 8 to 3.3V

  3. Reconnect the board to the USB port

Enter these commands to flash sdk_app_uart_eink.bin to BL602 over UART…

# TODO: Change ~/blflash to the full path of blflash
cd ~/blflash

# For Linux:
sudo cargo run flash sdk_app_uart_eink.bin \
    --port /dev/ttyUSB0

# For macOS:
cargo run flash sdk_app_uart_eink.bin \
    --port /dev/tty.usbserial-1420 \
    --initial-baud-rate 230400 \
    --baud-rate 230400

# For Windows: Change COM5 to the BL602 Serial Port
cargo run flash sdk_app_uart_eink.bin --port COM5

More details on flashing firmware

6.2 Run the firmware

Set BL602 to Normal Mode (Non-Flashing) and restart the board…

For PineCone:

  1. Set the PineCone Jumper (IO 8) to the L Position (Like this)

  2. Press the Reset Button

For BL10:

  1. Press and release the EN Button (Reset)

For Pinenut and MagicHome BL602:

  1. Disconnect the board from the USB Port

  2. Connect GPIO 8 to GND

  3. Reconnect the board to the USB port

After restarting, connect to BL602’s UART Port at 2 Mbps like so…

For Linux:

sudo 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

6.3 Enter the commands

  1. Press the Reset Button.

    We should see BL602 starting our firmware…

    # ▒Starting bl602 now....
    Booting BL602 Chip...
    ██████╗ ██╗      ██████╗  ██████╗ ██████╗
    ██╔══██╗██║     ██╔════╝ ██╔═████╗╚════██╗
    ██████╔╝██║     ███████╗ ██║██╔██║ █████╔╝
    ██╔══██╗██║     ██╔═══██╗████╔╝██║██╔═══╝
    ██████╔╝███████╗╚██████╔╝╚██████╔╝███████╗
    ╚═════╝ ╚══════╝ ╚═════╝  ╚═════╝ ╚══════╝
    
    ------------------------------------------------------------
    RISC-V Core Feature:RV32-ACFIMX
    Build Version: release_bl_iot_sdk_1.6.11-1-g66bb28da-dirty
    Build Date: Feb 17 2021
    Build Time: 19:06:40
    -----------------------------------------------------------
    
    blog init set power on level 2, 2, 2.
    [IRQ] Clearing and Disable all the pending IRQ...
    [OS] Starting aos_loop_proc task...
    [OS] Starting OS Scheduler...
    Init CLI with event Driven
  2. Press Enter to reveal the command prompt.

    Enter help to see the available commands…

    help
    ====Build-in Commands====
    ====Support 4 cmds once, seperate by ; ====
    help                     : print this
    p                        : print memory
    m                        : modify memory
    echo                     : echo for command
    exit                     : close CLI
    devname                  : print device name
    sysver                   : system version
    reboot                   : reboot system
    poweroff                 : poweroff system
    reset                    : system reset
    time                     : system time
    ota                      : system ota
    ps                       : thread dump
    ls                       : file list
    hexdump                  : dump file
    cat                      : cat file
    
    ====User Commands====
    display_image            : Display image
    blogset                  : blog pri set level
    blogdump                 : blog info dump
    bl_sys_time_now          : sys time now
  3. Enter display_image to render an image on our E-Ink Display.

    (This executes the display_image function that we’ve seen earlier)

  4. We should see this…

    display_image
    Doing start transfer handshake...
    0x9d 0xbe 0x9f 0xbe 0xe8 0xcd 0x9e 0xad 0xea 0x2a 0x3a 0xf8
    Received 'c'
    Sent 'a'
    0x63
    Received 'b'
    Start transfer handshake OK

    Here we see that our display_image function has completed the handshake.

    (If the handshake hangs, disconnect BL602 our computer’s USB port, reconnect it and run the firmware again.)

  5. Then our display_image function sends the black and red bitmaps over the UART Port…

    Sending black pixels...
    Sending red pixels...

    The image appears on the display, like the pic below.

  6. Note that the Grove E-Ink Display will flash some graphics when it is powered on…

    This seems to be triggered by the STM32 F031 Microcontroller that’s inside the Grove E-Ink Display. (See the schematics)

Grove E-Ink Display close up

Grove E-Ink Display close up

7 Black and Red Bitmaps

That’s not a plain black and white image right? I see some red fringes…

The E-Ink Display is actually showing a black, white AND red image!

We can’t show Fifty Shades of Grey on our display… But we can use Red as a Single Shade of Grey!

Our E-Ink Display is capable of rendering two separate bitmaps: black and red.

(Any pixel that’s not flipped on in the black and red bitmaps will appear as white… Thus it’s a Triple Colour Display)

Here’s how we define the black and red bitmaps in our firmware: demo.c

/// Define the Black Pixels of the image
const unsigned char IMAGE_BLACK[] = { 
    #include "image_black.inc"
};

/// Define the Red Pixels of the image
const unsigned char IMAGE_RED[] = { 
    #include "image_red.inc"
};

A peek into the black bitmap reveals this: image_black.inc

//  Min: 0, Max: 85
//  Rows: 104, Columns: 212
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
...

(That’s 2,756 bytes: 104 rows * 212 columns * 1 bit per pixel)

And for the red bitmap: image_red.inc

//  Min: 86, Max: 215
//  Rows: 104, Columns: 212
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
...

(Also 2,756 bytes)

What are Min and Max?

The black and red bitmaps were generated from a Greyscale PNG file: uart-cartoon2.png

Min and Max are the Threshold RGB Values used to generate each bitmap…

  1. Black Bitmap contains pixels whose original RGB values range from 0 to 85 (close to black)

  2. Red Bitmap contains pixels whose original RGB values range from 86 to 215 (between black and white)

Here’s how we convert the PNG file uart-cartoon2.png (202 x 104 resolution) to the C arrays image_black.inc (black bitmap) and image_red.inc (red bitmap)..

# Download the source code
git clone https://github.com/lupyuen/pinetime-graphic
cd pinetime-graphic

# TODO: Copy uart-cartoon2.png to the pinetime-graphic folder

# Convert the PNG file to a C array (black bitmap) with these min and max thresholds
cargo run -- --min 0  --max 85  uart-cartoon2.png >image_black.inc

# Convert the PNG file to a C array (red bitmap) with these min and max thresholds
cargo run -- --min 86 --max 215 uart-cartoon2.png >image_red.inc

8 What’s Next

Exciting things coming up…

  1. LoRa on BL602: We shall connect Semtech SX1276 to BL602 to achieve Triple Wireless Connectivity… WiFi, Bluetooth LE AND LoRa!

    (Many thanks to RAKwireless for providing a LoRa Node for our BL602 experiments!)

  2. BL602 for Education: We shall create more Open-Source Educational Content to make BL602 (and RISC-V) fully accessible to learners around the world.

    Hopefully someday we’ll see a Deconstructed PineTime Smartwatch: BL602 (RISC-V, WiFi, Bluetooth LE, LoRa) plus the sensors, actuators and display from a smartwatch… Connected on a Breadboard for easy coding!

Meanwhile there’s plenty more code in the BL602 IoT SDK to be deciphered and documented: ADC, DAC, WiFi, Bluetooth LE,

Come Join Us… Make BL602 Better!

🙏 👍 😀

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

lupyuen.github.io/src/uart.md

9 Notes

  1. This article is the expanded version of this Twitter Thread

  2. Would be great if we could have the STM32 Source Code for the Grove E-Ink Display. (See this issue)