上次介绍了ENC28J60在STM32上怎么写好第一个Hello World,这次在上次修改好驱动的基础上,使用UIP实现ARP包响应,ICMP响应(ping通MCU)
UIPgitee下载地址: https://gitee.com/jinling_gitee/uip.git github地址: https://github.com/adamdunkels/uip
下载后,项目路径如下:
我们需要使用的文件主要是uip文件夹下的文件,和unix下的uip-conf.h(UIP配置项)和tapdev.h(底层SPI基础读写实现)头文件
在STM32中使用UIP时,由于UIP不是专门针对STM32设计的,所以需要修改一些东西才能正常使用:
1. 修改unix/uip-conf.h文件,这是UIP对用户开放的一些配置文件
2. main.c中声明UIP_APPCALL、uip_log回调函数,这是UIP提供的回调函数,为保证程序尽量简单,本次ping没有用到,暂时为空函数
3. 实现tapdev.h头文件下声明的tapdev_init ENC28J60初始化、tapdev_read 读一包数据、tapdev_send 发送一包数据函数
修改uip-conf.h
对uip-conf.h做一些修改,才能正常使用:
1. 注释webserver.h引入头文件
实现UIP_APPCALL、uip_log函数
这两个函数是UIP的回调函数,本次目的只为使用UIP ping通MCU,所以实现为空函数:
在main.c中声明了两个函数,分别对应UIP_APPCALL和uip_log,和timer.c中调用的clock_time
.... //UIP_APPCALL void main_appcall(){ } //UIP_APPCALL void main_uiplog(char* msg) { } int clock_time(){ } .....
回到uip-conf.h,声明UIP_APPCALL和uip_log实现函数为main_appcall和main_uiplog:
#define UIP_APPCALL main_appcall #define uip_log main_uiplog
clock_time函数,已在clock.h中声明,只不过没有实现而已,所以此处不用重复声明
uipopt.h中声明uip_tcp_appstate_t appstate类型
在uipopt.h 503行位置,有已注释掉的uip_tcp_appstate_t声明,把此处打开:
或者去uip.h中注释掉使用uip_tcp_appstate_t位置:
实现tapdev_init、tapdev_read、tapdev_send函数
新建tapdev.c,实现如下:
#include <tapdev.h> #include "enc28j60.h"//ENC28J60驱动 #include <uip.h> //引用外部文件uip.c中声明的uip_ethaddr extern struct uip_eth_addr uip_ethaddr; //网卡地址 unsigned char my_mac[6] = {0x29, 0x7C, 0x07, 0x37, 0x24, 0x63}; void tapdev_init(void) { enc28j60_init(my_mac); for (int i = 0; i < 6; i++) { uip_ethaddr.addr[i] = my_mac[i]; } } //读取一包数据 unsigned int tapdev_read(void){ enc28j60_packet_receive(uip_buf, MAX_FRAMELEN); } //发送一包数据 void tapdev_send(void) { enc28j60_packet_send(uip_buf, MAX_FRAMELEN); }
把UIP相关文件放在一起
最后把上述提到的unix/uip-conf.h、unix/tapdev.h放到UIP文件夹中,通过IDE(如keil5)设置头文件路径为UIP文件夹,添加如下图红框中的.c文件
keil5 添加下图红框中的.c文件
对于keil5 IDE来说,需要修改每个.c文件的引入头文件,把""改为<>,例如:
最后,编译成功,就可以进行下一步操作了。
main.c实现arp响应和icmp响应
在此之前,需要保证添加以上文件后,可以正常编译通过:
首先在mainc.c中声明UIP的UIP_BUF:
这个UIP_BUF主要用来在接收到数据后,判断协议类型,源、目标MAC地址
#define UIP_BUF ((struct uip_eth_hdr *)&uip_buf[0])
处理ICMP请求操作:
处理ping请求过来的包
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 */ //初始化前等待一些时间 for(int i = 0;i < 20;i++) { //enc28j60PhyWrite(PHLCON,0x7a4); HAL_Delay(500); print("begin init enc28j60..."); } tapdev_init();//初始化enc28j60 print("init enc28j60 success!"); //初始化UIP uip_init(); uip_ipaddr_t ipaddr; uip_ipaddr(ipaddr, 192, 168, 1, 8);//MCU IP地址 uip_sethostaddr(ipaddr); uip_ipaddr(ipaddr, 192, 168, 1, 1);//网关,电脑和MCU经过路由器时使用 uip_setdraddr(ipaddr); uip_ipaddr(ipaddr, 255, 255, 252, 0);//子网掩码 uip_setnetmask(ipaddr); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ //尝试读取一包数据 uip_len = tapdev_read(); if(uip_len == 0){ //没有读到数据 continue; } //是否是ARP请求 if(UIP_BUF->type == htons(UIP_ETHTYPE_ARP)) { //拼装ARP响应请求 uip_arp_arpin(); if(uip_len>0){ //响应ARP请求发送数据 tapdev_send(); } } //是否是IPV4 if(UIP_BUF->type == htons(UIP_ETHTYPE_IP)) {
//ping过来时,会进入此判断 uip_arp_ipin(); //UIP解析数据 uip_input(); if(uip_len > 0){ //必须调用,不然会出现ping不通情况,用抓包工具看,显示收到数据包checksum bad 校验和失败 uip_arp_out(); tapdev_send(); } } /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
电脑端链接MCU后,需要打开网络设备器,设置为使用下面IP: