From 68f47c9d53b846d3aa3ce5d35f752e2864cbc0dd Mon Sep 17 00:00:00 2001 From: kuwoyuki Date: Sat, 9 Nov 2024 00:36:06 +0600 Subject: [PATCH] chore: rewrite to modbus rtu --- Makefile | 8 ++- README.md | 54 +++++++------- ch32ext.c | 182 ++++++++++++++---------------------------------- funconfig.h | 4 +- modbus.c | 172 +++++++++++++++++++++++++++++++++++++++++++++ modbus.h | 26 +++++++ rs485.c | 43 ++++++++++++ rs485.h | 11 +++ shift_reg.c | 46 ++++++++++++ shift_reg.h | 9 +++ simple_eeprom.h | 5 +- state_manager.h | 36 ++++++++-- systick.c | 50 +++++++++++++ systick.h | 27 +++++++ 14 files changed, 506 insertions(+), 167 deletions(-) create mode 100644 modbus.c create mode 100644 modbus.h create mode 100644 rs485.c create mode 100644 rs485.h create mode 100644 shift_reg.c create mode 100644 shift_reg.h create mode 100644 systick.c create mode 100644 systick.h diff --git a/Makefile b/Makefile index 0d1d60a..308db6b 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,13 @@ CH32V003FUN=./ch32v003fun/ch32v003fun MINICHLINK=./ch32v003fun/minichlink PREFIX=riscv64-elf LINKER_SCRIPT=./ch32ext.ld -# CFLAGS += -march=rv32ec_zicsr + +ADDITIONAL_C_FILES = systick.c \ + shift_reg.c \ + rs485.c \ + modbus.c include $(CH32V003FUN)/ch32v003fun.mk flash : cv_flash -clean : cv_clean +clean : cv_clean \ No newline at end of file diff --git a/README.md b/README.md index 8dd9a14..b431a32 100644 --- a/README.md +++ b/README.md @@ -7,36 +7,42 @@ KiCad hardware design files are in the `hw` directory. ![photo](img/ch32_extension_photo.jpg) ![render](img/ch32_extension.png) -## Communication Protocol +## comm protocol -### Message Format +uses Modbus RTU protocol over RS485 -The RS485 communication uses a simple 4-byte message format: +### Config ``` -Byte 0: Board Address (0x01-0xFE, 0xFF for broadcast) -Byte 1: Command (0x01 for SET_OUTPUTS) -Byte 2: Data High Byte -Byte 3: Data Low Byte +Baud Rate: 9600 +Data Bits: 8 +Stop Bits: 1 +Parity: None ``` -### Protocol Constants +### Modbus + +- Function Code: 0x06 (Write Single Register) +- Default Slave Address: 0x01 +- Register Map: + - Register 0x0000: Output Control Register (16 bits for controlling 16 relays) + +### Outputs + +Each bit in the control register corresponds to one output: +- Bit 0 controls Output 1 +- Bit 1 controls Output 2 +- etc. + +### Msg -```c -#define BOARD_ADDRESS 0x01 // Default board address -#define CMD_SET_OUTPUTS 0x01 // Command to set outputs -#define BROADCAST_ADDR 0xFF // Broadcast address ``` - -### Example Message - -To control the outputs, send a 4-byte message: - -```rust -let message = [ - address, // Board address (0xFF for broadcast) - CMD_SET_OUTPUTS, // Command - (value >> 8) as u8, // Data high byte - value as u8, // Data low byte -]; +Byte 0: Slave Address (0x01) +Byte 1: Function Code (0x06) +Byte 2: Register Address High (0x00) +Byte 3: Register Address Low (0x00) +Byte 4: Data High Byte +Byte 5: Data Low Byte +Byte 6: CRC Low Byte +Byte 7: CRC High Byte ``` diff --git a/ch32ext.c b/ch32ext.c index 3214ad1..62778b9 100644 --- a/ch32ext.c +++ b/ch32ext.c @@ -1,157 +1,81 @@ #include #include "ch32v003fun.h" +#include "modbus.h" +#include "rs485.h" +#include "shift_reg.h" #include "simple_eeprom.h" #include "state_manager.h" +#include "systick.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 +#define SLAVE_ADDRESS 0x01 +#define MAX_MODBUS_FRAME 16 +#define NUM_REGISTERS 1 +#define FRAME_TIMEOUT_MS 4 // 3.5 char times at 9600 baud (~1.042ms per char) -// "protocol" defines -#define BOARD_ADDRESS 0x01 -#define CMD_SET_OUTPUTS 0x01 -#define BROADCAST_ADDR 0xFF +static uint16_t holding_registers[NUM_REGISTERS]; -// "protocol" states -enum rx_states { - STATE_ADDR, - STATE_IGNORE, - STATE_CMD, - STATE_DATA_HIGH, - STATE_DATA_LOW -}; +static void handle_modbus_frame(uint8_t* rx_buffer, uint16_t rx_count) { + uint8_t response[MAX_MODBUS_FRAME]; + uint16_t response_len; + uint8_t result; -void setup_uart(int uartBRR) { - RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | - RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO; + result = modbus_process_message(rx_buffer, rx_count, SLAVE_ADDRESS, + holding_registers, NUM_REGISTERS); - // 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; + if (result == MODBUS_ERROR_NONE) { + shift_reg_write(holding_registers[0]); + save_state(holding_registers[0]); - // 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); + response_len = modbus_create_response(response, SLAVE_ADDRESS, + MODBUS_FC_WRITE_SINGLE_REGISTER, 0, + holding_registers[0]); + } else { + response_len = modbus_create_error_response( + response, SLAVE_ADDRESS, MODBUS_FC_WRITE_SINGLE_REGISTER, result); } - 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; + rs485_send(response, response_len); } int main(void) { + uint8_t rx_buffer[MAX_MODBUS_FRAME]; + uint16_t rx_count = 0; + uint32_t last_byte_time = 0; + SystemInit(); - setup_gpio(); - setup_uart(UART_BRR); + systick_init(); + shift_reg_init(); + rs485_init(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); + // restore last saved state + holding_registers[0] = load_state(); + shift_reg_write(holding_registers[0]); while (1) { - uint8_t byte = uart_receive_byte(); + if (!rs485_available()) { + continue; + } - 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; + uint32_t current_time = millis(); - case STATE_IGNORE: - bytes_to_ignore--; - if (bytes_to_ignore == 0) { - rx_state = STATE_ADDR; - } - break; + // check for frame timeout + if (rx_count > 0 && (current_time - last_byte_time) > FRAME_TIMEOUT_MS) { + rx_count = 0; + } - case STATE_CMD: - cmd = byte; - rx_state = STATE_DATA_HIGH; - break; + rx_buffer[rx_count++] = rs485_read(); + last_byte_time = current_time; - case STATE_DATA_HIGH: - data = (uint16_t)byte << 8; - rx_state = STATE_DATA_LOW; - break; + // process complete frame + if (rx_count >= 8) { + handle_modbus_frame(rx_buffer, rx_count); + rx_count = 0; + } - 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; + // buffer overflow protection + if (rx_count >= MAX_MODBUS_FRAME) { + rx_count = 0; } } } \ No newline at end of file diff --git a/funconfig.h b/funconfig.h index 794e5f9..5f0fa47 100644 --- a/funconfig.h +++ b/funconfig.h @@ -13,12 +13,12 @@ // #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_UART_PRINTF_BAUD 9600 // 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 +#define CH32V003 1 #endif \ No newline at end of file diff --git a/modbus.c b/modbus.c new file mode 100644 index 0000000..dafb02d --- /dev/null +++ b/modbus.c @@ -0,0 +1,172 @@ +#include "modbus.h" + +#include + +// frame spec +#define MB_MIN_LEN 4 +#define MB_CRC_LEN 2 +#define MB_WREG_LEN 8 +#define MB_BCAST 0xFF + +// resp len +#define MB_RSP_LEN 8 +#define MB_ERR_LEN 5 + +static uint16_t modbus_crc16(const unsigned char* buf, unsigned int len) { + static const uint16_t table[256] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, + 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, + 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, + 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, + 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, + 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, + 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, + 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, + 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, + 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, + 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, + 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, + 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, + 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, + 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, + 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, + 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, + 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, + 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, + 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, + 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, + 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, + 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, + 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, + 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, + 0x4100, 0x81C1, 0x8081, 0x4040}; + + uint8_t xor = 0; + uint16_t crc = 0xFFFF; + + while (len--) { + xor = (*buf++) ^ crc; + crc >>= 8; + crc ^= table[xor]; + } + + return crc; +} + +// byte ops +static inline uint16_t mb_word(uint8_t hi, uint8_t lo) { + return ((uint16_t)hi << 8) | lo; +} + +static inline void mb_split(uint16_t val, uint8_t* hi, uint8_t* lo) { + *hi = (val >> 8) & 0xFF; + *lo = val & 0xFF; +} + +static inline void mb_putcrc(uint8_t* buf, uint16_t len) { + uint16_t crc = modbus_crc16(buf, len); + buf[len] = crc & 0xFF; + buf[len + 1] = (crc >> 8) & 0xFF; +} + +// frame data +typedef struct { + uint8_t addr; + uint8_t func; + uint16_t reg; + uint16_t val; +} mb_frame_t; + +static int mb_parse(const uint8_t* buf, uint16_t len, mb_frame_t* frame) { + if (len < MB_MIN_LEN) { + return 0; + } + + frame->addr = buf[0]; + frame->func = buf[1]; + frame->reg = mb_word(buf[2], buf[3]); + + if (frame->func == MODBUS_FC_WRITE_SINGLE_REGISTER && len >= 6) { + frame->val = mb_word(buf[4], buf[5]); + } + + return 1; +} + +uint8_t modbus_process_message(uint8_t* buf, uint16_t len, uint8_t slave_addr, + uint16_t* holding_registers, + uint16_t num_registers) { + if (len < MB_MIN_LEN) { + return MODBUS_ERROR_VALUE; + } + + mb_frame_t frame; + if (!mb_parse(buf, len, &frame)) { + return MODBUS_ERROR_VALUE; + } + + if (frame.addr != slave_addr && frame.addr != MB_BCAST) { + return MODBUS_ERROR_ADDRESS; + } + + uint16_t rcv_crc = mb_word(buf[len - 1], buf[len - 2]); + uint16_t calc_crc = modbus_crc16(buf, len - 2); + +#ifdef MB_DEBUG + printf("RCV CRC: 0x%04X\n", rcv_crc); + printf("CALC CRC: 0x%04X\n", calc_crc); + printf("CRC %s\n", (rcv_crc == calc_crc) ? "OK" : "ERR"); +#endif + + if (rcv_crc != calc_crc) { + return MODBUS_ERROR_VALUE; + } + + switch (frame.func) { + case MODBUS_FC_WRITE_SINGLE_REGISTER: + if (len != MB_WREG_LEN) { + return MODBUS_ERROR_VALUE; + } + if (frame.reg >= num_registers) { + return MODBUS_ERROR_ADDRESS; + } + + holding_registers[frame.reg] = frame.val; + return MODBUS_ERROR_NONE; + + default: + return MODBUS_ERROR_FUNCTION; + } +} + +uint16_t modbus_create_response(uint8_t* rsp, uint8_t slave_addr, + uint8_t function, uint16_t address, + uint16_t value) { + rsp[0] = slave_addr; + rsp[1] = function; + + uint8_t hi, lo; + mb_split(address, &hi, &lo); + rsp[2] = hi; + rsp[3] = lo; + + mb_split(value, &hi, &lo); + rsp[4] = hi; + rsp[5] = lo; + + mb_putcrc(rsp, 6); + return MB_RSP_LEN; +} + +uint16_t modbus_create_error_response(uint8_t* rsp, uint8_t slave_addr, + uint8_t function, uint8_t error_code) { + rsp[0] = slave_addr; + rsp[1] = function | 0x80; // Set MSB for error response + rsp[2] = error_code; + + mb_putcrc(rsp, 3); + return MB_ERR_LEN; +} \ No newline at end of file diff --git a/modbus.h b/modbus.h new file mode 100644 index 0000000..286f726 --- /dev/null +++ b/modbus.h @@ -0,0 +1,26 @@ +#ifndef __MODBUS_H +#define __MODBUS_H + +#include + +// function codes +#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 + +// errors +#define MODBUS_ERROR_NONE 0x00 +#define MODBUS_ERROR_FUNCTION 0x01 +#define MODBUS_ERROR_ADDRESS 0x02 +#define MODBUS_ERROR_VALUE 0x03 + +uint8_t modbus_process_message(uint8_t *buf, uint16_t len, uint8_t slave_addr, + uint16_t *holding_registers, + uint16_t num_registers); +uint16_t modbus_create_response(uint8_t *response, uint8_t slave_addr, + uint8_t function, uint16_t address, + uint16_t value); +uint16_t modbus_create_error_response(uint8_t *response, uint8_t slave_addr, + uint8_t function, uint8_t error_code); + +#endif // __MODBUS_H diff --git a/rs485.c b/rs485.c new file mode 100644 index 0000000..64d0e59 --- /dev/null +++ b/rs485.c @@ -0,0 +1,43 @@ +#include "rs485.h" + +#include "ch32v003fun.h" + +#define RS485_DIR (1 << 0) + +void rs485_init(int uartBRR) { + RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | + RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO; + + // RS485_DIR as output, initial 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); + + 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 rs485_send(uint8_t *buf, uint16_t len) { + GPIOC->BSHR = RS485_DIR; // TX mode + + for (uint16_t i = 0; i < len; i++) { + while (!(USART1->STATR & USART_FLAG_TXE)); + USART1->DATAR = buf[i]; + } + + while (!(USART1->STATR & USART_FLAG_TC)); + GPIOC->BCR = RS485_DIR; // RX mode +} + +uint8_t rs485_available(void) { return (USART1->STATR & USART_FLAG_RXNE) != 0; } + +uint8_t rs485_read(void) { return USART1->DATAR & 0xFF; } diff --git a/rs485.h b/rs485.h new file mode 100644 index 0000000..2bf246e --- /dev/null +++ b/rs485.h @@ -0,0 +1,11 @@ +#ifndef RS485_H +#define RS485_H + +#include + +void rs485_init(int uartBRR); +void rs485_send(uint8_t *buf, uint16_t len); +uint8_t rs485_available(void); +uint8_t rs485_read(void); + +#endif diff --git a/shift_reg.c b/shift_reg.c new file mode 100644 index 0000000..3ea71e4 --- /dev/null +++ b/shift_reg.c @@ -0,0 +1,46 @@ +#include "shift_reg.h" + +#include "ch32v003fun.h" + +#define SRCLR (1 << 1) +#define SRCLK (1 << 2) +#define RCLK (1 << 3) +#define SER (1 << 4) + +void shift_reg_init(void) { + RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; + + // PC1-PC4 as outputs + 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; + + // reset shift register + GPIOC->BSHR = SRCLR; + GPIOC->BSHR = RCLK; + GPIOC->BCR = RCLK; +} + +void shift_reg_write(uint16_t data) { + GPIOC->BCR = RCLK; + + // reorder bits + uint16_t ordered_data = ((data & 0xFF00) >> 8) | ((data & 0x00FF) << 8); + + // shift out 16 bits, MSB first + 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; + } + + GPIOC->BSHR = RCLK; + GPIOC->BCR = RCLK; +} diff --git a/shift_reg.h b/shift_reg.h new file mode 100644 index 0000000..541f1ae --- /dev/null +++ b/shift_reg.h @@ -0,0 +1,9 @@ +#ifndef SHIFT_REG_H +#define SHIFT_REG_H + +#include + +void shift_reg_init(void); +void shift_reg_write(uint16_t data); + +#endif diff --git a/simple_eeprom.h b/simple_eeprom.h index a09c6cf..79ff1e4 100644 --- a/simple_eeprom.h +++ b/simple_eeprom.h @@ -1,7 +1,6 @@ #ifndef __SIMPLE_EEPROM_H #define __SIMPLE_EEPROM_H -#include #include #include "ch32v003fun.h" @@ -130,10 +129,10 @@ eeprom_status_t eeprom_init(void) { uint16_t *page = (uint16_t *)EEPROM_BASE; // check empty/partial page - bool needs_erase = true; + int needs_erase = 1; for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) { if (page[i] != 0xFFFF) { - needs_erase = false; + needs_erase = 0; break; } } diff --git a/state_manager.h b/state_manager.h index cea2dbe..eb8bec5 100644 --- a/state_manager.h +++ b/state_manager.h @@ -1,24 +1,46 @@ #ifndef __STATE_MANAGER_H #define __STATE_MANAGER_H -#include "simple_eeprom.h" - #define DEFAULT_STATE 0x0000 +// #define USE_EEPROM_STORAGE -void save_state(uint16_t state) { eeprom_write(state); } +#ifdef USE_EEPROM_STORAGE +#include "simple_eeprom.h" +static uint16_t current_state = DEFAULT_STATE; +static bool state_initialized = false; -uint16_t load_state(void) { return eeprom_read(); } +void save_state(uint16_t state) { + current_state = state; + eeprom_write(state); +} + +uint16_t load_state(void) { + if (!state_initialized) { + current_state = eeprom_read(); + state_initialized = true; + } + return current_state; +} 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 +#else // RAM-only +static uint16_t current_state = DEFAULT_STATE; + +void save_state(uint16_t state) { current_state = state; } + +uint16_t load_state(void) { return current_state; } + +void dump_eeprom(void) { printf("EEPROM storage disabled\n"); } + +#endif // USE_EEPROM_STORAGE + +#endif // __STATE_MANAGER_H \ No newline at end of file diff --git a/systick.c b/systick.c new file mode 100644 index 0000000..5cf764b --- /dev/null +++ b/systick.c @@ -0,0 +1,50 @@ +#include "systick.h" + +#include "ch32v003fun.h" + +// Global variable definition +volatile uint32_t systick_millis; + +/* + * Initialises the SysTick to trigger an IRQ with auto-reload, using HCLK/1 as + * its clock source + */ +void systick_init(void) { + // Reset any pre-existing configuration + SysTick->CTLR = 0x0000; + + // Set the compare register to trigger once per millisecond + SysTick->CMP = SYSTICK_ONE_MILLISECOND - 1; + + // Reset the Count Register, and the global millis counter to 0 + SysTick->CNT = 0x00000000; + systick_millis = 0x00000000; + + // Set the SysTick Configuration + // NOTE: By not setting SYSTICK_CTLR_STRE, we maintain compatibility with + // busywait delay funtions used by ch32v003_fun. + SysTick->CTLR |= SYSTICK_CTLR_STE | // Enable Counter + SYSTICK_CTLR_STIE | // Enable Interrupts + SYSTICK_CTLR_STCLK; // Set Clock Source to HCLK/1 + + // Enable the SysTick IRQ + NVIC_EnableIRQ(SysTicK_IRQn); +} + +/* + * SysTick ISR - must be lightweight to prevent the CPU from bogging down. + * Increments Compare Register and systick_millis when triggered (every 1ms) + */ +void SysTick_Handler(void) { + // Increment the Compare Register for the next trigger + // If more than this number of ticks elapse before the trigger is reset, + // you may miss your next interrupt trigger + // (Make sure the IQR is lightweight and CMP value is reasonable) + SysTick->CMP += SYSTICK_ONE_MILLISECOND; + + // Clear the trigger state for the next IRQ + SysTick->SR = 0x00000000; + + // Increment the milliseconds count + systick_millis++; +} diff --git a/systick.h b/systick.h new file mode 100644 index 0000000..18569ff --- /dev/null +++ b/systick.h @@ -0,0 +1,27 @@ +#ifndef SYSTICK_H +#define SYSTICK_H + +#include + +#include "ch32v003fun.h" + +// Number of ticks elapsed per millisecond (48,000 when using 48MHz Clock) +#define SYSTICK_ONE_MILLISECOND ((uint32_t)FUNCONF_SYSTEM_CORE_CLOCK / 1000) +// Number of ticks elapsed per microsecond (48 when using 48MHz Clock) +#define SYSTICK_ONE_MICROSECOND ((uint32_t)FUNCONF_SYSTEM_CORE_CLOCK / 1000000) + +// Simple macro functions to give a arduino-like functions to call +// millis() reads the incremented systick variable +// micros() reads the raw SysTick Count, and divides it by the number of +// ticks per microsecond ( WARN: Wraps every 90 seconds!) +#define millis() (systick_millis) +#define micros() (SysTick->CNT / SYSTICK_ONE_MICROSECOND) + +// Incremented in the SysTick IRQ - in this example once per millisecond +extern volatile uint32_t systick_millis; + +// Function declarations +void systick_init(void); +void SysTick_Handler(void) __attribute__((interrupt)); + +#endif // SYSTICK_H \ No newline at end of file