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

原文地址:https://www.cnblogs.com/GengMingYan/p/15738456.html