好久没给大家更新了,上周末大学大四开学,所以停更了几天,回来后在做项目,接下来的几篇文章,给大家带来几个项目,第一个介绍的是机械臂操作,说是机械臂操作,简单来说,就是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多次执行,就实现了无极控制机械臂的摆动幅度,基本完成了项目需求。