第一次提交:完成了网关的单路485数据采集,还有以太网链接和MQTT配置,实现数据上报和命令下发,差一个断网储存
This commit is contained in:
4
components/RS-485-SP3485EEN/CMakeLists.txt
Normal file
4
components/RS-485-SP3485EEN/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "RS-485-SP3485EEN.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES nvs_flash driver MQTT_ESP mqtt STATUS_LED MODBUS_ESP json
|
||||
)
|
||||
311
components/RS-485-SP3485EEN/RS-485-SP3485EEN.c
Normal file
311
components/RS-485-SP3485EEN/RS-485-SP3485EEN.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
components/RS-485-SP3485EEN/include/RS-485-SP3485EEN.h
Normal file
66
components/RS-485-SP3485EEN/include/RS-485-SP3485EEN.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// RS-485-SP3485EEN.h
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uart_port_t uart_num;
|
||||
gpio_num_t tx_pin;
|
||||
gpio_num_t rx_pin;
|
||||
gpio_num_t de_re_pin;
|
||||
char name[20];
|
||||
} rs485_channel_t;
|
||||
|
||||
/**
|
||||
* @brief RS-485-SP3485EEN.h
|
||||
* DE和RE引脚接在一起使用,默认是下拉,通过控制DE_RE引脚的高低电平来控制发送和接收
|
||||
* DE_RE = HIGH 发送数据,接收器使能,低电平有效。低电平时使能接收器,高电平时禁用接收器
|
||||
* DE_RE = LOW 接收数据,驱动器(发送器)使能,高电平有效。高电平时使能发送器,低电平时禁用发送器
|
||||
*
|
||||
* 所以默认是接收状态,发送数据时需要先将DE_RE引脚拉高,发送完数据后再拉低
|
||||
*/
|
||||
|
||||
// 第一个模块的引脚配置
|
||||
#define RS_485_SP3485EEN_UART_PORT (UART_NUM_0)
|
||||
#define RS_485_SP3485EEN_RO_PIN (GPIO_NUM_41) // 接收器输出。将总线上的差分信号转换为TTL电平,输出给 单片机RX
|
||||
#define RS_485_SP3485EEN_DE_RE_PIN (GPIO_NUM_42) // 数据使能,接收器使能。控制发送器和接收器是否工作
|
||||
#define RS_485_SP3485EEN_DI_PIN (GPIO_NUM_44) // 驱动器输入。单片机TX 发送的TTL电平信号,转换为总线上的差分信号
|
||||
|
||||
// 第二个模块的引脚配置
|
||||
#define RS_485_SP3485EEN_2_UART_PORT (UART_NUM_2)
|
||||
#define RS_485_SP3485EEN_2_RO_PIN (GPIO_NUM_43) // 接收器输出。将总线上的差分信号转换为TTL电平,输出给 单片机RX
|
||||
#define RS_485_SP3485EEN_2_DE_RE_PIN (GPIO_NUM_2) // 数据使能,接收器使能。控制发送器和接收器是否工作
|
||||
#define RS_485_SP3485EEN_2_DI_PIN (GPIO_NUM_1) // 驱动器输入。单片机TX 发送的TTL电平信号,转换为总线上的差分信号
|
||||
|
||||
// 公共配置
|
||||
#define BUF_SIZE 256
|
||||
#define BAUD_RATE 115200
|
||||
|
||||
// 通道数量常量
|
||||
#define RS485_NUM_CHANNELS 2
|
||||
|
||||
// ----------------------------
|
||||
// 通道数组
|
||||
// ----------------------------
|
||||
|
||||
extern rs485_channel_t rs485_channels[];
|
||||
#define NUM_CHANNELS RS485_NUM_CHANNELS
|
||||
|
||||
void RS_485_init(uart_port_t uart_num, int tx_pin, int rx_pin, int de_re_pin);
|
||||
void init_specific_rs485_channel(int channel_num); // 新增函数声明
|
||||
void init_all_rs485_channels(void);
|
||||
|
||||
void rs485_send(uart_port_t uart_num, const uint8_t *data, size_t len);
|
||||
int rs485_receive(uart_port_t uart_num, uint8_t *buffer, size_t buf_size, uint32_t timeout_ms);
|
||||
|
||||
BaseType_t start_rs485_rx_task_for_channel(int channel_num, UBaseType_t priority, uint32_t stack_size);
|
||||
|
||||
Reference in New Issue
Block a user