这里,我们需要了解uIP是把网页数据(源文件)存放在data_index_html,通过将这里面的数据发送给电脑浏览器,浏览器就会显示出我们所设计的界面了。当用户在网页上面操作的时候,浏览器就会发送消息给WEB服务器,服务器根据收到的消息内容,判断用户所执行的操作,然后发送新的页面到浏览器,这样用户就可以看到操作结果了。本章,我们实现的WEB服界面如图57.3.2所示:
图57.3.2 WEB服务器界面
图中两个按键分别控制DS0和DS1的亮灭,然后还显示了STM32芯片的温度和RTC时间等信息。
控制DS0,DS1亮灭我们是通过发送不同的页面请求来实现的,这里我们采用的是Get方法(科普找百度),将请求参数放到URL里面,然后WEB服务器根据URL的参数来相应内容,这样实际上STM32就是从URL获取控制参数,以控制DS0和DS1的亮灭。uIP在得到Get请求后判断URL内容,然后做出相应控制,最后修改data_index_html里面的部分内容(比如指示灯图标的变化,以及提示文字的变化等),再将data_index_html发送给浏览器,显示新的界面。
显示STM32温度和RTC时间是通过刷新实现的,uIP每次得到来自浏览器的请求就会更新data_index_html里面的温度和时间等信息,然后将data_index_html发送给浏览器,这样达到更新温度和时间的目的。但是这样我们需要手动刷新,比较笨,所以我们在网页源码里面加入了自动刷新的控制代码,每10秒钟刷新一次,这样就不需要手动刷新了。
handle_input函数实现了我们所说的这一切功能,另外请注意data_index_html是存放在httpd-fsdata.c(该文件通过include的方式包含进工程里面)里面的一个数组,并且由于该数组的内容需要不停的刷新,所以我们定义它为sram数据,data_index_html里面的数据,则是通过一个工具软件:amo的编程小工具集合V1.2.6.exe,将网页源码转换而来,该软件在光盘有提供,如果想自己做网页的朋友,可以通过该软件转换。
WEB服务器就为大家介绍这么多。
接下来看看TCP服务器appcall函数:tcp_server_demo_appcall,该函数在tcp_server_demo.c里面实现,该函数代码如下:
u8 tcp_server_databuf[200]; //发送数据缓存
u8 tcp_server_sta; //服务端状态
//[7]:0,无连接;1,已经连接;
//[6]:0,无数据;1,收到客户端数据
//[5]:0,无数据;1,有数据需要发送
//这是一个TCP 服务器应用回调函数。
//该函数通过UIP_APPCALL(tcp_demo_appcall)调用,实现Web Server的功能.
//当uip事件发生时,UIP_APPCALL函数会被调用,根据所属端口(1200),确定是否执行该函数。
//例如 : 当一个TCP连接被创建时、有新的数据到达、数据已经被应答、数据需要重发等事件
void tcp_server_demo_appcall(void)
{
struct tcp_demo_appstate *s = (struct tcp_demo_appstate *)&uip_conn->appstate;
if(uip_aborted())tcp_server_aborted(); //连接终止
if(uip_timedout())tcp_server_timedout(); //连接超时
if(uip_closed())tcp_server_closed(); //连接关闭
if(uip_connected())tcp_server_connected(); //连接成功
if(uip_acked())tcp_server_acked(); //发送的数据成功送达
//接收到一个新的TCP数据包
if (uip_newdata())//收到客户端发过来的数据
{
if((tcp_server_sta&(1<<6))==0)//还未收到数据
{
if(uip_len>199) ((u8*)uip_appdata)[199]=0;
strcpy((char*)tcp_server_databuf,uip_appdata);
tcp_server_sta|=1<<6;//表示收到客户端数据
}
}else if(tcp_server_sta&(1<<5))//有数据需要发送
{
s->textptr=tcp_server_databuf;
s->textlen=strlen((const char*)tcp_server_databuf);
tcp_server_sta&=~(1<<5);//清除标记
}
//当需要重发、新数据到达、数据包送达、连接建立时,通知uip发送数据
if(uip_rexmit()||uip_newdata()||uip_acked()||uip_connected()||uip_poll())
{
tcp_server_senddata();
}
}
该函数通过uip_newdata()判断是否接收到客户端发来的数据,如果是,则将数据拷贝到tcp_server_databuf缓存区,并标记收到客户端数据。当有数据要发送(KEY0按下)的时候,将需要发送的数据通过tcp_server_senddata函数发送出去。
最后,我们看看TCP客户端appcall函数:tcp_client_demo_appcall,该函数代码同TCP服务端代码十分相似,该函数在tcp_server_demo.c里面实现,代码如下:
u8 tcp_client_databuf[200]; //发送数据缓存
u8 tcp_client_sta; //客户端状态
//[7]:0,无连接;1,已经连接;
//[6]:0,无数据;1,收到客户端数据
//[5]:0,无数据;1,有数据需要发送
//这是一个TCP 客户端应用回调函数。
//该函数通过UIP_APPCALL(tcp_demo_appcall)调用,实现Web Client的功能.
//当uip事件发生时,UIP_APPCALL函数会被调用,根据所属端口(1400),确定是否执行该函数。
//例如 : 当一个TCP连接被创建时、有新的数据到达、数据已经被应答、数据需要重发等事件
void tcp_client_demo_appcall(void)
{
struct tcp_demo_appstate *s = (struct tcp_demo_appstate *)&uip_conn->appstate;
if(uip_aborted())tcp_client_aborted(); //连接终止
if(uip_timedout())tcp_client_timedout(); //连接超时
if(uip_closed())tcp_client_closed(); //连接关闭
if(uip_connected())tcp_client_connected(); //连接成功
if(uip_acked())tcp_client_acked(); //发送的数据成功送达
//接收到一个新的TCP数据包
if (uip_newdata())
{
if((tcp_client_sta&(1<<6))==0)//还未收到数据
{
if(uip_len>199) ((u8*)uip_appdata)[199]=0;
strcpy((char*)tcp_client_databuf,uip_appdata);
tcp_client_sta|=1<<6;//表示收到客户端数据
}
}else if(tcp_client_sta&(1<<5))//有数据需要发送
{
s->textptr=tcp_client_databuf;
s->textlen=strlen((const char*)tcp_client_databuf);
tcp_client_sta&=~(1<<5);//清除标记
}
//当需要重发、新数据到达、数据包送达、连接建立时,通知uip发送数据
if(uip_rexmit()||uip_newdata()||uip_acked()||uip_connected()||uip_poll())
{
tcp_client_senddata();
}
}
该函数也是通过uip_newdata()判断是否接收到服务端发来的数据,如果是,则将数据拷贝到tcp_client_databuf缓存区,并标记收到服务端数据。当有数据要发送(KEY2按下)的时候,将需要发送的数据通过tcp_client_senddata函数发送出去。
uIP通过clock-arch里面的clock_time获取时间节拍,我们通过在timerx.c里面初始化定时器6,用于提供clock_time时钟节拍,每10ms加1,这里代码就不贴出来了,请大家查看光盘源码。
最后在test.c里面,我们要实现好几个函数,但是这里仅贴出main函数以及uip_polling函数,该部分如下:
#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
int main(void)
{
u8 key;
u8 tcnt=0;
u8 tcp_server_tsta=0XFF;
u8 tcp_client_tsta=0XFF;
uip_ipaddr_t ipaddr;
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init(); //初始化LCD
KEY_Init(); //初始化按键
RTC_Init(); //初始化RTC
Adc_Init(); //初始化ADC
usmart_dev.init(72); //初始化USMART
POINT_COLOR=RED; //设置为红色
LCD_ShowString(60,10,200,16,16,"WarShip STM32");
LCD_ShowString(60,30,200,16,16,"ENC28J60 TEST");
LCD_ShowString(60,50,200,16,16,"ATOM@ALIENTEK");
while(tapdev_init()) //初始化ENC28J60错误
{
LCD_ShowString(60,70,200,16,16,"ENC28J60 Init Error!"); delay_ms(200);
LCD_Fill(60,70,240,86,WHITE);//清除之前显示
};
uip_init(); //uIP初始化
LCD_ShowString(60,70,200,16,16,"KEY0:Server Send Msg");
LCD_ShowString(60,90,200,16,16,"KEY2:Client Send Msg");
LCD_ShowString(60,110,200,16,16,"IP:192.168.1.16");
LCD_ShowString(60,130,200,16,16,"MASK:255.255.255.0");
LCD_ShowString(60,150,200,16,16,"GATEWAY:192.168.1.1");
LCD_ShowString(30,200,200,16,16,"TCP RX:");
LCD_ShowString(30,220,200,16,16,"TCP TX:");
LCD_ShowString(30,270,200,16,16,"TCP RX:");
LCD_ShowString(30,290,200,16,16,"TCP TX:");
POINT_COLOR=BLUE;
uip_ipaddr(ipaddr, 192,168,1,16); //设置本地设置IP地址
uip_sethostaddr(ipaddr);
uip_ipaddr(ipaddr, 192,168,1,1); //设置网关IP地址(其实就是你路由器的IP地址)
uip_setdraddr(ipaddr);
uip_ipaddr(ipaddr, 255,255,255,0);//设置网络掩码
uip_setnetmask(ipaddr);
uip_listen(HTONS(1200)); //监听1200端口,用于TCP Server
uip_listen(HTONS(80)); //监听80端口,用于Web Server
tcp_client_reconnect(); //尝试连接到TCP Server端,用于TCP Client
while (1)
{
uip_polling(); //处理uip事件,必须插入到用户程序的循环体中
key=KEY_Scan(0);
if(tcp_server_tsta!=tcp_server_sta)//TCP Server状态改变
{
if(tcp_server_sta&(1<<7))LCD_ShowString(30,180,200,16,16,"TCP Server
Connected ");
else LCD_ShowString(30,180,200,16,16,"TCP Server Disconnected");
if(tcp_server_sta&(1<<6)) //收到新数据
{
LCD_Fill(86,200,240,216,WHITE); //清除之前显示
LCD_ShowString(86,200,154,16,16,tcp_server_databuf);
printf("TCP Server RX:%s\r\n",tcp_server_databuf);//打印数据
tcp_server_sta&=~(1<<6); //标记数据已经被处理
}
tcp_server_tsta=tcp_server_sta;
}
if(key==KEY_RIGHT)//TCP Server 请求发送数据
{
if(tcp_server_sta&(1<<7)) //连接还存在
{
sprintf((char*)tcp_server_databuf,"TCP Server OK %d\r\n",tcnt);
LCD_Fill(86,220,240,236,WHITE); //清除之前显示
LCD_ShowString(86,220,154,16,16,tcp_server_databuf); //显示发送数据
tcp_server_sta|=1<<5;//标记有数据需要发送
tcnt++;
}
}
if(tcp_client_tsta!=tcp_client_sta)//TCP Client状态改变
{
if(tcp_client_sta&(1<<7))LCD_ShowString(30,250,200,16,16,"TCP Client
Connected ");
else LCD_ShowString(30,250,200,16,16,"TCP Client Disconnected");
if(tcp_client_sta&(1<<6)) //收到新数据
{
LCD_Fill(86,270,240,286,WHITE); //清除之前显示
LCD_ShowString(86,270,154,16,16,tcp_client_databuf);
printf("TCP Client RX:%s\r\n",tcp_client_databuf);//打印数据
tcp_client_sta&=~(1<<6); //标记数据已经被处理
}
tcp_client_tsta=tcp_client_sta;
}
if(key==KEY_LEFT)//TCP Client 请求发送数据
{
if(tcp_client_sta&(1<<7)) //连接还存在
{
sprintf((char*)tcp_client_databuf,"TCP Client OK %d\r\n",tcnt);
LCD_Fill(86,290,240,306,WHITE); //清除之前显示
LCD_ShowString(86,290,154,16,16,tcp_client_databuf);//显示发送数据
tcp_client_sta|=1<<5;//标记有数据需要发送
tcnt++;
}
}
delay_ms(1);
}
}
//uip事件处理函数
//必须将该函数插入用户主循环,循环调用.
void uip_polling(void)
{
u8 i;
static struct timer periodic_timer, arp_timer;
static u8 timer_ok=0;
if(timer_ok==0)//仅初始化一次
{
timer_ok = 1;
timer_set(&periodic_timer,CLOCK_SECOND/2); //创建1个0.5秒的定时器
timer_set(&arp_timer,CLOCK_SECOND*10); //创建1个10秒的定时器
}
uip_len=tapdev_read(); //从网络读取一个IP包,得到数据长度.uip_len在uip.c中定义
if(uip_len>0) //有数据
{
//处理IP数据包(只有校验通过的IP包才会被接收)
if(BUF->type == htons(UIP_ETHTYPE_IP))//是否是IP包?
{
uip_arp_ipin(); //去除以太网头结构,更新ARP表
uip_input(); //IP包处理
//当上面的函数执行后,如果需要发送数据,则全局变量 uip_len > 0
//需要发送的数据在uip_buf, 长度是uip_len (这是2个全局变量)
if(uip_len>0)//需要回应数据
{
uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求
tapdev_send();//发送数据到以太网
}
}else if (BUF->type==htons(UIP_ETHTYPE_ARP))//处理arp报文,是否是ARP包?
{
uip_arp_arpin();
//当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0
//需要发送的数据在uip_buf, 长度是uip_len(这是2个全局变量)
if(uip_len>0)tapdev_send();//需要发送数据,则通过tapdev_send发送
}
}
else if(timer_expired(&periodic_timer)) //0.5秒定时器超时
{
timer_reset(&periodic_timer); //复位0.5秒定时器
//轮流处理每个TCP连接, UIP_CONNS缺省是40个
for(i=0;i<UIP_CONNS;i++)
{
uip_periodic(i); //处理TCP通信事件
//当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0
//需要发送的数据在uip_buf, 长度是uip_len (这是2个全局变量)
if(uip_len>0)
{
uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求
tapdev_send();//发送数据到以太网
}
}
#if UIP_UDP //UIP_UDP
//轮流处理每个UDP连接, UIP_UDP_CONNS缺省是10个
for(i=0;i<UIP_UDP_CONNS;i++)
{
uip_udp_periodic(i); //处理UDP通信事件
//当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0
//需要发送的数据在uip_buf, 长度是uip_len (这是2个全局变量)
if(uip_len > 0)
{
uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求
tapdev_send();//发送数据到以太网
}
}
#endif
//每隔10秒调用1次ARP定时器函数 用于定期ARP处理,ARP表10秒更新一次,
//旧的条目会被抛弃
if(timer_expired(&arp_timer))
{
timer_reset(&arp_timer);
uip_arp_timer();
}
}
}
其中main函数相对比较简单,先初始化网卡(ENC28J60)和uIP等,然后设置IP地址(192.168.1.16)及监听端口(1200和80),就开始轮询uip_polling函数,实现uIP事件处理,同时扫描按键,实现数据发送处理。当有收到数据的时候,将其显示在LCD上,同时通过串口发送到电脑。注意,这里main函数调用的tcp_client_reconnect函数,用于本地(STM32)TCP Client去连接外部服务端,该函数设置服务端IP地址为192.168.1.103(就是你电脑的IP地址),连接端口为1400,只要没有连上,该函数就会不停的尝试连接。
uip_polling函数,第一次调用的时候创建两个定时器,当收到包的时候(uip_len>0),先区分是IP包还是ARP包,针对不同的包做不同处理,对我们来说主要是通过uip_input处理IP包,实现数据处理。当没有收到包的时候(uip_len=0),通过定时器定时处理各个TCP/UDP连接以及ARP表处理。
软件设计部分就为大家介绍到这里。
57.4 下载验证
在代码编译成功之后,我们通过下载代码到战舰STM32开发板上(假设网络模块已经连接上开发板),LCD显示如图57.4.1所示界面:
图57.4.1 初始界面
可以看到,此时TCP Server和TCP Client都是没有连接的,我们打开:网络调试助手V3.7.exe这个软件(该软件在光盘有提供),然后选择TCP Server,设置本地IP地址为:192.168.1.103(默认就是),设置本地端口为1400,点击连接按钮,就会收到开发板发过来的消息,此时我们按开发板的KEY2,就会发送数据给网络调试助手,同时也可以通过网络调试助手发送数据到STM32开发板。如图57.4.2所示:
图57.4.2 STM32 TCP Client测试
在连接成功建立的时候,会在战舰STM32开发板上面显示TCP Client的连接状态,然后如果收到来自电脑TCP Server端的数据,也会在LCD上面显示,并打印到串口。这是我们实现的TCP Client功能。
如果我们在网络调试助手,选择协议类型为TCP Client,然后设置服务器IP地址为192.168.1.16(就是我们STM32开发板设置的IP地址),然后设置服务器端口为1200,点击连接,同样可以收到开发板发过来的消息,此时我们按开发板的KEY0按键,就可以发送数据到网络调试助手,同时网络调试助手也可以发送数据到我们的开发板。如图57.4.3所示:
图57.4.3 STM32 TCP Server测试
在连接成功建立的时候,会在战舰STM32开发板上面显示TCP Server的连接状态,然后如果收到来自电脑TCP Client端的数据,便会在LCD上面显示,并打印到串口。这是我们实现的TCP Server功能。
最后,我们测试WEB服务器功能。打开浏览器,输入http://192.168.1.16 ,就可以看到如下界面,如图57.4.4所示:
图57.4.4 STM32 WEB Server测试
此时,我们点击网页上的DS0状态反转和DS1状态反转按钮,就可以控制DS0和DS1的亮灭了。同时在该界面还显示了STM32的温度和RTC时间,每次刷新的时候,进行数据更新,另外浏览器每10秒钟会自动刷新一次,以更新时间和温度信息。
|