VoCore2: Serial Port Transfer Small File

When I develop, sometimes wifi or network not working, or ssh can not connect, only way to fix it to use serial port, but serial port do not support transfer file…there are no way to transfer small files, like a simple driver or small application, have to reboot and use uboot to reload everything, and kermit load new firmware to uboot is really slow.

PS: I know somebody can use kermit or sz/lz, but first of all, you need to have them in firmware already, that is a chicken-and-egg problem 🙂

I find default busybox shell(sh) support command “echo -ne”, this is a amazing way to transfer binary data by serial port, and do not need any depends, so I write a simple application, hope this helps.

Usage Example, save TEST.ko to VoCore2 /tmp/TEST.ko: sspdt TEST.ko /dev/ttyACM0

Note: after transfer, remember to run md5sum to check if the data transfer is correct and complete, serial port do not guarantee that. From my test, < 100KB file normally do not have problem, and speed approx 1.5KB/s

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libserialport.h>

// apt install libserialport-dev
// gcc sspdt.c -o sspdt -lserialport
// speed is approx 1.5KB/s

#define min(a, b) ((a) < (b) ? (a) : (b))

uint8_t *alloc_from_file(const char *name, int *size)
    FILE *fp = fopen(name, "rb");
    if (fp == NULL)
        return NULL;

    fseek(fp, 0, SEEK_END);

    *size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    uint8_t *buf = malloc(*size);
    if (buf)
        fread(buf, 1, *size, fp);

    return buf;

struct sp_port *ssport_open(const char *name)
    struct sp_port *port;
    int baudrate = 115200, res;

    res = sp_get_port_by_name(name, &port);
    if (SP_OK != res)
        return NULL;

    res = sp_open(port, SP_MODE_READ_WRITE);
    if (SP_OK != res) {
        printf("sp_open=%d, %s\n", res, sp_last_error_message());
        return NULL;

    // clear input/output buffer.
    sp_flush(port, SP_BUF_BOTH);

    // gd32f150 supported protocol 115200, 8n1.
    sp_set_baudrate(port, baudrate);
    sp_set_bits(port, 8);
    sp_set_parity(port, SP_PARITY_NONE);
    sp_set_stopbits(port, 1);

    // necessary, or system will drop 0x11 and 0x13.
    sp_set_flowcontrol(port, SP_FLOWCONTROL_NONE);

    return port;

void ssport_close(struct sp_port *port)
    sp_flush(port, SP_BUF_BOTH);

int ssport_write(struct sp_port *port, const void *buf, size_t count)
    int wbyte;
    wbyte = sp_blocking_write(port, buf, count, 600);
    //print_hex("wr", buf, wbyte);
    return wbyte;

int ssport_read(struct sp_port *port, void *buf, size_t count)
    int rbyte;
    rbyte = sp_blocking_read(port, buf, count, 600);
    //print_hex("rd", buf, rbyte);
    return rbyte;

void process(int cur, int total)
    printf("%9dB / %9dB [%3.1f%%]", cur, total, (float)cur / total * 100);

void get_file_name(const char *name, char *out)
    int i = strlen(name) - 1;
    for (; i >= 0; i--) {
        if (name[i] == '/' || name[i] == '\\')
    strcpy(out, name + i);

int main(int argc, char *argv[])
    if (argc != 3) {
        printf("sspdt [input file] [serial port]\r\n");
        printf("send small files to device term by serial port.\r\n");
        return 0;

    int size = 0;
    uint8_t *buf = alloc_from_file(argv[1], &size);
    struct sp_port *sp = ssport_open(argv[2]);

    ssport_write(sp, "\n\n", 2);

    char name[128] = {0};
    get_file_name(argv[1], name);
    printf("file %s will save to device /tmp/%s\r\n", argv[1], name);

    // each time read 16 bytes
    for (int i = 0; i < size; i += 16) {
        char cmd[0x100] = {0};
        sprintf(cmd, "echo -ne '");
        for (int j = 0; j < min(size - i, 16); j++) {
            char tmp[16];
            sprintf(tmp, "\\x%02x", buf[i + j]);
            strcat(cmd, tmp);
        strcat(cmd, "' >> /tmp/");
        strcat(cmd, name);
        strcat(cmd, "\n");

        ssport_write(sp, cmd, strlen(cmd));
        process(i, size);
    process(size, size);

    return 1;

MPro: Support 60Hz FPS

We are proud to announce that MPRO now supports a refresh rate of 60Hz.

show jpeg on 5inch screen

Please download and try the following: https://vocore.io/misc/v2scrctl.zip. Use the latest firmware and the screen_test.c file.

The main upgrade is that the MPRO firmware now supports baseline JPEG compression, which is typically much smaller than bitmap. For example, an 800×480 16-bit bitmap is approximately 800KB, but a high-quality JPEG image is only 100~200KB. This reduction in size can increase the refresh speed by 300%~500%, allowing us to easily achieve a 60Hz refresh rate.

Note: The software part(like Simhub) may still need some time to adjust, so this feature will take some time to be implemented for dashboard users.

MPRO: Partially Draw

To increase FPS for bigger screens, partially draw is necessary and important.

MPRO driver board now support this.

Before send the frame, need to setup a “window”, then data will only write to this window.

Here is example code(download full code at https://vocore.io/misc/v2scrctl-release.zip, tools/screen_test.partily.c)

uint8_t cmd3[] = {0x00, 0x2c, 0x20, 0x4e, 0x00, 0x00, 0x63, 0x00, 0x65, 0x00, 0x64, 0x00};
res = libusb_control_transfer(handle, 0x40, 0xb0, 0, 0, cmd3, sizeof(cmd3), 100);
res = libusb_bulk_transfer(handle, 0x02, partily_frame1, sizeof(partily_frame1), NULL, 100);

cmd3[6] = cmd3[8] = 250;
res = libusb_control_transfer(handle, 0x40, 0xb0, 0, 0, cmd3, sizeof(cmd3), 100);
res = libusb_bulk_transfer(handle, 0x02, partily_frame1, sizeof(partily_frame1), NULL, 100);

explain cmd3:

  • 0x00, 0x2c: write to memory command code.
  • 0x20, 0x4e, 0x00, 0x00: write size is 0x00004e20(20000) bytes, this is because our paritly draw image is 100×100 resolution, and bitmap16, so it is 100x100x2 = 20000bytes.
  • 0x63, 0x00: start position X, in example it is 0x0063(99) pixels.
  • 0x65, 0x00: start position Y, in example it is 0x0064(101) pixels.
  • 0x64, 0x00: image window width or line width, in example it is 0x65(100) pixels.
  • no image window height, it can be calculated from [write size] / [window width] / 2

After this call, it will write 100×100 image block to screen position left top corner = (99,101)

PS: another block at (250, 250).

VoCore2: OpenWrt 23.05 Sound Part

Finally sound part works. Thanks to contributor patches.

The problem is caused by DMA did not correctly stopped before I2S stop, then DMA write to invalid memory address then casued watch dog reset the hole system, no oops.

Patches has upload to: https://github.com/Vonger/vocore2/tree/master/openwrt.2305

OpenWrt source code patch openwrt.patch folder, linux kernel patch at kernel.sound folder, need to copy to target/linux/ramips/patches-5.15

In menuconfig, need to select kmod-mt7628-es8388, kmod-i2c-mt7628 etc… For simple just copy menu.config to .config in your openwrt folder, will compile everything.

VoCore2: OpenWrt 23.05 Part2

Continuing with my last blog, the sound part. Last time I found there was a problem, some kernel code was broken because I could patch and compile it but once I ran aplay the kernel would crash.

I checked openwrt 18 and openwrt 19, the patch worked well for both of them, but from openwrt 20, the latest version, none of them worked.

I compared their source code about the sound card main reference clock, its output 12M was normal; I2C, I2S, which were used to control and send data, this part was normal too; sound card part driver es8328.c and es8328-i2c.c both seemed fine to me. Only one doubt was the DMA driver, we sent data to memory and it carried data to I2S then the sound card played sound. One more thing was DMA driver was moved to staging in linux kernel, that meant somebody found the problem too. If DMA has any bug like writing data to a place that surprises the kernel, the unhappy kernel crashes with no apparent reason and reboots itself.

So its time to hack the linux kernel again…Good luck to me.

I think the DMA driver’s source code has not changed, but the linux kernel somehow has changed. That brought a new problem. The code is in drivers/staging/ralink-gdma/ralink-gdma.c, this linux kernel is 5.15.137.

6.8inch Screen CH32V003+WS2812 DEMO

In order to simplify the development of dashboards based on the VoCore MPRO screen, CH32V003, and WS2812, we have created a simple demo that is now available for order at vocore.io/store.

The shell was designed using FreeCAD. We are not experts in design, so please feel free to modify it. 🙂

Download here https://github.com/vonger/6IN8SHELL

Reference link: https://github.com/Vonger/V7B_WS2812B

Reference link: https://vonger.cn/?p=15457

back side
front side

Note: This demo is designed to show how MPRO works with WS2812 LEDs. I am not skilled in design, so it is not recommended for DDU users. However, if you wish to use it as a low-cost DDU, I cannot stop you. 🙂

Setup in Simhub: Devices => Add new device => Create standard device => Generic VoCore Screen with I2C LEDs

Note: if you have exists arduino LED profile, export then import to Generic VoCore Screen with I2C LEDs, it will work.

VoCore2: OpenWrt 23.05

It is pretty straight to use OpenWrt 23.05. It works very smoothly. If you do not need to customize such as default IP, WiFi default enable, almost do not need to patch anything.

  • WiFi — works normal, 72Mbps for one antenna.
  • Ethernet — works normal, 100Mbps.
  • USB 2.0 — works normal.
  • SD card — works normal, need to patch dts.
  • Sound — need to check.

note: patch sdcard, add the following code to end of mt7628an_vocore2.dts

&sdhci {
        status = "okay";

PS: download feeds is very slow for openwrt, maybe it is because its source code do not add –depth 1. Patch ./scripts/feeds:157, add depth 1 to init_commit, after that, feed update speed increase 10 times. Or directly download from openwrt feeds, then move to openwrt/feeds folder then call ./script/feeds install -a

VoCore2: PoE upgrade

Current VoCore2 PoE version is based on MAX5987AETE, which has become too expensive and can no longer be afforded. Since last year, we have been searching for a new solution, but finding one with low power consumption and smaller size has proven to be difficult. After considering various options, the best balance solution for VoCore2 Ultimate + PoE was ultimately chosen.

The new solution is superior to the MAX5987AETE solution in most aspects, as it significantly reduces heat and allows for greater power delivery to devices. Additionally, this new solution simplifies the bill of materials (BOM) while maintaining a cost that is only one-third of the 5987 solution’s price.

Due to the reduced costs associated with this new solution, we are pleased to announce that the VoCore2 Ultimate + PoE’s price has been lowered from $44.98 to just $29.98, new version will start to ship after Chinese New Year.

The new version PoE also open source, have fun. 🙂

VoCore2: Upgrade

Happy new year 🙂

VoCore2 now are all upgrade to 32MB NOR flash version and price keeping same as 16MB version. So with bigger flash we can do even more.

Have fun in 2024!