"废物利用"也抄袭——废旧喷墨打印机和光驱DIY"绘图仪"
很长时间没有写博客,因为各种各样的事情占去大块时间,只有零碎时间偶尔在CSDN逛逛也偶尔回几个帖子。很久以前就看到一些光驱DIY雕刻机之类的,很是向往,最近这几天得闲就TB了一套Arduino UNO R3实验套件,也实践了一番这种单任务平台,从点亮个LED翻到步进电机就再也忍不住了,于是狠狠的操作了一番ULN2003和28BYJ48,好在经过对度娘的一番拷问,了解了不少东西,基本了解了各种所需的基本知识,其中比较需要自己实验的方面列出来,以供大家参考:
一、步进电机、驱动和接线
买了两个L298N来驱动光驱步进电机,其中in1-in4就是接Arduino程序里面定义的引脚1-4,电源采用12V供电。我拆的两个光驱电机(拆了3个其中一个是无刷的),具体接法可以自己试一试,连接两个引脚之后转动有阻力的就是一组,有万用表测一下也可以的。我这两个光驱电机都是依次排列的,即12、34两组,所以接的时候依次接L298N的ou1-ou4即可。唯一需要注意的地方就是L298N的GND接到Arduino的GND上。
二、给Arduino编程
用c写代码还是蛮别扭的,就像你开了这么多年自动挡,让你开手动档能不能走起呢?当然能,就是得光想着离合那点事。这里主要考虑的就是绘图的话那2k的内存不好办,所以还得上位机控制,所以整个程序框架建立的时候就是服从上位机指令;然后就是电机工作的平稳性,如果电机带着整个机械机构在那广场舞,那激光指不定射谁一眼呢(虽然我打算绑只圆珠笔搞定),所以平稳还是要的,也就修改了电机库使它从4拍变为8拍并且增加了S型加速。所以说,驱动细分不细分的,软件实现就可以了,毕竟我们的要求不高,2k内存还是绰绰有余的。
1、与上位机通讯
发送消息很简单:
//请求数据
void RequestData() {
Serial.println(r_RequestData);
delayMicroseconds(); //16000000/96000=1666.66666
}
注意别粘包了就好。而且发送的字符尽量少,1个字节可以表示二百五还多的命令很够用了。
接收消息也不难,分类处理一下就可以了:
//板子初始化操作后进入的循环执行函数。
void loop() {
if (Serial.available()!=) {
msgLen = Serial.readBytes(msgBuff, msgBuffSize); //读取消息
if (msgLen > ) {
CommandParsing(msgBuff); //处理消息
}
}else{
RequestData(); //请求数据
}
} void CommandParsing(char buff[]) {
if (buff[] == c_Stop) {
DoStop();
}else if (buff[] == c_xForward || buff[]==c_xBackOff) {
DoxMove(buff[], ToInt32(buff));
}else if (buff[] == c_yForward || buff[] == c_yBackOff) {
DoyMove(buff[], ToInt32(buff));
}else if (buff[] == c_zUp || buff[] == c_zDown) {
DozMove(buff[]);
}else if (buff[] == c_xSpeed) {
xSpeed = ToFloat(buff);
Serial.println(xSpeed);
xStepper.setSpeed(xSpeed);
}else if (buff[] == c_ySpeed) {
ySpeed = ToFloat(buff);
yStepper.setSpeed(ySpeed);
}else {
Serial.println(r_UnknownCommand);
}
memset(msgBuff, , msgBuffSize);
}
我的接收缓冲区只有5字节,也就是说每个命令都一样长——5字节,这可以让代码简单一些,看了一下Serial的源码,里面设置了接收缓冲区64字节,所以上位机可以一口气发10个命令,再多就被舍弃了,还是等待下位机请求之后再发才保险。
2、修改Stepper库
绝大部分还是保留原来的内容,只是稍作修改,在Stepper_CDROM.h中:
const int number_of_steps=; // total number of steps this motor can take unsigned long CurDelay;
unsigned long GetSModelLine(int StepCount,int StepLeft);
float sSpeed[] = {16.6667, 7.1429, 4.1667, 2.5, 1.6667, 1.3158, 1.1628, 1.0638};
把原来构造函数的第一个参数修改为常量8,后面也跟着修改了setSpeed函数的实现:
//设置每分钟转多少步
void Stepper_CDROM::setSpeed(float whatSpeed)
{
this->step_delay = 60L * 1000L * 1000L / this->number_of_steps / whatSpeed;
}
就如注释的一样,设置的速度是每分钟的步数而不是转数,其实这个值最后还是要不断的调试得出,我最后确定了用这样一个数值:
float ySpeed = ; //y轴步进电机每分钟步数的最大值
而和原来代码格格不入的变量名就是我搞出来的了,为了计算S型加速曲线,实际上只有8步速或减速过程,这些数值是最高速时的时间间隔的倍数。虽然这样做要比Exp函数(S型曲线的原函数计算非常耗时)来的快的多得多,但是由于我没有这方面的经验,所以步数和加速度可能很不理想。但是无论如何,我修改了原来代码的内容,使用了我的时间间隔来代替原有间隔,做为萌新看起来可能还不错:
void Stepper_CDROM::step(int steps_to_move)
{
int steps_left;
if (steps_to_move > ) {
this->direction = ;
steps_left = steps_to_move;
}
if (steps_to_move < ) {
this->direction = ;
steps_left = -steps_to_move;
}
int StepCount = steps_left; this->CurDelay = GetSModelLine(StepCount,steps_to_move);
// 到达延迟时间后转动一步,直到转动全部步数。
while (steps_left > )
{
unsigned long now = micros();
// 计算延迟是否到达
if (now - this->last_step_time >= CurDelay)
{
// 记录本次转动时间:
this->last_step_time = now;
// 根据方向设置当前拍:
if (this->direction == )
{
this->step_number++;
if (this->step_number == this->number_of_steps) {
this->step_number = ;
}
}
else
{
if (this->step_number == ) {
this->step_number = this->number_of_steps;
}
this->step_number--;
}
// 记录剩余步数:
steps_left--;
// 运行电机
stepMotor(this->step_number % );
//计算下一个延迟
this->CurDelay = GetSModelLine(StepCount, steps_left);
}
}
} unsigned long Stepper_CDROM::GetSModelLine(int StepCount,int StepLeft)
{
if (StepCount < ) {
return this->step_delay*; //不足16步则无法完成一次加速和一次减速,以1/2最高速度运行。
}
if (StepLeft <= ) {
return this->step_delay * this->sSpeed[StepLeft-]; //最后8拍倒序执行即减速。
}
if (StepCount - StepLeft < ) {
return this->step_delay * this->sSpeed[StepCount - StepLeft]; //前8拍顺序执行即加速。
}
return this->step_delay; //前后8拍之间的最高速度运行。
}
红的的行就是应用我的时间间隔的地方了,而下面的自定义函数就是计算过程,这只需要查表就可以了,当然,我懒到对于不能进行完整加减速的过程简单粗暴的用了一个半速。但无论如何,经过各种调试,现在这个光驱里拆出来的架子上面的机械机构运行时速度很快,声音很小;当然,还有更重要的一点,我测试的结果是500拍就差不多从一端走到另一端共3.8cm、12圈多一点,所以这个电机大约是40步转一圈,细分8拍之后,每一拍大约0.076mm,这个精度也可以了,但是如果用原来的库进行4拍驱动就只能达到0.152的精度,走6拍差不多1mm了,很明显的锯齿有木有。之前上传的程序有一点问题,已经修正了。下面添加一个光驱电机8拍的顺序:
1000、1100、0100、0110、0010、0011、0001、1001。1表示高电平,0表示低电平。
这几天又看了看A4988驱动,准备入手两三块,这个驱动编写程序要简单很多。
3、上位机程序
这下开上自动挡的赶脚又回来了,很简单的封一个类就可以:
Private Enum Command As Byte
c_Stop = '暂停
c_Continue = '继续
c_xForward = 'x轴前进
c_xBackOff = 'x轴后退
c_yForward = 'y轴前进
c_yBackOff = 'y轴后退
c_zUp = 'z轴抬起
c_zDown = 'z轴落下
c_xSpeed = 'x轴速度
c_ySpeed = 'y轴速度
End Enum Private Enum Request As Byte
r_UnknownCommand = Asc("d") '未知命令
r_RequestData = Asc("e") '请求数据
End Enum Private WithEvents mPort As SerialPort
Event RequestData(msg As String) Sub New(PortName As String, BaudRate As Integer)
mPort = New SerialPort(PortName)
mPort.BaudRate = BaudRate
Try
mPort.Open()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub Private Sub mPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles mPort.DataReceived
Dim inData As String = CType(sender, SerialPort).ReadLine.TrimEnd({CChar(vbCr), CChar(vbLf)})
RaiseEvent RequestData(inData)
End Sub Private Sub mPort_Disposed(sender As Object, e As EventArgs) Handles mPort.Disposed
Try
If mPort IsNot Nothing AndAlso mPort.IsOpen Then
mPort.Close()
End If
Catch ex As Exception
End Try
End Sub Sub SendCommand_Stop()
WritePort({Command.c_Stop, , , , })
End Sub ‘此处省略其他函数封装。 Private Sub WritePort(buff() As Byte)
Try
mPort.Write(buff, 0, 5)
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
使用指定的端口名和波特率初始化一下端口类,定义命令和请求,然后封装好不同的命令和请求就可以了,当然这里请求处理还不完整也为了测试方便,把请求事件那里的条件去掉了,但并不影响对整个程序结构的理解。
虽然我的上位机程序对通讯部分进行了很多修改,但是整个框架还是这样的。添加了一些简单的功能,但是有些部分还不是很成熟,把经过测试没有问题的部分说明一下:
3.1图像的简单处理:
我使用了OPENCV组件(NUGET)来完成这样几个工作:
A、读取和显示图片
Dim ofd As New OpenFileDialog
Try
ofd.Filter = "jpg files|*.jpg|png files|*.png|bmp files|*.bmp|all files|*.*"
ofd.Multiselect = False
If ofd.ShowDialog = DialogResult.OK Then
ImgSrc = New Mat(ofd.FileName)
pnlSrc.BackgroundImage = Mat2Img(ImgSrc)
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
这个程序很简单,就是读取文件然后显示在Panel上。其中Mat2Img函数如下:
Private Function Mat2Img(mat As Mat) As Bitmap
Dim ms As New MemoryStream
mat.WriteToStream(ms)
Dim result As Bitmap = Bitmap.FromStream(ms)
ms.Close()
Return result
End Function
就是利用内存流转储一下而已。
B、灰度化、二值化、边缘查找
ImgGray = ImgSrc.CvtColor(ColorConversionCodes.BGR2GRAY)
ImgBinary = ImgGray.Threshold(nudThreshValue.Value, nudThreshMaxValue.Value, cmb2ThresholdTypes)
Dim pss()() = ImgBinary.FindContoursAsArray(cmb2RetrievalModes, cmb2ContourApproximationModes)
这样就可以得到边缘的点坐标集合,并且这些点是相连的,所以可以优化绘制代码——按曲线绘制,比一行一行扫描看起来高大上一些。
C、二值图也按曲线绘制
这个功能还是利用我所熟悉的快速种子填充算法来做,只是Mat类获取点颜色的方法不太一样:
Private Function FindRegionByPoint(img As Mat, mTable(,) As Boolean, rect As Rect, p As Point, color As Byte) As Point()
Dim result As New List(Of Point)
Dim mArray As New Queue '栈——将处理点表
Dim mP As Point = p '正在处理点
Dim mAP As Point '临时变量——可能被入栈的点
mArray.Enqueue(mP) '入栈
Do
If mTable(mP.X, mP.Y) = False Then '若未处理过
If img.At(Of Byte)(mP.Y, mP.X) = color Then '相同颜色则添加临近点
mAP = New Point(mP.X, mP.Y - ) '临近点入栈
If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
mAP = New Point(mP.X, mP.Y + )
If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
mAP = New Point(mP.X - , mP.Y)
If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
mAP = New Point(mP.X + , mP.Y)
If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
result.Add(New Point(mP.X, mP.Y))
End If
mTable(mP.X, mP.Y) = True '修改为已处理
End If
If mArray.Count = Then Exit Do Else mP = mArray.Dequeue '出栈
Loop
Return result.ToArray
End Function
这个函数中使用了一个加速表,因为这个函数只是从一个点来查找一片点,有多个相连的点集的时候需要重复调用,所以加速表是重复使用的,当然以前做过一些简单的测试,用bitarray来实现速度还要快一点。至于遍历整个图像的函数就不贴了,两层循环而已,没意思。
以上就是这些天做的一些努力,当然了,今天100大洋的激光头也到货了,插在两个光驱做的框架上做了一下测试,也遇到一些问题。激光器模组的散热套连个硅脂都不送,贴合不紧导热不好,在现在低负荷运行的条件下根本不发热,也就没处理;接的电源过了ULN2003之后电压降很多,又没有合适的变压器,索性直接插了Ardiuno,所以电流很低,和A4988一起入手一块恒压恒流模块准备给它供电,这样就可以功率调节到合适的程度,当然散热器上带个小风扇是一定的。后面再利用打印机的字车扩大一下雕刻范围,也许还会买个光轴做个大一点的。
"废物利用"也抄袭——废旧喷墨打印机和光驱DIY"绘图仪"的更多相关文章
- "废物利用"也抄袭——“完全”DIY"绘图仪"<二、下位机程序设计>
就不说怎么组装了吧,一把辛酸泪.说程序,因为这有两把辛酸泪……一把给下位机的C代码一把为了VB.NET的图像处理……不过就上上一篇说的,它们可以正确运行了,并且今天克服了Arduino上电过程中步进电 ...
- "废物利用"也抄袭——“完全”DIY"绘图仪"<三、上位机程序设计>
上位机的程序主要是解析图片和生成较好的代码,现在实现的功能有灰度打印,二值打印,轮廓打印,骨骼打印.当然,必不可少的是打印大小的控制.测试了一些图片,总体来说,打印速度依次加快,因为打印的内容依次减少 ...
- "废物利用"也抄袭——“完全”DIY"绘图仪"<一、准备工作>
上一个光驱造已经解体若干天了,因为事情很多arduino也不太熟悉,直到今天才做出了一个可以用的样本.当然,上位机和下位机代码都写好了,可以正常工作,但是由于电路知识还是中学那点没还给老师的,加以各种 ...
- Touch Bar 废物利用系列 | 在触控栏上显示 Dock 应用图标
都说 Intel 第八代 CPU 对比上代是牙膏不小心挤多了,而配备第八代 CPU 的 MacBook Pro,只有 Touch Bar 版本,虽然贵了一点,但就一个字 -- 买! 收到电脑后,兴冲冲 ...
- opencv模块学习
一.简介 ''' 分辨率(resolution,港台称之为解析度)就是屏幕图像的精密度,是指显示器所能显示的像素的多少.由于屏幕上的点.线和面都是由像素组成的,显示器可显示的像素越多,画面就越精细,同 ...
- noip初赛复习总纲
初赛复习总纲 目录 初赛复习总纲 计算机发展史 计算机的分类 计算机的应用 操作系统盘点 计算机的基本结构 中央处理器(**CPU**--**Central Processing Unit**) 存储 ...
- "分辨率"到底是个什么概念?它和DPI之间是什么关系?
"分辨率"到底是个什么概念?它和DPI之间是什么关系? 分辨率:显示分辨率(屏幕分辨率)是屏幕图像的精密度,是指显示器所能显示的像素有多少.由于屏幕上的点.线和面都是由像素组成的, ...
- Epson L4158打印机安装与配置
上周购买了一台打印.扫描.复印三合一的Epson L4158喷墨打印机,主要用于打印数学纸版笔记套图.长笛乐谱.常用软件的cheatsheet(例如,GNU/Linux命令.GNU Emacs快捷键. ...
- usb口打印机的指令打印和驱动打印
打印机简介:是计算机的输出设备之一,用于将计算机处理结果打印在相关介质上. 打印机类型:激光打印机.喷墨打印机.针式打印机.热敏打印机等. 计算机和打印机之间的连接方式:usb口.串口.并口.网口.蓝 ...
随机推荐
- Sql server日期函数操作
1.获取前一小时内的数据:DATEADD(HOUR,-1,GETDATE()),将"HOUR"替换成DAY,Month,YEAR就是前一天,前一月,前一年 2.获取日期部分,格式为 ...
- Linux服务器上安装tomcat
安装软件 : apache-tomcat-9.0.0.M1.tar.gz(下载地址http://tomcat.apache.org/) 步骤一 Tomcat是其中一个开源的且免费的java Web服务 ...
- vue 插件(Sublime Text 3 常用插件以及安装方法)(转)
使用Package Control组件安装 也可以安装package control组件,然后直接在线安装:按Ctrl+` 调出console粘贴以下代码到底部命令行并回车: { import url ...
- ES的Zen发现机制
ES的Zen发现机制 Zen发现机制是ElasticSearch默认的发现模块.它提供的是单播发现,但是可被拓展为支持云环境下或者其他形式的发现机制.zen 发现模块集成了其他模块,如在发现期间,节点 ...
- Linux系统在启动过程中mbr主引导程序被破坏的解决方案
首先,mbr主引导程序被破坏是指系统在启动过程中,磁头找不到/boot分区(windows的启动分区在c盘). 1)下面我们模拟主引导分区被破坏的情况:(在启动分区划分446M的存储大小) 2)重启( ...
- 利用 squid 反向代理提高网站性能(转载)
本文在介绍 squid 反向代理的工作原理的基础上,指出反向代理技术在提高网站访问速度,增强网站可用性.安全性方面有很好的用途.作者在具体的实验环境下,利用 DNS 轮询和 Squid 反向代理技术, ...
- 使用jQuery操作DOM(2)
1.获取设置属性值 attr({属性名1:属性值1,属性名2:属性值2}); //设置属性值 removeAttr(“属性名”); //删除属性 注意:如果元素没有此属性,会不起作用 2.获取同辈元素 ...
- three.js入门——先跑个旋转的正方体
WebGl中文网看了几篇教程,又百度了几篇文章,顿时感觉手痒,打开编辑器,写个demo玩玩. demo是写在vue项目中的,所以首先: npm install three --save; npm in ...
- 熟悉linux命令
<鸟哥的linux私房菜>这本书终于看到了敲命令行这块了,有点小激动,打开虚拟机,开始~~~敲!!! 登录界面,用户名密码~~~ 登录成功,下面开始熟悉一下,linux的常见命令了: li ...
- Shell 命令行,实现对若干网站状态批量查询是否正常的脚本
Shell 命令行,实现对若干网站状态批量查询是否正常的脚本 如果你有比较多的网站,这些网站的运行状态是否正常则是一件需要关心的事情.但是逐一打开检查那简直是一件太糟心的事情了.所以,我想写一个 sh ...