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