1.1 导入
网上关于写字机的主流机构多具有以下特征:x,y两垂直轴,丝杆或传动带驱动,虽然相关资料全面,控制算法简单,但体积大,结构搭建复杂,灵活性欠佳,于是我便想创造一种结构简单的机构,但哪种结构可以胜任呢?直到我看见极点求积仪,看到它能在平面上灵活移动,而且结构简单,关节运动量小,我便有了答案——制作一个类求积仪机构。
2.数学理论上的推导
2.1 方程求解
首先,以关节1所在位置建立坐标系,若我们已知2个臂长为m,n,要让机构末端到达点(a,b),那么问题的关键便在于求出关节2坐标(x,y)。实际上这等价为已知一个三角形的三边长与两点坐标,求另一点坐标。
尺规作图的思想告诉我们作三角形时可以在线段两端作圆(如下图),连接交点与线段两端,那这是否能给我们带来些启发呢?
不难想到,我们可以效仿其通过圆的方程构造出两个圆(如图),那么两个圆的交点不就是(x,y)吗?于是我们可以联立两方程,得出:
x-a2+y-b2=n2
x2+y2=m2
通过展开平方式与消元,可以得到关于一个未知数的二元一次方程,代入解析式即可得到方程的两个根,而根据作圆的联想可知,这两个根实则就是圆的两个交点,于是我们取正根,就得到了此方程的解了:
(计算过程省略)
2.2模拟与分析
我们把这个数学模型导入geogebra模拟一下,看看有什么发现
https://download.csdn.net/download/2202_75517449/88861294?spm=1001.2014.3001.5501
通过观察,我们可以得出:
- 当b=0时,方程无解;b<0时,转为另一个根
- 当y<0时,第一关节应该得到负角
- 运动范围是以原点为圆心,以m+n为半径的处于y轴正半轴的半圆
- 当(a,b)成线性运动时,角度呈非线性变化;这就意味着不能直接以固定频率驱动步进电机。
3.上位机计划与编写
3.1上层图像处理
本程序采用python编写,图像处理部分是opencv库,串口通讯部分是Serial库,图形化显示部分为tkinter库。
在图像处理方面,本程序调用opencv的Canny算法与轮廓识别(findContours)实现了对轮廓的提取,调用腐蚀算法细化得到了图片的“骨架”,通过合理设置参数,便可得到数据点了。
3.2笔的轨迹
前文提到角度的非线性变化,既然一段较长的线无法通过线性角度变化得到,那么可以把这项工作拆分成若干项简单的任务,具体些就是将直线光栅化,像素化,通过Bresenham算法即可完成。这个算法把复杂问题简单化,将无数微观变化积累成了宏观的角度的非线性变化。
此后,只要用pen类的实例化对象p遍历数据点,得到角度变化数据了。
3.3图形化
本程序控件布局如下:
这部分多是一些基本的工作,包括创建组件,设置大小位置,绑定回调函数等等,值得注意的是对绘图过程的可视化:
本程序通过对两种不同坐标系的转换,从而实现了过程的可视化
当然,还有对图像点的平移操作,在此不赘述了。
4下位机
4.1motor类
本程序定义了一个motor类,便于对各种电机类型适配,调用时创建对象即可,实际上这也体现了面向对象的思想。
class motor
{
public:
u8 a, b, c, d, dir;
int ps;
motor(u8 ia, u8 ib, u8 ic, u8 id)
{
this->a = ia;
this->b = ib;
this->c = ic;
this->d = id;
pinMode(this->a, OUTPUT);
pinMode(this->b, OUTPUT);
pinMode(this->c, OUTPUT);
pinMode(this->d, OUTPUT);
}
void stop()
{
digitalWrite(this->a, LOW);
digitalWrite(this->b, LOW);
digitalWrite(this->c, LOW);
digitalWrite(this->d, LOW);
}
void move(u8 step, u8 dir)
{
u8 temp = step;
if (dir == 0)
{
temp = 7 - step;
}
digitalWrite(this->a, movelist[temp % 8] / 8 % 2);
digitalWrite(this->b, movelist[temp % 8] / 4 % 2);
digitalWrite(this->c, movelist[temp % 8] / 2 % 2);
digitalWrite(this->d, movelist[temp % 8] % 2);
}
};
4.2通信协议
为便于上位机与下位机的通信,本程序设计了自己的通信协议:
/*
波特率 9600
PC发送:
第1字节 xDir
第2字节 yDir
第3字节 zState
第4&5字节 xPS
第6&7字节 yPS
单片机发送: 0xff 表示打印完成
*/
4.3电机驱动
下位机中值得一提的地方就是电机驱动了。在主进程接收到相应的移动数据并解析完成后,会进入for循环,如果i是休眠时间的整数倍,那么执行一个脉冲。
for (i = 0; i < f->ps * 550 + 1; i++)
{
delayMicroseconds(1);
if (i % 550 == 0)
{
f->move(fi, f->dir);
}
if (i % sleeptime == 0)
{
u->move(ui, u->dir);
}
}
与直流有刷电机不同的是,步进电机无法通过占空比调节转速,只能通过频率改变,这就要求单片机可以在一时间发出不同频率的脉冲,我也原本以为只能使用多任务的方法实现,但过一段时间再想就想出来了这种算法,与前者相比,这种方法无需多任务较大的编译量,足够轻量化,简单化,只要delay()的时间准,执行速度快,就能输出大差不差的脉冲了。
- 总结
这是一种新型的写字机构型,在算法与结构上做了取舍,虽控制算法复杂,但好在结构简单,灵活性高,运动量小,可以适应多种情况。其从另一个角度上说,这也是机械臂在二维上的简单构思,继承了其优点。
此外,实践才是检验真理的唯一标准,程序免不了有bug,之后若在实践中发现问题,我定会努力改正。
最后,特别鸣谢网上技术资料的作者,本项目也会开源,以记录、分享我的一些思考与创作。