1. 新增SINK_CMD_SET_DPDM_CAL命令定义及对应寄存器 2. 新增校准参数结构体及相关任务提交、查询接口 3. 修改北向帧校验逻辑适配校准写入的6字节长度要求 4. 更新协议板版本至V1.1.56,寄存器版本号更新为0x0148 5. 修改Sink电压稳定确认参数,优化5V电压确认计数 6. 新增校准参数配置比较函数,独立处理校准写入事务 7. 更新修改日志,记录本次版本新增功能详情
920 lines
30 KiB
C
920 lines
30 KiB
C
/****************************************************************************
|
||
文件名称 : chrg_north.c
|
||
完成日期 :
|
||
主要功能 : 实现北向通信交互,包括接收命令解析,发送请求命令,切换通路。以独立线程处理。
|
||
版本历史 : 创建原始版本
|
||
说明 :
|
||
******************************************************************************/
|
||
|
||
#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_VOLT_STABLE_ERROR_MV 2000U // Sink实际电压与协议目标电压的最大允许误差
|
||
/* Sink电压稳定确认参数已迁移至chrg_sink.h,供chrg_sink_volt_judge使用。 */
|
||
|
||
/*****************************************************************
|
||
函数名称: 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_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_GET_SOURCE_VC;
|
||
}
|
||
}
|
||
|
||
/*****************************************************************
|
||
函数名称: chrg_sink_parse
|
||
函数描述: 解析北向负载板0x20回码,提取实际电压和D+/D-电压
|
||
输入参数: frame_data:完整北向回码 idx:当前通道 pSW:通道控制结构体
|
||
pNOR:北向总控结构体
|
||
输出参数: 更新pSW->Sink_voltage、VZ(D+)、VF(D-)
|
||
返回说明: -
|
||
其它说明: Sink电压状态机可通过SINK_VON_VOLTAGE_SOURCE选择使用本回码
|
||
实际电压或功率板实际电压
|
||
*****************************************************************/
|
||
void chrg_sink_parse(rt_uint8_t *frame_data, eIDX_SOU_CH idx,
|
||
struct chrg_switch_t *pSW, struct chrg_north_t *pNOR)
|
||
{
|
||
if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) {
|
||
return;
|
||
}
|
||
|
||
pSW->Sink_voltage = (rt_uint16_t)(((frame_data[4] << 8) |
|
||
frame_data[5]) * 10U);
|
||
pSW->Sink_voltage_seq++;
|
||
pSW->VZ = ((frame_data[14] << 8) | frame_data[15]);
|
||
pSW->VF = ((frame_data[16] << 8) | frame_data[17]);
|
||
|
||
//rt_kprintf("VZ=%d,VF=%d\n",pSW->VZ,pSW->VF);
|
||
|
||
#if NORTH_SINK_DEBUG
|
||
LOG_D("Sink[%d] real volt: %d mV", idx,
|
||
((frame_data[4] << 8) | frame_data[5]) * 10);
|
||
#endif
|
||
(void)pNOR;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
#define NORTH_SWITCH_SETTLE_MS 2U
|
||
#define NORTH_RX_RESET_TIMEOUT_MS 30U
|
||
|
||
/*
|
||
* 开始一次北向请求:切换物理通道后先让接收线程清理旧事务,
|
||
* 再为当前通道生成新的内部事务号,避免旧通道尾帧被误配给新请求。
|
||
* 事务号和清理序号都不进入北向协议帧,只用于协议板内部并发隔离。
|
||
*/
|
||
rt_uint32_t chrg_north_request_begin(eIDX_NOR_CH ch)
|
||
{
|
||
struct chrg_north_t *pNOR = &chrgnorth;
|
||
rt_uint32_t reset_request;
|
||
rt_tick_t start_tick;
|
||
|
||
if ((ch >= TOTAL_NOR_CHS) || (pNOR->tty == RT_NULL)) {
|
||
return 0U;
|
||
}
|
||
pNOR->idx = ch;
|
||
/* MUX切换稳定后再请求接收线程清理,避免旧通道尾包残留到新请求。 */
|
||
rt_thread_mdelay(NORTH_SWITCH_SETTLE_MS);
|
||
pNOR->transaction_id[ch]++;
|
||
if (pNOR->transaction_id[ch] == 0U) {
|
||
pNOR->transaction_id[ch] = 1U;
|
||
}
|
||
/* 请求号采用非零序号;回绕时跳过0,便于区分未初始化状态。 */
|
||
reset_request = pNOR->rx_reset_request + 1U;
|
||
if (reset_request == 0U) {
|
||
reset_request = 1U;
|
||
}
|
||
pNOR->rx_reset_request = reset_request;
|
||
/* 接收线程观察到请求号变化后执行实际清理,发送线程只负责发起请求。 */
|
||
(void)chrg_tty_break_recv(pNOR->tty);
|
||
start_tick = rt_tick_get();
|
||
while (pNOR->rx_reset_ack != reset_request) {
|
||
if ((rt_tick_t)(rt_tick_get() - start_tick) >=
|
||
rt_tick_from_millisecond(NORTH_RX_RESET_TIMEOUT_MS)) {
|
||
LOG_W("north rx reset timeout: ch=%d", ch + 1U);
|
||
return 0U;
|
||
}
|
||
rt_thread_mdelay(1);
|
||
}
|
||
return pNOR->transaction_id[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_RESET:
|
||
case SINK_CMD_SET_DPDM_CAL:
|
||
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 (command == SINK_CMD_SET_DPDM_CAL) ?
|
||
((length == 6U) ? RT_TRUE : RT_FALSE) :
|
||
((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->channel = (rt_uint8_t)idx;
|
||
pFrame->pMB->transaction_id = chrgnorth.transaction_id[idx];
|
||
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;
|
||
}
|
||
|
||
static void chrg_north_rx_reset_request_process(struct chrg_north_t *pNOR,
|
||
struct chrg_tty_t *pTTY, eIDX_NOR_CH *pFrameIdx)
|
||
{
|
||
rt_uint8_t discard[32];
|
||
|
||
if ((pNOR == RT_NULL) || (pTTY == RT_NULL) ||
|
||
(pFrameIdx == RT_NULL) ||
|
||
(pNOR->rx_reset_ack == pNOR->rx_reset_request)) {
|
||
return;
|
||
}
|
||
/*
|
||
* 只由北向接收线程执行:在同一上下文中复位环形缓冲区、未完成帧,
|
||
* 并排空UART驱动FIFO,避免与接收操作并发访问这些状态。
|
||
*/
|
||
rt_ringbuffer_reset(pNOR->rb);
|
||
chrg_north_frame_reset(&pNOR->frame, RT_TRUE);
|
||
while (rt_device_read(pTTY->dev, 0, discard, sizeof(discard)) > 0) {
|
||
}
|
||
*pFrameIdx = pNOR->idx;
|
||
pNOR->rx_reset_ack = pNOR->rx_reset_request;
|
||
}
|
||
|
||
/*****************************************************************
|
||
函数名称: 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->rx_reset_ack != pNOR->rx_reset_request) {
|
||
chrg_north_rx_reset_request_process(pNOR, pTTY, &frame_idx);
|
||
continue;
|
||
}
|
||
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 (pNOR->rx_reset_ack != pNOR->rx_reset_request) {
|
||
/* 切换请求到达期间读出的字节属于旧事务,整批丢弃。 */
|
||
chrg_north_rx_reset_request_process(pNOR, pTTY, &frame_idx);
|
||
continue;
|
||
}
|
||
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;
|
||
continue;
|
||
}
|
||
|
||
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
|
||
|