樹莓派 linux下modbus總結(TCP-modbus,RS232-modbus)
環境 win7 Anaconda2
一、安裝pyserial和modbus-tk:
C:\Users\admin>cd C:\Anaconda2
C:\Anaconda2>easy_install pyserial
Searching for pyserial
Best match: pyserial 3.4
Processing pyserial-3.4-py2.7.egg
pyserial 3.4 is already the active version in easy-install.pth
Installing miniterm.py script to C:\Anaconda2\Scripts
Using c:\anaconda2\lib\site-packages\pyserial-3.4-py2.7.egg
Processing dependencies for pyserial
Finished processing dependencies for pyserial
C:\Anaconda2>python
Python 2.7.12 |Anaconda 4.2.0 (64-bit)| (default, Jun 29 2016, 11:07:13) [MSC v.1500 64 bit (AMD64)] on win32
Type “help”, “copyright”, “credits” or “license” for more information.
Anaconda is brought to you by Continuum Analytics.
Please check out:
import serial
C:\Anaconda2>easy_install modbus-tk
Searching for modbus-tk
Reading https://pypi.python.org/simple/modbus-tk/
Downloading https://pypi.python.org/packages/80/71/3d8a6596cd65670d4d2beee262d5964deb933614fd7f58a739d5aa0f0332/modb
us_tk-0.5.7.tar.gz#md5=74c02b9b57dc32913da52d020eaf11d3
Best match: modbus-tk 0.5.7
Processing modbus_tk-0.5.7.tar.gz
Writing c:\users\admin\appdata\local\temp\easy_install-z7i_tm\modbus_tk-0.5.7\setup.cfg
Running modbus_tk-0.5.7\setup.py -q bdist_egg –dist-dir c:\users\admin\appdata\local\temp\easy_install-z7i_tm\modbu
s_tk-0.5.7\egg-dist-tmp-4bmk1v
zip_safe flag not set; analyzing archive contents…
Moving modbus_tk-0.5.7-py2.7.egg to c:\anaconda2\lib\site-packages
Adding modbus-tk 0.5.7 to easy-install.pth file
Installed c:\anaconda2\lib\site-packages\modbus_tk-0.5.7-py2.7.egg
Processing dependencies for modbus-tk
Finished processing dependencies for modbus-tk
C:\Anaconda2>python
Python 2.7.12 |Anaconda 4.2.0 (64-bit)| (default, Jun 29 2016, 11:07:13) [MSC v.1500 64 bit (AMD64)] on win32
Type “help”, “copyright”, “credits” or “license” for more information.
Anaconda is brought to you by Continuum Analytics.
Please check out: http://continuum.io/thanks and https://anaconda.org
import modbus_tk
二、
lwip-win32-msvc-0.1
已經解決的問題:
一:下載lwip的原始碼,在windows上重新組織檔案架構,然後進行編譯。剛好美國已經有人做了這樣面的工作,所以
就先用一下咯。畢竟一開始要做這方面的工作,還是有難度的。可以下載lwip-win32-msvc-0.1.zip檔案。
網路上有很多這樣的下載連線,但是都不好用(動不動就要你去註冊,花了時間註冊好了本以為可以下載了,沒想到下載時候提示,積分不夠,需要積分可以啊,買卡充值什麼的,煩哪。)。官方網也連線不上,後來發現在sina愛問裡面的共享裡面可以下載,很方便的哦。
下載完了之後解壓就可以用了。用vc6開啟就可以了。裡面有兩個工程,一個是Lwip4工程,編譯後是lib庫,供上層應用程式使用;另一個工程是test,是測試程式。分別編譯,lwip4可以順利的編譯通過,但是test工程編譯的時候會遇到問題:
(1)找不到packet32.h檔案。解決的辦法是到官網http://www.winpcap.org/devel.htm上下載winpcap的開發包Download WinPcap 4.0.2 Developer’s Pack,連線是http://www.winpcap.org/install/bin/WpdPack_4_0_2.zip 。下載之後解壓即能看到一系列的目錄,從include目錄下就能找到packet32.h,以及packet32.h檔案裡include的devioctl.h,一起拷貝過來到工程目錄裡面。另外還要到Lib資料夾中拷貝packet32.h的實現lib檔案packet.lib,然後加到工程中來。問題就迎刃而解了。
(2)typedef struct npf_if_addr {
struct sockaddr_storage IPAddress; ///< IP address.
struct sockaddr_storage SubnetMask; ///< Netmask for that address.
struct sockaddr_storage Broadcast; ///< Broadcast address.
}npf_if_addr;
會報packet32.h中上述結構體中的sockaddr_storage未定義:error C2079: ‘IPAddress’ uses undefined struct ‘sockaddr_storage
解決這個問題,可以自己在packet32.h中定義該結構體:
ifndef _SS_PAD1SIZE
struct sockaddr_storage {
u_char sa_len;
u_char sa_family;
u_char padding[128];
};
endif
或者sockaddr_storage 改成 sockaddr 也可以解決問題。
(3)pktif.c(191) : error C2065: ‘OID_802_3_PERMANENT_ADDRESS’ : undeclared identifier
pktif.c(199) : error C2065: ‘NDIS_PACKET_TYPE_ALL_LOCAL’ : undeclared identifier
報上述兩個錯誤,是因為這兩個變數是在Ntddndis.h檔案中定義的,該標頭檔案也是winpcap開發包中的檔案,所以如同(1)需要從開發包inlude目錄下把該檔案拷貝到工程裡來,同時在pktif.c檔案頭加上#include
if LWIP_COMPAT_SOCKETS
define accept(a,b,c) lwip_accept(a,b,c)
define bind(a,b,c) lwip_bind(a,b,c)
define shutdown(a,b) lwip_shutdown(a,b)
define closesocket(s) lwip_close(s)
define connect(a,b,c) lwip_connect(a,b,c)
define getsockname(a,b,c) lwip_getsockname(a,b,c)
define getpeername(a,b,c) lwip_getpeername(a,b,c)
define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)
define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)
define listen(a,b) lwip_listen(a,b)
define recv(a,b,c,d) lwip_recv(a,b,c,d)
define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)
define send(a,b,c,d) lwip_send(a,b,c,d)
define sendto(a,b,c,d,e,f) lwip_sendto(a,b,c,d,e,f)
define socket(a,b,c) lwip_socket(a,b,c)
define select(a,b,c,d,e) lwip_select(a,b,c,d,e)
define ioctlsocket(a,b,c) lwip_ioctl(a,b,c)
if LWIP_POSIX_SOCKETS_IO_NAMES
define read(a,b,c) lwip_read(a,b,c)
define write(a,b,c) lwip_write(a,b,c)
define close(s) lwip_close(s)
define fcntl(a,b,c) lwip_fcntl(a,b,c)
endif /* LWIP_POSIX_SOCKETS_IO_NAMES */
endif /* LWIP_COMPAT_SOCKETS */
int socket(int domain, int type, int protocol);
伺服器根據地址型別(ipv4,ipv6)、socket型別、協議建立socket。
domain:協議族,常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址
type:socket型別,常用的socket型別有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
protocol:協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
把一個地址族中的特定地址賦給socket
sockfd:socket描述字,也就是socket引用
addr:要繫結給sockfd的協議地址
addrlen:地址的長度
通常伺服器在啟動的時候都會繫結一個地址(如ip地址+埠號),用於提供服務。有些埠號是約定俗成的不能亂用,如80用作http,502用作modbus。
int listen(int sockfd, int backlog);
監聽socket
sockfd:要監聽的socket描述字
backlog:相應socket可以排隊的最大連線個數
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
連線某個socket
sockfd:客戶端的socket描述字
addr:伺服器的socket地址
addrlen:socket地址的長度
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP伺服器監聽到客戶端請求之後,呼叫accept()函式取接收請求
sockfd:伺服器的socket描述字
addr:客戶端的socket地址
addrlen:socket地址的長度
size_t read(int fd, void *buf, size_t count);
讀取socket內容
fd:socket描述字
buf:緩衝區
count:緩衝區長度
size_t write(int fd, const void *buf, size_t count);
向socket寫入內容,其實就是傳送內容
fd:socket描述字
buf:緩衝區
count:緩衝區長度
int close(int fd);
socket標記為以關閉 ,使相應socket描述字的引用計數-1,當引用計數為0的時候,觸發TCP客戶端向伺服器傳送終止連線請求。
- 使用socket建立嵌入式WebServer
要使用socket的前提是已經做好lwip和rtos的移植,如果低層驅動移植完畢,就可以使用socket快速建立應用。
本例是一個簡單的WebServer。
const unsigned char htmldata[] = “\
\
LWIP\
A WebServer Based on LwIP v1.4.1 Hello world!
“;
const unsigned char errhtml[] = “\
\
\
Error!\
\
\
404 - Page not found
\\
“;
/**
* @brief serve tcp connection
* @param conn: connection socket
* @retval None
*/
void http_server(int conn)
{
int buflen = 1500;
int ret;
unsigned char recv_buffer[1500];
/* Read in the request */
ret = read(conn, recv_buffer, buflen);
if(ret <= 0)
{
close(conn);
Printf(“read failed\r\n”);
return;
}
Printf("http server response!\r\n");
if(strncmp((char *)recv_buffer, "GET /lwip", 9) == 0)
{
write(conn, htmldata, sizeof(htmldata)-1);
}
else
{
write(conn, errhtml, sizeof(errhtml)-1);
}
/* Close connection socket */
close(conn);
}
/**
* @brief http_task
* @param arg: pointer on argument(not used here)
* @retval None
*/
static void http_task(void *arg)
{
int sock, newconn, size;
struct sockaddr_in address, remotehost;
/* create a TCP socket */
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
Printf(“can not create socket”);
return;
}
/* bind to port 80 at any interface */
address.sin_family = AF_INET;
address.sin_port = htons(80);
address.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&address, sizeof (address)) < 0)
{
Printf(“can not bind socket”);
close(sock);
return;
}
/* listen for connections (TCP listen backlog = 1) */
listen(sock, 1);
size = sizeof(remotehost);
while (1)
{
newconn = accept(sock, (struct sockaddr )&remotehost, (socklen_t )&size);
if (newconn >= 0)
{
http_server(newconn);
}
else
{
close(newconn);
}
}
}
/****************************************************
* void http_task_init(void)
*
* This function initializes the service.
****************************************************/
void http_task_init(void)
{
sys_thread_new( CHARGEN_THREAD_NAME, http_task, 0, 0, TCPIP_THREAD_PRIO+1); //函式棧在移植sys_thread_new中實現
}
- 使用socket建立Modbus TCP應用
Modbus TCP在網路傳輸層次,就是一串有特定含義的資料包的互動,LwIP層次並不識別是什麼資料。所以從這個角度來講,Modbus TCP移植和其他TCP應用的移植沒有任何差別。
透過表面看本質,只有撥開外層重重包裝看本質,我們才能從紛雜的事件中找到問題的重點,然後剝離不相關的部分,一次解決一個問題。基於這個思想,Modbus TCP應用可以直接劃分為2個層次,底層是驅動部分,負責一包資料從網路上接收上來或傳送出去,上層是Modbus的協議部分,就是Modbus暫存器的操作等。從這個角度來說,只要資料傳輸正確了,那麼怎麼處理就是另一個問題了,比如可以共用Modbus RS485的程式碼等。
下面測試了Modbus TCP的資料傳輸。
Modbus TCP設計有幾個重要的點:
1)Modbus是連續通訊,不能和http一樣完成一次連線後就斷開,所以要不停的read,當讀出錯時在close(conn)關閉連線。
2)Modbus可能存在通訊失敗情況,需要關閉socket後再重新建立socket。
3)Modbus作為工業協議,應用場景下一般不會多個客戶端連線一臺機器,並且多個客戶端連線一臺機器,暫存器的讀寫互斥會是一個大問題,所以常見的做法是一旦連線成功,就關閉socket禁止其他連線進來。客戶端主動斷開後再重新建立socket然後進入listen狀態。
/**
* @brief serve modbus_tcp connection
* @param conn: connection socket
* @retval None
*/
void modbus_tcp_server(int conn)
{
int buflen = 1500;
int ret;
unsigned char recv_buffer[1500];
int i;
Printf(“start modbus tcp\r\n”);
ret = read(conn, recv_buffer, buflen);
while ( ret > 0 )
{
ret = read(conn, recv_buffer, buflen);
Printf(“\r\n>:”); // debug print
for(i=0; i