Controlling a PL9823 LED over Bluetooth LE

I received an ESP32-Camera board from Aliexpress recently but unfortunately it’s camera was broken (the supplier is sending a free replacement). Not wanting to waste the otherwise good ESP32 I decided to see if it could be used to control the brightness and colour of an LED such as the PL9823. These LED’s are controlled using a serial data string which is documented elsewhere in this blog (https://ioprog.com/2016/04/09/stm32f042-driving-a-ws2812b-using-spi/ and https://ioprog.com/2018/11/08/stm32l031-controlling-a-pl9823-led-using-spi/)

It turned out to be pretty straightforward.

The ESP32 board was connected to the PC over a USB-Serial converter. Two buttons were added to control boot mode and the PL9823’s Data In pin was connected to IO2. The code to control all of this was developed in the Arduino environment (based off an example) and is as follows:

#include <SPI.h>

/*
 *  Controlling a PL9823 LED over bluetooth on an ESP32
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
    Ported to Arduino ESP32 by Evandro Copercini
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

#define SERVICE_UUID        "22389e17-7cee-41ce-8aa0-28a4482f7020"
#define CHARACTERISTIC_UUID "a575e1bf-e15f-4534-a80c-1837348360ad"

void writePL9823(uint32_t Colour)
{
    // each colour bit should map to 4 SPI bits.
    // Format of Colour (bytes) 00RRGGBB
    uint8_t SPI_Output[12];
    int SrcIndex = 0;
    int DestIndex = 0;
    for (DestIndex = 0; DestIndex < 12; DestIndex++)
    {
        if (Colour & (1 << 23))
        {
            SPI_Output[DestIndex] = 0xe0;
        }
        else
        {
            SPI_Output[DestIndex] = 0x80;
        }
        Colour = Colour << 1;
        if (Colour & (1 << 23))
        {
            SPI_Output[DestIndex] |= 0xe;
        }
        else
        {
            SPI_Output[DestIndex] |= 0x8;
        }
        Colour = Colour << 1;
    }    
    SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); 
    SPI.transfer(SPI_Output, 12);
    delay(10);
    SPI.endTransaction();
}

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();
    
      if (value.length() > 0) {
        // Write debug messages out to serial port
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
        // update the PL9823 LED
        uint32_t intvalue;
        intvalue = strtoul(pCharacteristic->getValue().c_str(),NULL,16);
        writePL9823(intvalue);
      }
    }
};

void setup() {
  
  SPI.begin(1,4,2,3); // (int8_t sck, int8_t miso, int8_t mosi, int8_t ss)
  Serial.begin(115200);  
  Serial.println("Connect to the device over BLE and change the colour of the LED");

  BLEDevice::init("BLE_PL9823");
  BLEServer *pServer = BLEDevice::createServer();

  BLEService *pService = pServer->createService(SERVICE_UUID);

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("ffffff");
  
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();

  // Write the initial value out to the PL9823
  uint32_t intvalue;
  intvalue = strtoul(pCharacteristic->getValue().c_str(),NULL,16); 
  writePL9823(intvalue);
}
void loop() {
  // put your main code here, to run repeatedly:
  
  delay(1000);
}

Using an Andoid app like BLE Scanner, it is possible to control the the LED by sending a hex string such as ff0000 for maximum red; 00ff00 for max green and 0000ff for max blue. These colours can be mixed to form arbitrary colours and brightness.

NRF51822 with an accelerometer and an Oled display

The CJMCU-8223 is a small breakout board featuring an NRF51822 and an LIS3DH accelerometer. It is shown above attached to an SSD1306 Oled display. Programs are loaded on to it using an ST-Link debugger clone.

There are many different ways to program this chip but I decided to develop code for it using the online compiler at mbed.org. This environment allows you write and compile code within your browser. Compiled programs are dropped in to your downloads directory in the form of “hex” files. These downloads can then be written to your target MCU using your preferred debugger. The advantage of this approach is that you don’t have to set up a toolchain on your PC and, you can avail of lots of ready made libraries and example code.

This particular example provides two Gatt services over BLE. An LEDService allows a connected bluetooth device to control the blue LED on the breadboard. An accelerometer service allows the connected device read the accelerometer values (scaled up by a factor of 1000 to avoid floating point printing). The code for this example can be found via this link over at mbed.org.

When you compile this code, the hex file delivered to your PC needs to be uploaded to the NRF51822. This is done as follows:

In one command window (terminal) start openocd as shown here:

Leaving that window running, open a new one and run telnet entering the commands shown below.

Once you have done this, type reset into the telnet session and the board should start running.

You can interact with the board using BLE Scanner on your phone or maybe work with node.js or python to develop a PC based application.

If you need to do some debugging, it is possible to export the program from the mbed environment into a format that is compatible with a number of toolchains (including gcc/gdb). I have tried this and it works pretty well although you may need to disable interrupts on the MCU while doing this as the debugger can get confused if there is bluetooth activity during the debug session.

Breadboard games 2020

With the Covid-19 pandemic ongoing it is not likely there will be any further Breadboard Games events this year. There was a small event in early March in Kevin St. public library with children from Singe Street primary school. On that day the attendees built a board very similar to the one we used in
St. Audoen’s school in 2017.

So what about the rest of the year? Well, a couple of events happened this year that made me think of a particular game: the passing of John Horton Conway and the pandemic so the game is of course “The game of life”. Lots has been written about this game and I will not repeat that here, instead I will focus on how this can be implemented using a low cost microcontroller (MCU), screen and breadboard. There will likely not be “event” based on this game but maybe a reader will be inspired to try it out for themselves.

game_of_life_breadboard

The schematic
game_of_life_schematic

This circuits makes use of components I had lying left over from other breadboard games projects. The MCU is the 30 cent (approx) STM32F030F4P6. This display is a 176×220 TFT board with a parallel interface. The databus runs between PA0-PA7 on the STM32 and DB0-DB7 on the display (it is not shown completely as it would have cluttered the schematic).
The specifications of the MCU are pretty meagre: 16kB of Flash, 4kB of RAM, 48MHz clock speed. All pins have been used to control the display.

The game
Conway’s game of life is all about automata (cells) that live or die depending upon what surrounds them. Cells can die of loneliness (less that 2 neighbours), starvation (more than 3 neighbours) and they can come to life if they are bordered by exactly 3 neighbours. These rules are applied repeatedly to the cells in the landscape. I’ve chosen to do this in two passes. The first pass decides what the state of each cell will be during the next time around based on current state. The second pass switches from the old state to the new state. This of course requires memory (RAM) and this MCU only has 4kB. The display has 176×220 = 38720 pixels. The game allocates one pixel per cell so without some sort of compression there is no way this MCU can store this amount of data. To further add to the storage burden I’ve decided to go with a coloured version of GoL called Quad-Life. Each pixel can be one of 4 colours or black. Where can this be stored?

Stealing memory from the Display
The RM68130 display has enough on-board RAM to store 18bits for each of the 176×220 pixels. All of this does not have to be used for controlling the display however. If the display is switched to 8 colour mode then only 3 of those 18 bits is required per pixel (1 for red, 1 for blue and 1 for green). The remaining 15 bits can be used to hold whatever you like without impacting on what you see on screen. At least that was the theory…. I’ve been using various small displays like this for a while and up until now have had little success reading data back from them. The SPI versions are particularly tricky; some don’t even have read-back functionality. Happily the RM68130 does allow you read back from it’s graphics memory but there’s a catch: the bits you write do not come back to you in the same order. Now I’m willing to accept I may have made a mistake here but I found it VERY confusing reading data back from the display. After many hours I figured out enough to store 1 byte of my choosing alongside each pixel. This means the MCU just got an additional 38720 bytes of RAM – plenty for storing the future states of each cell. By the way there are other displays that should work like this. I’ve tried the ST7775 and the ILI9225 (both parallel interfaces) and they both worked fine

Additional game details

GoL has to start somewhere. You need some way of supplying initial states for the cells (pixels). As you can see above there are no more pins available for user input (PA13 and PA14 are used for debugging). One further feature of the display is that also functions as a resistive touch screen. The user now simply draws the initial state using a stylus (or finger).

The GoL landscape is not meant to run in to a hard border so when a cell goes beyond an edge it “wraps” around to the other side.

Gameplay
The video below shows the game “in action”. It is not particularly fast and manages about 1 update per second. Consider it a slightly interactive desk ornament 🙂

The code
Code is as usual available over here on Github. There has be no attempt to optimize the performance of this code in any way. It is shown in what is hopefully the easiest to understand mode.

The STM32G030

The STM32G030 is similar in many ways to the STM32F030. It has approximately the same peripherals and is available for a similar price (in western countries). There are some significant differences however:
The ‘G030 however uses a Cortex M0+ (instead of the M0 in the F030),
The ‘G030 has a maximum clock speed of 64MHz (48MHz for the F030),
The model I chose STM32G030K8T6 has twice the Flash (64KB) and twice the RAM (8KB) that the ‘F030 has.
stm32g030_display
Full size image
I’ve put together a number of examples over on github that might help someone get started. These include a simple blinky, serial comms, serial comms with an ADC, systick interrupts and finally driving an ST7789 LCD display.

Getting around an openocd bug

I’ve been back working with the SAMD20 microcontroller family again for an RS485 network project. While writing code for the device I noticed that it would crash quite often. Further investigations revealed that sections of the code were not being written to flash memory. This device has a 64 byte flash page size and it will only accept writes of 64 byte blocks. If you try to write a smaller amount the write is ignored. This causes a problem when you want to write a flash image that is not an integer multiple of 64 bytes in size – the last few bytes will not be written out. The version of openocd I’m using came from here https://sourceforge.net/p/openocd/code/ci/master/tree/ . I reported the issue but while I’m waiting for a response I’ve managed to workaround the problem by adding some padding to the flash image in the linker file as shown here.

MEMORY
{
    flash : org = 0x00000000, len = 128k
    ram : org = 0x20000000, len = 16k
}
  
SECTIONS
{
        
	. = ORIGIN(flash);
        .text : {
		  *(.vectors); /* The interrupt vectors */
		  *(.text);		  
		  *(.rodata);		  

        } >flash
	. = ORIGIN(ram);
        .data : {
	  INIT_DATA_VALUES = LOADADDR(.data);
	  INIT_DATA_START = .;
	    *(.data);
	  INIT_DATA_END = .;
	  . = ALIGN(4);
        } >ram AT>flash
     
	BSS_START = .;
	.bss : {	  
	    *(.bss);
	    . = ALIGN(4);
	} > ram
	BSS_END = .;
	
	.padding : {
		  /* This is a 64 byte block of 0xff's to ensure that the last */
		  /* page of the program is written to the MCU */
		  /* The openocd SAMD driver does not seem to flush the last partial */
		  /* page out properly */

          LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);
		  LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);
		  LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);
		  LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);LONG(0xffffffff);
	} >flash
}


This linker script is for the SAMD20E17 MCU. The value chose for the padding data is important as the erased state of flash is logic ‘1’. By using 0xffffffff as a padding value (which may or may not be written to the flash) read-back verification will report a success.

Performance improvement for STM32F030/ST7789 graphics library

stm32f030_st7789_nrf24l01

I’ve been working on a new project involving an STM32F030, an ST7789 display and an NRF24L01 radio link. As part of this project I took a good look at the graphics library that I used in the Dublin Maker badge in 2019. It turns out that there was plenty of scope to improve it’s performance. Tweaks included flattening function calls and using the set/reset registers in the STM32F030. Here’s an excerpt from the old library:

void display::fillRectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t Colour)
{
    openAperture(x, y, x + width - 1, y + height - 1);
    for (y = 0; y < height; y++)
    {
        for (x = 0; x < width; x++)
        {
            writeData16(Colour);
        }
    }
}
void display::RSLow()
{
    GPIOB->ODR &= ~(1 << 1); // drive D/C pin low
}
void display::RSHigh()
{ 
    GPIOB->ODR |= (1 << 1); // drive D/C pin high
}

The new version of these functions looks like this:

void display::fillRectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t Colour)
{
    
    register uint32_t pixelcount = height * width;
    uint16_t LowerY = height+y;
    if ((LowerY) <= VIRTUAL_SCREEN_HEIGHT) 
    {
        openAperture(x, y, x + width - 1, y + height - 1);
        RSHigh();
        while(pixelcount--)
            transferSPI16(Colour);
    }
    else
    {
        // Drawing a box beyond the extents of the virtual screen.  
        // Need to wrap this around to the start of the screen.
        uint16_t LowerHeight = (VIRTUAL_SCREEN_HEIGHT-y);
        uint16_t UpperHeight = height - LowerHeight;
        openAperture(x, y, x + width - 1, VIRTUAL_SCREEN_HEIGHT-1);
        RSHigh();
        pixelcount = LowerHeight * width;
        while(pixelcount--)
            transferSPI16(Colour);
      
        openAperture(x, 0,x + width - 1, UpperHeight);
        RSHigh();
        pixelcount = UpperHeight * width;
        while(pixelcount--)
                transferSPI16(Colour);
        
    }
}
void display::RSLow()
{ 
// Using Set/Reset register here as this needs to be as fast as possible   
    GPIOB->BSRR = ((1 << 1) << 16); // drive D/C pin low
}
void display::RSHigh()
{ 
// Using Set/Reset register here as this needs to be as fast as possible     
    GPIOB->BSRR = ((1 << 1)); // drive D/C pin high
}

The new version is a good deal bigger for a couple of reasons:
First of all, the fill rectangle function has been extended so that it is usable with display scrolling (a new feature)
Secondly, the call to writeData16 has been eliminated (removing the function call overhead). This means that lower level SPI function calls have to be used. Also, the nested loop for x and y co-ordinates has been changed to a single loop that fires out the pixels as a continuous stream – the display hardware itself looks after the x and y coordinates.

So how much faster is it? To test this I wrote a simple program to fire a full filled rectangle at the display 50 times and measured how long it took.
The results:
The old driver :
50 rectangles (240*240) took 8.6 seconds. This corresponds to a pixel write speed of 334883 pixels per second.
The new driver:
50 rectangles (240*240) took 4.6 seconds or 626086 pixels per second. Nearly twice as fast as the older library. At this speed it takes 92 milliseconds to fill the display. Not stellar by PC standards but good enough for my needs.
Code is available over on github.

Various examples for the STM32G431

The STM32G431 was recently introduced by ST-Microelectronics. It contains a Cortex M4 core running at 170MHz along with ADC’s, DAC’s timers and some interesting DSP acceleration hardware. I’ve just got started on this chip and have uploaded a number of examples to github.
The version of openocd that came with my Ubuntu installation did not support this chip so I had to download a more up to date version from here.
Example code so far ranges from Blinky up to stereo analogue pass-through. I plan to work work on some FIR and IIR examples soon. stm32g431_bbreadboard

Micropython on a cheap NRF51822 module

IMG_20200202_153744
It is possible to buy small NRF51822 modules like the black “daughter-board” in the picture above for about €2. The motherboard it connects to breaks all the pins out to various headers, provides an JTAG/SWD port and a USB/Serial interface. These motherboards cost around $6 on Aliexpress.
I was curious about micropython on this platform so I took the following steps:

First, I downloaded the Nordic Semiconductors command line tools from here
They were then installed as follows:

sudo dpkg -i nRF-Command-Line-Tools_10_6_0_Linux-amd64.deb

(I had to fiddle with my PATH environment variable to make the programs available to the script used later).

Next, the motherboard and JTAG interfaces were plugged in to a USB port.

Micropython was compiled and downloaded as follows (Note, I had an arm cross compiler already on my system.):

git clone https://github.com/micropython/micropython.git micropython
cd micropython
make -C mpy-cross
cd ports/nrf
./drivers/bluetooth/download_ble_stack.sh
make submodules
make BOARD=wt51822_s4at SD=110 sd

The last line targets a particular board that is compatible with the little cheap Aliexpress one, it also includes the Bluetooth soft-device and flashes the board.

All that seemed to work ok and I tried to connect to the board over a serial terminal: No success. After a bit of searching I discovered file called mpconfigboard.h in the micropython/ports/nrf/boards/wt51822_s4at directory. This needs to be edited as follows so that TX and RX for the micropython shell are routed properly.

/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Ayke van Laethem
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

// Datasheet for board:
// https://4tronix.co.uk/picobot2/WT51822-S4AT.pdf
#define MICROPY_HW_BOARD_NAME       "WT51822-S4AT"
#define MICROPY_HW_MCU_NAME         "NRF51822"
#define MICROPY_PY_SYS_PLATFORM     "nrf51"

#define MICROPY_PY_MACHINE_UART     (1)
#define MICROPY_PY_MACHINE_HW_SPI   (1)
#define MICROPY_PY_MACHINE_TIMER    (1)
#define MICROPY_PY_MACHINE_RTCOUNTER (1)
#define MICROPY_PY_MACHINE_I2C      (1)
#define MICROPY_PY_MACHINE_ADC      (1)
#define MICROPY_PY_MACHINE_TEMP     (1)
#define MICROPY_PY_RANDOM_HW_RNG    (1)

#define MICROPY_HW_HAS_LED          (0)

// UART config
#define MICROPY_HW_UART1_RX         (11)
#define MICROPY_HW_UART1_TX         (9)
#define MICROPY_HW_UART1_HWFC       (0)

// SPI0 config
#define MICROPY_HW_SPI0_NAME        "SPI0"
#define MICROPY_HW_SPI0_SCK         (1)
#define MICROPY_HW_SPI0_MOSI        (2)
#define MICROPY_HW_SPI0_MISO        (3)

Note the changes to MICROPY_HW_UART1_RX and MICROPY_HW_UART1_TX.

I repeated the last “make” command and hey presto : I was able to type python into a serial terminal!

nrf51822_python

There are a couple of little problems:
1) You can’t paste large amounts of python into the terminal as it overflows the serial buffer in the device
2) The range of pins available is restricted as this board is not quite the same as the wt51822_s4at

Anyway, it’s a start

A simple oscilloscope using the Longan-Nano

Longan-nano-scope
The Longan-Nano comes with a nice little display that opens up lots of possibilities. One of these is an oscilloscope. The GD32VF103 on the board has a pair of fast ADC’c that supposedly work up to 1MHz. The approach I took didn’t quite achieve this so I had to settle for 500kHz instead – good enough for a demo.
The code makes use of Timer 2 to pace (trigger) ADC conversions on two channels (one on each ADC) which are labelled A0 and A3 on the ‘nano.
The picture above shows the display when two inputs are used: A steady DC value from a potentiometer and a sine-wave generated by a blue-pill board doing some fast PWM (See this post).
The picture of the display is showing “ghosting” because of the slow shutter speed of the camera.
Two buttons are fitted to the board to allow the user increase and decrease the sample rate which stretches and compresses the waveform on the screen.
This is not meant to be an actually useful instrument – it’s just a learning exercise for this device.
Code is over on github.

Fast PWM on the STM32F103 Bluepill

I needed a signal source for another project I’m working on so I set about building a signal generator of sorts using a “Bluepill” board. The STM32F103 on these boards does not have a Digital to Analogue Converter but it does have a very capable timer which can be used to generate waveforms.
STM32F103_TIM1_DMA_PWM

The timer (TIM1) is shown in the diagram above. In the mode that it is configured for this project it works as follows:
The CNT register counts up to the value in ARR
When CNT = ARR the upper digital comparator outputs a pulse which is used to reset CNT to 0 and to set the PWM output signal high (via the S-R Flip-Flop).
When CNT is equal to the value in CCR1, the lower digital comparator outputs a pulse which resets the PWM output low. ARR therefore controls the output frequency while CCR1 controls the output duty.
(CNT = count, ARR = auto-reload register, CCR1 = Counter-Compare Register 1)

I’m interested in producing audio range signals so up to about 20kHz. An trade-off between switching frequency, resolution, and output filtering arises here. First of all, I want to use a simple RC filter on the output to remove the high frequency switching. This means that the switching frequency has to be a lot higher than the target output signal i.e. much greater than 20kHz. But how much higher? The clock in the STM32F103 runs at 72MHz so this places an upper limit on things. Lets say we go for a switching frequency of 12MHz. This means that the counter will count from zero to five and then back up to zero again (6 states in total). This means that our PWM waveform generation system is only able to produce 6 different outputs (between 2 and 3 bits of resolution) which is pretty poor. I wanted a lot more resolution than that so after playing with the numbers I arrived at the following. Let ARR = 255 (8 bits of resolution). This gives a switching frequency of 280500Hz which is certainly not audible and is pretty easy to filter with passive components.
In order to produce a sine-wave output the CCR1 value needs to be varied sinusoidally. This could be done by generating an interrupt for each PWM cycle and loading values from a look-up table into CCR1. This places a reasonably high load on the CPU however given the high switching frequency. Another option is to use DMA which can transfer values from the lookup-table directly into CCR1. After all the values of the table have been used up, the DMA controller can then generate an interrupt allowing the program to perhaps modify the output signal if necessary. The interrupt rate is a lot lower with this approach and the CPU is free to do other tasks.
stm32f103_280kHz_PWM_logic_out
The figure above shows a logic analyzer view of the outputs. The upper signal is a fixed duty signal from CCR2 (another PWM output channel). The middle signal is the sinusoidal PWM output. The lower signal is a digital output that is toggled at the end of each DMA transfer (every millisecond).

After filtering the output looks like this:
stm32f103_280kHz_PWM

The upper trace is the PWM output after it has been passed through an RC filter with a cutoff of 20kHz. As expected the switching frequency (which is most pronounced around zero-crossings of the sinewave) is attenuated to about 1 tenth of its original size. The lower trace shows the unfiltered output.

The next step is to produce a variable frequency and voltage output – perhaps even two channels. Code is available over on github
Incidentally, the particular board used here is one with 128kB of flash (earlier ones I used had only 64kB).