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 +#include +#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 + +#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 +#include + +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 +#include +#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 +#include +#include +#include + +#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