需要的组件和用品
Arduino UNO和Genuino UNO 、WiFi Shield 、机械手臂
项目介绍示范:
界面功能介绍:
机器人手臂有6个电机。
A区:控制电机2,3,4(控制三个手关节)
B区:控制电机1(控制基座)
区域C:控制电机5(控制夹具的旋转)
D区:控制电机6(控制夹具)
系统架构:
工作流程:
客户端(在网页中 - 用JavaScript编写)
当用户触摸或扫过手指(或点击或移动鼠标)时,我们可以得到坐标(x,y)。工作流程如下:
在区域A的情况下,为了计算电机2,3,4的角度,我们需要进行一些几何计算。您可以在本页末尾参考。
服务器端(Arduino代码):
一旦从客户接收到一组角度,六个电机逐渐从当前角度移动到新角度。六个电机应同时移动并达到新的角度。在详细介绍如何控制所有电机之前,让我们来看看如何控制单个电机。假设我们想要将电机从当前角度(角度)移动到新角度(new_angle)。由于电机速度很高,我们应该放慢速度。为此,重复以下两个步骤,直到电机达到新的角度:
小步移动电机。
暂停一小段时间,然后再移动一步。
下图说明了新角度大于当前角度时的上述方案:
Wherestep_num是电机必须采取的步数。是预定义的值。两个后来决定速度和平滑度。以上仅适用于一个机器人。为了让机器人同时开始移动并到达目的地,我们可以这样做:六个电机采用相同的step_num ,但每个电机彼此不同。所以我们必须在这个项目中选择step_num 是最大的。step and timestep
一般来说,Arduino的工作流程如下:
5.几何计算
让我们将机器人手臂计算成以下几何问题:
已知
C是固定的
已知点 - D是来自用户的输入
已知点 - CB,BA,AD(分别用b,a,d表示)
每个臂段的长度查找:角度C,B,A 解决方案:
假设角度B和A是相同的
添加一些额外的点和细分
计算
我们知道点C和D =>我们可以计算出DC的长度(用c表示)
我们也可以计算δ
看三角形ABE,我们可以推断出AE = BE和∠E=π - 2α。
所以:
三角形CDE中的余弦定律:
将(1)和(2)改为(3),我们有:
简化
简化以上内容:
由于我们知道a,b,c和d,求解上述二次方程,我们可以计算出α的值。 - 并且β=π - α - 到现在为止我们找到β,让我们找到γ - 三角形BDC和BDA中的余弦定律:
求解这组方程,我们就可以计算出γ。
因此,它们所需的角度是:(δ+γ),β和β。这些是电动机2,3和4的角度。
6.源代码
源代码包括两个文件:
RobotArmWeb.ino:Arduino代码
Remote_arm.php:Web应用程序代码,上传到PHPoC WiFi Shield或PHPoC Shield。
您还需要将图像文件flywheel.png上传到PHPoC Shield。
原理图:
RobotArmWeb.ino #include "SPI.h" #include "Phpoc.h" #include <Servo.h> int angle_init[] = {90, 101, 165, 153, 90, 120}; // when motor stands straight. In web, the angle when motor stands straight is {0, 90, 130, 180, 0, 0}; int angle_offset[] = {0, 11, -15, -27, 0, 137}; // offset between real servo motor and angle on web int cur_angles[] = {90, 101, 165, 153, 90, 120}; // current angles of six motors (degree) int dest_angles[] = {0, 0, 0, 0, 0, 0}; // destined angles int angle_max[] = {180, 180, 160, 120, 180, 137}; int angle_min[] = { 0, 0, 0, 20, 0, 75}; int direction[] = {1, 1, 1, 1, 1 ,-1}; int angleSteps[] = {3, 2, 2, 2, 4 ,4}; // moving steps of each motor (degree) Servo servo1; Servo servo2; Servo servo3; Servo servo4; Servo servo5; Servo servo6; Servo servo[6] = {servo1, servo2, servo3, servo4, servo5, servo6}; PhpocServer server(80); PhpocClient client; int stepNum = 0; void setup(){ Serial.begin(9600); Phpoc.begin(PF_LOG_SPI | PF_LOG_NET); server.beginWebSocket("remote_arm"); servo1.attach(2); // attaches the servo on pin 2 to the servo object servo2.attach(3); // attaches the servo on pin 3 to the servo object servo3.attach(4); // attaches the servo on pin 4 to the servo object servo4.attach(5); // attaches the servo on pin 5 to the servo object servo5.attach(6); // attaches the servo on pin 6 to the servo object servo6.attach(7); // attaches the servo on pin 7 to the servo object for(int i = 0; i < 6; i++) servo[i].write(angle_init[i]); } void loop() { PhpocClient client = server.available(); if(client) { String angleStr = client.readLine(); if(angleStr) { Serial.println(angleStr); int commaPos1 = -1; int commaPos2; for(int i = 0; i < 5; i++) { commaPos2 = angleStr.indexOf(',', commaPos1 + 1); int angle = angleStr.substring(commaPos1 + 1, commaPos2).toInt(); dest_angles[i] = angle * direction[i] + angle_offset[i]; commaPos1 = commaPos2; } int angle5 = angleStr.substring(commaPos1 + 1).toInt(); dest_angles[5] = angle5 * direction[5] + angle_offset[5]; stepNum = 0; // move motors in many small steps to make motor move smooth, avoiding move motor suddently. The below is step calculation for(int i = 0; i < 6; i++) { int dif = abs(cur_angles[i] - dest_angles[i]); int step = dif / angleSteps[i]; if(stepNum < step) stepNum = step; } } } // move motors step by step if(stepNum > 0) { for(int i = 0; i < 6; i++) { int angleStepMove = (dest_angles[i] - cur_angles[i]) / stepNum; cur_angles[i] += angleStepMove; if(cur_angles[i] > angle_max[i]) cur_angles[i] = angle_max[i]; else if(cur_angles[i] < angle_min[i]) cur_angles[i] = angle_min[i]; servo[i].write(cur_angles[i]); } stepNum--; delay(20); } }
PHP代码:
Remote_arm.php <!DOCTYPE html> <html> <head> <title>Arduino - Arm Robot - Web</title> <meta name="viewport" content="width=device-width, initial-scale=0.9"> <style> body { text-align: center; } canvas { background-color: #FFFFFF; } </style> <script> var MOTOR_1 = 0; var MOTOR_2 = 1; var MOTOR_3 = 2; var MOTOR_4 = 3; var MOTOR_5 = 4; var MOTOR_6 = 5; var ARM_LENGTH_1 = 95; var ARM_LENGTH_2 = 88; var ARM_LENGTH_3 = 155; var SOLE_WIDTH = 160; var SOLE_HEIGHT = 73; var flywheel_img = new Image(); flywheel_img.src = "flywheel.png"; var zone_A_radius = ARM_LENGTH_1 + ARM_LENGTH_2 + ARM_LENGTH_3; var pivot_x = zone_A_radius + 5; var pivot_y = zone_A_radius + 5; var zone_B_radius = 100; var zone_B_center_x = pivot_x / 2; var zone_B_center_y = -(zone_B_radius + SOLE_HEIGHT + 20); var zone_B_last_angle = 0; var zone_C_radius = 70; var zone_C_center_x = -pivot_x / 2; var zone_C_center_y = -(zone_B_radius + SOLE_HEIGHT + 20); var zone_C_last_angle = 0; var zone_D_width = 30; var zone_D_height = 200.0; var zone_D_center_x = 0; var zone_D_center_y = -(zone_B_radius + SOLE_HEIGHT + 110); var grip_height = 70; var grip_width = 100; var canvas_width = zone_A_radius * 2 + 10; var canvas_height = zone_A_radius + SOLE_HEIGHT + zone_B_radius * 2 + 180; var grip_max_angle = 62.0; // 62 degree var click_state = 0; var mouse_xyra = {x:0, y:0, r:0.0, a:0.0}; var angles = [90, 90, 180, 180, 90, 17]; var ws = null; var servo = null, ctx = null; var last_time; var a = 0, b = 0, c = 0; function init() { servo = document.getElementById("servo"); servo.width = canvas_width; servo.height = canvas_height; servo.addEventListener("touchstart", mouse_down); servo.addEventListener("touchend", mouse_up); servo.addEventListener("touchmove", mouse_move); servo.addEventListener("mousedown", mouse_down); servo.addEventListener("mouseup", mouse_up); servo.addEventListener("mousemove", mouse_move); ctx = servo.getContext("2d"); ctx.translate(pivot_x, pivot_y); ctx.rotate(Math.PI); // quadratic equation parameters a = 4*ARM_LENGTH_1*ARM_LENGTH_3; b = 2*(ARM_LENGTH_2*ARM_LENGTH_1 + ARM_LENGTH_2*ARM_LENGTH_3); c = Math.pow(ARM_LENGTH_1, 2) + Math.pow(ARM_LENGTH_2, 2) + Math.pow(ARM_LENGTH_3, 2) - 2*ARM_LENGTH_1*ARM_LENGTH_3; } function update_view() { ctx.clearRect(-canvas_width/2, -SOLE_HEIGHT, canvas_width, canvas_height); ctx.save(); //draw zone A ctx.fillStyle = "#D9D9D9"; ctx.beginPath(); ctx.arc(0,0,zone_A_radius, 0, 2*Math.PI); ctx.fill(); ctx.fillStyle = "#FFFFFF"; ctx.fillRect(-pivot_x , -canvas_height + pivot_y, canvas_width, canvas_height - pivot_y - SOLE_HEIGHT); // draw arm segments ctx.strokeStyle="#00979D"; ctx.fillStyle = "#FF4500"; ctx.lineWidth = 20; ctx.rotate(angles[MOTOR_2] / 180 * Math.PI); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ARM_LENGTH_1,0); ctx.stroke(); draw_pivot(0, 0); ctx.translate(ARM_LENGTH_1,0); ctx.rotate(-Math.PI + angles[MOTOR_3] / 180 * Math.PI); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ARM_LENGTH_2, 0); ctx.stroke(); draw_pivot(0, 0); ctx.translate(ARM_LENGTH_2,0); ctx.rotate(-Math.PI + angles[MOTOR_4] / 180 * Math.PI); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ARM_LENGTH_3, 0); ctx.stroke(); draw_pivot(0, 0); ctx.restore(); ctx.strokeStyle = "#00979D"; ctx.lineWidth = 6; // draw zone B angle = (angles[MOTOR_1] + 45) * Math.PI / 180; ctx.save(); ctx.translate(zone_B_center_x, zone_B_center_y); ctx.rotate(angle); ctx.drawImage(flywheel_img, - zone_B_radius, - zone_B_radius, zone_B_radius * 2, zone_B_radius * 2); ctx.beginPath(); ctx.arc(0, 0, zone_B_radius, 0, 2 * Math.PI); ctx.stroke(); ctx.restore(); // draw zone C angle = (angles[MOTOR_5] + 45) * Math.PI / 180; ctx.save(); ctx.translate(zone_C_center_x, zone_C_center_y); ctx.rotate(angle); ctx.drawImage(flywheel_img, - zone_C_radius, - zone_C_radius, zone_C_radius * 2, zone_C_radius * 2); ctx.beginPath(); ctx.arc(0, 0, zone_C_radius, 0, 2 * Math.PI); ctx.stroke(); ctx.restore(); // draw zone D ctx.fillStyle = "#00DEE6"; ctx.lineWidth = 15; var grip_dist = Math.floor(angles[MOTOR_6] / grip_max_angle * zone_D_height); ctx.fillRect(zone_D_center_x - zone_D_width / 2, zone_D_center_y - zone_D_height / 2, zone_D_width, zone_D_height); ctx.lineWidth = 1; ctx.strokeStyle = "#FFFFFF"; var center_x = zone_D_center_x; var center_y = zone_D_center_y + grip_dist / 2; var grd = ctx.createLinearGradient(center_x, center_y, center_x, center_y + grip_height); grd.addColorStop(0,"#004A4D"); grd.addColorStop(1,"#b3fcff"); ctx.fillStyle = grd; ctx.beginPath(); ctx.moveTo(center_x + grip_width / 2, center_y); ctx.bezierCurveTo(center_x + grip_width / 4, center_y + grip_height, center_x - grip_width / 4, center_y + grip_height,center_x - grip_width / 2, center_y); ctx.closePath(); ctx.fill(); ctx.stroke(); center_x = zone_D_center_x; center_y = zone_D_center_y - grip_dist / 2; grd = ctx.createLinearGradient(center_x, center_y, center_x, center_y - grip_height); grd.addColorStop(0,"#004A4D"); grd.addColorStop(1,"#b3fcff"); ctx.fillStyle = grd; ctx.beginPath(); ctx.moveTo(center_x + grip_width / 2, center_y); ctx.bezierCurveTo(center_x + grip_width / 4, center_y - grip_height, center_x - grip_width / 4, center_y - grip_height,center_x - grip_width / 2, center_y); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore(); // draw sole ctx.fillStyle = "#006468"; ctx.fillRect(-SOLE_WIDTH/2, -SOLE_HEIGHT, SOLE_WIDTH, SOLE_HEIGHT); } function event_handler(event) { var x, y, r, alpha; // convert coordinate if(event.touches) { var touches = event.touches; x = (touches[0].pageX - touches[0].target.offsetLeft) - pivot_x; y = (touches[0].pageY - touches[0].target.offsetTop) - pivot_y; } else { x = event.offsetX - pivot_x; y = event.offsetY - pivot_y; } x = -x; y = -y; //check whether it's located in zone A or not r = Math.sqrt(x * x + y * y); if(r < zone_A_radius && y > -SOLE_HEIGHT) { if((x < SOLE_WIDTH/2) && (x > -SOLE_WIDTH/2) && (y < 0)) return false; alpha = Math.atan2(y, x); if(alpha < 0) alpha += 2*Math.PI; mouse_xyra.x = x; mouse_xyra.y = y; mouse_xyra.r = r; mouse_xyra.a = alpha; if(geometric_calculation()) return true; } //check whether it's located in zone B or not temp_x = x - zone_B_center_x; temp_y = y - zone_B_center_y; var distance = Math.sqrt(temp_x * temp_x + temp_y * temp_y); if(distance < zone_B_radius) { var angle = Math.atan2(temp_y, temp_x)* 180 / Math.PI; if(click_state == 0) zone_B_last_angle = angle; else { if((Math.abs(angle) > 90) && (angle * zone_B_last_angle < 0)) { if(zone_B_last_angle > 0) zone_B_last_angle = -180; else zone_B_last_angle = 180; } angles[MOTOR_1] += Math.floor(angle - zone_B_last_angle); angles[MOTOR_1] = Math.max(0, angles[MOTOR_1]); angles[MOTOR_1] = Math.min(180, angles[MOTOR_1]); zone_B_last_angle = angle; } return true; } //check whether it's located in zone C or not temp_x = x - zone_C_center_x; temp_y = y - zone_C_center_y; var distance = Math.sqrt(temp_x * temp_x + temp_y * temp_y); if(distance < zone_C_radius) { var angle = Math.atan2(temp_y, temp_x)* 180 / Math.PI; if(click_state == 0) zone_C_last_angle = angle; else { if((Math.abs(angle) > 90) && (angle * zone_C_last_angle < 0)) { if(zone_C_last_angle > 0) zone_C_last_angle = -180; else zone_C_last_angle = 180; } angles[MOTOR_5] += Math.floor(angle - zone_C_last_angle); angles[MOTOR_5] = Math.max(0, angles[MOTOR_5]); angles[MOTOR_5] = Math.min(180, angles[MOTOR_5]); zone_C_last_angle = angle; } return true; } //check whether it's located in zone D or not var temp_x = Math.abs(x - zone_D_center_x); var temp_y = Math.abs(y - zone_D_center_y); if(temp_x < (zone_D_width / 2) && temp_y < (zone_D_height / 2)) { var angle = temp_y / (zone_D_height / 2) * grip_max_angle; angles[MOTOR_6] = Math.floor(angle); console.log(angles[MOTOR_6]); return true; } return false; } function geometric_calculation() { var c_ = c - Math.pow(mouse_xyra.r, 2); var delta = b*b - 4*a*c_; if(delta < 0) return false; // no root var x1 = 0, x2 = 0; var x = 0; var cos_C = 0; var alpha = 0, beta = 0, gamma = 0; x1 = (-b + Math.sqrt(delta)) / (2*a); x2 = (-b - Math.sqrt(delta)) / (2*a); x = x1; if(x > 1) return false; alpha = Math.acos(x); alpha = alpha * 180 / Math.PI; beta = 180 - alpha; cos_C = Math.pow(mouse_xyra.r, 2) + Math.pow(ARM_LENGTH_1, 2) - ( Math.pow(ARM_LENGTH_2, 2) + Math.pow(ARM_LENGTH_3, 2) + 2*ARM_LENGTH_2*ARM_LENGTH_3*x ); cos_C = cos_C /(2*mouse_xyra.r*ARM_LENGTH_1); angle_C = Math.acos(cos_C); gamma = (angle_C + mouse_xyra.a) % (2*Math.PI); gamma = gamma * 180 / Math.PI; if(gamma < 0) gamma += 360; if(gamma > 180) { var temp = gamma - mouse_xyra.a * 180 / Math.PI; gamma = gamma - 2* temp; beta = 360 - beta; } if(gamma < 0 || gamma > 180) return false; angles[MOTOR_3] = Math.floor(beta); angles[MOTOR_4] = Math.floor(beta); angles[MOTOR_2] = Math.floor(gamma); return true; } function draw_pivot(x, y) { ctx.beginPath(); ctx.arc(x,y, 10, 0, 2*Math.PI); ctx.stroke(); ctx.fill(); } function ws_onmessage(e_msg) { e_msg = e_msg || window.event; // MessageEvent } function ws_onopen() { document.getElementById("ws_state").innerHTML = "OPEN"; document.getElementById("wc_conn").innerHTML = "Disconnect"; angles_change_notice(); update_view(); } function ws_onclose() { document.getElementById("ws_state").innerHTML = "CLOSED"; document.getElementById("wc_conn").innerHTML = "Connect"; console.log("socket was closed"); ws.onopen = null; ws.onclose = null; ws.onmessage = null; ws = null; } function wc_onclick() { if(ws == null) { ws = new WebSocket("ws://<?echo _SERVER("HTTP_HOST")?>/remote_arm", "text.phpoc"); document.getElementById("ws_state").innerHTML = "CONNECTING"; ws.onopen = ws_onopen; ws.onclose = ws_onclose; ws.onmessage = ws_onmessage; } else ws.close(); } function mouse_down() { if(ws == null) return; if(event.touches && (event.touches.length > 1)) click_state = event.touches.length; if(click_state > 1) return; var state = event_handler(event); if(state) { click_state = 1; angles_change_notice(); } event.preventDefault(); } function mouse_up() { click_state = 0; } function mouse_move() { if(ws == null) return; var d = new Date(); var time = d.getTime(); if((time - last_time) < 50) return; last_time = time; if(event.touches && (event.touches.length > 1)) click_state = event.touches.length; if(click_state > 1) return; if(!click_state) return; var state = event_handler(event); if(state) { click_state = 1; angles_change_notice(); } event.preventDefault(); } function angles_change_notice() { if(ws != null && ws.readyState == 1) { ws.send(angles.join(",") + "\r\n"); update_view(); } } window.onload = init; setTimeout(function(){ update_view();}, 500); </script> </head> <body> <h2>Arduino - Control Arm Robot via Web</h2> <canvas id="servo"></canvas> <p>WebSocket : <span id="ws_state">null</span><br></p> <button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button> </body> </html>
原文地址:https://create.arduino.cc/projecthub/phpoc_man/arduino-control-arm-robot-via-web-379ef3?ref=platform&ref_id=424_trending___&offset=3