第一次提交:完成了网关的单路485数据采集,还有以太网链接和MQTT配置,实现数据上报和命令下发,差一个断网储存

This commit is contained in:
Wang Beihong
2026-02-01 18:31:06 +08:00
commit b284cb4953
35 changed files with 4338 additions and 0 deletions

View File

@@ -0,0 +1,311 @@
#include "RS-485-SP3485EEN.h"
#include <esp_log.h>
#include "MQTT_ESP.h"
#include "STATUS_LED.h"
#include "MODBUS_ESP.h"
#include "cJSON.h"
#define TAG "RS485_DRIVER"
// Timeout threshold for UART = number of symbols (~10 tics) with unchanged state on receive pin
#define ECHO_READ_TOUT (3) // 3.5T * 8 = 28 ticks, TOUT=3 -> ~24..33 ticks
// ----------------------------
// 通道数组定义
// ----------------------------
rs485_channel_t rs485_channels[] = {
{RS_485_SP3485EEN_UART_PORT, RS_485_SP3485EEN_DI_PIN, RS_485_SP3485EEN_RO_PIN, RS_485_SP3485EEN_DE_RE_PIN, "RS485-1"},
{RS_485_SP3485EEN_2_UART_PORT, RS_485_SP3485EEN_2_DI_PIN, RS_485_SP3485EEN_2_RO_PIN, RS_485_SP3485EEN_2_DE_RE_PIN, "RS485-2"}};
// ============================
// UART 发送函数
// ============================
void rs485_send(uart_port_t uart_num, const uint8_t *data, size_t len)
{
if (data == NULL || len == 0)
{
ESP_LOGW(TAG, "rs485_send: empty payload");
return;
}
// 清空 RX防止残留帧污染
uart_flush_input(uart_num);
// 发送数据
int written = uart_write_bytes(uart_num, (const char *)data, len);
if (written < 0)
{
ESP_LOGE(TAG, "UART%d TX write error (%d)", uart_num, written);
return;
}
if ((size_t)written != len)
{
ESP_LOGW(TAG, "UART%d TX partial (%d/%d)", uart_num, written, len);
}
// RS485 半双工模式下uart_write_bytes 会自动等待发送完成
// 这里只需要等待一个额外的延时确保所有数据都已发送完成
// 根据波特率和字节数计算传输时间11位/字节1起始位+8数据位+1停止位+1奇偶校验位
uint32_t transmit_time_ms = (len * 11 * 1000) / (BAUD_RATE > 0 ? BAUD_RATE : 1);
vTaskDelay(pdMS_TO_TICKS(transmit_time_ms + 5));
// Modbus RTU 3.5T 帧间静默(保留短延时)
vTaskDelay(pdMS_TO_TICKS(5));
ESP_LOGI(TAG, "UART%d TX done (%d bytes)", uart_num, written);
}
// ============================
// UART 接收函数
// ============================
int rs485_receive(uart_port_t uart_num, uint8_t *buffer, size_t buf_size, uint32_t timeout_ms)
{
if (buffer == NULL || buf_size == 0)
{
ESP_LOGW(TAG, "rs485_receive: invalid buffer");
return -1;
}
int len = uart_read_bytes(uart_num, buffer, buf_size, pdMS_TO_TICKS(timeout_ms));
if (len > 0)
{
ESP_LOGI(TAG, "UART%d RX (%d bytes)", uart_num, len);
// 不在这里打印数据交由调用者task处理避免重复日志
}
else if (len == 0)
{
ESP_LOGD(TAG, "UART%d RX timeout", uart_num);
}
else
{
ESP_LOGW(TAG, "UART%d RX error (%d)", uart_num, len);
}
return len;
}
// ============================
// RS485 初始化函数
// ============================
void RS_485_init(uart_port_t uart_num, int tx_pin, int rx_pin, int de_re_pin)
{
uart_config_t uart_config = {
.baud_rate = BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
.rx_flow_ctrl_thresh = 122,
};
ESP_ERROR_CHECK(uart_driver_install(
uart_num,
BUF_SIZE * 2,
BUF_SIZE * 2,
0,
NULL,
0));
ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(
uart_num,
tx_pin,
rx_pin,
de_re_pin,
UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));
ESP_ERROR_CHECK(uart_set_rx_timeout(uart_num, ECHO_READ_TOUT));
ESP_LOGI(TAG,
"RS485 init OK: UART%d TX=%d RX=%d DE/RE=%d",
uart_num, tx_pin, rx_pin, de_re_pin);
}
// ============================
// 初始化指定RS485通道
// ============================
void init_specific_rs485_channel(int channel_num)
{
if (channel_num < 0 || channel_num >= NUM_CHANNELS)
{
ESP_LOGE(TAG, "Invalid channel number: %d", channel_num);
return;
}
rs485_channel_t *ch = &rs485_channels[channel_num];
// 初始化RS485硬件
RS_485_init(ch->uart_num, ch->tx_pin, ch->rx_pin, ch->de_re_pin);
ESP_LOGI(TAG, "Channel %d: %s (UART%d) initialized",
channel_num, ch->name, ch->uart_num);
}
// ============================
// 初始化所有RS485通道
// ============================
void init_all_rs485_channels(void)
{
ESP_LOGI(TAG, "Initializing %d RS485 channels", NUM_CHANNELS);
for (int i = 0; i < NUM_CHANNELS; i++)
{
init_specific_rs485_channel(i);
}
}
/* 接收任务:参数为通道索引 (int cast via intptr_t) */
static void rs485_rx_task(void *arg)
{
int channel = (int)(intptr_t)arg;
if (channel < 0 || channel >= NUM_CHANNELS)
{
ESP_LOGE(TAG, "rs485_rx_task: invalid channel %d", channel);
vTaskDelete(NULL);
return;
}
rs485_channel_t *ch = &rs485_channels[channel];
/* 使用堆分配,避免栈溢出 */
uint8_t *buf = malloc(BUF_SIZE);
if (buf == NULL)
{
ESP_LOGE(TAG, "rs485_rx_task: malloc failed");
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "%s RX task started", ch->name);
while (1)
{
int len = rs485_receive(ch->uart_num, buf, BUF_SIZE, 100); // 100ms timeout for Modbus
if (len > 0)
{
// 数据接收成功LED2 短暂亮起表示有数据
status_led_set(2, 1);
ESP_LOGI(TAG, "%s UART%d RX (%d bytes)", ch->name, ch->uart_num, len);
ESP_LOG_BUFFER_HEX(TAG, buf, len);
// 尝试解析MODBUS响应
modbus_response_t response;
if (modbus_parse_response(buf, len, &response))
{
// MODBUS解析成功构建JSON格式数据
cJSON *root = cJSON_CreateObject();
if (root != NULL)
{
// 添加基本信息
cJSON_AddStringToObject(root, "channel", ch->name);
cJSON_AddNumberToObject(root, "slave_addr", response.slave_addr);
cJSON_AddNumberToObject(root, "function_code", response.function_code);
if (response.is_exception)
{
// 异常响应
cJSON_AddStringToObject(root, "status", "exception");
cJSON_AddNumberToObject(root, "exception_code", response.exception_code);
ESP_LOGW(TAG, "Modbus exception response: code=0x%02X", response.exception_code);
}
else
{
// 正常响应
cJSON_AddStringToObject(root, "status", "success");
cJSON_AddNumberToObject(root, "byte_count", response.byte_count);
cJSON_AddNumberToObject(root, "register_count", response.register_count);
// 添加寄存器数组
cJSON *reg_array = cJSON_CreateArray();
if (reg_array != NULL)
{
for (uint8_t i = 0; i < response.register_count; i++)
{
cJSON_AddItemToArray(reg_array, cJSON_CreateNumber(response.registers[i]));
}
cJSON_AddItemToObject(root, "registers", reg_array);
}
ESP_LOGI(TAG, "Modbus response parsed: slave=%d, func=0x%02X, regs=%d",
response.slave_addr, response.function_code, response.register_count);
}
// 转换为JSON字符串
char *json_str = cJSON_Print(root);
if (json_str != NULL)
{
// 发布JSON数据到MQTT
int ret = mqtt_publish_message(CONFIG_MQTT_PUB_TOPIC, json_str, strlen(json_str), 0, 0);
if (ret < 0)
{
ESP_LOGW(TAG, "Failed to publish Modbus JSON to MQTT topic %s", CONFIG_MQTT_PUB_TOPIC);
}
else
{
ESP_LOGI(TAG, "Published Modbus JSON to MQTT topic %s, msg_id: %d", CONFIG_MQTT_PUB_TOPIC, ret);
ESP_LOGD(TAG, "JSON payload: %s", json_str);
}
free(json_str);
}
cJSON_Delete(root);
}
// 释放响应中的寄存器内存
modbus_free_response(&response);
}
else
{
// MODBUS解析失败原始数据直接上报
ESP_LOGW(TAG, "Failed to parse Modbus frame, publishing raw data");
int ret = mqtt_publish_message(CONFIG_MQTT_PUB_TOPIC, (char *)buf, len, 0, 0);
if (ret < 0)
{
ESP_LOGW(TAG, "Failed to publish RS485 data to MQTT topic %s", CONFIG_MQTT_PUB_TOPIC);
}
}
vTaskDelay(pdMS_TO_TICKS(50)); // 保持亮起50ms
status_led_blink_mode(2, 2); // 恢复心跳模式
}
else if (len == 0)
{
vTaskDelay(pdMS_TO_TICKS(50)); // 无数据短延时
}
else
{
// 接收错误,不记录日志避免刷屏
vTaskDelay(pdMS_TO_TICKS(200));
}
}
/* 永久任务通常不会到这里;若退出则释放 */
free(buf);
vTaskDelete(NULL);
}
/* 启动单个通道的接收任务
返回 pdPASS 或 pdFAIL */
BaseType_t start_rs485_rx_task_for_channel(int channel_num, UBaseType_t priority, uint32_t stack_size)
{
if (channel_num < 0 || channel_num >= NUM_CHANNELS)
{
ESP_LOGE(TAG, "start_rs485_rx_task_for_channel: invalid channel %d", channel_num);
return pdFAIL;
}
char tname[16];
snprintf(tname, sizeof(tname), "rs485_rx_%d", channel_num);
return xTaskCreate(rs485_rx_task, tname, stack_size, (void *)(intptr_t)channel_num, priority, NULL);
}
/* 可选:启动所有通道的接收任务(示例默认优先级与栈)*/
void start_all_rs485_rx_tasks(UBaseType_t priority, uint32_t stack_size)
{
for (int i = 0; i < NUM_CHANNELS; i++)
{
if (start_rs485_rx_task_for_channel(i, priority, stack_size) != pdPASS)
{
ESP_LOGW(TAG, "Failed to start RX task for channel %d", i);
}
else
{
ESP_LOGI(TAG, "Started RX task for channel %d", i);
}
}
}