问:不使用tcp-poll,如何更改tcp发送周期为20ms?
回答:
在裸机环境下若不使用 `tcp_poll` 机制,可以通过 **用户定时器中断 + 主动发送控制** 实现 20ms 的 TCP 数据发送频率。以下是具体实现方案:
---
### 1. **核心思路**
- **硬件定时器中断**:配置一个硬件定时器(如STM32的TIM2),周期为20ms,中断中仅设置发送标志位。
- **主循环主动发送**:在主循环中检测发送标志位,触发数据封装和发送。
- **协议栈线程安全**:避免在中断中直接调用LwIP函数,通过标志位通知主循环处理。
---
### 2. **实现步骤**
#### 步骤1:配置硬件定时器(以STM32 HAL库为例)
```c
// 定时器初始化(20ms周期,假设系统时钟为84MHz)
void MX_TIM2_Init(void) {
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 8400 - 1; // 分频至10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 200 - 1; // 10kHz / 200 = 50Hz(20ms)
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2);
}
// 定时器中断回调函数(设置发送标志)
volatile uint8_t send_flag = 0; // 发送标志位
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
send_flag = 1; // 置位发送标志
}
}
```
#### 步骤2:主循环中处理发送逻辑
```c
// 主循环代码片段
while (1) {
// 其他任务(如协议栈轮询)
MX_LWIP_Process(); // LwIP协议栈处理
// 检测发送标志
if (send_flag) {
send_flag = 0; // 清除标志
tcp_send_data(); // 自定义发送函数
}
}
```
#### 步骤3:实现数据发送函数
```c
// 自定义TCP发送函数
void tcp_send_data(void) {
struct tcp_pcb *pcb = get_tcp_connection(); // 获取已建立的TCP连接PCB
if (pcb && (pcb->state == ESTABLISHED)) {
// 准备数据(示例:20字节负载)
uint8_t buffer[20];
prepare_sensor_data(buffer); // 填充数据
// 写入发送缓冲区
err_t err = tcp_write(pcb, buffer, sizeof(buffer), TCP_WRITE_FLAG_COPY);
if (err == ERR_OK) {
tcp_output(pcb); // 触发协议栈立即发送
} else {
// 处理发送失败(如缓冲区满)
}
}
}
```
---
### 3. **关键注意事项**
- **线程安全性**:
- LwIP函数(如 `tcp_write`, `tcp_output`)需在**主线程上下文**调用,禁止在中断中直接使用。
- 若使用RTOS,需通过信号量或消息队列同步。
- **发送缓冲区管理**:
- 检查 `tcp_write` 的返回值,处理 `ERR_MEM`(缓冲区满)等错误。
- 根据网络拥塞情况动态调整发送策略(如丢包重传)。
- **连接状态检查**:
- 发送前需确认TCP连接处于 `ESTABLISHED` 状态,避免向已关闭的连接发送数据。
- **协议栈处理周期**:
- 确保 `MX_LWIP_Process()` 调用频率足够高(如每1ms一次),防止协议栈任务堆积。
---
### 4. **性能优化**
- **Zero-Copy发送**:
若数据允许直接引用(非临时缓冲区),使用 `TCP_WRITE_FLAG_COPY` 可避免内存复制:
```c
tcp_write(pcb, sensor_read_ptr, data_len, TCP_WRITE_FLAG_COPY);
```
- **批量发送**:
若单次20ms的数据量较小,可积累多个周期数据后合并发送,减少协议栈开销。
- **动态频率调整**:
根据网络延迟或ACK响应时间,动态调整定时器周期(如自适应调整为10-50ms)。
---
### 5. **替代方案:使用 `tcp_sent` 回调**
若需严格保证发送间隔,可在数据发送完成后通过 `tcp_sent` 回调启动下一次发送:
```c
// 注册发送完成回调
tcp_sent(pcb, tcp_sent_callback);
// 回调函数实现
err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len) {
// 启动下一次发送(如启动20ms定时器)
reset_send_timer();
return ERR_OK;
}
```
此方式依赖ACK确认机制,实际频率可能受网络延迟影响。
---
### 总结
通过 **硬件定时器 + 主循环标志位检测** 实现 20ms 发送频率的核心代码,既避免了 `tcp_poll` 的周期限制,又确保了协议栈的线程安全性。实际应用中需结合具体场景优化数据封装、错误处理和网络适应性。