#define UART_RX_BUFFER_SIZE 1
#else
-#if defined(SUPPORT_PPP_SERVICE)
-#define UART_RX_BUFFER_SIZE 2048
-// #warning "UART_RX_BUFFER_SIZE is 2048"
-#else
-#define UART_RX_BUFFER_SIZE 56
-// #warning "UART_RX_BUFFER_SIZE is 56"
-#endif
+/**
+ * UART_RX_BUFFER SIZE 似乎只能是 56 更多没用
+ */
+#define UART_RX_BUFFER_SIZE 56
#endif
--- /dev/null
+# 串口测试工具使用说明
+
+## 主要功能
+
+### ✅ 优雅的测试停止机制
+
+**新特性:先停发送,延时后再停接收**
+
+测试结束时的执行顺序:
+1. 🛑 停止发送线程
+2. ⏱️ 等待1秒(让最后的数据有时间传输)
+3. 🛑 停止接收线程
+4. 📊 打印最终统计
+
+这样可以确保最后发送的数据包能够被完整接收,避免误报丢包。
+
+### 示例输出
+
+```bash
+$ python3 serial_test.py -t /dev/ttyUSB0 -r /dev/ttyUSB1 -d 10 -i 100 -s 64 -b 1000000
+
+======================================================================
+ 串口测试程序
+======================================================================
+发送串口: /dev/ttyUSB0
+接收串口: /dev/ttyUSB1
+波特率: 1000000
+测试时长: 10 秒
+发送间隔: 100 毫秒
+包大小: 64 字节
+======================================================================
+
+打开发送串口: /dev/ttyUSB0 @ 1000000
+打开接收串口: /dev/ttyUSB1 @ 1000000
+串口打开成功!
+
+发送线程启动 (间隔: 100.00ms, 包大小: 64字节)
+接收线程启动
+
+测试开始,运行时长: 10 秒
+按 Ctrl+C 可以提前终止测试
+
+======================================================================
+统计时间: 2025-10-09 14:23:45
+运行时长: 10.05 秒
+----------------------------------------------------------------------
+发送: 100 包, 6,400 字节, 636.82 字节/秒
+接收: 100 包, 6,400 字节, 636.80 字节/秒
+错误: 0 次 (数据不连续: 0)
+丢包率: 0.00%
+======================================================================
+
+正在停止发送... # ← 先停发送
+发送线程已停止
+等待1秒以接收剩余数据... # ← 延时1秒
+正在停止接收... # ← 再停接收
+接收线程已停止
+
+######################################################################
+# 最终统计信息 #
+######################################################################
+
+======================================================================
+统计时间: 2025-10-09 14:23:56
+运行时长: 11.08 秒
+----------------------------------------------------------------------
+发送: 100 包, 6,400 字节, 577.62 字节/秒
+接收: 100 包, 6,400 字节, 577.60 字节/秒
+错误: 0 次 (数据不连续: 0)
+丢包率: 0.00%
+======================================================================
+
+发送串口已关闭
+接收串口已关闭
+日志文件已关闭
+```
+
+## 使用场景
+
+### 1. 基本测试
+
+```bash
+python3 serial_test.py \
+ -t /dev/ttyUSB0 \
+ -r /dev/ttyUSB1 \
+ -d 60 \
+ -i 100 \
+ -s 64 \
+ -b 1000000
+```
+
+### 2. 保存日志文件
+
+```bash
+python3 serial_test.py \
+ -t /dev/ttyUSB0 \
+ -r /dev/ttyUSB1 \
+ -d 60 \
+ -i 100 \
+ -s 64 \
+ -b 1000000 \
+ --tx-log tx_data.log \
+ --rx-log rx_data.log
+```
+
+### 3. 测试完成后对比
+
+```bash
+# 运行测试
+python3 serial_test.py \
+ -t /dev/ttyUSB0 -r /dev/ttyUSB1 \
+ -d 30 -i 50 -s 128 -b 1000000 \
+ --tx-log tx.log --rx-log rx.log
+
+# 对比日志
+python3 compare_logs.py tx.log rx.log
+```
+
+## 优雅停止的优势
+
+### 问题场景:没有延时时
+```
+时间轴:
+ 0.0s: 发送最后一个包
+ 0.0s: 同时停止发送和接收线程
+ 结果: 最后一个包可能还在传输中就停止接收 ❌
+```
+
+### 改进后:有延时
+```
+时间轴:
+ 0.0s: 发送最后一个包
+ 0.0s: 停止发送线程
+ 0.1s: 数据在串口传输中...
+ 0.2s: 接收线程接收到最后的数据
+ 1.0s: 停止接收线程
+ 结果: 所有数据都被正确接收 ✅
+```
+
+## 参数说明
+
+| 参数 | 简写 | 必需 | 说明 | 示例 |
+|------|------|------|------|------|
+| `--tx-port` | `-t` | ✅ | 发送串口设备 | `/dev/ttyUSB0` |
+| `--rx-port` | `-r` | ✅ | 接收串口设备 | `/dev/ttyUSB1` |
+| `--duration` | `-d` | ✅ | 测试时长(秒) | `60` |
+| `--interval` | `-i` | ✅ | 发送间隔(毫秒) | `100` |
+| `--size` | `-s` | ✅ | 每包数据大小(字节) | `64` |
+| `--baudrate` | `-b` | ❌ | 波特率(默认115200) | `1000000` |
+| `--tx-log` | | ❌ | 发送日志文件 | `tx_data.log` |
+| `--rx-log` | | ❌ | 接收日志文件 | `rx_data.log` |
+
+## 实时统计
+
+测试运行时会:
+- ✅ 每10秒自动打印统计信息
+- ✅ 发现数据不连续时立即打印错误
+- ✅ 测试结束时打印最终统计
+- ✅ 可以按 `Ctrl+C` 提前终止测试(同样会优雅停止)
+
+## 统计信息说明
+
+```
+发送: 100 包, 6,400 字节, 636.82 字节/秒
+ └─ 包数 └─ 总字节 └─ 平均速率
+
+接收: 100 包, 6,400 字节, 636.80 字节/秒
+ └─ 包数 └─ 总字节 └─ 平均速率
+
+错误: 0 次 (数据不连续: 0)
+ └─ 总错误 └─ 不连续错误
+
+丢包率: 0.00%
+ └─ (发送字节 - 接收字节) / 发送字节 * 100
+```
+
+## 日志文件格式
+
+生成的日志文件每行一个字节(十六进制大写):
+
+**tx_data.log**
+```
+00
+01
+02
+03
+...
+FE
+FF
+00 ← 循环
+```
+
+**rx_data.log**
+```
+00
+01
+02
+03
+...
+```
+
+## 常见问题
+
+### Q1: 为什么要延时1秒?
+
+**A:** 串口数据传输需要时间:
+- 1Mbps波特率下,64字节数据传输约需 **0.5ms**
+- 但系统调度、缓冲区处理可能需要 **几十到几百毫秒**
+- 1秒的延时足够覆盖绝大多数场景
+
+### Q2: 可以调整延时时间吗?
+
+**A:** 当前是硬编码1秒。如需调整,可以修改 `serial_test.py` 第274行:
+```python
+time.sleep(1) # 改为其他值,如 0.5 或 2
+```
+
+### Q3: 高速传输时会丢数据吗?
+
+**A:** 取决于:
+- 串口驱动缓冲区大小(通常4KB~64KB)
+- 发送间隔和包大小
+- 系统负载
+
+建议:
+- 高速传输时适当增加发送间隔
+- 使用日志文件保存数据,测试完成后对比
+
+### Q4: 为什么最后接收可能比发送多?
+
+**A:** 这是不可能的。如果出现这种情况,可能是:
+- Bug(请报告)
+- 统计计数错误
+
+## 测试技巧
+
+### 1. 压力测试
+```bash
+# 高频小包(每10ms发送64字节)
+python3 serial_test.py -t /dev/ttyUSB0 -r /dev/ttyUSB1 -d 300 -i 10 -s 64 -b 1000000
+
+# 低频大包(每500ms发送2048字节)
+python3 serial_test.py -t /dev/ttyUSB0 -r /dev/ttyUSB1 -d 300 -i 500 -s 2048 -b 1000000
+```
+
+### 2. 稳定性测试
+```bash
+# 长时间运行(1小时)
+python3 serial_test.py -t /dev/ttyUSB0 -r /dev/ttyUSB1 -d 3600 -i 100 -s 128 -b 1000000
+```
+
+### 3. 回环测试
+```bash
+# 设备端开启 PPP_DO_SERIAL_TEST
+# PC端使用同一串口收发
+python3 serial_test.py \
+ -t /dev/ttyUSB0 \
+ -r /dev/ttyUSB0 \
+ -d 60 -i 100 -s 64 -b 1000000 \
+ --tx-log tx.log --rx-log rx.log
+```
+
+## 依赖安装
+
+```bash
+# Ubuntu/Debian
+sudo apt-get install python3-serial
+
+# 或使用 pip
+pip3 install pyserial
+```
+
+## 权限问题
+
+Linux下可能需要串口权限:
+```bash
+# 方法1:临时授权
+sudo chmod 666 /dev/ttyUSB0
+
+# 方法2:将用户加入dialout组(推荐)
+sudo usermod -a -G dialout $USER
+# 重新登录后生效
+```
+
+## 组合使用
+
+完整的测试流程:
+
+```bash
+#!/bin/bash
+# 串口完整测试脚本
+
+DATE=$(date +%Y%m%d_%H%M%S)
+TX_LOG="tx_${DATE}.log"
+RX_LOG="rx_${DATE}.log"
+RESULT="result_${DATE}.txt"
+
+echo "开始串口测试..."
+
+# 1. 运行测试
+python3 serial_test.py \
+ -t /dev/ttyUSB0 \
+ -r /dev/ttyUSB1 \
+ -d 60 \
+ -i 100 \
+ -s 64 \
+ -b 1000000 \
+ --tx-log "$TX_LOG" \
+ --rx-log "$RX_LOG" \
+ | tee test_output.txt
+
+# 2. 对比日志
+echo -e "\n\n对比收发数据..."
+python3 compare_logs.py "$TX_LOG" "$RX_LOG" | tee "$RESULT"
+
+# 3. 检查结果
+if grep -q "✅ 完美匹配" "$RESULT"; then
+ echo -e "\n✅ 测试通过!"
+ exit 0
+else
+ echo -e "\n❌ 测试失败!"
+ exit 1
+fi
+```
+
+## 总结
+
+优雅停止机制确保:
+- ✅ 最后的数据包不会丢失
+- ✅ 统计信息更准确
+- ✅ 测试结果更可靠
+- ✅ 适用于各种波特率和数据量
+
+是串口测试的重要改进!🎯
+
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+串口日志对比工具
+用于对比发送日志和接收日志,查找差异
+"""
+
+import sys
+import argparse
+
+def load_hex_log(filename):
+ """加载十六进制日志文件"""
+ data = []
+ try:
+ with open(filename, 'r') as f:
+ for line_num, line in enumerate(f, 1):
+ line = line.strip()
+ if line:
+ try:
+ # 解析十六进制值
+ byte_val = int(line, 16)
+ data.append((line_num, byte_val))
+ except ValueError:
+ print(f"警告: {filename}:{line_num} 无效的十六进制值: {line}")
+ return data
+ except FileNotFoundError:
+ print(f"错误: 文件不存在: {filename}")
+ return None
+ except Exception as e:
+ print(f"错误: 读取文件失败: {e}")
+ return None
+
+def find_missing_ranges(tx_data, rx_data):
+ """
+ 找出发送了但没收到的数据范围
+ 使用LCS(最长公共子序列)思想来对齐数据
+ """
+ tx_len = len(tx_data)
+ rx_len = len(rx_data)
+
+ # 创建接收数据的字典,方便快速查找
+ rx_dict = {rx_data[i][1]: i for i in range(rx_len)}
+
+ # 简单策略:逐个对比,找出丢失的字节
+ missing_ranges = []
+ current_range_start = None
+ current_range_data = []
+
+ rx_index = 0
+
+ for tx_index in range(tx_len):
+ tx_line, tx_byte = tx_data[tx_index]
+
+ # 检查这个字节是否在接收数据中(从当前位置开始)
+ found = False
+ if rx_index < rx_len:
+ rx_line, rx_byte = rx_data[rx_index]
+ if tx_byte == rx_byte:
+ # 匹配,移动到下一个接收位置
+ found = True
+ rx_index += 1
+
+ # 如果之前有未收到的范围,现在结束它
+ if current_range_start is not None:
+ missing_ranges.append({
+ 'start': current_range_start,
+ 'end': tx_index - 1,
+ 'count': len(current_range_data),
+ 'data': current_range_data
+ })
+ current_range_start = None
+ current_range_data = []
+
+ if not found:
+ # 这个字节没有收到
+ if current_range_start is None:
+ current_range_start = tx_index
+ current_range_data.append((tx_line, tx_byte))
+
+ # 处理最后一个范围
+ if current_range_start is not None:
+ missing_ranges.append({
+ 'start': current_range_start,
+ 'end': tx_len - 1,
+ 'count': len(current_range_data),
+ 'data': current_range_data
+ })
+
+ return missing_ranges
+
+def compare_logs(tx_data, rx_data, max_errors=100):
+ """对比发送和接收数据"""
+ tx_len = len(tx_data)
+ rx_len = len(rx_data)
+
+ print("="*70)
+ print(" "*25 + "日志对比结果")
+ print("="*70)
+ print(f"发送字节数: {tx_len}")
+ print(f"接收字节数: {rx_len}")
+ print(f"丢失字节数: {tx_len - rx_len}")
+
+ if tx_len > 0:
+ loss_rate = (tx_len - rx_len) / tx_len * 100
+ print(f"丢包率: {loss_rate:.2f}%")
+ print("-"*70)
+
+ # 找出丢失的数据范围
+ missing_ranges = find_missing_ranges(tx_data, rx_data)
+
+ if not missing_ranges:
+ print("\n✅ 完美匹配!所有数据都收到了。")
+ print("="*70)
+ return True
+
+ # 显示丢失的数据范围
+ print(f"\n发现 {len(missing_ranges)} 个丢失区域:\n")
+
+ total_missing = 0
+ shown_ranges = 0
+
+ for i, range_info in enumerate(missing_ranges):
+ if shown_ranges >= max_errors:
+ remaining = len(missing_ranges) - shown_ranges
+ print(f"\n... (还有 {remaining} 个丢失区域未显示)")
+ break
+
+ start_pos = range_info['start']
+ end_pos = range_info['end']
+ count = range_info['count']
+ data = range_info['data']
+
+ total_missing += count
+ shown_ranges += 1
+
+ if count == 1:
+ # 单个字节丢失
+ line, byte_val = data[0]
+ print(f"❌ 位置 {start_pos}: 丢失 1 字节 = 0x{byte_val:02X} (发送行{line})")
+ else:
+ # 连续多个字节丢失
+ print(f"❌ 位置 {start_pos}~{end_pos}: 丢失 {count} 字节")
+
+ # 显示前8个和后8个字节(如果数据较多)
+ if count <= 16:
+ # 数据不多,全部显示
+ hex_str = ' '.join([f"{byte_val:02X}" for _, byte_val in data])
+ print(f" 数据: {hex_str}")
+ else:
+ # 数据太多,只显示首尾
+ head = ' '.join([f"{byte_val:02X}" for _, byte_val in data[:8]])
+ tail = ' '.join([f"{byte_val:02X}" for _, byte_val in data[-8:]])
+ print(f" 数据: {head} ... {tail}")
+
+ # 添加空行分隔不同的范围
+ if i < len(missing_ranges) - 1:
+ print()
+
+ print("-"*70)
+ print(f"总计丢失: {total_missing} 字节")
+
+ if tx_len != rx_len:
+ print(f"长度差异: {tx_len - rx_len} 字节")
+
+ print("\n❌ 发现数据丢失!")
+ print("="*70)
+
+ return False
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='串口日志对比工具 - 对比发送和接收的十六进制日志文件',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+示例:
+ %(prog)s tx.log rx.log
+ %(prog)s tx.log rx.log --max-errors 50
+ """
+ )
+
+ parser.add_argument('tx_log', help='发送日志文件')
+ parser.add_argument('rx_log', help='接收日志文件')
+ parser.add_argument('--max-errors', type=int, default=100,
+ help='最多显示的错误数量 (默认: 100)')
+
+ args = parser.parse_args()
+
+ print(f"加载发送日志: {args.tx_log}")
+ tx_data = load_hex_log(args.tx_log)
+ if tx_data is None:
+ return 1
+
+ print(f"加载接收日志: {args.rx_log}")
+ rx_data = load_hex_log(args.rx_log)
+ if rx_data is None:
+ return 1
+
+ print()
+ success = compare_logs(tx_data, rx_data, args.max_errors)
+
+ return 0 if success else 1
+
+if __name__ == '__main__':
+ sys.exit(main())
+
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+串口测试程序
+功能:
+- 支持双串口发送和接收测试
+- 数据按照0x00~0xFF循环发送
+- 检测接收数据连续性
+- 统计错误和吞吐量
+"""
+
+import serial
+import threading
+import time
+import argparse
+import sys
+from datetime import datetime
+
+class SerialTestStats:
+ """统计信息类"""
+ def __init__(self, tx_log_file=None, rx_log_file=None):
+ self.tx_bytes = 0 # 发送字节数
+ self.rx_bytes = 0 # 接收字节数
+ self.tx_packets = 0 # 发送包数
+ self.rx_packets = 0 # 接收包数
+ self.error_count = 0 # 错误计数
+ self.discontinuity_count = 0 # 不连续计数
+ self.last_expected_byte = None # 期望的下一个字节
+ self.lock = threading.Lock()
+ self.start_time = time.time()
+
+ # 日志文件
+ self.tx_log_file = None
+ self.rx_log_file = None
+
+ # 打开日志文件
+ if tx_log_file:
+ try:
+ self.tx_log_file = open(tx_log_file, 'w')
+ print(f"发送数据日志: {tx_log_file}")
+ except Exception as e:
+ print(f"警告: 无法创建发送日志文件: {e}")
+
+ if rx_log_file:
+ try:
+ self.rx_log_file = open(rx_log_file, 'w')
+ print(f"接收数据日志: {rx_log_file}")
+ except Exception as e:
+ print(f"警告: 无法创建接收日志文件: {e}")
+
+ def update_tx(self, data):
+ """更新发送统计并记录到日志"""
+ with self.lock:
+ self.tx_bytes += len(data)
+ self.tx_packets += 1
+
+ # 记录到日志文件(每个字节一行)
+ if self.tx_log_file:
+ for byte in data:
+ self.tx_log_file.write(f"{byte:02X}\n")
+ self.tx_log_file.flush() # 实时刷新
+
+ def update_rx(self, data):
+ """更新接收统计并检查连续性"""
+ with self.lock:
+ self.rx_bytes += len(data)
+ self.rx_packets += 1
+
+ # 记录到日志文件(每个字节一行)
+ if self.rx_log_file:
+ for byte in data:
+ self.rx_log_file.write(f"{byte:02X}\n")
+ self.rx_log_file.flush() # 实时刷新
+
+ # 检查数据连续性
+ for byte in data:
+ if self.last_expected_byte is None:
+ # 第一个字节,初始化
+ self.last_expected_byte = (byte + 1) % 256
+ else:
+ if byte != self.last_expected_byte:
+ self.error_count += 1
+ self.discontinuity_count += 1
+ print(f"\n[ERROR] 数据不连续! 期望: 0x{self.last_expected_byte:02X}, 实际: 0x{byte:02X}")
+ self.last_expected_byte = (byte + 1) % 256
+
+ def close(self):
+ """关闭日志文件"""
+ if self.tx_log_file:
+ self.tx_log_file.close()
+ self.tx_log_file = None
+ if self.rx_log_file:
+ self.rx_log_file.close()
+ self.rx_log_file = None
+
+ def print_stats(self):
+ """打印统计信息"""
+ with self.lock:
+ elapsed = time.time() - self.start_time
+ tx_rate = self.tx_bytes / elapsed if elapsed > 0 else 0
+ rx_rate = self.rx_bytes / elapsed if elapsed > 0 else 0
+
+ print("\n" + "="*70)
+ print(f"统计时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+ print(f"运行时长: {elapsed:.2f} 秒")
+ print("-"*70)
+ print(f"发送: {self.tx_packets:,} 包, {self.tx_bytes:,} 字节, {tx_rate:.2f} 字节/秒")
+ print(f"接收: {self.rx_packets:,} 包, {self.rx_bytes:,} 字节, {rx_rate:.2f} 字节/秒")
+ print(f"错误: {self.error_count:,} 次 (数据不连续: {self.discontinuity_count:,})")
+ if self.tx_bytes > 0:
+ loss_rate = (self.tx_bytes - self.rx_bytes) / self.tx_bytes * 100
+ print(f"丢包率: {loss_rate:.2f}%")
+ print("="*70 + "\n")
+
+class SerialTester:
+ """串口测试器"""
+ def __init__(self, tx_port, rx_port, duration, interval_ms, packet_size, baudrate=115200,
+ tx_log_file=None, rx_log_file=None):
+ self.tx_port = tx_port
+ self.rx_port = rx_port
+ self.duration = duration
+ self.interval = interval_ms / 1000.0 # 转换为秒
+ self.packet_size = packet_size
+ self.baudrate = baudrate
+ self.stats = SerialTestStats(tx_log_file, rx_log_file)
+ self.running = False
+ self.tx_running = False # 发送线程运行标志
+ self.rx_running = False # 接收线程运行标志
+ self.tx_serial = None
+ self.rx_serial = None
+ self.current_byte = 0 # 当前要发送的字节值
+
+ def open_ports(self):
+ """打开串口"""
+ try:
+ print(f"打开发送串口: {self.tx_port} @ {self.baudrate}")
+ self.tx_serial = serial.Serial(
+ port=self.tx_port,
+ baudrate=self.baudrate,
+ bytesize=serial.EIGHTBITS,
+ parity=serial.PARITY_NONE,
+ stopbits=serial.STOPBITS_ONE,
+ timeout=1
+ )
+
+ print(f"打开接收串口: {self.rx_port} @ {self.baudrate}")
+ self.rx_serial = serial.Serial(
+ port=self.rx_port,
+ baudrate=self.baudrate,
+ bytesize=serial.EIGHTBITS,
+ parity=serial.PARITY_NONE,
+ stopbits=serial.STOPBITS_ONE,
+ timeout=1
+ )
+
+ print("串口打开成功!\n")
+ return True
+
+ except serial.SerialException as e:
+ print(f"打开串口失败: {e}")
+ return False
+
+ def close_ports(self):
+ """关闭串口和日志文件"""
+ if self.tx_serial and self.tx_serial.is_open:
+ self.tx_serial.close()
+ print("发送串口已关闭")
+
+ if self.rx_serial and self.rx_serial.is_open:
+ self.rx_serial.close()
+ print("接收串口已关闭")
+
+ # 关闭日志文件
+ self.stats.close()
+ print("日志文件已关闭")
+
+ def generate_packet(self):
+ """生成测试数据包"""
+ packet = bytearray()
+ for i in range(self.packet_size):
+ packet.append(self.current_byte)
+ self.current_byte = (self.current_byte + 1) % 256
+ return bytes(packet)
+
+ def tx_thread(self):
+ """发送线程"""
+ print(f"发送线程启动 (间隔: {self.interval*1000:.2f}ms, 包大小: {self.packet_size}字节)")
+
+ while self.tx_running:
+ try:
+ # 生成并发送数据
+ packet = self.generate_packet()
+ self.tx_serial.write(packet)
+ self.stats.update_tx(packet)
+
+ # 等待指定间隔
+ time.sleep(self.interval)
+
+ except serial.SerialException as e:
+ print(f"\n[ERROR] 发送错误: {e}")
+ break
+ except Exception as e:
+ print(f"\n[ERROR] 发送异常: {e}")
+ break
+
+ print("发送线程退出")
+
+ def rx_thread(self):
+ """接收线程"""
+ print(f"接收线程启动")
+
+ while self.rx_running:
+ try:
+ # 检查是否有数据可读
+ if self.rx_serial.in_waiting > 0:
+ data = self.rx_serial.read(self.rx_serial.in_waiting)
+ self.stats.update_rx(data)
+ else:
+ time.sleep(0.01) # 短暂休眠避免CPU占用过高
+
+ except serial.SerialException as e:
+ print(f"\n[ERROR] 接收错误: {e}")
+ break
+ except Exception as e:
+ print(f"\n[ERROR] 接收异常: {e}")
+ break
+
+ print("接收线程退出")
+
+ def stats_thread(self):
+ """统计打印线程"""
+ while self.running:
+ time.sleep(10)
+ if self.running:
+ self.stats.print_stats()
+
+ def run(self):
+ """运行测试"""
+ if not self.open_ports():
+ return False
+
+ # 启动所有线程
+ self.running = True
+ self.tx_running = True
+ self.rx_running = True
+
+ # 创建线程
+ tx_t = threading.Thread(target=self.tx_thread, name="TX-Thread")
+ rx_t = threading.Thread(target=self.rx_thread, name="RX-Thread")
+ stats_t = threading.Thread(target=self.stats_thread, name="Stats-Thread")
+
+ # 启动线程
+ tx_t.start()
+ rx_t.start()
+ stats_t.start()
+
+ print(f"\n测试开始,运行时长: {self.duration} 秒")
+ print("按 Ctrl+C 可以提前终止测试\n")
+
+ try:
+ # 等待指定时长
+ time.sleep(self.duration)
+ except KeyboardInterrupt:
+ print("\n\n收到中断信号,正在停止测试...")
+
+ # 优雅地停止测试:先停发送,等1秒后再停接收
+ print("\n正在停止发送...")
+ self.tx_running = False
+ tx_t.join(timeout=5)
+ print("发送线程已停止")
+
+ # 等待1秒,让接收线程接收最后的数据
+ print("等待1秒以接收剩余数据...")
+ time.sleep(1)
+
+ print("正在停止接收...")
+ self.rx_running = False
+ rx_t.join(timeout=5)
+ print("接收线程已停止")
+
+ # 停止统计线程
+ self.running = False
+ stats_t.join(timeout=2)
+
+ # 打印最终统计
+ print("\n" + "#"*70)
+ print("#" + " "*25 + "最终统计信息" + " "*25 + "#")
+ print("#"*70)
+ self.stats.print_stats()
+
+ # 关闭串口
+ self.close_ports()
+
+ return True
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='串口测试程序 - 支持双串口收发测试',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+示例:
+ %(prog)s -t /dev/ttyUSB0 -r /dev/ttyUSB1 -d 60 -i 100 -s 64
+ %(prog)s --tx-port COM1 --rx-port COM2 --duration 120 --interval 50 --size 128 --baudrate 9600
+ %(prog)s -t /dev/ttyUSB0 -r /dev/ttyUSB1 -d 60 -i 100 -s 64 --tx-log tx.log --rx-log rx.log
+ """
+ )
+
+ parser.add_argument('-t', '--tx-port', required=True,
+ help='发送串口 (例如: /dev/ttyUSB0 或 COM1)')
+ parser.add_argument('-r', '--rx-port', required=True,
+ help='接收串口 (例如: /dev/ttyUSB1 或 COM2)')
+ parser.add_argument('-d', '--duration', type=int, required=True,
+ help='测试时长 (秒)')
+ parser.add_argument('-i', '--interval', type=int, required=True,
+ help='发送间隔 (毫秒)')
+ parser.add_argument('-s', '--size', type=int, required=True,
+ help='每包数据大小 (字节)')
+ parser.add_argument('-b', '--baudrate', type=int, default=115200,
+ help='波特率 (默认: 115200)')
+ parser.add_argument('--tx-log', type=str, default=None,
+ help='发送数据日志文件路径 (十六进制格式,一字节一行)')
+ parser.add_argument('--rx-log', type=str, default=None,
+ help='接收数据日志文件路径 (十六进制格式,一字节一行)')
+
+ args = parser.parse_args()
+
+ # 参数验证
+ if args.duration <= 0:
+ print("错误: 测试时长必须大于0")
+ return 1
+
+ if args.interval <= 0:
+ print("错误: 发送间隔必须大于0")
+ return 1
+
+ if args.size <= 0 or args.size > 4096:
+ print("错误: 数据大小必须在1-4096字节之间")
+ return 1
+
+ # 打印配置信息
+ print("="*70)
+ print(" "*25 + "串口测试程序" + " "*25)
+ print("="*70)
+ print(f"发送串口: {args.tx_port}")
+ print(f"接收串口: {args.rx_port}")
+ print(f"波特率: {args.baudrate}")
+ print(f"测试时长: {args.duration} 秒")
+ print(f"发送间隔: {args.interval} 毫秒")
+ print(f"包大小: {args.size} 字节")
+ if args.tx_log:
+ print(f"发送日志: {args.tx_log}")
+ if args.rx_log:
+ print(f"接收日志: {args.rx_log}")
+ print("="*70 + "\n")
+
+ # 创建测试器并运行
+ tester = SerialTester(
+ tx_port=args.tx_port,
+ rx_port=args.rx_port,
+ duration=args.duration,
+ interval_ms=args.interval,
+ packet_size=args.size,
+ baudrate=args.baudrate,
+ tx_log_file=args.tx_log,
+ rx_log_file=args.rx_log
+ )
+
+ success = tester.run()
+
+ return 0 if success else 1
+
+if __name__ == '__main__':
+ sys.exit(main())
+
(void)para;
(void)paraLen;
- PRINT("[PPP_INFO]PPP SIGNAL INFO: Getting network signal information...\r\n");
+ PRINT("[PPP_INFO]PPP SIGNAL INFO IN ALL MODE: Getting network signal information...\r\n");
- errcode_t ret = ppp_get_network_signal_info();
+ errcode_t ret = ppp_api_update_netinfo_in_all_mode();
if (ret == ERRCODE_SUCC) {
- PRINT("[PPP_INFO]PPP SIGNAL INFO: Network signal information retrieved successfully\r\n");
+ PRINT("[PPP_INFO]PPP SIGNAL INFO IN ALL MODE: Network signal information retrieved successfully\r\n");
} else {
- PRINT("[PPP_ERR]PPP SIGNAL INFO: Failed to get network signal information, error: %d\r\n", ret);
+ PRINT("[PPP_ERR]PPP SIGNAL INFO IN ALL MODE: Failed to get network signal information, error: %d\r\n", ret);
}
return ret;
{
if (!para || paraLen == 0) {
PRINT("[PPP_ERR]PPP TCP PERF: Missing parameters\r\n");
- PRINT("[PPP_INFO]Usage: AT^PPPTCPPERF=<host>,<port>,<connections>,<time>[,<recv_timeout_ms>]\r\n");
+ PRINT("[PPP_INFO]Usage: AT^PPPTCPPERF=<host>,<port>,<connections>,<time>[,<send_interval_ms>]\r\n");
PRINT("[PPP_INFO]Example: AT^PPPTCPPERF=110.42.224.129,8910,5,30\r\n");
- PRINT("[PPP_INFO]Example: AT^PPPTCPPERF=110.42.224.129,8910,5,30,2000\r\n");
+ PRINT("[PPP_INFO]Example: AT^PPPTCPPERF=110.42.224.129,8910,1,300,500\r\n");
return ERRCODE_FAIL;
}
if (ConvertCmdToParam(para, paraLen, argv, &argc) != CUSTOM_UTILS_SUCCESS || (argc != 4 && argc != 5)) {
PRINT("[PPP_ERR]PPP TCP PERF: Invalid parameters, need 4 or 5 parameters, got %d\r\n", argc);
- PRINT("[PPP_INFO]Usage: AT^PPPTCPPERF=<host>,<port>,<connections>,<time>[,<recv_timeout_ms>]\r\n");
+ PRINT("[PPP_INFO]Usage: AT^PPPTCPPERF=<host>,<port>,<connections>,<time>[,<send_interval_ms>]\r\n");
return ERRCODE_FAIL;
}
int port = atoi((char*)argv[1]);
int connections = atoi((char*)argv[2]);
int test_time = atoi((char*)argv[3]);
- int recv_timeout_ms = TCP_TEST_DEFAULT_RECV_TIMEOUT_MS; // 默认值
+ int send_interval_ms = TCP_TEST_DEFAULT_SEND_INTERVAL_MS; // 默认值
// 解析可选的接收超时参数
if (argc == 5) {
- recv_timeout_ms = atoi((char*)argv[4]);
- if (recv_timeout_ms < 100 || recv_timeout_ms > 30000) {
- PRINT("[PPP_ERR]PPP TCP PERF: Invalid recv timeout %d (must be 100-30000 ms)\r\n", recv_timeout_ms);
+ send_interval_ms = atoi((char*)argv[4]);
+ if (send_interval_ms < 10 || send_interval_ms > 1000) {
+ PRINT("[PPP_ERR]PPP TCP PERF: Invalid send interval %d (must be 10-1000 ms)\r\n", send_interval_ms);
return ERRCODE_FAIL;
}
}
PRINT("[PPP_INFO]Target: %s:%d\r\n", server_host, port);
PRINT("[PPP_INFO]Connections: %d\r\n", connections);
PRINT("[PPP_INFO]Duration: %d seconds\r\n", test_time);
- PRINT("[PPP_INFO]Receive Timeout: %d ms\r\n", recv_timeout_ms);
+ PRINT("[PPP_INFO]Send interval: %d ms\r\n", send_interval_ms);
#ifdef SUPPORT_TCP_TEST
// 初始化TCP测试配置
config.test_duration = (uint32_t)test_time;
config.connection_count = (uint16_t)connections;
config.verbose = true; // AT命令模式不使用详细输出
- config.send_interval_ms = 500; // 500ms间隔,给其他进程更多CPU时间
+ config.recv_timeout_ms = 500; // 可配置的接收超时
config.data_min_size = 1024; // 较小的数据包
config.data_max_size = 4096; // 适中的最大包
- config.recv_timeout_ms = (uint32_t)recv_timeout_ms; // 可配置的接收超时
+ config.send_interval_ms = (uint32_t)send_interval_ms;// 500ms间隔,给其他进程更多CPU时间
// 初始化TCP测试
errcode_t ret = tcp_test_init();
if (current_baudrate == baudrate) {
ppp_print_info("PPP SET BAUDRATE: Baudrate already set to %d, no change needed\n", baudrate);
- return ERRCODE_SUCC;
+ // return ERRCODE_SUCC;
}
// 设置新的波特率到配置模型
*/
errcode_t ppp_stop_dial(void);
+/**
+ * @brief 暂停PPP通讯,切换到AT命令模式(不断开连接)
+ * @return 错误码
+ *
+ * 功能说明:
+ * - 发送 "+++" 转义序列切换到AT命令模式
+ * - 停止PPP数据接收线程/中断
+ * - 初始化AT处理器
+ * - 执行AT测试验证
+ *
+ * 注意:PPP连接保持,只是暂时切换到AT模式,与 ppp_resume() 配对使用
+ */
+errcode_t ppp_ppp_mode_pause(void);
+
+/**
+ * @brief 恢复PPP通讯,从AT命令模式返回PPP数据模式
+ * @return 错误码
+ *
+ * 功能说明:
+ * - 发送 "ATO\r\n" 命令恢复PPP数据模式
+ * - 等待 "CONNECT" 响应
+ * - 去初始化AT处理器
+ * - 恢复PPP数据接收线程/中断
+ *
+ * 注意:与 ppp_pause() 配对使用
+ */
+errcode_t ppp_ppp_mode_resume(void);
+
/* PPP数据接收线程函数已移至ppp_lwip_thread.c */
/**
*/
errcode_t ppp_api_update_all_runtime_status(void);
+/**
+ * @brief 更新所有runtime变量值
+ * @return ERRCODE_SUCC-成功,其他-失败
+ */
+errcode_t ppp_api_update_netinfo_in_all_mode(void);
+
/**
* @brief 设置默认网卡
* @param netif_name 网卡名称
/* PPP接收模式配置 */
#ifndef PPP_USE_INTERRUPT_MODE
-#define PPP_USE_INTERRUPT_MODE 1 // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询)
+#define PPP_USE_INTERRUPT_MODE 1 // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询)
#endif
-/* PPP线程模式配置(仅轮询模式有效) */
-#ifndef PPP_USE_DUAL_THREAD
-#define PPP_USE_DUAL_THREAD 1 // 1=双线程模式(接收+处理), 0=单线程模式(接收直接处理)
+#ifndef PPP_DO_SERIAL_TEST
+#define PPP_DO_SERIAL_TEST 0 // 1=进行串口读取能力测试, 0=不进行串口读取能力测试
#endif
+
/* PPP性能优化配置 - 解决接收阻塞问题 */
#define PPP_LWIP_PBUF_POOL_SIZE 32 // 增加pbuf池大小,默认16太小
#define PPP_LWIP_TCPIP_MSG_INPKT 64 // 增加输入消息数量,默认32太小
// 保存用户配置
memcpy_s(&g_ppp_config, sizeof(ppp_config_t), config, sizeof(ppp_config_t));
+ // PPP_DO_SERIAL_TEST 不进行电源初始化, 用于调试串口读取能力测试。
+ #ifndef PPP_DO_SERIAL_TEST
ppp_api_power_init();
+ #endif
#define PPP_SERIAL_INIT 1 //外接串口调试模组时,串口要设置input防止干扰。
#if PPP_SERIAL_INIT
goto init_failed;
}
+
+ #ifndef PPP_DO_SERIAL_TEST
+
// 初始化AT命令处理器
ret = ppp_at_handler_init(SIO_UART_BUS, g_ppp_config.baudrate, true);
if (ret != PPP_ERR_OK) {
} else {
PPP_DEBUG_PRINT("Successfully switched to AT mode\n");
}
+#else
+ PPP_DEBUG_PRINT("PPP_DO_SERIAL_TEST is defined, skip switch to AT mode\n");
+ ret = ppp_serial_switch_to_ppp_mode();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_PRINT("Failed to switch to PPP mode: %d\n", ret);
+ ppp_serial_deinit();
+ goto init_failed;
+ } else {
+ PPP_DEBUG_PRINT("Successfully switched to PPP mode\n");
+ }
+#endif
+
#else
ppp_api_leiniao_serial_pin_to_gpio_input();
#endif
g_ppp_state = PPP_STATE_INITIALIZED;
PPP_DEBUG_PRINT("PPP module initialized successfully\n");
-
+
// 打开4G电源
ppp_api_power_control(true);
+
+#ifndef PPP_DO_SERIAL_TEST
PPP_DEBUG_PRINT("Waiting for modem stabilization after power control...\n");
// 等待5秒让调制解调器完全稳定
osal_msleep(5000);
ppp_api_update_all_runtime_status();
ppp_get_network_signal_info();
+#else
+ // 9. 启动PPP数据接收处理(根据配置选择中断或轮询模式)
+ #if PPP_USE_INTERRUPT_MODE
+ // 中断模式:注册UART中断回调(UART总线号从ppp_serial_get_uart_bus()获取)
+ PPP_DEBUG_PRINT("Enabling PPP interrupt mode...\n");
+ ret = ppp_interrupt_enable();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_PRINT("Failed to enable PPP interrupt: %d\n", ret);
+ goto init_failed;
+ }
+ PPP_DEBUG_PRINT("PPP interrupt mode enabled successfully\n");
+ #else
+ // 轮询模式:启动接收线程
+ PPP_DEBUG_PRINT("Starting PPP receive thread (polling mode)...\n");
+ ret = ppp_lwip_start_receive_thread();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_PRINT("Failed to start PPP data receive thread: %d\n", ret);
+ goto init_failed;
+ }
+ PPP_DEBUG_PRINT("PPP receive thread started successfully\n");
+ #endif
+#endif
- return ERRCODE_SUCC;
+return ERRCODE_SUCC;
#if PPP_SERIAL_INIT
init_failed:
#endif
return ERRCODE_SUCC;
}
+/**
+ * @brief 暂停PPP通讯,切换到AT命令模式(不断开连接)
+ * @return ERRCODE_SUCC-成功,其他-失败
+ *
+ * 功能说明:
+ * 1. 发送 "+++" 转义序列切换到AT命令模式
+ * 2. 停止PPP数据接收线程/中断
+ * 3. 初始化AT处理器
+ * 4. 执行AT测试验证
+ *
+ * 注意:PPP连接保持,只是暂时切换到AT模式
+ */
+errcode_t ppp_ppp_mode_pause(void)
+{
+ PPP_DEBUG_CRITICAL("=== PPP Pause Started ===");
+
+ // 检查PPP状态
+ if (g_ppp_state != PPP_STATE_CONNECTED) {
+ PPP_DEBUG_ERROR("PPP not connected, cannot pause (state: %d)", g_ppp_state);
+ return ERRCODE_FAIL;
+ }
+
+ ppp_error_t ret = PPP_ERR_OK;
+
+ // 1. 停止PPP数据接收(根据配置选择中断或轮询模式)
+#if PPP_USE_INTERRUPT_MODE
+ PPP_DEBUG_INFO("Disabling PPP interrupt mode...");
+ ppp_interrupt_disable();
+ PPP_DEBUG_INFO("PPP interrupt mode disabled");
+#else
+ PPP_DEBUG_INFO("Stopping PPP receive thread...");
+ ppp_lwip_stop_receive_thread();
+ PPP_DEBUG_INFO("PPP receive thread stopped");
+#endif
+
+ // 2. 发送 "+++" 转义序列切换到AT命令模式
+ PPP_DEBUG_INFO("Sending escape sequence (+++) to switch to AT mode...");
+
+ // 根据Hayes标准,转义序列前后需要1秒的静默期
+ osal_msleep(1000); // 前置静默期
+
+ const char escape_seq[] = "+++";
+ ret = ppp_serial_write((uint8_t*)escape_seq, strlen(escape_seq));
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to send escape sequence: %d", ret);
+ goto error_recovery;
+ }
+
+ osal_msleep(1000); // 后置静默期
+ PPP_DEBUG_INFO("Escape sequence sent, waiting for modem response...");
+
+ // 3. 等待调制解调器响应(通常是 "OK")
+ osal_msleep(1000); // 等待响应
+
+ // 读取调制解调器响应
+ uint8_t response_buffer[64];
+ uint32_t bytes_read = 0;
+ ret = ppp_serial_read(response_buffer, sizeof(response_buffer), &bytes_read);
+ if (ret == PPP_ERR_OK && bytes_read > 0) {
+ PPP_DEBUG_INFO("Modem response to escape sequence: %u bytes", bytes_read);
+ // 可以检查是否包含 "OK"
+ bool found_ok = false;
+ for (uint32_t i = 0; i < bytes_read - 1; i++) {
+ if (response_buffer[i] == 'O' && response_buffer[i+1] == 'K') {
+ found_ok = true;
+ break;
+ }
+ }
+ if (found_ok) {
+ PPP_DEBUG_INFO("Modem confirmed AT mode with OK");
+ } else {
+ PPP_DEBUG_WARN("No OK response from modem, continuing anyway...");
+ }
+ } else {
+ PPP_DEBUG_WARN("No immediate response from modem after escape sequence");
+ }
+
+ // 4. 切换串口到AT模式
+ PPP_DEBUG_INFO("Switching serial to AT mode...");
+ ret = ppp_serial_switch_to_at_mode();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to switch to AT mode: %d", ret);
+ goto error_recovery;
+ }
+
+ // 5. 初始化AT处理器
+ PPP_DEBUG_INFO("Initializing AT handler...");
+ ret = ppp_at_handler_init(SIO_UART_BUS, g_ppp_config.baudrate, true);
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to initialize AT handler: %d", ret);
+ goto error_recovery;
+ }
+
+ // 6. 测试AT通信
+ PPP_DEBUG_INFO("Testing AT communication...");
+ ret = ppp_at_test_communication();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_WARN("AT communication test failed: %d", ret);
+ // 尝试第二次
+ osal_msleep(500);
+ ret = ppp_at_test_communication();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("AT communication still failed after retry");
+ goto error_recovery;
+ }
+ }
+
+ PPP_DEBUG_CRITICAL("=== PPP Paused - Now in AT Mode ===");
+ PPP_DEBUG_INFO("PPP connection is suspended but not terminated");
+ PPP_DEBUG_INFO("Use ppp_resume() to return to PPP data mode");
+
+ // 更新状态(暂停状态,但保留CONNECTED信息)
+ // 不修改 g_ppp_state,保持 PPP_STATE_CONNECTED
+
+ return ERRCODE_SUCC;
+
+error_recovery:
+ PPP_DEBUG_ERROR("Failed to pause PPP, attempting to resume data mode...");
+
+ // 尝试恢复到PPP数据模式
+ ppp_at_handler_deinit();
+ ppp_serial_switch_to_ppp_mode();
+
+#if PPP_USE_INTERRUPT_MODE
+ ppp_interrupt_enable();
+#else
+ ppp_lwip_start_receive_thread();
+#endif
+
+ return ERRCODE_FAIL;
+}
+
+/**
+ * @brief 恢复PPP通讯,从AT命令模式返回PPP数据模式
+ * @return ERRCODE_SUCC-成功,其他-失败
+ *
+ * 功能说明:
+ * 1. 发送 "ATO\r\n" 命令恢复PPP数据模式
+ * 2. 等待 "CONNECT" 响应
+ * 3. 去初始化AT处理器
+ * 4. 恢复PPP数据接收线程/中断
+ *
+ * 注意:与 ppp_pause() 配对使用
+ */
+errcode_t ppp_ppp_mode_resume(void)
+{
+ PPP_DEBUG_CRITICAL("=== PPP Resume Started ===");
+
+ // 检查串口模式
+ if (ppp_serial_get_mode() != PPP_SERIAL_MODE_AT) {
+ PPP_DEBUG_ERROR("Serial not in AT mode, cannot resume (mode: %d)",
+ ppp_serial_get_mode());
+ return ERRCODE_FAIL;
+ }
+
+ ppp_error_t ret = PPP_ERR_OK;
+
+ // 1. 发送 ATO 命令恢复数据模式
+ PPP_DEBUG_INFO("Sending ATO command to resume PPP data mode...");
+
+ const char ato_cmd[] = "ATO\r\n";
+ ret = ppp_serial_write((uint8_t*)ato_cmd, strlen(ato_cmd));
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to send ATO command: %d", ret);
+ return ERRCODE_FAIL;
+ }
+
+ PPP_DEBUG_INFO("ATO command sent, waiting for CONNECT response...");
+
+ // 2. 等待 CONNECT 响应(最多等待10秒)
+ bool connect_found = false;
+ uint8_t response_buffer[128];
+ uint32_t total_wait = 0;
+ const uint32_t max_wait = 10000; // 10秒超时
+
+ while (total_wait < max_wait) {
+ uint32_t bytes_read = 0;
+ ret = ppp_serial_read(response_buffer, sizeof(response_buffer), &bytes_read);
+
+ if (ret == PPP_ERR_OK && bytes_read > 0) {
+ PPP_DEBUG_INFO("Received %u bytes from modem", bytes_read);
+
+ // 检查是否包含 "CONNECT"
+ for (uint32_t i = 0; i < bytes_read - 6; i++) {
+ if (strncmp((char*)&response_buffer[i], "CONNECT", 7) == 0) {
+ connect_found = true;
+ PPP_DEBUG_INFO("Received CONNECT response from modem");
+ break;
+ }
+ }
+
+ if (connect_found) {
+ break;
+ }
+ }
+
+ osal_msleep(500);
+ total_wait += 500;
+ }
+
+ if (!connect_found) {
+ PPP_DEBUG_ERROR("Timeout waiting for CONNECT response after ATO");
+ return ERRCODE_FAIL;
+ }
+
+ // 3. 去初始化AT处理器
+ PPP_DEBUG_INFO("Deinitializing AT handler...");
+ ppp_at_handler_deinit();
+
+ // 4. 切换串口到PPP数据模式
+ PPP_DEBUG_INFO("Switching serial to PPP data mode...");
+ ret = ppp_serial_switch_to_ppp_mode();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to switch to PPP data mode: %d", ret);
+ return ERRCODE_FAIL;
+ }
+
+ // 5. 恢复PPP数据接收(根据配置选择中断或轮询模式)
+#if PPP_USE_INTERRUPT_MODE
+ PPP_DEBUG_INFO("Re-enabling PPP interrupt mode...");
+ ret = ppp_interrupt_enable();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to enable PPP interrupt: %d", ret);
+ return ERRCODE_FAIL;
+ }
+ PPP_DEBUG_INFO("PPP interrupt mode re-enabled");
+#else
+ PPP_DEBUG_INFO("Restarting PPP receive thread...");
+ ret = ppp_lwip_start_receive_thread();
+ if (ret != PPP_ERR_OK) {
+ PPP_DEBUG_ERROR("Failed to restart PPP receive thread: %d", ret);
+ return ERRCODE_FAIL;
+ }
+ PPP_DEBUG_INFO("PPP receive thread restarted");
+#endif
+
+ PPP_DEBUG_CRITICAL("=== PPP Resumed - Back to Data Mode ===");
+ PPP_DEBUG_INFO("PPP connection restored and active");
+
+ // 状态保持 PPP_STATE_CONNECTED(未改变)
+
+ return ERRCODE_SUCC;
+}
+
/**
* @brief 停止PPP连接
*/
return ERRCODE_SUCC;
}
-// 重复函数定义已删除 - 使用后面的新实现
+errcode_t ppp_api_update_netinfo_in_all_mode(void)
+{
+
+ errcode_t ret = ERRCODE_SUCC;
+ uint8_t need_resume = 0;
+ if(ppp_serial_get_mode() == PPP_SERIAL_MODE_PPP_DATA) {
+ PPP_DEBUG_ERROR("In ppp mode pause it");
+ ppp_ppp_mode_pause();
+ need_resume = 1;
+ }else{
+ PPP_DEBUG_ERROR("In AT mode");
+ need_resume = 0;
+ }
+ // 更新信号强度
+ PPP_DEBUG_CRITICAL("Step 2: Starting signal strength update...");
+ if (ppp_api_update_signal() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to update signal");
+ ret = ERRCODE_FAIL;
+ } else {
+ PPP_DEBUG_CRITICAL("Step 2: Signal strength update completed successfully");
+ }
+
+ // 更新网络状态
+ if (ppp_api_update_network_status() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to update network status");
+ ret = ERRCODE_FAIL;
+ }
+
+ if(ppp_get_network_signal_info() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to get network signal info");
+ ret = ERRCODE_FAIL;
+ }
-// 重复函数定义已删除 - 使用后面的新实现
+ if( need_resume) {
+ ppp_ppp_mode_resume();
+ }
+
+ return ret;
+}
/**
* @brief 获取PPP网络接口的实际名称
PPP_DEBUG_ERROR("Unsupported baudrate: %u", baudrate);
return PPP_ERR_INVALID_PARAM;
}
+#ifndef PPP_DO_SERIAL_TEST
/* 先查询当前modem波特率 */
uint32_t current_modem_baudrate = ppp_query_current_modem_baudrate();
PPP_DEBUG_ERROR("Failed to set baudrate to %u", baudrate);
return ret;
}
-
+#else
+ uint32_t current_modem_baudrate = baudrate;
+#endif
/* 更新串口配置 */
uart_attr_t uart_attr = {
.baud_rate = baudrate,
/**
* @file ppp_interrupt.c
- * @brief PPP UART中断接收处理 - 使用消息队列和专用处理线程
+ * @brief PPP UART中断接收处理 - 使用乒乓缓冲区优化
*
* 核心设计:
- * - ISR中只做快速数据复制到动态分配的缓冲区
- * - 通过消息队列传递数据指针到处理线程
- * - 处理线程负责调用pppos_input_tcpip()和释放缓冲区
- * - 避免ISR中执行耗时操作(pbuf_alloc、内存复制、消息队列投递等)
+ * - 使用乒乓缓冲区(2个buffer,每个2KB)避免频繁malloc/free
+ * - ISR中写入当前buffer,满了自动切换到另一个buffer
+ * - 通过消息队列传递buffer索引(而非拷贝数据)
+ * - 定时器监控buffer状态,超时自动flush(30ms)
+ * - 处理线程负责调用pppos_input_tcpip()并释放buffer
*/
#include "ppp_interrupt.h"
#include "errcode.h"
#include "osal_task.h"
#include "osal_msgqueue.h"
+#include "time/osal_timer.h"
#include "soc_osal.h"
#include <string.h>
#include <stdbool.h>
#include "netif/ppp/ppp.h"
#include "netif/ppp/pppos.h"
-/* PPP UART硬件缓冲区大小
- 与uart_porting.c中 UART_RX_BUFFER_SIZE 要一一对应
- #if defined(SUPPORT_PPP_SERVICE)
- #define UART_RX_BUFFER_SIZE 2048
- #else
- */
-#define PPP_UART_RX_BUFFER_SIZE 2048 // 硬件缓冲区大小(足够容纳MTU 1500 + PPP开销)
+/* 乒乓缓冲区配置 */
+#ifndef PPP_PING_PONG_BUFFER_SIZE
+#define PPP_PING_PONG_BUFFER_SIZE 2048 // 每个buffer大小(2KB)
+#endif
+
+#define PPP_PING_PONG_BUFFER_COUNT 2 // 乒乓buffer数量(固定2个)
+#define PPP_PING_PONG_TIMEOUT_MS 30 // buffer超时时间(30ms)
/* PPP中断消息队列配置 */
-#define PPP_IRQ_MSG_QUEUE_SIZE 128 // 消息队列深度(从32增加到128,应对突发流量)
+#define PPP_IRQ_MSG_QUEUE_SIZE 16 // 队列深度(乒乓模式下无需太大)
#define PPP_IRQ_PROCESS_TASK_PRIO 24 // 处理线程优先级
#define PPP_IRQ_PROCESS_STACK_SIZE 4096 // 处理线程栈大小
-/* PPP中断数据块结构 - 通过消息队列传递 */
+/* 乒乓缓冲区状态 */
+typedef enum {
+ PPP_BUFFER_STATE_EMPTY = 0, // 空闲,可以写入
+ PPP_BUFFER_STATE_FILLING, // 正在填充中
+ PPP_BUFFER_STATE_FULL, // 已满或超时,等待处理
+ PPP_BUFFER_STATE_PROCESSING // 正在处理中
+} ppp_buffer_state_t;
+
+/* 乒乓缓冲区结构 */
+typedef struct {
+ uint8_t data[PPP_PING_PONG_BUFFER_SIZE]; // 数据缓冲区
+ volatile uint32_t write_pos; // 当前写入位置
+ volatile ppp_buffer_state_t state; // 缓冲区状态
+ volatile uint32_t last_write_time; // 最后写入时间戳(ms)
+} ppp_ping_pong_buffer_t;
+
+/* PPP中断消息结构 - 通过消息队列传递buffer索引 */
typedef struct {
- uint8_t *data; // 动态分配的数据缓冲区指针
- uint16_t length; // 数据长度
-} ppp_irq_data_block_t;
+ uint8_t buffer_index; // 缓冲区索引(0或1)
+ uint16_t length; // 数据长度
+} ppp_irq_msg_t;
/* 中断使能标志 */
static bool g_ppp_interrupt_enabled = false;
static unsigned long g_ppp_irq_msg_queue = 0; // 消息队列ID (OSAL使用unsigned long)
static volatile bool g_ppp_irq_task_running = false;
+/* 乒乓缓冲区 */
+static ppp_ping_pong_buffer_t g_ping_pong_buffers[PPP_PING_PONG_BUFFER_COUNT];
+static volatile uint8_t g_current_buffer_index = 0; // 当前正在填充的buffer索引
+
+/* 定时器相关 */
+static osal_timer g_ppp_timeout_timer;
+static volatile bool g_ppp_timer_running = false;
+
/* 统计信息 */
static ppp_interrupt_stats_t g_ppp_interrupt_stats = {0};
+/* 扩展统计信息:乒乓缓冲区相关 */
+typedef struct {
+ uint32_t buffer_full_count; // buffer满次数
+ uint32_t buffer_timeout_count; // buffer超时flush次数
+ uint32_t buffer_switch_count; // buffer切换次数
+ uint32_t dropped_both_full; // 两个buffer都满导致丢弃的字节数
+} ppp_pingpong_stats_t;
+
+static ppp_pingpong_stats_t g_pingpong_stats = {0};
+
/* 外部函数声明(在ppp_lwip.c中实现) */
extern ppp_pcb* ppp_lwip_get_pcb(void);
extern bool ppp_lwip_is_initialized(void);
+/* 内部函数声明 */
+static uint32_t ppp_get_current_time_ms(void);
+static ppp_error_t ppp_flush_buffer(uint8_t buffer_index);
+static void ppp_timeout_timer_callback(unsigned long arg);
+
+/**
+ * @brief 获取当前时间戳(毫秒)
+ */
+static uint32_t ppp_get_current_time_ms(void)
+{
+ unsigned long long jiffies = osal_get_jiffies();
+ return (uint32_t)osal_jiffies_to_msecs((unsigned int)jiffies);
+}
+
/**
- * @brief PPP中断数据处理线程
- * @note 从消息队列接收数据块,调用pppos_input_tcpip(),然后释放缓冲区
+ * @brief PPP中断数据处理线程(乒乓缓冲区模式)
+ * @note 从消息队列接收buffer索引,直接访问乒乓缓冲区,调用pppos_input_tcpip()
*/
static void ppp_irq_process_thread(void *arg)
{
(void)arg;
- ppp_irq_data_block_t data_block;
- unsigned int buffer_size = sizeof(ppp_irq_data_block_t);
-
- PPP_DEBUG_INFO("PPP IRQ process thread started");
-
+ ppp_irq_msg_t msg;
+ unsigned int msg_size = sizeof(ppp_irq_msg_t);
+
+ PPP_DEBUG_INFO("PPP IRQ process thread started (ping-pong mode)");
+ #ifdef PPP_DO_SERIAL_TEST
+ const uint8_t print_data[] = "PPP IRQ process thread started (ping-pong mode)\n";
+ uapi_uart_write(SIO_UART_BUS, print_data, sizeof(print_data), 100);
+ #endif
while (g_ppp_irq_task_running) {
- // 从消息队列读取数据块(阻塞等待)
+ // 从消息队列读取buffer索引消息(阻塞等待)
int ret = osal_msg_queue_read_copy(
g_ppp_irq_msg_queue,
- &data_block,
- &buffer_size,
+ &msg,
+ &msg_size,
OSAL_MSGQ_WAIT_FOREVER
);
- if (ret != OSAL_SUCCESS) {
- // 读取失败,继续等待
+ if (ret != OSAL_SUCCESS || !g_ppp_irq_task_running) {
continue;
}
- // 检查数据有效性
- if (!data_block.data || data_block.length == 0) {
- PPP_DEBUG_ERROR("Invalid data block: data=%p, length=%u",
- data_block.data, data_block.length);
- if (data_block.data) {
- free(data_block.data); // 释放无效数据
- }
+ // 验证buffer索引
+ if (msg.buffer_index >= PPP_PING_PONG_BUFFER_COUNT) {
+ PPP_DEBUG_ERROR("Invalid buffer index: %u", msg.buffer_index);
+ continue;
+ }
+
+ ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[msg.buffer_index];
+
+ // 验证数据长度
+ if (msg.length == 0 || msg.length > PPP_PING_PONG_BUFFER_SIZE) {
+ PPP_DEBUG_ERROR("Invalid data length: %u", msg.length);
+ buffer->state = PPP_BUFFER_STATE_EMPTY;
+ buffer->write_pos = 0;
continue;
}
- // ===== 注意:不要在此检测"异常帧" =====
- // 7D 5D、7D 5E 等是PPP协议的正常转义序列,lwIP会自动解码:
- // 7D 5D → 0x7D (5D ^ 0x20 = 0x7D)
- // 7D 5E → 0x7E (5E ^ 0x20 = 0x7E)
- // 大量的7D 5D数据说明原始TCP数据包含很多0x7D字节,这是正常的!
- // 必须将所有数据交给lwIP的pppos_input处理,不能在此丢弃!
+ // 标记为处理中
+ buffer->state = PPP_BUFFER_STATE_PROCESSING;
- // 打印摘要(减少详细数据打印以提高性能)
- PPP_DEBUG_DEBUG("PPP Process: %u bytes", data_block.length);
+ // 打印摘要
+ PPP_DEBUG_DEBUG("PPP Process: buffer[%u], %u bytes", msg.buffer_index, msg.length);
- // 只打印前16字节(从64减到16,减少打印开销)
+ // 只打印前16字节
PPP_DEBUG_PRINT(" Data: ");
- for (int i = 0; i < data_block.length && i < 16; i++) {
- PPP_DEBUG_PRINT("%02X ", data_block.data[i]);
+ for (uint32_t i = 0; i < msg.length && i < 16; i++) {
+ PPP_DEBUG_PRINT("%02X ", buffer->data[i]);
}
- if (data_block.length > 16) {
- PPP_DEBUG_PRINT("... (%u more bytes)", data_block.length - 16);
+ if (msg.length > 16) {
+ PPP_DEBUG_PRINT("... (%u more bytes)", msg.length - 16);
}
PPP_DEBUG_PRINT("\n");
+#ifndef PPP_DO_SERIAL_TEST
// 获取PPP控制块
ppp_pcb *ppp_pcb_ptr = ppp_lwip_get_pcb();
if (!ppp_pcb_ptr || !ppp_lwip_is_initialized()) {
- PPP_DEBUG_WARN("PPP not initialized, dropping %u bytes", data_block.length);
- g_ppp_interrupt_stats.dropped_not_initialized += data_block.length;
- free(data_block.data);
+ PPP_DEBUG_WARN("PPP not initialized, dropping %u bytes", msg.length);
+ g_ppp_interrupt_stats.dropped_not_initialized += msg.length;
+ buffer->state = PPP_BUFFER_STATE_EMPTY;
+ buffer->write_pos = 0;
continue;
}
+
- // 调用lwIP的线程安全接口处理PPP数据(仅处理正常帧)
- // pppos_input_tcpip会:
- // 1. 分配pbuf
- // 2. 复制数据到pbuf
- // 3. 通过tcpip_inpkt()发送到TCPIP线程
- // 4. TCPIP线程自动调用pppos_input()进行协议解析
- err_t result = pppos_input_tcpip(ppp_pcb_ptr, data_block.data, data_block.length);
-
+ // 调用lwIP的线程安全接口处理PPP数据
+ // pppos_input_tcpip会复制数据到pbuf,所以我们可以安全地重用buffer
+ err_t result = pppos_input_tcpip(ppp_pcb_ptr, buffer->data, msg.length);
if (result != ERR_OK) {
PPP_DEBUG_WARN("pppos_input_tcpip failed: %d, dropped %u bytes",
- result, data_block.length);
- g_ppp_interrupt_stats.dropped_lwip_error += data_block.length;
+ result, msg.length);
+ g_ppp_interrupt_stats.dropped_lwip_error += msg.length;
g_ppp_interrupt_stats.lwip_error_count++;
} else {
- g_ppp_interrupt_stats.total_bytes_sent_to_lwip += data_block.length;
+ g_ppp_interrupt_stats.total_bytes_sent_to_lwip += msg.length;
g_ppp_interrupt_stats.success_count++;
}
-
- // 释放动态分配的缓冲区
- free(data_block.data);
+#else
+ err_t result = ERR_OK;
+ uint32_t write_result = uapi_uart_write(SIO_UART_BUS, buffer->data, msg.length, 100);
+ if (write_result != msg.length) {
+ PPP_DEBUG_WARN("uapi_uart_write failed: %d, dropped %u bytes", write_result, msg.length);
+ g_ppp_interrupt_stats.dropped_lwip_error += msg.length;
+ g_ppp_interrupt_stats.lwip_error_count++;
+ } else {
+ PPP_DEBUG_WARN("uapi_uart_write : %u bytes", msg.length);
+ g_ppp_interrupt_stats.total_bytes_sent_to_lwip += msg.length;
+ g_ppp_interrupt_stats.success_count++;
+ }
+#endif
+ // 释放buffer(标记为空闲)
+ buffer->state = PPP_BUFFER_STATE_EMPTY;
+ buffer->write_pos = 0;
}
PPP_DEBUG_INFO("PPP IRQ process thread exited");
}
/**
- * @brief UART接收中断回调函数(中断上下文)
- * @note 此函数在中断上下文中调用,必须极快返回(目标<7μs)
+ * @brief 刷新指定buffer到消息队列
+ * @param buffer_index 缓冲区索引
+ * @return PPP_ERR_OK 成功,其他值失败
+ */
+static ppp_error_t ppp_flush_buffer(uint8_t buffer_index)
+{
+ if (buffer_index >= PPP_PING_PONG_BUFFER_COUNT) {
+ return PPP_ERR_INVALID_PARAM;
+ }
+
+ ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[buffer_index];
+
+ // 只flush FILLING状态的buffer
+ if (buffer->state != PPP_BUFFER_STATE_FILLING || buffer->write_pos == 0) {
+ return PPP_ERR_OK; // 没有数据需要flush
+ }
+
+ // 标记为FULL状态
+ buffer->state = PPP_BUFFER_STATE_FULL;
+
+ // 准备消息
+ ppp_irq_msg_t msg = {
+ .buffer_index = buffer_index,
+ .length = (uint16_t)buffer->write_pos
+ };
+
+ // 发送到消息队列(非阻塞)
+ int ret = osal_msg_queue_write_copy(
+ g_ppp_irq_msg_queue,
+ &msg,
+ sizeof(ppp_irq_msg_t),
+ OSAL_MSGQ_NO_WAIT
+ );
+
+ if (ret != OSAL_SUCCESS) {
+ // 队列满,无法发送,保持FULL状态等待下次重试
+ g_ppp_interrupt_stats.dropped_queue_full += buffer->write_pos;
+ return PPP_ERR_UNKNOWN;
+ }
+
+ return PPP_ERR_OK;
+}
+
+/**
+ * @brief 定时器回调:检查buffer超时并flush
+ * @note 定时器上下文,应快速返回
+ * @param arg 定时器参数(未使用)
+ * @warning osal_timer默认是one-shot,必须在回调中手动重启实现周期性执行
+ */
+static void ppp_timeout_timer_callback(unsigned long arg)
+{
+ (void)arg;
+
+ // 增加定时器调用计数(用于诊断)
+ static volatile uint32_t timer_call_count = 0;
+ timer_call_count++;
+
+ // 每100次(约3秒)打印一次诊断信息
+ if ((timer_call_count % 100) == 1) {
+ PPP_DEBUG_INFO("[TIMER] Callback executed %u times, enabled=%d, running=%d",
+ timer_call_count, g_ppp_interrupt_enabled, g_ppp_timer_running);
+ }
+
+ if (!g_ppp_interrupt_enabled || !g_ppp_timer_running) {
+ return;
+ }
+
+ uint32_t current_time = ppp_get_current_time_ms();
+
+ // 检查所有buffer的超时状态
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i];
+
+ // 只处理FILLING状态的buffer
+ if (buffer->state == PPP_BUFFER_STATE_FILLING && buffer->write_pos > 0) {
+ uint32_t elapsed = current_time - buffer->last_write_time;
+
+ // 超时检查
+ if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) {
+ // 超时,flush buffer
+ PPP_DEBUG_DEBUG("[TIMER] Flushing buffer[%u] due to timeout (%u ms)", i, elapsed);
+ if (ppp_flush_buffer(i) == PPP_ERR_OK) {
+ g_pingpong_stats.buffer_timeout_count++;
+
+ // ⚠️ 关键修复:Timer flush后切换buffer(类似ISR中buffer满的逻辑)
+ // 只有当前flush的是正在使用的buffer时才切换
+ if (i == g_current_buffer_index) {
+ uint8_t next_idx = (i + 1) % PPP_PING_PONG_BUFFER_COUNT;
+ ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx];
+
+ if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ g_current_buffer_index = next_idx;
+ g_pingpong_stats.buffer_switch_count++;
+ }
+ // 如果next_buffer不是EMPTY,保持当前索引,等待其处理完成
+ }
+ }
+ }
+ }
+ }
+
+ // ⚠️ 关键修复:osal_timer是one-shot类型,必须手动重启以实现周期性执行
+ // 不重启的话,定时器只会执行一次!
+ if (g_ppp_timer_running) {
+ osal_timer_start(&g_ppp_timeout_timer);
+ }
+}
+
+/**
+ * @brief UART接收中断回调函数(乒乓缓冲区模式)
+ * @note 中断上下文,必须极快返回
*
- * 数据流:
- * 1. UART硬件接收数据 -> 2048字节硬件缓冲区
- * 2. 触发中断回调(本函数)
- * 3. 快速复制数据到动态分配的缓冲区(~1-3μs)
- * 4. 将缓冲区指针发送到消息队列(~1-2μs)
- * 5. 返回(总耗时<7μs,远低于之前的20-50μs)
- * 6. 处理线程接收数据并调用pppos_input_tcpip()
+ * 优化策略:
+ * 1. 写入当前buffer(memcpy,~60字节 < 1μs)
+ * 2. buffer满了直接flush并切换(~2μs)
+ * 3. 无malloc/free,减少内存碎片和延迟
+ * 4. 总耗时目标:< 5μs(比之前的7μs更快)
*/
void _ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error)
{
// 快速路径:参数校验
- if (error || !buffer || length == 0 || length > PPP_UART_RX_BUFFER_SIZE) {
+ if (error || !buffer || length == 0 || length > 200) { // 单次中断最多200字节
if (error) {
g_ppp_interrupt_stats.error_count++;
}
return;
}
- // 更新接收统计
+ // 更新统计(用于诊断 - 可以在统计打印中看到)
g_ppp_interrupt_stats.total_interrupts++;
g_ppp_interrupt_stats.total_bytes_received += length;
- // ⚠️ 中断中禁止打印!打印会导致严重延迟(1-10ms vs 目标<7μs)
- // 所有日志都在处理线程中打印
-
- // 动态分配缓冲区(使用malloc,~1-2μs)
- uint8_t *data_copy = (uint8_t *)malloc(length);
- if (!data_copy) {
- // 内存分配失败,丢弃数据
- g_ppp_interrupt_stats.dropped_malloc_fail += length;
- // ⚠️ 中断中禁止打印!统计已记录,错误会在线程中通过统计信息体现
- return;
+ // 诊断:每10次中断增加一个标记(避免频繁打印)
+ static volatile uint32_t irq_count_marker = 0;
+ if ((g_ppp_interrupt_stats.total_interrupts % 10) == 1) {
+ irq_count_marker = g_ppp_interrupt_stats.total_interrupts;
+ // 不能在中断中打印,但可以更新这个全局变量,在统计中查看
}
- // 快速复制数据(~1-3μs for 2KB @ 1GB/s memcpy speed)
- memcpy(data_copy, buffer, length);
+ // ⚠️ 中断中禁止打印!
- // 准备消息队列数据块
- ppp_irq_data_block_t data_block = {
- .data = data_copy,
- .length = length
- };
+ // 获取当前buffer
+ uint8_t current_idx = g_current_buffer_index;
+ ppp_ping_pong_buffer_t *current_buffer = &g_ping_pong_buffers[current_idx];
- // 发送到消息队列(非阻塞,~1-2μs)
- int ret = osal_msg_queue_write_copy(
- g_ppp_irq_msg_queue,
- &data_block,
- sizeof(ppp_irq_data_block_t),
- OSAL_MSGQ_NO_WAIT // 非阻塞
- );
+ // 尝试写入当前buffer
+ uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos;
- if (ret != OSAL_SUCCESS) {
- // 消息队列满,丢弃数据
- g_ppp_interrupt_stats.dropped_queue_full += length;
- free(data_copy);
- // ⚠️ 中断中禁止打印!统计已记录,队列满错误会在线程中通过统计信息体现
- return;
+ if (current_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ // buffer是空的,初始化为FILLING状态
+ current_buffer->state = PPP_BUFFER_STATE_FILLING;
+ current_buffer->write_pos = 0;
}
- // 成功入队
- // 总耗时估算:malloc(1-2μs) + memcpy(1-3μs) + queue_write(1-2μs) ≈ 3-7μs
- // ✓ 无打印,实际耗时符合设计目标
+ if (current_buffer->state == PPP_BUFFER_STATE_FILLING && length <= available) {
+ // 空间足够,直接写入
+ memcpy(current_buffer->data + current_buffer->write_pos, buffer, length);
+ current_buffer->write_pos += length;
+ current_buffer->last_write_time = ppp_get_current_time_ms();
+
+ // 检查是否满了
+ if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) {
+ // buffer满,flush
+ ppp_flush_buffer(current_idx);
+ g_pingpong_stats.buffer_full_count++;
+
+ // 切换到另一个buffer
+ uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT;
+ ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx];
+
+ if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ g_current_buffer_index = next_idx;
+ g_pingpong_stats.buffer_switch_count++;
+ }
+ // 如果next_buffer不是EMPTY,保持当前索引,下次数据会被丢弃
+ }
+ } else {
+ // 当前buffer空间不足或不可用,尝试切换到另一个buffer
+ uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT;
+ ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx];
+
+ if (next_buffer->state == PPP_BUFFER_STATE_EMPTY && length <= PPP_PING_PONG_BUFFER_SIZE) {
+ // 先flush当前buffer(如果有数据)
+ if (current_buffer->state == PPP_BUFFER_STATE_FILLING && current_buffer->write_pos > 0) {
+ ppp_flush_buffer(current_idx);
+ }
+
+ // 切换到新buffer
+ g_current_buffer_index = next_idx;
+ g_pingpong_stats.buffer_switch_count++;
+
+ // 写入新buffer
+ next_buffer->state = PPP_BUFFER_STATE_FILLING;
+ next_buffer->write_pos = 0;
+ memcpy(next_buffer->data, buffer, length);
+ next_buffer->write_pos = length;
+ next_buffer->last_write_time = ppp_get_current_time_ms();
+ } else {
+ // 两个buffer都不可用,丢弃数据
+ g_pingpong_stats.dropped_both_full += length;
+ }
+ }
}
void ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error)
}
/**
- * @brief 启用PPP UART中断接收
+ * @brief 启用PPP UART中断接收(乒乓缓冲区模式)
* @return PPP_ERR_OK 成功,其他值失败
*/
ppp_error_t ppp_interrupt_enable(void)
return PPP_ERR_OK;
}
- PPP_DEBUG_INFO("Enabling PPP UART interrupt mode");
+ PPP_DEBUG_INFO("Enabling PPP UART interrupt mode (ping-pong buffer)");
+
+ // 1. 初始化乒乓缓冲区
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ g_ping_pong_buffers[i].write_pos = 0;
+ g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY;
+ g_ping_pong_buffers[i].last_write_time = 0;
+ memset(g_ping_pong_buffers[i].data, 0, PPP_PING_PONG_BUFFER_SIZE);
+ }
+ g_current_buffer_index = 0;
+
+ PPP_DEBUG_INFO("Ping-pong buffers initialized: %d x %d bytes = %d KB total",
+ PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE,
+ (PPP_PING_PONG_BUFFER_COUNT * PPP_PING_PONG_BUFFER_SIZE) / 1024);
- // 1. 创建消息队列
+ // 2. 创建消息队列(只传递buffer索引,所以队列可以更小)
int ret = osal_msg_queue_create(
"ppp_irq_queue",
PPP_IRQ_MSG_QUEUE_SIZE,
&g_ppp_irq_msg_queue,
0,
- sizeof(ppp_irq_data_block_t)
+ sizeof(ppp_irq_msg_t)
);
if (ret != OSAL_SUCCESS) {
return PPP_ERR_MEMORY;
}
- PPP_DEBUG_INFO("Message queue created (depth=%d, item_size=%d)",
- PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_data_block_t));
+ PPP_DEBUG_INFO("Message queue created (depth=%d, item_size=%d bytes)",
+ PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_msg_t));
- // 2. 启动处理线程
+ // 3. 启动处理线程
g_ppp_irq_task_running = true;
osal_kthread_lock();
return PPP_ERR_MEMORY;
}
- PPP_DEBUG_INFO("Process thread created (stack=%d, prio=%d)",
- PPP_IRQ_PROCESS_STACK_SIZE, PPP_IRQ_PROCESS_TASK_PRIO);
+ PPP_DEBUG_INFO("Process thread created (stack=%d)", PPP_IRQ_PROCESS_STACK_SIZE);
+
+ // 4. 启动定时器(30ms周期检查buffer超时)
+ // 先设置timer的handler、data和interval
+ g_ppp_timeout_timer.handler = ppp_timeout_timer_callback;
+ g_ppp_timeout_timer.data = 0;
+ g_ppp_timeout_timer.interval = PPP_PING_PONG_TIMEOUT_MS;
+
+ ret = osal_timer_init(&g_ppp_timeout_timer);
+ if (ret != OSAL_SUCCESS) {
+ PPP_DEBUG_ERROR("Failed to init timeout timer: 0x%x", ret);
+ g_ppp_irq_task_running = false;
+ osal_msleep(100);
+ osal_msg_queue_delete(g_ppp_irq_msg_queue);
+ return PPP_ERR_MEMORY;
+ }
+
+ g_ppp_timer_running = true;
+ ret = osal_timer_start(&g_ppp_timeout_timer);
+ if (ret != OSAL_SUCCESS) {
+ PPP_DEBUG_ERROR("Failed to start timeout timer: 0x%x", ret);
+ g_ppp_timer_running = false;
+ g_ppp_irq_task_running = false;
+ osal_timer_destroy(&g_ppp_timeout_timer);
+ osal_msleep(100);
+ osal_msg_queue_delete(g_ppp_irq_msg_queue);
+ return PPP_ERR_MEMORY;
+ }
+
+ PPP_DEBUG_INFO("Timeout timer started (period=%d ms)", PPP_PING_PONG_TIMEOUT_MS);
- // 3. 注册UART中断回调
+ // 5. 注册UART中断回调
uint8_t uart_bus = ppp_serial_get_uart_bus();
+ PPP_DEBUG_INFO("Registering UART RX callback:");
+ PPP_DEBUG_INFO(" UART Bus: %d", uart_bus);
+ PPP_DEBUG_INFO(" Condition: 0x07 (FULL_OR_SUFFICIENT_DATA_OR_IDLE)");
+ PPP_DEBUG_INFO(" Threshold: 56 bytes");
+ PPP_DEBUG_INFO(" Callback: ppp_uart_rx_interrupt_handler @ %p", ppp_uart_rx_interrupt_handler);
+
+ // ⚠️ 关键修复:降低阈值从200到56字节,确保小数据包也能触发中断
+ // PPP LCP/IPCP响应通常只有30-50字节,200字节阈值太高会导致无法触发
ret = uapi_uart_register_rx_callback(
uart_bus,
0x07, // UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE
- PPP_UART_RX_BUFFER_SIZE,
+ 8, // 56字节阈值
ppp_uart_rx_interrupt_handler
);
+ PPP_DEBUG_INFO("uapi_uart_register_rx_callback returned: 0x%x (ERRCODE_SUCC=0x%x)",
+ ret, ERRCODE_SUCC);
+
if (ret != ERRCODE_SUCC) {
PPP_DEBUG_ERROR("Failed to register UART RX callback: 0x%x", ret);
// 清理资源
+ g_ppp_timer_running = false;
+ osal_timer_stop(&g_ppp_timeout_timer);
+ osal_timer_destroy(&g_ppp_timeout_timer);
g_ppp_irq_task_running = false;
- osal_msleep(100); // 等待线程退出
+ osal_msleep(100);
osal_msg_queue_delete(g_ppp_irq_msg_queue);
return PPP_ERR_UART;
}
+ PPP_DEBUG_INFO("UART RX callback registered successfully!");
+
g_ppp_interrupt_enabled = true;
- PPP_DEBUG_INFO("PPP UART interrupt enabled successfully");
- PPP_DEBUG_INFO(" Hardware buffer: %d bytes", PPP_UART_RX_BUFFER_SIZE);
- PPP_DEBUG_INFO(" Message queue: %d slots", PPP_IRQ_MSG_QUEUE_SIZE);
- PPP_DEBUG_INFO(" ISR latency: <7μs (malloc + memcpy + queue_write)");
- PPP_DEBUG_INFO(" Data flow: UART IRQ -> malloc/memcpy -> msg_queue -> process_thread -> pppos_input_tcpip");
+ PPP_DEBUG_INFO("PPP UART interrupt enabled successfully (ping-pong mode)");
+ PPP_DEBUG_INFO(" Ping-pong buffers: %d x %d KB",
+ PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE / 1024);
+ PPP_DEBUG_INFO(" Timeout: %d ms", PPP_PING_PONG_TIMEOUT_MS);
+ PPP_DEBUG_INFO(" Message queue: %d slots (only %d bytes per message)",
+ PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_msg_t));
+ PPP_DEBUG_INFO(" ISR latency: <5μs (memcpy + queue_write, no malloc!)");
+ PPP_DEBUG_INFO(" Data flow: UART IRQ -> ping-pong buffer -> timer/full flush -> msg_queue -> process_thread -> pppos_input_tcpip");
+ PPP_DEBUG_INFO(" DIAGNOSTIC: Watch for [TIMER] and interrupt stats in ppp_interrupt_print_stats()");
return PPP_ERR_OK;
}
return PPP_ERR_OK;
}
- PPP_DEBUG_INFO("Disabling PPP UART interrupt");
+ PPP_DEBUG_INFO("Disabling PPP UART interrupt (ping-pong mode)");
// 1. 注销UART中断回调
uint8_t uart_bus = ppp_serial_get_uart_bus();
uapi_uart_unregister_rx_callback(uart_bus);
- // 2. 停止处理线程
+ // 2. 停止定时器
+ if (g_ppp_timer_running) {
+ g_ppp_timer_running = false;
+ osal_timer_stop(&g_ppp_timeout_timer);
+ osal_timer_destroy(&g_ppp_timeout_timer);
+ PPP_DEBUG_INFO("Timeout timer stopped");
+ }
+
+ // 3. 停止处理线程
g_ppp_irq_task_running = false;
// 发送一个空消息以唤醒线程(如果阻塞在队列读取)
- ppp_irq_data_block_t empty_block = {0};
- osal_msg_queue_write_copy(g_ppp_irq_msg_queue, &empty_block,
- sizeof(ppp_irq_data_block_t), OSAL_MSGQ_NO_WAIT);
+ ppp_irq_msg_t empty_msg = {0};
+ osal_msg_queue_write_copy(g_ppp_irq_msg_queue, &empty_msg,
+ sizeof(ppp_irq_msg_t), OSAL_MSGQ_NO_WAIT);
// 等待线程退出
osal_msleep(200);
- // 3. 删除消息队列
+ // 4. 删除消息队列
osal_msg_queue_delete(g_ppp_irq_msg_queue);
+ // 5. 清空乒乓缓冲区
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ g_ping_pong_buffers[i].write_pos = 0;
+ g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY;
+ g_ping_pong_buffers[i].last_write_time = 0;
+ }
+ g_current_buffer_index = 0;
+
g_ppp_interrupt_enabled = false;
g_ppp_irq_process_task = NULL;
void ppp_interrupt_reset_stats(void)
{
memset(&g_ppp_interrupt_stats, 0, sizeof(ppp_interrupt_stats_t));
- PPP_DEBUG_INFO("PPP interrupt statistics reset");
+ memset(&g_pingpong_stats, 0, sizeof(ppp_pingpong_stats_t));
+ PPP_DEBUG_INFO("PPP interrupt statistics reset (including ping-pong stats)");
}
/**
- * @brief 打印中断统计信息
+ * @brief 打印中断统计信息(乒乓缓冲区模式)
*/
void ppp_interrupt_print_stats(void)
{
- PPP_DEBUG_INFO("=== PPP Interrupt Statistics ===");
+ PPP_DEBUG_INFO("=== PPP Interrupt Statistics (Ping-Pong Mode) ===");
PPP_DEBUG_INFO("Interrupt Status: %s", g_ppp_interrupt_enabled ? "ENABLED" : "DISABLED");
- PPP_DEBUG_INFO("Hardware Buffer: %d bytes", PPP_UART_RX_BUFFER_SIZE);
- PPP_DEBUG_INFO("Message Queue: %d slots (capacity: ~%d KB)",
- PPP_IRQ_MSG_QUEUE_SIZE,
- (PPP_IRQ_MSG_QUEUE_SIZE * PPP_UART_RX_BUFFER_SIZE) / 1024);
+ PPP_DEBUG_INFO("Ping-Pong Buffers: %d x %d bytes = %d KB total",
+ PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE,
+ (PPP_PING_PONG_BUFFER_COUNT * PPP_PING_PONG_BUFFER_SIZE) / 1024);
+ PPP_DEBUG_INFO("Timeout: %d ms", PPP_PING_PONG_TIMEOUT_MS);
+ PPP_DEBUG_INFO("Message Queue: %d slots (%d bytes per message)",
+ PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_msg_t));
+ PPP_DEBUG_INFO("");
+
+ // 乒乓缓冲区状态
+ PPP_DEBUG_INFO("Buffer Status:");
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ const char *state_str = "UNKNOWN";
+ switch (g_ping_pong_buffers[i].state) {
+ case PPP_BUFFER_STATE_EMPTY: state_str = "EMPTY"; break;
+ case PPP_BUFFER_STATE_FILLING: state_str = "FILLING"; break;
+ case PPP_BUFFER_STATE_FULL: state_str = "FULL"; break;
+ case PPP_BUFFER_STATE_PROCESSING: state_str = "PROCESSING"; break;
+ }
+ PPP_DEBUG_INFO(" Buffer[%u]: %s, %u/%u bytes%s",
+ i, state_str, g_ping_pong_buffers[i].write_pos, PPP_PING_PONG_BUFFER_SIZE,
+ (i == g_current_buffer_index) ? " (current)" : "");
+ }
+ PPP_DEBUG_INFO("");
+
+ // 乒乓缓冲区统计
+ PPP_DEBUG_INFO("Ping-Pong Buffer Statistics:");
+ PPP_DEBUG_INFO(" Buffer Full Count: %u", g_pingpong_stats.buffer_full_count);
+ PPP_DEBUG_INFO(" Buffer Timeout Count: %u", g_pingpong_stats.buffer_timeout_count);
+ PPP_DEBUG_INFO(" Buffer Switch Count: %u", g_pingpong_stats.buffer_switch_count);
+ PPP_DEBUG_INFO(" Dropped (Both Full): %u bytes ⚠️", g_pingpong_stats.dropped_both_full);
PPP_DEBUG_INFO("");
+
+ // 中断统计
PPP_DEBUG_INFO("Total Interrupts: %u", g_ppp_interrupt_stats.total_interrupts);
PPP_DEBUG_INFO(" - Success: %u", g_ppp_interrupt_stats.success_count);
PPP_DEBUG_INFO(" - Error: %u", g_ppp_interrupt_stats.error_count);
PPP_DEBUG_INFO(" - lwIP Error: %u", g_ppp_interrupt_stats.lwip_error_count);
PPP_DEBUG_INFO("");
+
+ // 字节统计
PPP_DEBUG_INFO("Total Bytes Received: %u", g_ppp_interrupt_stats.total_bytes_received);
PPP_DEBUG_INFO("Total Bytes Sent to lwIP: %u", g_ppp_interrupt_stats.total_bytes_sent_to_lwip);
PPP_DEBUG_INFO("");
+
+ // 丢弃统计
PPP_DEBUG_INFO("Dropped Bytes:");
PPP_DEBUG_INFO(" - Not Initialized: %u", g_ppp_interrupt_stats.dropped_not_initialized);
PPP_DEBUG_INFO(" - lwIP Error: %u", g_ppp_interrupt_stats.dropped_lwip_error);
- PPP_DEBUG_INFO(" - malloc Failed: %u", g_ppp_interrupt_stats.dropped_malloc_fail);
- PPP_DEBUG_INFO(" - Queue Full: %u ⚠️", g_ppp_interrupt_stats.dropped_queue_full);
+ PPP_DEBUG_INFO(" - Queue Full: %u", g_ppp_interrupt_stats.dropped_queue_full);
+ PPP_DEBUG_INFO(" - Both Buffers Full: %u ⚠️", g_pingpong_stats.dropped_both_full);
PPP_DEBUG_INFO("");
- // 计算丢包率和队列满率
+ // 计算丢包率
if (g_ppp_interrupt_stats.total_bytes_received > 0) {
uint32_t total_dropped = g_ppp_interrupt_stats.dropped_not_initialized +
g_ppp_interrupt_stats.dropped_lwip_error +
- g_ppp_interrupt_stats.dropped_malloc_fail +
- g_ppp_interrupt_stats.dropped_queue_full;
+ g_ppp_interrupt_stats.dropped_queue_full +
+ g_pingpong_stats.dropped_both_full;
uint32_t drop_rate = (total_dropped * 10000) / g_ppp_interrupt_stats.total_bytes_received;
- PPP_DEBUG_INFO("Drop Rate: %u.%02u%%", drop_rate / 100, drop_rate % 100);
+ PPP_DEBUG_INFO("Total Drop Rate: %u.%02u%%", drop_rate / 100, drop_rate % 100);
// 队列满导致的丢失率
if (g_ppp_interrupt_stats.dropped_queue_full > 0) {
uint32_t queue_full_rate = (g_ppp_interrupt_stats.dropped_queue_full * 10000) /
g_ppp_interrupt_stats.total_bytes_received;
- PPP_DEBUG_WARN("Queue Full Rate: %u.%02u%% ⚠️ (consider increasing queue size)",
- queue_full_rate / 100, queue_full_rate % 100);
+ PPP_DEBUG_INFO("Queue Full Rate: %u.%02u%%", queue_full_rate / 100, queue_full_rate % 100);
+ }
+
+ // 两个buffer都满导致的丢失率
+ if (g_pingpong_stats.dropped_both_full > 0) {
+ uint32_t both_full_rate = (g_pingpong_stats.dropped_both_full * 10000) /
+ g_ppp_interrupt_stats.total_bytes_received;
+ PPP_DEBUG_WARN("Both Buffers Full Rate: %u.%02u%% ⚠️ (processing too slow or buffers too small)",
+ both_full_rate / 100, both_full_rate % 100);
}
}
if (g_ppp_interrupt_stats.success_count > 0) {
uint32_t avg_bytes = g_ppp_interrupt_stats.total_bytes_sent_to_lwip /
g_ppp_interrupt_stats.success_count;
- PPP_DEBUG_INFO("Average bytes per packet: %u", avg_bytes);
+ PPP_DEBUG_INFO("Average bytes per message: %u", avg_bytes);
+ }
+
+ if (g_ppp_interrupt_stats.total_interrupts > 0) {
+ uint32_t avg_bytes_per_irq = g_ppp_interrupt_stats.total_bytes_received /
+ g_ppp_interrupt_stats.total_interrupts;
+ PPP_DEBUG_INFO("Average bytes per interrupt: %u", avg_bytes_per_irq);
}
- PPP_DEBUG_INFO("================================");
+ PPP_DEBUG_INFO("=================================================");
}
\ No newline at end of file
/**
* @file ppp_lwip_thread.c
- * @brief PPP接收线程实现 - 支持单线程和双线程模式
+ * @brief PPP接收线程实现 - 乒乓缓冲区轮询模式(双线程)
*
- * 线程模式配置:
- * - PPP_USE_DUAL_THREAD=1: 双线程模式(接收线程拼包 + 处理线程处理)
- * - PPP_USE_DUAL_THREAD=0: 单线程模式(接收线程直接处理)
+ * 设计思路:
+ * - 接收线程:轮询UART读取数据,填充到乒乓buffer
+ * - 处理线程:从消息队列获取buffer索引,调用lwIP处理
+ * - 用轮询替代中断,避免UART中断配置问题
+ * - 保留乒乓buffer的高效性和低延迟优势
*/
#include "ppp_api.h"
#include "soc_osal.h"
#include "schedule/osal_task.h"
#include "msgqueue/osal_msgqueue.h"
+#include "time/osal_timer.h"
#include "custom_utils.h"
#include <stdlib.h>
#include <string.h>
-/* PPP接收模式配置(中断相关代码已移除,使用独立的ppp_interrupt模块) */
+/* lwIP相关头文件 */
+#include "lwip/err.h"
+#include "netif/ppp/ppp.h"
+#include "netif/ppp/pppos.h"
-/* PPP线程模式配置 */
-#ifndef PPP_USE_DUAL_THREAD
-#define PPP_USE_DUAL_THREAD 0 // 默认使用双线程模式
+/* 乒乓缓冲区配置 */
+#ifndef PPP_PING_PONG_BUFFER_SIZE
+#define PPP_PING_PONG_BUFFER_SIZE 2048 // 每个buffer大小(2KB)
#endif
-#if PPP_USE_DUAL_THREAD
-#define PPP_THREAD_MODE_STR "DUAL_THREAD"
-#else
-#define PPP_THREAD_MODE_STR "SINGLE_THREAD"
-#endif
+#define PPP_PING_PONG_BUFFER_COUNT 2 // 乒乓buffer数量(固定2个)
+#define PPP_PING_PONG_TIMEOUT_MS 60 // buffer超时时间(30ms)
+
+/* PPP消息队列配置 */
+#define PPP_MSG_QUEUE_SIZE 16 // 队列深度
+#define PPP_PROCESS_TASK_PRIO 24 // 处理线程优先级
+#define PPP_PROCESS_STACK_SIZE 4096 // 处理线程栈大小
+#define PPP_RX_STACK_SIZE 4096 // 接收线程栈大小
-/* PPP帧队列相关(仅双线程模式使用) */
-#if PPP_USE_DUAL_THREAD
-#define PPP_FRAME_QUEUE_SIZE 32 // 队列大小
-#define PPP_MAX_FRAME_SIZE 2048 // 从1500增加到2048
+/* 乒乓缓冲区状态 */
+typedef enum {
+ PPP_BUFFER_STATE_EMPTY = 0, // 空闲,可以写入
+ PPP_BUFFER_STATE_FILLING, // 正在填充中
+ PPP_BUFFER_STATE_FULL, // 已满或超时,等待处理
+ PPP_BUFFER_STATE_PROCESSING // 正在处理中
+} ppp_buffer_state_t;
-/* PPP帧结构(仅双线程模式使用) */
+/* 乒乓缓冲区结构 */
typedef struct {
- uint8_t *data; // 帧数据
- uint32_t length; // 帧长度
-} ppp_frame_t;
-#endif
+ uint8_t data[PPP_PING_PONG_BUFFER_SIZE]; // 数据缓冲区
+ volatile uint32_t write_pos; // 当前写入位置
+ volatile ppp_buffer_state_t state; // 缓冲区状态
+ volatile uint32_t last_write_time; // 最后写入时间戳(ms)
+} ppp_ping_pong_buffer_t;
-/* PPP字节丢弃统计结构已在ppp_types.h中定义 */
+/* PPP消息结构 - 通过消息队列传递buffer索引 */
+typedef struct {
+ uint8_t buffer_index; // 缓冲区索引(0或1)
+ uint16_t length; // 数据长度
+} ppp_msg_t;
/* PPP线程相关 */
static uint32_t g_ppp_rx_thread_handle = 0; // 接收线程句柄
-#if PPP_USE_DUAL_THREAD
-static uint32_t g_ppp_process_thread_handle = 0; // 处理线程句柄(仅双线程模式)
-#endif
+static uint32_t g_ppp_process_thread_handle = 0; // 处理线程句柄
static volatile bool g_ppp_rx_thread_running = false;
-#if PPP_USE_DUAL_THREAD
static volatile bool g_ppp_process_thread_running = false;
-#endif
static volatile bool g_ppp_rx_thread_completed = false;
-#if PPP_USE_DUAL_THREAD
static volatile bool g_ppp_process_thread_completed = false;
-#endif
-/* PPP帧队列(仅双线程模式使用) */
-#if PPP_USE_DUAL_THREAD
-static unsigned long g_ppp_frame_queue = 0xFFFFFFFFu;
-#endif
+/* 消息队列 */
+static unsigned long g_ppp_msg_queue = 0;
+
+/* 乒乓缓冲区 */
+static ppp_ping_pong_buffer_t g_ping_pong_buffers[PPP_PING_PONG_BUFFER_COUNT];
+static volatile uint8_t g_current_buffer_index = 0; // 当前正在填充的buffer索引
+
+/* 定时器相关 */
+static osal_timer g_ppp_timeout_timer;
+static volatile bool g_ppp_timer_running = false;
-/* PPP丢弃统计 */
-static ppp_discard_stats_t g_ppp_discard_stats = {0};
+/* 统计信息 */
+typedef struct {
+ uint32_t total_bytes_received; // 总接收字节数
+ uint32_t total_bytes_processed; // 总处理字节数
+ uint32_t total_bytes_discarded; // 总丢弃字节数
+ uint32_t buffer_full_count; // buffer满次数
+ uint32_t buffer_timeout_count; // buffer超时flush次数
+ uint32_t buffer_switch_count; // buffer切换次数
+ uint32_t dropped_both_full; // 两个buffer都满导致丢弃的字节数
+ uint32_t dropped_queue_full; // 队列满导致丢弃的字节数
+ uint32_t lwip_error_count; // lwIP错误次数
+ uint32_t read_count; // UART读取次数
+ uint32_t read_empty_count; // UART读取为空次数
+} ppp_polling_stats_t;
+
+static ppp_polling_stats_t g_ppp_stats = {0};
+
+/* 外部函数声明 */
+extern ppp_pcb* ppp_lwip_get_pcb(void);
+extern bool ppp_lwip_is_initialized(void);
+
+/* 内部函数声明 */
+static uint32_t ppp_get_current_time_ms(void);
+static ppp_error_t ppp_flush_buffer(uint8_t buffer_index);
+static void ppp_timeout_timer_callback(unsigned long arg);
-static ppp_error_t ppp_process_data_block(const uint8_t *data, uint32_t length);
+/**
+ * @brief 获取当前时间戳(毫秒)
+ */
+static uint32_t ppp_get_current_time_ms(void)
+{
+ unsigned long long jiffies = osal_get_jiffies();
+ return (uint32_t)osal_jiffies_to_msecs((unsigned int)jiffies);
+}
/**
- * @brief 处理PPP数据并打印详细信息(单线程模式专用)
+ * @brief 刷新指定buffer到消息队列
+ * @param buffer_index 缓冲区索引
+ * @return PPP_ERR_OK 成功,其他值失败
*/
-static ppp_error_t ppp_process_and_print_data(const uint8_t *data, uint32_t length)
+static ppp_error_t ppp_flush_buffer(uint8_t buffer_index)
{
- if (!data || length == 0) {
+ if (buffer_index >= PPP_PING_PONG_BUFFER_COUNT) {
return PPP_ERR_INVALID_PARAM;
}
- // 打印详细的接收数据(在处理阶段,不影响接收效率)
- PPP_DEBUG_INFO("PPP Processing: %u bytes", length);
- PPP_DEBUG_PRINT("PPP RX Data: ");
- for (uint32_t i = 0; i < length; i++) {
- PPP_DEBUG_PRINT("%02X ", data[i]);
- // 每16字节换行,便于阅读
- if ((i + 1) % 16 == 0) {
- PPP_DEBUG_PRINT("\n ");
- }
+ ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[buffer_index];
+
+ // 只flush FILLING状态的buffer
+ if (buffer->state != PPP_BUFFER_STATE_FILLING || buffer->write_pos == 0) {
+ return PPP_ERR_OK; // 没有数据需要flush
}
- PPP_DEBUG_PRINT("\n");
-
- // 调用lwIP处理
- ppp_error_t lwip_ret = ppp_lwip_input(data, length);
- if (lwip_ret != PPP_ERR_OK) {
- PPP_DEBUG_WARN("lwIP input failed: %d", lwip_ret);
- return lwip_ret;
- } else {
- PPP_DEBUG_DEBUG("PPP data processed successfully by lwIP");
- return PPP_ERR_OK;
+
+ // 标记为FULL状态
+ buffer->state = PPP_BUFFER_STATE_FULL;
+
+ // 准备消息
+ ppp_msg_t msg = {
+ .buffer_index = buffer_index,
+ .length = (uint16_t)buffer->write_pos
+ };
+
+ // 发送到消息队列(非阻塞)
+ int ret = osal_msg_queue_write_copy(
+ g_ppp_msg_queue,
+ &msg,
+ sizeof(ppp_msg_t),
+ OSAL_MSGQ_NO_WAIT
+ );
+
+ if (ret != OSAL_SUCCESS) {
+ // 队列满,无法发送
+ g_ppp_stats.dropped_queue_full += buffer->write_pos;
+ buffer->state = PPP_BUFFER_STATE_FILLING; // 恢复状态,等待下次重试
+ return PPP_ERR_UNKNOWN;
}
+
+ return PPP_ERR_OK;
}
-#if PPP_USE_DUAL_THREAD
/**
- * @brief å¤\84ç\90\86æ\8e¥æ\94¶å\88°ç\9a\84æ\95°æ\8d®å\9d\97ï¼\88å\8f\8c线ç¨\8b模å¼\8fï¼\89
+ * @brief å®\9aæ\97¶å\99¨å\9b\9eè°\83ï¼\9aæ£\80æ\9f¥bufferè¶\85æ\97¶å¹¶flush
*/
-static ppp_error_t ppp_process_data_block(const uint8_t *data, uint32_t length)
+static void ppp_timeout_timer_callback(unsigned long arg)
{
- if (!data || length == 0) {
- return PPP_ERR_INVALID_PARAM;
- }
+ (void)arg;
- // 分配帧结构
- ppp_frame_t *frame = (ppp_frame_t *)malloc(sizeof(ppp_frame_t));
- if (!frame) {
- PPP_DEBUG_ERROR("Failed to allocate frame structure");
-
- // 统计内存分配失败丢弃
- g_ppp_discard_stats.total_bytes_discarded += length;
- g_ppp_discard_stats.malloc_fail_bytes += length;
-
- return PPP_ERR_MEMORY;
+ if (!g_ppp_timer_running) {
+ return;
}
- // 分配帧数据
- frame->data = (uint8_t *)malloc(length);
- if (!frame->data) {
- PPP_DEBUG_ERROR("Failed to allocate frame data");
-
- // 统计内存分配失败丢弃
- g_ppp_discard_stats.total_bytes_discarded += length;
- g_ppp_discard_stats.malloc_fail_bytes += length;
+ uint32_t current_time = ppp_get_current_time_ms();
+
+ // 检查所有buffer的超时状态
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i];
- free(frame);
- return PPP_ERR_MEMORY;
+ // 只处理FILLING状态的buffer
+ if (buffer->state == PPP_BUFFER_STATE_FILLING && buffer->write_pos > 0) {
+ uint32_t elapsed = current_time - buffer->last_write_time;
+
+ if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) {
+ PPP_DEBUG_DEBUG("[TIMER] Flushing buffer[%u] due to timeout (%u ms)", i, elapsed);
+
+ if (ppp_flush_buffer(i) == PPP_ERR_OK) {
+ g_ppp_stats.buffer_timeout_count++;
+
+ // Timer flush后切换buffer
+ if (i == g_current_buffer_index) {
+ uint8_t next_idx = (i + 1) % PPP_PING_PONG_BUFFER_COUNT;
+ ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx];
+
+ if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ g_current_buffer_index = next_idx;
+ g_ppp_stats.buffer_switch_count++;
+ }
+ }
+ }
+ }
+ }
}
- // 复制数据
- memcpy(frame->data, data, length);
- frame->length = length;
+ // osal_timer是one-shot类型,必须手动重启
+ if (g_ppp_timer_running) {
+ osal_timer_start(&g_ppp_timeout_timer);
+ }
+}
+
+/**
+ * @brief PPP数据处理线程 - 从乒乓buffer处理数据
+ */
+static void ppp_lwip_process_thread(void *arg)
+{
+ (void)arg;
+ ppp_msg_t msg;
+ unsigned int msg_size = sizeof(ppp_msg_t);
- // 发送到队列
- int ret = osal_msg_queue_write_copy(g_ppp_frame_queue, &frame, sizeof(ppp_frame_t *), 0);
- if (ret != OSAL_SUCCESS) {
- PPP_DEBUG_WARN("Failed to send data to queue: %d, dropping data", ret);
+ PPP_DEBUG_INFO("PPP process thread started (ping-pong polling mode)");
+ #ifdef PPP_DO_SERIAL_TEST
+ const uint8_t print_data[] = "PPP process thread started (ping-pong polling mode)\n";
+ uapi_uart_write(SIO_UART_BUS, print_data, sizeof(print_data), 100);
+ #endif
+ while (g_ppp_process_thread_running) {
+ // 从消息队列读取buffer索引消息(阻塞等待)
+ int ret = osal_msg_queue_read_copy(
+ g_ppp_msg_queue,
+ &msg,
+ &msg_size,
+ OSAL_MSGQ_WAIT_FOREVER
+ );
- // 统计队列满丢弃
- g_ppp_discard_stats.total_bytes_discarded += length;
- g_ppp_discard_stats.queue_full_bytes += length;
+ if (ret != OSAL_SUCCESS || !g_ppp_process_thread_running) {
+ continue;
+ }
- free(frame->data);
- free(frame);
- return PPP_ERR_UNKNOWN;
+ // 验证buffer索引
+ if (msg.buffer_index >= PPP_PING_PONG_BUFFER_COUNT) {
+ PPP_DEBUG_ERROR("Invalid buffer index: %u", msg.buffer_index);
+ continue;
+ }
+
+ ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[msg.buffer_index];
+
+ // 验证数据长度
+ if (msg.length == 0 || msg.length > PPP_PING_PONG_BUFFER_SIZE) {
+ PPP_DEBUG_ERROR("Invalid data length: %u", msg.length);
+ buffer->state = PPP_BUFFER_STATE_EMPTY;
+ buffer->write_pos = 0;
+ continue;
+ }
+
+ // 标记为处理中
+ buffer->state = PPP_BUFFER_STATE_PROCESSING;
+
+ // 打印摘要
+ PPP_DEBUG_DEBUG("PPP Process: buffer[%u], %u bytes", msg.buffer_index, msg.length);
+
+ // 只打印前16字节
+ PPP_DEBUG_PRINT(" Data: ");
+ for (uint32_t i = 0; i < msg.length && i < 16; i++) {
+ PPP_DEBUG_PRINT("%02X ", buffer->data[i]);
+ }
+ if (msg.length > 16) {
+ PPP_DEBUG_PRINT("... (%u more bytes)", msg.length - 16);
+ }
+ PPP_DEBUG_PRINT("\n");
+
+#ifndef PPP_DO_SERIAL_TEST
+ // 获取PPP控制块
+ ppp_pcb *ppp_pcb_ptr = ppp_lwip_get_pcb();
+ if (!ppp_pcb_ptr || !ppp_lwip_is_initialized()) {
+ PPP_DEBUG_WARN("PPP not initialized, dropping %u bytes", msg.length);
+ g_ppp_stats.total_bytes_discarded += msg.length;
+ buffer->state = PPP_BUFFER_STATE_EMPTY;
+ buffer->write_pos = 0;
+ continue;
+ }
+
+ // 调用lwIP的线程安全接口处理PPP数据
+ // pppos_input_tcpip会复制数据到pbuf,所以我们可以安全地重用buffer
+ err_t result = pppos_input_tcpip(ppp_pcb_ptr, buffer->data, msg.length);
+
+ if (result != ERR_OK) {
+ PPP_DEBUG_WARN("pppos_input_tcpip failed: %d, dropped %u bytes",
+ result, msg.length);
+ g_ppp_stats.total_bytes_discarded += msg.length;
+ g_ppp_stats.lwip_error_count++;
+ } else {
+ g_ppp_stats.total_bytes_processed += msg.length;
+ }
+#else
+ err_t result = ERR_OK;
+ uint32_t write_result = uapi_uart_write(SIO_UART_BUS, buffer->data, msg.length, 100);
+ if (write_result != msg.length) {
+ PPP_DEBUG_WARN("uapi_uart_write failed: %d, dropped %u bytes", write_result, msg.length);
+ g_ppp_stats.total_bytes_discarded += msg.length;
+ g_ppp_stats.lwip_error_count++;
+ } else {
+ PPP_DEBUG_WARN("uapi_uart_write : %u bytes", msg.length);
+ g_ppp_stats.total_bytes_processed += msg.length;
+ }
+#endif
+ // 释放buffer(标记为空闲)
+ buffer->state = PPP_BUFFER_STATE_EMPTY;
+ buffer->write_pos = 0;
}
- PPP_DEBUG_DEBUG("Sent data block to queue: %u bytes", length);
- return PPP_ERR_OK;
+ PPP_DEBUG_INFO("PPP process thread completed");
+ g_ppp_process_thread_completed = true;
}
-#endif
/**
- * @brief PPP数据接收线程 - 轮询模式(类似SLIP的高效策略)
+ * @brief PPP数据接收线程 - 轮询UART并填充到乒乓buffer
*/
static void ppp_lwip_receive_thread(void *arg)
{
(void)arg;
- PPP_DEBUG_INFO("PPP receive thread started (polling mode)");
+ PPP_DEBUG_INFO("PPP receive thread started (ping-pong polling mode)");
- // 分配串口接收缓冲区
- uint8_t *rx_buffer = (uint8_t *)malloc(PPP_RX_BUFFER_SIZE);
+ // 分配临时读取缓冲区(每次最多读取512字节)
+ const uint32_t read_chunk_size = 1;
+ uint8_t *rx_buffer = (uint8_t *)malloc(read_chunk_size);
if (!rx_buffer) {
PPP_DEBUG_ERROR("PPP RX buffer alloc failed");
g_ppp_rx_thread_completed = true;
return;
}
- uint32_t bytes_read = 0;
uint32_t total_bytes = 0;
- uint32_t idle_count = 0;
+ uint32_t consecutive_empty = 0;
- PPP_DEBUG_INFO("PPP receive thread: allocated %d bytes RX buffer", PPP_RX_BUFFER_SIZE);
+ PPP_DEBUG_INFO("PPP receive thread: allocated %d bytes temp buffer", read_chunk_size);
- // 主循环:类似SLIP的连续读取策略
+ // 主循环:连续轮询UART读取
while (g_ppp_rx_thread_running) {
- uint32_t batch_bytes = 0;
- uint32_t read_count = 0;
+ uint32_t bytes_read = 0;
+ ppp_error_t ret = ppp_serial_tryread(rx_buffer, read_chunk_size, &bytes_read);
- // 连续读取直到无数据,类似SLIP的slipif_poll
- while (g_ppp_rx_thread_running) {
- ppp_error_t ret = ppp_serial_tryread(rx_buffer, PPP_RX_BUFFER_SIZE, &bytes_read);
+ if (ret == PPP_ERR_OK && bytes_read > 0) {
+ // 成功读取数据
+ total_bytes += bytes_read;
+ consecutive_empty = 0;
+ g_ppp_stats.read_count++;
+ g_ppp_stats.total_bytes_received += bytes_read;
- if (ret == PPP_ERR_OK && bytes_read > 0) {
- total_bytes += bytes_read;
- batch_bytes += bytes_read;
- read_count++;
-
- // 更新接收字节统计
- g_ppp_discard_stats.total_bytes_received += bytes_read;
+ PPP_DEBUG_DEBUG("PPP RX: Got %u bytes", bytes_read);
+
+ // 填充到乒乓buffer
+ uint32_t processed = 0;
+ while (processed < bytes_read && g_ppp_rx_thread_running) {
+ uint8_t current_idx = g_current_buffer_index;
+ ppp_ping_pong_buffer_t *current_buffer = &g_ping_pong_buffers[current_idx];
- PPP_DEBUG_DEBUG("PPP RX: Got %u bytes (read #%u)", bytes_read, read_count);
-
-#if PPP_USE_DUAL_THREAD
- // 双线程模式:发送到队列由处理线程处理
- ppp_error_t process_ret = ppp_process_data_block(rx_buffer, bytes_read);
- if (process_ret != PPP_ERR_OK) {
- PPP_DEBUG_WARN("Failed to process data block: %d", process_ret);
+ // 如果buffer是空的,初始化为FILLING状态
+ if (current_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ current_buffer->state = PPP_BUFFER_STATE_FILLING;
+ current_buffer->write_pos = 0;
}
-#else
- // 单线程模式:调用处理函数(包含详细数据打印)
- ppp_error_t process_ret = ppp_process_and_print_data(rx_buffer, bytes_read);
- if (process_ret != PPP_ERR_OK) {
- g_ppp_discard_stats.total_bytes_discarded += bytes_read;
+
+ // 尝试写入当前buffer
+ uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos;
+ uint32_t to_write = bytes_read - processed;
+
+ if (current_buffer->state == PPP_BUFFER_STATE_FILLING && to_write <= available) {
+ // 空间足够,直接写入
+ memcpy(current_buffer->data + current_buffer->write_pos,
+ rx_buffer + processed, to_write);
+ current_buffer->write_pos += to_write;
+ current_buffer->last_write_time = ppp_get_current_time_ms();
+ processed += to_write;
+
+ // 检查是否满了
+ if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) {
+ // buffer满,flush
+ ppp_flush_buffer(current_idx);
+ g_ppp_stats.buffer_full_count++;
+
+ // 切换到另一个buffer
+ uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT;
+ ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx];
+
+ if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ g_current_buffer_index = next_idx;
+ g_ppp_stats.buffer_switch_count++;
+ }
+ }
} else {
- g_ppp_discard_stats.total_bytes_processed += bytes_read;
- }
-#endif
- } else if (ret != PPP_ERR_OK) {
- // 读取错误
- if (!g_ppp_rx_thread_running) {
- PPP_DEBUG_DEBUG("PPP receive thread stopping, ignoring read error");
- break;
+ // 当前buffer空间不足或不可用,尝试切换
+ uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT;
+ ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx];
+
+ if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) {
+ // 先flush当前buffer(如果有数据)
+ if (current_buffer->state == PPP_BUFFER_STATE_FILLING &&
+ current_buffer->write_pos > 0) {
+ ppp_flush_buffer(current_idx);
+ }
+
+ // 切换到新buffer
+ g_current_buffer_index = next_idx;
+ g_ppp_stats.buffer_switch_count++;
+
+ // 继续处理剩余数据(下次循环会写入新buffer)
+ } else {
+ // 两个buffer都不可用,丢弃数据
+ uint32_t discarded = bytes_read - processed;
+ g_ppp_stats.dropped_both_full += discarded;
+ g_ppp_stats.total_bytes_discarded += discarded;
+ PPP_DEBUG_WARN("Both buffers full, discarding %u bytes", discarded);
+ break;
+ }
}
- PPP_DEBUG_ERROR("PPP serial tryread error: %d", ret);
- break;
- } else {
- // 无数据,退出连续读取循环
+ }
+
+ // 有数据,不休眠,继续读取
+ } else if (ret != PPP_ERR_OK) {
+ // 读取错误
+ if (!g_ppp_rx_thread_running) {
break;
}
- }
-
- if (batch_bytes > 0) {
- // æ\9c\89æ\95°æ\8d®å¤\84ç\90\86ï¼\8cé\87\8d置空é\97²è®¡æ\95°å\99¨
- idle_count = 0;
- PPP_DEBUG_DEBUG("PPP batch: %u bytes in %u reads", batch_bytes, read_count);
+ PPP_DEBUG_ERROR("PPP serial tryread error: %d", ret);
+ osal_msleep(10); // 错误时稍微休眠
+ } else {
+ // æ\97 æ\95°æ\8d®
+ consecutive_empty++;
+ g_ppp_stats.read_empty_count++;
- // 短暂休眠后继续,保持高响应性
+ // 无数据时休眠1ms
osal_msleep(1);
- } else {
- // 无数据时使用类似SLIP的休眠策略
- idle_count++;
- osal_msleep(20); // 类似SLIP的20ms休眠,不打印避免日志干扰
}
- // 定期打印统计信息(减少频率)
- if ((total_bytes % 4096) == 0 && total_bytes > 0) {
+ // 定期打印统计信息
+ if ((total_bytes % 10240) == 0 && total_bytes > 0) {
PPP_DEBUG_INFO("PPP RX: %u total bytes received", total_bytes);
}
}
g_ppp_rx_thread_completed = true;
}
-#if PPP_USE_DUAL_THREAD
-/**
- * @brief PPP数据处理线程 - 从队列获取数据块并处理(仅双线程模式)
- */
-static void ppp_lwip_process_thread(void *arg)
-{
- (void)arg;
-
- PPP_DEBUG_INFO("PPP process thread started");
-
- uint32_t processed_blocks = 0;
- uint32_t processed_bytes = 0;
-
- // 主循环:从队列获取数据块并处理
- while (g_ppp_process_thread_running) {
- ppp_frame_t *data_block = NULL;
- uint32_t msg_size = sizeof(ppp_frame_t *);
-
- // 从队列获取数据块(超时100ms,避免无限阻塞)
- int ret = osal_msg_queue_read_copy(g_ppp_frame_queue, &data_block, &msg_size, OSAL_WAIT_FOREVER);
-
- if (ret == OSAL_SUCCESS && data_block) {
- // 处理数据块
- PPP_DEBUG_DEBUG("Processing data block: %u bytes", data_block->length);
-
- PPP_DEBUG_PRINT("PPP RX Data: ");
- for (uint32_t i = 0; i < data_block->length ; i++) {
- PPP_DEBUG_PRINT("%02X ", data_block->data[i]);
- }
- PPP_DEBUG_PRINT("\n");
- // 调用lwIP流式处理
- ppp_error_t process_ret = ppp_process_and_print_data(data_block->data, data_block->length);
-
-
- // 更新统计
- processed_blocks++;
- processed_bytes += data_block->length;
- if (process_ret != PPP_ERR_OK) {
- g_ppp_discard_stats.total_bytes_discarded += data_block->length;
- } else {
- g_ppp_discard_stats.total_bytes_processed += data_block->length;
- }
-
- // 释放数据块内存
- free(data_block->data);
- free(data_block);
-
- // 定期打印统计
- if ((processed_blocks % 50) == 0) {
- PPP_DEBUG_DEBUG("PPP Process: %u blocks, %u bytes processed",
- processed_blocks, processed_bytes);
- }
- } else if (ret != OSAL_SUCCESS) {
- // 超时或其他错误
- if (!g_ppp_process_thread_running) {
- PPP_DEBUG_DEBUG("PPP process thread stopping");
- break;
- }
- // 超时是正常的,不需要警告。只有真正的错误才需要警告
- // OSAL_FAILURE (-1) 通常表示超时,这是正常的
- // 这里我们不再对超时进行警告,因为这是正常的队列空闲状态
- PPP_DEBUG_DEBUG("Queue read timeout (normal when no data), ret: %d", ret);
- }
- // 超时是正常的,继续循环
- }
-
- // 清理队列中剩余的数据块
- ppp_frame_t *data_block = NULL;
- uint32_t msg_size = sizeof(ppp_frame_t *);
- while (osal_msg_queue_read_copy(g_ppp_frame_queue, &data_block, &msg_size, 0) == OSAL_SUCCESS) {
- if (data_block) {
- free(data_block->data);
- free(data_block);
- }
- }
-
- PPP_DEBUG_INFO("PPP process thread completed, processed %u blocks, %u bytes",
- processed_blocks, processed_bytes);
- g_ppp_process_thread_completed = true;
-}
-#endif /* PPP_USE_DUAL_THREAD */
-
/**
* @brief 启动PPP接收和处理线程
*/
ppp_error_t ppp_lwip_start_receive_thread(void)
{
-#if PPP_USE_DUAL_THREAD
if (g_ppp_rx_thread_handle != 0 || g_ppp_process_thread_handle != 0) {
PPP_DEBUG_WARN("PPP threads already running");
return PPP_ERR_INVALID_STATE;
}
- PPP_DEBUG_INFO("Starting PPP receive and process threads (" PPP_THREAD_MODE_STR " mode)");
-#else
- if (g_ppp_rx_thread_handle != 0) {
- PPP_DEBUG_WARN("PPP thread already running");
- return PPP_ERR_INVALID_STATE;
- }
-
- PPP_DEBUG_INFO("Starting PPP receive thread (" PPP_THREAD_MODE_STR " mode)");
-#endif
+ PPP_DEBUG_INFO("Starting PPP receive and process threads (ping-pong polling mode)");
// 重置完成标志
g_ppp_rx_thread_completed = false;
-
-#if PPP_USE_DUAL_THREAD
g_ppp_process_thread_completed = false;
- // 创建数据队列(仅双线程模式)
- int queue_ret = osal_msg_queue_create("ppp_data", PPP_FRAME_QUEUE_SIZE,
- &g_ppp_frame_queue, 0, sizeof(ppp_frame_t *));
- if (queue_ret != OSAL_SUCCESS) {
- PPP_DEBUG_ERROR("Failed to create PPP data queue: %d", queue_ret);
+ // 1. 初始化乒乓缓冲区
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ g_ping_pong_buffers[i].write_pos = 0;
+ g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY;
+ g_ping_pong_buffers[i].last_write_time = 0;
+ memset(g_ping_pong_buffers[i].data, 0, PPP_PING_PONG_BUFFER_SIZE);
+ }
+ g_current_buffer_index = 0;
+
+ PPP_DEBUG_INFO("Ping-pong buffers initialized: %d x %d bytes = %d KB total",
+ PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE,
+ (PPP_PING_PONG_BUFFER_COUNT * PPP_PING_PONG_BUFFER_SIZE) / 1024);
+
+ // 2. 创建消息队列
+ int ret = osal_msg_queue_create(
+ "ppp_queue",
+ PPP_MSG_QUEUE_SIZE,
+ &g_ppp_msg_queue,
+ 0,
+ sizeof(ppp_msg_t)
+ );
+
+ if (ret != OSAL_SUCCESS) {
+ PPP_DEBUG_ERROR("Failed to create message queue: 0x%x", ret);
return PPP_ERR_MEMORY;
}
- // 启动处理线程
+ PPP_DEBUG_INFO("Message queue created (depth=%d, item_size=%d bytes)",
+ PPP_MSG_QUEUE_SIZE, sizeof(ppp_msg_t));
+
+ // 3. 启动定时器(30ms周期检查buffer超时)
+ g_ppp_timeout_timer.handler = ppp_timeout_timer_callback;
+ g_ppp_timeout_timer.data = 0;
+ g_ppp_timeout_timer.interval = PPP_PING_PONG_TIMEOUT_MS;
+
+ ret = osal_timer_init(&g_ppp_timeout_timer);
+ if (ret != OSAL_SUCCESS) {
+ PPP_DEBUG_ERROR("Failed to init timeout timer: 0x%x", ret);
+ osal_msg_queue_delete(g_ppp_msg_queue);
+ return PPP_ERR_MEMORY;
+ }
+
+ g_ppp_timer_running = true;
+ ret = osal_timer_start(&g_ppp_timeout_timer);
+ if (ret != OSAL_SUCCESS) {
+ PPP_DEBUG_ERROR("Failed to start timeout timer: 0x%x", ret);
+ g_ppp_timer_running = false;
+ osal_timer_destroy(&g_ppp_timeout_timer);
+ osal_msg_queue_delete(g_ppp_msg_queue);
+ return PPP_ERR_MEMORY;
+ }
+
+ PPP_DEBUG_INFO("Timeout timer started (period=%d ms)", PPP_PING_PONG_TIMEOUT_MS);
+
+ // 4. 启动处理线程
g_ppp_process_thread_running = true;
app_task_definition_t process_task_cfg = {0};
process_task_cfg.create_queue_handle = NULL;
process_task_cfg.attr.name = "ppp_process";
process_task_cfg.attr.priority = osPriorityNormal1;
- process_task_cfg.attr.stack_size = 4096;
+ process_task_cfg.attr.stack_size = PPP_PROCESS_STACK_SIZE;
process_task_cfg.func = ppp_lwip_process_thread;
process_task_cfg.task_handle = &g_ppp_process_thread_handle;
process_task_cfg.use_mem = USE_EXTEND_MEM;
if (process_ret != ERRCODE_SUCC) {
PPP_DEBUG_ERROR("Failed to create PPP process thread: %d", process_ret);
g_ppp_process_thread_running = false;
- osal_msg_queue_delete(g_ppp_frame_queue);
- g_ppp_frame_queue = 0;
+ g_ppp_timer_running = false;
+ osal_timer_stop(&g_ppp_timeout_timer);
+ osal_timer_destroy(&g_ppp_timeout_timer);
+ osal_msg_queue_delete(g_ppp_msg_queue);
return PPP_ERR_MEMORY;
}
-#endif /* PPP_USE_DUAL_THREAD */
- // 启动接收线程
+ // 5. 启动接收线程
g_ppp_rx_thread_running = true;
app_task_definition_t rx_task_cfg = {0};
rx_task_cfg.create_queue_handle = NULL;
rx_task_cfg.attr.name = "ppp_receive";
rx_task_cfg.attr.priority = osPriorityNormal1;
- rx_task_cfg.attr.stack_size = 6144; // 单线程模式需要更大栈空间
+ rx_task_cfg.attr.stack_size = PPP_RX_STACK_SIZE;
rx_task_cfg.func = ppp_lwip_receive_thread;
rx_task_cfg.task_handle = &g_ppp_rx_thread_handle;
rx_task_cfg.use_mem = USE_EXTEND_MEM;
PPP_DEBUG_ERROR("Failed to create PPP receive thread: %d", rx_ret);
g_ppp_rx_thread_running = false;
-#if PPP_USE_DUAL_THREAD
// 清理处理线程
g_ppp_process_thread_running = false;
osThreadTerminate((osThreadId_t)g_ppp_process_thread_handle);
g_ppp_process_thread_handle = 0;
+ // 清理定时器
+ g_ppp_timer_running = false;
+ osal_timer_stop(&g_ppp_timeout_timer);
+ osal_timer_destroy(&g_ppp_timeout_timer);
+
// 清理资源
- osal_msg_queue_delete(g_ppp_frame_queue);
- g_ppp_frame_queue = 0;
-#endif
+ osal_msg_queue_delete(g_ppp_msg_queue);
return PPP_ERR_MEMORY;
}
- PPP_DEBUG_INFO("PPP threads started successfully (" PPP_THREAD_MODE_STR " mode)");
+ PPP_DEBUG_INFO("PPP threads started successfully (ping-pong polling mode)");
+ PPP_DEBUG_INFO(" Mode: Polling (1ms interval when idle)");
+ PPP_DEBUG_INFO(" Buffers: %d x %d KB", PPP_PING_PONG_BUFFER_COUNT,
+ PPP_PING_PONG_BUFFER_SIZE / 1024);
+ PPP_DEBUG_INFO(" Timeout: %d ms", PPP_PING_PONG_TIMEOUT_MS);
+
return PPP_ERR_OK;
}
*/
ppp_error_t ppp_lwip_stop_receive_thread(void)
{
-#if PPP_USE_DUAL_THREAD
if (g_ppp_rx_thread_handle == 0 && g_ppp_process_thread_handle == 0) {
return PPP_ERR_OK;
}
-#else
- if (g_ppp_rx_thread_handle == 0) {
- return PPP_ERR_OK;
- }
-#endif
- PPP_DEBUG_INFO("Stopping PPP threads (" PPP_THREAD_MODE_STR " mode)");
+ PPP_DEBUG_INFO("Stopping PPP threads (ping-pong polling mode)");
+
+ // 1. 停止定时器
+ if (g_ppp_timer_running) {
+ g_ppp_timer_running = false;
+ osal_timer_stop(&g_ppp_timeout_timer);
+ osal_timer_destroy(&g_ppp_timeout_timer);
+ PPP_DEBUG_INFO("Timeout timer stopped");
+ }
- // 停止接收线程
+ // 2. 停止接收线程
if (g_ppp_rx_thread_handle != 0) {
g_ppp_rx_thread_running = false;
- // 等待接收线程完成
int wait_count = 0;
- const int max_wait = 100;
-
- while (wait_count < max_wait && !g_ppp_rx_thread_completed) {
+ while (wait_count < 100 && !g_ppp_rx_thread_completed) {
osal_msleep(10);
wait_count++;
}
g_ppp_rx_thread_handle = 0;
}
-#if PPP_USE_DUAL_THREAD
- // 停止处理线程
+ // 3. 停止处理线程
if (g_ppp_process_thread_handle != 0) {
g_ppp_process_thread_running = false;
- // 等待处理线程完成
- int wait_count = 0;
- const int max_wait = 100;
+ // 发送空消息唤醒线程
+ ppp_msg_t empty_msg = {0};
+ osal_msg_queue_write_copy(g_ppp_msg_queue, &empty_msg,
+ sizeof(ppp_msg_t), OSAL_MSGQ_NO_WAIT);
- while (wait_count < max_wait && !g_ppp_process_thread_completed) {
+ int wait_count = 0;
+ while (wait_count < 100 && !g_ppp_process_thread_completed) {
osal_msleep(10);
wait_count++;
}
g_ppp_process_thread_handle = 0;
}
- // 清理队列
- if (g_ppp_frame_queue != 0) {
- osal_msg_queue_delete(g_ppp_frame_queue);
- g_ppp_frame_queue = 0;
+ // 4. 清理队列
+ if (g_ppp_msg_queue != 0) {
+ osal_msg_queue_delete(g_ppp_msg_queue);
+ g_ppp_msg_queue = 0;
}
-#endif
+
+ // 5. 清空乒乓缓冲区
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ g_ping_pong_buffers[i].write_pos = 0;
+ g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY;
+ g_ping_pong_buffers[i].last_write_time = 0;
+ }
+ g_current_buffer_index = 0;
// 重置状态标志
g_ppp_rx_thread_running = false;
g_ppp_rx_thread_completed = false;
-#if PPP_USE_DUAL_THREAD
g_ppp_process_thread_running = false;
g_ppp_process_thread_completed = false;
-#endif
- PPP_DEBUG_INFO("PPP receive and process threads stopped");
+ PPP_DEBUG_INFO("PPP threads stopped");
return PPP_ERR_OK;
}
*/
bool ppp_lwip_is_receive_thread_running(void)
{
-#if PPP_USE_DUAL_THREAD
return g_ppp_rx_thread_running && (g_ppp_rx_thread_handle != 0) &&
g_ppp_process_thread_running && (g_ppp_process_thread_handle != 0);
-#else
- return g_ppp_rx_thread_running && (g_ppp_rx_thread_handle != 0);
-#endif
}
/**
- * @brief 获取PPP字节丢弃统计信息
+ * @brief 获取PPP统计信息(兼容接口)
*/
void ppp_lwip_get_discard_stats(ppp_discard_stats_t *stats)
{
if (stats) {
- memcpy(stats, &g_ppp_discard_stats, sizeof(ppp_discard_stats_t));
+ memset(stats, 0, sizeof(ppp_discard_stats_t));
+ stats->total_bytes_received = g_ppp_stats.total_bytes_received;
+ stats->total_bytes_processed = g_ppp_stats.total_bytes_processed;
+ stats->total_bytes_discarded = g_ppp_stats.total_bytes_discarded;
}
}
/**
- * @brief 重置PPP字节丢弃统计信息
+ * @brief 重置PPP统计信息
*/
void ppp_lwip_reset_discard_stats(void)
{
- memset(&g_ppp_discard_stats, 0, sizeof(ppp_discard_stats_t));
- PPP_DEBUG_INFO("PPP discard statistics reset");
+ memset(&g_ppp_stats, 0, sizeof(ppp_polling_stats_t));
+ PPP_DEBUG_INFO("PPP polling statistics reset");
}
/**
- * @brief 打印PPP字节丢弃统计信息
+ * @brief 打印PPP统计信息
*/
void ppp_lwip_print_discard_stats(void)
{
- PPP_DEBUG_INFO("=== PPP Discard Statistics ===");
- PPP_DEBUG_INFO("Total Bytes Received: %u", g_ppp_discard_stats.total_bytes_received);
- PPP_DEBUG_INFO("Total Bytes Processed: %u", g_ppp_discard_stats.total_bytes_processed);
- PPP_DEBUG_INFO("Total Bytes Discarded: %u", g_ppp_discard_stats.total_bytes_discarded);
- PPP_DEBUG_INFO(" - Invalid Frame Bytes: %u", g_ppp_discard_stats.invalid_frame_bytes);
- PPP_DEBUG_INFO(" - Queue Full Bytes: %u", g_ppp_discard_stats.queue_full_bytes);
- PPP_DEBUG_INFO(" - Malloc Fail Bytes: %u", g_ppp_discard_stats.malloc_fail_bytes);
- PPP_DEBUG_INFO(" - Oversized Frame Bytes: %u", g_ppp_discard_stats.oversized_frame_bytes);
- PPP_DEBUG_INFO(" - Incomplete Frame Bytes: %u", g_ppp_discard_stats.incomplete_frame_bytes);
- PPP_DEBUG_INFO("Total Frames Received: %u", g_ppp_discard_stats.total_frames_received);
- PPP_DEBUG_INFO("Total Frames Processed: %u", g_ppp_discard_stats.total_frames_processed);
- PPP_DEBUG_INFO("Total Frames Discarded: %u", g_ppp_discard_stats.total_frames_discarded);
-
- // 计算丢弃率
- if (g_ppp_discard_stats.total_bytes_received > 0) {
- uint32_t discard_rate = (g_ppp_discard_stats.total_bytes_discarded * 100) /
- g_ppp_discard_stats.total_bytes_received;
- PPP_DEBUG_INFO("Byte Discard Rate: %u.%02u%%",
- discard_rate / 100, discard_rate % 100);
+ PPP_DEBUG_INFO("=== PPP Polling Statistics (Ping-Pong Mode) ===");
+ PPP_DEBUG_INFO("Mode: Polling (轮询替代中断)");
+ PPP_DEBUG_INFO("Buffers: %d x %d bytes = %d KB total",
+ PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE,
+ (PPP_PING_PONG_BUFFER_COUNT * PPP_PING_PONG_BUFFER_SIZE) / 1024);
+ PPP_DEBUG_INFO("Timeout: %d ms", PPP_PING_PONG_TIMEOUT_MS);
+ PPP_DEBUG_INFO("");
+
+ // Buffer状态
+ PPP_DEBUG_INFO("Buffer Status:");
+ for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
+ const char *state_str = "UNKNOWN";
+ switch (g_ping_pong_buffers[i].state) {
+ case PPP_BUFFER_STATE_EMPTY: state_str = "EMPTY"; break;
+ case PPP_BUFFER_STATE_FILLING: state_str = "FILLING"; break;
+ case PPP_BUFFER_STATE_FULL: state_str = "FULL"; break;
+ case PPP_BUFFER_STATE_PROCESSING: state_str = "PROCESSING"; break;
+ }
+ PPP_DEBUG_INFO(" Buffer[%u]: %s, %u/%u bytes%s",
+ i, state_str, g_ping_pong_buffers[i].write_pos,
+ PPP_PING_PONG_BUFFER_SIZE,
+ (i == g_current_buffer_index) ? " (current)" : "");
+ }
+ PPP_DEBUG_INFO("");
+
+ // 乒乓buffer统计
+ PPP_DEBUG_INFO("Ping-Pong Buffer Statistics:");
+ PPP_DEBUG_INFO(" Buffer Full Count: %u", g_ppp_stats.buffer_full_count);
+ PPP_DEBUG_INFO(" Buffer Timeout Count: %u", g_ppp_stats.buffer_timeout_count);
+ PPP_DEBUG_INFO(" Buffer Switch Count: %u", g_ppp_stats.buffer_switch_count);
+ PPP_DEBUG_INFO("");
+
+ // UART轮询统计
+ PPP_DEBUG_INFO("UART Polling Statistics:");
+ PPP_DEBUG_INFO(" Total Read Attempts: %u", g_ppp_stats.read_count + g_ppp_stats.read_empty_count);
+ PPP_DEBUG_INFO(" Successful Reads: %u", g_ppp_stats.read_count);
+ PPP_DEBUG_INFO(" Empty Reads: %u", g_ppp_stats.read_empty_count);
+ if (g_ppp_stats.read_count + g_ppp_stats.read_empty_count > 0) {
+ uint32_t hit_rate = (g_ppp_stats.read_count * 100) /
+ (g_ppp_stats.read_count + g_ppp_stats.read_empty_count);
+ PPP_DEBUG_INFO(" Hit Rate: %u%%", hit_rate);
}
+ PPP_DEBUG_INFO("");
+
+ // 字节统计
+ PPP_DEBUG_INFO("Byte Statistics:");
+ PPP_DEBUG_INFO(" Total Bytes Received: %u", g_ppp_stats.total_bytes_received);
+ PPP_DEBUG_INFO(" Total Bytes Processed: %u", g_ppp_stats.total_bytes_processed);
+ PPP_DEBUG_INFO(" Total Bytes Discarded: %u", g_ppp_stats.total_bytes_discarded);
+ PPP_DEBUG_INFO(" - Both Buffers Full: %u", g_ppp_stats.dropped_both_full);
+ PPP_DEBUG_INFO(" - Queue Full: %u", g_ppp_stats.dropped_queue_full);
+ PPP_DEBUG_INFO(" lwIP Errors: %u", g_ppp_stats.lwip_error_count);
+ PPP_DEBUG_INFO("");
- if (g_ppp_discard_stats.total_frames_received > 0) {
- uint32_t frame_discard_rate = (g_ppp_discard_stats.total_frames_discarded * 100) /
- g_ppp_discard_stats.total_frames_received;
- PPP_DEBUG_INFO("Frame Discard Rate: %u.%02u%%",
- frame_discard_rate / 100, frame_discard_rate % 100);
+ // 丢包率
+ if (g_ppp_stats.total_bytes_received > 0) {
+ uint32_t drop_rate = (g_ppp_stats.total_bytes_discarded * 10000) /
+ g_ppp_stats.total_bytes_received;
+ PPP_DEBUG_INFO("Drop Rate: %u.%02u%%", drop_rate / 100, drop_rate % 100);
}
- PPP_DEBUG_INFO("==============================");
-}
\ No newline at end of file
+
+ // 平均字节数
+ if (g_ppp_stats.read_count > 0) {
+ uint32_t avg_bytes = g_ppp_stats.total_bytes_received / g_ppp_stats.read_count;
+ PPP_DEBUG_INFO("Average bytes per read: %u", avg_bytes);
+ }
+
+ PPP_DEBUG_INFO("=================================================");
+}
\ No newline at end of file
ppp_api_cat1_bat_pin(1);
osal_msleep(900); // 使用OSAL延时函数
ppp_api_cat1_power_pin(0);
+#ifdef PPP_DO_SERIAL_TEST
+ ppp_api_cat1_reset_pin(1);
+#endif
+
#endif
g_ppp_cat1_power_status = true;
/* 初始化UART */
PPP_DEBUG_DEBUG("Calling uapi_uart_init...");
+ // 注意:uart_porting.c中已经做了UART初始化,这里只需要设置属性
// errcode_t ret = uapi_uart_init(g_serial_ctx.uart_bus,
// &g_serial_ctx.pin_config,
// &g_serial_ctx.uart_attr,
if (g_serial_ctx.current_mode == PPP_SERIAL_MODE_PPP_DATA) {
/* PPP数据模式:使用1ms超时快速读取,类似SLIP的sio_tryread */
- int32_t read_result = uapi_uart_read(g_serial_ctx.uart_bus, buffer, buffer_size, 1);
+ int32_t read_result = uapi_uart_read(g_serial_ctx.uart_bus, buffer, buffer_size, 0);
if (read_result > 0) {
*bytes_read = (uint32_t)read_result;
g_serial_ctx.rx_bytes += *bytes_read;
#define ZHIHUI_TCP_LOG_LEVEL_INFO 3
#define ZHIHUI_TCP_LOG_LEVEL_DEBUG 4
-#define DEFAULT_ZHIHUI_TCP_LOG_LEVEL ZHIHUI_TCP_LOG_LEVEL_WARN
+#define DEFAULT_ZHIHUI_TCP_LOG_LEVEL ZHIHUI_TCP_LOG_LEVEL_INFO
#if ZHIHUI_TCP_LOG_ENABLE
#define TCP_TEST_DEFAULT_TIME 30
#define TCP_TEST_DEFAULT_CONNECTIONS 10
#define TCP_TEST_DEFAULT_RECV_TIMEOUT_MS 1000 // 默认接收超时1000ms
+#define TCP_TEST_DEFAULT_SEND_INTERVAL_MS 500 // 默认发送间隔100ms
/* 新的固定数据模式配置 */
#define TCP_TEST_FIXED_DATA_SIZE 2048 // 固定数据长度,可通过宏配置