commit 1e0915ac08cea0a69d450c356fea22f86cd0bf66 Author: kuwoyuki Date: Fri Nov 8 00:11:06 2024 +0600 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e98125 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.pio +.vscode +*.elf +*.hex +*.lst +*.bin +*.map +.clangd +.cache diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6176b1e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ch32v003fun"] + path = ch32v003fun + url = https://github.com/cnlohr/ch32v003fun.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0d1d60a --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all : flash + +TARGET:=ch32ext +CH32V003FUN=./ch32v003fun/ch32v003fun +MINICHLINK=./ch32v003fun/minichlink +PREFIX=riscv64-elf +LINKER_SCRIPT=./ch32ext.ld +# CFLAGS += -march=rv32ec_zicsr + +include $(CH32V003FUN)/ch32v003fun.mk + +flash : cv_flash +clean : cv_clean diff --git a/ch32ext.c b/ch32ext.c new file mode 100644 index 0000000..3214ad1 --- /dev/null +++ b/ch32ext.c @@ -0,0 +1,157 @@ +#include + +#include "ch32v003fun.h" +#include "simple_eeprom.h" +#include "state_manager.h" + +#define RS485_DIR (1 << 0) // RS485 direction control +#define SRCLR (1 << 1) // Shift register clear (active low) +#define SRCLK (1 << 2) // Shift register clock +#define RCLK (1 << 3) // Storage register clock +#define SER (1 << 4) // Serial data input + +// "protocol" defines +#define BOARD_ADDRESS 0x01 +#define CMD_SET_OUTPUTS 0x01 +#define BROADCAST_ADDR 0xFF + +// "protocol" states +enum rx_states { + STATE_ADDR, + STATE_IGNORE, + STATE_CMD, + STATE_DATA_HIGH, + STATE_DATA_LOW +}; + +void setup_uart(int uartBRR) { + RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | + RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO; + + // RS485_DIR as output, set recv mode + GPIOC->CFGLR &= ~(0xf << (4 * 0)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 0); + GPIOC->BCR = RS485_DIR; + + // UART pins (PD5=TX, PD6=RX) + GPIOD->CFGLR &= ~(0xf << (4 * 5) | 0xf << (4 * 6)); + GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5); + GPIOD->CFGLR |= GPIO_CNF_IN_FLOATING << (4 * 6); + + // 115200 8N1 + USART1->CTLR1 = + USART_WordLength_8b | USART_Parity_No | USART_Mode_Tx | USART_Mode_Rx; + USART1->CTLR2 = USART_StopBits_1; + USART1->CTLR3 = USART_HardwareFlowControl_None; + USART1->BRR = uartBRR; + USART1->CTLR1 |= CTLR1_UE_Set; +} + +void setup_gpio(void) { + RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; + + // PC1-PC4 as out + for (int pin = 1; pin <= 4; pin++) { + GPIOC->CFGLR &= ~(0xf << (4 * pin)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * pin); + } + + GPIOC->BCR = SRCLR | SRCLK | RCLK | SER; +} + +void shift_out(uint16_t data) { + GPIOC->BCR = RCLK; + + // reorder + uint16_t ordered_data = ((data & 0xFF00) >> 8) | // 1st reg (Q0-Q7) + ((data & 0x00FF) << 8); // 2nd reg (Q8-Q15) + + // shift out 16 bits, MSB 1st + for (int8_t i = 15; i >= 0; i--) { + GPIOC->BCR = SRCLK; + if (ordered_data & (1 << i)) { + GPIOC->BSHR = SER; + } else { + GPIOC->BCR = SER; + } + GPIOC->BSHR = SRCLK; + } + + // latch + GPIOC->BSHR = RCLK; + GPIOC->BCR = RCLK; +} + +uint8_t uart_receive_byte(void) { + while (!(USART1->STATR & USART_FLAG_RXNE)); + return USART1->DATAR & 0xFF; +} + +int main(void) { + SystemInit(); + setup_gpio(); + setup_uart(UART_BRR); + + // reset shift reg + GPIOC->BSHR = SRCLR; + GPIOC->BSHR = RCLK; // rising edge on RCLK + GPIOC->BCR = RCLK; + + // load last state + uint16_t current_state = load_state(); + printf("Loaded state: %04x\n", current_state); + shift_out(current_state); + + uint8_t rx_state = STATE_ADDR; + uint8_t addr, cmd; + uint16_t data = 0; + uint8_t bytes_to_ignore = 0; + + printf("SystemClk:%d\r\n", FUNCONF_SYSTEM_CORE_CLOCK); + printf("ChipID:%08lx\r\n", *(uint32_t *)0x1FFFF7C4); + + while (1) { + uint8_t byte = uart_receive_byte(); + + switch (rx_state) { + case STATE_ADDR: + addr = byte; + if (addr == BOARD_ADDRESS || addr == BROADCAST_ADDR) { + rx_state = STATE_CMD; + } else { + // crc??)) + bytes_to_ignore = 3; // skip cmd + data_high + data_low + rx_state = STATE_IGNORE; + } + break; + + case STATE_IGNORE: + bytes_to_ignore--; + if (bytes_to_ignore == 0) { + rx_state = STATE_ADDR; + } + break; + + case STATE_CMD: + cmd = byte; + rx_state = STATE_DATA_HIGH; + break; + + case STATE_DATA_HIGH: + data = (uint16_t)byte << 8; + rx_state = STATE_DATA_LOW; + break; + + case STATE_DATA_LOW: + data |= byte; + if (cmd == CMD_SET_OUTPUTS) { + printf("Set outputs: 0x%04X\n", data); + shift_out(data); + save_state(data); + dump_eeprom(); + } + rx_state = STATE_ADDR; + break; + } + } +} \ No newline at end of file diff --git a/ch32ext.ld b/ch32ext.ld new file mode 100644 index 0000000..1c82465 --- /dev/null +++ b/ch32ext.ld @@ -0,0 +1,151 @@ +ENTRY(InterruptVector) + +MEMORY { + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 16K /* 16KB FLASH */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 2K /* 2KB RAM */ +} + +SECTIONS { + /* Initialization code section */ + .init : { + _sinit = .; + . = ALIGN(4); + KEEP(*(SORT_NONE(.init))) + . = ALIGN(4); + _einit = .; + } >FLASH AT>FLASH + + /* Main code section */ + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + *(.rodata) + *(.rodata*) + *(.gnu.linkonce.t.*) + . = ALIGN(4); + } >FLASH AT>FLASH + + .fini : { + KEEP(*(SORT_NONE(.fini))) + . = ALIGN(4); + } >FLASH AT>FLASH + + PROVIDE(_etext = .); + PROVIDE(_eitcm = .); + + .preinit_array : { + PROVIDE_HIDDEN(__preinit_array_start = .); + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN(__preinit_array_end = .); + } >FLASH AT>FLASH + + .init_array : { + PROVIDE_HIDDEN(__init_array_start = .); + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.init_array EXCLUDE_FILE(*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o) .ctors)) + PROVIDE_HIDDEN(__init_array_end = .); + } >FLASH AT>FLASH + + .fini_array : { + PROVIDE_HIDDEN(__fini_array_start = .); + KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP(*(.fini_array EXCLUDE_FILE(*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o) .dtors)) + PROVIDE_HIDDEN(__fini_array_end = .); + } >FLASH AT>FLASH + + .ctors : { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP(*crtbegin.o(.ctors)) + KEEP(*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP(*(EXCLUDE_FILE(*crtend.o *crtend?.o) .ctors)) + KEEP(*(SORT(.ctors.*))) + KEEP(*(.ctors)) + } >FLASH AT>FLASH + + .dtors : { + KEEP(*crtbegin.o(.dtors)) + KEEP(*crtbegin?.o(.dtors)) + KEEP(*(EXCLUDE_FILE(*crtend.o *crtend?.o) .dtors)) + KEEP(*(SORT(.dtors.*))) + KEEP(*(.dtors)) + } >FLASH AT>FLASH + + .dalign : { + . = ALIGN(4); + PROVIDE(_data_vma = .); + } >RAM AT>FLASH + + .dlalign : { + . = ALIGN(4); + PROVIDE(_data_lma = .); + } >FLASH AT>FLASH + + .data : { + . = ALIGN(4); + *(.gnu.linkonce.r.*) + *(.data .data.*) + *(.gnu.linkonce.d.*) + . = ALIGN(8); + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.*) + *(.sdata2*) + *(.gnu.linkonce.s.*) + . = ALIGN(8); + *(.srodata.cst16) + *(.srodata.cst8) + *(.srodata.cst4) + *(.srodata.cst2) + *(.srodata .srodata.*) + . = ALIGN(4); + PROVIDE(_edata = .); + } >RAM AT>FLASH + + .bss : { + . = ALIGN(4); + PROVIDE(_sbss = .); + *(.sbss*) + *(.gnu.linkonce.sb.*) + *(.bss*) + *(.gnu.linkonce.b.*) + *(COMMON*) + . = ALIGN(4); + PROVIDE(_ebss = .); + } >RAM AT>FLASH + + PROVIDE(_end = _ebss); + PROVIDE(end = .); + PROVIDE(_eusrstack = ORIGIN(RAM) + LENGTH(RAM)); + + /DISCARD/ : { + *(.note .note.*) + *(.eh_frame .eh_frame.*) + *(.comment .comment.*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx*) + } + + /* EEPROM section - reserves exactly one 64-byte page */ + .eeprom : { + . = ALIGN(64); + _reserved_nv_start = .; + KEEP(*(.eeprom)); + . = _reserved_nv_start + 64; + _reserved_nv_end = .; + } >FLASH AT>FLASH + + PROVIDE(_reserved_nv_start = _reserved_nv_start); + PROVIDE(_reserved_nv_end = _reserved_nv_end); +} diff --git a/ch32v003fun b/ch32v003fun new file mode 160000 index 0000000..e4b7019 --- /dev/null +++ b/ch32v003fun @@ -0,0 +1 @@ +Subproject commit e4b70191d4c51f28e1517fde7b558d6d45480195 diff --git a/funconfig.h b/funconfig.h new file mode 100644 index 0000000..794e5f9 --- /dev/null +++ b/funconfig.h @@ -0,0 +1,24 @@ +#ifndef _FUNCONFIG_H +#define _FUNCONFIG_H + +// #define FUNCONF_USE_PLL 1 // Use built-in 2x PLL +// #define FUNCONF_USE_HSI 1 // Use HSI Internal Oscillator +// #define FUNCONF_USE_HSE 0 // Use External Oscillator +// #define FUNCONF_HSITRIM 0x10 // Use factory calibration on HSI Trim. +// #define FUNCONF_SYSTEM_CORE_CLOCK 48000000 // Computed Clock in Hz (Default only for 003, other chips have other defaults) +// #define FUNCONF_HSE_BYPASS 0 // Use HSE Bypass feature (for oscillator input) +// #define FUNCONF_USE_CLK_SEC 1 // Use clock security system, enabled by default +#define FUNCONF_USE_DEBUGPRINTF 1 +// #define FUNCONF_USE_UARTPRINTF 0 +// #define FUNCONF_NULL_PRINTF 0 // Have printf but direct it "nowhere" +#define FUNCONF_SYSTICK_USE_HCLK 1 // Should systick be at 48 MHz or 6MHz? +// #define FUNCONF_TINYVECTOR 0 // If enabled, Does not allow normal interrupts. +// #define FUNCONF_UART_PRINTF_BAUD 115200 // Only used if FUNCONF_USE_UARTPRINTF is set. +// #define FUNCONF_DEBUGPRINTF_TIMEOUT 160000 // Arbitrary time units +// #define FUNCONF_ENABLE_HPE 1 // Enable hardware interrupt stack. Very good on QingKeV4, i.e. x035, v10x, v20x, v30x, but questionable on 003. +// #define FUNCONF_USE_5V_VDD 0 // Enable this if you plan to use your part at 5V - affects USB and PD configration on the x035. +// #define FUNCONF_DEBUG 0 // Log fatal errors with "printf" + +#define CH32V003 1 + +#endif \ No newline at end of file diff --git a/simple_eeprom.h b/simple_eeprom.h new file mode 100644 index 0000000..a09c6cf --- /dev/null +++ b/simple_eeprom.h @@ -0,0 +1,150 @@ +#ifndef __SIMPLE_EEPROM_H +#define __SIMPLE_EEPROM_H + +#include +#include + +#include "ch32v003fun.h" + +// config +#define EEPROM_PAGE_SIZE 64 // size in bytes +#define EEPROM_HALFWORD_SIZE 2 // size of halfword in bytes +#define EEPROM_MAX_ENTRIES (EEPROM_PAGE_SIZE / EEPROM_HALFWORD_SIZE) + +typedef enum { + EEPROM_OK = 0, + EEPROM_ERROR_WRITE, + EEPROM_ERROR_ERASE, + EEPROM_ERROR_VERIFY +} eeprom_status_t; + +// ######## internal variables +// import from .ld, halal by +// https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html +extern char _reserved_nv_start[]; +extern char _reserved_nv_end[]; + +#define EEPROM_BASE (FLASH_BASE + (uint32_t)(uintptr_t)_reserved_nv_start) + +static void flash_unlock(void) { + if (FLASH->CTLR & 0x80) { + FLASH->KEYR = FLASH_KEY1; + FLASH->KEYR = FLASH_KEY2; + } +} + +static void flash_lock(void) { FLASH->CTLR = 0x80; } + +static void flash_unlock_fast_mode(void) { + if (FLASH->CTLR & 0x8000) { + FLASH->MODEKEYR = FLASH_KEY1; + FLASH->MODEKEYR = FLASH_KEY2; + } +} + +static void flash_wait_busy(void) { while (FLASH->STATR & FLASH_STATR_BSY); } + +static eeprom_status_t flash_erase_page(void) { + uint16_t *page_addr = (uint16_t *)EEPROM_BASE; + + flash_unlock_fast_mode(); + flash_wait_busy(); + + // erase page + FLASH->CTLR = CR_PAGE_ER; + FLASH->ADDR = (uint32_t)page_addr; + FLASH->CTLR = CR_STRT_Set | CR_PAGE_ER; + flash_wait_busy(); + + // verify erase + for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) { + if (page_addr[i] != 0xFFFF) { + return EEPROM_ERROR_ERASE; + } + } + + // disable fast mode + FLASH->CTLR = 0x8000; + return EEPROM_OK; +} + +static int find_next_free_slot(void) { + uint16_t *word = (uint16_t *)EEPROM_BASE; + for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) { + if (word[i] == 0xFFFF) { // look for erased entries + return i; + } + } + return EEPROM_MAX_ENTRIES; +} + +eeprom_status_t eeprom_write(uint16_t data) { + flash_unlock(); + + // find next available slot + int slot = find_next_free_slot(); + uint16_t *word = (uint16_t *)EEPROM_BASE + slot; + + // page is full, erase it + if (slot >= EEPROM_MAX_ENTRIES) { + eeprom_status_t status = flash_erase_page(); + if (status != EEPROM_OK) { + flash_lock(); + return status; + } + word = (uint16_t *)EEPROM_BASE; // reset to start of page + } + + // write data + FLASH->CTLR = CR_PG_Set; + *word = data; // write full 16-bit value + flash_wait_busy(); + + // verify write + if (*word != data) { + flash_lock(); + return EEPROM_ERROR_VERIFY; + } + + flash_lock(); + return EEPROM_OK; +} + +uint16_t eeprom_read(void) { + uint16_t *word = + (uint16_t *)(EEPROM_BASE + EEPROM_PAGE_SIZE - EEPROM_HALFWORD_SIZE); + + // scan back from the end until we find a non-erased value + for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) { + uint16_t val = *word; + if (val != 0xFFFF) { + return val; + } + word--; + } + + return 0xFFFF; // nothing found +} + +eeprom_status_t eeprom_init(void) { + uint16_t *page = (uint16_t *)EEPROM_BASE; + + // check empty/partial page + bool needs_erase = true; + for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) { + if (page[i] != 0xFFFF) { + needs_erase = false; + break; + } + } + + if (needs_erase) { + return flash_erase_page(); + } + + return EEPROM_OK; +} + +eeprom_status_t eeprom_wipe(void) { return flash_erase_page(); } + +#endif \ No newline at end of file diff --git a/state_manager.h b/state_manager.h new file mode 100644 index 0000000..cea2dbe --- /dev/null +++ b/state_manager.h @@ -0,0 +1,24 @@ +#ifndef __STATE_MANAGER_H +#define __STATE_MANAGER_H + +#include "simple_eeprom.h" + +#define DEFAULT_STATE 0x0000 + +void save_state(uint16_t state) { eeprom_write(state); } + +uint16_t load_state(void) { return eeprom_read(); } + +void dump_eeprom(void) { + uint16_t *word = (uint16_t *)(EEPROM_BASE); + printf("EEPROM contents:"); + for (int i = 0; i < 32; i++) { + uint16_t val = word[i]; + // if (!(val & 0x8000)) { + printf(" %04X", val); + // } + } + printf("\n"); +} + +#endif \ No newline at end of file