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上的內容,可防止產品被抄板。