312 lines
11 KiB
C
312 lines
11 KiB
C
#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发送: 数据为空");
|
||
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 发送写入错误 (%d)", uart_num, written);
|
||
return;
|
||
}
|
||
if ((size_t)written != len)
|
||
{
|
||
ESP_LOGW(TAG, "UART%d 发送部分数据 (%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 发送完成 (%d 字节)", 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接收: 缓冲区无效");
|
||
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 接收超时", uart_num);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "UART%d 接收错误 (%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);
|
||
}
|
||
}
|
||
}
|