Tiếp tục chuỗi tutorial lập trình giao tiếp ENC28J60 với giao thức TCP, trong bài này chúng ta sẽ hoàn thành các hàm gửi nhận data trong giao thức TCP Client. Về cơ bản, cũng tương tự như với TCP server thôi.
Nhận dữ liệu từ server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void TCP_readData(TCP_struct *TCP_Struct_Frame,uint16_t len) { uint16_t data_len,data_offset,i; //kiểm tra số hiệu gói tin if(swap32(TCP_Struct_Frame->Sequence_Number) == tcp_client1.Acknowledgement) //nếu chính xác thì nhận, không thì bỏ quả vì có thể nó là gói tin thất lạc thôi { data_len= swap16(TCP_Struct_Frame->TotoLength) -20 - (TCP_Struct_Frame->data_offset >> 2); //tính độ dài chuỗi tin nhắn gửi tới data_offset = (TCP_Struct_Frame->data_offset >> 2) - 20; //tính điểm bắt đầu của data //in ra màn hình for(i=0;i<data_len;i++) UART_putChar(TCP_Struct_Frame->data[i+data_offset]); TCP_make_herder(TCP_Struct_Frame,len,FOR_PSH_ACK); //lúc này client sẽ chủ động + độ dài data mà nó nhận đc vào Sequence_Number tcp_client1.Sequence_Number = swap32(TCP_Struct_Frame->Sequence_Number); tcp_client1.Acknowledgement = swap32(TCP_Struct_Frame->Acknowledgement); TCP_send(TCP_Struct_Frame,54,(uint8_t *)"",0);//trả lời lại ack } } |
Giải thích: Sau khi nhận được data từ server mình in ra màn hình, lưu lại các số hiệu gói tin và chả lời lại với ACK hoặc PSH|ACK cũng được
Server gửi dữ liệu sẽ dùng cờ PSH|ACK do vậy trong hàm TCP_read nhánh cờ TCP_PSH|TCP_ACK mình sẽ kiểm tra tcp_client status, nếu đã có kết nối với server thì gọi hàm TCP_readData
1 2 3 4 |
if(tcp_client1.tcp_status == 2) //neu đã kết nối với server { TCP_readData(TCP_Struct_Frame,len); } |
Bây giờ mở hercules mode TCP_server, mở thêm tab debug uart và gửi thử dữ liệu đi nào
Gửi dữ liệu lên server
Mình sẽ thêm nhánh maker header FOR_SEND cho hàm TCP_make_herder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
else if(type == FOR_SEND) { memcpy(TCP_Struct_Frame->MAC_dich,tcp_client1.mac_defaul,6); memcpy(TCP_Struct_Frame->MAC_nguon,macaddr,6); TCP_Struct_Frame->Ethernet_type = 0x0008; memcpy(TCP_Struct_Frame->SourceIP,ip,4); memcpy(TCP_Struct_Frame->DestIP,tcp_client1.ip_defaul,4); TCP_Struct_Frame->Header_length = 0x45; //make IP TCP_Struct_Frame->Services=0x0000; TCP_Struct_Frame->Identification=0x4470; TCP_Struct_Frame->Flag=0x0040; TCP_Struct_Frame->TimeToLive=0x80; TCP_Struct_Frame->Protocol=0x06; //tcp TCP_Struct_Frame->CheckSum=0x0000; TCP_Struct_Frame->TotoLength = swap16(40); memcpy(TCP_Struct_Frame->SourceIP,ip,4); TCP_Struct_Frame->Source_Port = swap16(tcp_client1.client_port); TCP_Struct_Frame->Dest_Port = swap16(tcp_client1.port); TCP_Struct_Frame->Sequence_Number=swap32(tcp_client1.Sequence_Number); TCP_Struct_Frame->Acknowledgement=swap32(tcp_client1.Acknowledgement); TCP_Struct_Frame->data_offset=0x50; TCP_Struct_Frame->Window = 0xCA01; // thong bao cho server biet bo dem nhan toi da TCP_Struct_Frame->TCP_Checksums=0x0000; TCP_Struct_Frame->Urgent_Pointer=0x0000; } |
Và viết hàm để sendData lên server
Hàm này sẽ nhận vào dữ liệu, độ dài của dữ liệu, và 1 timeout, sau khi gửi dữ liệu đi, server sẽ phản hồi lại cờ ACK, do vậy mình dùng vòng lặp vô tận while(1) để kiểm tra biến status đã nhận được cờ ACK chưa, nếu chưa và quá timout thì thoát và báo gửi lỗi ! Ngoài ra, khi hàm này được gọi mà chưa kết nối server thì mình cũng thoát luôn mà không cần gửi vì điều đó vô nghĩa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
uint8_t TCP_sendData(uint8_t *data,uint16_t data_length,uint32_t timeout) { uint32_t t; TCP_struct *TCP_Struct_Frame = (TCP_struct *)eth_buffer; if(tcp_client1.tcp_status != 2 ) { UART_putString("Khong co ket noi\r\n"); return 2; } net_SetStatus(0); TCP_make_herder(TCP_Struct_Frame,0,FOR_SEND); TCP_send(TCP_Struct_Frame,54,data,data_length); t = HAL_GetTick(); tcp_client1.tcp_status = 3; //check ack net_SetStatus(1); while(1) { if(tcp_client1.tcp_status == 2) { UART_putString("Gui thanh cong\r\n"); return 1; } if(HAL_GetTick() - t > timeout) { tcp_client1.tcp_status = 2; UART_putString("Gui du lieu that bai\r\n"); return 0; } } } |
tcp_clien1.status = 3 // đang cần check cờ ack
Và giờ trong hàm TCP_read nhánh bắt cờ ACK, mình sẽ thêm đoạn code kiểm tra xem cờ ACK này có phải là ACK của gói tin ta vừa gửi đi không
1 2 3 4 5 6 |
if(tcp_client1.tcp_status == 3) { tcp_client1.Sequence_Number = swap32(TCP_Struct_Frame->Acknowledgement); tcp_client1.Acknowledgement = swap32(TCP_Struct_Frame->Sequence_Number); tcp_client1.tcp_status = 2; } |
Thực chất, để đảm bảo chắc chắn cái ACK này là phản hôig của gói tin mình vừa gửi đi, ta phải kiểm tra kĩ hơn số hiệu gói tin nữa
Mình sẽ viết thêm 1 vài hàm nhận data từ UART để tiện cho việc gửi dữ liệu đi. Tức là mình sẽ gõ tin nhắn cần gửi vào Tab UART hercules, truyền xuống cho chip để chip gửi lên server.
Các bạn bật ngắt UART lên
Và nhận từng byte data bằng ngắt UART, đừng quên gọi hàm cho phép nhận
Cuối cùng là trong hàm main, check data gửi tới để gọi hàm send lên server
Tải FULL soure cho bài này tại đây
https://drive.google.com/file/d/1yDZA2OIVuso6ORLCL9TyTu-s6T-ojWJp/view?usp=sharing