chore: rewrite to modbus rtu
This commit is contained in:
6
Makefile
6
Makefile
@@ -5,7 +5,11 @@ 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
|
||||
|
||||
|
||||
54
README.md
54
README.md
@@ -7,36 +7,42 @@ KiCad hardware design files are in the `hw` directory.
|
||||

|
||||

|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
182
ch32ext.c
182
ch32ext.c
@@ -1,157 +1,81 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
172
modbus.c
Normal file
172
modbus.c
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "modbus.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
26
modbus.h
Normal file
26
modbus.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef __MODBUS_H
|
||||
#define __MODBUS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
||||
43
rs485.c
Normal file
43
rs485.c
Normal file
@@ -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; }
|
||||
11
rs485.h
Normal file
11
rs485.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef RS485_H
|
||||
#define RS485_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
46
shift_reg.c
Normal file
46
shift_reg.c
Normal file
@@ -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;
|
||||
}
|
||||
9
shift_reg.h
Normal file
9
shift_reg.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef SHIFT_REG_H
|
||||
#define SHIFT_REG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void shift_reg_init(void);
|
||||
void shift_reg_write(uint16_t data);
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef __SIMPLE_EEPROM_H
|
||||
#define __SIMPLE_EEPROM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
#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
|
||||
50
systick.c
Normal file
50
systick.c
Normal file
@@ -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++;
|
||||
}
|
||||
27
systick.h
Normal file
27
systick.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef SYSTICK_H
|
||||
#define SYSTICK_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#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
|
||||
Reference in New Issue
Block a user