? ? ? ? 好久沒給大家更新了,上周末大學大四開學,所以停更了幾天,回來后在做項目,接下來的幾篇文章,給大家帶來幾個項目,第一個介紹的是機械臂操作,說是機械臂操作,簡單來說,就是tcp網絡通信,完成相應的指令。
? ? ? ? 任務要求是:
????????1)基于TCP服務器的機械臂,端口號可指定暫時為8888, ip是Windows的ip;
????????查看Windows的IP:按住Windows+r 按鍵,輸入cmd , 輸入ipconfig
????????2)點擊軟件中的開啟監聽;
????????3)機械臂需要發送16進制數,共5個字節,協議如下
????????0xff 0x02 x y 0xff
????????0xff:起始結束協議,固定的;
????????0x02:控制機械手臂協議,固定的;
????????x:指定要操作的機械臂 0x00 紅色擺臂 0x01 藍色擺臂
????????y:指定角度
? ? ? ? 機械臂的小應用如下圖,因為涉及版權問題,先不貼給大家了,可以私信
????????首先我們來看一下完成這個任務需要怎么做,初始化階段,核心是建立客戶端與服務器的通信鏈路。先通過socket()函數創建 TCP 套接字(負責數據傳輸的 “通道”),若創建失敗則報錯退出;接著定義服務器的網絡信息(IP、端口、協議族),并通過connect()函數與目標服務器建立連接,確保后續指令能準確送達;同時初始化紅 / 藍臂的初始角度(紅 0°、藍 90°),并向用戶提示控制按鍵規則與角度范圍,為后續操作鋪墊。
然后是核心交互階段,以 “用戶輸入 - 角度計算 - 指令封裝 - 數據發送” 為循環邏輯。通過getchar()獲取用戶按鍵(w/s/d/a/q),先處理退出邏輯(按 q 則關閉連接并退出);若為控制按鍵,則先更新對應機械臂的角度(紅臂 ±1°、藍臂 ±1°),并強制將角度限制在規定范圍(紅 - 90°~90°、藍 0°~180°);再按固定協議封裝 5 字節指令(起始符 0xff + 類型 0x02 + 臂標識 + 角度 + 結束符 0xff),最后通過send()將指令發送給服務器,完成一次控制;若輸入無效按鍵,則提示用戶重新輸入。
最后是收尾階段,當用戶按 q 退出循環后,通過close()關閉之前創建的 TCP 套接字,釋放網絡資源,確保程序優雅退出,避免資源泄漏。
#include <myhead.h> // 自定義頭文件,通常包含標準庫和項目通用定義// 宏定義服務器的端口號和IP地址
#define PORT 8888 // 服務器監聽的端口號
#define IP "192.168.0.74" // 服務器的IP地址int main(int argc, const char *argv[])
{char key; // 存儲用戶輸入的控制按鍵char buff[5]; // 用于發送數據的緩沖區,長度為5字節// 創建TCP套接字:AF_INET表示IPv4協議,SOCK_STREAM表示TCP協議,0表示自動選擇協議int oldfd = socket(AF_INET, SOCK_STREAM, 0);// 檢查套接字是否創建成功if (oldfd == -1){perror("socket"); // 打印錯誤信息return -1; // 創建失敗,退出程序}// 定義服務器的網絡地址結構struct sockaddr_in server = {.sin_family = AF_INET, // 使用IPv4地址族.sin_port = htons(PORT), // 將端口號轉換為網絡字節序.sin_addr.s_addr = inet_addr(IP) // 將字符串IP轉換為網絡字節序};// 連接到服務器if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("connect"); // 打印連接失敗信息return -1; // 連接失敗,退出程序}// 連接成功后,打印服務器信息和操作提示printf("已成功連接了服務器%s-%d\n", inet_ntoa(server.sin_addr), PORT);printf("控制命令為:w--(紅色臂順時針+1°) s--(紅色臂逆時針-1°) d--(藍色臂順時針+1°) a--(藍色臂逆時針-1°)\n");printf("角度范圍:紅色臂(-90°~90°) 藍色臂(0°~180°)\n");printf("按q鍵退出程序\n");// 初始化機械臂角度int red_angle = 0; // 紅色臂初始角度設為0°int blue_angle = 90; // 藍色臂初始角度設為90°// 主控制循環:持續接收用戶輸入并發送控制命令while (1){printf("\n請輸入控制按鍵:");key = getchar(); // 獲取用戶輸入的按鍵while(getchar()!='\n'); // 清空輸入緩沖區,避免殘留字符影響下次輸入// 檢查是否退出程序if (key == 'q'){printf("退出程序...\n");break; // 跳出循環,結束程序}// 初始化數據緩沖區(通信協議格式)buff[0] = 0xff; // 幀頭標志buff[1] = 0x02; // 數據類型或長度標識buff[4] = 0xff; // 幀尾標志// 根據用戶輸入的按鍵執行相應操作switch (key){case 'w': // 紅色臂順時針旋轉+1°red_angle += 1;// 限制角度在有效范圍內if(red_angle > 90){red_angle = 90; // 超過最大角度,強制設為最大值}else if(red_angle < -90) {red_angle = -90; // 小于最小角度,強制設為最小值}buff[2] = 0x00; // 0x00表示控制紅色臂buff[3] = red_angle; // 存儲當前角度值printf("紅色臂角度更新:%d°\n", red_angle);break;case 's': // 紅色臂逆時針旋轉-1°red_angle -= 1;// 限制角度在有效范圍內if(red_angle > 90){red_angle = 90;}else if(red_angle < -90){red_angle = -90;}buff[2] = 0x00; // 0x00表示控制紅色臂buff[3] = red_angle; // 存儲當前角度值printf("紅色臂角度更新:%d°\n", red_angle);break;case 'd': // 藍色臂順時針旋轉+1°blue_angle += 1;// 限制角度在有效范圍內if(blue_angle > 180){blue_angle = 180;}else if(blue_angle < 0){blue_angle = 0;}buff[2] = 0x01; // 0x01表示控制藍色臂buff[3] = blue_angle;// 存儲當前角度值printf("藍色臂角度更新:%d°\n", blue_angle);break;case 'a': // 藍色臂逆時針旋轉-1°blue_angle -= 1;// 限制角度在有效范圍內if(blue_angle > 180) {blue_angle = 180;}else if(blue_angle < 0){blue_angle = 0;}buff[2] = 0x01; // 0x01表示控制藍色臂buff[3] = blue_angle;// 存儲當前角度值printf("藍色臂角度更新:%d°\n", blue_angle);break;default: // 處理無效輸入printf("無效按鍵!請重新輸入(w/s/d/a/q)\n");continue; // 跳過本次循環,不發送數據}// 發送數據到服務器int res = send(oldfd, buff, 5, 0);if (res == -1) // 檢查發送是否成功{perror("send"); // 打印發送失敗信息close(oldfd); // 關閉套接字return -1; // 退出程序}}// 關閉套接字,釋放資源close(oldfd);return 0;
}
? ? ? ? 我的這個代碼主要以單次命令為主,即輸入wads回車完成對機械臂紅藍色機械臂的控制,在完善代碼的過程中,我發現紅藍色機械臂的角度受限紅色機械臂的可調角度為-90°-90°,而藍色機械臂可調角度為0-180°所以我加了個限制,使角度始終為這個區間內,來看一下效果。
? ? ? ? 先看一下自己電腦現在的IP地址
? ? ? ? 隨后我發現這樣一次次的操作再加上回車太過麻煩,效率非常低,我隨后通過init_curses()函數初始化終端為無緩沖、無回顯模式,支持實時按鍵響應(無需按回車確認)
#include <myhead.h>
#include <curses.h> // 包含curses庫
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#define PORT 8888
#define IP "192.168.0.74"// 初始化curses模式
void init_curses() {initscr(); // 初始化屏幕cbreak(); // 關閉行緩沖,按鍵直接生效noecho(); // 關閉輸入回顯keypad(stdscr, TRUE); // 啟用特殊按鍵支持refresh(); // 刷新屏幕
}// 顯示程序信息和控制說明
void show_info(int red_angle, int blue_angle) {clear(); // 清空屏幕// 顯示標題和連接信息mvprintw(1, 2, "===== 機械臂控制系統 =====");mvprintw(3, 2, "已連接服務器: %s:%d", IP, PORT);// 顯示當前角度信息mvprintw(5, 2, "當前角度:");mvprintw(6, 4, "紅色臂: %d° (范圍: -90° ~ 90°)", red_angle);mvprintw(7, 4, "藍色臂: %d° (范圍: 0° ~ 180°)", blue_angle);// 顯示控制說明mvprintw(9, 2, "控制命令:");mvprintw(10, 4, "w: 紅色臂順時針 (+1°)");mvprintw(11, 4, "s: 紅色臂逆時針 (-1°)");mvprintw(12, 4, "d: 藍色臂順時針 (+1°)");mvprintw(13, 4, "a: 藍色臂逆時針 (-1°)");mvprintw(14, 4, "q: 退出程序");// 顯示狀態提示mvprintw(16, 2, "狀態: 就緒 (按任意控制鍵操作)");mvprintw(17, 2, "----------------------------------------");refresh(); // 刷新屏幕顯示
}// 顯示操作結果提示
void show_status(const char *msg) {mvprintw(16, 2, "狀態: %s", msg); // 在固定位置顯示狀態mvprintw(18, 2, "按任意鍵繼續...");refresh();getch(); // 等待按鍵繼續
}int main(int argc, const char *argv[]) {char key;char buff[5]; int oldfd = socket(AF_INET, SOCK_STREAM, 0);int red_angle = 0; // 紅色臂初始角度int blue_angle = 90; // 藍色臂初始角度// 初始化cursesinit_curses();// 創建socketif (oldfd == -1) {endwin(); // 退出curses模式perror("socket創建失敗");return -1;}// 設置服務器地址struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP)};// 連接服務器if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {endwin(); // 退出curses模式perror("連接服務器失敗");close(oldfd);return -1;}// 顯示初始界面show_info(red_angle, blue_angle);// 主控制循環while (1) {key = getch(); // 無緩沖讀取按鍵,無需回車// 退出程序if (key == 'q') {break;}buff[0] = 0xff; buff[1] = 0x02; buff[4] = 0xff;char status_msg[100] = {0};int send_flag = 0;// 處理按鍵邏輯switch (key) {case 'w': // 紅色臂+1°if (red_angle < 90) {red_angle++;buff[2] = 0x00;buff[3] = red_angle;sprintf(status_msg, "紅色臂已更新至 %d° (發送成功)", red_angle);send_flag = 1;} else {sprintf(status_msg, "紅色臂已達最大角度 90° (無法繼續增加)");}break;case 's': // 紅色臂-1°if (red_angle > -90) {red_angle--;buff[2] = 0x00;buff[3] = red_angle;sprintf(status_msg, "紅色臂已更新至 %d° (發送成功)", red_angle);send_flag = 1;} else {sprintf(status_msg, "紅色臂已達最小角度 -90° (無法繼續減小)");}break;case 'd': // 藍色臂+1°if (blue_angle < 180) {blue_angle++;buff[2] = 0x01;buff[3] = blue_angle;sprintf(status_msg, "藍色臂已更新至 %d° (發送成功)", blue_angle);send_flag = 1;} else {sprintf(status_msg, "藍色臂已達最大角度 180° (無法繼續增加)");}break;case 'a': // 藍色臂-1°if (blue_angle > 0) {blue_angle--;buff[2] = 0x01;buff[3] = blue_angle;sprintf(status_msg, "藍色臂已更新至 %d° (發送成功)", blue_angle);send_flag = 1;} else {sprintf(status_msg, "藍色臂已達最小角度 0° (無法繼續減小)");}break;default:sprintf(status_msg, "無效按鍵! 請使用 w/s/d/a/q");break;}// 發送數據if (send_flag) {int res = send(oldfd, buff, 5, 0);if (res == -1) {sprintf(status_msg, "發送失敗: %s", strerror(errno));}}// 更新界面和顯示狀態show_info(red_angle, blue_angle);show_status(status_msg);}// 清理資源close(oldfd);endwin(); // 退出curses模式,恢復終端printf("程序已退出\n");return 0;
}
? ? ? ? 單擊wads鍵無需緩沖直接執行,長按wads多次執行,就實現了無極控制機械臂的擺動幅度,基本完成了項目需求。