切换到宽版
  • 53325阅读
  • 155回复

从零开始做光驱激光雕刻机 [复制链接]

上一主题 下一主题
离线ly7317090803
 

发帖
51
M币
2068
专家
44
粉丝
58
只看楼主 倒序阅读 我要置顶 楼主  发表于: 2015-12-27
— 本帖被 發騷友 设置为精华,作者+3000M币+5专家(2015-12-27) —
目前很多便宜的激光雕刻机都是直接使用Arduino板子为主控直接组装的,其实只要往ATmega328芯片烧录网上GRBL的固件即可(同时做好外围供电电路),但本文则是从零开始讲述各个硬件及软件的组建,废话不多说,以下主要讲述4个方面:X、Y轴的硬件平台组成;驱动电路及行程限位;激光模组;主控及软件分析;

1. XY轴硬件组成。
其实很简单,为了尽量节约成本,除激光模块及主控芯片外成本也就4块钱(光驱是从古董电脑里免费拆的)。

两个光驱,把激光部分及其附属电路拆下,就留一个能固定东西的导轨,一个平放做Y轴,一个横着固定在架子上做X轴,固定的架子也很简单,直接利用下面光驱上另一块铁片折弯90°做的,再加上几根扎带固定,图中的中性笔笔芯用来固定X轴的水平度。

弄个坏的手机背盖做平台(Sony Z1后盖),用双面胶粘在Y轴的滑块上,不过注意要保持水平度。


2. 驱动电路及行程开关
驱动芯片采用国产便宜的芯片MD127,低电压驱动芯片(2.7-5.5V均可)

当然淘宝上也有很多类似的芯片价格也就1元左右。电源处必须加10uF电容,少了它将无法工作。但需注意一点:此芯片不支持细分,也就是无法将每步分得更细,只能8步为一周期(相当于1/2细分),因此精度也就每步0.07mm,随之而来的就是相当大的误差,这个必须由程序去弥补,下面会说到。

限位开关采用光电对管,一个红外发射一个红外接收,当其中有东西挡在它们之间时接收管则会因无光而断开回路。

注意挡片要不透光的

原理如上,挡住时(黄色线)输出高(10K上拉电阻),没挡住时输出低。

3. 激光模组
说实话这激光便宜的还真买不得,我这个50多块,100mW 蓝紫激光,不到一个月因激光衰减换过一次激光头,老板说工作温度不得超过50度,于是我加了个大风扇专门吹它,大冬天的人都快冷怕了。可是不到20天后激光模组又无法雕刻了,因为平时调试时都是把激光关掉或用PWM把光调暗,目前只能再去买个了,说实话这个衰减还真跟温度无直接关系,自从上次换过后平时最高温度不超过30度(摸上去冰冷的)。

由于激光模组只有2根线(电源+、电源-),因此在电源-极串加N沟道场效应管SI2306,因为其内部有恒流电路,因此占空比<2%时输出弱光(相当于提供一个达不到恒流芯片工作的电压),频率要求不高,约1kHz左右,电源5V。

固定外面的散热铝块到X轴滑块上,背面贴了双面胶,再用扎带捆住。前面2颗螺丝用来固定激光模组。

4. 主控、主体软件
其实上面3点均不难,怎么做基本都能实现,仅是精度上的问题而已。但软件则不同,错一点可能整幅作品就全废了。

首先是硬件电路图,为了简单明了就没用电路绘图软件了:

电路很简单,整个系统可以在3V电源下正常工作(注意给STM32F103主控芯片的电源搞好点,免得串口读到的数据有误),也就是电池都能轻松带动。整个实物如下,由于未全部整合,主控等目前用杜邦线相连:





接着是软件部分,也是最关键的,首先肯定是如何驱动光驱的步进电机,一般光驱传动轴转一圈前进(后退)3mm,由于是1/2细分,故需40步才能转1圈(不细分时20步走一圈),也就是每步进1mm需要13.33步(脉冲)--13.33 step/mm,每个步进电机需4个IO控制(以下用M1_O1~M1_O4表示,即电机M1的OUT1~4,_H表示输出高,_L表示输出低)
  1. void Step_out(u8 Motor1,u8 Motor2)//电机M1 M2输出控制函数,控制2相线圈轮流导通,详细参看网上步进电机驱动资料
  2. {  
  3.          if(Motor1&0x80)// Motor1 bit7表示是否使能输出
  4.                 {        
  5.                         switch(Motor1&0x7F)//Motor1 bit7表示是否使能输出,其余位表示输出第几步
  6.                         {
  7.                 case 0:                M1_O1_H;M1_O2_L;M1_O3_L;M1_O4_L; break;
  8.                 case 1:                M1_O1_H;M1_O2_L;M1_O3_H;M1_O4_L;break;
  9.                 case 2:                M1_O1_L;M1_O2_L;M1_O3_H;M1_O4_L;break;
  10.                 case 3:                M1_O1_L;M1_O2_H;M1_O3_H;M1_O4_L;break;
  11.                 case 4:                M1_O1_L;M1_O2_H;M1_O3_L;M1_O4_L;break;
  12.                 case 5:                M1_O1_L;M1_O2_H;M1_O3_L;M1_O4_H;break;
  13.                 case 6:                M1_O1_L;M1_O2_L;M1_O3_L;M1_O4_H;break;
  14.                 case 7:                M1_O1_H;M1_O2_L;M1_O3_L;M1_O4_H;break;
  15.                         }
  16.                 }
  17.                 if(Motor2&0x80)
  18.                 {        
  19.                         switch(Motor2&0x7F)//与Motor1同理
  20.                         {
  21.                 case 0:                M2_O1_H;M2_O2_L;M2_O3_L;M2_O4_L;break;
  22.                 case 1:                M2_O1_H;M2_O2_L;M2_O3_H;M2_O4_L;break;
  23.                 case 2:                M2_O1_L;M2_O2_L;M2_O3_H;M2_O4_L;break;
  24.                 case 3:                M2_O1_L;M2_O2_H;M2_O3_H;M2_O4_L;break;
  25.                 case 4:                M2_O1_L;M2_O2_H;M2_O3_L;M2_O4_L;break;
  26.                 case 5:                M2_O1_L;M2_O2_H;M2_O3_L;M2_O4_H;break;
  27.                 case 6:                M2_O1_L;M2_O2_L;M2_O3_L;M2_O4_H;break;
  28.                 case 7:                M2_O1_H;M2_O2_L;M2_O3_L;M2_O4_H;break;
  29.                         }
  30.                 }
  31.         if((Motor1&0x80)||(Motor2&0x80))delay_n100us_loop(9);//若有输出则延时1ms左右
  32. }
  33. //电机输出控制函数,输入参数:步数+方向  返回错误标记或0
  34. // M1_step、M2_step最高位bit15表示正反转,0-顺时针  1-逆时针,其余位表示输出步数
  35. u8 Motor_Move(u16 M1_step,u16 M2_step)
  36. {
  37. unsigned char stepinfo_M1,stepinfo_M2;
  38. u8 Error_mark=0;//错误标示,初始值为0
  39.         while((M1_step&0x7FFF)!=0 || (M2_step&0x7FFF)!=0)//循环,一直到步数全部减到0
  40.         {
  41.         Error_mark=0;//清零错误标记
  42.         stepinfo_M1=stepinfo_M2=0; //清零输出,stepinfo_M1(M2)输出到上面的Step_out函数
  43.         if(M1_step&0x7FFF)//计算motor1的输出
  44. {
  45.         M1_step--;
  46.         if((M1_step&0x8000)==0) //表示正转
  47.         {
  48.                 M1_step_calc++;//M1计算时用的坐标位置记录寄存器,因为可能强制设置原点,故会导致当前设置的原点不是机器实际的原点,但为防止机器超出范围工作,因此设置Distance_M1寄存器来记录机器实际位置。Virtual_offset_M1表示在强制设置原点后Distance_M1与M1_step_calc的偏差值,假如机器需工作的范围超出实际范围,Distance_M1的值不会变,且实际的轴也不会动,但M1_step_calc会虚拟地变化,当M1_step_calc与Distance_M1的差值恢复到Virtual_offset_M1后则表示计算的工作坐标已恢复到正常范围内,即可以执行电机的动作。下同
  49.                 if((Distance_M1==X_MAX || Virtual_offset_M1+M1_step_calc-1 != Distance_M1)&& (CNC_State&0x40) ){Error_mark=2;}
  50.                 else
  51.                 {Distance_M1++; //记录机器实际位置,以限位开关为原点的位置
  52.                 if( (Step_index_last & 0x007F) ==0) //到达步数最小值则下一步为第7步,下同
  53. Step_index_last |= 0x07;
  54.                 else Step_index_last--;}
  55.         }
  56.         else  //反转
  57.         {
  58.                 M1_step_calc--;///
  59.                 if((Distance_M1==0  || Virtual_offset_M1+M1_step_calc+1 != Distance_M1)&& (CNC_State&0x40)){Error_mark=1;}
  60.                 else {Distance_M1--; //记录机器实际位置,以限位开关为原点的位置,下同
  61.                 if( (Step_index_last & 0x007F) ==0x07)//到达步数最大值则下一步为第0步,下同
  62. Step_index_last &= 0xFFF8;
  63.                 else Step_index_last++;}
  64.         }
  65.         if(!Error_mark)stepinfo_M1 |= 0x80;//使能输出
  66. }
  67.          if(M2_step&0x7FFF)//计算motor2的输出
  68.          {
  69.                  M2_step--;
  70.                  if((M2_step&0x8000)==0) //表示正转
  71.                  {
  72.                          M2_step_calc++;////
  73.                          if((Distance_M2==Y_MAX  || Virtual_offset_M2+M2_step_calc-1 != Distance_M2)&& (CNC_State&0x40))//Virtual_offset_M2+M2_step_calc-1 != Distance_M2判断是否回归到正常工作坐标
  74.                                 {Error_mark|=8;}
  75.                          else {Distance_M2++;
  76.                          if( (Step_index_last & 0x7F00) ==0) Step_index_last |= 0x700;
  77.                          else Step_index_last -= 0x100;}
  78.                  }
  79.                  else  //反转
  80.                  {
  81.                          M2_step_calc--;////
  82.                           if((Distance_M2==0  || Virtual_offset_M2+M2_step_calc+1 != Distance_M2)&& (CNC_State&0x40)){Error_mark|=4;}
  83.                         else
  84.                         { Distance_M2--;
  85.                          if( (Step_index_last & 0x7F00) ==0x0700) Step_index_last &= 0x80FF;
  86.                                 else Step_index_last += 0x100;}
  87.                  }
  88.                 if(!(Error_mark&0x0C)) stepinfo_M2 |= 0x80;//使能输出
  89.          }
  90. // Step_index_last记录上一次电机运行到第几步,高8位(bit15-8)为电机M2,bit7-0记录M1,本次运行的步数取决于上一步
  91. stepinfo_M1 |= (Step_index_last&0xFF);
  92. stepinfo_M2 |= ((Step_index_last&0xFF00)>>8);
  93. Step_out(stepinfo_M1,stepinfo_M2);//输出
  94.         }
  95.         return Error_mark;
  96. }



以上两个函数为电机驱动的底层函数,实现的主要目的是输入步数和方向来控制电机的输出。
底层步进电机驱动函数外,软件上主要需解决3个方面问题:1、串口通信部分;2、常用的指令逐个解析及回复;3、CNC插补算法。

● 串口通信,串口一般9600的波特率,虽然速度不快但需对串口数据接收及主程序的执行控制好,一边对串口收到的数据不可漏掉或未读完就提取指令;一边要对输出进行计算同时电机输出也得控制好。本程序里是先给串口接收区划分一个1000字节的缓冲区,同时来再多数据也能不拉下(GRBL有时一次最大发送700字节的指令,故在此不得不设置一个大点的区域),若单片机的寄存器不能设置那么大,其实也可以设置一个环形的缓冲区域:

主程序每200ms判断一次串口是否收到数据,若有数据则收到数据标记位置位(USART_RX_STA的bit7置为1)。
另外在主程序解析串口数据时也要监测串口中是否又来了新的数据,
  1. H1:
  2. if(USART_RX_STA&0x01)Wait_Nodata();//若有数据来了则等待接收完
  3. /………解析指令部分………/
  4. if(uart_buf_index<USART_RX_NUM && uart_buf_index)goto H1;//本条指令读取结束,看是否读取数据完毕,若还有未读完的则再循环读取. USART_RX_NUM串口总数据长度,uart_buf_index当前解析指令的位置。
  5. //判断是否还有数据的函数,等待接收完
  6. u8 Wait_Nodata()//等待串口无数据,有数据时USART_RX_STA的bit0会置1
  7. {
  8.         do{
  9.                 USART_RX_STA &= 0xFE;
  10.                 delay_ms_loop(70);//延时70ms
  11.         }while( USART_RX_STA&0x01);
  12.         return 0;
  13. }

● 指令解析及回复
指令主要有G0、G01、G02、G03、G90、G92、M2、M3、M4、M5,用法及意义如下:
G0  快速定位,如:G00 X10.111 Y3.254 即移动到10.111,3.254的位置
G01 直线插补,不是G0那样最快移动到目的地,如:G01 X10.111 Y3.254  表示从当前坐标位置直线移动到10.111,3.254,经过的路径是一条直线
G02 顺圆插补,顺时针的圆弧插补,如 G02 X1.211 Y3.222 I-1.0 J2.111  ‘X1.211 Y3.222’表示圆弧终点是1.211,3.222,圆弧的圆心是距离当前机器坐标(-1.0,2.111)的位置
G03 与G02类似,只是变为逆时针的圆弧
G90 表示当前的执行参数为绝对尺寸,G91为相对尺寸,例如:G91  G00 X10 Y10且当前位置是(1,1),则最后执行后将停在(11,11),而G90 则是停在(10,10)的位置
G92 强制设定当前的坐标值
M2 表示程序结束。注意在收到此条指令后需发送“Grbl 0.8c ['$' for help]”,即GRBL版本号
M3、M4对于激光雕刻来说意义不大(都是表示开启激光),切割时表示电机的正反转
M5 关闭激光或电机
此外在收到0x18时需返回“Grbl 0.8c ['$' for help]”,收到’$G’时需回复“[G0 G54 G17 G21 G90 G94 M0 M5 M9 T0 F250.000]”,只是从原固件程序模仿的,实际参数意义不大。收到“$$”时需回复$0-$22各参数的配置,目前主要用到的是$0、$1,$0表示x轴的精度step/mm,即1毫米需要多少步才能到,$1表示y轴的 (step/mm)。设置$0则发送$0=13.333即可。
当电脑端发送’?’时,表示请求获取当前机器的状态及工作坐标,此时回复“<Run,MPos:1.234,1.444,0.000,WPos:1.231,1.451,0.000>”,Run表示机器有轴正在运行,Idle表示处于空闲,后面分别则是机器坐标、工作坐标(X Y Z三个轴的坐标)

注意收到的每条指令均是以16进制0x0D为结尾,回复指令时则回复 0x0D 0x0A两字节为结尾。
Arduino有个IO单独监控串口的DTR,当串口被打开时,DTR会产生一个脉冲,此时Arduino的主控会发送“Grbl 0.8c ['$' for help]”来请求连接主机。接着电脑软件会依次发送?、$G、$$三条指令来获取基本信息。

解析这些指令也很简单,首先读取第一字符,判断是属于哪种指令,再解析下一字节的数字代表什么功能,接着提取坐标等数值,最后执行即可。

另外每读取到一条指令后均需要返回’ok’,电脑端软件以此判断队列中还有几条指令未执行,若未执行的指令过多软件就会等待(默认是等待50S),遇到无法解析的指令时则发送error: Expected command 来反馈无法解析此指令。



[ 此帖被ly7317090803在2015-12-31 17:03重新编辑 ]
本文内容包含图片或附件,获取更多资讯,请 登录 后查看;或者 注册 成为会员获得更多权限
本帖最近打赏记录:共51条打赏M币+172
wjhwpp M币 +5 認真發帖 2016-09-18
xihuankanni M币 +1 真是大神级别,软件都会编辑 2016-06-10
benic M币 +3 優秀文章 2016-04-29
huakun888 M币 +10 謝謝分享 2016-03-01
落花萧然 M币 +3 - 2016-02-14
wooy M币 +3 - 2016-01-09
春日阳光 M币 +3 優秀文章,论真是高手好云呢! 2016-01-07
lyk52125 M币 +3 原創內容 2016-01-04
jun4664 M币 +3 謝謝分享,楼主写的很用心,已收藏谢谢分享。 2016-01-04
海之韵 M币 +3 - 2016-01-03
离线ly7317090803

发帖
51
M币
2068
专家
44
粉丝
58
只看该作者 1楼 发表于: 2015-12-27
从零开始做小激光雕刻机
● 插补算法
插补算法基本就2种:一种直线插补,一种圆弧插补。其次需重点考虑的则是计算误差方面的问题,例如本次的精度最大也就0.07mm,计算中出现的累计误差会对整个运算的结果造成非常大的影响。

1)  直线插补,因为雕刻机每步只能往三个方向走(x轴、y轴方向,与轴夹角45°的方向),因此对于直线的路径需采用插补的方法来实现。

起点是O,N(Xi,Yi)为机器坐标,E为结束点,则N点相对于轮廓OE的位置偏差,可以用轮廓终点E的位矢和动点N的位矢与X轴的夹角正切差来表示。即
Yi/Xi-Ye/Xe   正数XeXi乘以该式,最后得:Fi=XeYi-XiYe
故偏差值Fi的符号反映了动点N相对于直线OE的位置偏离情况:
    ① Fi = 0 时,动点N在直线上;
    ② Fi ≻ 0 时,动点N在直线的上方区域;
        ③ Fi ≺ 0 时,动点N在直线的下方区域。

据此可以判断出直线插补的刀具进给方向为:  
① 当动点在直线上方区域时, 应 +X 方向进给一步;
② 当动点在直线下方区域时,应 +Y 方向进给一步;
③ 动点在直线上时, 既可以+X方向也可以+Y方向进给一步,在此假定取+X方向。
蓝色线表示运动的实际路径,实际的运行如下图:


由于公式中存在乘法,故考虑采用迭代的算法:
① 当 Fi ≥ 0 时,动点在直线上 或 在直线上方区域
       向 +X 方向进给一步
       代入原式,新位置的偏差计算公式为: Fi+1 = Fi – Ye

② 当 Fi < 0时,动点在直线下方区域
       向 +Y 方向进给一步
       新位置的偏差计算公式为: Fi+1 = Fi + Xe

③ 开始加工直线轮廓时,刀具总是处在直线轮廓的起点位置。因此偏差值的初始值                      F0 = 0    
以上分析了直线在第一象限的处理办法。详细原理可以网上搜索直线插补的更多资料,下面为4个象限的各种插补策略:


何时到终点不能用Fi=0来判断,但到终点时走的步数是不变的,且等于在X轴方向上刀具应该走的总步数+在Y轴方向上刀具应该走的总步数。因此预先计算好总步数即可。

下面看2个计算步数的函数:
函数1
  1. static void Linear_StepCalculate(void)//以实际mm为单位的坐标进行计算,基本单位为mm
  2. // Position.Current.x当前的x轴坐标,Position.Previous.x上一次x轴的坐标,单位为mm
  3. // Position.Target.x目标x的坐标,单位mm
  4. {
  5.     Position.Previous.x=Position.Current.x;
  6.     Position.Previous.y=Position.Current.y;
  7.     Linear.Xe=(u16)(fabs(Position.Target.x-Position.Previous.x)*S1);
  8.     Linear.Ye=(u16)(fabs(Position.Target.y-Position.Previous.y)*S2);
  9. }

函数2:M1(M2)_step_calc为当前的步数为单位的坐标
  1. static void Linear_StepCalculate(void)//以机器实际的步数坐标为计算,基本单位为步数
  2. {
  3. //以实际位置,减少误差
  4.     Position.Previous.x=M1_step_calc*1.0/S1;
  5.     Position.Previous.y=M2_step_calc*1.0/S2;
  6.     Linear.Xe=(u16)(Position.Target.x*S1);
  7.     Linear.Ye=(u16)(Position.Target.y*S2);
  8.     
  9.     Linear.Xe=Linear.Xe>M1_step_calc?Linear.Xe-M1_step_calc:M1_step_calc-Linear.Xe;
  10.     Linear.Ye=Linear.Ye>M2_step_calc?Linear.Ye-M2_step_calc:M2_step_calc-Linear.Ye;
  11. }
因为本设备最大精度为0.07mm,所以每次移动产生的最大误差小于0.07,按函数1移动10次后累计的误差就有可能到0.6mm左右,移动100次则可能误差6mm;但函数2是用步数来作为基本单位,每次把目标坐标换算为步数,而当前坐标的步数本来就不存在误差值,因此就消除了累计误差,就算系统的精度再差都不会出现累计误差(圆弧插补也用了此原理)。


2) 圆弧插补,基本的思想与直线插补一致,但有复杂了很多,因为圆弧跨过轴之后,插补算法就变了,也就是一段圆弧加入不在同一象限则需进行分步处理。

动点至圆心的距离与圆弧半径的差值作为动点的偏差值,即 ,动点N相对于逆圆弧SE的位置偏离情况:
    ① F = 0 时,动点在逆圆弧上;
    ② F > 0 时,动点在逆圆弧外侧区域;
    ③ F < 0 时,动点在圆弧内侧区域。

因此机器进给方向为:
①     当动点在圆弧外侧区域(Fi >0)时, 应–X 方向进给一步;
②     当动点在圆弧内侧区域(Fi <0)时,应 +Y 方向进给一步;
③     当动点在圆弧上(Fi=0)时,既可以-X方向也可以+Y方向进给一步,在此假定取-X方向。
同样采用迭代公式,因此第一象限逆圆弧插补的偏差值迭代计算公式如下:

新位置的偏差值与当前点的偏差值和当前点的坐标都有关系。因此在插补过程中,必须不断地修正动点的当前坐标,为下一步的偏差计算做好准备。
根据圆弧顺或逆及四个象限,一起共有8种情况,运算公式等如下图:

过轴的圆弧判断:首先判断起点 和 终点是否在同一象限,再分别判断起点走到终点所需要经过的路径(若跨象限则分步处理)。
A.    判断两坐标是否同一象限
//返回值说明:坐标1(a1 b1):bit2-0 象限数 坐标2(a2 b2):bit6-4 象限数 若同一象限则仅有bit2-0有值
  1. u8 Judge_quadrant(float a1,float b1,float a2,float b2)
  2. {
  3.     u8 temp=0,temp1=0;
  4.     //temp bit3-0分别表示b2.a2、b1.a1的符号(负数则置1)
  5.     if(a1<0)temp|=0x01;if(b1<0)temp|=0x02;
  6.     if(a2<0)temp|=0x04;if(b2<0)temp|=0x08;
  7.     
  8.     
  9.     //计算a1 b1所在象限    
  10.     if( (temp&0x03)<2 )temp1|=(temp&0x03)+1;
  11.             else temp1|=6-(temp&0x03);
  12.     if(temp1==1 && a1==0)temp1=2;//x轴为0,y为正时是第二象限
  13.         else if(temp1==2 && b1==0)temp1=3;//y=0,x为负数则是第3象限
  14.             
  15.     if( (temp&0x0C)<8 )temp1|=((temp&0x0C)<<2)+0x10;
  16.             else temp1|=0x60-((temp&0x0C)<<2);
  17.         if((temp1&0xF0)==0x10 && a2==0)temp1+=0x10;
  18.             else if((temp1&0xF0)==0x20 && b2==0)temp1+=0x10;    
  19.     temp=temp1;        
  20.     temp=(((temp>>4)^temp)&0x07);    
  21.     
  22.     if( temp==0 )
  23.         return (temp1&0x0F);
  24.     else
  25.     {
  26.         return temp1;
  27.     }    
  28. }
B.    圆弧路径计算及分段
先将起点、终点换算为以圆心为原点的坐标系(则起点坐标用:Circular.I,Circular.J来表示,终点:Relative_target_x,Relative_target_y),先计算可能需分几段圆弧:
  1. quadrant_temp=Judge_quadrant(Circular.I,Circular.J,Relative_target_x,Relative_target_y);
  2. // quadrant_temp判断起点 终点各在第几象限的结果
  3. //先给Loop_quadrant计算循环次数
  4. if(!(Loop_quadrant&0x80))// Loop_quadrant bit7为0表示Loop_quadrant未计算过
  5.     {
  6.         //分顺圆 逆圆
  7.         if(Circular.type==SR)
  8.         {
  9.             if( (quadrant_temp&0xF0) ==0)//判断是否同一象限
  10.             {

  1. if( (Circular.J>=0&& Circular.I<Relative_target_x) || (Circular.J<0&& Circular.I>Relative_target_x) )//路径只需在同一象限的一段圆弧
  2.                     Loop_quadrant=1;
  3.                 Else//需要绕3个象限再回当前象限,即可能需分5段
  4.                 {
  5.                     Loop_quadrant=5;
  6.                     if(Circular.J==0 || Circular.I==0)//起点有在轴上的
  7.                     Loop_quadrant--;    
  8.                 }
  9.             }
  10.             Else//不在同一象限,则利用象限数相加减
  11.             {
  12.                 Loop_quadrant=(quadrant_temp>>4)>(quadrant_temp&0x07)?4+(quadrant_temp&0x07)-(quadrant_temp>>4):(quadrant_temp&0x07)-(quadrant_temp>>4);
  13.                 if(Circular.J!=0 && Circular.I!=0)//起点根均不在轴上
  14.                     Loop_quadrant++;
  15.             }
  16.         }
  17.         Else//逆圆与顺圆同理
  18.         {
  19.             if( (quadrant_temp&0xF0) ==0)//判断是否同一象限
  20.             {
  21.                 if( (Circular.J>=0&& Circular.I>Relative_target_x) || (Circular.J<0&& Circular.I<Relative_target_x) )
  22.                     Loop_quadrant=1;
  23.                 else
  24.                 {
  25.                     Loop_quadrant=5;
  26.                     if(Relative_target_x==0 || Relative_target_y==0)//有一根在轴上
  27.                     Loop_quadrant--;    
  28.                 }
  29.             }
  30.             else
  31.             {
  32.                 Loop_quadrant=(quadrant_temp>>4)>(quadrant_temp&0x07)?(quadrant_temp>>4)-(quadrant_temp&0x07):4+(quadrant_temp>>4)-(quadrant_temp&0x07);
  33.         if(Relative_target_x!=0 && Relative_target_y!=0)//终点均不在轴上
  34.                     Loop_quadrant++;
  35.             }
  36.         }
  37.         Loop_quadrant|=0x80;//标记为已计算
  38.     }
  39. if(fabs(Relative_target_x)> Radius+0.07|| fabs(Relative_target_y)>Radius+0.07) //目标点距圆心距离大于圆弧的半径
  40. {printf("The e:%f>%f %f>%f",fabs(Relative_target_x),Radius,fabs(Relative_target_y),Radius);break;}  //无法到达的点
  41. //判断完循环次数主要为防止死循环,因为某些小半径的圆弧可能会因误差导致始终无法到达目标点
下面是针对顺圆弧如何分段的判断及处理:
  1. if( (quadrant_temp&0xF0) ==0)//判断起点 终点是否同一象限
  2.     {
  3.         switch(quadrant_temp)//判断大于270°的圆弧

  1. // Quadrant_changed不为0表示需要换象限,其值为下一象限值
  2.         {
  3. case 1:if(Relative_target_x<Circular.I && Relative_target_y>Circular.J)//End需在S左上
  4.     {if(Circular.J==0)Quadrant_changed |= 0x03;else Quadrant_changed |= 0x04;}
  5.                 break;
  6. case 2:if(Relative_target_x<Circular.I && Relative_target_y<Circular.J)//END需在Start的左下
  7.         {if(Circular.I==0)Quadrant_changed |= 0x04;else Quadrant_changed |= 0x01;}
  8.                 break;
  9. case 3:if(Relative_target_x>Circular.I && Relative_target_y<Circular.J)//END需在Start的右下
  10.     {if(Circular.J==0)Quadrant_changed |= 0x01;else Quadrant_changed |= 0x02;}
  11.                 break;
  12. case 4:if(Relative_target_x>Circular.I && Relative_target_y>Circular.J)//END需在Start的右上
  13.         {if(Circular.I==0)Quadrant_changed |= 0x02;else Quadrant_changed |= 0x03;}
  14.                 break;
  15.         }
  16.             
  17.     }
  18.     else//不在同一象限
  19.   {
  20. //计算起点所在象限的下一象限值
  21. temp_1=(quadrant_temp&0x0F)==1?4:(quadrant_temp&0x0F)-1;//顺转则减
  22.         if( temp_1!=(quadrant_temp>>4)) //下一象限是否到目标区域
  23.         {
  24.             //下一象限不是终点的象限,则需设置换象限值
  25.             switch(quadrant_temp&0x0F)
  26.         {
  27.             case 1://将下一象限的值赋值给Quadrant_changed,若起点在y轴上则下一象限为第3象限,详细图解参看Quadrant_changed的switch语句(下面)。其余象限类似
  28.   {if(Circular.J==0)Quadrant_changed |= 0x03;
  29.     else Quadrant_changed |= temp_1;}
  30.                 break;
  31.             case 2:
  32.   {if(Circular.I==0)Quadrant_changed |= 0x04;
  33.     else Quadrant_changed |= temp_1;}
  34.                 break;
  35.             case 3:
  36.   {if(Circular.J==0)Quadrant_changed |= 0x01;
  37.     else Quadrant_changed |= temp_1;}
  38.                 break;
  39.             case 4:
  40.   {if(Circular.I==0)Quadrant_changed |= 0x02;
  41.     else Quadrant_changed |= temp_1;}
  42.                 break;
  43.         }
  44.         }
  45.         else //下一象限就是目标点的象限
  46.         {
  47.             switch(temp_1)//需先判断需要换象限,比较靠近轴的则无需换

//上图为 case 1的情况图解,其余类似        
  1. {
  2. case 1://x    Zero_limit表示小于此值则认为在此轴上以减少运算次数
  3. if(fabs(Circular.I)>Zero_limit && fabs(Relative_target_x)>Zero_limit)
  4.                         Quadrant_changed |= temp_1;
  5.                     break;
  6. case 2://y
  7. if(fabs(Circular.J)>Zero_limit && fabs(Relative_target_y)>Zero_limit)
  8.                         Quadrant_changed |= temp_1;
  9.                     break;
  10. case 3://x
  11. if(fabs(Circular.I)>Zero_limit && fabs(Relative_target_x)>Zero_limit)
  12.                         Quadrant_changed |= temp_1;
  13.                     break;
  14. case 4://y
  15. if(fabs(Circular.J)>Zero_limit && fabs(Relative_target_y)>Zero_limit)
  16.                         Quadrant_changed |= temp_1;
  17.                     break;
  18.             }
  19.         }
  20.     }
  21.     if(Quadrant_changed&0x07)// 不为0则需要换象限计算
  22.     {
  23. switch(Quadrant_changed&0x07)// Quadrant_changed的switch语句,根据Quadrant_changed的值设置本次圆弧插补的终点

//本次圆弧插补的终点并未设置在轴上(Zero_P、Zero_N不为0),是为减少象限的再次判断(例如顺圆弧中终点4落在x轴上,则原本是第一象限,插补后还是第一象限)。目前为了提高精度故将Zero_P、Zero_N设为0    
  1. {
  2.         case 1:Relative_target_x=Zero_P;Relative_target_y=Radius;
  3.             break;
  4.         case 2:Relative_target_x=0-Radius;Relative_target_y=Zero_P;
  5.             break;
  6.         case 3:Relative_target_x=Zero_N;Relative_target_y=0-Radius;
  7.             break;
  8.         case 4:Relative_target_x=Radius;Relative_target_y=Zero_N;
  9.             break;
  10.         }
  11.         Position.Target.x=the_origin_x+Relative_target_x;//计算实际的终点坐标值
  12.         Position.Target.y=the_origin_y+Relative_target_y;
  13.     }
经过以上的判断就将跨象限的圆弧逐个分段,插补最多分5段,最少1段。
单象限圆弧插补算法:
  1. Circular.Quadrant=Circula_QuadrantJudge();//首先是圆弧象限判别
  2.     Circular.zN=Circular.Xe+Circular.Ye;//总步数 用于终点判别,圆弧插补终点的判别与直线插补是一致的,在此略
  3.     Circular.F=0;//每次进行插补时必须清零
  4.     Xi=(u16)(fabs(Circular.I)*S1);//计算圆弧圆心相对起点的增量坐标
  5.     Yi=(u16)(fabs(Circular.J)*S2);
  6.     while(Circular.zN--)//循环插补,知道循环总步数的次数
  7.     {
  8.         Xs=Ys=0;
  9.         //偏差比较
  10. if(Circular.F >= 0)
  11. {
  12.     if(Circular.F==0 )
  13.     {
  14. //哪个方向需走的步数多则在F=0时选择走哪个方向    
  15. if(Circular.Xe<Circular.Ye){Circular.DIR=Y;Ys =1;}else {Circular.DIR=X;Xs =1;}
  16.         switch (Circular.Quadrant)
  17.             {
  18.                 case SR1:{Ys |=0x8000;
  19. //第一象限的顺圆,根据Circular.DIR的值进行插补计算及步进输出
  20.     if(Circular.DIR==Y){Circular.F=Circular.F-2*Yi+1;Yi--;}
  21.      else {Circular.F=Circular.F+2*Xi+1;Xi++;}
  22. break;}    //设置方向为+ 或 -
  23.                 case NR1:{{………}
  24. break;}
  25.                 case SR2:{{………}
  26. break;}
  27.                 case NR2:{{……..}
  28. break;}                
  29.                 case SR3:{{………}
  30. break;}
  31.                 case NR3:{{………}
  32. break;}
  33.                 case SR4:{{………}
  34. break;}
  35.                 case NR4:{{………}
  36. break;}
  37.                 default: ;//Doing Nothing                    
  38.             }
  39.             }
  40. else //F>0时。基本上都是根据之前的公式直接照写即可,下面用….来省略
  41. {switch (Circular.Quadrant)
  42.     {
  43.     case SR1:{Ys=0x8001;Circular.DIR=Y;Circular.F=Circular.F-2*Yi+1;Yi--;break;}
  44.     case NR1:{…….break;}
  45.     case SR2:{……..break;}
  46.     case NR2:{………break;}
  47.     case SR3:{……  break;}
  48.     case NR3:{……..break;}
  49.     case SR4:{………break;}
  50.     case NR4:{………break;}
  51.     default: ;//Doing Nothing                    
  52.     }
  53. }
  54. }
  55. else//F<0时
  56. {
  57.     switch (Circular.Quadrant)
  58.     {
  59.     case SR1:{Xs=1;Circular.DIR=X;Circular.F=Circular.F+2*Xi+1;Xi++;break;}
  60.     case NR1:{Ys=1;Circular.DIR=Y;Circular.F=Circular.F+2*Yi+1;Yi++;break;}
  61. case SR2:{…….break;}
  62.     case NR2:{…….break;}    
  63.     case SR3:{…….break;}
  64.     case NR3:{……break;}
  65. case SR4:{…….break;}
  66.     case NR4:{……..break;}
  67.     default: ;//Doing Nothing                    
  68.     }
  69. }
  70. //由于计算的目标值与实际轨迹的误差,会导致(根据总步数所走的)最后的坐标与目标坐标不一致,但此时又假设已到目标值来进行下一点计算,会导致下一步总步数出现多(或少几步),即出现过补或少补
  71.     switch (Circular.DIR)//根据之前的计算输出到电机
  72.     {
  73.             case X:{Motor_Move(Xs,Ys);break;}
  74.             case Y:{Motor_Move(Xs,Ys);break;}
  75.             default: ;//Doing Nothing
  76.         }
程序文件下载:

最后就是GRBL软件通过串口与主控芯片对接的问题,首先用软件打开串口后主控芯片需主动发送“Grbl 0.8c ['$' for help]”,也就是发版本号给电脑:

下面是激光刻出的一些图案,没搞视频了玩到实物才是最有用的



下面是刚买来激光的强度,激光头质量真是不敢恭维




[ 此帖被ly7317090803在2015-12-31 17:14重新编辑 ]
本文内容包含图片或附件,获取更多资讯,请 登录 后查看;或者 注册 成为会员获得更多权限
本帖最近打赏记录:共61条打赏M币+196专家+1
wjhwpp M币 +5 歡迎探討 2016-09-18
huakun888 专家 +1 優秀文章 2016-03-01
huakun888 M币 +10 優秀文章 2016-03-01
srad01 M币 +3 優秀文章 2016-01-31
xuyaz M币 +3 好教程,好教程,好教程。重要的话说三遍 2016-01-06
cola4148 M币 +3 原創內容 2016-01-06
jimmyltb M币 +3 謝謝分享 2016-01-06
jun4664 M币 +3 謝謝分享 2016-01-04
ateman3145 M币 +1 優秀文章 2016-01-04
冰封的记忆 M币 +3 - 2016-01-03
离线494448345

发帖
14
M币
982
专家
0
粉丝
2
只看该作者 2楼 发表于: 2015-12-27
牛!!!! 有没有视频?
本帖最近打赏记录:共1条打赏M币+3
z88088081 M币 +3 原創內容 2015-12-28
离线lydega

发帖
1247
M币
2649
专家
1
粉丝
10
只看该作者 3楼 发表于: 2015-12-27
没分了,先收藏,想搞的时候可以找来看看。
离线wc5095928

发帖
14613
M币
9047
专家
11
粉丝
1202
只看该作者 4楼 发表于: 2015-12-27
用PWM把光调暗
驱动激光管不要用PWM
用恒流
虽然效率低点 但是不容易坏
  PWM不等于恒流 只是高速开关而已 还是过流
如果还坏 那就是商家忽悠你 把激光管超功率使用了
本帖最近打赏记录:共1条打赏M币+3
tsfrhym M币 +3 精彩回帖謝謝分享学习学习 2016-01-03
离线518zl

发帖
1236
M币
1315
专家
1
粉丝
27
只看该作者 5楼 发表于: 2015-12-27
謝謝分享学习学习
离线素食猫

发帖
4178
M币
1182
专家
5
粉丝
44
只看该作者 6楼 发表于: 2015-12-27
离线zhmy8828

发帖
174
M币
4713
专家
1
粉丝
23
只看该作者 7楼 发表于: 2015-12-27
真厉害,不知道刻出来的效果如何?
离线hzxin

发帖
302
M币
3214
专家
3
粉丝
46
只看该作者 8楼 发表于: 2015-12-27
谢谢分享,值得学习。
离线xiamofeng

发帖
4686
M币
3239
专家
1
粉丝
59
只看该作者 9楼 发表于: 2015-12-27
太牛了膜拜中。。
快速回复
限80 字节
如果您提交过一次失败了,可以用”恢复数据”来恢复帖子内容
 
上一个 下一个