Compare commits

..

10 Commits

Author SHA1 Message Date
3adc6f3b28 fix: onewire 2024-11-25 01:13:26 +06:00
8adc726b0b chore: debloat (remove platformio), onewire improvements
Squashed commit of the following:

commit 5f16309f629b9928d2134b85ae64af69bc3ebbcd
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Sun Nov 24 22:55:15 2024 +0600

    fix: Makefile, improve onewire retries

commit 55496a3bda941b52ff349dc75c9c06eb5a37c07d
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Mon Nov 18 00:41:18 2024 +0600

    fix: make onewire validity less strict

commit 3428a9bc9792508972ce3e7e4e35a64f047bca10
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Sun Nov 17 23:57:55 2024 +0600

    chore: rm bins

commit 1594e5ed430522b15466c8afa62ff7fb1b28947c
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Sun Nov 17 23:32:01 2024 +0600

    chore: unplatformiofy
2024-11-24 22:56:05 +06:00
2a7aa8aea3 chore: _ 2024-11-12 01:33:50 +06:00
48943ba71f chore: housekeeping 2024-11-12 00:55:52 +06:00
5f9c966602 fix: yet more wiznet bugs, reconnects, led status indicators 2024-11-11 15:40:05 +06:00
8fe50deeed chore: onewire 2024-11-11 01:31:19 +06:00
39f7755477 chore: cleanup 2024-11-10 14:59:30 +06:00
3ac9c62241 chore: mqtt impl, fix more wiznet bugs 2024-11-10 02:47:54 +06:00
80cf21f143 chore: bloatify 2024-11-09 03:34:43 +06:00
40f1cab745 fix: spi dma
Squashed commit of the following:

commit f0df85e2d18ff36b04443ddb23e645cbbd5bfa58
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Wed Oct 16 01:36:26 2024 +0600

    fix: SPI DMA

    wait for SPI TXE and BSY flags, also fix korean lib

commit a16b9f769807a78803ba1d7cd10a4a4843827bb2
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Tue Oct 15 21:09:59 2024 +0600

    moar log

commit 0c457e17ffb956cb5fbbc40865e62f8acf8f2eea
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Tue Oct 15 14:09:31 2024 +0600

    _

commit a0b6820bc1312e429d04bf0bb39bc2a8b234cfc5
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Tue Oct 15 13:55:24 2024 +0600

    rewrite w/o interrupts

commit 83c2ab75b326be098bc15698d77ab650b14613e0
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Tue Oct 15 13:01:41 2024 +0600

    dma config

commit d871fef77d7c1838ac84f02a499f5555f78bc9ce
Author: kuwoyuki <kuwoyuki@cock.li>
Date:   Tue Oct 15 04:47:23 2024 +0600

    more dma
2024-10-16 01:37:31 +06:00
94 changed files with 2852 additions and 833 deletions

8
.gitignore vendored
View File

@@ -1,6 +1,10 @@
.pio
.vscode/.browse.c_cpp.db* .vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
prv/ prv/
ch32_node.elf
ch32_node.lst
ch32_node.map
ch32_node.bin
ch32_node.hex

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "ch32v003fun"]
path = ch32v003fun
url = https://github.com/cnlohr/ch32v003fun.git

20
.vscode/settings.json vendored
View File

@@ -17,6 +17,24 @@
"debug.h": "c", "debug.h": "c",
"systick.h": "c", "systick.h": "c",
"timer.h": "c", "timer.h": "c",
"stdint.h": "c" "stdint.h": "c",
"mqtt_interface.h": "c",
"gpio.h": "c",
"modbus_handler.h": "c",
"rs485.h": "c",
"system_init.h": "c",
"mqtt_handler.h": "c",
"cstdlib": "c",
"modbus_master.h": "c",
"ch32v003_gpio_branchless.h": "c",
"mqttpacket.h": "c",
"stdbool.h": "c",
"onewire_temp.h": "c",
"modbus.h": "c",
"network.h": "c",
"cstdint": "c",
"utils.h": "c",
"onewire.h": "c",
"string.h": "c"
} }
} }

42
Makefile Normal file
View File

@@ -0,0 +1,42 @@
TARGET ?= ch32_node
TARGET_MCU ?= CH32V203
TARGET_MCU_PACKAGE ?= CH32V203C8T6
CH32V003FUN ?= ./ch32v003fun/ch32v003fun
MINICHLINK ?= ./ch32v003fun/minichlink
PREFIX ?= riscv64-none-elf
NEWLIB ?= /usr/riscv64-none-elf/include/
INCLUDE_DIRS ?= \
-I./include \
-I./ioLibrary_Driver \
-I./ioLibrary_Driver/MQTT \
-I./ioLibrary_Driver/MQTT/MQTTPacket/src
PROJECT_C_FILES := $(filter-out ./ch32_node.c, $(wildcard ./*.c))
LIB_C_FILES := \
./ioLibrary_Driver/socket.c \
./ioLibrary_Driver/wizchip_conf.c \
./ioLibrary_Driver/W5500/w5500.c \
./ioLibrary_Driver/DHCP/dhcp.c \
./ioLibrary_Driver/MQTT/mqtt_interface.c \
./ioLibrary_Driver/MQTT/MQTTClient.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTPacket.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTFormat.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTDeserializePublish.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTSerializePublish.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTConnectClient.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTSubscribeClient.c \
./ioLibrary_Driver/MQTT/MQTTPacket/src/MQTTUnsubscribeClient.c
ADDITIONAL_C_FILES ?= $(PROJECT_C_FILES) $(LIB_C_FILES)
include $(CH32V003FUN)/ch32v003fun.mk
CFLAGS += -Wall -Wextra $(INCLUDE_DIRS)
all: flash
flash: cv_flash
clean: cv_clean
.PHONY: all flash clean

View File

@@ -1,57 +1,28 @@
# ch32-node fw # CH32V203 Node Firmware
fw for a ch32v203 node w/ w5500 ethernet
## current Firmware for a CH32V203 MCU with a W5500 Ethernet controller.
### SysTick IRQ and DMA issue ## TODO:
[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. - Set up interrupts for RS485 publish
- 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`...
Not an interrupt priority issue?: ## Features
![LA](notes/2024-10-15-001533_1895x722_scrot.png)
- W5500 Ethernet controller support
- Network protocol support including:
- DHCP client
- MQTT client
- RS485 communication
- OneWire sensors
## ~~previous (DNS Processing)~~ ## Development Environment
solved by [patching](https://git.hye.su/mira/ch32-node/commit/259d63197e06c1a92b979490d4cd8f0fdb98f8d0#diff-6ba50689ba55dac7cfe3e9b011e594098c931e21) the korean bloatlib (`dns_makequery` in DNS.c)
w/ ch32v003fun - Board: CH32V203C8T6
- Framework: ch32v003fun
- 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` ## Project Structure
> **DNS_run() failed, res = 0**
w/ WCH HAL (none-os).. I get a full response - `/include` - Header files
- `/lib` - Project libraries including WIZnet ioLibrary
> Receive DNS message from 192.168.102.1(53). len = 56 - `/src` - Source code
> 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`

88
ch32_node.c Normal file
View File

@@ -0,0 +1,88 @@
#include <string.h>
#include "ch32v003fun.h"
#include "config.h"
#include "debug.h"
#include "gpio.h"
#include "modbus.h"
#include "mqtt_handler.h"
#include "network.h"
#include "onewire_temp.h"
#include "spi_dma.h"
#include "systick.h"
#include "timer.h"
#include "uart.h"
#define W5500_INIT_DELAY_MS 55
static mqtt_state_t mqtt_state;
void sysinit(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) {
modbus_publish_value(&mqtt_state.client, RS485_DEVICES[device_idx].name,
property, value);
}
}
int main(void) {
sysinit();
Delay_Ms(W5500_INIT_DELAY_MS);
configure_network();
network_init();
// block forever until dhcp resolves
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(on_modbus_value);
uint32_t last_temp_publish = 0;
uint32_t last_temp_conversion = 0;
const uint32_t TEMP_PUBLISH_INTERVAL = 10000; // 10 seconds
const uint32_t CONVERSION_INTERVAL = 1000; // 1 second
while (1) {
uint32_t current_time = millis();
led_status_process();
dhcp_process();
mqtt_process(&mqtt_state);
modbus_handler_process();
// TODO: doesn't make sense to convert every 1s, should be the same interval
// as publish
if (current_time - last_temp_conversion >= CONVERSION_INTERVAL) {
onewire_temp_start_parallel();
last_temp_conversion = current_time;
}
onewire_temp_process(); // process all sensors
if (current_time - last_temp_publish >= TEMP_PUBLISH_INTERVAL) {
if (mqtt_state.is_connected) {
onewire_temp_publish_values(&mqtt_state.client, NODE_CONFIG.id);
}
last_temp_publish = current_time;
}
}
return 0;
}

1
ch32v003fun Submodule

Submodule ch32v003fun added at 69801af882

10
config.c Normal file
View File

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

98
gpio.c Normal file
View File

@@ -0,0 +1,98 @@
#include "gpio.h"
#include <stdbool.h>
#include "ch32v003fun.h"
#include "systick.h"
#define LED_BLINK_SLOW 1000 // Warning blink interval (ms)
#define LED_BLINK_FAST 500 // Error blink interval (ms)
#define LED_BREATH_PERIOD 2000 // Breathing effect period (ms)
#define STATE_STABILITY 500 // Minimum time before state change (ms)
#define LED_G (1 << 4)
#define LED_B (1 << 3)
typedef struct {
uint32_t last_update;
uint32_t stable_since;
led_state_t current_state;
led_state_t target_state;
uint8_t blink_state;
uint32_t last_blink;
} led_status_t;
static led_status_t led_status = {0};
void gpio_init(void) {
// Enable clock for GPIOB
RCC->APB2PCENR |= RCC_APB2Periph_GPIOB;
// GPIOB: Pins 3 and 4 as Output, Push-Pull, 10MHz
GPIOB->CFGLR &= ~((0xF << (4 * 3)) | (0xF << (4 * 4)));
GPIOB->CFGLR |= ((GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 3)) |
((GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 4));
// GPIOB: Pin 9 as Output, Push-Pull, 10MHz
GPIOB->CFGHR &= ~(0xF << (4 * (9 - 8)));
GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * (9 - 8));
led_status.current_state = LED_STATE_OFF;
led_status.target_state = LED_STATE_OFF;
GPIOB->BSHR = LED_G | LED_B;
}
void led_status_set(led_state_t state) {
uint32_t now = millis();
// stability timer upd on state change
if (led_status.target_state != state) {
led_status.stable_since = now;
led_status.target_state = state;
}
}
static inline void leds(bool g, bool b) {
uint32_t val = (g ? LED_G << 16 : LED_G) | (b ? LED_B << 16 : LED_B);
GPIOB->BSHR = val;
}
void led_status_process(void) {
uint32_t now = millis();
if (now - led_status.stable_since < STATE_STABILITY) {
return;
}
switch (led_status.target_state) {
case LED_STATE_OFF:
GPIOB->BSHR = LED_G | LED_B;
break;
case LED_STATE_ON:
leds(1, 0); // green on, blue off
break;
case LED_STATE_WARNING:
if (now - led_status.last_blink >= LED_BLINK_SLOW) {
led_status.blink_state = !led_status.blink_state;
led_status.last_blink = now;
leds(led_status.blink_state, 0);
}
break;
case LED_STATE_ERROR:
if (now - led_status.last_blink >= LED_BLINK_FAST) {
led_status.blink_state = !led_status.blink_state;
led_status.last_blink = now;
leds(led_status.blink_state, !led_status.blink_state);
}
break;
case LED_STATE_BUSY:
bool blue_on = (now % LED_BREATH_PERIOD) < LED_BREATH_PERIOD / 2;
leds(0, blue_on);
break;
}
}

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -3,23 +3,48 @@
#include <stdint.h> #include <stdint.h>
// debug flags
#define DEBUG_MODE 1 #define DEBUG_MODE 1
#define DNS_RUN_INTERVAL_MS 100 // device type definitions
typedef enum {
DEVICE_RELAY = 1,
DEVICE_SOIL_SENSOR = 2,
DEVICE_THERMOMETER = 3
} device_type_t;
// #define LOCAL_PORT 5000 // 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
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
#define MQTT_SERVER_IP {192, 168, 102, 100}
#define MQTT_PORT 1883
// MQTT configuration // MQTT configuration
#define CLIENT_ID "ch32_node"
#define MQTT_TARGET_IP {192, 168, 102, 147}
#define MQTT_TARGET_PORT 1883
#define MQTT_KEEP_ALIVE_INTERVAL 60 #define MQTT_KEEP_ALIVE_INTERVAL 60
#define MQTT_TX_BUFFER_SIZE 128 #define MQTT_TX_BUFFER_SIZE 128
#define MQTT_RX_BUFFER_SIZE 128 #define MQTT_RX_BUFFER_SIZE 128
#define MQTT_COMMAND_TIMEOUT_MS 1000 #define MQTT_COMMAND_TIMEOUT_MS 1000
#define SUB_TOPIC "listen/world"
#define PUB_TOPIC "hello/world" // node config declaration
extern node_config_t NODE_CONFIG;
// RS485 devices configuration
#define RS485_DEVICE_COUNT 1
// RS485 devices declaration
extern const rs485_device_t RS485_DEVICES[RS485_DEVICE_COUNT];
#endif // CONFIG_H #endif // CONFIG_H

View File

@@ -1,8 +1,7 @@
#ifndef _FUNCONFIG_H #ifndef _FUNCONFIG_H
#define _FUNCONFIG_H #define _FUNCONFIG_H
// board definition file will already take care of this #define CH32V20x 1
// #define CH32V003 1
#define FUNCONF_USE_HSI 0 // Use HSI Internal Oscillator #define FUNCONF_USE_HSI 0 // Use HSI Internal Oscillator
#define FUNCONF_USE_HSE 1 // Use External Oscillator #define FUNCONF_USE_HSE 1 // Use External Oscillator

View File

@@ -3,7 +3,20 @@
#include <stdint.h> #include <stdint.h>
// Function prototype for initializing GPIO // Status states
void init_gpio(void); typedef enum {
LED_STATE_OFF,
LED_STATE_ON, // ok
LED_STATE_WARNING, // slow blink
LED_STATE_ERROR, // fast blink
LED_STATE_BUSY // "breathing"
} led_state_t;
// Initialize GPIO
void gpio_init(void);
// LED status handling
void led_status_set(led_state_t state);
void led_status_process(void);
#endif // GPIO_H #endif // GPIO_H

56
include/modbus.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef MODBUS_H
#define MODBUS_H
#include <MQTT/MQTTClient.h>
#include <stdbool.h>
#include <stdint.h>
#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

46
include/mqtt_handler.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef MQTT_HANDLER_H
#define MQTT_HANDLER_H
#include <DHCP/dhcp.h>
#include <stdbool.h>
#include "ch32v003fun.h"
#include "network.h"
#define MAX_PAYLOAD_LENGTH 256
// Options structure for client identification
typedef struct {
char* clientid;
char* username;
char* password;
int qos;
} ch32_mqtt_options_t;
// MQTT state
typedef struct {
Network network;
MQTTClient client;
ch32_mqtt_options_t opts;
uint32_t last_reconnect;
uint32_t last_yield;
bool is_connected;
char base_topic[64];
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);
// 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 // MQTT_HANDLER_H

27
include/network.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef NETWORK_H
#define NETWORK_H
#include <MQTT/MQTTClient.h>
#include <stdint.h>
// 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

119
include/onewire.h Normal file
View File

@@ -0,0 +1,119 @@
#ifndef CH32V003_ONEWIRE_H
#define CH32V003_ONEWIRE_H
#include <stdbool.h>
#include <stdint.h>
#include "ch32v003fun.h"
// GPIO Direct Access Definitions
#ifndef OneWire_Direct_GPIO_h
#define OneWire_Direct_GPIO_h
static inline __attribute__((always_inline)) uint8_t directRead() {
return (GPIOB->INDR & (1 << 9)) ? 1 : 0;
}
static inline void directModeInput() {
GPIOB->CFGHR &= ~(0xF << (4 * (9 - 8)));
GPIOB->CFGHR |= (GPIO_CNF_IN_FLOATING << (4 * (9 - 8)));
}
static inline void directModeOutput() {
GPIOB->CFGHR &= ~(0xF << (4 * (9 - 8)));
GPIOB->CFGHR |= ((GPIO_Speed_50MHz | GPIO_CNF_OUT_PP) << (4 * (9 - 8)));
}
static inline __attribute__((always_inline)) void directWriteLow() {
GPIOB->BCR = (1 << 9);
}
static inline __attribute__((always_inline)) void directWriteHigh() {
GPIOB->BSHR = (1 << 9);
}
#define DIRECT_READ() directRead()
#define DIRECT_WRITE_LOW() directWriteLow()
#define DIRECT_WRITE_HIGH() directWriteHigh()
#define DIRECT_MODE_INPUT() directModeInput()
#define DIRECT_MODE_OUTPUT() directModeOutput()
#endif
// timing configuration
// time between line check retries
#define ONEWIRE_RESET_RETRY_TIME 2
// reset cycle
#define ONEWIRE_RESET_LOW_TIME 480
#define ONEWIRE_RESET_SAMPLE_TIME 60
#define ONEWIRE_RESET_POST_TIME 410
// write 1 bit
#define ONEWIRE_WRITE_1_LOW_TIME 6
#define ONEWIRE_WRITE_1_TOTAL_TIME 64
// write 0 bit
#define ONEWIRE_WRITE_0_LOW_TIME 80
#define ONEWIRE_WRITE_0_TOTAL_TIME 84
// read bit
#define ONEWIRE_READ_INIT_LOW_TIME 6
#define ONEWIRE_READ_SAMPLE_TIME 8
#define ONEWIRE_READ_TOTAL_TIME 64
// OneWire Function Declarations
// Initialize the OneWire bus
void OneWireBegin(void);
// Perform a 1-Wire reset cycle. Returns 1 if a device responds with a presence
// pulse
uint8_t OneWireReset(void);
// Select a device on the bus using its ROM code
void OneWireSelect(const uint8_t rom[8]);
// Skip ROM selection - addresses all devices on the bus
void OneWireSkip(void);
// Write a byte to the bus. If 'power' is true, maintain strong pullup after
// write
void OneWireWrite(uint8_t v, uint8_t power);
// Write multiple bytes to the bus
void OneWireWriteBytes(const uint8_t *buf, uint16_t count, bool power);
// Read a byte from the bus
uint8_t OneWireRead(void);
// Read multiple bytes from the bus
void OneWireReadBytes(uint8_t *buf, uint16_t count);
// Write a single bit to the bus
void OneWireWriteBit(uint8_t v);
// Read a single bit from the bus
uint8_t OneWireReadBit(void);
// Stop forcing power onto the bus
void OneWireDepower(void);
// Reset the search state
void OneWireResetSearch(void);
// Setup search to find devices of a specific family code
void OneWireTargetSearch(uint8_t family_code);
// Search for the next device on the bus
bool OneWireSearch(uint8_t *newAddr, bool search_mode);
// Calculate 8-bit CRC
uint8_t OneWireCrc8(const uint8_t *addr, uint8_t len);
// Check if received CRC matches calculated CRC
bool OneWireCheckCrc16(const uint8_t *input, uint16_t len,
const uint8_t *inverted_crc, uint16_t crc);
// Calculate 16-bit CRC
uint16_t OneWireCrc16(const uint8_t *input, uint16_t len, uint16_t crc);
#endif // CH32V003_ONEWIRE_H

46
include/onewire_temp.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef ONEWIRE_TEMP_H
#define ONEWIRE_TEMP_H
#include <stdbool.h>
#include <stdint.h>
#include "mqtt_handler.h"
#define ONEWIRE_MAX_SENSORS 16
#define ONEWIRE_TEMP_INVALID -999.0f
typedef enum {
ONEWIRE_STATE_READY,
ONEWIRE_STATE_CONVERTING,
ONEWIRE_STATE_READ
} onewire_state_t;
// Initialize OneWire temperature system
void onewire_temp_init(void);
// Process all sensors (call regularly)
void onewire_temp_process(void);
// Start parallel conversion on all sensors
void onewire_temp_start_parallel(void);
// Set parallel conversion mode
void onewire_temp_set_parallel(bool enable);
// Get temperature for sensor index
float onewire_temp_get(uint8_t index);
// Get total number of discovered sensors
uint8_t onewire_temp_count(void);
// Get sensor address
const uint8_t* onewire_temp_address(uint8_t index);
// Check if sensor is valid
bool onewire_temp_valid(uint8_t index);
// MQTT
void onewire_temp_publish_discovery(MQTTClient* client, const char* node_id);
void onewire_temp_publish_values(MQTTClient* client, const char* node_id);
#endif // ONEWIRE_TEMP_H

View File

@@ -12,7 +12,7 @@ typedef enum {
} transfer_state_t; } transfer_state_t;
// SPI DMA initialization function // SPI DMA initialization function
void init_spidma(void); void spidma_init(void);
// SPI DMA buffer read // SPI DMA buffer read
void spidma_read_buffer(uint8_t *buf, uint16_t len); void spidma_read_buffer(uint8_t *buf, uint16_t len);
@@ -20,11 +20,11 @@ void spidma_read_buffer(uint8_t *buf, uint16_t len);
// SPI DMA buffer write // SPI DMA buffer write
void spidma_write_buffer(uint8_t *buf, uint16_t len); void spidma_write_buffer(uint8_t *buf, uint16_t len);
// SPI (non-DMA) byte read // // SPI (non-DMA) byte read
uint8_t spi_read_byte(); // uint8_t spi_read_byte();
// SPI (non-DMA) byte write // // SPI (non-DMA) byte write
void spi_write_byte(uint8_t byte); // void spi_write_byte(uint8_t byte);
// activate CS // activate CS
void spi_select(void); void spi_select(void);

View File

@@ -8,9 +8,8 @@
(uint32_t)(FUNCONF_SYSTEM_CORE_CLOCK / TIMER_DELAY) (uint32_t)(FUNCONF_SYSTEM_CORE_CLOCK / TIMER_DELAY)
#define millis() (systick_millis) #define millis() (systick_millis)
void init_systick(void);
// ms counter incremented by SysTick // ms counter incremented by SysTick
// extern volatile uint32_t systick_millis; extern volatile uint32_t systick_millis;
void systick_init(void);
#endif // SYSTICK_H #endif // SYSTICK_H

View File

@@ -3,15 +3,29 @@
#include <stdint.h> #include <stdint.h>
// Macro definitions // RS485 baud rate
#define APB1_CLOCK (FUNCONF_SYSTEM_CORE_CLOCK / 2) // APB1 is divided by 2 #define UART1_BAUD_RATE 9600
#define UART2_BAUD_RATE 115200
// APB1 bus clock is half the system core clock
#define APB1_CLOCK (FUNCONF_SYSTEM_CORE_CLOCK / 2)
// Calculate baud rate divisors
// Adds BAUD_RATE/2 for rounding to nearest integer
#define UART_BRR_APB1 \ #define UART_BRR_APB1 \
(((APB1_CLOCK) + (UART_BAUD_RATE / 2)) / (UART_BAUD_RATE)) // USART2 (((APB1_CLOCK) + (UART2_BAUD_RATE / 2)) / (UART2_BAUD_RATE))
#define UART_BRR_APB2 \ #define UART_BRR_APB2 \
(((FUNCONF_SYSTEM_CORE_CLOCK) + (UART_BAUD_RATE / 2)) / \ (((FUNCONF_SYSTEM_CORE_CLOCK) + (UART1_BAUD_RATE / 2)) / (UART1_BAUD_RATE))
(UART_BAUD_RATE)) // USART1
// Function prototypes // 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 #endif // UART_H

51
include/utils.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef UTILS_H
#define UTILS_H
#include <stdint.h>
/**
* @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

View File

@@ -1,42 +0,0 @@
#ifndef W5500_H
#define W5500_H
#include <MQTT/MQTTClient.h>
#include <stdint.h>
// Definitions for socket indexes
#define DHCP_SOCKET 0
#define DNS_SOCKET 1
#define TCP_SOCKET 2
// Options structure for client identification
typedef struct {
char* clientid;
char* username;
char* password;
int qos;
} ch32_mqtt_options_t;
extern volatile int ip_assigned;
void handle_ip_assigned(void);
// Initializes the W5500 chip
void configure_network(void);
void configure_dhcp(void);
// resolves a domain name
void resolve_domain_name(const char* domain_name);
// init and connect the MQTT client
MQTTClient setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts);
int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos,
messageHandler message_callback);
// publish a message to a topic
void publish_message(MQTTClient* client, const char* payload,
const char* topic);
#endif // W5500_H

View File

@@ -601,7 +601,9 @@ int8_t parseDHCPMSG(void)
printf("DHCP message : %d.%d.%d.%d(%d) %d received. \r\n",svr_addr[0],svr_addr[1],svr_addr[2], svr_addr[3],svr_port, len); printf("DHCP message : %d.%d.%d.%d(%d) %d received. \r\n",svr_addr[0],svr_addr[1],svr_addr[2], svr_addr[3],svr_port, len);
#endif #endif
} }
else return 0; else {
return 0;
}
if (svr_port == DHCP_SERVER_PORT) { if (svr_port == DHCP_SERVER_PORT) {
// compare mac address // compare mac address
if ( (pDHCPMSG->chaddr[0] != DHCP_CHADDR[0]) || (pDHCPMSG->chaddr[1] != DHCP_CHADDR[1]) || if ( (pDHCPMSG->chaddr[0] != DHCP_CHADDR[0]) || (pDHCPMSG->chaddr[1] != DHCP_CHADDR[1]) ||
@@ -974,10 +976,7 @@ void DHCP_init(uint8_t s, uint8_t * buf)
/* Reset the DHCP timeout count and retry count. */ /* Reset the DHCP timeout count and retry count. */
void reset_DHCP_timeout(void) void reset_DHCP_timeout(void)
{ {
#ifdef _DHCP_DEBUG_ dhcp_tick_1s = 0;
printf("> reset_DHCP_timeout !!!\r\n");
#endif
// dhcp_tick_1s = 0;
dhcp_tick_next = DHCP_WAIT_TIME; dhcp_tick_next = DHCP_WAIT_TIME;
dhcp_retry_count = 0; dhcp_retry_count = 0;
} }

View File

@@ -57,7 +57,7 @@ extern "C" {
* @details If you want to display debug & processing message, Define _DHCP_DEBUG_ * @details If you want to display debug & processing message, Define _DHCP_DEBUG_
* @note If defined, it depends on <stdio.h> * @note If defined, it depends on <stdio.h>
*/ */
#define _DHCP_DEBUG_ // #define _DHCP_DEBUG_
/* Retry to processing DHCP */ /* Retry to processing DHCP */

View File

@@ -14,6 +14,7 @@
* Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/ *******************************************************************************/
#include "MQTTClient.h" #include "MQTTClient.h"
#include <string.h>
static void NewMessageData(MessageData* md, MQTTString* aTopicName, MQTTMessage* aMessage) { static void NewMessageData(MessageData* md, MQTTString* aTopicName, MQTTMessage* aMessage) {
md->topicName = aTopicName; md->topicName = aTopicName;
@@ -41,7 +42,7 @@ static int sendPacket(MQTTClient* c, int length, Timer* timer)
if (sent == length) if (sent == length)
{ {
TimerCountdown(&c->ping_timer, c->keepAliveInterval); // record the fact that we have successfully sent the packet TimerCountdown(&c->ping_timer, c->keepAliveInterval); // record the fact that we have successfully sent the packet
rc = SUCCESSS; rc = SUCCESS;
} }
else else
rc = FAILURE; rc = FAILURE;
@@ -56,7 +57,8 @@ void MQTTClientInit(MQTTClient* c, Network* network, unsigned int command_timeou
c->ipstack = network; c->ipstack = network;
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
c->messageHandlers[i].topicFilter = 0; c->messageHandlers[i].topicFilter[0] = '\0';
c->messageHandlers[i].fp = NULL;
c->command_timeout_ms = command_timeout_ms; c->command_timeout_ms = command_timeout_ms;
c->buf = sendbuf; c->buf = sendbuf;
c->buf_size = sendbuf_size; c->buf_size = sendbuf_size;
@@ -108,12 +110,17 @@ static int readPacket(MQTTClient* c, Timer* timer)
int len = 0; int len = 0;
int rem_len = 0; int rem_len = 0;
/* 1. read the header byte. This has the packet type in it */ /* 1. read the header byte. This has the packet type in it */
if (c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer)) != 1) int read_len = c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer));
goto exit; if (read_len == 0) { // Timeout/no data
return SUCCESS;
}
if (read_len < 0) {
return FAILURE;
}
len = 1; len = 1;
/* 2. read the remaining length. This is variable in itself */ /* 2. read the remaining length. This is variable in itself */
decodePacket(c, &rem_len, TimerLeftMS(timer)); decodePacket(c, &rem_len, TimerLeftMS(timer));
len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */
@@ -167,15 +174,15 @@ int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message)
// we have to find the right message handler - indexed by topic // we have to find the right message handler - indexed by topic
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
{ {
if (c->messageHandlers[i].topicFilter != 0 && (MQTTPacket_equals(topicName, (char*)c->messageHandlers[i].topicFilter) || if (c->messageHandlers[i].topicFilter[0] != '\0' && (MQTTPacket_equals(topicName, c->messageHandlers[i].topicFilter) ||
isTopicMatched((char*)c->messageHandlers[i].topicFilter, topicName))) isTopicMatched(c->messageHandlers[i].topicFilter, topicName)))
{ {
if (c->messageHandlers[i].fp != NULL) if (c->messageHandlers[i].fp != NULL)
{ {
MessageData md; MessageData md;
NewMessageData(&md, topicName, message); NewMessageData(&md, topicName, message);
c->messageHandlers[i].fp(&md); c->messageHandlers[i].fp(&md);
rc = SUCCESSS; rc = SUCCESS;
} }
} }
} }
@@ -185,7 +192,7 @@ int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message)
MessageData md; MessageData md;
NewMessageData(&md, topicName, message); NewMessageData(&md, topicName, message);
c->defaultMessageHandler(&md); c->defaultMessageHandler(&md);
rc = SUCCESSS; rc = SUCCESS;
} }
return rc; return rc;
@@ -194,11 +201,16 @@ int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message)
int keepalive(MQTTClient* c) int keepalive(MQTTClient* c)
{ {
int rc = FAILURE; int rc = SUCCESS;
if (c->keepAliveInterval == 0) if (c->keepAliveInterval == 0)
{ {
rc = SUCCESSS; goto exit;
}
if (!c->isconnected)
{
// rc = FAILURE;
goto exit; goto exit;
} }
@@ -209,9 +221,24 @@ int keepalive(MQTTClient* c)
Timer timer; Timer timer;
TimerInit(&timer); TimerInit(&timer);
TimerCountdownMS(&timer, 1000); TimerCountdownMS(&timer, 1000);
int len = MQTTSerialize_pingreq(c->buf, c->buf_size); int len = MQTTSerialize_pingreq(c->buf, c->buf_size);
if (len > 0 && (rc = sendPacket(c, len, &timer)) == SUCCESSS) // send the ping packet if (len <= 0)
{
rc = FAILURE;
goto exit;
}
rc = sendPacket(c, len, &timer);
if (rc == SUCCESS)
{
c->ping_outstanding = 1; c->ping_outstanding = 1;
TimerCountdown(&c->ping_timer, c->keepAliveInterval);
}
}
else
{
rc = FAILURE;
} }
} }
@@ -219,14 +246,16 @@ exit:
return rc; return rc;
} }
int cycle(MQTTClient* c, Timer* timer) int cycle(MQTTClient* c, Timer* timer)
{ {
// read the socket, see what work is due // read the socket, see what work is due
unsigned short packet_type = readPacket(c, timer); int packet_type = readPacket(c, timer);
if (packet_type == FAILURE) {
return FAILURE;
}
int len = 0, int len = 0,
rc = SUCCESSS; rc = SUCCESS;
switch (packet_type) switch (packet_type)
{ {
@@ -267,7 +296,7 @@ int cycle(MQTTClient* c, Timer* timer)
rc = FAILURE; rc = FAILURE;
else if ((len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREL, 0, mypacketid)) <= 0) else if ((len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREL, 0, mypacketid)) <= 0)
rc = FAILURE; rc = FAILURE;
else if ((rc = sendPacket(c, len, timer)) != SUCCESSS) // send the PUBREL packet else if ((rc = sendPacket(c, len, timer)) != SUCCESS) // send the PUBREL packet
rc = FAILURE; // there was a problem rc = FAILURE; // there was a problem
if (rc == FAILURE) if (rc == FAILURE)
goto exit; // there was a problem goto exit; // there was a problem
@@ -279,9 +308,12 @@ int cycle(MQTTClient* c, Timer* timer)
c->ping_outstanding = 0; c->ping_outstanding = 0;
break; break;
} }
keepalive(c); rc = keepalive(c); // Check keepalive return value
if (rc != SUCCESS) {
return rc;
}
exit: exit:
if (rc == SUCCESSS) if (rc == SUCCESS)
rc = packet_type; rc = packet_type;
return rc; return rc;
} }
@@ -289,7 +321,7 @@ exit:
int MQTTYield(MQTTClient* c, int timeout_ms) int MQTTYield(MQTTClient* c, int timeout_ms)
{ {
int rc = SUCCESSS; int rc = SUCCESS;
Timer timer; Timer timer;
TimerInit(&timer); TimerInit(&timer);
@@ -371,7 +403,7 @@ int MQTTConnect(MQTTClient* c, MQTTPacket_connectData* options)
TimerCountdown(&c->ping_timer, c->keepAliveInterval); TimerCountdown(&c->ping_timer, c->keepAliveInterval);
if ((len = MQTTSerialize_connect(c->buf, c->buf_size, options)) <= 0) if ((len = MQTTSerialize_connect(c->buf, c->buf_size, options)) <= 0)
goto exit; goto exit;
if ((rc = sendPacket(c, len, &connect_timer)) != SUCCESSS) // send the connect packet if ((rc = sendPacket(c, len, &connect_timer)) != SUCCESS) // send the connect packet
goto exit; // there was a problem goto exit; // there was a problem
// this will be a blocking call, wait for the connack // this will be a blocking call, wait for the connack
@@ -388,7 +420,7 @@ int MQTTConnect(MQTTClient* c, MQTTPacket_connectData* options)
rc = FAILURE; rc = FAILURE;
exit: exit:
if (rc == SUCCESSS) if (rc == SUCCESS)
c->isconnected = 1; c->isconnected = 1;
#if defined(MQTT_TASK) #if defined(MQTT_TASK)
@@ -422,7 +454,7 @@ int MQTTSubscribe(MQTTClient* c, const char* topicFilter, enum QoS qos, messageH
len = MQTTSerialize_subscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic, &charQos); len = MQTTSerialize_subscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic, &charQos);
if (len <= 0) if (len <= 0)
goto exit; goto exit;
if ((rc = sendPacket(c, len, &timer)) != SUCCESSS) // send the subscribe packet if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet
goto exit; // there was a problem goto exit; // there was a problem
if (waitfor(c, SUBACK, &timer) == SUBACK) // wait for suback if (waitfor(c, SUBACK, &timer) == SUBACK) // wait for suback
@@ -436,9 +468,10 @@ int MQTTSubscribe(MQTTClient* c, const char* topicFilter, enum QoS qos, messageH
int i; int i;
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
{ {
if (c->messageHandlers[i].topicFilter == 0) if (c->messageHandlers[i].topicFilter[0] == '\0')
{ {
c->messageHandlers[i].topicFilter = topicFilter; strncpy(c->messageHandlers[i].topicFilter, topicFilter, MAX_TOPIC_LENGTH - 1);
c->messageHandlers[i].topicFilter[MAX_TOPIC_LENGTH - 1] = '\0';
c->messageHandlers[i].fp = messageHandler; c->messageHandlers[i].fp = messageHandler;
rc = 0; rc = 0;
break; break;
@@ -476,7 +509,7 @@ int MQTTUnsubscribe(MQTTClient* c, const char* topicFilter)
if ((len = MQTTSerialize_unsubscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic)) <= 0) if ((len = MQTTSerialize_unsubscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic)) <= 0)
goto exit; goto exit;
if ((rc = sendPacket(c, len, &timer)) != SUCCESSS) // send the subscribe packet if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet
goto exit; // there was a problem goto exit; // there was a problem
if (waitfor(c, UNSUBACK, &timer) == UNSUBACK) if (waitfor(c, UNSUBACK, &timer) == UNSUBACK)
@@ -520,7 +553,7 @@ int MQTTPublish(MQTTClient* c, const char* topicName, MQTTMessage* message)
topic, (unsigned char*)message->payload, message->payloadlen); topic, (unsigned char*)message->payload, message->payloadlen);
if (len <= 0) if (len <= 0)
goto exit; goto exit;
if ((rc = sendPacket(c, len, &timer)) != SUCCESSS) // send the subscribe packet if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet
goto exit; // there was a problem goto exit; // there was a problem
if (message->qos == QOS1) if (message->qos == QOS1)

View File

@@ -43,10 +43,19 @@
#define MAX_MESSAGE_HANDLERS 5 /* redefinable - how many subscriptions do you want? */ #define MAX_MESSAGE_HANDLERS 5 /* redefinable - how many subscriptions do you want? */
#endif #endif
#define MAX_TOPIC_LENGTH 64
enum QoS { QOS0, QOS1, QOS2 }; enum QoS { QOS0, QOS1, QOS2 };
/* all failure return codes must be negative */ /* all failure return codes must be negative */
enum returnCode { BUFFER_OVERFLOW = -2, FAILURE = -1, SUCCESSS = 0 }; enum returnCode {
NO_DATA = -100, /* No data available (timeout) */
PROTOCOL_ERROR = -4, /* Invalid packet type or format */
DECODE_ERROR = -3, /* Failed to decode packet length */
BUFFER_OVERFLOW = -2, /* Buffer too small */
FAILURE = -1, /* Generic failure */
SUCCESS = 0
};
/* The Platform specific header must define the Network and Timer structures and functions /* The Platform specific header must define the Network and Timer structures and functions
* which operate on them. * which operate on them.
@@ -97,7 +106,7 @@ typedef struct MQTTClient
struct MessageHandlers struct MessageHandlers
{ {
const char* topicFilter; char topicFilter[MAX_TOPIC_LENGTH];
void (*fp) (MessageData*); void (*fp) (MessageData*);
} messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */ } messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */

View File

@@ -85,7 +85,11 @@ uint8_t WIZCHIP_READ(uint32_t AddrSel)
spi_data[2] = (AddrSel & 0x000000FF) >> 0; spi_data[2] = (AddrSel & 0x000000FF) >> 0;
WIZCHIP.IF.SPI._write_burst(spi_data, 3); WIZCHIP.IF.SPI._write_burst(spi_data, 3);
} }
ret = WIZCHIP.IF.SPI._read_byte(); if (WIZCHIP.IF.SPI._read_burst) {
WIZCHIP.IF.SPI._read_burst(&ret, 1);
} else {
ret = WIZCHIP.IF.SPI._read_byte();
}
WIZCHIP.CS._deselect(); WIZCHIP.CS._deselect();
WIZCHIP_CRITICAL_EXIT(); WIZCHIP_CRITICAL_EXIT();

View File

@@ -54,6 +54,7 @@
// //
//***************************************************************************** //*****************************************************************************
#include "socket.h" #include "socket.h"
#include <stdio.h>
//M20150401 : Typing Error //M20150401 : Typing Error
//#define SOCK_ANY_PORT_NUM 0xC000; //#define SOCK_ANY_PORT_NUM 0xC000;
@@ -507,6 +508,7 @@ int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t
return SOCKERR_SOCKMODE; return SOCKERR_SOCKMODE;
} }
CHECK_SOCKDATA(); CHECK_SOCKDATA();
//M20140501 : For avoiding fatal error on memory align mismatched //M20140501 : For avoiding fatal error on memory align mismatched
//if(*((uint32_t*)addr) == 0) return SOCKERR_IPINVALID; //if(*((uint32_t*)addr) == 0) return SOCKERR_IPINVALID;
//{ //{
@@ -518,24 +520,37 @@ int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t
//} //}
// //
//if(*((uint32_t*)addr) == 0) return SOCKERR_IPINVALID; //if(*((uint32_t*)addr) == 0) return SOCKERR_IPINVALID;
if((taddr == 0) && ((getSn_MR(sn)&Sn_MR_MACRAW) != Sn_MR_MACRAW)) return SOCKERR_IPINVALID; if((taddr == 0) && ((getSn_MR(sn)&Sn_MR_MACRAW) != Sn_MR_MACRAW)) {
if((port == 0) && ((getSn_MR(sn)&Sn_MR_MACRAW) != Sn_MR_MACRAW)) return SOCKERR_PORTZERO; return SOCKERR_IPINVALID;
}
if((port == 0) && ((getSn_MR(sn)&Sn_MR_MACRAW) != Sn_MR_MACRAW)) {
return SOCKERR_PORTZERO;
}
tmp = getSn_SR(sn); tmp = getSn_SR(sn);
//#if ( _WIZCHIP_ < 5200 ) //#if ( _WIZCHIP_ < 5200 )
if((tmp != SOCK_MACRAW) && (tmp != SOCK_UDP) && (tmp != SOCK_IPRAW)) return SOCKERR_SOCKSTATUS; if((tmp != SOCK_MACRAW) && (tmp != SOCK_UDP) && (tmp != SOCK_IPRAW)) {
return SOCKERR_SOCKSTATUS;
}
//#else //#else
// if(tmp != SOCK_MACRAW && tmp != SOCK_UDP) return SOCKERR_SOCKSTATUS; // if(tmp != SOCK_MACRAW && tmp != SOCK_UDP) return SOCKERR_SOCKSTATUS;
//#endif //#endif
setSn_DIPR(sn,addr); setSn_DIPR(sn,addr);
setSn_DPORT(sn,port); setSn_DPORT(sn,port);
freesize = getSn_TxMAX(sn); freesize = getSn_TxMAX(sn);
if (len > freesize) len = freesize; // check size not to exceed MAX size. if (len > freesize) {
len = freesize; // check size not to exceed MAX size.
}
while(1) while(1)
{ {
freesize = getSn_TX_FSR(sn); freesize = getSn_TX_FSR(sn);
if(getSn_SR(sn) == SOCK_CLOSED) return SOCKERR_SOCKCLOSED; if(getSn_SR(sn) == SOCK_CLOSED) {
if( (sock_io_mode & (1<<sn)) && (len > freesize) ) return SOCK_BUSY; return SOCKERR_SOCKCLOSED;
}
if( (sock_io_mode & (1<<sn)) && (len > freesize) ) {
return SOCK_BUSY;
}
if(len <= freesize) break; if(len <= freesize) break;
}; };
wiz_send_data(sn, buf, len); wiz_send_data(sn, buf, len);
@@ -558,6 +573,7 @@ int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t
setSn_CR(sn,Sn_CR_SEND); setSn_CR(sn,Sn_CR_SEND);
/* wait to process the command... */ /* wait to process the command... */
while(getSn_CR(sn)); while(getSn_CR(sn));
while(1) while(1)
{ {
tmp = getSn_IR(sn); tmp = getSn_IR(sn);
@@ -586,6 +602,7 @@ int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t
#endif #endif
//M20150409 : Explicit Type Casting //M20150409 : Explicit Type Casting
//return len; //return len;
return (int32_t)len; return (int32_t)len;
} }

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

29
lmao.sh
View File

@@ -1,29 +0,0 @@
#!/bin/bash
SERIAL_PORT="/dev/ttyACM0"
BAUD_RATE="115200"
{
stty -F $SERIAL_PORT $BAUD_RATE
COUNT=0
PREVIOUS_TIME=""
while IFS= read -r LINE; do
CURRENT_TIME=$(date +%s.%N)
# 5 msgs
if [ $COUNT -lt 5 ]; then
echo "msg $COUNT: $LINE"
if [[ -n $PREVIOUS_TIME ]]; then
INTERVAL=$(echo "$CURRENT_TIME - $PREVIOUS_TIME" | bc)
echo "interval: ${INTERVAL}s"
fi
PREVIOUS_TIME=$CURRENT_TIME
COUNT=$((COUNT + 1))
else
break
fi
done
} <$SERIAL_PORT

277
modbus.c Normal file
View File

@@ -0,0 +1,277 @@
#include "modbus.h"
#include <string.h>
#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);
}
}

386
mqtt_handler.c Normal file
View File

@@ -0,0 +1,386 @@
#include "mqtt_handler.h"
#include <MQTT/MQTTClient.h>
#include <socket.h>
#include <string.h>
#include "debug.h"
#include "gpio.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_RECONNECT_INTERVAL 5000 // 5 seconds between reconnection attempts
// Homie convention constants
#define HOMIE_VERSION "4.0"
#define HOMIE_STATE_READY "ready"
#define HOMIE_STATE_LOST "lost"
#define HOMIE_STATE_SLEEPING "sleeping"
#define HOMIE_STATE_DISCONNECTED "disconnected"
// nodes list buffer
char nodes_list[MAX_PAYLOAD_LENGTH];
// Parse Homie topic format: homie/node-id/device-name/property/[set|get]
static bool parse_homie_topic(const char* topic, size_t topic_len,
char* device_name, size_t name_max,
char* property, size_t prop_max,
uint8_t* is_set) {
const char* segment_start = topic;
const char* topic_end = topic + topic_len;
uint8_t segment = 0;
// Skip first three segments (homie/node-id/device-name/)
while (segment < 3 && segment_start < topic_end) {
const char* slash = memchr(segment_start, '/', topic_end - segment_start);
if (!slash) return false;
if (segment == 2) {
size_t len = slash - segment_start;
if (len >= name_max) return false;
memcpy(device_name, segment_start, len);
device_name[len] = '\0';
}
segment_start = slash + 1;
segment++;
}
const char* slash = memchr(segment_start, '/', topic_end - segment_start);
if (!slash) return false;
size_t len = slash - segment_start;
if (len >= prop_max) return false;
memcpy(property, segment_start, len);
property[len] = '\0';
segment_start = slash + 1;
if (segment_start >= topic_end) return false; // Missing set/get
*is_set = (*segment_start == 's');
return true;
}
// MQTT client
int setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts,
MQTTClient* client) {
static unsigned char tx_buffer[MQTT_TX_BUFFER_SIZE];
static unsigned char rx_buffer[MQTT_RX_BUFFER_SIZE];
int rc;
uint8_t target_ip[] = MQTT_SERVER_IP;
// Initialize network
NewNetwork(network, TCP_SOCKET);
rc = ConnectNetwork(network, target_ip, MQTT_PORT);
if (rc != SOCK_OK) {
DEBUG_PRINT("Network connection failed: %d\n", rc);
return -1;
}
// Initialize the MQTT client
MQTTClientInit(client, network, MQTT_COMMAND_TIMEOUT_MS, tx_buffer,
sizeof(tx_buffer), rx_buffer, sizeof(rx_buffer));
// Setup MQTT connection data
MQTTPacket_connectData connect_data = MQTTPacket_connectData_initializer;
// Configure Last Will and Testament
char will_topic[MAX_TOPIC_LENGTH];
snprintf(will_topic, sizeof(will_topic), "homie/%s/$state", NODE_CONFIG.id);
connect_data.willFlag = 1; // Enable LWT
connect_data.will =
(MQTTPacket_willOptions)MQTTPacket_willOptions_initializer;
connect_data.will.topicName.cstring = will_topic;
connect_data.will.message.cstring = HOMIE_STATE_LOST;
connect_data.will.retained = 1;
connect_data.will.qos = QOS1;
// rest
connect_data.MQTTVersion = 3;
connect_data.clientID.cstring = opts->clientid;
connect_data.username.cstring = opts->username;
connect_data.password.cstring = opts->password;
connect_data.keepAliveInterval = MQTT_KEEP_ALIVE_INTERVAL;
connect_data.cleansession = 1;
// Connect to MQTT broker
rc = MQTTConnect(client, &connect_data);
if (rc != 0) {
DEBUG_PRINT("Failed to connect: %d\n", rc);
return -2;
}
return 0;
}
int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos,
messageHandler message_callback) {
int rc = MQTTSubscribe(client, topic, qos, message_callback);
if (rc != 0) {
DEBUG_PRINT("Failed to subscribe to %s: %d\n", topic, rc);
return rc;
}
return 0;
}
void publish_message(MQTTClient* client, const char* payload,
const char* topic) {
MQTTMessage message = {.qos = QOS0,
.retained = 0,
.dup = 0,
.payload = (void*)payload,
.payloadlen = strlen(payload)
};
if (MQTTPublish(client, topic, &message) != 0) {
DEBUG_PRINT("Publish failed\n");
}
}
// publish retained messages
void publish_retained(MQTTClient* client, const char* topic,
const char* payload) {
MQTTMessage message = {.qos = QOS1,
.retained = 1,
.dup = 0,
.payload = (void*)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);
// Device attributes
snprintf(topic, sizeof(topic), "homie/%s/$homie", NODE_CONFIG.id);
publish_retained(client, topic, HOMIE_VERSION);
snprintf(topic, sizeof(topic), "homie/%s/$name", NODE_CONFIG.id);
publish_retained(client, topic, NODE_CONFIG.name);
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
for (int i = 0; i < RS485_DEVICE_COUNT; i++) {
if (i > 0 && remaining > 1) {
*ptr++ = ',';
remaining--;
}
size_t len = strlen(RS485_DEVICES[i].name);
if (len >= remaining) break;
memcpy(ptr, RS485_DEVICES[i].name, len);
ptr += len;
remaining -= len;
}
*ptr = '\0';
snprintf(topic, sizeof(topic), "homie/%s/$nodes", NODE_CONFIG.id);
publish_retained(client, topic, nodes_list);
// loc attribute
if (NODE_CONFIG.location[0] != '\0') {
snprintf(topic, sizeof(topic), "homie/%s/$location", NODE_CONFIG.id);
publish_retained(client, topic, NODE_CONFIG.location);
}
}
static void publish_rs485_node_attributes(MQTTClient* client,
const rs485_device_t* device) {
char topic[MAX_TOPIC_LENGTH];
// Node base attributes
snprintf(topic, sizeof(topic), "homie/%s/%s/$name", NODE_CONFIG.id,
device->name);
publish_retained(client, topic, device->name);
// Set properties based on device type
switch (device->type) {
case DEVICE_RELAY:
snprintf(topic, sizeof(topic), "homie/%s/%s/$properties", NODE_CONFIG.id,
device->name);
publish_retained(client, topic, "state");
snprintf(topic, sizeof(topic), "homie/%s/%s/state/$name", NODE_CONFIG.id,
device->name);
publish_retained(client, topic, "State");
snprintf(topic, sizeof(topic), "homie/%s/%s/state/$datatype",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "boolean");
snprintf(topic, sizeof(topic), "homie/%s/%s/state/$settable",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "true");
break;
case DEVICE_SOIL_SENSOR:
snprintf(topic, sizeof(topic), "homie/%s/%s/$properties", NODE_CONFIG.id,
device->name);
publish_retained(client, topic, "moisture,temperature");
snprintf(topic, sizeof(topic), "homie/%s/%s/moisture/$name",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "Moisture Level");
snprintf(topic, sizeof(topic), "homie/%s/%s/moisture/$datatype",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "integer");
snprintf(topic, sizeof(topic), "homie/%s/%s/moisture/$unit",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "%");
// Temperature property
snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$name",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "Temperature");
snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$datatype",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "float");
snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$unit",
NODE_CONFIG.id, device->name);
publish_retained(client, topic, "°C");
break;
case DEVICE_THERMOMETER:
// TODO
DEBUG_PRINT("not implemented\n");
}
}
// Initialize MQTT with Homie discovery
void mqtt_init(mqtt_state_t* state) {
state->opts.clientid = (char*)NODE_CONFIG.id; // Use node ID as client ID
state->opts.username = "";
state->opts.password = "";
state->opts.qos = QOS1;
state->last_reconnect = 0;
state->last_yield = 0;
state->is_connected = false;
}
// Find device by name and return its index
static int8_t find_device_index(const char* name) {
for (uint8_t i = 0; i < RS485_DEVICE_COUNT; i++) {
if (strcmp(RS485_DEVICES[i].name, name) == 0) {
return i;
}
}
return -1;
}
void message_arrived(MessageData* md) {
if (!md || !md->message || !md->topicName) {
DEBUG_PRINT("Error: MessageData is NULL.\n");
return;
}
char device_name[16];
char property[16];
uint8_t is_set;
if (!parse_homie_topic(md->topicName->lenstring.data,
md->topicName->lenstring.len, device_name,
sizeof(device_name), property, sizeof(property),
&is_set)) {
DEBUG_PRINT("Failed to parse topic\n");
return;
}
// For set operations, parse value
if (is_set && md->message->payloadlen > 0) {
uint16_t value = 0;
int8_t idx = find_device_index(device_name);
if (idx < 0) return;
if (RS485_DEVICES[idx].type == DEVICE_RELAY) {
if (md->message->payloadlen != 16) return;
char* str = (char*)md->message->payload;
for (int i = 0; i < 16; i++) {
value = (value << 1) | (str[i] - '0');
}
} else {
// todo:
DEBUG_PRINT("not implemented\n");
}
modbus_handler_send_request(idx, property, is_set, value);
}
}
void mqtt_process(mqtt_state_t* state) {
uint32_t now = millis();
int rc;
if (!state->is_connected) {
if (now - state->last_reconnect >= MQTT_RECONNECT_INTERVAL) {
rc = setup_mqtt_client(&state->network, &state->opts, &state->client);
if (rc == SUCCESS) {
state->is_connected = true;
if (!state->discovery_published) {
publish_device_attributes(&state->client);
for (int i = 0; i < RS485_DEVICE_COUNT; i++) {
publish_rs485_node_attributes(&state->client, &RS485_DEVICES[i]);
}
// with onewire we can discover new devices on the bus during runtime
// is it worth implementing?
onewire_temp_publish_discovery(&state->client, NODE_CONFIG.id);
state->discovery_published = 1;
}
char sub_topic[MAX_TOPIC_LENGTH];
snprintf(sub_topic, sizeof(sub_topic), "homie/%s/+/+/set",
NODE_CONFIG.id);
rc = subscribe_to_topic(&state->client, sub_topic, QOS1,
message_arrived);
if (rc != SUCCESS) {
state->is_connected = false;
}
}
state->last_reconnect = now;
}
led_status_set(LED_STATE_BUSY);
} else if (now - state->last_yield >= MQTT_YIELD_INTERVAL) {
rc = MQTTYield(&state->client, MQTT_MAX_PACKET_WAIT);
if (rc != SUCCESS) {
DEBUG_PRINT("MQTT Yield failed with rc=%d, ping_outstanding=%d\n", rc,
state->client.ping_outstanding);
state->is_connected = false;
state->discovery_published = false;
}
state->last_yield = now;
led_status_set(LED_STATE_ON);
}
}

130
network.c Normal file
View File

@@ -0,0 +1,130 @@
#include "network.h"
#include <DHCP/dhcp.h>
#include <W5500/w5500.h>
#include <string.h>
#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(&current_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; }

BIN
notes/SPI_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
notes/SPI_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
notes/SPI_DMA_no_irq_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
notes/SPI_DMA_no_irq_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

624
onewire.c Normal file
View File

@@ -0,0 +1,624 @@
/*
Single-File One Wire Communication Functions for CH32V003
Relies on the CH32V003fun library from:
https://github.com/cnlohr/ch32v003fun
This is very heavily derived from the Arduino OneWire library,
at https://github.com/PaulStoffregen/OneWire
Original copyright notices follow:
--------------------------------------------
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
The latest version of this library may be found at:
http://www.pjrc.com/teensy/td_libs_OneWire.html
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
January 2010.
DO NOT EMAIL for technical support, especially not for ESP chips!
All project support questions must be posted on public forums
relevant to the board or chips used. If using Arduino, post on
Arduino's forum. If using ESP, post on the ESP community forums.
There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
Github's issue tracker for OneWire should be used only to report
specific bugs. DO NOT request project support via Github. All
project and tech support questions must be posted on forums, not
github issues. If you experience a problem and you are not
absolutely sure it's an issue with the library, ask on a forum
first. Only use github to report issues after experts have
confirmed the issue is with OneWire rather than your project.
Back in 2010, OneWire was in need of many bug fixes, but had
been abandoned the original author (Jim Studt). None of the known
contributors were interested in maintaining OneWire. Paul typically
works on OneWire every 6 to 12 months. Patches usually wait that
long. If anyone is interested in more actively maintaining OneWire,
please contact Paul (this is pretty much the only reason to use
private email about OneWire).
OneWire is now very mature code. No changes other than adding
definitions for newer hardware support are anticipated.
ESP32 mods authored by stickbreaker:
@stickbreaker 30APR2018 add IRAM_ATTR to read_bit() OneWireWriteBit() to solve
ICache miss timing failure. thanks @everslick re:
https://github.com/espressif/arduino-esp32/issues/1335 Altered by garyd9 for
clean merge with Paul Stoffregen's source
Version 2.3:
Unknown chip fallback mode, Roger Clark
Teensy-LC compatibility, Paul Stoffregen
Search bug fix, Love Nystrom
Version 2.2:
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
Fix DS18B20 example negative temperature
Fix DS18B20 example's low res modes, Ken Butcher
Improve reset timing, Mark Tillotson
Add const qualifiers, Bertrik Sikken
Add initial value input to crc16, Bertrik Sikken
Add target_search() function, Scott Roberts
Version 2.1:
Arduino 1.0 compatibility, Paul Stoffregen
Improve temperature example, Paul Stoffregen
DS250x_PROM example, Guillermo Lovato
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
Improvements from Glenn Trewitt:
- crc16() now works
- check_crc16() does all of calculation/checking work.
- Added read_bytes() and write_bytes(), to reduce tedious loops.
- Added ds2408 example.
Delete very old, out-of-date readme file (info is here)
Version 2.0: Modifications by Paul Stoffregen, January 2010:
http://www.pjrc.com/teensy/td_libs_OneWire.html
Search fix from Robin James
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
Use direct optimized I/O in all cases
Disable interrupts during timing critical sections
(this solves many random communication errors)
Disable interrupts during read-modify-write I/O
Reduce RAM consumption by eliminating unnecessary
variables and trimming many to 8 bits
Optimize both crc8 - table version moved to flash
Modified to work with larger numbers of devices - avoids loop.
Tested in Arduino 11 alpha with 12 sensors.
26 Sept 2008 -- Robin James
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
Updated to work with arduino-0008 and to include skip() as of
2007/07/06. --RJL20
Modified to calculate the 8-bit CRC directly, avoiding the need for
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
-- Tom Pollard, Jan 23, 2008
Jim Studt's original library was modified by Josh Larios.
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Much of the code was inspired by Derek Yerger's code, though I don't
think much of that remains. In any event that was..
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
The CRC code was excerpted and inspired by the Dallas Semiconductor
sample code bearing this copyright.
//---------------------------------------------------------------------------
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//--------------------------------------------------------------------------
*/
#include "onewire.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "ch32v003fun.h"
// global search state
unsigned char ROM_NO[8];
uint8_t LastDiscrepancy;
uint8_t LastFamilyDiscrepancy;
bool LastDeviceFlag;
void OneWireBegin(void);
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
// with a presence pulse. Returns 0 if there is no device or the
// bus is shorted or otherwise held low for more than 250uS
uint8_t OneWireReset(void);
// Issue a 1-Wire rom select command, you do the reset first.
void OneWireSelect(const uint8_t rom[8]);
// Issue a 1-Wire rom skip command, to address all on bus.
// void skip(void);
// Write a byte. If 'power' is one then the wire is held high at
// the end for parasitically powered devices. You are responsible
// for eventually depowering it by calling depower() or doing
// another read or write.
void OneWireWrite(uint8_t v, uint8_t power);
void OneWireWriteBytes(const uint8_t *buf, uint16_t count, bool power);
// Read a byte.
uint8_t OneWireRead(void);
void OneWireReadBytes(uint8_t *buf, uint16_t count);
// Write a bit. The bus is always left powered at the end, see
// note in write() about that.
void OneWireWriteBit(uint8_t v);
// Read a bit.
uint8_t OneWireReadBit(void);
// Stop forcing power onto the bus. You only need to do this if
// you used the 'power' flag to write() or used a write_bit() call
// and aren't about to do another read or write. You would rather
// not leave this powered if you don't have to, just in case
// someone shorts your bus.
void OneWireDepower(void);
// Clear the search state so that if will start from the beginning again.
void OneWireResetSearch();
// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
void OneWireTargetSearch(uint8_t family_code);
// Look for the next device. Returns 1 if a new address has been
// returned. A zero might mean that the bus is shorted, there are
// no devices, or you have already retrieved all of them. It
// might be a good idea to check the CRC to make sure you didn't
// get garbage. The order is deterministic. You will always get
// the same devices in the same order.
bool OneWireSearch(uint8_t *newAddr, bool search_mode);
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
// ROM and scratchpad registers.
uint8_t OneWireCrc8(const uint8_t *addr, uint8_t len);
// Compute the 1-Wire CRC16 and compare it against the received CRC.
// Example usage (reading a DS2408):
// // Put everything in a buffer so we can compute the CRC easily.
// uint8_t buf[13];
// buf[0] = 0xF0; // Read PIO Registers
// buf[1] = 0x88; // LSB address
// buf[2] = 0x00; // MSB address
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
// if (!CheckCRC16(buf, 11, &buf[11])) {
// // Handle error.
// }
//
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param inverted_crc - The two CRC16 bytes in the received data.
// This should just point into the received data,
// *not* at a 16-bit integer.
// @param crc - The crc starting value (optional)
// @return True, iff the CRC matches.
bool OneWireCheckCrc16(const uint8_t *input, uint16_t len,
const uint8_t *inverted_crc, uint16_t crc);
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
// the integrity of data received from many 1-Wire devices. Note that the
// CRC computed here is *not* what you'll get from the 1-Wire network,
// for two reasons:
// 1) The CRC is transmitted bitwise inverted.
// 2) Depending on the endian-ness of your processor, the binary
// representation of the two-byte return value may have a different
// byte order than the two bytes you get from 1-Wire.
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param crc - The crc starting value (optional)
// @return The CRC16, as defined by Dallas Semiconductor.
uint16_t OneWireCrc16(const uint8_t *input, uint16_t len, uint16_t crc);
void OneWireBegin() {
directModeInput();
OneWireResetSearch();
}
// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn't then it is broken or shorted
// and we return a 0;
//
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
//
uint8_t OneWireReset(void) {
uint8_t r;
uint8_t retries = 125;
DIRECT_MODE_INPUT();
// wait until the wire is high... just in case
do {
if (--retries == 0) return 0;
Delay_Us(ONEWIRE_RESET_RETRY_TIME);
} while (!DIRECT_READ());
DIRECT_WRITE_LOW();
DIRECT_MODE_OUTPUT(); // drive output low
Delay_Us(ONEWIRE_RESET_LOW_TIME);
DIRECT_MODE_INPUT(); // allow it to float
Delay_Us(ONEWIRE_RESET_SAMPLE_TIME);
r = !DIRECT_READ();
Delay_Us(ONEWIRE_RESET_POST_TIME);
return r;
}
//
// Write a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
void OneWireWriteBit(uint8_t v) {
if (v & 1) {
DIRECT_WRITE_LOW();
DIRECT_MODE_OUTPUT(); // drive output low
Delay_Us(ONEWIRE_WRITE_1_LOW_TIME);
DIRECT_WRITE_HIGH(); // drive output high
Delay_Us(ONEWIRE_WRITE_1_TOTAL_TIME - ONEWIRE_WRITE_1_LOW_TIME);
} else {
DIRECT_WRITE_LOW();
DIRECT_MODE_OUTPUT(); // drive output low
Delay_Us(ONEWIRE_WRITE_0_LOW_TIME);
DIRECT_WRITE_HIGH(); // drive output high
Delay_Us(ONEWIRE_WRITE_0_TOTAL_TIME - ONEWIRE_WRITE_0_LOW_TIME);
}
}
//
// Read a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
uint8_t OneWireReadBit(void) {
uint8_t r;
DIRECT_MODE_OUTPUT();
DIRECT_WRITE_LOW();
Delay_Us(ONEWIRE_READ_INIT_LOW_TIME); // 6us initial low pulse
DIRECT_MODE_INPUT(); // let pin float, pull up will raise
Delay_Us(ONEWIRE_READ_SAMPLE_TIME); // 8us until sample point
r = DIRECT_READ();
// Wait for remainder of the read timeslot
// Total - init_low - sample = 64 - 6 - 8 = 50us
Delay_Us(ONEWIRE_READ_TOTAL_TIME - ONEWIRE_READ_INIT_LOW_TIME -
ONEWIRE_READ_SAMPLE_TIME);
return r;
}
//
// Write a byte. The writing code uses the active drivers to raise the
// pin high, if you need power after the write (e.g. DS18S20 in
// parasite power mode) then set 'power' to 1, otherwise the pin will
// go tri-state at the end of the write to avoid heating in a short or
// other mishap.
//
void OneWireWrite(uint8_t v, uint8_t power /* = 0 */) {
uint8_t bitMask;
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
OneWireWriteBit((bitMask & v) ? 1 : 0);
}
if (!power) {
DIRECT_MODE_INPUT();
DIRECT_WRITE_LOW();
}
}
void OneWireWriteBytes(const uint8_t *buf, uint16_t count, bool power) {
for (uint16_t i = 0; i < count; i++) OneWireWrite(buf[i], 0);
if (!power) {
DIRECT_MODE_INPUT();
DIRECT_WRITE_LOW();
}
}
//
// Read a byte
//
uint8_t OneWireRead() {
uint8_t bitMask;
uint8_t r = 0;
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
if (OneWireReadBit()) r |= bitMask;
}
return r;
}
void OneWireReadBytes(uint8_t *buf, uint16_t count) {
for (uint16_t i = 0; i < count; i++) buf[i] = OneWireRead();
}
//
// Do a ROM select
//
void OneWireSelect(const uint8_t rom[8]) {
uint8_t i;
OneWireWrite(0x55, 0); // Choose ROM
for (i = 0; i < 8; i++) OneWireWrite(rom[i], 0);
}
//
// Do a ROM skip
//
void OneWireSkip() {
OneWireWrite(0xCC, 0); // Skip ROM
}
void OneWireDepower() { DIRECT_MODE_INPUT(); }
//
// You need to use this function to start a search again from the beginning.
// You do not need to do it for the first search, though you could.
//
void OneWireResetSearch() {
// reset the search state
LastDiscrepancy = 0;
LastDeviceFlag = false;
LastFamilyDiscrepancy = 0;
for (int i = 7;; i--) {
ROM_NO[i] = 0;
if (i == 0) break;
}
}
// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
//
void OneWireTargetSearch(uint8_t family_code) {
// set the search state to find SearchFamily type devices
ROM_NO[0] = family_code;
for (uint8_t i = 1; i < 8; i++) ROM_NO[i] = 0;
LastDiscrepancy = 64;
LastFamilyDiscrepancy = 0;
LastDeviceFlag = false;
}
//
// Perform a search. If this function returns a '1' then it has
// enumerated the next device and you may retrieve the ROM from the
// OneWireAddress variable. If there are no devices, no further
// devices, or something horrible happens in the middle of the
// enumeration then a 0 is returned. If a new device is found then
// its address is copied to newAddr. Use OneWireReset_search() to
// start over.
//
// --- Replaced by the one from the Dallas Semiconductor web site ---
//--------------------------------------------------------------------------
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
// search state.
// Return TRUE : device found, ROM number in ROM_NO buffer
// FALSE : device not found, end of search
//
bool OneWireSearch(uint8_t *newAddr, bool search_mode /* = true */) {
uint8_t id_bit_number;
uint8_t last_zero, rom_byte_number;
bool search_result;
uint8_t id_bit, cmp_id_bit;
unsigned char rom_byte_mask, search_direction;
// initialize for search
id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = false;
// if the last call was not the last one
if (!LastDeviceFlag) {
// 1-Wire reset
if (!OneWireReset()) {
// reset the search
LastDiscrepancy = 0;
LastDeviceFlag = false;
LastFamilyDiscrepancy = 0;
return false;
}
// issue the search command
if (search_mode == true) {
OneWireWrite(0xF0, 0); // NORMAL SEARCH
} else {
OneWireWrite(0xEC, 0); // CONDITIONAL SEARCH
}
// loop to do the search
do {
// read a bit and its complement
id_bit = OneWireReadBit();
cmp_id_bit = OneWireReadBit();
// check for no devices on 1-wire
if ((id_bit == 1) && (cmp_id_bit == 1)) {
break;
} else {
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit) {
search_direction = id_bit; // bit write value for search
} else {
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < LastDiscrepancy) {
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
} else {
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == LastDiscrepancy);
}
// if 0 was picked then record its position in LastZero
if (search_direction == 0) {
last_zero = id_bit_number;
// check for Last discrepancy in family
if (last_zero < 9) LastFamilyDiscrepancy = last_zero;
}
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction == 1)
ROM_NO[rom_byte_number] |= rom_byte_mask;
else
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
// serial number search direction write bit
OneWireWriteBit(search_direction);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and
// reset mask
if (rom_byte_mask == 0) {
rom_byte_number++;
rom_byte_mask = 1;
}
}
} while (rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!(id_bit_number < 65)) {
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
LastDiscrepancy = last_zero;
// check for last device
if (LastDiscrepancy == 0) {
LastDeviceFlag = true;
}
search_result = true;
}
}
// if no device found then reset counters so next 'search' will be like a
// first
if (!search_result || !ROM_NO[0]) {
LastDiscrepancy = 0;
LastDeviceFlag = false;
LastFamilyDiscrepancy = 0;
search_result = false;
} else {
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
}
return search_result;
}
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton
// Products"
//
//
// Compute a Dallas Semiconductor 8 bit CRC directly.
// this is much slower, but a little smaller, than the lookup table.
//
uint8_t OneWireCrc8(const uint8_t *addr, uint8_t len) {
uint8_t crc = 0;
while (len--) {
uint8_t inbyte = *addr++;
for (uint8_t i = 8; i; i--) {
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix) crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
bool OneWireCheckCrc16(const uint8_t *input, uint16_t len,
const uint8_t *inverted_crc, uint16_t crc) {
crc = ~OneWireCrc16(input, len, crc);
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
}
uint16_t OneWireCrc16(const uint8_t *input, uint16_t len, uint16_t crc) {
static const uint8_t oddparity[16] = {0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0};
for (uint16_t i = 0; i < len; i++) {
// Even though we're just copying a byte from the input,
// we'll be doing 16-bit computation with it.
uint16_t cdata = input[i];
cdata = (cdata ^ crc) & 0xff;
crc >>= 8;
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) crc ^= 0xC001;
cdata <<= 6;
crc ^= cdata;
cdata <<= 1;
crc ^= cdata;
}
return crc;
}

385
onewire_temp.c Normal file
View File

@@ -0,0 +1,385 @@
#include "onewire_temp.h"
#include <string.h>
#include "debug.h"
#include "onewire.h"
#include "systick.h"
#define ONEWIRE_CONVERSION_TIME_MS 750
#define ONEWIRE_MAX_RETRIES 255
typedef struct {
uint8_t address[8];
float temperature;
onewire_state_t state;
uint32_t convert_start_time;
bool valid;
uint8_t error_count;
uint32_t last_success_time;
} onewire_sensor_t;
typedef struct {
onewire_sensor_t sensors[ONEWIRE_MAX_SENSORS];
uint8_t count;
bool parallel_mode;
} onewire_system_t;
static onewire_system_t ow_sys = {0};
void onewire_temp_init(void) {
uint8_t addr[8];
OneWireBegin();
OneWireResetSearch();
while (ow_sys.count < ONEWIRE_MAX_SENSORS) {
if (!OneWireSearch(addr, true)) {
break;
}
// Validate sensor
if (OneWireCrc8(addr, 7) != addr[7]) {
continue;
}
// Check if it's a temperature sensor
switch (addr[0]) {
case 0x10: // DS18S20
case 0x28: // DS18B20
case 0x22: // DS1822
break;
default:
continue; // Not a supported temperature sensor
}
// Store valid sensor
onewire_sensor_t* sensor = &ow_sys.sensors[ow_sys.count];
memcpy(sensor->address, addr, 8);
sensor->valid = true;
sensor->state = ONEWIRE_STATE_READY;
sensor->temperature = ONEWIRE_TEMP_INVALID;
sensor->error_count = 0;
sensor->last_success_time = millis();
ow_sys.count++;
}
}
static float convert_temperature(const uint8_t* data, uint8_t family_code) {
int16_t raw = (data[1] << 8) | data[0];
// DS18S20
if (family_code == 0x10) {
raw = raw << 3;
if (data[7] == 0x10) {
raw = (raw & 0xFFF0) + 12 - data[6];
}
}
// DS18B20/DS1822
else {
switch (data[4] & 0x60) {
case 0x00:
raw &= ~7;
break; // 9-bit
case 0x20:
raw &= ~3;
break; // 10-bit
case 0x40:
raw &= ~1;
break; // 11-bit
}
}
return (float)(raw / 16.0);
}
static bool start_conversion(onewire_sensor_t* sensor) {
if (!sensor->valid || !sensor->state != ONEWIRE_STATE_READY) {
return false;
}
if (!OneWireReset()) {
return false;
}
OneWireSelect(sensor->address);
OneWireWrite(0x44, 0); // Start conversion w/o parasite power
sensor->state = ONEWIRE_STATE_CONVERTING;
sensor->convert_start_time = millis();
return true;
}
static bool read_temperature(onewire_sensor_t* sensor) {
uint8_t data[9];
if (!OneWireReset()) {
return false;
}
OneWireSelect(sensor->address);
OneWireWrite(0xBE, 0); // Read scratchpad
OneWireReadBytes(data, 9);
if (OneWireCrc8(data, 8) != data[8]) {
return false;
}
sensor->temperature = convert_temperature(data, sensor->address[0]);
sensor->error_count = 0;
sensor->last_success_time = millis();
return true;
}
void onewire_temp_process(void) {
uint32_t now = millis();
for (uint8_t i = 0; i < ow_sys.count; i++) {
onewire_sensor_t* sensor = &ow_sys.sensors[i];
if (!sensor->valid) {
continue;
}
switch (sensor->state) {
case ONEWIRE_STATE_READY:
if (!ow_sys.parallel_mode) {
if (!start_conversion(sensor)) {
sensor->error_count++;
if (sensor->error_count >= ONEWIRE_MAX_RETRIES) {
sensor->valid = false;
DEBUG_PRINT(
"sensor %d marked temporarily invalid after %d retries\n", i,
ONEWIRE_MAX_RETRIES);
}
}
}
break;
case ONEWIRE_STATE_CONVERTING:
if (now - sensor->convert_start_time >= ONEWIRE_CONVERSION_TIME_MS) {
sensor->state = ONEWIRE_STATE_READ;
}
break;
case ONEWIRE_STATE_READ:
if (!read_temperature(sensor)) {
sensor->error_count++;
if (sensor->error_count >= ONEWIRE_MAX_RETRIES) {
sensor->valid = false;
DEBUG_PRINT(
"sensor %d marked temporarily invalid after %d retries\n", i,
ONEWIRE_MAX_RETRIES);
}
} else {
sensor->valid = true; // re-enable
sensor->error_count = 0;
}
sensor->state = ONEWIRE_STATE_READY;
break;
}
}
}
void onewire_temp_start_parallel(void) {
if (!OneWireReset()) {
return;
}
OneWireSkip(); // Address all devices
OneWireWrite(0x44, 1); // Start conversion with parasite power
uint32_t now = millis();
for (uint8_t i = 0; i < ow_sys.count; i++) {
if (ow_sys.sensors[i].valid) {
ow_sys.sensors[i].state = ONEWIRE_STATE_CONVERTING;
ow_sys.sensors[i].convert_start_time = now;
}
}
}
float onewire_temp_get(uint8_t index) {
return (index < ow_sys.count && ow_sys.sensors[index].valid)
? ow_sys.sensors[index].temperature
: ONEWIRE_TEMP_INVALID;
}
uint8_t onewire_temp_count(void) { return ow_sys.count; }
const uint8_t* onewire_temp_address(uint8_t index) {
return (index < ow_sys.count) ? ow_sys.sensors[index].address : NULL;
}
bool onewire_temp_valid(uint8_t index) {
return (index < ow_sys.count) ? ow_sys.sensors[index].valid : false;
}
void onewire_temp_set_parallel(bool enable) { ow_sys.parallel_mode = enable; }
// MQTT
void onewire_temp_publish_discovery(MQTTClient* client, const char* node_id) {
char topic[MAX_TOPIC_LENGTH];
char sensor_name[32];
uint8_t sensor_count = onewire_temp_count();
// append to node list
size_t current_len = strlen(nodes_list);
char* ptr = nodes_list + current_len;
size_t remaining = sizeof(nodes_list) - current_len;
for (uint8_t i = 0; i < sensor_count && remaining > 1; i++) {
if (!onewire_temp_valid(i)) {
continue;
}
// , if not 1st
if (current_len > 0 && remaining > 1) {
*ptr++ = ',';
remaining--;
current_len++;
}
const uint8_t* addr = onewire_temp_address(i);
int written =
snprintf(sensor_name, sizeof(sensor_name),
"temp_%02x%02x%02x%02x%02x%02x%02x%02x", addr[0], addr[1],
addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]);
if (written < 0 || (size_t)written >= remaining) {
break;
}
memcpy(ptr, sensor_name, written);
ptr += written;
remaining -= written;
current_len += written;
}
*ptr = '\0';
// pub node list
snprintf(topic, sizeof(topic), "homie/%s/$nodes", node_id);
publish_retained(client, topic, nodes_list);
for (uint8_t i = 0; i < sensor_count; i++) {
if (!onewire_temp_valid(i)) {
continue;
}
const uint8_t* addr = onewire_temp_address(i);
snprintf(sensor_name, sizeof(sensor_name),
"temp_%02x%02x%02x%02x%02x%02x%02x%02x", addr[0], addr[1], addr[2],
addr[3], addr[4], addr[5], addr[6], addr[7]);
snprintf(topic, sizeof(topic), "homie/%s/%s/$name", node_id, sensor_name);
char display_name[48];
snprintf(display_name, sizeof(display_name),
"Temperature Sensor %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6],
addr[7]);
publish_retained(client, topic, display_name);
snprintf(topic, sizeof(topic), "homie/%s/%s/$properties", node_id,
sensor_name);
publish_retained(client, topic, "temperature,address,status,error_count");
// temperature properties
snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$name", node_id,
sensor_name);
publish_retained(client, topic, "Temperature");
snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$datatype", node_id,
sensor_name);
publish_retained(client, topic, "float");
snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$unit", node_id,
sensor_name);
publish_retained(client, topic, "°C");
// address properties
snprintf(topic, sizeof(topic), "homie/%s/%s/address/$name", node_id,
sensor_name);
publish_retained(client, topic, "ROM Address");
snprintf(topic, sizeof(topic), "homie/%s/%s/address/$datatype", node_id,
sensor_name);
publish_retained(client, topic, "string");
char addr_str[24];
snprintf(addr_str, sizeof(addr_str),
"%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1],
addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]);
snprintf(topic, sizeof(topic), "homie/%s/%s/address", node_id, sensor_name);
publish_retained(client, topic, addr_str);
snprintf(topic, sizeof(topic), "homie/%s/%s/status/$name", node_id,
sensor_name);
publish_retained(client, topic, "Sensor Status");
snprintf(topic, sizeof(topic), "homie/%s/%s/status/$datatype", node_id,
sensor_name);
publish_retained(client, topic, "enum");
snprintf(topic, sizeof(topic), "homie/%s/%s/status/$format", node_id,
sensor_name);
publish_retained(client, topic, "valid,invalid");
snprintf(topic, sizeof(topic), "homie/%s/%s/error_count/$name", node_id,
sensor_name);
publish_retained(client, topic, "Error Count");
snprintf(topic, sizeof(topic), "homie/%s/%s/error_count/$datatype", node_id,
sensor_name);
publish_retained(client, topic, "integer");
}
}
// MQTT
void onewire_temp_publish_values(MQTTClient* client, const char* node_id) {
char topic[MAX_TOPIC_LENGTH];
char value[8];
uint8_t sensor_count = onewire_temp_count();
for (uint8_t i = 0; i < sensor_count; i++) {
bool is_valid = onewire_temp_valid(i);
const uint8_t* addr = onewire_temp_address(i);
char base_topic[MAX_TOPIC_LENGTH];
snprintf(base_topic, sizeof(base_topic),
"homie/%s/temp_%02x%02x%02x%02x%02x%02x%02x%02x", node_id, addr[0],
addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]);
// publish status
snprintf(topic, sizeof(topic), "%s/status", base_topic);
publish_message(client, is_valid ? "valid" : "invalid", topic);
// publish error cnt
snprintf(topic, sizeof(topic), "%s/error_count", base_topic);
snprintf(value, sizeof(value), "%u", ow_sys.sensors[i].error_count);
publish_message(client, value, topic);
snprintf(topic, sizeof(topic), "%s/temperature", base_topic);
if (is_valid) {
float temp = onewire_temp_get(i);
if (temp == ONEWIRE_TEMP_INVALID) {
publish_message(client, "0.00", topic);
} else {
// temp to str
int16_t temp_fixed = (int16_t)(temp * 100);
uint8_t neg = temp_fixed < 0;
if (neg) temp_fixed = -temp_fixed;
uint8_t idx = 0;
if (neg) value[idx++] = '-';
uint16_t whole = temp_fixed / 100;
uint8_t decimal = temp_fixed % 100;
if (whole >= 10) value[idx++] = '0' + (whole / 10);
value[idx++] = '0' + (whole % 10);
value[idx++] = '.';
value[idx++] = '0' + (decimal / 10);
value[idx++] = '0' + (decimal % 10);
value[idx] = '\0';
publish_message(client, value, topic);
}
} else {
publish_message(client, "0.00", topic);
}
}
}

View File

@@ -1,26 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env]
platform = https://git.hye.su/mira/platform-ch32v.git
platform_packages =
toolchain-riscv @ https://git.hye.su/mira/toolchain-riscv-linux.git ; gcc 14.2.0
framework-ch32v003fun @ https://github.com/cnlohr/ch32v003fun.git ; upstream cnlohr repo
board = genericCH32V203C8T6
framework = ch32v003fun
upload_protocol = wlink # isp, minichlink, wch-link, wlink
build_unflags = -march=rv32imacxw
build_flags = -march=rv32imac_zicsr -Wall -Wextra
debug_build_flags = -O0 -ggdb3 -g3
[env:local]
platform_packages =
toolchain-riscv @ symlink:///home/mira/src/xpack-riscv-none-elf-gcc-14.2.0-2 ; gcc 14.2.0
framework-ch32v003fun @ https://github.com/cnlohr/ch32v003fun.git ; upstream cnlohr repo

131
spi_dma.c Normal file
View File

@@ -0,0 +1,131 @@
#include "spi_dma.h"
#include "ch32v003fun.h"
#include "debug.h"
#define RX_Channel DMA1_Channel2
#define TX_Channel DMA1_Channel3
static const uint8_t tx_dummy_byte = 0xFF;
static uint8_t rx_dummy;
void spi_select(void) {
GPIOA->BCR = (1 << 4); // Set PA4 (CS) low
}
void spi_unselect(void) {
GPIOA->BSHR = (1 << 4); // Set PA4 (CS) high
}
// SPI DMA
void spidma_read_buffer(uint8_t* buf, uint16_t len) {
// tx
TX_Channel->MADDR = (uint32_t)&tx_dummy_byte;
TX_Channel->CNTR = len;
TX_Channel->CFGR &= ~DMA_MemoryInc_Enable;
// rx
RX_Channel->MADDR = (uint32_t)buf;
RX_Channel->CNTR = len;
RX_Channel->CFGR |= DMA_MemoryInc_Enable;
// enable
RX_Channel->CFGR |= DMA_CFGR1_EN;
TX_Channel->CFGR |= DMA_CFGR1_EN;
while (!(DMA1->INTFR & DMA1_FLAG_TC2) && !(DMA1->INTFR & DMA1_FLAG_TC3));
/**
* In transmission mode, when the DMA has written all the data to be
* transmitted (flag TCIF is set in the DMA_ISR register), the BSY flag can be
* monitored to ensure that the SPI communication is complete. This is
* required to avoid corrupting the last transmission before disabling the SPI
* or entering the Stop mode. The software must first wait until TXE=1 and
* then until BSY=0.
*/
while (!(SPI1->STATR & SPI_STATR_TXE));
while (SPI1->STATR & SPI_I2S_FLAG_BSY);
// clear intfr
DMA1->INTFCR |= DMA1_FLAG_TC2 | DMA1_FLAG_TC3;
RX_Channel->CFGR &= ~DMA_CFGR1_EN;
TX_Channel->CFGR &= ~DMA_CFGR1_EN;
}
void spidma_write_buffer(uint8_t* buf, uint16_t len) {
// tx
TX_Channel->MADDR = (uint32_t)buf;
TX_Channel->CNTR = len;
TX_Channel->CFGR |= DMA_MemoryInc_Enable;
// rx
RX_Channel->MADDR = (uint32_t)&rx_dummy;
RX_Channel->CNTR = len;
RX_Channel->CFGR &= ~DMA_MemoryInc_Enable;
// enable
TX_Channel->CFGR |= DMA_CFGR1_EN;
RX_Channel->CFGR |= DMA_CFGR1_EN;
while (!(DMA1->INTFR & DMA1_FLAG_TC2) && !(DMA1->INTFR & DMA1_FLAG_TC3));
// wait for SPI to complete: TXE = 1, then BSY = 0
while (!(SPI1->STATR & SPI_STATR_TXE));
while (SPI1->STATR & SPI_I2S_FLAG_BSY);
// clear intfr
DMA1->INTFCR |= DMA1_FLAG_TC2 | DMA1_FLAG_TC3;
RX_Channel->CFGR &= ~DMA_CFGR1_EN;
TX_Channel->CFGR &= ~DMA_CFGR1_EN;
}
void spidma_init(void) {
// Enable clock for GPIOA and SPI1
RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1;
// Enable clock for DMA1
RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;
// SPI1 Pin Configuration
// CS on PA4, 10MHz Output, push-pull
GPIOA->CFGLR &= ~(0xf << (4 * 4));
GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 4);
// SCK on PA5, 10MHz Output, alt func, push-pull
GPIOA->CFGLR &= ~(0xf << (4 * 5));
GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5);
// MISO on PA6, 10MHz input, floating
GPIOA->CFGLR &= ~(0xf << (4 * 6));
GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * 6);
// MOSI on PA7, 10MHz Output, alt func, push-pull
GPIOA->CFGLR &= ~(0xf << (4 * 7));
GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 7);
// Reset and Configure SPI1
SPI1->CTLR1 = 0; // Clear CTLR1 initially
SPI1->CTLR1 = SPI_Mode_Master | // Master mode
SPI_Direction_2Lines_FullDuplex | // Full duplex
SPI_DataSize_8b | // 8-bit data frame format
SPI_CPHA_1Edge | // Clock polarity
SPI_CPOL_Low | // Clock phase
SPI_BaudRatePrescaler_64 | // Baud rate prescaler
SPI_NSS_Soft; // Software NSS management
// Enable TX and RX DMA
SPI1->CTLR2 = SPI_CTLR2_TXDMAEN | SPI_CTLR2_RXDMAEN;
SPI1->I2SCFGR &= SPI_Mode_Select; // Disable I2S mode
SPI1->CTLR1 |= SPI_CTLR1_SPE; // Enable SPI
// DMA setup
TX_Channel->PADDR = (uint32_t)&SPI1->DATAR; // TX Channel
TX_Channel->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
DMA_Mode_Normal | DMA_DIR_PeripheralDST;
RX_Channel->PADDR = (uint32_t)&SPI1->DATAR; // RX Channel
RX_Channel->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
DMA_Mode_Normal | DMA_DIR_PeripheralSRC;
}

View File

@@ -1,18 +0,0 @@
#include "gpio.h"
#include "ch32v003fun.h"
void init_gpio(void) {
// Enable clock for GPIOB
RCC->APB2PCENR |= RCC_APB2Periph_GPIOB;
// GPIOB: Pins 3 and 4 as Output, Push-Pull, 10MHz
GPIOB->CFGLR &= ~((0xF << (4 * 3)) | (0xF << (4 * 4)));
GPIOB->CFGLR |= ((GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 3)) |
((GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 4));
// XXX: SysTick debug
// GPIOB: Pin 9 as Output, Push-Pull, 10MHz
GPIOB->CFGHR &= ~(0xF << (4 * (9 - 8)));
GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * (9 - 8));
}

View File

@@ -1,103 +0,0 @@
#include <DHCP/dhcp.h>
#include "ch32v003fun.h"
#include "debug.h"
#include "gpio.h"
#include "socket.h"
#include "spi_dma.h"
#include "systick.h"
#include "timer.h"
#include "uart.h"
#include "w5500.h"
void init_system(void) {
SystemInit();
init_gpio();
init_systick();
tim2_init();
init_uart(UART_BRR_APB1);
init_spidma();
}
// cb fn for when a message is received
void message_arrived(MessageData* md) {
if (md == NULL || md->message == NULL) {
DEBUG_PRINT("Error: MessageData is NULL.\n");
return;
}
MQTTMessage* message = md->message;
DEBUG_PRINT("MQTT Message Arrived:\n");
DEBUG_PRINT("QoS: %d\n", message->qos);
DEBUG_PRINT("Retained: %d\n", message->retained);
DEBUG_PRINT("Duplicate: %d\n", message->dup);
DEBUG_PRINT("Message ID: %u\n", message->id);
DEBUG_PRINT("Payload Length: %u\n", (unsigned int)message->payloadlen);
if (message->payload != NULL && message->payloadlen > 0) {
DEBUG_PRINT("Payload: ");
for (unsigned int i = 0; i < (unsigned int)message->payloadlen; ++i) {
putchar(((unsigned char*)message->payload)[i]);
}
DEBUG_PRINT("\n");
} else {
DEBUG_PRINT("Payload: <No data>\n");
}
}
void print_binary(uint32_t value) {
for (int i = 31; i >= 0; i--) {
DEBUG_PRINT("%d", (value >> i) & 1);
if (i % 4 == 0) printf(" "); // Add space for readability
}
DEBUG_PRINT("\n");
}
int main(void) {
init_system();
Delay_Ms(55);
uint32_t intsyscr = __get_INTSYSCR();
DEBUG_PRINT("INTSYSCR Register Configuration:\n");
DEBUG_PRINT("Hexadecimal: 0x%08X\n", intsyscr);
DEBUG_PRINT("Binary: ");
print_binary(intsyscr);
// DEBUG_PRINT("init_system() done\n");
configure_network();
// TODO: enabling any kind of SysTick IRQ literally causes the socket to hang
// forever with 1ms interval the hang is on DHCP_ACK, on check_DHCP_leasedIP
// -> sendto() ARP req with 100ms it happens somewhere on DNS req
// systick irq enable here would complete the DHCP configuration and hang on
// DNS.. init_systick();
configure_dhcp();
resolve_domain_name("example.com");
Network network;
ch32_mqtt_options_t opts = {CLIENT_ID, "", "", QOS0};
// Set up MQTT client
MQTTClient client = setup_mqtt_client(&network, &opts);
subscribe_to_topic(&client, SUB_TOPIC, QOS0, message_arrived);
// Publish a message
publish_message(&client, "hi", PUB_TOPIC);
while (1) {
// if ((millis() - dhcp_last_invocation) >= DHCP_INTERVAL) {
// dhcp_last_invocation = millis();
// DHCP_run();
// }
MQTTYield(&client, 1000); // keepalive
}
MQTTDisconnect(&client);
close(TCP_SOCKET);
return 0;
}

View File

@@ -1,168 +0,0 @@
#include "spi_dma.h"
#include "ch32v003fun.h"
#include "debug.h"
volatile transfer_state_t tx_state = IDLE;
volatile transfer_state_t rx_state = IDLE;
static const uint8_t tx_dummy_byte = 0xFF;
static uint8_t rx_dummy; // Static RX dummy buffer
static inline void wait_for_transfer_complete(void) {
while (tx_state != TX_DONE || rx_state != RX_DONE);
}
// static inline void spi_wait_not_busy(void) {
// while ((SPI1->STATR & SPI_STATR_BSY) != 0);
// }
void set_transfer_states(void) {
tx_state = TRANSMITTING;
rx_state = RECEIVING;
}
void clear_transfer_states(void) {
tx_state = IDLE;
rx_state = IDLE;
}
void DMA1_HandleIrq(uint32_t channel_interrupt,
volatile transfer_state_t* state,
transfer_state_t active_state,
transfer_state_t done_state) {
if (DMA1->INTFR & channel_interrupt) { // check if DMA interrupt occurred
DMA1->INTFCR = channel_interrupt; // clear the interrupt flag
if (*state == active_state) {
*state = done_state;
}
}
}
void DMA1_Channel3_IRQHandler(void) __attribute__((interrupt));
void DMA1_Channel2_IRQHandler(void) __attribute__((interrupt));
void DMA1_Channel3_IRQHandler(void) {
DMA1_HandleIrq(DMA1_IT_TC3, &tx_state, TRANSMITTING, TX_DONE);
}
void DMA1_Channel2_IRQHandler(void) {
DMA1_HandleIrq(DMA1_IT_TC2, &rx_state, RECEIVING, RX_DONE);
}
void configure_dma(DMA_Channel_TypeDef* tx_channel, uint32_t tx_addr,
DMA_Channel_TypeDef* rx_channel, uint32_t rx_addr,
int rx_circular, uint16_t len) {
// disable DMA channels
tx_channel->CFGR &= ~DMA_CFGR1_EN;
rx_channel->CFGR &= ~DMA_CFGR1_EN;
// set memory addresses and transfer count
tx_channel->MADDR = tx_addr;
tx_channel->CNTR = len;
rx_channel->MADDR = rx_addr;
rx_channel->CNTR = len;
// set or clear the circular mode for RX
rx_channel->CFGR =
(rx_channel->CFGR & ~DMA_CFGR1_CIRC) | (rx_circular ? DMA_CFGR1_CIRC : 0);
// enable DMA channels
tx_channel->CFGR |= DMA_CFGR1_EN;
rx_channel->CFGR |= DMA_CFGR1_EN;
}
void spidma_read_buffer(uint8_t* buf, uint16_t len) {
set_transfer_states();
configure_dma(DMA1_Channel3, (uint32_t)&tx_dummy_byte, DMA1_Channel2,
(uint32_t)buf, 0, len);
wait_for_transfer_complete();
clear_transfer_states();
}
void spidma_write_buffer(uint8_t* buf, uint16_t len) {
set_transfer_states();
configure_dma(DMA1_Channel3, (uint32_t)buf, DMA1_Channel2, (uint32_t)rx_dummy,
1, len);
wait_for_transfer_complete();
clear_transfer_states();
}
uint8_t spi_transfer(uint8_t data) {
while (!(SPI1->STATR & SPI_STATR_TXE));
SPI1->DATAR = data;
while (!(SPI1->STATR & SPI_STATR_RXNE));
return SPI1->DATAR;
}
uint8_t spi_read_byte() { return spi_transfer(tx_dummy_byte); }
void spi_write_byte(uint8_t byte) { spi_transfer(byte); }
void spi_select(void) {
GPIOA->BCR = (1 << 4); // Set PA4 (CS) low
}
void spi_unselect(void) {
GPIOA->BSHR = (1 << 4); // Set PA4 (CS) high
}
void init_spidma(void) {
// Enable clock for GPIOA and SPI1
RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1;
// Enable clock for DMA1
RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;
// SPI1 Pin Configuration
// CS on PA4, 10MHz Output, open-drain
GPIOA->CFGLR &= ~(0xf << (4 * 4));
GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 4);
// SCK on PA5, 10MHz Output, alt func, push-pull
GPIOA->CFGLR &= ~(0xf << (4 * 5));
GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5);
// MOSI on PA7, 10MHz input, floating
GPIOA->CFGLR &= ~(0xf << (4 * 6));
GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * 6);
// MISO on PA6, 10MHz Output, alt func, push-pull
GPIOA->CFGLR &= ~(0xf << (4 * 7));
GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 7);
// Reset and Configure SPI1
SPI1->CTLR1 = 0; // Clear CTLR1 initially
SPI1->CTLR1 = SPI_Mode_Master | // Master mode
SPI_Direction_2Lines_FullDuplex | // Full duplex
SPI_DataSize_8b | // 8-bit data frame format
SPI_CPOL_Low | // Clock polarity
SPI_CPHA_1Edge | // Clock phase
SPI_BaudRatePrescaler_64 | // Baud rate prescaler
SPI_NSS_Soft; // Software NSS management
// Enable TX and RX DMA
SPI1->CTLR2 = SPI_CTLR2_TXDMAEN | SPI_CTLR2_RXDMAEN;
SPI1->I2SCFGR &= SPI_Mode_Select; // Disable I2S mode
SPI1->CTLR1 |= SPI_CTLR1_SPE; // Enable SPI
// DMA setup
DMA1_Channel3->PADDR = (uint32_t)&SPI1->DATAR; // TX Channel
DMA1_Channel3->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
DMA_Mode_Normal | DMA_DIR_PeripheralDST | DMA_IT_TC;
DMA1_Channel2->PADDR = (uint32_t)&SPI1->DATAR; // RX Channel
DMA1_Channel2->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
DMA_Mode_Normal | DMA_DIR_PeripheralSRC | DMA_IT_TC;
// NVIC_SetPriority(DMA1_Channel2_IRQn, 0);
// NVIC_SetPriority(DMA1_Channel3_IRQn, 0);
NVIC_SetPriority(DMA1_Channel2_IRQn, 0x20);
NVIC_SetPriority(DMA1_Channel3_IRQn, 0x20);
NVIC_EnableIRQ(DMA1_Channel3_IRQn);
NVIC_EnableIRQ(DMA1_Channel2_IRQn);
}

View File

@@ -1,37 +0,0 @@
#include "uart.h"
#include "ch32v003fun.h"
// Write multiple chars to UART
int _write(__attribute__((unused)) int fd, const char *buf, int size) {
for (int i = 0; i < size; i++) {
while (!(USART2->STATR & USART_FLAG_TC)); // Wait for transmission complete
USART2->DATAR = *buf++; // Send character
}
return size;
}
// Write a single char to UART
int putchar(int c) {
while (!(USART2->STATR & USART_FLAG_TC)); // Wait for transmission complete
USART2->DATAR = (uint8_t)c; // Send character
return 1;
}
void init_uart(int uart_brr) {
RCC->APB2PCENR |= RCC_APB2Periph_GPIOA; // Enable GPIOA on APB2
RCC->APB1PCENR |= RCC_APB1Periph_USART2; // Enable USART2 on APB1
GPIOA->CFGLR &= ~(0xf << (4 * 2)); // Clear bits for PA2
GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)
<< (4 * 2); // Set PA2 as AF
// USART configuration: 115200 baud rate, 8 data bits, no parity, 1 stop bit
USART2->CTLR1 = USART_WordLength_8b | USART_Parity_No | USART_Mode_Tx;
USART2->CTLR2 = USART_StopBits_1;
USART2->CTLR3 = USART_HardwareFlowControl_None;
USART2->BRR = uart_brr;
// Enable USART2
USART2->CTLR1 |= CTLR1_UE_Set;
}

View File

@@ -1,180 +0,0 @@
#include "w5500.h"
#include <DHCP/dhcp.h>
#include <DNS/dns.h>
#include <MQTT/MQTTClient.h>
#include <W5500/w5500.h>
#include <socket.h>
#include <string.h>
#include "ch32v003fun.h"
#include "config.h"
#include "debug.h"
#include "spi_dma.h"
volatile uint32_t count = 0;
volatile int ip_assigned = 0;
// Global buffers for DHCP and DNS
static uint8_t dhcp_buffer[512];
static uint8_t dns_buffer[512];
// Function to handle IP assignment details
void handle_ip_assigned(void) {
DEBUG_PRINT("IP Assigned!\n");
wiz_NetInfo net_info;
getIPfromDHCP(net_info.ip);
getGWfromDHCP(net_info.gw);
getSNfromDHCP(net_info.sn);
uint8_t dns[4];
getDNSfromDHCP(dns);
DEBUG_PRINT(
"IP: %d.%d.%d.%d\nGW: %d.%d.%d.%d\nNet: %d.%d.%d.%d\nDNS: "
"%d.%d.%d.%d\n",
net_info.ip[0], net_info.ip[1], net_info.ip[2], net_info.ip[3],
net_info.gw[0], net_info.gw[1], net_info.gw[2], net_info.gw[3],
net_info.sn[0], net_info.sn[1], net_info.sn[2], net_info.sn[3], dns[0],
dns[1], dns[2], dns[3]);
wizchip_setnetinfo(&net_info);
}
// Callback functions
void callback_ip_assigned(void) {
DEBUG_PRINT("Callback: IP assigned! Leased time: %lu sec\n",
getDHCPLeasetime());
ip_assigned = 1;
}
void callback_ip_conflict(void) { DEBUG_PRINT("Callback: IP conflict!\n"); }
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);
}
void configure_dhcp(void) {
wiz_NetInfo net_info = {.mac = {0xEA, 0x11, 0x22, 0x33, 0x44, 0xEA},
.dhcp = NETINFO_DHCP};
setSHAR(net_info.mac);
DHCP_init(DHCP_SOCKET, dhcp_buffer);
// Register DHCP callbacks
reg_dhcp_cbfunc(callback_ip_assigned, callback_ip_assigned,
callback_ip_conflict);
// Attempt to acquire an IP address using DHCP
while (!ip_assigned) {
DHCP_run();
Delay_Ms(100);
DEBUG_PRINT("DHCP_run()...\n");
}
if (!ip_assigned) {
DEBUG_PRINT("\r\nIP was not assigned\r\n");
return;
}
handle_ip_assigned();
}
// 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++;
}
}
MQTTClient setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts) {
static unsigned char tx_buffer[MQTT_TX_BUFFER_SIZE];
static unsigned char rx_buffer[MQTT_RX_BUFFER_SIZE];
MQTTClient client;
int rc;
uint8_t target_ip[] = MQTT_TARGET_IP;
NewNetwork(network, TCP_SOCKET);
if (ConnectNetwork(network, target_ip, MQTT_TARGET_PORT) != SOCK_OK) {
DEBUG_PRINT("Network connection failed.\n");
return (MQTTClient){0}; // Return an empty client on failure
}
// Initialize the MQTT client
MQTTClientInit(&client, network, MQTT_COMMAND_TIMEOUT_MS, tx_buffer,
sizeof(tx_buffer), rx_buffer, sizeof(rx_buffer));
// Setup MQTT connection data
MQTTPacket_connectData connect_data = MQTTPacket_connectData_initializer;
connect_data.willFlag = 0;
connect_data.MQTTVersion = 3;
connect_data.clientID.cstring = opts->clientid;
connect_data.username.cstring = opts->username;
connect_data.password.cstring = opts->password;
connect_data.keepAliveInterval = MQTT_KEEP_ALIVE_INTERVAL;
connect_data.cleansession = 1;
// Connect to MQTT broker
rc = MQTTConnect(&client, &connect_data);
if (rc != 0) {
DEBUG_PRINT("Failed to connect: %d\n", rc);
return (MQTTClient){0}; // Return an empty client on failure
}
return client;
}
int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos,
messageHandler message_callback) {
int rc = MQTTSubscribe(client, topic, qos, message_callback);
if (rc != 0) {
DEBUG_PRINT("Failed to subscribe to %s: %d\n", topic, rc);
return rc;
}
return 0; // Success
}
void publish_message(MQTTClient* client, const char* payload,
const char* topic) {
MQTTMessage message = {.qos = QOS0,
.retained = 0,
.dup = 0,
.payload = (void*)payload,
.payloadlen = strlen(payload)
};
if (MQTTPublish(client, topic, &message) != 0) {
DEBUG_PRINT("Publish failed\n");
} else {
DEBUG_PRINT("Message published successfully\n");
}
}

View File

@@ -1,13 +1,13 @@
#include "systick.h" #include "systick.h"
#include "MQTT/mqtt_interface.h"
#include "ch32v003fun.h" #include "ch32v003fun.h"
#include "debug.h" #include "debug.h"
// ms counter // ms counter
volatile uint32_t systick_millis = 0; volatile uint32_t systick_millis = 0;
volatile int toggle_state = 0;
void init_systick(void) { void systick_init(void) {
SysTick->CTLR = 0; SysTick->CTLR = 0;
SysTick->CMP = SYSTICK_ONE_MILLISECOND - 1; SysTick->CMP = SYSTICK_ONE_MILLISECOND - 1;
SysTick->CNT = 0; SysTick->CNT = 0;
@@ -27,12 +27,12 @@ void init_systick(void) {
*/ */
void SysTick_Handler(void) __attribute__((interrupt)); void SysTick_Handler(void) __attribute__((interrupt));
void SysTick_Handler(void) { void SysTick_Handler(void) {
if (toggle_state) { // if (toggle_state) {
GPIOB->BSHR = (1 << 9); // Set PB9 high // GPIOB->BSHR = (1 << 9); // Set PB9 high
} else { // } else {
GPIOB->BCR = (1 << 9); // Set PB9 low // GPIOB->BCR = (1 << 9); // Set PB9 low
} // }
toggle_state = !toggle_state; // toggle_state = !toggle_state;
// Increment the Compare Register for the next trigger // Increment the Compare Register for the next trigger
// If more than this number of ticks elapse before the trigger is reset, // If more than this number of ticks elapse before the trigger is reset,
// you may miss your next interrupt trigger // you may miss your next interrupt trigger
@@ -42,4 +42,5 @@ void SysTick_Handler(void) {
// clear irq // clear irq
SysTick->SR = 0; SysTick->SR = 0;
systick_millis++; systick_millis++;
} MilliTimer_Handler();
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

View File

@@ -1,7 +1,6 @@
#include "timer.h" #include "timer.h"
#include <DHCP/dhcp.h> #include <DHCP/dhcp.h>
#include <DNS/dns.h>
#include "ch32v003fun.h" #include "ch32v003fun.h"
#include "debug.h" #include "debug.h"
@@ -37,6 +36,5 @@ void TIM2_IRQHandler(void) {
// DEBUG_PRINT("TIM2 IRQ\n"); // DEBUG_PRINT("TIM2 IRQ\n");
DHCP_time_handler(); DHCP_time_handler();
DNS_time_handler();
} }
} }

94
uart.c Normal file
View File

@@ -0,0 +1,94 @@
#include "uart.h"
#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++) {
while (!(USART2->STATR & USART_FLAG_TC)); // Wait for transmission complete
USART2->DATAR = *buf++; // Send character
}
return size;
}
// Write a single char to UART
int putchar(int c) {
while (!(USART2->STATR & USART_FLAG_TC)); // Wait for transmission complete
USART2->DATAR = (uint8_t)c; // Send character
return (unsigned char)c;
}
void uart2_init(int uart_brr) {
RCC->APB2PCENR |= RCC_APB2Periph_GPIOA; // Enable GPIOA on APB2
RCC->APB1PCENR |= RCC_APB1Periph_USART2; // Enable USART2 on APB1
GPIOA->CFGLR &= ~(0xf << (4 * 2)); // Clear bits for PA2
GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)
<< (4 * 2); // Set PA2 as AF
// USART configuration: 115200 baud rate, 8 data bits, no parity, 1 stop bit
USART2->CTLR1 = USART_WordLength_8b | USART_Parity_No | USART_Mode_Tx;
USART2->CTLR2 = USART_StopBits_1;
USART2->CTLR3 = USART_HardwareFlowControl_None;
USART2->BRR = uart_brr;
// Enable USART2
USART2->CTLR1 |= CTLR1_UE_Set;
}
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; }