Files
chrg/apps/chrg/applications/thread/chrg_north.c
T
wrh 792e201d1e 一、可以读取版本号
- 新增QMP4810300-4多协议双向电源通信协议V1.2文档
- 多个源文件头文件版本号更新至V1.1.8
- 在chrg_comm.c中添加Modbus固件版本只读寄存器
- 新增测试日志与修改日志V1.1.8版本记录
- 修复PD协议切换逻辑问题,新增复位机制
2026-08-24 18:06:10 +08:00

934 lines
31 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.
/****************************************************************************
文件名称 : chrg_north.c
完成日期 :
当前版本号 : V1.1.8
主要功能 : 实现北向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。
版本历史 : 创建原始版本
说明 :
******************************************************************************/
#include <string.h>
#include <stdlib.h>
#include <rtthread.h>
#include "chrg_eload.h"
#include "chrg_gpio.h"
#include "chrg_thread.h"
#include "chrg_north_pkg.h"
#include "chrg_north.h"
#include "chrg_roll_nor.h"
#include "chrg_switch.h"
#include "chrg_utils.h"
#include "ulog.h"
#include "chrg_rel.h"
//#define LOG_TAG "chrg.nor"
#define DBG_LEVEL DBG_LOG
#include <rtdbg.h>
#define SOURCE_VOLT_ERROR_MV 200U // Source目标电压允许误差,单位mV
#define SOURCE_VOLT_RETRY_FRAMES 3U // Source电压持续超差后的重发确认帧数
#define SOURCE_QC_MIN_VOLT_MV 5000U // QC2.0/QC3.0允许使用的最低目标电压
#define SOURCE_QC_SWITCH_ERROR 0x04U // Source协议或CC切换失败错误位
#define SINK_CURRENT_START_GRACE_MS 200U // Sink启动后暂不判断无电流的保护时间
#define SINK_VOLT_STABLE_ERROR_MV 2000U // Sink实际电压与协议目标电压的最大允许误差
#define SINK_VOLT_5V_TARGET_MV 5000U // VSafe5V目标电压,需延长稳定确认时间
#define SINK_VOLT_CONFIRM_NORMAL_COUNT 5U // 非5V目标连续有效电压确认帧数
#define SINK_VOLT_CONFIRM_5V_COUNT 15U // 5V目标连续有效电压确认帧数,避免把握手阶段VSafe5V误判为协议稳定
/*****************************************************************
函数名称: chrg_source_apply_protocol_voltage_rule
函数描述: 根据电源协议类型修正北向解析得到的目标电压
输入参数: pSW:当前通道控制结构体 volt_mv:北向解析电压(mV)
输出参数: -
返回说明: 应写入Source状态机的目标电压(mV)
其它说明: QC2.0/QC3.0在完成升压协商前必须保持默认5V,因此其北向
回码低于5000mV时统一按5000mV处理;其它协议保持原值
*****************************************************************/
static rt_uint16_t chrg_source_apply_protocol_voltage_rule(
const struct chrg_switch_t *pSW, rt_uint16_t volt_mv)
{
if (pSW == RT_NULL) {
return volt_mv;
}
if (((pSW->source.protocol == PRO_QC20) ||
(pSW->source.protocol == PRO_QC30)) &&
(volt_mv < SOURCE_QC_MIN_VOLT_MV)) {
return SOURCE_QC_MIN_VOLT_MV;
}
return volt_mv;
}
/*****************************************************************
函数名称: chrg_sink_voltage_is_stable
函数描述: 判断北向返回的Sink实际电压是否已进入协议目标电压允许范围
输入参数: actual_volt_mv:北向返回的实际电压(mV)
target_volt_mv:当前协议申请的目标电压(mV)
输出参数: -
返回说明: RT_TRUE:实际电压与目标电压差值不大于允许误差
RT_FALSE:实际电压尚未到达目标范围或仍保留旧协议电压
其它说明: 使用差值的绝对值比较,避免只判断下限时将20V残压误判为5V已稳定
*****************************************************************/
static rt_bool_t chrg_sink_voltage_is_stable(rt_uint16_t actual_volt_mv,
rt_uint16_t target_volt_mv)
{
rt_uint16_t voltage_error_mv;
if (actual_volt_mv >= target_volt_mv) {
voltage_error_mv = actual_volt_mv - target_volt_mv;
} else {
voltage_error_mv = target_volt_mv - actual_volt_mv;
}
return (voltage_error_mv <= SINK_VOLT_STABLE_ERROR_MV) ?
RT_TRUE : RT_FALSE;
}
/*****************************************************************
函数名称: chrg_sink_voltage_confirm_count
函数描述: 根据Sink协议目标电压选择进入RUNING前所需的连续稳定帧数
输入参数: target_volt_mv:当前协议申请的目标电压,单位mV
输出参数: -
返回说明: 5V目标返回延长确认帧数,其它目标返回普通确认帧数
其它说明: 快充协议切换时总线会先经过VSafe5V;当目标本身也是5V时,
单凭电压值无法区分过渡阶段和协议稳定阶段,因此增加确认时间,
防止电子负载提前启动后导致协议板复位
*****************************************************************/
static rt_uint8_t chrg_sink_voltage_confirm_count(rt_uint16_t target_volt_mv)
{
return (target_volt_mv == SINK_VOLT_5V_TARGET_MV) ?
SINK_VOLT_CONFIRM_5V_COUNT : SINK_VOLT_CONFIRM_NORMAL_COUNT;
}
/*****************************************************************
函数名称: chrg_source_voltage_control
函数描述: 根据目标电压与实际电压统一控制Source电压下发和重试
输入参数: idx:当前通道 pSW:通道控制结构体 volt_mv:目标电压(mV)
curr_ma:目标电流(mA)
输出参数: 更新source.set_volt/set_curr、work_mode和电压重试计数
返回说明: 0:状态处理成功 <0:参数错误或命令提交失败
其它说明: set_volt同时作为上次目标,停止时会清零;目标变化时立即下发,
目标不变但实际误差连续3帧超过200mV时重发
*****************************************************************/
static int chrg_source_voltage_control(eIDX_SOU_CH idx,
struct chrg_switch_t *pSW, rt_uint16_t volt_mv, rt_uint16_t curr_ma)
{
static rt_uint8_t volt_retry_count[TOTAL_SOU_CHS] = {0};
rt_uint16_t volt_error;
rt_uint16_t old_volt_mv;
rt_uint16_t old_curr_ma;
rt_bool_t need_set = RT_FALSE;
int ret;
if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) {
return -1;
}
old_volt_mv = pSW->source.set_volt;
old_curr_ma = pSW->source.set_curr;
pSW->source.set_volt = volt_mv;
pSW->source.set_curr = curr_ma;
if (!pSW->source.On_Flag) {
return 0;
}
volt_error = (pSW->Now_voltage >= volt_mv) ?
(pSW->Now_voltage - volt_mv) :
(volt_mv - pSW->Now_voltage);
if (volt_mv != old_volt_mv) {
/* 新目标与set_volt旧值不同,需要立即写入功率板。 */
need_set = RT_TRUE;
} else if (volt_error <= SOURCE_VOLT_ERROR_MV) {
volt_retry_count[idx] = 0;
} else {
if (volt_retry_count[idx] < SOURCE_VOLT_RETRY_FRAMES) {
volt_retry_count[idx]++;
}
if (volt_retry_count[idx] >= SOURCE_VOLT_RETRY_FRAMES) {
need_set = RT_TRUE;
}
}
pSW->source.work_mode = need_set ?
SOURCE_WORK_RUNING : SOURCE_WORK_WAIT;
ret = chrg_source_work_state(idx, pSW);
if ((ret == 0) && need_set) {
volt_retry_count[idx] = 0;
} else if ((ret != 0) && need_set) {
/* 入队失败时恢复旧目标,使下一帧仍能立即识别为目标变化。 */
pSW->source.set_volt = old_volt_mv;
pSW->source.set_curr = old_curr_ma;
}
return ret;
}
/*****************************************************************
函数名称: chrg_source_parse
函数描述: 按电源协议类型处理目标电压并驱动Source状态机
输入参数: frame_data:完整北向回码 idx:当前通道 pSW:通道控制结构体
pNOR:北向总控结构体
输出参数: 更新电压、电流、source.work_mode及电压重试计数
返回说明: 0:回码正常 <0:参数错误、切换错误或功率板命令提交失败
其它说明: 标准协议使用COM配置目标参与实际电压校验;快充协议读取
frame_data[8~11]中的协商目标值;frame_data[14]的bit2表示
Source板协议/CC切换失败,错误回码不得驱动功率板输出
*****************************************************************/
int chrg_source_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx,
struct chrg_switch_t *pSW, struct chrg_north_t *pNOR)
{
rt_uint16_t volt_mv;
rt_uint16_t curr_ma;
(void)pNOR;
if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL) ||
(frame_data == RT_NULL)) {
return -1;
}
pSW->source.qc_err = frame_data[14];
if ((pSW->source.qc_err & SOURCE_QC_SWITCH_ERROR) != 0U) {
return -2;
}
/* 标准协议不使用北向帧内目标值,但仍进入统一误差判断,确保INIT或
* COM下发的目标电压没有生效时能够自动重试。 */
if (pSW->source.protocol == PRO_NONE) {
return chrg_source_voltage_control(idx, pSW,
pSW->source.pd_set.mm.max_vol,
pSW->source.pd_set.mm.max_cur);
}
if (!pSW->source.On_Flag) {
return 0;
}
volt_mv = (frame_data[8] << 8) | frame_data[9];
curr_ma = (frame_data[10] << 8) | frame_data[11];
// /* 特殊协议规则只修正运行目标,不覆盖北向原始回码和COM协议配置。 */
// volt_mv = chrg_source_apply_protocol_voltage_rule(pSW, volt_mv);
return chrg_source_voltage_control(idx, pSW, volt_mv, curr_ma);
}
/*****************************************************************
函数名称: chrg_source_setting
函数描述: 根据电源协议类型和启停标志选择标准输出或快充解析流程
输入参数: frame_data:北向回码 idx:当前通道 pSW:通道控制结构体
pNOR:北向总控结构体
输出参数: 可能更新功率板设定值或北向子状态sub
返回说明: -
其它说明: 标准协议直接设置输出,快充协议使用协商结果驱动状态机
*****************************************************************/
void chrg_source_setting(rt_uint8_t *frame_data, eIDX_SOU_CH idx,
struct chrg_switch_t *pSW, struct chrg_north_t *pNOR)
{
//执行输出调整
if (pSW->source.On_Flag == 1) {
/* 标准协议与快充协议都由解析入口选择各自目标值并统一校验。 */
(void)chrg_source_parse(frame_data, idx, pSW, pNOR);
}else if(pSW->source.On_Flag == 0){
//chrg_source_stop(frame_data,pSW,idx,&pNOR->enable[idx]);
pSW->sub = IDX_SET_SOURCE_STOP_STATE;
}
}
/*****************************************************************
函数名称: chrg_sink_parse
函数描述: 解析北向负载板回码并根据电压、电流反馈驱动Sink状态机
输入参数: frame_data:完整北向回码 idx:当前通道 pSW:通道控制结构体
pNOR:北向总控结构体
输出参数: 更新VZ、VF、低压计数、稳定电压计数、无电流计数及sink.work_mode
返回说明: -
其它说明: 电压连续低于门槛进入LOW;掉电处理恢复副继电器后,电压重新
出现时先重设协议和CC;一帧有效电压达到目标附近即进入RUNING
*****************************************************************/
void chrg_sink_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx,
struct chrg_switch_t *pSW, struct chrg_north_t *pNOR)
{
rt_uint16_t volt_mv = ((frame_data[4] << 8) | frame_data[5]) * 10;
rt_uint8_t stable_confirm_count;
static rt_uint8_t volt_low[TOTAL_SOU_CHS] = {0};
static rt_uint8_t no_curr[TOTAL_SOU_CHS] = {0};
static rt_uint8_t volt_set_ok[TOTAL_SOU_CHS] = {0};
static rt_uint8_t volt_stable_count[TOTAL_SOU_CHS] = {0};
static rt_tick_t running_start_tick[TOTAL_SOU_CHS] = {0};
if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) {
return;
}
stable_confirm_count = chrg_sink_voltage_confirm_count(
pSW->sink.pro_loadv);
pSW->VZ = ((frame_data[14] << 8) | frame_data[15]);
pSW->VF = ((frame_data[16] << 8) | frame_data[17]);
#if NORTH_SINK_DEBUG
LOG_D("Sink[%d] real volt: %d mV", idx, volt_mv);
#endif
if(pSW->sink.On_work == 1 && pSW->sink.test.test_mode == 0) {
if (volt_mv <= WORK_MIN_VOLT) { // 电压过低,禁止进入工作状态机
volt_stable_count[idx] = 0;
if (volt_set_ok[idx] == 1) {
if (++volt_low[idx] >= 50) {
volt_set_ok[idx] = 0;
no_curr[idx] = 0;
//pSW->sink.work_mode = SINK_WORK_LOW;
}
}
}
else{ // 电压稳定时执行工作状态机(继电器/工作模式切换)
volt_low[idx] = 0;
volt_set_ok[idx] = 1;
pSW->change_flag = 0;
if (pSW->sink.work_mode == SINK_WORK_LOW_MONITOR) {
/* 副继电器恢复后首次重新检测到输入电压,只启动协议重握手。
* 当前回码不计入稳定电压次数,防止跳过协议设置直接带载。 */
volt_stable_count[idx] = 0;
no_curr[idx] = 0;
//pSW->sub = IDX_SET_SINK_CC_BEFORE_PD;
pSW->sink.work_mode = SINK_WORK_WAIT;
} else if ((pSW->sink.work_mode == SINK_WORK_LOW) ||
(pSW->sink.work_mode == SINK_WORK_LOW_AUX_WAIT)) {
/* 掉电安全批次完成前,即使收到残留回码也不得进入RUNING。 */
volt_stable_count[idx] = 0;
no_curr[idx] = 0;
}
else if (chrg_sink_voltage_is_stable(volt_mv,
pSW->sink.pro_loadv)) {
if ((pSW->sink.work_mode != SINK_WORK_RUNING) &&
(pSW->sink.work_mode != SINK_WORK_LOAD_STANDBY)) {
if (volt_stable_count[idx] < stable_confirm_count) {
volt_stable_count[idx]++;
}
if (volt_stable_count[idx] >= stable_confirm_count) {
/* 回码已经过CRC、ID、命令和配置版本校验,并连续达到当前
* 目标对应的确认帧数;5V采用更长窗口过滤VSafe5V过渡阶段。 */
volt_stable_count[idx] = 0;
pSW->sink.work_mode = SINK_WORK_RUNING;
running_start_tick[idx] = rt_tick_get();
no_curr[idx] = 0;
}
} else if (pSW->sink.work_mode == SINK_WORK_LOAD_STANDBY) {
/* 协议保持在线但电子负载主动停止,不执行无电流重试。 */
volt_stable_count[idx] = 0;
no_curr[idx] = 0;
} else if ((pSW->Now_current > 200) ||
(pSW->sink.loadc < 200)) {
volt_stable_count[idx] = 0;
no_curr[idx] = 0;
} else if ((rt_tick_t)(rt_tick_get() - running_start_tick[idx]) >=
rt_tick_from_millisecond(SINK_CURRENT_START_GRACE_MS)) {
volt_stable_count[idx] = 0;
if (++no_curr[idx] >= 2) {
pSW->sink.work_mode = SINK_WORK_WAIT;
no_curr[idx] = 0;
}
}
} else {
volt_stable_count[idx] = 0;
no_curr[idx] = 0;
}
}
}
else{
volt_low[idx] = 0;
no_curr[idx] = 0;
volt_set_ok[idx] = 0;
volt_stable_count[idx] = 0;
}
chrg_sink_work(idx,pSW);
}
struct chrg_north_t chrgnorth = {
.devname = DEV_NAME_NORTH, // 设备名
.tty = RT_NULL, // 串口句柄初始为空
.enable = {0, 0, 0, 0}, // 4个通道使能初始关闭
.free_flag = 1,
// 通道1(IDX_NOR_CH1)初始化
.sw[IDX_NOR_CH1] = {
.CV_mode = 2,
.change_flag = 0,
.show_step = 0,
.sub = IDX_GET_SINK_VC,
.id = ID_SINK, // 初始为负载模式
.source = { // 电源侧参数初始化
.On_Flag = 0, // 启动标志初始关闭
.work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT
}, // 关键:补充逗号分隔source和sink
.sink = {
.protocol = 0xFE,
.work_mode= SINK_WORK_WAIT,
.test.test_mode_old = 0,
}
},
// 通道2(IDX_NOR_CH2)初始化
.sw[IDX_NOR_CH2] = {
.show_step = 0,
.id = ID_SINK,
.CV_mode = 2,
.change_flag = 0,
.source = {
.On_Flag = 0,
.work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT
},
.sink = {
.protocol = 0xFE,
.work_mode= SINK_WORK_WAIT,
.test.test_mode_old = 0,
}
},
// 通道3(IDX_NOR_CH3)初始化
.sw[IDX_NOR_CH3] = {
.show_step = 0,
.CV_mode = 2,
.id = ID_SINK,
.change_flag = 0,
.source = {
.On_Flag = 0,
.work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT
},
.sink = {
.protocol = 0xFE,
.work_mode= SINK_WORK_WAIT,
.test.test_mode_old = 0,
}
},
// 通道4(IDX_NOR_CH4)初始化
.sw[IDX_NOR_CH4] = {
.show_step = 0,
.CV_mode = 2,
.id = ID_SINK,
.change_flag = 0,
.source = {
.On_Flag = 0,
.work_mode = SOURCE_WORK_WAIT, // 上电保持等待,禁止自动执行Source INIT
},
.sink = {
.protocol = 0xFE,
.work_mode= SINK_WORK_WAIT,
.test.test_mode_old = 0,
}
}
};
/*****************************************************************
函数名称: chrg_north_switch
函数描述: 北向通道选择
输入参数: ch: 北向通道
输出参数: -
返回说明: <0: 错误
其它说明: -
*****************************************************************/
int chrg_north_switch (eIDX_NOR_CH ch)
{
if (ch >= TOTAL_NOR_CHS) {
return -1;
}
return chrg_switch_ch(chrgios[IDX_GPO_NOR_SW_A].base,
chrgios[IDX_GPO_NOR_SW_B].base, (eIDX_CH)ch);
}
/*****************************************************************
函数名称: chrg_north_send
函数描述: 北向通道数据发送
输入参数: *data: 数据 length: 数据长度
输出参数: -
返回说明: <0: 错误 >0:发送数据长度
其它说明: -
*****************************************************************/
int chrg_north_send (rt_uint8_t *data, rt_size_t length)
{
rt_ssize_t ret = -1;
struct chrg_tty_t *pTTY = chrgnorth.tty;
if (RT_NULL != pTTY) {
#if NORTH_FRAME_DEBUG
LOG_HEX("sendN", 32, data, length);
#endif
ret = chrg_tty_send(pTTY, data, length);
}
return ret;
}
/*****************************************************************
函数名称: chrg_north_frame_shape_valid
函数描述: 检查北向回码命令和长度组合是否符合协议定义
输入参数: id:角色 length:帧长度 command:回码命令
输出参数: -
返回说明: RT_TRUE:组合合法 RT_FALSE:组合非法
其它说明: 0x20量测回码固定21字节,设置命令回码固定5字节
*****************************************************************/
static rt_bool_t chrg_north_frame_shape_valid(eIDX_ID id,
rt_uint8_t length, rt_uint8_t command)
{
if (command == SRC_CMD_GET_VOLTAGE) {
return (length == 21U) ? RT_TRUE : RT_FALSE;
}
if (command == SRC_CMD_GET_PROTOCOL) {
return (length >= MIN_FRAME_LENGTH) ? RT_TRUE : RT_FALSE;
}
if (id == ID_SOURCE) {
if ((command == SRC_CMD_SET_CC_LEVEL) ||
(command == SRC_CMD_SET_CC_CHOOSE) ||
(command == SRC_CMD_RESET) ||
(command == SRC_CMD_SET_PROTOCOL)) {
return (length == MIN_FRAME_LENGTH) ? RT_TRUE : RT_FALSE;
}
return RT_FALSE;
}
if (id == ID_SINK) {
switch (command) {
case SINK_CMD_SET_CC:
case SINK_CMD_SET_LEVEL:
case SINK_CMD_SET_QC20:
case SINK_CMD_SET_QC30:
case SINK_CMD_SET_FCP:
case SINK_CMD_SET_AFC:
case SINK_CMD_SET_SCP:
case SINK_CMD_SET_VIVO:
case SINK_CMD_SET_TFC:
case SINK_CMD_SET_PD0:
case SINK_CMD_SET_PPS:
case SINK_CMD_SET_PD3P1:
case SINK_CMD_SET_UFCS:
case SINK_CMD_SET_VOOC:
case SINK_CMD_SET_AVS:
case LOAD_PRO_NONE:
return (length == MIN_FRAME_LENGTH) ? RT_TRUE : RT_FALSE;
default:
return RT_FALSE;
}
}
return RT_FALSE;
}
/*****************************************************************
函数名称: chrg_north_ringbuffer_byte
函数描述: 不移动读指针地读取北向环形缓冲区指定偏移字节
输入参数: rb:环形缓冲区 offset:相对当前读指针偏移 data:输出字节
输出参数: data返回读取到的字节
返回说明: 0:成功 <0:参数或偏移错误
其它说明: 仅由北向接收线程调用,不与其它线程并发访问该环形缓冲区
*****************************************************************/
static int chrg_north_ringbuffer_byte(const struct rt_ringbuffer *rb,
rt_size_t offset, rt_uint8_t *data)
{
rt_size_t index;
if ((rb == RT_NULL) || (data == RT_NULL) ||
(offset >= rt_ringbuffer_data_len((struct rt_ringbuffer *)rb))) {
return -1;
}
index = rb->read_index + offset;
if (index >= rb->buffer_size) {
index -= rb->buffer_size;
}
*data = rb->buffer_ptr[index];
return 0;
}
/*****************************************************************
函数名称: chrg_north_ringbuffer_discard
函数描述: 从北向环形缓冲区丢弃指定数量字节
输入参数: rb:环形缓冲区 length:需要丢弃的字节数
输出参数: -
返回说明: -
其它说明: 用于移除帧头前噪声,同时保留末尾可能未接收完整的帧头
*****************************************************************/
static void chrg_north_ringbuffer_discard(struct rt_ringbuffer *rb,
rt_size_t length)
{
rt_uint8_t data;
while ((length > 0U) && (rt_ringbuffer_getchar(rb, &data) != 0U)) {
length--;
}
}
/*****************************************************************
函数名称: chrg_north_frame_head
函数描述: 在北向字节流中查找并提取合法四字节帧头
输入参数: rb:环形缓冲区 head:帧头输出区 id:当前通道角色
输出参数: head返回长度、固定字节、角色和命令
返回说明: >0:完整帧长度 0:暂未找到完整帧头 <0:参数或读取错误
其它说明: 未找到时保留末尾3字节,支持帧头跨多次UART接收
*****************************************************************/
static int chrg_north_frame_head(struct rt_ringbuffer *rb,
rt_uint8_t *head, eIDX_ID id)
{
rt_size_t i;
rt_size_t j;
rt_size_t len_rb;
rt_uint8_t length;
rt_uint8_t reserved;
rt_uint8_t frame_id;
rt_uint8_t command;
if ((rb == RT_NULL) || (head == RT_NULL)) {
return -1;
}
len_rb = rt_ringbuffer_data_len(rb);
if (len_rb < LEN_FRAME_HEAD) {
return 0;
}
/* 使用非破坏读取逐字节滑动,查找完整的4字节候选帧头。 */
for (i = 0; (i + LEN_FRAME_HEAD) <= len_rb; i++) {
if ((chrg_north_ringbuffer_byte(rb, i, &length) != 0) ||
(chrg_north_ringbuffer_byte(rb, i + 1U, &reserved) != 0) ||
(chrg_north_ringbuffer_byte(rb, i + 2U, &frame_id) != 0) ||
(chrg_north_ringbuffer_byte(rb, i + 3U, &command) != 0)) {
break;
}
/* 长度、固定字节、角色或命令不匹配时继续搜索下一个候选位置。 */
if ((reserved != 0x00U) || (frame_id != (rt_uint8_t)id) ||
(length < MIN_FRAME_LENGTH) ||
!chrg_north_frame_shape_valid(id, length, command)) {
continue;
}
/* 找到合法帧头后,仅丢弃它前面的噪声,再正式取出4字节帧头。 */
chrg_north_ringbuffer_discard(rb, i);
for (j = 0; j < LEN_FRAME_HEAD; j++) {
if (rt_ringbuffer_getchar(rb, &head[j]) == 0U) {
return -1;
}
}
return length;
}
/* 保留末尾可能尚未接收完整的4字节帧头,等待下一批UART数据续接。 */
if (len_rb > (LEN_FRAME_HEAD - 1U)) {
chrg_north_ringbuffer_discard(rb,
len_rb - (LEN_FRAME_HEAD - 1U));
}
return 0;
}
/*****************************************************************
函数名称: chrg_north_pickup_frame
函数描述: 北向通道数据帧提取
输入参数: *rb: 缓冲区 *pFrame: 数据帧 idx:通道
输出参数: -
返回说明: <0: 错误 >0:帧长度
其它说明: -
*****************************************************************/
int a_number;
#define NORTH_FRAME_TIMEOUT_MS 100
/*****************************************************************
函数名称: chrg_north_frame_reset
函数描述: 复位北向帧接收状态并按需释放未完成帧内存
输入参数: pFrame:帧接收上下文 release:是否释放pMB及payload
输出参数: 清零接收长度、帧头标志、超时计时和消息指针
返回说明: -
其它说明: 完整帧已移交邮箱时release应为RT_FALSE,异常中止时为RT_TRUE
*****************************************************************/
static void chrg_north_frame_reset(struct pickup_info_t *pFrame, rt_bool_t release)
{
if (RT_NULL == pFrame) {
return;
}
if (release && (RT_NULL != pFrame->pMB)) {
if (RT_NULL != pFrame->pMB->payload) {
rt_free(pFrame->pMB->payload);
}
rt_free(pFrame->pMB);
}
pFrame->last_tick = 0;
pFrame->length = 0;
pFrame->findhead = 0;
pFrame->len_msg = 0;
pFrame->pMB = RT_NULL;
}
/*****************************************************************
函数名称: chrg_north_frame_append
函数描述: 从北向环形缓冲区追加数据到当前未完成帧
输入参数: rb:北向接收环形缓冲区 pFrame:帧接收上下文
输出参数: 更新已接收长度和最后接收时间
返回说明: 1:完整帧接收完成 0:仍需等待数据 <0:参数或状态异常
其它说明: 单次仅复制当前帧剩余长度,避免读取下一帧数据
*****************************************************************/
static int chrg_north_frame_append(struct rt_ringbuffer *rb,
struct pickup_info_t *pFrame)
{
rt_size_t remaining;
rt_size_t copied;
if ((RT_NULL == rb)||(RT_NULL == pFrame)||(RT_NULL == pFrame->pMB)||
(RT_NULL == pFrame->pMB->payload)||(pFrame->len_msg <= 0)||
(pFrame->length > (rt_size_t)pFrame->len_msg)) {
return -1;
}
remaining = (rt_size_t)pFrame->len_msg - pFrame->length;
if (0 == remaining) {
return 1;
}
copied = rt_ringbuffer_data_len(rb);
if (copied > remaining) {
copied = remaining;
}
if (copied > 0) {
copied = rt_ringbuffer_get(rb,
&pFrame->pMB->payload[pFrame->length], copied);
pFrame->length += copied;
pFrame->last_tick = rt_tick_get();
}
return (pFrame->length == (rt_size_t)pFrame->len_msg) ? 1 : 0;
}
/*****************************************************************
函数名称: chrg_north_frame_dispatch
函数描述: 校验并分发已经组装完成的北向数据帧
输入参数: pFrame:包含完整pMB消息的帧接收上下文
输出参数: 合法完整帧发送到北向轮询邮箱
返回说明: -
其它说明: 本函数只负责帧结构和CRC校验,不执行Source/Sink业务解析;
校验失败时释放消息,校验成功后将消息所有权移交北向轮询线程
*****************************************************************/
static void chrg_north_frame_dispatch(struct pickup_info_t *pFrame)
{
struct mb_msg_t *pMB;
rt_uint8_t crc_calc;
rt_uint8_t crc_recv;
if (pFrame == RT_NULL) {
return;
}
pMB = pFrame->pMB;
if ((pMB == RT_NULL) || (pMB->payload == RT_NULL) ||
(pMB->length < MIN_FRAME_LENGTH) ||
(pMB->length > 0xFFU) ||
(pMB->payload[0] != (rt_uint8_t)pMB->length) ||
(pMB->payload[1] != 0x00U)) {
LOG_E("invalid north frame: len=%d", pMB ? pMB->length : 0);
chrg_north_frame_reset(pFrame, RT_TRUE);
return;
}
crc_calc = calc_crc8(pMB->payload, (rt_uint8_t)(pMB->length - 1U));
crc_recv = pMB->payload[pMB->length - 1U];
if (crc_calc != crc_recv) {
LOG_E("north crc failed: len=%d calc=%02x recv=%02x",
pMB->length, crc_calc, crc_recv);
chrg_north_frame_reset(pFrame, RT_TRUE);
return;
}
chrg_north_frame_reset(pFrame, RT_FALSE);
(void)chrg_thread_mb_send(IDX_THR_NORTH, pMB);
}
static int chrg_north_pickup_frame (struct rt_ringbuffer *rb,
struct pickup_info_t *pFrame, eIDX_NOR_CH idx)
{
int cnt = 0;
int status = 0;
rt_size_t len_rb = 0;
if ((RT_NULL == rb)||(RT_NULL == pFrame)) {
return -1;
}
struct chrg_switch_t *pSW = &chrgnorth.sw[idx];
//rt_kprintf("north_idx=%d\r\n",idx);
do {
if (0 == pFrame->findhead) {
//pFrame->len_msg = chrg_north_frame_head(pSW->id,rb, pFrame->head, pSW->id);
pFrame->len_msg = chrg_north_frame_head(rb, pFrame->head, pSW->id);
// rt_kprintf("msg=:%d",pFrame->len_msg);
if (pFrame->len_msg > 0) {
if ((pFrame->len_msg < LEN_FRAME_HEAD)||
(pFrame->len_msg >= SIZE_BUF_TTY)) {
LOG_E("invalid msg length: %d", pFrame->len_msg);
chrg_north_frame_reset(pFrame, RT_TRUE);
return -3;
}
pFrame->pMB = (struct mb_msg_t *)rt_malloc(sizeof(struct mb_msg_t));
if (RT_NULL == pFrame->pMB) {
LOG_E("malloc mb failed.");
chrg_north_frame_reset(pFrame, RT_FALSE);
return -2;
}
pFrame->pMB->length = pFrame->len_msg;
pFrame->pMB->payload = RT_NULL;
pFrame->pMB->payload = (rt_uint8_t *)rt_malloc(pFrame->len_msg);
if (RT_NULL == pFrame->pMB->payload) {
LOG_E("malloc buf failed.");
chrg_north_frame_reset(pFrame, RT_TRUE);
return -4;
}
rt_memcpy(pFrame->pMB->payload, pFrame->head, LEN_FRAME_HEAD);
pFrame->length = LEN_FRAME_HEAD;
pFrame->findhead = 1;
pFrame->last_tick = rt_tick_get();
status = chrg_north_frame_append(rb, pFrame);
if (status > 0) {
chrg_north_frame_dispatch(pFrame);
cnt++;
} else if (status < 0) {
chrg_north_frame_reset(pFrame, RT_TRUE);
return -5;
} else {
break;
}
}
} else {
status = chrg_north_frame_append(rb, pFrame);
if (status > 0) {
chrg_north_frame_dispatch(pFrame);
cnt++;
} else if (status < 0) {
chrg_north_frame_reset(pFrame, RT_TRUE);
return -5;
} else {
break;
}
}
len_rb = rt_ringbuffer_data_len(rb);
} while (len_rb >= LEN_FRAME_HEAD);
return cnt;
}
/*****************************************************************
函数名称: chrg_north_thread_entry
函数描述: 北向通道数据接收线程体
输入参数: *data: 本线程信息
输出参数: -
返回说明: -
其它说明: -
*****************************************************************/
void chrg_north_thread_entry (void *data)
{
struct chrg_thread_t *pTHR = (struct chrg_thread_t *)data;
if (RT_NULL == pTHR) {
return ;
}
int len = 0;
rt_size_t put_len = 0;
eIDX_NOR_CH frame_idx = IDX_NOR_CH1; // 当前未完成帧所属的物理通道
struct chrg_tty_t *pTTY = RT_NULL;
struct chrg_north_t *pNOR = &chrgnorth;
//北向协议板线程体通道初始化为CH1,
chrg_north_switch(IDX_NOR_CH1);
pTTY = chrg_tty_create(pNOR->devname, 230400, 0, -1, 1);
if (RT_NULL == pTTY) {
LOG_E("tty can't create.");
return ;
}
chrg_tty_set_recv_tmo(pTTY, 10);
if (chrg_tty_connect(pTTY) != RT_EOK) {
chrg_tty_destory(pTTY);
return ;
}
pNOR->tty = pTTY;
pNOR->rb = rt_ringbuffer_create(SIZE_BUF_TTY*2);
if (RT_NULL == pNOR->rb) {
LOG_E("create rb '%s' failed.", pNOR->devname);
return ;
}
while (1) {
if (pNOR->frame.findhead &&
((rt_tick_t)(rt_tick_get() - pNOR->frame.last_tick) >=
rt_tick_from_millisecond(NORTH_FRAME_TIMEOUT_MS))) {
chrg_north_frame_reset(&pNOR->frame, RT_TRUE);
LOG_W("partial frame timeout");
}
rt_memset(pNOR->rx_buf, 0, sizeof(pNOR->rx_buf));
len = chrg_tty_recv(pTTY, pNOR->rx_buf, sizeof(pNOR->rx_buf));
if (len <= 0) {
rt_thread_mdelay(1);
continue;
}
/*
* 四个物理通道共用一个UART组帧器。切换通道后必须终止旧通道的未完成帧,
* 防止旧帧继续吞入新通道回码。本复位只在接收线程执行,避免发送线程与
* 接收线程同时操作环形缓冲区和组帧上下文。
*/
if (frame_idx != pNOR->idx) {
rt_ringbuffer_reset(pNOR->rb);
chrg_north_frame_reset(&pNOR->frame, RT_TRUE);
frame_idx = pNOR->idx;
}
put_len = rt_ringbuffer_put(pNOR->rb, pNOR->rx_buf, len);
if (put_len != (rt_size_t)len) {
LOG_E("north ringbuffer overflow: recv=%d put=%d", len, put_len);
rt_ringbuffer_reset(pNOR->rb);
chrg_north_frame_reset(&pNOR->frame, RT_TRUE);
continue;
}
#if NORTH_FRAME_DEBUG
LOG_HEX("recvN", 32, pNOR->rx_buf, len);
#endif
chrg_north_pickup_frame(pNOR->rb, &pNOR->frame, pNOR->idx);
}
}
#if 1
static int north_ch(rt_uint8_t argc, char **argv)
{
int ret = 0;
if (2 != argc) {
rt_kprintf("help : %s [0-3]\r\n", argv[0]);
return -1;
}
ret = chrg_north_switch((eIDX_NOR_CH)atoi(argv[1]));
return ret;
}
MSH_CMD_EXPORT(north_ch, north channel select);
static int north_diag(int argc, char **argv)
{
rt_uint8_t i;
struct chrg_north_t *pNOR = &chrgnorth;
struct chrg_rollnor_t *pROLL = &chrgrollnor;
rt_kprintf("roll: run=%d idx=%d ch=%d type=%d sub=%d\r\n",
pROLL->run, pROLL->idx, pROLL->ch, pROLL->type, pROLL->sub);
rt_kprintf("rx: rb_len=%d findhead=%d frame_len=%d received=%d\r\n",
pNOR->rb ? rt_ringbuffer_data_len(pNOR->rb) : 0,
pNOR->frame.findhead, pNOR->frame.len_msg, pNOR->frame.length);
for (i = 0; i < TOTAL_NOR_CHS; i++) {
rt_kprintf("ch%d: enable=%d id=%d sub=%d\r\n", i,
pNOR->enable[i], pNOR->sw[i].id, pNOR->sw[i].sub);
}
return 0;
}
MSH_CMD_EXPORT(north_diag, show north polling and frame state);
#endif