优化ppp串口中断处理 2490/1 2490/head 2491/target/1
author孙春阳 <snickersun@163.com>
Mon, 13 Oct 2025 04:13:59 +0000 (12:13 +0800)
committer孙春阳 <snickersun@163.com>
Mon, 13 Oct 2025 04:13:59 +0000 (12:13 +0800)
code/sdk/zhihui/ppp/doc/serial_test/compare_logs.py
code/sdk/zhihui/ppp/include/ppp_config.h
code/sdk/zhihui/ppp/src/ppp_interrupt.c
code/sdk/zhihui/ppp/src/ppp_lwip_thread.c

index 903e13b..ad84f31 100644 (file)
@@ -116,30 +116,72 @@ def compare_logs(tx_data, rx_data, max_errors=100):
     # 显示丢失的数据范围
     print(f"\n发现 {len(missing_ranges)} 个丢失区域:\n")
     
-    total_missing = 0
+    # 先计算所有丢失的字节总数(包括未显示的)
+    total_missing = sum(range_info['count'] for range_info in missing_ranges)
     shown_ranges = 0
     
+    # 计算应该显示的范围
+    total_ranges = len(missing_ranges)
+    show_last_n = 2  # 始终显示最后N条
+    
+    # 如果总数很少,全部显示;否则显示前面部分+最后N条
+    if total_ranges <= max_errors:
+        # 全部显示
+        ranges_to_show = list(range(total_ranges))
+    else:
+        # 显示前面部分和最后N条
+        show_first_n = max_errors - show_last_n
+        ranges_to_show = list(range(show_first_n)) + list(range(total_ranges - show_last_n, total_ranges))
+        # 标记需要显示省略号的位置
+        show_ellipsis_after = show_first_n - 1
+    
+    # 标记是否已显示过省略提示和最后区域标题
+    ellipsis_shown = False
+    last_section_shown = False
+    
     for i, range_info in enumerate(missing_ranges):
-        if shown_ranges >= max_errors:
-            remaining = len(missing_ranges) - shown_ranges
-            print(f"\n... (还有 {remaining} 个丢失区域未显示)")
-            break
+        # 检查是否需要显示这个范围
+        if i not in ranges_to_show:
+            continue
+        
+        # 检查是否需要显示省略号(在显示最后区域之前)
+        if total_ranges > max_errors and not ellipsis_shown and i >= total_ranges - show_last_n:
+            skipped = total_ranges - max_errors
+            # 计算省略的字节总数
+            skipped_bytes = sum(missing_ranges[j]['count'] 
+                              for j in range(show_first_n, total_ranges - show_last_n))
+            print()
+            print("─" * 70)
+            print(f"⚠️  中间省略了 {skipped} 个丢失区域 (共 {skipped_bytes} 字节)")
+            print("─" * 70)
+            print()
+            ellipsis_shown = True
+        
+        # 检查是否是最后几条,显示标题
+        if total_ranges > max_errors and not last_section_shown and i >= total_ranges - show_last_n:
+            print("┌" + "─" * 68 + "┐")
+            print(f"│ 📌 最后 {show_last_n} 个丢失区域(始终显示)" + " " * 35 + "│")
+            print("└" + "─" * 68 + "┘")
+            print()
+            last_section_shown = True
         
         start_pos = range_info['start']
         end_pos = range_info['end']
         count = range_info['count']
         data = range_info['data']
         
-        total_missing += count
         shown_ranges += 1
         
+        # 显示区域编号
+        region_label = f"[区域 {i+1}/{total_ranges}]"
+        
         if count == 1:
             # 单个字节丢失
             line, byte_val = data[0]
-            print(f"❌ 位置 {start_pos}: 丢失 1 字节 = 0x{byte_val:02X} (发送行{line})")
+            print(f"❌ {region_label} 位置 {start_pos}: 丢失 1 字节 = 0x{byte_val:02X} (发送行{line})")
         else:
             # 连续多个字节丢失
-            print(f"❌ 位置 {start_pos}~{end_pos}: 丢失 {count} 字节")
+            print(f"❌ {region_label} 位置 {start_pos}~{end_pos}: 丢失 {count} 字节")
             
             # 显示前8个和后8个字节(如果数据较多)
             if count <= 16:
@@ -153,11 +195,12 @@ def compare_logs(tx_data, rx_data, max_errors=100):
                 print(f"   数据: {head} ... {tail}")
         
         # 添加空行分隔不同的范围
-        if i < len(missing_ranges) - 1:
+        if i < len(missing_ranges) - 1 and (i+1) in ranges_to_show:
             print()
     
     print("-"*70)
     print(f"总计丢失: {total_missing} 字节")
+    print(f"显示区域: {shown_ranges}/{total_ranges} 个")
     
     if tx_len != rx_len:
         print(f"长度差异: {tx_len - rx_len} 字节")
index 3ab6a1e..02339eb 100644 (file)
@@ -50,7 +50,7 @@ extern "C" {
 #define PPP_KEEPALIVE_INTERVAL      30                  // 保活间隔(秒)
 
 /* PPP接收模式配置 */
-#ifndef SUPPORT_PPP_SERVICE_POLLING
+#ifdef SUPPORT_PPP_SERVICE_POLLING
 #define PPP_USE_INTERRUPT_MODE      0                  // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询)
 #elif defined(SUPPORT_PPP_SERVICE_INTERRUPT)
 #define PPP_USE_INTERRUPT_MODE      1                  // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询)
index 00b0ef9..1be7831 100644 (file)
@@ -35,7 +35,7 @@
 #define PPP_PING_PONG_BUFFER_SIZE  2048  // 每个buffer大小(2KB)
 #endif
 
-#define PPP_PING_PONG_BUFFER_COUNT 2     // 乒乓buffer数量(固定2个)
+#define PPP_PING_PONG_BUFFER_COUNT 3     // 三缓冲模式(改为3个)
 #define PPP_PING_PONG_TIMEOUT_MS   30    // buffer超时时间(30ms)
 
 /* PPP中断消息队列配置 */
@@ -57,11 +57,12 @@ typedef struct {
     volatile uint32_t write_pos;              // 当前写入位置
     volatile ppp_buffer_state_t state;        // 缓冲区状态
     volatile uint32_t last_write_time;        // 最后写入时间戳(ms)
+    volatile uint32_t first_write_time;       // 首次写入时间戳(用于统计填充时间)
 } ppp_ping_pong_buffer_t;
 
 /* PPP中断消息结构 - 通过消息队列传递buffer索引 */
 typedef struct {
-    uint8_t buffer_index;   // 缓冲区索引(0或1)
+    uint8_t buffer_index;   // 缓冲区索引(0~PPP_PING_PONG_BUFFER_COUNT-1)
     uint16_t length;        // 数据长度
 } ppp_irq_msg_t;
 
@@ -75,7 +76,7 @@ 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;
+static volatile uint8_t g_current_buffer_index = 0;  // ⚠️ volatile不保证原子性,但单字节写入在ARM上通常是原子的
 
 /* 全局时间戳(由定时器更新,ISR只读) */
 static volatile uint32_t g_timer_timestamp_ms = 0;
@@ -89,10 +90,18 @@ 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都满导致丢弃的字节数
+    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 switch_failed_count;     // buffer切换失败次数(安全性指标)
+    uint32_t race_condition_detected; // 检测到的竞态条件次数(诊断)
+    uint32_t cross_buffer_count;      // 跨buffer写入次数(数据跨两个buffer)
+    uint32_t timeout_unfilled_count;  // 超时时未满的flush次数
+    uint32_t fill_time_min_ms;        // buffer填充最短时间(ms)
+    uint32_t fill_time_max_ms;        // buffer填充最长时间(ms)
+    uint32_t fill_time_total_ms;      // buffer填充总时间(用于计算平均值)
+    uint32_t fill_time_count;         // buffer填充次数统计
 } ppp_pingpong_stats_t;
 
 static ppp_pingpong_stats_t g_pingpong_stats = {0};
@@ -228,6 +237,7 @@ static void ppp_irq_process_thread(void *arg)
         // 释放buffer(标记为空闲)
         buffer->state = PPP_BUFFER_STATE_EMPTY;
         buffer->write_pos = 0;
+        buffer->first_write_time = 0;  // 重置首次写入时间
     }
     
     PPP_DEBUG_INFO("PPP IRQ process thread exited");
@@ -236,9 +246,10 @@ static void ppp_irq_process_thread(void *arg)
 /**
  * @brief 刷新指定buffer到消息队列
  * @param buffer_index 缓冲区索引
+ * @param is_timeout 是否因超时触发(用于统计)
  * @return PPP_ERR_OK 成功,其他值失败
  */
-static ppp_error_t ppp_flush_buffer(uint8_t buffer_index)
+static ppp_error_t ppp_flush_buffer_internal(uint8_t buffer_index, bool is_timeout)
 {
     if (buffer_index >= PPP_PING_PONG_BUFFER_COUNT) {
         return PPP_ERR_INVALID_PARAM;
@@ -251,6 +262,32 @@ static ppp_error_t ppp_flush_buffer(uint8_t buffer_index)
         return PPP_ERR_OK;  // 没有数据需要flush
     }
     
+    // 统计填充时间(从首次写入到flush)
+    if (buffer->first_write_time > 0) {
+        uint32_t current_time = ppp_get_current_time_ms();
+        uint32_t fill_time = current_time - buffer->first_write_time;
+        
+        // 更新最小/最大填充时间
+        if (g_pingpong_stats.fill_time_count == 0) {
+            g_pingpong_stats.fill_time_min_ms = fill_time;
+            g_pingpong_stats.fill_time_max_ms = fill_time;
+        } else {
+            if (fill_time < g_pingpong_stats.fill_time_min_ms) {
+                g_pingpong_stats.fill_time_min_ms = fill_time;
+            }
+            if (fill_time > g_pingpong_stats.fill_time_max_ms) {
+                g_pingpong_stats.fill_time_max_ms = fill_time;
+            }
+        }
+        g_pingpong_stats.fill_time_total_ms += fill_time;
+        g_pingpong_stats.fill_time_count++;
+    }
+    
+    // 统计超时且未满的flush
+    if (is_timeout && buffer->write_pos < PPP_PING_PONG_BUFFER_SIZE) {
+        g_pingpong_stats.timeout_unfilled_count++;
+    }
+    
     // 标记为FULL状态
     buffer->state = PPP_BUFFER_STATE_FULL;
     
@@ -277,6 +314,12 @@ static ppp_error_t ppp_flush_buffer(uint8_t buffer_index)
     return PPP_ERR_OK;
 }
 
+// 便利函数:非超时flush
+static ppp_error_t ppp_flush_buffer(uint8_t buffer_index)
+{
+    return ppp_flush_buffer_internal(buffer_index, false);
+}
+
 /**
  * @brief 定时器回调:检查buffer超时并flush
  * @note 定时器上下文,应快速返回
@@ -306,6 +349,12 @@ static void ppp_timeout_timer_callback(unsigned long arg)
                       timer_call_count, g_ppp_interrupt_enabled, g_ppp_timer_running);
     }
     
+    // 每333次(约10秒)打印一次PPP丢失统计
+    // 30ms * 333 ≈ 10秒
+    if ((timer_call_count % 333) == 0) {
+        ppp_interrupt_print_stats();
+    }
+    
     if (!g_ppp_interrupt_enabled || !g_ppp_timer_running) {
         return;
     }
@@ -317,35 +366,54 @@ static void ppp_timeout_timer_callback(unsigned long arg)
     
     uint8_t current_idx = g_current_buffer_index;  // 读取一次,避免重复读取
     
-    // 检查非当前buffer的超时状态
-    // ⚠️ 数据安全设计:定时器只处理非当前buffer,避免与ISR竞争
-    // 当前buffer的超时由ISR在写入时主动检查并处理
+    // ⚠️ 新增:检查所有buffer的超时状态(包括当前buffer)
+    // 需求4:超时时如果当前buffer未满也要flush,避免数据滞留
     for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) {
-        // 跳过当前正在写入的buffer(避免与ISR竞争,保证数据安全)
-        if (i == current_idx) {
-            continue;
+        // ⚠️ 竞态检测:在处理过程中再次检查current_idx是否变化
+        // 如果这个buffer变成current了,需要小心处理
+        uint8_t current_check = g_current_buffer_index;
+        bool is_current = (i == current_check);
+        
+        // 检测到current_idx在循环中变化(竞态)
+        if (!is_current && i == current_idx) {
+            g_pingpong_stats.race_condition_detected++;
+            continue;  // 跳过,避免竞态
         }
         
         ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i];
         
-        // 只处理FILLING状态的buffer
+        // 只处理FILLING状态且有数据的buffer
         if (buffer->state == PPP_BUFFER_STATE_FILLING && buffer->write_pos > 0) {
-            // ⚠️ 修复:避免误判超时
-            // last_write_time == 0 表示ISR刚写入数据,需要更新时间戳(不是超时)
+            // last_write_time == 0 表示ISR刚写入数据,需要更新时间戳
             if (buffer->last_write_time == 0) {
-                // 第一次看到,更新时间戳,不flush
                 buffer->last_write_time = current_time;
                 PPP_DEBUG_DEBUG("[TIMER] Update timestamp for buffer[%u]", i);
             } else {
-                // 已经有时间戳了,检查是否真的超时
+                // 检查是否超时
                 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);
+                    // ⚠️ 超时了!即使未满也要flush(需求4)
+                    PPP_DEBUG_DEBUG("[TIMER] Flushing buffer[%u] due to timeout (%u ms), %s, %u bytes", 
+                                   i, elapsed, 
+                                   is_current ? "CURRENT" : "non-current",
+                                   buffer->write_pos);
                     
-                    if (ppp_flush_buffer(i) == PPP_ERR_OK) {
+                    // 使用internal函数,标记为超时flush
+                    if (ppp_flush_buffer_internal(i, true) == PPP_ERR_OK) {
                         g_pingpong_stats.buffer_timeout_count++;
+                        
+                        // 如果是当前buffer超时被flush,需要切换到下一个
+                        if (is_current) {
+                            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++;
+                                PPP_DEBUG_INFO("[TIMER] Switched from buffer[%u] to buffer[%u] after timeout", i, next_idx);
+                            }
+                        }
                     }
                 }
             }
@@ -409,143 +477,141 @@ static void ppp_uart_frame_error_callback(uint32_t *err_info, uint32_t len)
  * 1. 写入当前buffer(memcpy,~60字节 < 1μs)
  * 2. buffer满了直接flush并切换(~2μs)
  * 3. 无malloc/free,减少内存碎片和延迟
- * 4. 总耗时目标:< 5μs(比之前的7μs更快)
+ * 4. ⚠️ 新增:跨buffer写入支持(需求1)
+ * 5. 总耗时目标:< 5μs
  */
 void _ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error)
 {
-    // 快速路径:参数校验
+    // ⚠️ 中断中禁止打印!必须极快返回
+    
+    // 快速路径:参数校验(异常情况也不打印,只统计)
     if ( !buffer || length == 0 || length > 200) {  // 单次中断最多200字节
-        PPP_DEBUG_ERROR("ppp_uart_rx_interrupt_handler !buffer || length == 0 || length > 200 ");
+        // 不打印!在中断中打印会阻塞系统,只记录统计
+        g_ppp_interrupt_stats.error_count++;
         return;
     }
     if (error) {
         g_ppp_interrupt_stats.error_count++;
     }
     
-    // 更新统计(用于诊断 - 可以在统计打印中看到)
+    // 更新统计
     g_ppp_interrupt_stats.total_interrupts++;
     g_ppp_interrupt_stats.total_bytes_received += length;
     
-    // 诊断:每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;
-        // 不能在中断中打印,但可以更新这个全局变量,在统计中查看
-    }
-    
-    // ⚠️ 中断中禁止打印!
-    
     // 获取当前buffer
     uint8_t current_idx = g_current_buffer_index;
     ppp_ping_pong_buffer_t *current_buffer = &g_ping_pong_buffers[current_idx];
     
-    // ⚠️ 数据安全设计:ISR负责检查当前buffer的超时
-    // 定时器只处理非当前buffer,避免竞态
-    // 
-    // 检查逻辑(方案2 + 全局时间戳):
-    // - last_write_time == 0:ISR刚写入,定时器还没更新时间戳(不算超时)
-    // - last_write_time != 0:定时器已更新过时间戳,可以检查是否真的超时
-    //   使用 g_timer_timestamp_ms(定时器每30ms更新一次)
+    // ⚠️ 检查当前buffer的超时
     if (current_buffer->state == PPP_BUFFER_STATE_FILLING && 
         current_buffer->write_pos > 0 && 
-        current_buffer->last_write_time != 0) {  // ← 只检查已有时间戳的
-        // 使用全局时间戳(ISR中安全,不调用函数)
+        current_buffer->last_write_time != 0) {
         uint32_t current_time = g_timer_timestamp_ms;
         uint32_t elapsed = current_time - current_buffer->last_write_time;
         
         if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) {
-            // 当前buffer真的超时了,flush并切换
+            // 超时,flush并切换
             ppp_flush_buffer(current_idx);
             g_pingpong_stats.buffer_timeout_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) {
+            if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) {
                 g_current_buffer_index = next_idx;
                 g_pingpong_stats.buffer_switch_count++;
                 current_idx = next_idx;
-                current_buffer = next_buffer;
+                current_buffer = &g_ping_pong_buffers[current_idx];
             }
         }
     }
     
-    // å¦\82æ\9e\9cå½\93å\89\8dbufferä¸\8då\8f¯å\86\99ï¼\88ç\8a¶æ\80\81ä¸\8d对ï¼\89ï¼\8cå°\9dè¯\95å\88\87æ\8d¢
+    // 如果当前buffer不可写,尝试切换
     if (current_buffer->state != PPP_BUFFER_STATE_EMPTY && 
         current_buffer->state != PPP_BUFFER_STATE_FILLING) {
         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) {
+        if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) {
             g_current_buffer_index = next_idx;
             g_pingpong_stats.buffer_switch_count++;
             current_idx = next_idx;
-            current_buffer = next_buffer;
+            current_buffer = &g_ping_pong_buffers[current_idx];
         }
     }
     
-    // 尝试写入当前buffer
-    uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos;
-    
+    // 初始化空buffer
     if (current_buffer->state == PPP_BUFFER_STATE_EMPTY) {
-        // buffer是空的,初始化为FILLING状态
         current_buffer->state = PPP_BUFFER_STATE_FILLING;
         current_buffer->write_pos = 0;
-        available = PPP_PING_PONG_BUFFER_SIZE;  // 重新计算可用空间
+        current_buffer->first_write_time = g_timer_timestamp_ms;  // ⚠️ 需求3:记录首次写入时间
     }
     
-    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();
-        current_buffer->last_write_time = 0;  // 由定时器回调更新
-        
-        // 检查是否满了
-        if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) {
-            // buffer满,flush
+    // 计算当前buffer剩余空间
+    uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos;
+    uint16_t remaining_data = length;
+    const uint8_t *data_ptr = (const uint8_t *)buffer;
+    
+    // ⚠️ 需求1:跨buffer写入处理
+    if (current_buffer->state == PPP_BUFFER_STATE_FILLING) {
+        if (remaining_data <= available) {
+            // 情况1:数据完全放得下,直接写入
+            memcpy(current_buffer->data + current_buffer->write_pos, data_ptr, remaining_data);
+            current_buffer->write_pos += remaining_data;
+            current_buffer->last_write_time = 0;  // 由定时器更新
+            
+            // 检查是否正好填满
+            if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) {
+                ppp_flush_buffer(current_idx);
+                g_pingpong_stats.buffer_full_count++;
+                
+                // 切换buffer
+                uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT;
+                if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) {
+                    g_current_buffer_index = next_idx;
+                    g_pingpong_stats.buffer_switch_count++;
+                } else {
+                    g_pingpong_stats.switch_failed_count++;
+                }
+            }
+        } else {
+            // 情况2:数据超过当前buffer剩余空间,需要跨buffer写入
+            g_pingpong_stats.cross_buffer_count++;  // ⚠️ 统计跨buffer次数
+            
+            // 第1步:先填满当前buffer
+            if (available > 0) {
+                memcpy(current_buffer->data + current_buffer->write_pos, data_ptr, available);
+                current_buffer->write_pos += available;
+                data_ptr += available;
+                remaining_data -= available;
+            }
+            
+            // 第2步:flush当前buffer
             ppp_flush_buffer(current_idx);
             g_pingpong_stats.buffer_full_count++;
             
-            // 切换到另一个buffer
+            // 第3步:切换到下一个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) {
+            if (next_buffer->state == PPP_BUFFER_STATE_EMPTY && 
+                remaining_data <= PPP_PING_PONG_BUFFER_SIZE) {
+                // 第4步:把剩余数据写入新buffer
                 g_current_buffer_index = next_idx;
                 g_pingpong_stats.buffer_switch_count++;
+                
+                next_buffer->state = PPP_BUFFER_STATE_FILLING;
+                next_buffer->write_pos = 0;
+                next_buffer->first_write_time = g_timer_timestamp_ms;  // 新buffer的首次写入时间
+                
+                memcpy(next_buffer->data, data_ptr, remaining_data);
+                next_buffer->write_pos = remaining_data;
+                next_buffer->last_write_time = 0;  // 由定时器更新
+            } else {
+                // 下一个buffer不可用,剩余数据丢弃
+                g_pingpong_stats.dropped_both_full += remaining_data;
+                g_pingpong_stats.switch_failed_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();
-            next_buffer->last_write_time = 0;  // 由定时器回调更新
-        } else {
-            // 两个buffer都不可用,丢弃数据
-            g_pingpong_stats.dropped_both_full += length;
-        }
+        // 当前buffer不可用,数据丢弃
+        g_pingpong_stats.dropped_both_full += length;
     }
 }
 
@@ -573,6 +639,7 @@ ppp_error_t ppp_interrupt_enable(void)
         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_ping_pong_buffers[i].first_write_time = 0;
         memset(g_ping_pong_buffers[i].data, 0, PPP_PING_PONG_BUFFER_SIZE);
     }
     g_current_buffer_index = 0;
@@ -871,9 +938,28 @@ void ppp_interrupt_print_stats(void)
     // 乒乓缓冲区统计
     PPP_DEBUG_WARN("Ping-Pong Buffer Statistics:");
     PPP_DEBUG_WARN("  Buffer Full Count: %u", g_pingpong_stats.buffer_full_count);
-    PPP_DEBUG_WARN("  Buffer Timeout Count: %u", g_pingpong_stats.buffer_timeout_count);
-    PPP_DEBUG_WARN("  Buffer Switch Count: %u", g_pingpong_stats.buffer_switch_count);
+    PPP_DEBUG_WARN("  Buffer Timeout Count: %u (total)", g_pingpong_stats.buffer_timeout_count);
+    PPP_DEBUG_WARN("    - Timeout Unfilled: %u ⚠️ (未满即超时)", g_pingpong_stats.timeout_unfilled_count);
+    PPP_DEBUG_WARN("  Buffer Switch Count: %u (success)", g_pingpong_stats.buffer_switch_count);
+    PPP_DEBUG_WARN("  Buffer Switch Failed: %u ⚠️", g_pingpong_stats.switch_failed_count);
+    PPP_DEBUG_WARN("  Cross-Buffer Writes: %u 📌 (跨buffer写入)", g_pingpong_stats.cross_buffer_count);
     PPP_DEBUG_WARN("  Dropped (Both Full): %u bytes ⚠️", g_pingpong_stats.dropped_both_full);
+    
+    // 填充时间统计
+    if (g_pingpong_stats.fill_time_count > 0) {
+        uint32_t avg_fill_time = g_pingpong_stats.fill_time_total_ms / g_pingpong_stats.fill_time_count;
+        PPP_DEBUG_WARN("  Buffer Fill Time (从首字节到flush):");
+        PPP_DEBUG_WARN("    - Min: %u ms ⏱️", g_pingpong_stats.fill_time_min_ms);
+        PPP_DEBUG_WARN("    - Max: %u ms ⏱️", g_pingpong_stats.fill_time_max_ms);
+        PPP_DEBUG_WARN("    - Avg: %u ms ⏱️", avg_fill_time);
+        PPP_DEBUG_WARN("    - Sample Count: %u", g_pingpong_stats.fill_time_count);
+    } else {
+        PPP_DEBUG_WARN("  Buffer Fill Time: No data yet");
+    }
+    
+    if (g_pingpong_stats.race_condition_detected > 0) {
+        PPP_DEBUG_ERROR("  Race Conditions Detected: %u 🔴", g_pingpong_stats.race_condition_detected);
+    }
     PPP_DEBUG_WARN("");
     
     // 中断层统计
index d9d786a..e4330e2 100644 (file)
@@ -76,7 +76,7 @@ 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 volatile uint8_t g_current_buffer_index = 0;  // ⚠️ volatile不保证原子性,但单字节写入在ARM上通常是原子的
 
 /* 定时器相关 */
 static osal_timer g_ppp_timeout_timer;
@@ -95,6 +95,8 @@ typedef struct {
     uint32_t lwip_error_count;          // lwIP错误次数
     uint32_t read_count;                // UART读取次数
     uint32_t read_empty_count;          // UART读取为空次数
+    uint32_t switch_failed_count;       // buffer切换失败次数(安全性指标)
+    uint32_t race_condition_detected;   // 检测到的竞态条件次数(诊断)
 } ppp_polling_stats_t;
 
 static ppp_polling_stats_t g_ppp_stats = {0};
@@ -194,6 +196,15 @@ static void ppp_timeout_timer_callback(unsigned long arg)
             continue;
         }
         
+        // ⚠️ 竞态检测:在处理过程中再次检查current_idx是否变化
+        // 如果变化了,说明接收线程在我们处理期间切换了buffer
+        uint8_t current_check = g_current_buffer_index;
+        if (i == current_check && current_check != current_idx) {
+            // 竞态条件:我们准备处理的buffer现在变成current了
+            g_ppp_stats.race_condition_detected++;
+            continue;  // 跳过这个buffer
+        }
+        
         ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i];
         
         // 只处理FILLING状态的buffer
@@ -504,7 +515,8 @@ static void ppp_lwip_receive_thread(void *arg)
                 }
                 
                 if (!switched) {
-                    // 没有空闲 buffer,等待处理线程
+                    // ⚠️ 切换失败:没有空闲buffer,等待处理线程
+                    g_ppp_stats.switch_failed_count++;
                     PPP_DEBUG_WARN("No empty buffer available, waiting...");
                     osal_msleep(1);
                 }
@@ -885,7 +897,11 @@ void ppp_lwip_print_discard_stats(void)
     PPP_DEBUG_WARN("Ping-Pong Buffer Statistics:");
     PPP_DEBUG_WARN("  Buffer Full Count: %u", g_ppp_stats.buffer_full_count);
     PPP_DEBUG_WARN("  Buffer Timeout Count: %u", g_ppp_stats.buffer_timeout_count);
-    PPP_DEBUG_WARN("  Buffer Switch Count: %u", g_ppp_stats.buffer_switch_count);
+    PPP_DEBUG_WARN("  Buffer Switch Count: %u (success)", g_ppp_stats.buffer_switch_count);
+    PPP_DEBUG_WARN("  Buffer Switch Failed: %u ⚠️", g_ppp_stats.switch_failed_count);
+    if (g_ppp_stats.race_condition_detected > 0) {
+        PPP_DEBUG_ERROR("  Race Conditions Detected: %u 🔴", g_ppp_stats.race_condition_detected);
+    }
     PPP_DEBUG_WARN("");
     
     // UART轮询统计