tcp_write() errors on snd_queuelen
作者 codercjg 在 23 三月 2016, 4:21 下午
stm32f107+lwip1.3.1长时间实时上传数据, 当lwip tcp client连续高速向PC server发送数据时,tcp_write()失败,跟踪进入发现snd_queuelen超出限制,而实际发送队列根本没满。
snd_queuelen是tcp发送队列中包的数目, tcp_enqueue()增加snd_queuelen的长度,tcp_receive()收到包的Ack后,减短snd_queuelen。
使用stm32例程时,tcp_receive()是在ethernet接收中断中间接调用的,程序主循环中tcp_write()间接调用tcp_receive()。
中断上下文中tcp_receive()和主循环中tcp_enqueue()对pcb->snd_queuelen的操作引起了竞态。因为裸跑没有操作系统,不能加锁,也不打算关中断。
解决办法:
文件tcp_out.c函数tcp_enqueue()中修改两个地方:
1)213行
useg = queue = seg = NULL;
seglen = 0;
queuelen = 0; /* add by chengjg */
2)401行
/* update number of segments on the queues /
pcb->snd_queuelen += queuelen; / pcb->snd_queuelen = queuelen; edit by chengjg /
之后就跑得很稳定了, 连续跑了3个多小时候抓包出现out of order错误,原来seqno也被改了。
所以正确的做法是进入tcp_enqueue()后先关网络接收中断,出tcp_enqueue()前开网络接收中断*,防止执行tcp_enqueue()期间,相关变量在中断里被修改导致程序出bug。
具体过程分析可参考一老外遇到的情况http://lwip.100.n7.nabble.com/tcp-write-errors-on-snd-queuelen-td8599.html
部分内容如下:
I did change the data I am sending to more human readable data and found that all the data is going out in WireShark until the tcp_write returns the error. I spent a lot of time digging into this and finally found the problem. I am hoping you can help me determine if it is something I am doing wrong or Texas Instruments or lwip. First, below is a portion of the attached log file from lwip running with the TCP_QLEN_DEBUG enabled. … [line 3642] tcp_enqueue: 37 (after enqueued) tcp_enqueue: queuelen: 37 tcp_enqueue: 38 (after enqueued) tcp_receive: queuelen 38 … 23 (after freeing unacked) tcp_receive: queuelen 23 … 0 (after freeing unacked) tcp_enqueue: queuelen: 0 tcp_enqueue: 1 (after enqueued) tcp_enqueue: queuelen: 1 … … [line 3764] tcp_enqueue: 59 (after enqueued) tcp_enqueue: queuelen: 59 tcp_receive: queuelen 59 … 35 (after freeing unacked) tcp_receive: queuelen 35 … 11 (after freeing unacked) tcp_enqueue: 60 (after enqueued) tcp_enqueue: queuelen: 60 tcp_enqueue: 61 (after enqueued) tcp_enqueue: queuelen: 61 tcp_enqueue: 62 (after enqueued) tcp_enqueue: queuelen: 62 … … [line 3853] tcp_enqueue: queuelen: 102 tcp_enqueue: 103 (after enqueued) pcb->nrtx > 12 tcp_enqueue: queuelen: 103 tcp_enqueue: 104 (after enqueued) tcp_enqueue: queuelen: 104 tcp_enqueue: 105 (after enqueued) tcp_enqueue: queuelen: 105 tcp_enqueue: 106 (after enqueued) tcp_enqueue: queuelen: 106 tcp_enqueue: 107 (after enqueued) tcp_receive: queuelen 107 … 95 (after freeing unacked) tcp_receive: queuelen 95 … 71 (after freeing unacked) tcp_receive: queuelen 71 … 48 (after freeing unacked) tcp_receive: valid queue length tcp_enqueue: queuelen: 48 tcp_enqueue: pbufs on queue => at least one queue non-empty tcp_enqueue: 49 (after enqueued) tcp_receive: queuelen 49 … 48 (after freeing unacked) tcp_receive: valid queue length Normally, we see the tcp_receive prefix take out queues and usually to 0 (though not always – line 3241 in file). However, at line 3765, the tcp receive interrupt went off during a tcp_write (tcp_enqueue). We found that in the tcp_write, the queue length is read near the beginning of the function into a local variable and then stored back into the global variable toward the end of the function (see below). From lwip version 1.3.2 in the tcp_out.c file: Line 195 queuelen = pcb->snd_queuelen; Line 411 pcb->snd_queuelen = queuelen; As you can see from line 3765 of the log file, the tcp_receive removed queues from the buffer after the tcp_enqueue had read the value to process. It then sets the queue length at the end to the internally modified local value. So when we get to line 3866, the queue length is incorrect and the value will not ever get back to a zero. If this situation happens enough over time, eventually it will reach the TCP_SND_QUEUELEN limit and not function any longer. So it explains why I don’t see missed packets on WireShark as it is an lwip variable that is getting set wrong. I assume this would affect the number of pbufs in use after this point. It would seem to me that the tcp_enqueue function should only add to the global value the number of packets that it uses and not resave the entire value over the global to something that could now be old. Do you agree? Is there something else in the lwipopts.h file that I may not have configured correctly that is suppose to prevent this? I am using a TI Cortex-M3 Stellaris port of the lwip code.
用python实现TCP Server
作者 codercjg 在 21 三月 2016, 4:27 下午
之前调试stm32以太网实时上传数据时,需要一个上位机的Server端进行测试。
比较了之后决定选择python,因为它比VC和java更方便。它的函数库比较强,学习曲线也比较平缓,几句代码就能实现其他语言几十甚至几百行的功能。贴出来,方便以后回头复习。
import socket
import time
import struct
def GetNowTime():
return time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime(time.time()))
def GetNowTimeX():
return time.strftime(“%Y-%m-%d %H-%M-%S”, time.localtime(time.time()))
def Log(msg, f):
time = GetNowTime()
print “%s %s”%(time, msg)
f.write(“%s %s\n”%(time, msg))
f.flush()
def HanldeData(conn, fdata, flog):
Log(“begin receive ecg data”, flog)
global seq
while 1:
try:
data = conn.recv(36)
except:
Log(“detect client off-line”, flog)
break;
if not data:
Log(“receive error”, flog)
break
if len(data)<36:
print “recv error”
break
results = struct.unpack(“iiiiiiiii”, data)
seq +=1
print GetNowTime(),
fdata.write(GetNowTime())
for result in results:
print “%10d “%result,
fdata.write(“%10d “%result)
print “”
fdata.write(“\n”)
fdata.flush()
if name == “main”:
host = “”
port = 4 # tcp server port
seq = 0
fdata = file(“ecg_%s.txt”%GetNowTimeX(), “w”) # data file
flog = file(“log_%s.txt”%GetNowTimeX(), “w”) # log file
Log(“server start”, flog)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.ioctl(socket.SIO_KEEPALIVE_VALS, (1, 4, 2))
s.bind((host, port))
s.listen(4)
Log(“listen on tcp port 4″, flog)
try:
while 1:
Log(“waiting client to connect”, flog)
conn,addr = s.accept()
Log(“connected by %s”%str(addr), flog)
HanldeData(conn, fdata, flog)
conn.close()
Log(“close the client”, flog)
finally:
Log(“server stop”, flog)
conn.close()
fdata.close()
flog.close()
心电图机
作者 codercjg 在 21 三月 2016, 3:20 下午
12导连心电图机有10个电极,电极名称分别为RA、LA、LL(F)、RL(N)和V1 V2 V3 V4 V5 V6,导联名称为
I II II aVR aVL aVF C1 C2 C3 C4 C5 C6, 如下图所示
12个导连为电极间的电势差,其中RA、LA和LL三个电极的平均值称作威尔逊中心Vw, 12导联名称和各个导连值计算方法如下:
Vw = (RA+LA+LL)/3
I = LA-RA
II=LL-RA
III=LL-LA
aVR = RA-(LA+LL)/2 = 3/2(RA-Vw)
aVL = LA-(LA+LL)/2= 3/2(LA-Vw)
aVF = LL-(LA+LL)/2= 3/2(LL-Vw)
C1=V1-Vw
C2=V2-Vw
C3=V3-Vw
C4=V4-Vw
C5=V5-Vw
C6=V6-Vw
导联值其实就是电势差
加权系数误差:
心电图机检查时有这一项,给R导联加一个3MV的正弦波,然后看aVR(3MV)、aVL(1.5MV)、aVF(1.5MV)、C1-C6(1MV)的值是否在允许的误差范围之内(+-10%)
STM32串口ISP烧写固件和IAP升级固件
作者 codercjg 在 21 三月 2016, 2:50 下午
ISP:
拉高stm32 MCU boot0,拉低reset脚延时,然后拉高reset脚,MCU复位从bootloader启动,该bootloader支持串口对stm32 MCU Flash进行擦除、读写等操作。
ISP协议可参考ST官方文档AN3155,ISP工具可参考flash_loader_demo_v2.8.0.exe,安装目录下也有该工具相关的源代码,可以参考下。
如果自己实现ISP下载工具,通过RS232流控脚控制Stm32 MCU 引脚Boot0和Reset 烧写stm32 Flash是一种比较好的ISP烧写方式。
要注意的是,像去读保护命令和去写保护命令执行完后会复位MCU的,这时需要重新发送0x7F进入ISP模式。
上位机可用C#的SerialPort类控制串口, SerialPort.RtsEnable 和 SerialPort.DtrEnable可直接拉高或拉低RS232的RTS脚和DTR脚。
去除读保护后,会擦除整片Flash;加读保护后,JLink等无法调试和读出Flash内容,可防止产品代码被复制。
IAP:
当STM32 MCU Flash中已存在通过ISP方式烧写的固件,若要进行升级,可通过IAP方式升级。把STM32 Flash分成IAP bootloader和APP区。
IAP升级相当于擦掉Flash APP区代码,然后通过串口或者SD卡等读取要升级的代码,烧写到Flash APP区替换原来的部分完成升级。
要注意的地方:
1.APP区代码需要设置中断向量表 NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0×9000);
2.要更改起始烧写地址
STM32F107使用LWIP协议栈
作者 codercjg 在 21 三月 2016, 2:19 下午
STM32F107自带一个以太网口,官网的例子和实际项目还是有一定差距的。
有几点要注意的地方:
1.官网的例子如果不插网线上电,之后再插网线是一直连不上的,实际使用中需要读取phy寄存器PHY_BSR来判断网线是否插入和断开
2.PHY_ADDRESS默认为0,这个得根据phy地址线上电时的值决定,实在不行也可以通过读取phy id 的方式确定地址是多少
- Ethernet DMA接收缓存ETH_RXBUFNB 和发送缓存ETH_TXBUFNB的长度需根据需要进行修改
4.需要设置回掉函数
建立连接:tcp_connect(pcb, &serveraddr, TCP_PORT, tcp_client_connected);
收到数据:tcp_recv(pcb, tcp_client_recv);
出错:tcp_err(pcb, tcp_error);
5.可开启心跳包机制LWIP_TCP_KEEPALIVE,通过定期发送心跳包,是否收到ACK检测tcp异常的发生(如突然拔网线等)
6.发送数据前需检查缓冲区剩余长度,tcp_write()只是把数据包加入发送队列,实际发送动作由tcp_output()执行。
if((tcp_sndbuf(TcpPCB) > len) && (tcp_write(TcpPCB, data, len, TCP_WRITE_FLAG_MORE) == ERR_OK)) {
/* send current data /
tcp_output(TcpPCB);
} else {
/ send data in tcp queue */
tcp_output(TcpPCB);
}
STM32F107使用LWIP协议栈
作者 codercjg 在 21 三月 2016, 2:18 下午
STM32F107自带一个以太网口,官网的例子和实际项目还是有一定差距的。
有几点要注意的地方:
1.官网的例子如果不插网线上电,之后再插网线是一直连不上的,.实际使用中需要读取phy寄存器PHY_BSR来判断网线是否插入和断开
2.PHY_ADDRESS默认为0,这个得根据phy地址线上电时的值决定,实在不行也可以通过读取phy id 的方式确定地址是多少
- Ethernet DMA接收缓存ETH_RXBUFNB 和发送缓存ETH_TXBUFNB的长度需根据需要进行修改
4.需要设置回掉函数
建立连接:tcp_connect(pcb, &serveraddr, TCP_PORT, tcp_client_connected);
收到数据:tcp_recv(pcb, tcp_client_recv);
出错:tcp_err(pcb, tcp_error);
5.可开启心跳包机制LWIP_TCP_KEEPALIVE,通过定期发送心跳包,是否收到ACK检测tcp异常的发生(如突然拔网线等)
6.发送数据前需检查缓冲区剩余长度,tcp_write()只是把数据包加入发送队列,实际发送动作由tcp_output()执行。
if((tcp_sndbuf(TcpPCB) > len) && (tcp_write(TcpPCB, data, len, TCP_WRITE_FLAG_MORE) == ERR_OK)) {
/* send current data /
tcp_output(TcpPCB);
} else {
/ send data in tcp queue */
tcp_output(TcpPCB);
}
STM32读保护和写保护
作者 codercjg 在 8 三月 2016, 5:30 下午
所有STM32的芯片都提供对Flash的保护,防止对Flash的非法访问–写保护和读保护。 读保护是作用于整个Flash存储区,一旦设置了Flash的读保护,内置的Flash存储区只能通过程序的正常执行才能读出,而不能通过下述任何一种方式读出: 通过调试器(JTAG或SWD) 从RAM中启动并执行的程序 写保护是以四页(1KB/页)Flash存储区为单位提供保护,对被保护的页实施编程或擦除操作将不被执行,同时产生操作错误标志。 以下是一个简单的小结: 读保护 写保护 对Flash的操作功能 有效 有效 CPU只能读; 禁止调试和非法访问 有效 无效 CPU可以读写; 禁止调试和非法访问;页0~3为写保护 无效 有效 CPU可读; 允许调试和非法访问 无效 无效 CPU可以读写; 允许调试和非法访问
设置为读保护后就不能用调试器调试了,解除读保护时,会擦除整片flash。
产品出厂时直接通过ISP命令设置读保护,别人就不能读出Flash上的内容,可防止产品被抄板。