WS2812(SK6812) Control with SPI and DMA

There are a couple of ways to control WS2812B and its clones. Among them, the method that uses SPI bus via DMA would be the easiest choice for the following reasons; Firstly SPI bus is ubiquitous. Secondly DMA minimizes the burden of the processor and handles timing with hardware. No code is involved in transferring data. Thus once proper SPI clock is chosen, operation is quite reliable as well. Only downside is that you have to dedicate one SPI port for the control of the LED, since you cannot share this line with any other SPI devices

The timing of SK6812 is shown below. There are slight variations in the actual timing between devices. But basically all use the same high/low ratio, namely 1:3 for logic zero and 2:2 for logic one.

Thus set the SPI clock frequency to somewhere between 2.8hMHz and 4MHz, and use nibble (0x8) for logic zero, (0xc) for logic one. Then all should be good. Actually there are a few things to think about but let’s check the timing first.

The main clock used in the experiment was 12MHz. The frequency of the SPI clock was 3MHz (divided by 4). Value [0x88] was sent out via SPI bus and captured using DSO. In the picture below, the waveform shown above is the MOSI signal [0x88], and below is SCLK signal.

From the measurement, you can see that the width of the pulse is about 340nsec, 333nsec to be exact, and the width of zero period is 1000nsec, which fits the specification of logic zero. So the signal captured should be recognised as two consecutive zeros.

First thing to remember is that if you do not use DMA but use firmware code to send a stream of data to SPI bus one by one, then you will certainly see a gap between two consecutive data, which dose not recognized by the smart LED.

Second thing is when the SPI started to send its first byte, its data signal (MOSI) goes from zero to one sometime before its first clock appears if the first bit is one. It is shown in the following picture. MOSI waveform shown above leads the SPI clock below by 400nsec or so.

Thus although the same [0x88] data is sent as before, if this is the very first byte of the data stream, the pulse width of the first nibble is a bit thicker than the specification due to this fact. This will confuse the logic in the LED. To avoid this, your data stream should start with (0x0) nibble always. And actual data comes at the second nibble.

Following picture shows STM32L052 breakout board is driving a string of 10 SK6812 LEDs. The board on the right (ST32L0 discovery board) is used to program the board on the left.

(source code)

Fixing B-L475E-IoT01A Board Reset Issue

There are at least two revisions of the B-L475E-IOT0A board. Earlier boards (version C) have rather critical bug in the hardware, so the STLink-V2 does not work. This is because the NRST net between STLink and the main MCU is missing in the version C board. For more information, check the manual. Unfortunately the manual didn’t describe how to fix it, although it is rather straightforward.

Grab a soldering tools and connect two points shown below.

On the schematic, these two points correspond to

and

Repurposing STLink of Nucleo64 Board

Most of the development kits from ST are equipped with on-board STLink-V2 debugger / programmers. However it is not very convenient to use in Linux environment.

Instead, Segger kindly provides a tool that allows you to reflash on-board STLink into JLink. Process takes only seconds. And you can enjoy most of the great features of JLink with no cost at least for STM32 devices. It keeps the UART port function, so you can use it as a serial monitor in addition to the JLink.

If you have a Nucleo-64 board handy, you can make a nice stand-alone JLink debugger by reflashing the on-board STLink and snap it off.

Note that the original pin headers are removed and some of them are replaced with right-angle type for the convenience. Two of the pins of ST-Link jumper are used to provide 3.3V power. And TX and RX pins of UART are soldered at the back not to interfere with the debug pins.

Note the small changes in the solder bridges SB3 through SB10. Basically this allows the 3V3 line to be connected to the first pin of the ST-Link jumper and GND line to the second through SB6. Both resistors are shunt (zero ohm).

Now you can use this breakout board to (1) power the target, (2) flash and debug via JLink and (3) have serial monitor connection all at the same time.

However beware the current limit of 150mA (actually lower than 150mA since the breakout board consumes some) of the LDO (LD3985M33R) used in the breakout board.

USB MSC Device with FLASH Memory

USB Mass Storage Class implements bulk-only-transfer (BOT) with SCSI protocol. USB packets from the host eventually converted into SCSI transport commands by the middleware, in which data is exchanged (read / write) with the unit of logical block, typically 512 bytes.

This SCSI commands works well with SD card system where a dedicated controller does the job of managing the actual memory elements. If you want to use a FLASH chip as a memory unit instead, you need to handle read / write operation directly.

Fortunately, most of flash memory support 4KB block erase. This makes the 4096 bytes as a natural choice for the size of the logical block. In STM32Cube framework this is defined in the file usbd_storage_if.c

   94 #define STORAGE_LUN_NBR                  1
   95 #define STORAGE_BLK_NBR                  0x10000
   96 #define STORAGE_BLK_SIZ                  0x200

During initial enumeration, this information is registered to the host. The middleware maintains one logical block size of buffer and handles USB transaction whose each payload is only 64 bytes. It then calls SCSI requests to store / retrieve data to / from physical memory device in this case a FLASH memory when the buffer is filled. Thus the buffer size should be increased by the same amount in the file usbd_conf.h

  108 #define MSC_MEDIA_PACKET     4096

Now, remaing task is to simply convert SCSI block read/write commands into FLASH memory sector read/write operations.

    1 /**
    2   * @brief  .
    3   * @param  lun: .
    4   * @retval USBD_OK if all operations are OK else USBD_FAIL
    5   */
    6 int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    7 {
    8   /* USER CODE BEGIN 6 */
    9     SFlash_ReadSector(blk_addr, buf);
   10     return (USBD_OK);
   11   /* USER CODE END 6 */
   12 }
   13 
   14 /**
   15   * @brief  .
   16   * @param  lun: .
   17   * @retval USBD_OK if all operations are OK else USBD_FAIL
   18   */
   19 int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
   20 {
   21   /* USER CODE BEGIN 7 */
   22     SFlash_WriteSector(blk_addr, buf);
   23     return (USBD_OK);
   24   /* USER CODE END 7 */
   25 }

Here the logical unit number(lun) is always zero. This is because there is only one LUN is defined. And the number of logical blocks (blk_len) is always one. This is because the internal buffer size is equal to the size of local block. See MSC_MEDIA_PACKET definition above.

When it is first plugged in, it appears as a raw disk with no FAT. So no logical drive will appear as if unformatted drive. It is necessary to create a FAT using fdisk. To minimize the overhead space, choose FAT16 <32M option in the fdisk (or cfdisk) utility.

Then format the drive

sudo mkfs.vfat -F 16 /dev/sdb1

At this point the host system may recognize the device as a formatted disk drive. Otherwise plug it out and in again. In the picture below, Nucleo32-L432KC (at the center) and Adesto AT25SF081 (bottom left) are used in this project. 

(source code)

STM32L052 Touch Sensor

Some of STM32 MCUs are equipped with TSC (Touch Sensing Controller), whose hardware performance is comparable to ASIC solutions. It is a self-sufficient module supporting multi-touch with decent SNR. And you can lower its power consumption down to few tens of micro amps.

Setting up the TSC requires to select one port for sampling for each group. One group consists of one sampling channel, where a sampling capacitor is connected, with one or more measurement channel, where a sensor pad is connected.

In each group, charge transferred from a measurement channel to the sampling channel automatically without firmware intervention. This is done independently from other groups. Thus channels belongs to each group are measured at the same time. In the same group, however measurement should be done sequentially among channels.

Following picture shows the test setup. A PCB touch panel on the right is connected to the STM32L052 breakout board on the left.

The MCU is programmed to measure the touch sensor value at every 100 msec. It transfers the measurement to a PC via UART connection. On the PC side, a python utility is prepared to collect data.

The python script also shows real-time plots as well as simple statistical data. From this data, you can easily check certain characteristics of the touch pad such as SNR, long-term drift, as well as the effect of the front cover structure on the sensitivity of the sensor.

It varies depending on the situation, minimum SNR of around 4 is considered to be necessary for the reliable operation. If the SNR is smaller than 3, additional post processing is required. If the noise is random, simple averaging can enhance the SNR effectively. Otherwise things get complicated. You may need to activate the spread spectrum (clock jittering) feature of the controller to make the noise spread out.

(source code)