chore: rewrite to modbus rtu

This commit is contained in:
2024-11-09 00:36:06 +06:00
parent 0a0084f98f
commit 68f47c9d53
14 changed files with 506 additions and 167 deletions

182
ch32ext.c
View File

@@ -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;
}
}
}