Files
DistributedCollectorGateway/components/RS-485-SP3485EEN/RS-485-SP3485EEN.c

312 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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);
}
}
}