新增:
- rs485_modbus.h/c — 完整 Modbus RTU 从机实现 (516行)
· FC 0x02 读离散输入 (9-bit 位图: car_state/线圈/光耦/继电器)
· FC 0x03 读保持寄存器 (含 HR 0x0010/0x0011)
· FC 0x04 读输入寄存器 (基础 IR + 全数据块 0x0100-0x0112)
· FC 0x06/0x10 写寄存器 (含值域校验+只读拦截)
· FC 0x11 Report Slave ID
· CRC16 查表 (poly=0xA001)
· 全数据块组装 (19 reg = 38B big-endian)
· 主动上报 3 级速率调度 (事件立即/活动150ms/空闲800ms)
· 车间距/通过时间测量 (mstick/2=10ms单位, relay边沿触发)
· 光耦状态实时读取 (GPIOB_ReadPortPin)
· Holding Register 写穿透: Fusion Mode 同步 direction_mode,
Hold Time 同步 g_hold_time, Freq Level 同步 g_loop_cng_unit
改造:
- rs485_700bs.h/c: 暴露 rs485_send() + rx缓冲(_rx_buf/_rx_len/_rx_last_tick) 供Modbus共用
- peripheral_main.c: g_fm_700bs_disable 分派 700BS/Modbus; modbus_init() 随 rs485_init 调用
321 lines
11 KiB
C
321 lines
11 KiB
C
/*
|
|
* rs485_700bs.c
|
|
* DLD154Pro RS485 700BS 从机协议实现
|
|
*
|
|
* 物理层: RS485 半双工, 9600 8N1, PA14=TX PA15=RX PA13=RE
|
|
* 帧格式: 主机 3 字节 (STX ADDR XOR), 从机可变长
|
|
* 心跳: 空闲 5s / 故障 1s / 事件立即
|
|
*
|
|
* 2026-07-23 初始实现
|
|
*/
|
|
#include "CONFIG.h"
|
|
#include "CH59x_common.h"
|
|
#include "cmcng.h"
|
|
#include "rs485_700bs.h"
|
|
#include "storage.h"
|
|
#include "dbn_ble_srv.h"
|
|
#include <string.h>
|
|
|
|
/*===========================================================================
|
|
* UART0 中断接收缓冲
|
|
*===========================================================================*/
|
|
#define RS485_RX_BUF_SIZE 16
|
|
uint8_t _rx_buf[RS485_RX_BUF_SIZE];
|
|
uint8_t _rx_len = 0;
|
|
uint32_t _rx_last_tick = 0; // 帧间隔计时 (3.5 字符 ≈ 4ms @9600)
|
|
|
|
/*===========================================================================
|
|
* 心跳 & 状态追踪
|
|
*===========================================================================*/
|
|
static uint32_t _hb_last_ms = 0; // 上次心跳时刻
|
|
static uint8_t _hb_need_init = 1; // 需要发 0xA1 首次心跳
|
|
static uint8_t _last_car_state = 0; // car_state 快照 (翻转沿检测)
|
|
static uint8_t _last_loop_state = 0; // loop_state 快照
|
|
|
|
/*===========================================================================
|
|
* RS485 方向控制 (PA13)
|
|
*===========================================================================*/
|
|
#define RS485_RE_TX() GPIOA_SetBits(GPIO_Pin_13)
|
|
#define RS485_RE_RX() GPIOA_ResetBits(GPIO_Pin_13)
|
|
|
|
/*===========================================================================
|
|
* UART0 初始化 — 9600 8N1, 中断接收
|
|
*===========================================================================*/
|
|
void rs485_init(void)
|
|
{
|
|
/* PA14=TX(推挽), PA15=RX(上拉输入), PA13=RE(推挽, 默认接收) */
|
|
GPIOA_SetBits(GPIO_Pin_13); // RE 先拉高, GPIO 模式
|
|
GPIOA_ModeCfg(GPIO_Pin_14, GPIO_ModeOut_PP_5mA);
|
|
GPIOA_ModeCfg(GPIO_Pin_15, GPIO_ModeIN_PU);
|
|
GPIOA_ModeCfg(GPIO_Pin_13, GPIO_ModeOut_PP_5mA);
|
|
|
|
/* UART0 外设: 9600 8N1 */
|
|
UART0_BaudRateCfg(RS485_BAUD);
|
|
R8_UART0_LCR = 0x03; // 8 数据位, 1 停止位, 无校验
|
|
R8_UART0_MCR = 0x04; // RE=0 进入接收模式
|
|
|
|
/* 中断: 接收可用 */
|
|
UART0_CLR_RXFIFO();
|
|
UART0_ByteTrigCfg(UART_1BYTE_TRIG); // 每收到 1 字节触发中断
|
|
UART0_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
|
|
PFIC_EnableIRQ(UART0_IRQn);
|
|
|
|
RS485_RE_RX(); // 默认接收模式
|
|
_hb_last_ms = mstick(); // 对齐心跳起始时刻,避免首次立即触发
|
|
PRINT("RS485: init ok, addr=%d, baud=%d\n", g_rs485_id, RS485_BAUD);
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 发送一帧 — 切 RE=TX, 发完等 1ms, 切回 RE=RX
|
|
*===========================================================================*/
|
|
/* 发送一帧 (供 Modbus 模块复用) */
|
|
void rs485_send(const uint8_t *data, uint8_t len)
|
|
{
|
|
RS485_RE_TX();
|
|
mDelayuS(100); // 总线稳定
|
|
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
while (R8_UART0_TFC != 0); // 等 TX FIFO 空 (上一字节发完)
|
|
R8_UART0_THR = data[i];
|
|
}
|
|
while (R8_UART0_TFC != 0); // 等最后一字节发完
|
|
mDelayuS(500); // 等待停止位 + 总线释放
|
|
UART0_CLR_TXFIFO();
|
|
RS485_RE_RX();
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 命令处理
|
|
*===========================================================================*/
|
|
|
|
/* 直接从原始测量值计算线圈频率 (Hz),不依赖 BLE 报告通道 */
|
|
static uint32_t rs485_get_freq(void)
|
|
{
|
|
extern uint32_t loop1_CAPVD; // vd1_task() 实时更新的 IIR 滤波值
|
|
if (loop1_CAPVD == 0 || loop1_LPCNT == 0) return 0;
|
|
/* freq = FREQ_SYS / (CAPVD / LPCNT) + 360 (补偿值) */
|
|
return (uint32_t)((uint64_t)FREQ_SYS * loop1_LPCNT / loop1_CAPVD) + 360;
|
|
}
|
|
|
|
/* 0xFA — 查询有车状态 + 线圈好坏 */
|
|
static void handle_query_status(uint8_t addr)
|
|
{
|
|
extern uint8_t loop1_LOOP_OK;
|
|
uint8_t data = addr & 0x1F; // bit4-0 = 地址
|
|
if (g_loop_acs_info.car_state) data |= 0x80; // bit7 = 有车
|
|
if (!loop1_LOOP_OK) data |= 0x20; // bit5 = 线圈断开 (LOOP_OK=0)
|
|
uint8_t buf[3] = { RSP_STATUS, data, (uint8_t)(RSP_STATUS ^ data) };
|
|
rs485_send(buf, 3);
|
|
}
|
|
|
|
/* 0x1B — 查询线圈频率 */
|
|
static void handle_query_freq(uint8_t addr)
|
|
{
|
|
uint32_t freq = rs485_get_freq(); // Hz
|
|
uint8_t buf[6];
|
|
buf[0] = RSP_FREQ;
|
|
buf[1] = (uint8_t)((freq >> 16) & 0xFF); // Fre_1 (MSB)
|
|
buf[2] = (uint8_t)((freq >> 8) & 0xFF); // Fre_2
|
|
buf[3] = (uint8_t)(freq & 0xFF); // Fre_3 (LSB)
|
|
buf[4] = addr & 0x1F;
|
|
buf[5] = buf[0] ^ buf[1] ^ buf[2] ^ buf[3] ^ buf[4];
|
|
rs485_send(buf, 6);
|
|
}
|
|
|
|
/* 0x2C — 复位初始化线圈 */
|
|
static void handle_reset_init(uint8_t addr)
|
|
{
|
|
(void)addr;
|
|
extern uint8_t g_loop_stable;
|
|
extern uint8_t loop1_LOOP_OK0;
|
|
extern uint8_t loop1_LC_Reset;
|
|
extern uint8_t loop1_INI_LOOP;
|
|
extern uint16_t loop1_stable_cnt;
|
|
extern uint16_t LC_Hold_CNT;
|
|
extern uint32_t loop1_ORG_CNT, loop1_ORG_SUM;
|
|
|
|
loop1_LC_Reset = 1;
|
|
loop1_INI_LOOP = 1;
|
|
loop1_LOOP_OK0 = 0;
|
|
g_loop_stable = 0;
|
|
loop1_stable_cnt = 0;
|
|
LC_Hold_CNT = 0;
|
|
loop1_ORG_CNT = 0;
|
|
loop1_ORG_SUM = 0;
|
|
_hb_need_init = 1;
|
|
_last_car_state = (_last_car_state ^ 1);
|
|
PRINT("RS485: coil reset init\n");
|
|
}
|
|
|
|
/* 0x3D — 查询拨码 DIP1-5 (DLD154Pro 无物理拨码, 从 BLE 配置映射) */
|
|
static void handle_query_dip(uint8_t addr)
|
|
{
|
|
uint8_t data = 0;
|
|
uint8_t sens = g_loop_cng_unit.sensitvity & 0x0F; // 灵敏度 0-3
|
|
data |= (sens & 0x01); // DIP1 = bit0
|
|
data |= ((sens >> 1) & 0x01) << 1; // DIP2 = bit1
|
|
if (g_loop_cng_unit.output_mode & 0x01) data |= 0x04; // DIP3 = bit2 (1=脉冲)
|
|
if (g_loop_cng_unit.exist_mode == 0) data |= 0x08; // DIP4 = bit3 (0=永久→ON=1)
|
|
if (g_loop_cng_unit.delay_time > 0) data |= 0x10; // DIP5 = bit4 (>0=启用延时)
|
|
uint8_t buf[4] = { RSP_DIP, data, (uint8_t)(addr & 0x1F),
|
|
(uint8_t)(RSP_DIP ^ data ^ (addr & 0x1F)) };
|
|
rs485_send(buf, 4);
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 心跳上报 (0xAA) — 空闲 5s / 故障 1s / 事件立即
|
|
*===========================================================================*/
|
|
|
|
/* Fre_Err 计算: bit0=下限异常, bit4=上限异常 */
|
|
static uint8_t calc_freq_err(uint32_t freq)
|
|
{
|
|
(void)freq;
|
|
/* DLD154Pro 未实现频率越限检测, 始终返回 0 */
|
|
return 0x00;
|
|
}
|
|
|
|
static void rs485_send_heartbeat(void)
|
|
{
|
|
extern uint8_t loop1_LOOP_OK;
|
|
uint32_t freq = rs485_get_freq();
|
|
uint8_t st = g_rs485_id & 0x1F;
|
|
|
|
if (g_loop_acs_info.car_state) st |= 0x80; // bit7=有车
|
|
if (!loop1_LOOP_OK) st |= 0x20; // bit5=线圈断开
|
|
|
|
uint8_t buf[9];
|
|
buf[0] = HB_PREAMBLE; // 0xAA
|
|
buf[1] = _hb_need_init ? HB_INIT_FIRST : HB_INIT_NORMAL;
|
|
_hb_need_init = 0;
|
|
buf[2] = st; // State
|
|
buf[3] = calc_freq_err(freq); // Fre_Err
|
|
buf[4] = (uint8_t)((freq >> 16) & 0xFF); // Fre_1
|
|
buf[5] = (uint8_t)((freq >> 8) & 0xFF); // Fre_2
|
|
buf[6] = (uint8_t)(freq & 0xFF); // Fre_3
|
|
buf[7] = g_rs485_id & 0x1F; // ADDR
|
|
buf[8] = buf[0];
|
|
for (uint8_t i = 1; i < 8; i++) buf[8] ^= buf[i];
|
|
|
|
rs485_send(buf, 9);
|
|
}
|
|
void tmp_send(void){
|
|
rs485_send_heartbeat();
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 帧解析 & 主循环
|
|
*===========================================================================*/
|
|
|
|
/* UART0 接收中断 (每字节触发) */
|
|
__attribute__((interrupt("WCH-Interrupt-fast")))
|
|
void UART0_IRQHandler(void)
|
|
{
|
|
switch (UART0_GetITFlag()) {
|
|
case UART_II_RECV_RDY: // 接收数据就绪
|
|
if (_rx_len < RS485_RX_BUF_SIZE) {
|
|
_rx_buf[_rx_len++] = UART0_RecvByte();
|
|
} else {
|
|
_rx_len = 0; // 溢出, 丢弃
|
|
}
|
|
_rx_last_tick = mstick();
|
|
break;
|
|
|
|
case UART_II_LINE_STAT: // 线路状态 (帧错误/溢出等)
|
|
UART0_GetLinSTA(); // 读 LSR 清状态
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 处理收到的完整帧 */
|
|
static void rs485_process_frame(void)
|
|
{
|
|
if (_rx_len < 3) return; // 最少 3 字节
|
|
|
|
/* XOR 校验: STX ^ ADDR/CMD */
|
|
uint8_t stx = _rx_buf[0];
|
|
uint8_t addr = _rx_buf[1];
|
|
uint8_t xorr = _rx_buf[2];
|
|
|
|
if ((stx ^ addr) != xorr) {
|
|
PRINT("RS485: XOR fail %02X %02X %02X\n", stx, addr, xorr);
|
|
_rx_len = 0;
|
|
return;
|
|
}
|
|
|
|
/* 地址匹配检查 (0x2C 复位命令全地址响应) */
|
|
if (stx != CMD_RESET_INIT && (addr & 0x1F) != (g_rs485_id & 0x1F)) {
|
|
_rx_len = 0;
|
|
return; // 不是给我的
|
|
}
|
|
|
|
switch (stx) {
|
|
case CMD_QUERY_STATUS: handle_query_status(addr); break;
|
|
case CMD_QUERY_FREQ: handle_query_freq(addr); break;
|
|
case CMD_RESET_INIT: handle_reset_init(addr); break;
|
|
case CMD_QUERY_DIP: handle_query_dip(addr); break;
|
|
default:
|
|
PRINT("RS485: unknown cmd 0x%02X\n", stx);
|
|
break;
|
|
}
|
|
|
|
_rx_len = 0;
|
|
}
|
|
|
|
/* 主循环调用 — 帧超时检测 + 周期心跳 */
|
|
void rs485_poll(void)
|
|
{
|
|
/* 帧超时: 3.5 字符 ≈ 4ms @9600, 10ms 足够 */
|
|
if (_rx_len > 0 && mstick() - _rx_last_tick > 10) {
|
|
rs485_process_frame();
|
|
}
|
|
|
|
/* 心跳上报 */
|
|
extern uint8_t loop1_LOOP_OK;
|
|
uint32_t now = mstick();
|
|
uint8_t car_state = g_loop_acs_info.car_state;
|
|
uint8_t loop_state = loop1_LOOP_OK; // 1=正常, 0=断开
|
|
uint32_t interval;
|
|
|
|
/* 事件触发: car_state 翻转沿 → 立即上报 */
|
|
if (car_state != _last_car_state) {
|
|
_last_car_state = car_state;
|
|
rs485_send_heartbeat();
|
|
_hb_last_ms = now;
|
|
return;
|
|
}
|
|
/* 线圈状态变化 → 立即上报 */
|
|
if (loop_state != _last_loop_state) {
|
|
_last_loop_state = loop_state;
|
|
rs485_send_heartbeat();
|
|
_hb_last_ms = now;
|
|
return;
|
|
}
|
|
|
|
/* 周期心跳 */
|
|
interval = (!loop_state) ? RS485_FAULT_MS : RS485_HEARTBEAT_MS;
|
|
if (now - _hb_last_ms >= interval) {
|
|
rs485_send_heartbeat();
|
|
_hb_last_ms = now;
|
|
}
|
|
}
|
|
|
|
/* 强制立即上报 (由外部事件触发) */
|
|
void rs485_event_notify(void)
|
|
{
|
|
_last_car_state = (_last_car_state ^ 1); // 强制触发
|
|
}
|
|
|
|
void rs485_loop_notify(void)
|
|
{
|
|
_last_loop_state = (_last_loop_state ^ 1); // 强制触发
|
|
}
|
|
|
|
void rs485_reset_heartbeat(void)
|
|
{
|
|
_hb_need_init = 1;
|
|
_hb_last_ms = 0; // 立即触发首次心跳
|
|
}
|