diff --git a/samples/fatfs/source/mmc_pico_spi.c b/samples/fatfs/source/mmc_pico_spi.c
index 58e633b..a5865bd 100644
--- a/samples/fatfs/source/mmc_pico_spi.c
+++ b/samples/fatfs/source/mmc_pico_spi.c
@@ -15,12 +15,14 @@
 // see: https://qiita.com/Yukiya_Ishioka/items/6b5b6cb246f1d1e94461
 
 #include "pico/stdlib.h"
-#include "ws19804_c.h"
+#include <hardware/spi.h>
+#include <pico/stdio.h>
+#include "picocalc_c.h"
 
-#define DEF_SPI_TX_PIN  11
-#define DEF_SPI_RX_PIN  12
-#define DEF_SPI_SCK_PIN 10
-#define DEF_SPI_CSN_PIN 22
+#define DEF_SPI_TX_PIN  19
+#define DEF_SPI_RX_PIN  16
+#define DEF_SPI_SCK_PIN 18
+#define DEF_SPI_CSN_PIN 17
 
 #define FCLK_FAST() { }
 #define FCLK_SLOW() { }
@@ -80,26 +82,57 @@ static volatile UINT Timer1, Timer2;        /* 1kHz decrement timer stopped at z
 static BYTE CardType;   /* Card type flags */
 
 
+void HWReadSPI(unsigned char *buff, int cnt) {
+    spi_read_blocking(Pico_SD_SPI_MOD, 0xff, buff, cnt);
+}
+
+void HWSendSPI(const unsigned char *buff, int cnt) {
+    spi_write_blocking(Pico_SD_SPI_MOD, buff, cnt);
+}
 
 /*-----------------------------------------------------------------------*/
 /* SPI controls (Platform dependent)                                     */
 /*-----------------------------------------------------------------------*/
 
 /* Initialize MMC interface */
-static void init_spi (void)
+static void init_sd_spi (void)
 {
+
+    //initilase GPIO ports
+    gpio_init(DEF_SPI_SCK_PIN );
+    gpio_init(DEF_SPI_TX_PIN);
+    gpio_init(DEF_SPI_RX_PIN);
+    gpio_put(DEF_SPI_SCK_PIN,0);
+    gpio_put(DEF_SPI_TX_PIN,0);
+    gpio_set_pulls(DEF_SPI_RX_PIN,true,false);
+
+    //set GPIO post function
+    gpio_set_function(DEF_SPI_SCK_PIN, GPIO_FUNC_SPI); // SCK
+    gpio_set_function(DEF_SPI_TX_PIN, GPIO_FUNC_SPI);  // TX
+    gpio_set_function(DEF_SPI_RX_PIN, GPIO_FUNC_SPI);  // RX
+
+    gpio_set_dir(DEF_SPI_SCK_PIN,GPIO_OUT);
+    gpio_set_dir(DEF_SPI_TX_PIN,GPIO_OUT);
+    gpio_set_dir(DEF_SPI_RX_PIN,GPIO_IN);
+
+
     /* CS# */
     gpio_init( DEF_SPI_CSN_PIN );
+    gpio_set_function(DEF_SPI_CSN_PIN, GPIO_FUNC_SIO);
     gpio_set_dir( DEF_SPI_CSN_PIN, GPIO_OUT);
 
     CS_HIGH();            /* Set CS# high */
-    
+
+     //initalise SPI module
+    spi_init(Pico_SD_SPI_MOD, 250000);
+    spi_set_format(Pico_SD_SPI_MOD, 8, 0, 0, SPI_MSB_FIRST);
+
     sleep_ms(10);
 }
 
 static BYTE rcvr_spi() {
     BYTE buff;
-    ws19804_read_blocking(0xff, &buff, 1);
+    HWReadSPI( &buff, 1);
     return buff;
 }
 
@@ -109,12 +142,12 @@ static void rcvr_spi_multi (
     UINT btr        /* Number of bytes to receive (even number) */
 )
 {
-    ws19804_read_blocking(0xff, buff, btr);
+    HWReadSPI(buff, btr);
 }
 
 
 static void xmit_spi(BYTE buff) {
-    ws19804_write_blocking(&buff, 1);
+    HWSendSPI(&buff, 1);
 }
 
 /* Send multiple byte */
@@ -123,7 +156,7 @@ static void xmit_spi_multi (
     UINT btx            /* Number of bytes to send (even number) */
 )
 {
-    ws19804_write_blocking(buff, btx);
+    HWSendSPI(buff, btx);
 }
 
 
@@ -298,7 +331,7 @@ DSTATUS disk_initialize (
 
 
     if (drv) return STA_NOINIT;         /* Supports only drive 0 */
-    init_spi();                         /* Initialize SPI */
+    init_sd_spi();                         /* Initialize SPI */
 
     if (Stat & STA_NODISK) return Stat; /* Is card existing in the soket? */
 
diff --git a/samples/font/mono8x16/mono8x16.cpp b/samples/font/mono8x16/mono8x16.cpp
index 0e322f2..317e79f 100644
--- a/samples/font/mono8x16/mono8x16.cpp
+++ b/samples/font/mono8x16/mono8x16.cpp
@@ -2,71 +2,127 @@
 
 namespace mono8x16 {
 
-static constexpr int W = 8;
-static constexpr int H = 16;
-static constexpr int CHAR_STRIDE = (W * H + 7) / 8;
+    static constexpr int W = 8;
+    static constexpr int H = 16;
+    static constexpr int CHAR_STRIDE = (W * H + 7) / 8;
 
-void draw_char_rgb444(
-    uint8_t *dest,
-    int dest_stride,
-    int dest_w,
-    int dest_h,
-    int x0,
-    int y0,
-    const char c,
-    uint16_t color
-) {
-    if (c <= 0x20 || 0x80 <= c) return;
-    const uint8_t *rd_ptr = bmp + (c - 0x20) * CHAR_STRIDE;
-    for (int iy = 0; iy < H; iy++) {
-        int y = y0 + iy;
-        uint8_t pattern = *(rd_ptr++);
-        if (0 <= y && y < dest_h) {
-            for (int ix = 0; ix < W; ix++) {
-                int x = x0 + ix;
-                if ((pattern & 1) && 0 <= x && x < dest_w) {
-                    int wr_index = dest_stride * y + x * 3 / 2;
-                    if ((x & 1) == 0) {
-                        dest[wr_index  ] = (color >> 4) & 0xff;
-                        dest[wr_index+1] &= 0x0f;
-                        dest[wr_index+1] |= (color << 4) & 0xf0;
+    void draw_char_rgb444(
+            uint8_t *dest,
+            int dest_stride,
+            int dest_w,
+            int dest_h,
+            int x0,
+            int y0,
+            const char c,
+            uint16_t color
+    ) {
+        if (c <= 0x20 || 0x80 <= c) return;
+        const uint8_t *rd_ptr = bmp + (c - 0x20) * CHAR_STRIDE;
+        for (int iy = 0; iy < H; iy++) {
+            int y = y0 + iy;
+            uint8_t pattern = *(rd_ptr++);
+            if (0 <= y && y < dest_h) {
+                for (int ix = 0; ix < W; ix++) {
+                    int x = x0 + ix;
+                    if ((pattern & 1) && 0 <= x && x < dest_w) {
+                        int wr_index = dest_stride * y + x * 3 / 2;
+                        if ((x & 1) == 0) {
+                            dest[wr_index] = (color >> 4) & 0xff; //高8位
+                            dest[wr_index + 1] &= 0x0f;
+                            dest[wr_index + 1] |= (color << 4) & 0xf0;//低4位
+                        } else {
+                            dest[wr_index] &= 0xf0;
+                            dest[wr_index] |= (color >> 8) & 0xf;//高4位
+                            dest[wr_index + 1] = color & 0xff;//低8位
+                        }
                     }
-                    else {
-                        dest[wr_index  ] &= 0xf0;
-                        dest[wr_index  ] |= (color >> 8) & 0xf;
-                        dest[wr_index+1] = color & 0xff;
+                    pattern >>= 1;
+                }
+            }
+        }
+    }
+
+    void draw_string_rgb444(
+            uint8_t *dest,
+            int dest_stride,
+            int dest_w,
+            int dest_h,
+            int x0,
+            int y0,
+            const char *str,
+            uint16_t color
+    ) {
+        char c;
+        while ((c = *(str++)) != '\0') {
+            draw_char_rgb444(
+                    dest,
+                    dest_stride,
+                    dest_w,
+                    dest_h,
+                    x0,
+                    y0,
+                    c,
+                    color
+            );
+            x0 += W + 1;
+        }
+    }
+
+    void draw_char_rgb565(
+            uint8_t *dest,
+            int dest_stride,
+            int dest_w,
+            int dest_h,
+            int x0,
+            int y0,
+            const char c,
+            uint16_t color
+    ) {
+        if (c <= 0x20 || 0x80 <= c) return;
+        const uint8_t *rd_ptr = bmp + (c - 0x20) * CHAR_STRIDE;
+        for (int iy = 0; iy < H; iy++) {
+            int y = y0 + iy;
+            uint8_t pattern = *(rd_ptr++);
+            if (0 <= y && y < dest_h) {
+                for (int ix = 0; ix < W; ix++) {
+                    int x = x0 + ix;
+                    if ((pattern & 1) && 0 <= x && x < dest_w) {
+                        int wr_index = dest_stride * y + x * 2;  // 每个像素占2字节
+                        dest[wr_index   ] = (color >> 8) & 0xff;
+                        dest[wr_index + 1 ] = (color & 0xff);
+
                     }
+                    pattern >>= 1;
                 }
-                pattern >>= 1;
             }
         }
     }
-}
 
-void draw_string_rgb444(
-    uint8_t *dest,
-    int dest_stride,
-    int dest_w,
-    int dest_h,
-    int x0,
-    int y0,
-    const char* str,
-    uint16_t color
-) {
-    char c;
-    while ((c = *(str++)) != '\0') {
-        draw_char_rgb444(
-            dest,
-            dest_stride,
-            dest_w,
-            dest_h,
-            x0,
-            y0,
-            c,
-            color
-        );
-        x0 += W + 1;
+    void draw_string_rgb565(
+            uint8_t *dest,
+            int dest_stride,
+            int dest_w,
+            int dest_h,
+            int x0,
+            int y0,
+            const char *str,
+            uint16_t color
+    ) {
+        char c;
+        while ((c = *(str++)) != '\0') {
+            draw_char_rgb565(
+                    dest,
+                    dest_stride,
+                    dest_w,
+                    dest_h,
+                    x0,
+                    y0,
+                    c,
+                    color
+            );
+            x0 += W + 1;
+        }
     }
-}
+
 
 }
diff --git a/samples/font/mono8x16/mono8x16.hpp b/samples/font/mono8x16/mono8x16.hpp
index b18fedc..764445a 100644
--- a/samples/font/mono8x16/mono8x16.hpp
+++ b/samples/font/mono8x16/mono8x16.hpp
@@ -29,6 +29,27 @@ void draw_string_rgb444(
     uint16_t color
 );
 
+
+    void draw_char_rgb565(
+            uint8_t *dest,
+            int dest_stride,
+            int dest_w,
+            int dest_h,
+            int x0,
+            int y0,
+            const char c,
+            uint16_t color
+    );
+     void draw_string_rgb565(
+            uint8_t *dest,
+            int dest_stride,
+            int dest_w,
+            int dest_h,
+            int x0,
+            int y0,
+            const char* str,
+            uint16_t color
+    );
 };
 
 #endif
diff --git a/samples/v3/CMakeLists.txt b/samples/v3/CMakeLists.txt
new file mode 100644
index 0000000..56eccdd
--- /dev/null
+++ b/samples/v3/CMakeLists.txt
@@ -0,0 +1,65 @@
+cmake_minimum_required(VERSION 3.12)
+
+# Pull in PICO SDK (must be before project)
+set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
+
+project(picocalc_nes C CXX ASM)
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+
+# Initialize the SDK
+pico_sdk_init()
+
+add_executable(picocalc_nes)
+
+pico_generate_pio_header(picocalc_nes ${CMAKE_CURRENT_LIST_DIR}/picocalc.pio)
+
+set(SHAPONES_PATH ../../core)
+set(FATFS_PATH ../fatfs/source)
+
+target_sources(
+    picocalc_nes PRIVATE 
+    common.cpp
+    picocalc.cpp
+    i2ckbd.cpp
+    picocalc_nes.cpp
+    boot_menu.cpp
+    ${SHAPONES_PATH}/src/cpu.cpp
+    ${SHAPONES_PATH}/src/dma.cpp
+    ${SHAPONES_PATH}/src/input.cpp
+    ${SHAPONES_PATH}/src/interrupt.cpp
+    ${SHAPONES_PATH}/src/memory.cpp
+    ${SHAPONES_PATH}/src/ppu.cpp
+    ${SHAPONES_PATH}/src/apu.cpp
+    ${SHAPONES_PATH}/src/shapones.cpp
+    ${SHAPONES_PATH}/src/utils_std.cpp
+    ${FATFS_PATH}/ff.c
+    ${FATFS_PATH}/ffsystem.c
+    ${FATFS_PATH}/ffunicode.c
+    ${FATFS_PATH}/mmc_pico_spi.c
+    ../font/mono8x16/mono8x16.cpp
+    ../font/mono8x16/mono8x16_bmp.cpp
+)
+
+target_include_directories(
+    picocalc_nes PRIVATE
+    .
+    ${SHAPONES_PATH}/include
+    ${FATFS_PATH}
+    ../font/mono8x16
+)
+
+target_link_libraries(
+    picocalc_nes PRIVATE
+    pico_stdlib
+    hardware_pio
+    hardware_pwm
+    hardware_dma
+    hardware_irq
+    hardware_spi
+    hardware_i2c
+    pico_multicore
+)
+
+pico_add_extra_outputs(picocalc_nes)
diff --git a/samples/v3/Makefile.sample.mk b/samples/v3/Makefile.sample.mk
new file mode 100644
index 0000000..61488a1
--- /dev/null
+++ b/samples/v3/Makefile.sample.mk
@@ -0,0 +1,38 @@
+.PHONY: all install clean
+
+REPO_DIR=$(shell pwd)
+SRC_DIR=.
+BUILD_DIR=build
+
+BIN_NAME=pico_nes_ws19804.uf2
+BIN=$(BUILD_DIR)/$(BIN_NAME)
+
+CORE_DIR=../../core
+FATFS_DIR=../fatfs/source
+
+SRC_LIST=\
+	$(wildcard $(SRC_DIR)/*.*) \
+	$(wildcard $(SRC_DIR)/../font/mono8x16/*.*) \
+	$(wildcard $(CORE_DIR)/src/*.*) \
+	$(wildcard $(CORE_DIR)/include/shapones/*.*)  \
+	$(wildcard $(FATFS_DIR)/*.*) 
+
+all: $(BIN)
+
+$(BIN): $(SRC_LIST) CMakeLists.txt
+	mkdir -p $(BUILD_DIR)
+	cd $(BUILD_DIR) \
+		&& cmake .. \
+		&& make -j
+	@echo "------------------------------"
+	@echo "UF2 File:"
+	@echo $(REPO_DIR)/$(BIN)
+	@ls -l $(REPO_DIR)/$(BIN)
+
+install: $(BIN)
+	sudo mkdir -p /mnt/e
+	sudo mount -t drvfs e: /mnt/e
+	cp $(BIN) /mnt/e/.
+
+clean:
+	rm -rf build
diff --git a/samples/v3/boot_menu.cpp b/samples/v3/boot_menu.cpp
new file mode 100644
index 0000000..c34e5fe
--- /dev/null
+++ b/samples/v3/boot_menu.cpp
@@ -0,0 +1,174 @@
+#include "boot_menu.hpp"
+
+#include <string.h>
+
+#include "picocalc.hpp"
+#include "mono8x16.hpp"
+#include "common.hpp"
+
+#include "ff.h"
+#include "diskio.h"
+
+constexpr int MAX_FILES = 64;
+
+// enumerate NES files
+static int enum_files(FATFS *fs, char **fname_list, int *fsize_list);
+
+// show ROM select menu
+static int rom_select(int num_files, char **file_list);
+
+// load NES file
+static bool load_nes(const char *fname, int size);
+
+bool boot_menu() {
+    FATFS fs;
+    char *fname_list[MAX_FILES];
+    int fsize_list[MAX_FILES];
+    int num_files = enum_files(&fs, fname_list, fsize_list);
+    if (num_files <= 0) {
+        return false;
+    }
+
+    int index = rom_select(num_files, fname_list);
+
+    if ( ! load_nes(fname_list[index], fsize_list[index])) {
+        return false;
+    }
+
+    for (int i = 0; i < num_files; i++) {
+        free(fname_list[i]);
+    }
+    
+    return true;
+}
+
+static int enum_files(FATFS *fs, char **fname_list, int *fsize_list) {
+    char tmp[16];
+    int y = 20;
+    constexpr int x_result = 120;
+
+    DSTATUS dsts;
+    FRESULT fres;
+
+    clear_frame_buff();
+
+    // Init FatFS
+    draw_string(0, y, "Init FatFS"); update_lcd();
+    dsts = disk_initialize(0);
+    //dsts = STA_NOINIT;
+    if (dsts & STA_NOINIT) {
+        sprintf(tmp, "[NG] code=0x%x", (int)dsts);
+        draw_string(x_result, y, tmp); update_lcd();
+        return -1;
+    }
+    draw_string(x_result, y, "[OK]"); update_lcd();
+    y += 20;
+
+    // Mount
+    draw_string(0, y, "Disk Mount"); update_lcd();
+    fres = f_mount(fs, "", 0);
+    if (fres != FR_OK) {
+        sprintf(tmp, "[NG] code=0x%x", (int)fres);
+        draw_string(x_result, y, tmp); update_lcd();
+        return -1;
+    }
+    draw_string(x_result, y, "[OK]"); update_lcd();
+    y += 20;
+
+    // Enumerate File
+    draw_string(0, y, "File List"); update_lcd();
+
+    DIR dobj;
+    FILINFO finfo;
+    fres = f_findfirst(&dobj, &finfo, "", "*.nes");
+    int num_files = 0;
+    while (fres == FR_OK && finfo.fname[0] && strlen(finfo.fname) > 4 ) {
+        fname_list[num_files] = (char*)malloc(strlen(finfo.fname) + 1);
+        strcpy(fname_list[num_files], finfo.fname);
+        fsize_list[num_files] = finfo.fsize;
+        fres = f_findnext(&dobj, &finfo);
+        num_files++;
+    }
+    if (fres != FR_OK) {
+        sprintf(tmp, "[NG] code=0x%x", (int)fres);
+        draw_string(x_result, y, tmp); update_lcd();
+        return -1;
+    }
+    f_closedir(&dobj);
+    draw_string(x_result, y, "[OK]"); update_lcd();
+    y += 20;
+
+    return num_files;
+}
+
+static int rom_select(int num_files, char **file_list) {
+    int sel_index = 0;
+    int page_index = 0;
+    char buf[6];
+    for(;;) {
+        clear_frame_buff();
+        page_index = (sel_index / items_per_page) * items_per_page;
+
+        for (int i = 0; i < items_per_page && (page_index + i) < num_files; i++) {
+            draw_string(20, i * 20, file_list[page_index + i]);
+        }
+
+        sprintf(buf,"%02d",sel_index+1);
+        draw_string(0,FRAME_BUFF_HEIGHT-20,buf);
+        draw_string(0, (sel_index % items_per_page) * 20, "=>");
+
+        update_lcd();
+
+        switch (wait_key()) {
+            case nes::input::BTN_UP:
+                if (sel_index == 0) {
+                    sel_index = num_files - 1;
+                } else {
+                    sel_index--;
+                }
+                break;
+            case nes::input::BTN_DOWN:
+                if (sel_index == num_files - 1) {
+                    sel_index = 0;
+                } else {
+                    sel_index++;
+                }
+                break;
+            case nes::input::BTN_A:
+            case nes::input::BTN_START:
+                return sel_index;
+        }
+    }
+}
+
+static bool load_nes(const char *fname, int size) {
+    FIL fil;
+    FRESULT fr;
+
+    clear_frame_buff();
+    draw_string(0, 0, "Loading...");
+    update_lcd();
+
+    fr = f_open(&fil, fname, FA_READ);
+    if (fr) {
+        draw_string(0, 20, "File open failed.");
+        update_lcd();
+        return false;
+    }
+
+    UINT sz;
+    uint8_t *ines = (uint8_t*)malloc(size);
+    fr = f_read(&fil, ines, size, &sz);
+    if (fr) {
+        draw_string(0, 20, "File read failed.");
+        update_lcd();
+        return false;
+    }
+
+    f_close(&fil);
+
+    nes::memory::map_ines(ines);
+    picocalc::set_spi_speed(SYS_CLK_FREQ / 4);
+
+    return true;
+}
diff --git a/samples/v3/boot_menu.hpp b/samples/v3/boot_menu.hpp
new file mode 100644
index 0000000..a0f9b70
--- /dev/null
+++ b/samples/v3/boot_menu.hpp
@@ -0,0 +1,7 @@
+#ifndef BOOT_MENU_HPP
+#define BOOT_MENU_HPP
+
+// show boot menu
+bool boot_menu();
+constexpr int items_per_page = 11;//(FRAME_BUFF_HEIGHT/20)-1
+#endif
diff --git a/samples/v3/common.cpp b/samples/v3/common.cpp
new file mode 100644
index 0000000..00654fd
--- /dev/null
+++ b/samples/v3/common.cpp
@@ -0,0 +1,173 @@
+#include "common.hpp"
+#include "i2ckbd.hpp"
+
+#include <hardware/irq.h>
+#include <hardware/structs/timer.h>
+
+static absolute_time_t now;
+uint8_t keycheck = 0;
+uint8_t keyread = 0;
+uint8_t frame_buff[FRAME_BUFF_STRIDE * nes::SCREEN_HEIGHT];
+//0 == released, 1 = pressed
+int input_pins[] = {
+        PIN_PAD_A, PIN_PAD_B, PIN_PAD_SELECT, PIN_PAD_START,
+        PIN_PAD_UP, PIN_PAD_DOWN, PIN_PAD_LEFT, PIN_PAD_RIGHT
+};
+
+int wait_key() {
+    // wait any button pushed
+    int i = -1;
+    for (;;) {
+        sleep_ms(10);
+        for (i = 0; i < 8; i++) {
+            if (input_pins[i] == 1) {
+                input_pins[i] = 0;
+                return i;
+            }
+        }
+    }
+    return i;
+}
+
+void draw_string(int x, int y, const char *str) {
+    mono8x16::draw_string_rgb565(
+            frame_buff, FRAME_BUFF_STRIDE, FRAME_BUFF_WIDTH, FRAME_BUFF_HEIGHT,
+            x, y, str, 0xffff);
+}
+
+void clear_frame_buff() {
+    for (int i = 0; i < FRAME_BUFF_STRIDE * FRAME_BUFF_HEIGHT; i++) {
+        frame_buff[i] = 0;
+    }
+}
+
+void update_lcd() {
+    picocalc::start_write_data((picocalc::WIDTH - FRAME_BUFF_WIDTH) / 2, (picocalc::HEIGHT - FRAME_BUFF_HEIGHT) / 2,
+                               FRAME_BUFF_WIDTH, FRAME_BUFF_HEIGHT, frame_buff);
+    picocalc::finish_write_data();
+}
+
+void setBacklight(int level){//STM32: i2c reg is REG_ID_BKL(0x05)
+    //level is 0-100%
+    level*=255;
+    level/=100;
+    i2ckbd::I2C_Send_RegData(i2ckbd::I2C_KBD_ADDR,0x05,(uint8_t)level);
+}
+
+//keyboard key status to input_pins map
+void set_kdb_key(uint8_t pin_offset, uint8_t key_status) {
+
+    if (key_status == 1) {
+        input_pins[pin_offset] = 1;
+    } else if (key_status == 3) {
+        input_pins[pin_offset] = 0;
+    }
+}
+
+void kbd_interrupt() {
+    int kbd_ret;
+    int c;
+    static int ctrlheld = 0;
+    uint8_t key_stat = 0;//press,release, or hold
+    if (keycheck == 0) {
+        if (keyread == 0) {
+            kbd_ret = i2ckbd::write_i2c_kbd();
+            keyread = 1;
+        } else {
+            kbd_ret = i2ckbd::read_i2c_kbd();
+            keyread = 0;
+        }
+        keycheck=KEYCHECKTIME;
+    }
+    if (kbd_ret < 0) {
+        if (i2ckbd::check_if_failed() > 0) {
+            printf("try to reset i2c\n");
+            i2ckbd::reset_failed();
+            i2ckbd::init_i2c_kbd();//re-init
+        }
+    }
+
+    if (kbd_ret) {
+        if (kbd_ret == 0xA503)ctrlheld = 0;
+        else if (kbd_ret == 0xA502) {
+            ctrlheld = 1;
+        } else if ((kbd_ret & 0xff) == 1) {//pressed
+            key_stat = 1;
+        } else if ((kbd_ret & 0xff) == 3) {
+            key_stat = 3;
+        }
+
+        c = kbd_ret >> 8;
+        int realc = -1;
+        switch (c) {
+            case 0xA1:
+            case 0xA2:
+            case 0xA3:
+            case 0xA4:
+            case 0xA5:
+                realc = -1;//skip shift alt ctrl keys
+                break;
+            default:
+                realc = c;
+                break;
+        }
+
+        c = realc;
+        if (c >= 'a' && c <= 'z' && ctrlheld)c = c - 'a' + 1;
+
+        switch (c) {
+            case 0xb5://UP
+                set_kdb_key(4, key_stat);
+                break;
+            case 0xb6://DOWN
+                set_kdb_key(5, key_stat);
+                break;
+            case 0xb4://LEFT
+                set_kdb_key(6, key_stat);
+                break;
+            case 0xb7://RIGHT
+                set_kdb_key(7, key_stat);
+                break;
+            case '-':// select
+                set_kdb_key(2, key_stat);
+                break;
+            case '=':// start
+                set_kdb_key(3, key_stat);
+                break;
+            case '['://B
+                set_kdb_key(1, key_stat);
+                break;
+            case ']'://A
+                set_kdb_key(0, key_stat);
+                break;
+            default:
+                break;
+        }
+    }
+}
+
+static void __attribute__ ((optimize("-Os"))) __not_in_flash_func(timer_tick_cb)(unsigned alarm) {
+
+    absolute_time_t next;
+    update_us_since_boot(&next, to_us_since_boot(now) + ( TICKSPERSEC));
+    if (hardware_alarm_set_target(0, next)) {
+        update_us_since_boot(&next, time_us_64() + ( TICKSPERSEC));
+        hardware_alarm_set_target(0, next);
+    }
+
+    kbd_interrupt();
+    if(keycheck){
+        keycheck--;
+    }
+
+}
+
+void device_init() {
+
+    hardware_alarm_claim(0);
+    update_us_since_boot(&now, time_us_64());
+    hardware_alarm_set_callback(0, timer_tick_cb);
+    hardware_alarm_force_irq(0);
+
+    //setBacklight(80);
+}
\ No newline at end of file
diff --git a/samples/v3/common.hpp b/samples/v3/common.hpp
new file mode 100644
index 0000000..9ae098b
--- /dev/null
+++ b/samples/v3/common.hpp
@@ -0,0 +1,51 @@
+#ifndef COMMON_HPP
+#define COMMON_HPP
+
+#include "stdint.h"
+#include "pico/stdlib.h"
+#include "hardware/clocks.h"
+#include "hardware/gpio.h"
+#include "shapones/shapones.hpp"
+#include "picocalc.hpp"
+#include "mono8x16.hpp"
+
+// system clock frequency
+static constexpr uint32_t SYS_CLK_FREQ = 250 * MHZ;
+
+// frame buffer for DMA (RGB565)
+constexpr int FRAME_BUFF_WIDTH = 240;
+constexpr int FRAME_BUFF_STRIDE = FRAME_BUFF_WIDTH * 2;
+constexpr int FRAME_BUFF_HEIGHT = nes::SCREEN_HEIGHT;
+extern uint8_t frame_buff[FRAME_BUFF_STRIDE * nes::SCREEN_HEIGHT];
+
+// pad pins
+static constexpr int PIN_PAD_A      = 0;
+static constexpr int PIN_PAD_B      = 0;
+static constexpr int PIN_PAD_START  = 0;
+static constexpr int PIN_PAD_SELECT = 0;
+static constexpr int PIN_PAD_RIGHT  = 0;
+static constexpr int PIN_PAD_DOWN   = 0;
+static constexpr int PIN_PAD_LEFT   = 0;
+static constexpr int PIN_PAD_UP     = 0;
+extern int input_pins[];
+
+static constexpr uint TICKSPERSEC = 1000;   /* Ticks per second */
+static constexpr uint8_t KEYCHECKTIME=16;
+
+// wait until some key is pressed
+int wait_key();
+
+// clear frame buffer with black color
+void clear_frame_buff();
+
+// draw string to frame buffer
+void draw_string(int x, int y, const char *str);
+
+// transfer image from frame buffer to LCD
+void update_lcd();
+
+void device_init();
+
+void setBacklight(int);
+
+#endif
diff --git a/samples/v3/i2ckbd.cpp b/samples/v3/i2ckbd.cpp
new file mode 100644
index 0000000..15a3111
--- /dev/null
+++ b/samples/v3/i2ckbd.cpp
@@ -0,0 +1,79 @@
+#include <stdio.h>
+#include <pico/stdio.h>
+#include "i2ckbd.hpp"
+
+namespace i2ckbd{
+
+    static uint8_t i2c_inited = 0;
+    uint16_t i2c_failed = 0;
+
+    void init_i2c_kbd(){
+        gpio_set_function(I2C_KBD_SCL, GPIO_FUNC_I2C);
+        gpio_set_function(I2C_KBD_SDA, GPIO_FUNC_I2C);
+        i2c_init(I2C_KBD_MOD, I2C_KBD_SPEED);
+        gpio_pull_up(I2C_KBD_SCL);
+        gpio_pull_up(I2C_KBD_SDA);
+
+        i2c_inited = 1;
+    }
+
+    int write_i2c_kbd(){
+         int retval;
+        unsigned char msg[2];
+        msg[0] = 0x09;
+
+        if(i2c_inited == 0) return -1;
+
+        retval = i2c_write_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, msg, 1, false, 500000);
+        if ( retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
+            printf( "i2c write error\n");
+            i2c_failed+=1;
+            return -1;
+        }
+        return 0;
+    }
+
+    int read_i2c_kbd(){
+        int retval;
+        //static int ctrlheld=0;
+        uint16_t buff = 0;
+        unsigned char msg[2];
+        int c  = -1;
+        msg[0] = 0x09;
+
+        if(i2c_inited == 0) return -1;
+
+        retval = i2c_read_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, (unsigned char*)&buff, 2, false, 500000);
+        if ( retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
+            printf("i2c read error read\n");
+            i2c_failed+=1;
+            return -1;
+        }
+
+        return buff;
+    }
+
+    int I2C_Send_RegData(int i2caddr,int reg,char command){
+        int retval;
+        unsigned char I2C_Send_Buffer[2];
+        I2C_Send_Buffer[0]=reg;
+        I2C_Send_Buffer[1]=command;
+        uint8_t I2C_Sendlen=2;
+        uint16_t  I2C_Timeout=1000;
+
+        retval=i2c_write_timeout_us(I2C_KBD_MOD, (uint8_t)i2caddr, (uint8_t *)I2C_Send_Buffer, I2C_Sendlen,false, I2C_Timeout*1000);
+
+        if ( retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
+            printf( "I2C_Send_RegData write error\n");
+            return -1;
+        }
+        return 0;
+    }
+    int check_if_failed(){
+        return i2c_failed;
+    }
+    void reset_failed(){
+        i2c_deinit(I2C_KBD_MOD);
+        i2c_failed = 0;
+    }
+}
\ No newline at end of file
diff --git a/samples/v3/i2ckbd.hpp b/samples/v3/i2ckbd.hpp
new file mode 100644
index 0000000..01ba106
--- /dev/null
+++ b/samples/v3/i2ckbd.hpp
@@ -0,0 +1,26 @@
+#ifndef I2C_KEYBOARD_HPP
+#define I2C_KEYBOARD_HPP
+
+#include <pico/stdlib.h>
+#include <pico/platform.h>
+#include <hardware/gpio.h>
+#include <hardware/i2c.h>
+
+#define I2C_KBD_MOD i2c1
+
+namespace i2ckbd {
+
+    static constexpr uint I2C_KBD_SDA = 6;
+    static constexpr uint I2C_KBD_SCL = 7;
+    static constexpr uint I2C_KBD_SPEED = 400000;
+    static constexpr uint I2C_KBD_ADDR = 0x1F;
+
+
+    void init_i2c_kbd();
+    int write_i2c_kbd();
+    int read_i2c_kbd();
+    int check_if_failed();
+    void reset_failed();
+    int I2C_Send_RegData(int i2caddr, int reg, char command);
+}
+#endif
diff --git a/samples/v3/picocalc.cpp b/samples/v3/picocalc.cpp
new file mode 100644
index 0000000..91cd5de
--- /dev/null
+++ b/samples/v3/picocalc.cpp
@@ -0,0 +1,436 @@
+
+#include "picocalc.hpp"
+#include "picocalc_c.h"
+#include "picocalc.pio.h"
+
+namespace picocalc {
+
+static int sys_clock_hz;
+static direction_t curr_dir = EMPTY;
+static int curr_speed = 10 * MHZ;
+
+static PIO spi_pio;
+static uint spi_sm;
+static uint spi_offset;
+static uint dma_tx;
+
+// SPI push byte
+static void pio_push(uint8_t data);
+
+// SPI pop byte
+static uint8_t pio_pop();
+
+// wait for SPI to idle
+static void pio_wait_idle();
+
+// setup SPI direction and speed
+static void setup_pio(direction_t new_dir, int new_speed);
+
+// set SPI direction
+static void set_spi_direction(direction_t new_dir);
+
+void init(int sys_clk_hz) {
+    sys_clock_hz = sys_clk_hz;
+
+    spi_pio = pio0;
+    spi_sm = 0;
+
+    spi_offset = pio_add_program(spi_pio, &picocalc_program );
+    pio_gpio_init(spi_pio, PIN_MISO);
+    pio_gpio_init(spi_pio, PIN_MOSI);
+    pio_gpio_init(spi_pio, PIN_SCK);
+    pio_sm_set_consecutive_pindirs(spi_pio, spi_sm, PIN_MISO, 1, false);
+    pio_sm_set_consecutive_pindirs(spi_pio, spi_sm, PIN_MOSI, 1, true);
+    pio_sm_set_consecutive_pindirs(spi_pio, spi_sm, PIN_SCK, 1, true);
+
+    dma_tx = dma_claim_unused_channel(true);
+    
+    gpio_init(PIN_RST);
+    gpio_init(PIN_DC);
+    gpio_init(PIN_LCD_CS);
+    gpio_init(PIN_RAM_CS);
+    gpio_set_dir(PIN_RST, GPIO_OUT);
+    gpio_set_dir(PIN_DC, GPIO_OUT);
+    gpio_set_dir(PIN_RAM_CS,GPIO_OUT);
+    gpio_set_dir(PIN_LCD_CS, GPIO_OUT);
+
+    gpio_put(PIN_RST, 1);
+    gpio_put(PIN_LCD_CS, 1);
+    gpio_put(PIN_RAM_CS,1);
+    
+    // hardware reset
+    gpio_put(PIN_RST, 1);
+    sleep_ms(1);
+    gpio_put(PIN_RST, 0);
+    sleep_ms(10);
+    gpio_put(PIN_RST, 1);
+    sleep_ms(10);
+
+    {
+        uint8_t data[] = {0xc3};
+        write_command(0xF0,data,sizeof(data));
+    }
+    {
+        uint8_t data[] = {0x96};
+        write_command(0xF0,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x48};
+        write_command(0x36,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x65};
+        write_command(0x3A,data,sizeof(data));
+    }
+    {
+        // Frame Rate Control
+        uint8_t data[] = {0xA0};
+        write_command(0xB1,data,sizeof(data));
+    }
+    {
+        uint8_t data[] = {0x00};
+        write_command(0xB4,data,sizeof(data));
+    }
+    {
+        uint8_t data[] = {0xc6};
+        write_command(0xB7,data,sizeof(data));
+    }
+    {
+        uint8_t data[] = {0x02,0xE0};
+        write_command(0xB9,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x80,0x06};
+        write_command(0xC0,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x15};
+        write_command(0xC1,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0xA7};
+        write_command(0xC2,data,sizeof(data));
+    }
+    {
+        uint8_t data[] = {0x04};
+        write_command(0xC5,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x40,0x8A,0x00,0x00,0x29,0x19,0xAA,0x33};
+        write_command(0xE8,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0xF0,0x06,0x0F,0x05,0x04,0x20,0x37,0x33,0x4C,0x37,0x13,0x14,0x2B,0x31};
+        write_command(0xE0,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0xF0,0x11,0x1B,0x11,0x0F,0x0A,0x37,0x43,0x4C,0x37,0x13,0x13,0x2C,0x32};
+        write_command(0xE1,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x3C};
+        write_command(0xF0,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x69};
+        write_command(0xF0,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x00};
+        write_command(0x35,data,sizeof(data));
+    }
+    write_command(0x11);//TFT_SLPOUT
+    sleep_ms(120);
+    //TFT_INVON
+    write_command(0x21);
+
+    clear(0);
+
+    write_command(0x29);//TFT_DISPON
+    sleep_ms(120);
+
+    {
+        uint8_t data[] = {0x00,0x00,0x01,0x3F};
+        write_command(0x2A,data,sizeof(data));
+    }
+
+    {
+        uint8_t data[] = {0x00,0x00,0x01,0x3F};
+        write_command(0x2B,data,sizeof(data));
+    }
+    write_command(0x2C);
+
+/*
+    {
+        //// Positive Gamma Control
+        uint8_t data[] = {0x00,0x03,0x09,0x08,0x16,0x0a,0x3f,0x78,0x4c,0x09,0x0a,0x08,0x16,0x1a,0x0f};
+        write_command(0xE0,data,sizeof(data));
+    }
+    {
+        //// Negative Gamma Control
+        uint8_t data[] = {0x00,0x16,0x19,0x03,0x0f,0x05,0x32,0x45,0x46,0x04,0x0e,0x0d,0x35,0x37,0x0f};
+        write_command(0xE1,data,sizeof(data));
+    }
+    {
+        // Power Control 1
+        uint8_t data[] = {0x17,0x15};
+        write_command(0xC0,data,sizeof(data));
+    }
+    {
+        // Power Control 2
+        uint8_t data[] = {0x41};
+        write_command(0xC1,data,sizeof(data));
+    }
+    {
+        //// VCOM Control
+        uint8_t data[] = {0x00,0x12,0x80};
+        write_command(0xC5,data,sizeof(data));
+    }
+    {
+        // Memory Access Control
+        //MX,MV, RGB mode
+        uint8_t data[] = {0x48}; // (0x40 | 0x20) or 0x48
+        write_command(0x36,data,sizeof(data));
+    }
+
+    {
+        // Pixel Interface Format
+        uint8_t data[] = {0x65}; //0x65=16 bit colour for SPI,0x66=18bits
+        write_command(0x3A,data,sizeof(data));
+    }
+
+    {
+        // Interface Mode Control
+        uint8_t data[] = {0x00};
+        write_command(0xB0,data,sizeof(data));
+    }
+    {
+        // Frame Rate Control
+        uint8_t data[] = {0xA0};
+        write_command(0xB1,data,sizeof(data));
+    }
+
+    {
+        // Display Inversion Control
+        uint8_t data[] = {0x02};
+        write_command(0xB4,data,sizeof(data));
+    }
+
+    {
+        // Display Function Control
+        uint8_t data[] = {0x02,0x02,0x3B};
+        write_command(0xB6,data,sizeof(data));
+    }
+
+    {
+        // Entry Mode Set
+        uint8_t data[] = {0xC6,0xE9,0x00};
+        write_command(0xB7,data,sizeof(data));
+    }
+
+    {
+        // Adjust Control 3
+        uint8_t data[] = {0xA9,0x51,0x2C,0x82};
+        write_command(0xF7,data,sizeof(data));
+    }
+
+        write_command(0x11);//TFT_SLPOUT
+        sleep_ms(120);
+
+        //TFT_INVON
+        write_command(0x21);
+
+        clear(0);
+
+        write_command(0x29);//TFT_DISPON
+        sleep_ms(120);
+*/
+}
+
+void setup_pio(direction_t new_dir, int new_speed) {
+    if (new_dir == curr_dir && new_speed == curr_speed) return;
+
+    float div = sys_clock_hz / 2.f / new_speed;
+    
+    pio_sm_set_enabled(spi_pio, spi_sm, false);
+
+    // load new PIO
+    if (new_dir == TX) {
+        pio_sm_config pio_cfg = picocalc_program_get_default_config(spi_offset);
+        sm_config_set_out_pins(&pio_cfg, PIN_MOSI, 1);
+        sm_config_set_sideset_pins(&pio_cfg, PIN_SCK);
+        sm_config_set_fifo_join(&pio_cfg, PIO_FIFO_JOIN_TX);
+        sm_config_set_out_shift(&pio_cfg, false, true, 8);
+        sm_config_set_clkdiv(&pio_cfg, div);
+        pio_sm_init(spi_pio, spi_sm, spi_offset, &pio_cfg);
+    }
+    else if (new_dir == RX) {
+        pio_sm_config pio_cfg = picocalc_program_get_default_config(spi_offset);
+        sm_config_set_out_pins(&pio_cfg, PIN_MOSI, 1);
+        sm_config_set_in_pins(&pio_cfg, PIN_MISO);
+        sm_config_set_sideset_pins(&pio_cfg, PIN_SCK);
+        sm_config_set_out_shift(&pio_cfg, false, true, 8);
+        sm_config_set_in_shift(&pio_cfg, false, true, 8);
+        sm_config_set_clkdiv(&pio_cfg, div);
+        pio_sm_init(spi_pio, spi_sm, spi_offset, &pio_cfg);
+    }
+
+    hw_set_bits(&spi_pio->input_sync_bypass, 1u << PIN_MISO);
+    pio_sm_set_enabled(spi_pio, spi_sm, true);
+
+    curr_dir = new_dir;
+    curr_speed = new_speed;
+}
+
+void set_spi_direction(direction_t new_dir) {
+    setup_pio(new_dir, curr_speed);
+}
+
+void set_spi_speed(int new_speed) {
+    setup_pio(curr_dir, new_speed);
+}
+
+void clear(uint16_t color) {
+    uint8_t data[WIDTH * 2];
+    for (int x = 0; x < WIDTH * 2; x++) {
+        data[x] = color;
+    }
+    for (int y = 0; y < HEIGHT; y++) {
+        start_write_data(0, y, WIDTH, 1, data);
+        finish_write_data();
+    }
+}
+
+void start_write_data(int x0, int y0, int w, int h, uint8_t *data) {
+    int x1 = x0 + w - 1;
+    int y1 = y0 + h - 1;
+    {
+        uint8_t xcoord[] = {
+            (uint8_t)(x0 >> 8), 
+            (uint8_t)(x0 & 0xff), 
+            (uint8_t)(x1 >> 8), 
+            (uint8_t)(x1 & 0xff)
+        };
+        write_command(0x2a, xcoord, sizeof(xcoord));
+    }
+    {
+        uint8_t ycoord[] = {
+            (uint8_t)(y0 >> 8), 
+            (uint8_t)(y0 & 0xff), 
+            (uint8_t)(y1 >> 8), 
+            (uint8_t)(y1 & 0xff)
+        };
+        write_command(0x2b, ycoord, sizeof(ycoord));
+    }
+    //write_command(0x2c, data, w * h * 3 / 2);
+
+    {
+        gpio_put(PIN_DC, 0);
+        gpio_put(PIN_LCD_CS, 0);
+        uint8_t cmd = 0x2c;
+        write_blocking(&cmd, 1);
+        gpio_put(PIN_DC, 1);
+        
+        {
+            dma_channel_config dma_cfg = dma_channel_get_default_config(dma_tx);
+            channel_config_set_transfer_data_size(&dma_cfg, DMA_SIZE_8);
+            channel_config_set_dreq(&dma_cfg, pio_get_dreq(spi_pio, spi_sm, true));
+            dma_channel_configure(dma_tx, &dma_cfg,
+                                    &spi_pio->txf[spi_sm], // write address
+                                    data, // read address
+                                    w * h * 2, // element count (each element is of size transfer_data_size)
+                                    false); // don't start yet
+        }
+        
+        dma_start_channel_mask(1u << dma_tx);
+    }
+
+}
+
+void finish_write_data() {
+    dma_channel_wait_for_finish_blocking(dma_tx);
+    pio_wait_idle();
+    gpio_put(PIN_LCD_CS, 1);
+}
+
+void write_command(uint8_t cmd, const uint8_t *data, int len) {
+    set_spi_direction(TX);
+    gpio_put(PIN_DC, 0); // command mode
+    gpio_put(PIN_LCD_CS, 0);
+    write_blocking(&cmd, 1);
+    if (data) {
+        gpio_put(PIN_DC, 1); // data mode
+        write_blocking(data, len);
+    }
+    gpio_put(PIN_LCD_CS, 1);
+}
+
+void write_command(uint8_t cmd) {
+    set_spi_direction(TX);
+    write_command(cmd, nullptr, 0);
+}
+
+void write_blocking(const uint8_t *data, int len) {
+    set_spi_direction(TX);
+    for(int i = 0; i < len; i++) {
+        pio_push(data[i]);
+    }
+    pio_wait_idle();
+}
+
+void read_blocking(uint8_t tx_repeat, uint8_t *buff, int len) {
+    set_spi_direction(RX);
+    int tx_remain = len, rx_remain = len;
+    io_rw_8 *txfifo = (io_rw_8 *) &spi_pio->txf[spi_sm];
+    io_rw_8 *rxfifo = (io_rw_8 *) &spi_pio->rxf[spi_sm];
+    while (tx_remain || rx_remain) {
+        if (tx_remain && !pio_sm_is_tx_fifo_full(spi_pio, spi_sm)) {
+            *txfifo = tx_repeat;
+            --tx_remain;
+        }
+        if (rx_remain && !pio_sm_is_rx_fifo_empty(spi_pio, spi_sm)) {
+            *buff++ = *rxfifo;
+            --rx_remain;
+        }
+    }
+}
+
+static void pio_push(uint8_t data) {
+    while (pio_sm_is_tx_fifo_full(spi_pio, spi_sm)) { }
+    *(volatile uint8_t*)&spi_pio->txf[spi_sm] = data;
+}
+
+static uint8_t pio_pop() {
+    while (pio_sm_is_rx_fifo_empty(spi_pio, spi_sm)) { }
+    return *(volatile uint8_t*)&spi_pio->rxf[spi_sm];
+}
+
+static void pio_wait_idle() {
+    uint32_t stall_mask = 1u << (spi_sm + PIO_FDEBUG_TXSTALL_LSB);
+    spi_pio->fdebug = stall_mask;
+    while (!(spi_pio->fdebug & stall_mask)) { }
+}
+
+extern "C" {
+
+void ws19804_write_blocking(const uint8_t *buff, int len) {
+    picocalc::write_blocking(buff, len);
+}
+void ws19804_read_blocking(uint8_t tx_repeat, uint8_t *buff, int len) {
+    picocalc::read_blocking(tx_repeat, buff, len);
+}
+
+}
+
+}
diff --git a/samples/v3/picocalc.hpp b/samples/v3/picocalc.hpp
new file mode 100644
index 0000000..6b82739
--- /dev/null
+++ b/samples/v3/picocalc.hpp
@@ -0,0 +1,58 @@
+#ifndef WS19804_HPP
+#define WS19804_HPP
+
+#include "stdint.h"
+#include "pico/stdlib.h"
+#include "hardware/pio.h"
+#include "hardware/gpio.h"
+#include "hardware/clocks.h"
+#include "hardware/dma.h"
+
+namespace picocalc {
+
+static constexpr int WIDTH = 320;
+static constexpr int HEIGHT = 320;//LCD SCREEN HEIGHT,not logical Height
+static constexpr uint PIN_DC = 14;
+static constexpr uint PIN_LCD_CS = 13;
+//static constexpr uint PIN_TP_CS = 16;
+static constexpr uint PIN_RAM_CS = 21;
+static constexpr uint PIN_SCK = 10;
+static constexpr uint PIN_MOSI = 11;
+static constexpr uint PIN_MISO = 12;
+static constexpr uint PIN_RST = 15;
+//static constexpr uint PIN_BL = 13;
+
+enum direction_t {
+    EMPTY, TX, RX
+};
+
+// setup GPIO, PIO, LCD
+void init(int sys_clk_hz);
+
+// set SPI clock frequency
+void set_spi_speed(int new_speed);
+
+// clear display
+void clear(uint16_t color);
+
+// start DMA
+void start_write_data(int x0, int y0, int w, int h, uint8_t *data);
+
+// wait DMA to finish
+void finish_write_data();
+
+// write LCD command
+void write_command(uint8_t cmd, const uint8_t *data, int len);
+
+// write LCD command
+void write_command(uint8_t cmd);
+
+// SPI write
+void write_blocking(const uint8_t *data, int len);
+
+// SPI read
+void read_blocking(uint8_t tx_repeat, uint8_t *buff, int len);
+
+}
+
+#endif
diff --git a/samples/v3/picocalc.pio b/samples/v3/picocalc.pio
new file mode 100644
index 0000000..2af8cc1
--- /dev/null
+++ b/samples/v3/picocalc.pio
@@ -0,0 +1,7 @@
+.program picocalc
+.side_set 1
+
+.wrap_target
+    out pins, 1     side 0
+    in pins, 1      side 1
+.wrap
diff --git a/samples/v3/picocalc_c.h b/samples/v3/picocalc_c.h
new file mode 100644
index 0000000..8599482
--- /dev/null
+++ b/samples/v3/picocalc_c.h
@@ -0,0 +1,14 @@
+#ifndef WS19804_C_H
+#define WS19804_C_H
+
+#include "stdint.h"
+
+#define Pico_SD_SPI_MOD spi0
+
+// SPI write function for FatFS
+void ws19804_write_blocking(const uint8_t *data, int len);
+
+// SPI read function for FatFS
+void ws19804_read_blocking(uint8_t tx_repeat, uint8_t *buff, int len);
+
+#endif
diff --git a/samples/v3/picocalc_nes.cpp b/samples/v3/picocalc_nes.cpp
new file mode 100644
index 0000000..fbc1069
--- /dev/null
+++ b/samples/v3/picocalc_nes.cpp
@@ -0,0 +1,251 @@
+#include "hardware/gpio.h"
+#include "hardware/clocks.h"
+#include "hardware/vreg.h"
+#include "hardware/watchdog.h"
+#include "pico/stdlib.h"
+#include "pico/multicore.h"
+#include "pico/util/queue.h"
+#include "pico/time.h"
+
+#include "i2ckbd.hpp"
+#include "picocalc.hpp"
+
+#include "pwm_audio.hpp"
+
+#include "shapones/shapones.hpp"
+
+#include "common.hpp"
+#include "boot_menu.hpp"
+
+// monitor pin for debugging
+static constexpr int PIN_MONITOR = 1;
+
+// speaker PWM out
+static constexpr int PIN_SPEAKER = 26;
+
+// APU configuration
+static constexpr int SPK_LATENCY = 256;
+static constexpr int SPK_PWM_FREQ = 22050;
+
+/*
+// NES color table ,rgb444
+static const uint16_t COLOR_TABLE[] = {
+    0x555, 0x027, 0x019, 0x308, 0x406, 0x603, 0x500, 0x410, 
+    0x230, 0x040, 0x040, 0x040, 0x034, 0x000, 0x000, 0x000, 
+    0x999, 0x05C, 0x33F, 0x62E, 0x81B, 0xA16, 0x922, 0x740, 
+    0x560, 0x270, 0x080, 0x072, 0x067, 0x000, 0x000, 0x000, 
+    0xFFF, 0x5AF, 0x78F, 0xB6F, 0xE5F, 0xF5B, 0xF76, 0xD82, 
+    0xAB0, 0x7C0, 0x5D2, 0x3D7, 0x3BD, 0x444, 0x000, 0x000, 
+    0xFFF, 0xADF, 0xCCF, 0xDBF, 0xFBF, 0xFBD, 0xFBB, 0xEC9, 
+    0xDD7, 0xBE7, 0xAE9, 0x9EB, 0xADE, 0xAAA, 0x000, 0x000,
+};
+*/
+
+// NES color table ,rgb565
+static const uint16_t COLOR_TABLE[] = {
+0x52aa, 0x010e, 0x0093, 0x3011, 0x400c, 0x6006, 0x6000, 0x4080,
+0x2180, 0x0220, 0x0220, 0x0220, 0x0188, 0x0000, 0x0000, 0x0000,
+0x9cd3, 0x02b9, 0x319f, 0x611d, 0x8897, 0xa88c, 0xa904, 0x7220,
+0x5320, 0x23a0, 0x0440, 0x03a4, 0x032e, 0x0000, 0x0000, 0x0000,
+0xffff, 0x555f, 0x745f, 0xbb3f, 0xeabf, 0xfab7, 0xfbac, 0xdc44,
+0xadc0, 0x7660, 0x56e4, 0x36ee, 0x35db, 0x4228, 0x0000, 0x0000,
+0xffff, 0xaeff, 0xce7f, 0xdddf, 0xfddf, 0xfddb, 0xfdd7, 0xee73,
+0xdeee, 0xbf6e, 0xaf73, 0x9f77, 0xaefd, 0xad55, 0x0000, 0x0000
+};
+
+/*
+static const uint16_t COLOR_TABLE[] = {
+        0x52AA, 0x0117, 0x00D2, 0x1861, 0x20C2, 0x30A0, 0x2840, 0x2108,
+        0x10C2, 0x0020, 0x0020, 0x0020, 0x018A, 0x0000, 0x0000, 0x0000,
+        0x9CD3, 0x02BF, 0x1CFF, 0x4E3F, 0x733F, 0xA89F, 0x9105, 0x82E0,
+        0x5B00, 0x2120, 0x0010, 0x0019, 0x0053, 0x0000, 0x0000, 0x0000,
+        0xFFFF, 0x52FF, 0x7BFF, 0xB7FF, 0xE7FF, 0xF79E, 0xFFDC, 0xD740,
+        0xAC00, 0x8300, 0x5A40, 0x3AA0, 0x3B7C, 0x5294, 0x0000, 0x0000,
+        0xFFFF, 0xBFFF, 0xDDFF, 0xEFFC, 0xFFFF, 0xFFFB, 0xFFEA, 0xD749,
+        0xCE79, 0xC6B8, 0xAD8C, 0x94EF, 0xC678, 0xAD55, 0x0000, 0x0000,
+};
+ */
+// line buffer FIFO between core1 --> core0
+// <------------- STRIDE ------------>
+// +---------+-----------------------+    A
+// | y coord | color numbers (256px) |    |
+// | 1 Byte  | 256 Byte              |    |
+// +---------+-----------------------+  DEPTH
+// |    :    |           :           |    |
+// |    :    |           :           |    |
+// +---------+-----------------------+    V
+static constexpr int LINE_FIFO_DEPTH = 8;
+static constexpr int LINE_FIFO_STRIDE = nes::SCREEN_WIDTH + 1;
+static uint8_t line_buff[LINE_FIFO_DEPTH * LINE_FIFO_STRIDE];
+static volatile int line_fifo_wptr = 0;
+static volatile int line_fifo_rptr = 0;
+
+// sound buffer for DMA
+static uint8_t spk_buff[SPK_LATENCY];
+
+// start game
+static void boot_nes();
+
+// core0 main loop
+static void cpu_loop();
+
+// core1 main loop
+static void ppu_loop();
+
+// APU DMA finish IRQ handler
+static void apu_dma_handler();
+
+// let APU fill the sound buffer
+static void apu_fill_buffer(PwmAudio::sample_t *buff);
+
+// PWM audio driver
+PwmAudio speaker(PIN_SPEAKER, SPK_LATENCY, 8, (float)SYS_CLK_FREQ / SPK_PWM_FREQ, apu_dma_handler);
+
+int main() {
+    // setup clocks
+    vreg_set_voltage(VREG_VOLTAGE_1_30);
+    sleep_ms(100);
+    stdio_init_all();
+    set_sys_clock_khz(SYS_CLK_FREQ / 1000, true);
+    setup_default_uart();
+    
+    // setup monitor pin
+    gpio_init(PIN_MONITOR);
+    gpio_set_dir(PIN_MONITOR, GPIO_OUT);
+    gpio_put(PIN_MONITOR, 0);
+
+    i2ckbd::init_i2c_kbd();
+    device_init();
+
+    picocalc::init(SYS_CLK_FREQ);
+
+    // show boot menu
+    if ( ! boot_menu()) {
+        for(;;) sleep_ms(100);
+    }
+
+    // boot game
+    boot_nes();
+
+    return 0;
+}
+
+static void boot_nes() {
+    // set APU sampling rate
+    nes::apu::set_sampling_rate(SPK_PWM_FREQ);
+
+    // reset
+    nes::reset();
+
+    // start APU loop
+    apu_fill_buffer(speaker.get_buffer(0));
+    apu_fill_buffer(speaker.get_buffer(1));
+    speaker.play();
+    
+    // start PPU loop
+    multicore_launch_core1(ppu_loop);
+
+    // start CPU loop
+    cpu_loop();
+}
+
+
+static void cpu_loop() {
+    auto t_last_frame = get_absolute_time();
+    int frame_count = 0;
+    char fps_str[16];
+    for(;;) {
+        // run CPU
+        nes::cpu::service();
+
+        // update input status
+        nes::input::InputStatus input_status;
+        input_status.raw = 0;
+        for(int i = 0; i < 8; i++) {
+            if (input_pins[i] == 1) {
+                input_status.raw |= (1 << i);
+            }
+        }
+        if( (input_status.raw & 0x0C) == 0x0C) {
+            watchdog_enable(1, 1);
+            watchdog_reboot(0, 0, 0);
+        }
+        nes::input::set_raw(0, input_status);
+
+        // check line buffer FIFO state
+        int fifo_rptr = line_fifo_rptr;
+        if (line_fifo_wptr != fifo_rptr) {
+            // convert color number to RGB565
+            int y = line_buff[fifo_rptr * LINE_FIFO_STRIDE];
+            int x0 = (nes::SCREEN_WIDTH - FRAME_BUFF_WIDTH) / 2;
+            uint8_t *rd_ptr = line_buff + (fifo_rptr * LINE_FIFO_STRIDE + x0 + 1);
+            uint8_t *wr_ptr = frame_buff + (y * FRAME_BUFF_STRIDE);
+            for (int x = 0; x < FRAME_BUFF_WIDTH; x++) {
+
+                uint16_t c0 = COLOR_TABLE[*(rd_ptr++) & 0x3f];
+                *(wr_ptr++) = (c0 >> 8) & 0xff;
+                *(wr_ptr++)  = c0 & 0xff;
+
+            }
+            line_fifo_rptr = (fifo_rptr + 1) % LINE_FIFO_DEPTH;
+
+            if (y == nes::SCREEN_HEIGHT - 1) {
+                // fps measurement
+                auto t_now = get_absolute_time();
+                if (frame_count < 60-1) {
+                    frame_count++;
+                }
+                else {
+                    auto t_diff = absolute_time_diff_us(t_last_frame, t_now);
+                    float fps = (60.0f * 1000000) / t_diff;
+                    sprintf(fps_str, "%5.2ffps", fps);
+                    t_last_frame = t_now;
+                    frame_count = 0;
+                }
+
+                // DMA transfer
+                picocalc::finish_write_data();
+                //draw_string(0, 0, fps_str);
+                picocalc::start_write_data((picocalc::WIDTH - FRAME_BUFF_WIDTH) / 2, (picocalc::HEIGHT - FRAME_BUFF_HEIGHT) / 2, FRAME_BUFF_WIDTH, FRAME_BUFF_HEIGHT, frame_buff);
+                static int tmp = 0;
+                //gpio_put(PIN_MONITOR, tmp);
+                tmp ^= 1;
+            }
+        }
+    }
+}
+
+
+static void ppu_loop() {
+    constexpr int FRAME_DELAY_US = 16666;
+    absolute_time_t next_time = delayed_by_us(get_absolute_time(), FRAME_DELAY_US);
+    
+    for(;;) {
+        int wptr = line_fifo_wptr;
+        bool eol = nes::ppu::service(&line_buff[wptr * LINE_FIFO_STRIDE + 1]);
+        int y = nes::ppu::current_focus_y();
+        if (eol && y < nes::SCREEN_HEIGHT) {
+            // Vsync
+            if (y == 0) {
+                busy_wait_until(next_time);
+                next_time = delayed_by_us(get_absolute_time(), FRAME_DELAY_US);
+            }
+
+            // push new line
+            line_buff[wptr * LINE_FIFO_STRIDE] = y;
+            line_fifo_wptr = (wptr + 1) % LINE_FIFO_DEPTH;
+        }
+    }
+}
+
+static void apu_dma_handler() {
+    speaker.flip_buffer();
+    apu_fill_buffer(speaker.get_next_buffer());
+}
+
+static void apu_fill_buffer(PwmAudio::sample_t *buff) {
+    nes::apu::service(spk_buff, speaker.LATENCY);
+    for (int i = 0; i < speaker.LATENCY; i++) {
+        buff[i] = spk_buff[i];
+    }
+}
\ No newline at end of file
diff --git a/samples/v3/pwm_audio.hpp b/samples/v3/pwm_audio.hpp
new file mode 100644
index 0000000..73c2c24
--- /dev/null
+++ b/samples/v3/pwm_audio.hpp
@@ -0,0 +1,109 @@
+#ifndef PWM_AUDIO_HPP
+#define PWM_AUDIO_HPP
+
+#include "stdint.h"
+#include "hardware/gpio.h"
+#include "hardware/pwm.h"
+#include "hardware/dma.h"
+#include "hardware/clocks.h"
+#include "hardware/irq.h"
+#include "pico/stdlib.h"
+
+using DmaFinishdedHandler = void(*)();
+
+class PwmAudio;
+static PwmAudio *inst;
+
+class PwmAudio {
+public:
+    using sample_t = uint32_t;
+
+    const dma_channel_transfer_size DMA_WIDTH =
+        sizeof(sample_t) == 1 ? DMA_SIZE_8 :
+        sizeof(sample_t) == 2 ? DMA_SIZE_16 :
+                                DMA_SIZE_32;
+    
+    const int PIN;
+    const int SLICE_NUM;
+    const int SAMPLE_BITS;
+    const float FREQUENCY;
+    const int LATENCY;
+    DmaFinishdedHandler dma_handler;
+
+    sample_t *buff;
+    int dma_ch;
+    int playing_bank;
+
+    PwmAudio(int pin, int latency, int sample_bits, float freq_ratio, DmaFinishdedHandler dma_handler) :
+        PIN(pin),
+        SLICE_NUM(pwm_gpio_to_slice_num(pin)),
+        SAMPLE_BITS(sample_bits),
+        FREQUENCY(freq_ratio),
+        LATENCY(latency),
+        buff(new sample_t[latency]),
+        dma_handler(dma_handler)
+    {
+        gpio_set_function(PIN, GPIO_FUNC_PWM);
+
+        int pwm_period = 1 << SAMPLE_BITS;
+        float pwm_clkdiv = freq_ratio / pwm_period;
+
+        pwm_config pwm_cfg = pwm_get_default_config();
+        pwm_config_set_clkdiv(&pwm_cfg, pwm_clkdiv);
+        pwm_config_set_wrap(&pwm_cfg, pwm_period - 1);
+        pwm_init(SLICE_NUM, &pwm_cfg, true);
+
+        pwm_set_gpio_level(PIN, 1 << (SAMPLE_BITS-1));
+
+        inst = this;
+    }
+
+    void play() {
+        dma_ch = dma_claim_unused_channel(true);
+        dma_channel_set_irq0_enabled(dma_ch, true);
+        irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
+        irq_set_enabled(DMA_IRQ_0, true);
+
+        playing_bank = 0;
+        start_dma();
+    }
+    
+    void stop() {
+        dma_channel_unclaim(dma_ch);
+        pwm_set_gpio_level(PIN, 1 << (SAMPLE_BITS-1));
+    }
+
+    void start_dma() {
+        dma_channel_config dma_cfg = dma_channel_get_default_config(dma_ch);
+        channel_config_set_transfer_data_size(&dma_cfg, DMA_SIZE_32);
+        channel_config_set_read_increment(&dma_cfg, true);
+        channel_config_set_write_increment(&dma_cfg, false);
+        channel_config_set_dreq(&dma_cfg, DREQ_PWM_WRAP0 + SLICE_NUM);
+        dma_channel_configure(
+            dma_ch,
+            &dma_cfg,
+            &pwm_hw->slice[SLICE_NUM].cc, // write addr
+            buff + (playing_bank * LATENCY), // read addr
+            LATENCY, // number of data
+            true // start immediately
+        );
+    }
+
+    void flip_buffer() {
+        int fill_bank = playing_bank;
+        playing_bank = (playing_bank + 1) & 1;
+        inst->start_dma();
+        dma_hw->ints0 = (1u << dma_ch);
+    }
+
+    sample_t* get_buffer(int bank) {
+        return buff + (bank * LATENCY);
+    }
+
+    sample_t* get_next_buffer() {
+        int next_bank = (playing_bank + 1) & 1;
+        return get_buffer(next_bank);
+    }
+};
+
+#endif
