diff --git a/.vscode/settings.json b/.vscode/settings.json index 38c93d2..77ea6f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,12 @@ "cstdlib": "c", "modbus_master.h": "c", "ch32v003_gpio_branchless.h": "c", - "mqttpacket.h": "c" + "mqttpacket.h": "c", + "stdbool.h": "c", + "onewire_temp.h": "c", + "modbus.h": "c", + "network.h": "c", + "cstdint": "c", + "utils.h": "c" } } \ No newline at end of file diff --git a/README.md b/README.md index 6a5df10..1ea6650 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,29 @@ -# ch32-node fw -fw for a ch32v203 node w/ w5500 ethernet +# CH32V203 Node Firmware -## current +Firmware for a CH32V203 MCU with a W5500 Ethernet controller. -### SysTick IRQ and DMA issue -[Enabling](https://git.hye.su/mira/ch32-node/src/branch/master/src/main.c#L54) the SysTick IRQ leads to a permanent hang of the socket. - - With a 1ms IRQ interval, the hang occurs at `DHCP_ACK` during the `check_DHCP_leasedIP` -> `sendto()` [ARP request](https://git.hye.su/mira/ch32-node/src/branch/master/lib/ioLibrary_Driver/socket.c#L563). - - With a 100ms interval, the hang occurs during the DNS request. - - With a 1s interval it works - - [Disabling SPI DMA](https://git.hye.su/mira/ch32-node/src/branch/master/src/w5500.c#L62) solves it (just commenting out `reg_wizchip_spiburst_cbfunc`), so it's most likely DMA related? - - SPI DMA only works with [prescalers](https://git.hye.su/mira/ch32-node/src/branch/master/src/spi_dma.c#L140) 8 and 64? - - Also, for some reason it needs a ~50ms delay before configuring w5500 when compiled **with** `-O0`, not needed with `-Os`... +## TODO: +UnplatformIOify this -## Diff between SPI w/ and w/o DMA: -### SPI DMA -![SPI_DMA 1](notes/SPI_DMA_no_irq_1.png) -![SPI_DMA 2](notes/SPI_DMA_no_irq_2.png) -### SPI -![SPI 1](notes/SPI_1.png) -![SPI 2](notes/SPI_2.png) +## Features -00 09 04 C0 A8 66 01 02 -00 09 04 C0 A8 66 01 02 02 +- W5500 Ethernet controller support +- Network protocol support including: + - DHCP client + - MQTT client +- RS485 communication +- OneWire sensors -## ~~previous (DNS Processing)~~ -solved by [patching](https://git.hye.su/mira/ch32-node/commit/259d63197e06c1a92b979490d4cd8f0fdb98f8d0#diff-6ba50689ba55dac7cfe3e9b011e594098c931e21) the korean bloatlib (`dns_makequery` in DNS.c) +## Development Environment -w/ ch32v003fun +- Platform: CH32V +- Board: CH32V203C8T6 +- Framework: ch32v003fun +- Build System: PlatformIO +- Compiler: RISC-V GCC 14.2.0 -> Partial DNS message: `11 23 81 82 00 01 00 00 00 00 00 00 03 68 79 65 00 00 00 02 73` -> **DNS_run() failed, res = 0** +## Project Structure -w/ WCH HAL (none-os).. I get a full response - -> Receive DNS message from 192.168.102.1(53). len = 56 -> Partial DNS message: `11 23 81 80 00 01 00 02 00 00 00 00 03 68 79 65 02 73 75 00 00 01 00 01 C0 0C 00 01 00 01 00 00 01 2C 00 04 68 15 33 7F C0 0C 00 01 00 01 00 00 01 2C 00 04 AC 43 B4 9A ` -> Result: 172.67.180.154 - -`RCC_CTLR`, GPIO Registers, `SPIx_CTLR1` registers are identical - -## LA - -### ch32v003fun - -![ch32v003fun](notes/2024-10-10-191033_1876x540_scrot.png) - -``` -00 00 34 11 27 01 00 00 01 00 00 00 00 00 00 03 68 79 65 00 00 00 02 73 75 00 00 00 00 01 00 01 -``` - -- 32 bytes long -- (Post `68 79 65`): `00 00 00 02 73 75 00 00 00 00 01 00 01` - -### none-os - -![none-os](notes/2024-10-10-191043_1891x525_scrot.png) - -``` -00 00 34 11 27 01 00 00 01 00 00 00 00 00 00 03 68 79 65 02 73 75 00 00 01 00 01 -``` - -- 27 bytes long -- (Post `68 79 65`): `02 73 75 00 00 01 00 01` +- `/include` - Header files +- `/lib` - Project libraries including WIZnet ioLibrary +- `/src` - Source code diff --git a/include/config.h b/include/config.h index 7da6d67..089a376 100644 --- a/include/config.h +++ b/include/config.h @@ -3,52 +3,48 @@ #include -// Debug flags +// debug flags #define DEBUG_MODE 1 -// Device Bus Types -typedef enum { BUS_RS485, BUS_ONEWIRE } bus_type_t; - -// Device Type Definitions +// device type definitions typedef enum { DEVICE_RELAY = 1, DEVICE_SOIL_SENSOR = 2, DEVICE_THERMOMETER = 3 } device_type_t; -// Node Configuration +// node configuration typedef struct { const char* id; // Unique identifier for the node const char* name; // Human readable name const char* location; // Optional location description + uint8_t mac[6]; // MAC address } node_config_t; -// RS485 Device Configuration +// RS485 device configuration typedef struct { uint8_t slave_id; // Modbus slave ID device_type_t type; // Type of device const char* name; // Device name (used in MQTT topics) } rs485_device_t; -// Network Configuration +// Network configuration #define MQTT_SERVER_IP {192, 168, 102, 100} #define MQTT_PORT 1883 -// MQTT Configuration +// MQTT configuration #define MQTT_KEEP_ALIVE_INTERVAL 60 #define MQTT_TX_BUFFER_SIZE 128 #define MQTT_RX_BUFFER_SIZE 128 #define MQTT_COMMAND_TIMEOUT_MS 1000 -// Node Specific Configuration -static const node_config_t NODE_CONFIG = { - .id = "ch32-node1", .name = "CH32 Node 1", .location = "somewhere"}; +// node config declaration +extern node_config_t NODE_CONFIG; -// RS485 Devices Configuration +// RS485 devices configuration #define RS485_DEVICE_COUNT 1 -static const rs485_device_t RS485_DEVICES[RS485_DEVICE_COUNT] = { - {.slave_id = 0x01, .type = DEVICE_RELAY, .name = "relay-1"}}; -// {.slave_id = 0x02, .type = DEVICE_SOIL_SENSOR, .name = "soil-monitor-1"}}; +// RS485 devices declaration +extern const rs485_device_t RS485_DEVICES[RS485_DEVICE_COUNT]; #endif // CONFIG_H diff --git a/include/dhcp.h b/include/dhcp.h deleted file mode 100644 index 68d0f99..0000000 --- a/include/dhcp.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef DHCP_H -#define DHCP_H - -#include - -typedef enum { - DHCP_STATE_INIT, - DHCP_STATE_DISCOVER, - DHCP_STATE_REQUEST, - DHCP_STATE_LEASED, - DHCP_STATE_RENEW, - DHCP_STATE_RELEASE -} dhcp_state_t; - -void dhcp_init(void); -void dhcp_process(void); -uint8_t dhcp_get_state(void); - -#endif diff --git a/include/gpio.h b/include/gpio.h index 5851d5f..368ec85 100644 --- a/include/gpio.h +++ b/include/gpio.h @@ -13,7 +13,7 @@ typedef enum { } led_state_t; // Initialize GPIO -void init_gpio(void); +void gpio_init(void); // LED status handling void led_status_set(led_state_t state); diff --git a/include/modbus.h b/include/modbus.h new file mode 100644 index 0000000..d8b2b94 --- /dev/null +++ b/include/modbus.h @@ -0,0 +1,56 @@ +#ifndef MODBUS_H +#define MODBUS_H + +#include +#include +#include + +#include "config.h" + +// Function codes +#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 + +// Modbus protocol exception codes +#define MODBUS_ERROR_NONE 0x00 +#define MODBUS_ERROR_ILLEGAL_FUNCTION 0x01 +#define MODBUS_ERROR_ILLEGAL_ADDRESS 0x02 +#define MODBUS_ERROR_ILLEGAL_VALUE 0x03 +#define MODBUS_ERROR_DEVICE_FAILURE 0x04 +#define MODBUS_ERROR_ACKNOWLEDGE 0x05 +#define MODBUS_ERROR_DEVICE_BUSY 0x06 +#define MODBUS_ERROR_MEMORY_PARITY 0x08 +#define MODBUS_ERROR_TIMEOUT 0x0B // No response received +#define MODBUS_ERROR_CRC 0x0C // CRC check failed + +// Frame length +#define MB_MIN_LEN 4 +#define MB_CRC_LEN 2 +#define MB_WREG_LEN 8 +#define MB_MAX_BUFFER 32 + +// States for the Modbus protocol state machine +typedef enum { + MODBUS_IDLE, // Ready to send new request + MODBUS_WAITING_RESPONSE, // Request sent, waiting for slave response + MODBUS_PROCESS_RESPONSE // Response received, processing data +} modbus_state_t; + +// Callback function type for handling received Modbus register values +typedef void (*modbus_value_cb)(uint8_t device_idx, const char* property, + uint16_t value); + +// Initialize the Modbus protocol handler with callback for received values +void modbus_handler_init(modbus_value_cb value_callback); +void modbus_handler_process(void); +// Send a Modbus read or write request to a slave device +// Returns true if request was queued successfully +bool modbus_handler_send_request(uint8_t device_idx, const char* property, + bool is_write, uint16_t value); +// Publish a Modbus register value to MQTT broker +// Formats topic as device_name/property +void modbus_publish_value(MQTTClient* client, const char* device_name, + const char* property, uint16_t value); + +#endif // MODBUS_H diff --git a/include/modbus_handler.h b/include/modbus_handler.h deleted file mode 100644 index 4b99e0c..0000000 --- a/include/modbus_handler.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MODBUS_HANDLER_H -#define MODBUS_HANDLER_H - -#include - -#include "config.h" -#include "modbus_master.h" - -typedef void (*modbus_value_cb)(uint8_t device_idx, const char* property, - uint16_t value); - -void modbus_handler_init(modbus_context_t* ctx, modbus_value_cb value_callback); -void modbus_handler_process(void); -bool modbus_handler_send_request(uint8_t device_idx, const char* property, - uint8_t is_write, uint16_t value); - -#endif \ No newline at end of file diff --git a/include/modbus_master.h b/include/modbus_master.h deleted file mode 100644 index 61378d6..0000000 --- a/include/modbus_master.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef __MODBUS_MASTER_H -#define __MODBUS_MASTER_H - -#include -#include - -// Function codes -#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 -#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 -#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 - -// Error codes -#define MODBUS_ERROR_NONE 0x00 -#define MODBUS_ERROR_FUNCTION 0x01 -#define MODBUS_ERROR_ADDRESS 0x02 -#define MODBUS_ERROR_VALUE 0x03 -#define MODBUS_ERROR_TIMEOUT 0x04 - -// Frame length -#define MB_MIN_LEN 4 -#define MB_CRC_LEN 2 -#define MB_WREG_LEN 8 -#define MB_MAX_BUFFER 32 - -// State machine states -typedef enum { - MODBUS_IDLE, - MODBUS_WAITING_RESPONSE, - MODBUS_PROCESS_RESPONSE -} modbus_state_t; - -// Modbus context structure -typedef struct { - modbus_state_t state; - uint32_t last_send_time; - uint32_t response_timeout; - uint8_t buffer[MB_MAX_BUFFER]; - uint16_t rx_len; - uint16_t current_bit; - uint16_t last_value; - void (*on_response)(uint8_t* buf, uint16_t len, - uint16_t value); // Response callback - void (*on_error)(uint8_t error_code); // Error callback -} modbus_context_t; - -uint16_t modbus_create_request(uint8_t* req, uint8_t slave_addr, - uint8_t function, uint16_t address, - uint16_t value); -uint8_t modbus_process_response(uint8_t* buf, uint16_t len, uint16_t* value); -void modbus_init(modbus_context_t* ctx, - void (*response_callback)(uint8_t*, uint16_t, uint16_t), - void (*error_callback)(uint8_t)); -void modbus_set_timeout(modbus_context_t* ctx, uint32_t timeout_ms); -void modbus_process(modbus_context_t* ctx); -bool modbus_send_request(modbus_context_t* ctx, uint8_t slave_addr, - uint8_t function, uint16_t address, uint16_t value); -#endif // __MODBUS_MASTER_H diff --git a/include/mqtt_handler.h b/include/mqtt_handler.h index aed0b47..673e4be 100644 --- a/include/mqtt_handler.h +++ b/include/mqtt_handler.h @@ -5,7 +5,7 @@ #include #include "ch32v003fun.h" -#include "w5500.h" +#include "network.h" #define MAX_PAYLOAD_LENGTH 256 @@ -17,6 +17,7 @@ typedef struct { int qos; } ch32_mqtt_options_t; +// MQTT state typedef struct { Network network; MQTTClient client; @@ -28,16 +29,18 @@ typedef struct { bool discovery_published; } mqtt_state_t; +// List of connected MQTT nodes extern char nodes_list[MAX_PAYLOAD_LENGTH]; void mqtt_init(mqtt_state_t* state); void mqtt_process(mqtt_state_t* state); +// Callback for handling incoming MQTT messages void message_arrived(MessageData* md); -void publish_value(MQTTClient* client, const char* device_name, - const char* property, uint16_t value); +// Publish a retained message to an MQTT topic void publish_retained(MQTTClient* client, const char* topic, const char* payload); +// Publish a QoS 0 message to a MQTT topic void publish_message(MQTTClient* client, const char* payload, const char* topic); -#endif \ No newline at end of file +#endif // MQTT_HANDLER_H diff --git a/include/network.h b/include/network.h new file mode 100644 index 0000000..6768ab2 --- /dev/null +++ b/include/network.h @@ -0,0 +1,27 @@ +#ifndef NETWORK_H +#define NETWORK_H + +#include +#include + +// Definitions for socket indexes +#define DHCP_SOCKET 0 +#define DNS_SOCKET 1 +#define TCP_SOCKET 2 + +typedef enum { + DHCP_STATE_INIT, + DHCP_STATE_DISCOVER, + DHCP_STATE_REQUEST, + DHCP_STATE_LEASED, + DHCP_STATE_RENEW, + DHCP_STATE_RELEASE +} dhcp_state_t; + +// Initializes the W5500 chip +void configure_network(void); +void network_init(void); +void dhcp_process(void); +uint8_t dhcp_get_state(void); + +#endif // NETWORK_H diff --git a/include/rs485.h b/include/rs485.h deleted file mode 100644 index e962bdb..0000000 --- a/include/rs485.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef RS485_H -#define RS485_H - -#include - -void rs485_init(int uart_brr); -void rs485_send(uint8_t *buf, uint16_t len); -uint8_t rs485_available(void); -uint8_t rs485_read(void); - -#endif // RS485_H diff --git a/include/spi_dma.h b/include/spi_dma.h index 024259c..bf30d94 100644 --- a/include/spi_dma.h +++ b/include/spi_dma.h @@ -12,7 +12,7 @@ typedef enum { } transfer_state_t; // SPI DMA initialization function -void init_spidma(void); +void spidma_init(void); // SPI DMA buffer read void spidma_read_buffer(uint8_t *buf, uint16_t len); diff --git a/include/system_init.h b/include/system_init.h deleted file mode 100644 index 4b99f1b..0000000 --- a/include/system_init.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef SYSTEM_INIT_H -#define SYSTEM_INIT_H - -#include - -#define W5500_INIT_DELAY_MS 55 - -void init_system(void); -bool wait_for_dhcp(void); - -#endif diff --git a/include/systick.h b/include/systick.h index bfcfe82..d284762 100644 --- a/include/systick.h +++ b/include/systick.h @@ -10,6 +10,6 @@ // ms counter incremented by SysTick extern volatile uint32_t systick_millis; -void init_systick(void); +void systick_init(void); #endif // SYSTICK_H diff --git a/include/uart.h b/include/uart.h index d3bdec0..20ef8a9 100644 --- a/include/uart.h +++ b/include/uart.h @@ -3,20 +3,29 @@ #include +// RS485 baud rate #define UART1_BAUD_RATE 9600 +#define UART2_BAUD_RATE 115200 -// Macro definitions -#define APB1_CLOCK (FUNCONF_SYSTEM_CORE_CLOCK / 2) // APB1 is divided by 2 +// APB1 bus clock is half the system core clock +#define APB1_CLOCK (FUNCONF_SYSTEM_CORE_CLOCK / 2) -// USART2 -#define UART_BRR_APB1 (((APB1_CLOCK) + (UART_BAUD_RATE / 2)) / (UART_BAUD_RATE)) - -// USART1 +// Calculate baud rate divisors +// Adds BAUD_RATE/2 for rounding to nearest integer +#define UART_BRR_APB1 \ + (((APB1_CLOCK) + (UART2_BAUD_RATE / 2)) / (UART2_BAUD_RATE)) #define UART_BRR_APB2 \ (((FUNCONF_SYSTEM_CORE_CLOCK) + (UART1_BAUD_RATE / 2)) / (UART1_BAUD_RATE)) // Function prototypes -void init_uart(int uart_brr); +// UART2 +void uart2_init(int uart_brr); + +// RS485 functions +void rs485_init(int uart_brr); +void rs485_send(uint8_t *buf, uint16_t len); +uint8_t rs485_available(void); +uint8_t rs485_read(void); #endif // UART_H diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..a97746b --- /dev/null +++ b/include/utils.h @@ -0,0 +1,51 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +/** + * @brief Combines two bytes into a 16-bit word + * @param hi High byte + * @param lo Low byte + * @return Combined 16-bit word + */ +static inline uint16_t to_word(uint8_t hi, uint8_t lo) { + return (hi << 8) | lo; +} + +/** + * @brief Splits a 16-bit word into two bytes + * @param val Value to split + * @param hi Pointer to store high byte + * @param lo Pointer to store low byte + */ +static inline void to_bytes(uint16_t val, uint8_t* hi, uint8_t* lo) { + *hi = (val >> 8) & 0xFF; + *lo = val & 0xFF; +} + +/** + * @brief Parses a decimal number from a string + * @param str String to parse + * @return Parsed decimal number + */ +static inline uint8_t parse_decimal(const char* str) { + uint8_t num = 0; + while (*str >= '0' && *str <= '9') { + num = num * 10 + (*str - '0'); + str++; + } + return num; +} + +/** + * @brief Extracts the node number from an ID string + * @param id ID string to extract from + * @return Extracted node number + */ +static inline uint8_t parse_node_number(const char* id) { + const char* last_dash = strrchr(id, '-'); + return last_dash ? parse_decimal(last_dash + 1) : 0; +} + +#endif // UTILS_H diff --git a/include/w5500.h b/include/w5500.h deleted file mode 100644 index 9ee2d11..0000000 --- a/include/w5500.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef W5500_H -#define W5500_H - -#include -#include - -// Definitions for socket indexes -#define DHCP_SOCKET 0 -#define DNS_SOCKET 1 -#define TCP_SOCKET 2 - -extern volatile int ip_assigned; - -// Initializes the W5500 chip -void configure_network(void); - -// resolves a domain name -// void resolve_domain_name(const char* domain_name); - -#endif // W5500_H diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..3653edc --- /dev/null +++ b/src/config.c @@ -0,0 +1,10 @@ +#include "config.h" + +// Node config definition +node_config_t NODE_CONFIG = {.id = "ch32-node-1", + .name = "test node 1, broken usb", + .location = "dunno yet"}; + +// RS485 devices definition +const rs485_device_t RS485_DEVICES[RS485_DEVICE_COUNT] = { + {.slave_id = 0x01, .type = DEVICE_RELAY, .name = "relay-1"}}; diff --git a/src/dhcp.c b/src/dhcp.c deleted file mode 100644 index 2a93fb5..0000000 --- a/src/dhcp.c +++ /dev/null @@ -1,105 +0,0 @@ -#include "dhcp.h" - -#include -#include - -#include "debug.h" -#include "systick.h" -#include "w5500.h" - -static volatile dhcp_state_t dhcp_state = DHCP_STATE_INIT; -static volatile uint32_t dhcp_lease_time = 0; -static volatile uint32_t dhcp_last_time = 0; -static volatile uint32_t dhcp_retry_count = 0; -static uint32_t last_check_time = 0; - -// Buffers -static uint8_t dhcp_buffer[512]; -static wiz_NetInfo current_net_info = { - .mac = {0xEA, 0x11, 0x22, 0x33, 0x44, 0xEA}, .dhcp = NETINFO_DHCP}; - -static void update_network_config(void) { - wizchip_setnetinfo(¤t_net_info); - DEBUG_PRINT("IP config: %d.%d.%d.%d\n", current_net_info.ip[0], - current_net_info.ip[1], current_net_info.ip[2], - current_net_info.ip[3]); -} - -void callback_ip_assigned(void) { - dhcp_lease_time = getDHCPLeasetime(); - DEBUG_PRINT("IP newly assigned! Lease time: %lu sec\n", dhcp_lease_time); - - getIPfromDHCP(current_net_info.ip); - getGWfromDHCP(current_net_info.gw); - getSNfromDHCP(current_net_info.sn); - getDNSfromDHCP(current_net_info.dns); - - dhcp_state = DHCP_STATE_LEASED; -} - -void callback_ip_updated(void) { - dhcp_lease_time = getDHCPLeasetime(); - DEBUG_PRINT("IP lease updated! New lease time: %lu sec\n", dhcp_lease_time); - - getIPfromDHCP(current_net_info.ip); - getGWfromDHCP(current_net_info.gw); - getSNfromDHCP(current_net_info.sn); - getDNSfromDHCP(current_net_info.dns); - - dhcp_state = DHCP_STATE_LEASED; -} - -void callback_ip_conflict(void) { - DEBUG_PRINT("IP conflict!\n"); - dhcp_state = DHCP_STATE_INIT; - dhcp_retry_count = 0; -} - -void dhcp_init(void) { - setSHAR(current_net_info.mac); - DHCP_init(DHCP_SOCKET, dhcp_buffer); - reg_dhcp_cbfunc(callback_ip_assigned, callback_ip_updated, - callback_ip_conflict); - dhcp_state = DHCP_STATE_INIT; - dhcp_retry_count = 0; -} - -void dhcp_process(void) { - uint32_t current_time = millis(); - - // 500ms processing - if ((current_time - last_check_time) >= 500) { - last_check_time = current_time; - - switch (dhcp_state) { - case DHCP_STATE_INIT: - DHCP_run(); - if (dhcp_state == DHCP_STATE_LEASED) { - update_network_config(); - } - break; - - case DHCP_STATE_LEASED: - // renew @ 50% of lease time - if (current_time - dhcp_last_time >= (dhcp_lease_time * 500)) { - dhcp_state = DHCP_STATE_RENEW; - DHCP_run(); - } - break; - - case DHCP_STATE_RENEW: - DHCP_run(); - if (dhcp_state == DHCP_STATE_LEASED) { - update_network_config(); - dhcp_last_time = current_time; - } - break; - - default: - DHCP_run(); - break; - } - } -} - -uint8_t dhcp_get_state(void) { return dhcp_state; } \ No newline at end of file diff --git a/src/gpio.c b/src/gpio.c index 4a87d0e..77a6454 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -24,7 +24,7 @@ typedef struct { static led_status_t led_status = {0}; -void init_gpio(void) { +void gpio_init(void) { // Enable clock for GPIOB RCC->APB2PCENR |= RCC_APB2Periph_GPIOB; diff --git a/src/main.c b/src/main.c index 4bc14c6..4a6392a 100644 --- a/src/main.c +++ b/src/main.c @@ -3,42 +3,56 @@ #include "ch32v003fun.h" #include "config.h" #include "debug.h" -#include "dhcp.h" #include "gpio.h" -#include "modbus_handler.h" +#include "modbus.h" #include "mqtt_handler.h" +#include "network.h" #include "onewire_temp.h" -#include "system_init.h" +#include "spi_dma.h" #include "systick.h" -#include "w5500.h" +#include "timer.h" +#include "uart.h" + +#define W5500_INIT_DELAY_MS 55 static mqtt_state_t mqtt_state; -static modbus_context_t modbus_ctx; + +void system_init(void) { + SystemInit(); + gpio_init(); + systick_init(); + tim2_init(); + spidma_init(); + uart2_init(UART_BRR_APB1); + rs485_init(UART_BRR_APB2); +} // Callback for modbus values static void on_modbus_value(uint8_t device_idx, const char* property, uint16_t value) { if (mqtt_state.is_connected) { - publish_value(&mqtt_state.client, RS485_DEVICES[device_idx].name, property, - value); + modbus_publish_value(&mqtt_state.client, RS485_DEVICES[device_idx].name, + property, value); } } int main(void) { - init_system(); + system_init(); Delay_Ms(W5500_INIT_DELAY_MS); configure_network(); - dhcp_init(); + network_init(); // block forever until dhcp resolves - wait_for_dhcp(); + while (dhcp_get_state() != DHCP_STATE_LEASED) { + dhcp_process(); + } // init handlers // init sensors before mqtt so we can add them to discovery onewire_temp_init(); onewire_temp_set_parallel(true); mqtt_init(&mqtt_state); - modbus_handler_init(&modbus_ctx, on_modbus_value); + modbus_handler_init(on_modbus_value); uint32_t last_temp_publish = 0; uint32_t last_temp_conversion = 0; diff --git a/src/modbus.c b/src/modbus.c new file mode 100644 index 0000000..ca5f804 --- /dev/null +++ b/src/modbus.c @@ -0,0 +1,277 @@ +#include "modbus.h" + +#include + +#include "debug.h" +#include "systick.h" +#include "uart.h" +#include "utils.h" + +// modbus ctx +typedef struct { + uint8_t buffer[256]; + uint16_t rx_len; + uint32_t last_send_time; + uint32_t response_timeout; + modbus_state_t state; + void (*on_response)(uint16_t value); + void (*on_error)(uint8_t error); +} modbus_t; + +// handler ctx +typedef struct { + uint8_t current_device_idx; + char current_property[16]; + void (*value_cb)(uint8_t device_idx, const char* property, uint16_t value); +} modbus_handler_t; + +static modbus_t mb; +static modbus_handler_t handler; + +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; +} + +static bool modbus_send(uint8_t slave_id, uint8_t function, uint16_t addr, + uint16_t value) { + if (mb.state != MODBUS_IDLE) { + return false; + } + + uint8_t* req = mb.buffer; + req[0] = slave_id; + req[1] = function; + + uint8_t hi, lo; + to_bytes(addr, &hi, &lo); + req[2] = hi; + req[3] = lo; + + to_bytes(value, &hi, &lo); + req[4] = hi; + req[5] = lo; + + uint16_t crc = modbus_crc16(req, 6); + req[6] = crc & 0xFF; + req[7] = (crc >> 8) & 0xFF; + + rs485_send(req, 8); + mb.last_send_time = millis(); + mb.rx_len = 0; + mb.state = MODBUS_WAITING_RESPONSE; + + return true; +} + +static void process_response(void) { + uint8_t* buf = mb.buffer; + uint16_t len = mb.rx_len; + + // check minimum length and CRC + if (len < 5 || + modbus_crc16(buf, len - 2) != ((buf[len - 1] << 8) | buf[len - 2])) { + mb.on_error(MODBUS_ERROR_CRC); + return; + } + + // check for error response + if (buf[1] & 0x80) { + mb.on_error(buf[2]); + return; + } + + // extract value based on function code + uint16_t value; + switch (buf[1]) { + case MODBUS_FC_READ_HOLDING_REGISTERS: + if (len >= 7) { + value = (buf[3] << 8) | buf[4]; + mb.on_response(value); + } + break; + + case MODBUS_FC_WRITE_SINGLE_REGISTER: + if (len == 8) { + value = (buf[4] << 8) | buf[5]; + mb.on_response(value); + } + break; + + default: + mb.on_error(MODBUS_ERROR_ILLEGAL_FUNCTION); + } +} + +static void modbus_process(void) { + if (mb.state != MODBUS_WAITING_RESPONSE) { + return; + } + + // chec for timeout + if (millis() - mb.last_send_time >= mb.response_timeout) { + mb.on_error(MODBUS_ERROR_TIMEOUT); + mb.state = MODBUS_IDLE; + return; + } + + // read available data + while (rs485_available() && mb.rx_len < sizeof(mb.buffer)) { + mb.buffer[mb.rx_len++] = rs485_read(); + + // complete message? + if (mb.rx_len >= 8 || (mb.rx_len >= 5 && mb.buffer[1] & 0x80) || + (mb.rx_len >= 5 && mb.buffer[1] == MODBUS_FC_READ_HOLDING_REGISTERS && + mb.rx_len >= (3 + mb.buffer[2] + 2))) { + process_response(); + mb.state = MODBUS_IDLE; + break; + } + } +} + +static void modbus_init(void (*response_cb)(uint16_t), + void (*error_cb)(uint8_t)) { + memset(&mb, 0, sizeof(mb)); + mb.state = MODBUS_IDLE; + mb.response_timeout = 200; + mb.on_response = response_cb; + mb.on_error = error_cb; + + // flush any stale data - we get 0x00 on startup?? + while (rs485_available()) { + rs485_read(); + } +} + +// internal response cb +static void on_modbus_response(uint16_t value) { + if (handler.current_device_idx >= RS485_DEVICE_COUNT || !handler.value_cb) { + return; + } + + // forward value to mqtt cb + handler.value_cb(handler.current_device_idx, handler.current_property, value); + + handler.current_device_idx = 0xFF; + handler.current_property[0] = '\0'; +} + +// internal error cb +static void on_modbus_error(__attribute__((unused)) uint8_t error_code) { + handler.current_device_idx = 0xFF; + handler.current_property[0] = '\0'; +} + +static uint16_t get_register_address(device_type_t type, const char* property) { + // todo: more device types + if (type == DEVICE_RELAY && strcmp(property, "state") == 0) { + return 0x0000; + } + + return 0xFFFF; // invalid reg +} + +void modbus_handler_init(modbus_value_cb value_callback) { + memset(&handler, 0xFF, sizeof(handler)); + handler.current_property[0] = '\0'; + handler.value_cb = value_callback; + + modbus_init(on_modbus_response, on_modbus_error); +} + +void modbus_handler_process(void) { modbus_process(); } + +bool modbus_handler_send_request(uint8_t device_idx, const char* property, + bool is_write, uint16_t value) { + if (device_idx >= RS485_DEVICE_COUNT) { + return false; + } + + uint16_t reg = get_register_address(RS485_DEVICES[device_idx].type, property); + if (reg == 0xFFFF) { + return false; + } + + uint8_t function = is_write ? MODBUS_FC_WRITE_SINGLE_REGISTER + : MODBUS_FC_READ_HOLDING_REGISTERS; + + if (modbus_send(RS485_DEVICES[device_idx].slave_id, function, reg, value)) { + // store ctx for resp handling + handler.current_device_idx = device_idx; + strncpy(handler.current_property, property, + sizeof(handler.current_property) - 1); + handler.current_property[sizeof(handler.current_property) - 1] = '\0'; + return true; + } + + return false; +} + +void modbus_publish_value(MQTTClient* client, const char* device_name, + const char* property, uint16_t value) { + char topic[MAX_TOPIC_LENGTH]; + char payload[17]; // 16 bits + null terminator + + snprintf(topic, sizeof(topic), "homie/%s/%s/%s", NODE_CONFIG.id, device_name, + property); + + // format based on property type + if (strcmp(property, "state") == 0) { + for (int i = 15; i >= 0; i--) { + payload[15 - i] = '0' + ((value >> i) & 1); + } + payload[16] = '\0'; + } else { + // todo: + return; + } + + MQTTMessage message = {.qos = QOS1, + .retained = 1, + .payload = payload, + .payloadlen = strlen(payload)}; + + if (MQTTPublish(client, topic, &message) != 0) { + DEBUG_PRINT("Failed to publish to %s\n", topic); + } +} diff --git a/src/modbus_handler.c b/src/modbus_handler.c deleted file mode 100644 index d09f445..0000000 --- a/src/modbus_handler.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "modbus_handler.h" - -#include - -#include "debug.h" -#include "systick.h" - -static struct { - modbus_context_t* ctx; - modbus_value_cb value_cb; - uint8_t last_device_idx; - char last_property[16]; -} handler; - -static void on_modbus_response(uint8_t* buf __attribute__((unused)), - uint16_t len __attribute__((unused)), - uint16_t value) { - if (handler.last_device_idx >= RS485_DEVICE_COUNT || !handler.value_cb) { - return; - } - - // call the value callback with parsed data - handler.value_cb(handler.last_device_idx, handler.last_property, value); - - // clear the context - handler.last_device_idx = 0xFF; - handler.last_property[0] = '\0'; -} - -static void on_modbus_error(uint8_t error_code __attribute__((unused))) { - // clear the context on error - handler.last_device_idx = 0xFF; - handler.last_property[0] = '\0'; -} - -void modbus_handler_init(modbus_context_t* ctx, - modbus_value_cb value_callback) { - handler.ctx = ctx; - handler.value_cb = value_callback; - handler.last_device_idx = 0xFF; - handler.last_property[0] = '\0'; - - // Initialize modbus with our callbacks - modbus_init(ctx, on_modbus_response, on_modbus_error); - modbus_set_timeout(ctx, 200); -} - -void modbus_handler_process(void) { modbus_process(handler.ctx); } - -// Get Modbus register for specific device type and property -static uint16_t get_register_address(device_type_t type, const char* property) { - if (type == DEVICE_RELAY && strcmp(property, "state") == 0) { - return 0x0000; - } - - return 0xFFFF; -} -bool modbus_handler_send_request(uint8_t device_idx, const char* property, - uint8_t is_write, uint16_t value) { - if (device_idx >= RS485_DEVICE_COUNT) { - return false; - } - - uint16_t reg = get_register_address(RS485_DEVICES[device_idx].type, property); - if (reg == 0xFFFF) { - return false; - } - - uint8_t fc = is_write ? MODBUS_FC_WRITE_SINGLE_REGISTER - : MODBUS_FC_READ_HOLDING_REGISTERS; - - if (modbus_send_request(handler.ctx, RS485_DEVICES[device_idx].slave_id, fc, - reg, value)) { - handler.last_device_idx = device_idx; - strncpy(handler.last_property, property, sizeof(handler.last_property) - 1); - handler.last_property[sizeof(handler.last_property) - 1] = '\0'; - return true; - } - - return false; -} \ No newline at end of file diff --git a/src/modbus_master.c b/src/modbus_master.c deleted file mode 100644 index 19b3783..0000000 --- a/src/modbus_master.c +++ /dev/null @@ -1,247 +0,0 @@ -#include "modbus_master.h" - -#include "debug.h" -#include "rs485.h" -#include "systick.h" - -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; -} - -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; -} - -uint16_t modbus_create_request(uint8_t* req, uint8_t slave_addr, - uint8_t function, uint16_t address, - uint16_t value) { - req[0] = slave_addr; - req[1] = function; - - uint8_t hi, lo; - mb_split(address, &hi, &lo); - req[2] = hi; - req[3] = lo; - - if (function == MODBUS_FC_WRITE_SINGLE_REGISTER) { - mb_split(value, &hi, &lo); - req[4] = hi; - req[5] = lo; - - // add crc - uint16_t crc = modbus_crc16(req, 6); - req[6] = crc & 0xFF; - req[7] = (crc >> 8) & 0xFF; - return 8; - } else if (function == MODBUS_FC_READ_HOLDING_REGISTERS) { - mb_split(value, &hi, &lo); // value = number of registers to read - req[4] = hi; - req[5] = lo; - - // add crc - uint16_t crc = modbus_crc16(req, 6); - req[6] = crc & 0xFF; - req[7] = (crc >> 8) & 0xFF; - return 8; - } - - return 0; -} - -uint8_t modbus_process_response(uint8_t* buf, uint16_t len, uint16_t* value) { - if (len < MB_MIN_LEN) { - return MODBUS_ERROR_VALUE; - } - - // check for error response - if (buf[1] & 0x80) { - return buf[2]; - } - - // verify crc - uint16_t rcv_crc = mb_word(buf[len - 1], buf[len - 2]); - uint16_t calc_crc = modbus_crc16(buf, len - 2); - - if (rcv_crc != calc_crc) { - return MODBUS_ERROR_VALUE; - } - - switch (buf[1]) { - case MODBUS_FC_WRITE_SINGLE_REGISTER: - if (len != MB_WREG_LEN) { - return MODBUS_ERROR_VALUE; - } - *value = mb_word(buf[4], buf[5]); - break; - - case MODBUS_FC_READ_HOLDING_REGISTERS: - if (len < 5) { - return MODBUS_ERROR_VALUE; - } - uint8_t byte_count = buf[2]; - for (uint8_t i = 0; i < byte_count / 2; i++) { - value[i] = mb_word(buf[3 + i * 2], buf[4 + i * 2]); - } - break; - - default: - return MODBUS_ERROR_FUNCTION; - } - - return MODBUS_ERROR_NONE; -} - -void modbus_init(modbus_context_t* ctx, - void (*response_callback)(uint8_t*, uint16_t, uint16_t), - void (*error_callback)(uint8_t)) { - while (rs485_available()) { - uint8_t byte = rs485_read(); - DEBUG_PRINT("Flushed stale byte: 0x%02X\n", byte); - } - - ctx->state = MODBUS_IDLE; - ctx->last_send_time = 0; - ctx->response_timeout = 100; // Default 100ms timeout - ctx->rx_len = 0; - ctx->current_bit = 1; - ctx->last_value = 0; - ctx->on_response = response_callback; - ctx->on_error = error_callback; -} - -void modbus_set_timeout(modbus_context_t* ctx, uint32_t timeout_ms) { - ctx->response_timeout = timeout_ms; -} - -bool modbus_send_request(modbus_context_t* ctx, uint8_t slave_addr, - uint8_t function, uint16_t address, - uint16_t value) { - if (ctx->state != MODBUS_IDLE) { - return false; // Busy - } - - uint16_t len = - modbus_create_request(ctx->buffer, slave_addr, function, address, value); - rs485_send(ctx->buffer, len); - - ctx->last_send_time = millis(); - ctx->rx_len = 0; - ctx->state = MODBUS_WAITING_RESPONSE; - - return true; // Success -} - -void modbus_process(modbus_context_t* ctx) { - uint32_t current_time = millis(); - - switch (ctx->state) { - case MODBUS_IDLE: - break; - - case MODBUS_WAITING_RESPONSE: - // Check for timeout - if (current_time - ctx->last_send_time >= ctx->response_timeout) { - DEBUG_PRINT("Timeout after %lu ms with %d bytes received", - current_time - ctx->last_send_time, ctx->rx_len); - if (ctx->on_error) { - ctx->on_error(MODBUS_ERROR_TIMEOUT); - } - ctx->state = MODBUS_IDLE; - break; - } - - // Read available data - while (rs485_available() && ctx->rx_len < sizeof(ctx->buffer)) { - ctx->buffer[ctx->rx_len++] = rs485_read(); - // DEBUG_PRINT("Received byte: 0x%02X\n", ctx->buffer[ctx->rx_len - 1]); - - if (ctx->rx_len >= MB_MIN_LEN) { - // holding reg - if (ctx->buffer[1] == MODBUS_FC_READ_HOLDING_REGISTERS) { - if (ctx->rx_len >= 3 && ctx->rx_len >= (3 + ctx->buffer[2] + 2)) { - ctx->state = MODBUS_PROCESS_RESPONSE; - break; - } - } - - // single reg w - else if (ctx->buffer[1] == MODBUS_FC_WRITE_SINGLE_REGISTER) { - if (ctx->rx_len >= MB_WREG_LEN) { - ctx->state = MODBUS_PROCESS_RESPONSE; - break; - } - } - - else if (ctx->buffer[1] & 0x80) { - if (ctx->rx_len >= 5) { // err 5b - ctx->state = MODBUS_PROCESS_RESPONSE; - break; - } - } - } - } - break; - - case MODBUS_PROCESS_RESPONSE: - uint16_t value; - uint8_t result = - modbus_process_response(ctx->buffer, ctx->rx_len, &value); - - if (result == MODBUS_ERROR_NONE) { - if (ctx->on_response) { - ctx->on_response(ctx->buffer, ctx->rx_len, value); - } - ctx->last_value = value; - } else if (ctx->on_error) { - ctx->on_error(result); - } - - ctx->state = MODBUS_IDLE; - break; - } -} \ No newline at end of file diff --git a/src/mqtt_handler.c b/src/mqtt_handler.c index 3164bb4..a32ca50 100644 --- a/src/mqtt_handler.c +++ b/src/mqtt_handler.c @@ -6,13 +6,13 @@ #include "debug.h" #include "gpio.h" -#include "modbus_handler.h" +#include "modbus.h" #include "onewire_temp.h" #include "systick.h" // MQTT #define MQTT_YIELD_INTERVAL 100 // 100ms between yields in main loop -#define MQTT_MAX_PACKET_WAIT 20 // Only wait up to 20ms for packet processing +#define MQTT_MAX_PACKET_WAIT 20 // Only wait up to 20ms for packet processing #define MQTT_RECONNECT_INTERVAL 5000 // 5 seconds between reconnection attempts // Homie convention constants @@ -158,41 +158,9 @@ void publish_retained(MQTTClient* client, const char* topic, } } -// TODO: this is a modbus value only -void publish_value(MQTTClient* client, const char* device_name, - const char* property, uint16_t value) { - DEBUG_PRINT("publish_value(device_name=%s, property=%s, value=%u)\n", - device_name, property, value); - - char topic[MAX_TOPIC_LENGTH]; - char payload[17]; // 16 bits + null terminator - - snprintf(topic, sizeof(topic), "homie/%s/%s/%s", NODE_CONFIG.id, device_name, - property); - - // formta based on property type - if (strcmp(property, "state") == 0) { - for (int i = 15; i >= 0; i--) { - payload[15 - i] = '0' + ((value >> i) & 1); - } - payload[16] = '\0'; - } else { - // todo: - return; - } - - MQTTMessage message = {.qos = QOS1, - .retained = 1, - .payload = payload, - .payloadlen = strlen(payload)}; - - if (MQTTPublish(client, topic, &message) != 0) { - DEBUG_PRINT("Failed to publish to %s\n", topic); - } -} - static void publish_device_attributes(MQTTClient* client) { char topic[MAX_TOPIC_LENGTH]; + char mac_str[18]; char* ptr = nodes_list; size_t remaining = sizeof(nodes_list); @@ -206,6 +174,14 @@ static void publish_device_attributes(MQTTClient* client) { snprintf(topic, sizeof(topic), "homie/%s/$state", NODE_CONFIG.id); publish_retained(client, topic, HOMIE_STATE_READY); + // Format MAC address as XX:XX:XX:XX:XX:XX + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", + NODE_CONFIG.mac[0], NODE_CONFIG.mac[1], NODE_CONFIG.mac[2], + NODE_CONFIG.mac[3], NODE_CONFIG.mac[4], NODE_CONFIG.mac[5]); + + snprintf(topic, sizeof(topic), "homie/%s/$mac", NODE_CONFIG.id); + publish_retained(client, topic, mac_str); + ptr = nodes_list; *ptr = '\0'; // add rs485 devices diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..bdea962 --- /dev/null +++ b/src/network.c @@ -0,0 +1,130 @@ +#include "network.h" + +#include +#include +#include + +#include "ch32v003fun.h" +#include "config.h" +#include "debug.h" +#include "spi_dma.h" +#include "systick.h" +#include "utils.h" + +#define DHCP_INTERVAL_CONNECTING 50 // init/renew +#define DHCP_INTERVAL_CONNECTED 500 // leased + +void configure_network(void) { + DEBUG_PRINT("Starting network configuration...\n"); + + // Setup chip select and SPI callbacks + reg_wizchip_cs_cbfunc(spi_select, spi_unselect); + // reg_wizchip_spi_cbfunc(spi_read_byte, spi_write_byte); + reg_wizchip_spiburst_cbfunc(spidma_read_buffer, spidma_write_buffer); + + uint8_t rx_tx_buff_sizes[] = {2, 2, 2, 2, 2, 2, 2, 2}; + wizchip_init(rx_tx_buff_sizes, rx_tx_buff_sizes); +} + +static volatile dhcp_state_t dhcp_state = DHCP_STATE_INIT; +static volatile uint32_t dhcp_lease_time = 0; +static volatile uint32_t last_lease_time = 0; +static uint32_t last_processing_time = 0; + +// Buffers +static uint8_t dhcp_buffer[512]; +static wiz_NetInfo current_net_info = { + .mac = {0x02, 0x32, 0x00, 0x01, 0x00, 0x00}, .dhcp = NETINFO_DHCP}; + +static void update_network_config(void) { + wizchip_setnetinfo(¤t_net_info); + DEBUG_PRINT("IP config: %d.%d.%d.%d\n", current_net_info.ip[0], + current_net_info.ip[1], current_net_info.ip[2], + current_net_info.ip[3]); +} + +void callback_dhcp_ip_update(void) { + dhcp_lease_time = getDHCPLeasetime(); + DEBUG_PRINT("IP %s! Lease time: %lu sec\n", + dhcp_state == DHCP_STATE_INIT ? "assigned" : "updated", + dhcp_lease_time); + + // Update all network configuration at once + getIPfromDHCP(current_net_info.ip); + getGWfromDHCP(current_net_info.gw); + getSNfromDHCP(current_net_info.sn); + getDNSfromDHCP(current_net_info.dns); + + dhcp_state = DHCP_STATE_LEASED; +} + +void callback_ip_conflict(void) { + DEBUG_PRINT("IP conflict!\n"); + dhcp_state = DHCP_STATE_INIT; +} + +// Initialize network with MAC based on node ID +void network_init(void) { + uint8_t node_num = parse_node_number(NODE_CONFIG.id); + + // MAC address + current_net_info.mac[0] = 0x02; // Locally administered + current_net_info.mac[1] = 0x32; // Organization ID (CH32) + current_net_info.mac[2] = 0x00; // Sub-organization + current_net_info.mac[3] = 0x01; // Device type + current_net_info.mac[4] = node_num >> 8; // Node number high byte + current_net_info.mac[5] = node_num & 0xFF; // Node number low byte + + // Copy MAC to network info + memcpy(NODE_CONFIG.mac, current_net_info.mac, 6); + + // Initialize network + setSHAR(current_net_info.mac); + DHCP_init(DHCP_SOCKET, dhcp_buffer); + reg_dhcp_cbfunc(callback_dhcp_ip_update, callback_dhcp_ip_update, + callback_ip_conflict); + dhcp_state = DHCP_STATE_INIT; +} + +void dhcp_process(void) { + uint32_t current_time = millis(); + uint16_t processing_interval = + dhcp_state == DHCP_STATE_INIT || dhcp_state == DHCP_STATE_RENEW + ? DHCP_INTERVAL_CONNECTING + : DHCP_INTERVAL_CONNECTED; + + if ((current_time - last_processing_time) >= processing_interval) { + last_processing_time = current_time; + + switch (dhcp_state) { + case DHCP_STATE_INIT: + DHCP_run(); + if (dhcp_state == DHCP_STATE_LEASED) { + update_network_config(); + } + break; + + case DHCP_STATE_LEASED: + // renew @ 50% of lease time + if (current_time - last_lease_time >= (dhcp_lease_time * 500)) { + dhcp_state = DHCP_STATE_RENEW; + DHCP_run(); + } + break; + + case DHCP_STATE_RENEW: + DHCP_run(); + if (dhcp_state == DHCP_STATE_LEASED) { + update_network_config(); + last_lease_time = current_time; + } + break; + + default: + DHCP_run(); + break; + } + } +} + +uint8_t dhcp_get_state(void) { return dhcp_state; } diff --git a/src/rs485.c b/src/rs485.c deleted file mode 100644 index 7a78b34..0000000 --- a/src/rs485.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "rs485.h" - -#include "ch32v003fun.h" - -#define RS485_TX_EN (1 << 14) // PB14 -#define RS485_RX_EN (1 << 15) // PB15 - -void rs485_init(int uart_brr) { - RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | // Enable GPIOA - RCC_APB2Periph_GPIOB | // Enable GPIOB - RCC_APB2Periph_USART1; // Enable USART1 - - // configure uart1 pins (PA9=TX, PA10=RX) - GPIOA->CFGHR &= - ~(0xf << (4 * 1) | 0xf << (4 * 2)); // Clear PA9 & PA10 config - GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) - << (4 * 1); // PA9 (TX) - GPIOA->CFGHR |= GPIO_CNF_IN_FLOATING << (4 * 2); // PA10 (RX) - - // configure rs485 direction control pins (PB14=TX_EN, PB15=RX_EN) - GPIOB->CFGHR &= - ~(0xf << (4 * 6) | 0xf << (4 * 7)); // Clear PB14 & PB15 config - GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) - << (4 * 6); // PB14 (TX_EN) - GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) - << (4 * 7); // PB15 (RX_EN) - - // initial state: rx mode - GPIOB->BCR = RS485_TX_EN; // TX_EN low - GPIOB->BSHR = RS485_RX_EN; // RX_EN high - - // uart1 configuration - 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 = uart_brr; - USART1->CTLR1 |= CTLR1_UE_Set; -} - -void rs485_send(uint8_t *buf, uint16_t len) { - // enable tx mode - GPIOB->BSHR = RS485_TX_EN; // TX_EN high - GPIOB->BSHR = RS485_RX_EN; // RX_EN low - - for (uint16_t i = 0; i < len; i++) { - while (!(USART1->STATR & USART_FLAG_TXE)); - USART1->DATAR = buf[i]; - } - - while (!(USART1->STATR & USART_FLAG_TC)); - - // switch back to rx mode - GPIOB->BCR = RS485_TX_EN; // TX_EN low - GPIOB->BCR = RS485_RX_EN; // RX_EN high -} - -uint8_t rs485_available(void) { return (USART1->STATR & USART_FLAG_RXNE) != 0; } - -uint8_t rs485_read(void) { return USART1->DATAR & 0xFF; } diff --git a/src/spi_dma.c b/src/spi_dma.c index 49ed657..2836791 100644 --- a/src/spi_dma.c +++ b/src/spi_dma.c @@ -79,7 +79,7 @@ void spidma_write_buffer(uint8_t* buf, uint16_t len) { TX_Channel->CFGR &= ~DMA_CFGR1_EN; } -void init_spidma(void) { +void spidma_init(void) { // Enable clock for GPIOA and SPI1 RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1; // Enable clock for DMA1 diff --git a/src/system_init.c b/src/system_init.c deleted file mode 100644 index d187d4d..0000000 --- a/src/system_init.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "system_init.h" - -#include - -#include "ch32v003fun.h" -#include "debug.h" -#include "dhcp.h" -#include "gpio.h" -#include "rs485.h" -#include "spi_dma.h" -#include "systick.h" -#include "timer.h" -#include "uart.h" - -#define DHCP_TIMEOUT_MS 120000 - -void init_system(void) { - SystemInit(); - init_gpio(); - init_systick(); - tim2_init(); - init_spidma(); - init_uart(UART_BRR_APB1); - rs485_init(UART_BRR_APB2); -} - -bool wait_for_dhcp(void) { - // uint32_t start = millis(); - while (dhcp_get_state() != DHCP_STATE_LEASED) { - // if (millis() - start >= DHCP_TIMEOUT_MS) { - // DEBUG_PRINT("DHCP timeout\n"); - // return false; - // } - dhcp_process(); - } - return true; -} diff --git a/src/systick.c b/src/systick.c index cd21ade..9a9d639 100644 --- a/src/systick.c +++ b/src/systick.c @@ -7,7 +7,7 @@ // ms counter volatile uint32_t systick_millis = 0; -void init_systick(void) { +void systick_init(void) { SysTick->CTLR = 0; SysTick->CMP = SYSTICK_ONE_MILLISECOND - 1; SysTick->CNT = 0; diff --git a/src/uart.c b/src/uart.c index 579a245..0f933ac 100644 --- a/src/uart.c +++ b/src/uart.c @@ -2,6 +2,9 @@ #include "ch32v003fun.h" +#define RS485_TX_EN (1 << 14) // PB14 +#define RS485_RX_EN (1 << 15) // PB15 + // Write multiple chars to UART int _write(__attribute__((unused)) int fd, const char *buf, int size) { for (int i = 0; i < size; i++) { @@ -18,7 +21,7 @@ int putchar(int c) { return (unsigned char)c; } -void init_uart(int uart_brr) { +void uart2_init(int uart_brr) { RCC->APB2PCENR |= RCC_APB2Periph_GPIOA; // Enable GPIOA on APB2 RCC->APB1PCENR |= RCC_APB1Periph_USART2; // Enable USART2 on APB1 @@ -34,4 +37,58 @@ void init_uart(int uart_brr) { USART2->BRR = uart_brr; // Enable USART2 USART2->CTLR1 |= CTLR1_UE_Set; -} \ No newline at end of file +} + +void rs485_init(int uart_brr) { + RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | // Enable GPIOA + RCC_APB2Periph_GPIOB | // Enable GPIOB + RCC_APB2Periph_USART1; // Enable USART1 + + // configure uart1 pins (PA9=TX, PA10=RX) + GPIOA->CFGHR &= + ~(0xf << (4 * 1) | 0xf << (4 * 2)); // Clear PA9 & PA10 config + GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) + << (4 * 1); // PA9 (TX) + GPIOA->CFGHR |= GPIO_CNF_IN_FLOATING << (4 * 2); // PA10 (RX) + + // configure rs485 direction control pins (PB14=TX_EN, PB15=RX_EN) + GPIOB->CFGHR &= + ~(0xf << (4 * 6) | 0xf << (4 * 7)); // Clear PB14 & PB15 config + GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) + << (4 * 6); // PB14 (TX_EN) + GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) + << (4 * 7); // PB15 (RX_EN) + + // initial state: rx mode + GPIOB->BCR = RS485_TX_EN; // TX_EN low + GPIOB->BSHR = RS485_RX_EN; // RX_EN high + + // uart1 configuration + 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 = uart_brr; + USART1->CTLR1 |= CTLR1_UE_Set; +} + +void rs485_send(uint8_t *buf, uint16_t len) { + // enable tx mode + GPIOB->BSHR = RS485_TX_EN; // TX_EN high + GPIOB->BSHR = RS485_RX_EN; // RX_EN low + + for (uint16_t i = 0; i < len; i++) { + while (!(USART1->STATR & USART_FLAG_TXE)); + USART1->DATAR = buf[i]; + } + + while (!(USART1->STATR & USART_FLAG_TC)); + + // switch back to rx mode + GPIOB->BCR = RS485_TX_EN; // TX_EN low + GPIOB->BCR = RS485_RX_EN; // RX_EN high +} + +uint8_t rs485_available(void) { return (USART1->STATR & USART_FLAG_RXNE) != 0; } + +uint8_t rs485_read(void) { return USART1->DATAR & 0xFF; } diff --git a/src/w5500.c b/src/w5500.c deleted file mode 100644 index 9d34304..0000000 --- a/src/w5500.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "w5500.h" - -#include -#include - -#include "ch32v003fun.h" -#include "config.h" -#include "debug.h" -#include "spi_dma.h" -#include "systick.h" - -void configure_network(void) { - DEBUG_PRINT("===\n"); - DEBUG_PRINT("Starting network configuration...\n"); - DEBUG_PRINT("===\n"); - - // Setup chip select and SPI callbacks - reg_wizchip_cs_cbfunc(spi_select, spi_unselect); - // reg_wizchip_spi_cbfunc(spi_read_byte, spi_write_byte); - reg_wizchip_spiburst_cbfunc(spidma_read_buffer, spidma_write_buffer); - - uint8_t rx_tx_buff_sizes[] = {2, 2, 2, 2, 2, 2, 2, 2}; - wizchip_init(rx_tx_buff_sizes, rx_tx_buff_sizes); -} - -// static uint8_t dns_buffer[512]; - -// todo: rm !!! -// void resolve_domain_name(const char* domain_name) { -// DEBUG_PRINT("Resolving domain name \"%s\"...\n", domain_name); - -// DNS_init(DNS_SOCKET, dns_buffer); -// // cloudflare dns -// uint8_t dns[] = {1, 1, 1, 1}; -// uint8_t addr[4]; -// int8_t res; -// uint8_t retries = 0; - -// while (retries < 3) { -// Delay_Ms(250); - -// res = DNS_run(dns, (uint8_t*)domain_name, addr); -// if (res == 1) { -// DEBUG_PRINT("Result: %d.%d.%d.%d\n", addr[0], addr[1], addr[2], -// addr[3]); break; -// } else { -// DEBUG_PRINT("DNS_run() failed, res = %d. Retries: %u\n", res, retries); -// } -// retries++; -// } -// }