Compare commits
11 Commits
74bc0d25b7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
4a391756b4
|
|||
|
3adc6f3b28
|
|||
|
8adc726b0b
|
|||
|
2a7aa8aea3
|
|||
|
48943ba71f
|
|||
|
5f9c966602
|
|||
|
8fe50deeed
|
|||
|
39f7755477
|
|||
|
3ac9c62241
|
|||
|
80cf21f143
|
|||
|
40f1cab745
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "ch32v003fun"]
|
||||||
|
path = ch32v003fun
|
||||||
|
url = https://github.com/cnlohr/ch32v003fun.git
|
||||||
20
.vscode/settings.json
vendored
20
.vscode/settings.json
vendored
@@ -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
42
Makefile
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
TARGET ?= ch32_node
|
||||||
|
TARGET_MCU ?= CH32V203
|
||||||
|
TARGET_MCU_PACKAGE ?= CH32V203C8T6
|
||||||
|
|
||||||
|
CH32V003FUN ?= ./ch32v003fun/ch32v003fun
|
||||||
|
MINICHLINK ?= ./ch32v003fun/minichlink
|
||||||
|
|
||||||
|
PREFIX ?= riscv64-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
|
||||||
69
README.md
69
README.md
@@ -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
|
||||||

|
|
||||||
|
|
||||||
|
- 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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```
|
|
||||||
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
88
ch32_node.c
Normal 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
1
ch32v003fun
Submodule
Submodule ch32v003fun added at 69801af882
10
config.c
Normal file
10
config.c
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// Node config definition
|
||||||
|
node_config_t NODE_CONFIG = {.id = "ch32-node-1",
|
||||||
|
.name = "Greenhouse node 1",
|
||||||
|
.location = "Greenhouse"};
|
||||||
|
|
||||||
|
// 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
98
gpio.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
@@ -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, 1}
|
||||||
|
#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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
56
include/modbus.h
Normal 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
46
include/mqtt_handler.h
Normal 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
27
include/network.h
Normal 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
119
include/onewire.h
Normal 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
46
include/onewire_temp.h
Normal 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
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
51
include/utils.h
Normal 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
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
@@ -109,8 +111,13 @@ static int readPacket(MQTTClient* c, Timer* timer)
|
|||||||
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 */
|
||||||
@@ -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)
|
||||||
@@ -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 */
|
||||||
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
if (WIZCHIP.IF.SPI._read_burst) {
|
||||||
|
WIZCHIP.IF.SPI._read_burst(&ret, 1);
|
||||||
|
} else {
|
||||||
ret = WIZCHIP.IF.SPI._read_byte();
|
ret = WIZCHIP.IF.SPI._read_byte();
|
||||||
|
}
|
||||||
|
|
||||||
WIZCHIP.CS._deselect();
|
WIZCHIP.CS._deselect();
|
||||||
WIZCHIP_CRITICAL_EXIT();
|
WIZCHIP_CRITICAL_EXIT();
|
||||||
@@ -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,11 +520,17 @@ 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
|
||||||
@@ -530,12 +538,19 @@ int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
46
lib/README
46
lib/README
@@ -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
29
lmao.sh
@@ -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
277
modbus.c
Normal 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
386
mqtt_handler.c
Normal 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
130
network.c
Normal 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(¤t_net_info);
|
||||||
|
DEBUG_PRINT("IP config: %d.%d.%d.%d\n", current_net_info.ip[0],
|
||||||
|
current_net_info.ip[1], current_net_info.ip[2],
|
||||||
|
current_net_info.ip[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void callback_dhcp_ip_update(void) {
|
||||||
|
dhcp_lease_time = getDHCPLeasetime();
|
||||||
|
DEBUG_PRINT("IP %s! Lease time: %lu sec\n",
|
||||||
|
dhcp_state == DHCP_STATE_INIT ? "assigned" : "updated",
|
||||||
|
dhcp_lease_time);
|
||||||
|
|
||||||
|
// Update all network configuration at once
|
||||||
|
getIPfromDHCP(current_net_info.ip);
|
||||||
|
getGWfromDHCP(current_net_info.gw);
|
||||||
|
getSNfromDHCP(current_net_info.sn);
|
||||||
|
getDNSfromDHCP(current_net_info.dns);
|
||||||
|
|
||||||
|
dhcp_state = DHCP_STATE_LEASED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void callback_ip_conflict(void) {
|
||||||
|
DEBUG_PRINT("IP conflict!\n");
|
||||||
|
dhcp_state = DHCP_STATE_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize network with MAC based on node ID
|
||||||
|
void network_init(void) {
|
||||||
|
uint8_t node_num = parse_node_number(NODE_CONFIG.id);
|
||||||
|
|
||||||
|
// MAC address
|
||||||
|
current_net_info.mac[0] = 0x02; // Locally administered
|
||||||
|
current_net_info.mac[1] = 0x32; // Organization ID (CH32)
|
||||||
|
current_net_info.mac[2] = 0x00; // Sub-organization
|
||||||
|
current_net_info.mac[3] = 0x01; // Device type
|
||||||
|
current_net_info.mac[4] = node_num >> 8; // Node number high byte
|
||||||
|
current_net_info.mac[5] = node_num & 0xFF; // Node number low byte
|
||||||
|
|
||||||
|
// Copy MAC to network info
|
||||||
|
memcpy(NODE_CONFIG.mac, current_net_info.mac, 6);
|
||||||
|
|
||||||
|
// Initialize network
|
||||||
|
setSHAR(current_net_info.mac);
|
||||||
|
DHCP_init(DHCP_SOCKET, dhcp_buffer);
|
||||||
|
reg_dhcp_cbfunc(callback_dhcp_ip_update, callback_dhcp_ip_update,
|
||||||
|
callback_ip_conflict);
|
||||||
|
dhcp_state = DHCP_STATE_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dhcp_process(void) {
|
||||||
|
uint32_t current_time = millis();
|
||||||
|
uint16_t processing_interval =
|
||||||
|
dhcp_state == DHCP_STATE_INIT || dhcp_state == DHCP_STATE_RENEW
|
||||||
|
? DHCP_INTERVAL_CONNECTING
|
||||||
|
: DHCP_INTERVAL_CONNECTED;
|
||||||
|
|
||||||
|
if ((current_time - last_processing_time) >= processing_interval) {
|
||||||
|
last_processing_time = current_time;
|
||||||
|
|
||||||
|
switch (dhcp_state) {
|
||||||
|
case DHCP_STATE_INIT:
|
||||||
|
DHCP_run();
|
||||||
|
if (dhcp_state == DHCP_STATE_LEASED) {
|
||||||
|
update_network_config();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DHCP_STATE_LEASED:
|
||||||
|
// renew @ 50% of lease time
|
||||||
|
if (current_time - last_lease_time >= (dhcp_lease_time * 500)) {
|
||||||
|
dhcp_state = DHCP_STATE_RENEW;
|
||||||
|
DHCP_run();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DHCP_STATE_RENEW:
|
||||||
|
DHCP_run();
|
||||||
|
if (dhcp_state == DHCP_STATE_LEASED) {
|
||||||
|
update_network_config();
|
||||||
|
last_lease_time = current_time;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
DHCP_run();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t dhcp_get_state(void) { return dhcp_state; }
|
||||||
BIN
notes/SPI_1.png
Normal file
BIN
notes/SPI_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
BIN
notes/SPI_2.png
Normal file
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
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
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
624
onewire.c
Normal 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
385
onewire_temp.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
131
spi_dma.c
Normal 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;
|
||||||
|
}
|
||||||
18
src/gpio.c
18
src/gpio.c
@@ -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));
|
|
||||||
}
|
|
||||||
103
src/main.c
103
src/main.c
@@ -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;
|
|
||||||
}
|
|
||||||
168
src/spi_dma.c
168
src/spi_dma.c
@@ -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);
|
|
||||||
}
|
|
||||||
37
src/uart.c
37
src/uart.c
@@ -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;
|
|
||||||
}
|
|
||||||
180
src/w5500.c
180
src/w5500.c
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
11
test/README
11
test/README
@@ -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
|
|
||||||
@@ -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
94
uart.c
Normal 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; }
|
||||||
Reference in New Issue
Block a user