STM32 + UIP + ENC28J60 实现TCP 简单通讯
MCU: STM32F103C6T6
背景
上次介绍了怎么把UIP移植到STM32中来,并最后实现一个ping操作,这次在上次基础上实现MCU当TCP服务端,电脑当客户端通过TCP端链接MCU,实现通讯。
为保证程序尽量精简,程序在接受到TCP数据后,会原封不动返回给客户端(电脑), 并通过串口打印。
在使用UIP TCP功能前,需要可以让MCU获取当前时间,主要为实现每10ms毫秒处理一次TCP连接,和每5s秒刷新一次ARP;例如HAL库中有一个HAL_GetTick(),可以获取当前毫秒时间。
操作流程
整体TCP使用流程:
1. 初始化enc28j60、UIP
2. 设置IP、网关、子网掩码
3. 开启端口监听
4. 处理ARP请求、响应
5. 每10ms处理一次TCP请求
6. 每5秒刷新一次ARP
7. 通过UIP提供的UIP_APPCALL回调,接收TCP数据、发送TCP数据
main主循环中每10ms处理一批TCP请求、每5s 刷新一次ARP.在操作前,需保证网卡初始化正常,UIP运行正常。
TCP操作
网卡部分初始化、UIP初始化、设置IP、网关等信息不在重复介绍,主要介绍TCP操作部分。
设置TCP端口
通过调用uip的uip_listen函数,来设置要监听的TCP端口:
uip_listen(HTONS(8099));//8099为端口号
这里注意,uip_listen内套了一个HTONS函数(宏),端口号是用HTONS包裹的。
声明UIP回调函数UIP_APPCALL实现
UIP_APPCALL也是一个宏。UIP会在各种事件触发的时候调用它(个人理解..)
TCP的接收、发送也是在此处做;
首先,需要先创建一个函数,例如在main.c中声明一个main_appcall:
//处理接收到的TCP消息 void main_appcall() { //处理UIP各种事件,TCP数据接收、发送,就在此处处理 if( uip_newdata() ){ //有新数据,uip_appdata为TCP数据、uip_len为TCP数据长度 print3(uip_appdata, uip_len);//可取消,调试信息 uip_send(uip_appdata,uip_len);//发送TCP数据 } }
然后在uip-conf.h中声明UIP_APPCALL宏
void main_appcall(); #define UIP_APPCALL main_appcall
最后在设置UIP监听、主循环中处理TCP请求:
uip_listen(HTONS(1234));//设置监听 ... while(1){ //UIP_COUNTS : TCP最大连接数 for(uint8_t i = 0; i < UIP_CONNS; i++) { uip_periodic(i); if(uip_len > 0) { //有TCP新数据 print("tcp data handler..."); uip_arp_out(); tapdev_send(); } } }
最后附上main.c部分代码:
#define UIP_BUF ((struct uip_eth_hdr *)&uip_buf[0]); void main_appcall() { //处理UIP各种事件,TCP数据接收、发送,就在此处处理 if( uip_newdata() ){ //有新数据,uip_appdata为TCP数据、uip_len为TCP数据长度 print3(uip_appdata, uip_len);//可取消,调试信息 uip_send(uip_appdata,uip_len);//发送TCP数据 } }
//记录ARP、TCP刷新时间,目的实现定时处理TCP请求、刷新ARP
uint32_t lastTimer = 0,lastTimerARP = 0;
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ MX_USART1_UART_Init(); //设置开始时间 lastTimer = HAL_GetTick(); lastTimerARP = HAL_GetTick(); //等待一些时间,准备初始化ENC28J60 /* USER CODE BEGIN 2 */ for(int i = 0;i < 20;i++) { //enc28j60PhyWrite(PHLCON,0x7a4); HAL_Delay(500); print("begin init enc28j60..."); } //开始初始化,如果初始化不成功会阻塞 //enc28j60_init(my_mac); tapdev_init();//初始化enc28j60 //表示初始化成功,说明接线正常 print("init enc28j60 success!!!"); uip_init(); uip_ipaddr_t ipaddr; uip_ipaddr(ipaddr, 192, 168, 1, 8); uip_sethostaddr(ipaddr); uip_ipaddr(ipaddr, 192, 168, 1, 1); uip_setdraddr(ipaddr); uip_ipaddr(ipaddr, 255, 255, 252, 0); uip_setnetmask(ipaddr); //开启指定端口监听, 注意端口处HTONS,不是直接写的端口号! uip_listen(HTONS(1234)); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ //处理IP、ARP、Ping消息 uip_len = tapdev_read(); if(uip_len != 0){ if(UIP_BUF->type == htons(UIP_ETHTYPE_ARP)) { uip_arp_arpin(); if(uip_len>0){ print("rarp package send!!!"); tapdev_send(); } } if(UIP_BUF->type == htons(UIP_ETHTYPE_IP)) { uip_arp_ipin(); uip_input(); if(uip_len > 0){ uip_arp_out(); tapdev_send(); } } } //处理TCP链接,10毫秒处理一次 if((HAL_GetTick() - lastTimer) > 10) { //UIP_COUNTS : TCP最大连接数 for(uint8_t i = 0; i < UIP_CONNS; i++) { uip_periodic(i); if(uip_len > 0) { //有TCP新数据 print("tcp data handler..."); uip_arp_out(); tapdev_send(); } } lastTimer = HAL_GetTick(); } //ARP 刷新 if((HAL_GetTick() - lastTimerARP) > 5000) { lastTimerARP = HAL_GetTick(); uip_arp_timer(); } /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
最后附上全部代码(keil5),已放在蓝奏云:https://wwb.lanzouw.com/ieZu2y933pe
非常感谢xukai871105总结的笔记,本次TCP成功跑通全靠这篇文章,推荐去看一看,写的很详细: https://blog.csdn.net/xukai871105/article/details/17471865