nille程晨 顺手智造
之前的
手工版binWatch 被很多人吐槽说不好看,我这个理工男的审美真是伤不起呀。经受了多次精神刺激之后,本人决定再做一个版本的 binWatch —— Matrix 版。
该模块内部使用的是Leonardo 的 ATmega32U4 ,所以我们通过模块上的 MicroUSB 端口就能够直接烧写程序,另外模块内置了 3.7V/140mAH 的锂电池,这样就省去了我们单独添加电源的问题。
显示方面,本人对于整个表面64 个 LED 的规划如下
第一排从右往左的5 个灯用来表示小时时间的二进制显示, 5 个 LED 可表示的最大数是 31 ,我们只需要显示到 23 就可以了;
第二排从右往左的6 个灯用来表示分钟时间的二进制显示, 6 个 LED 可表示的最大数是 63 ,我们只需要显示到 59 就可以了;
第三行作为分割行是不现实任何东西的;
再往下的5 行能够显示两位数字,本人就用这块区域来显示文字化的两位数,显示的内容要依据第一、二排的第一个 LED 。当第一排的第一个 LED 点亮时,下方的数字区就显示的是小时数;当第二排的第一个 LED 点亮时,下方的数字区就显示的是分钟数。第一、二排的 LED 同一时间只有一个点亮,而切换显示内容的功能我们交给了模块上的轻触按键。
在DF 的网站上找到模块相对应的原理图,根据矩阵 LED 、轻触按键和控制板的连接关系定义变量及数组如下。
byte row[8] = {9, 8, 4, A3, 3, 10, 11, 6};
byte col[8] = {2, 7, A5, 5, 13, A4, 12, A2};
//触碰按键接在A1口
int pushButton = A1; 复制代码
另外我们需要定义一个8*8 的矩阵,用来保存显示在 LED 矩阵上的内容。
byte picture[8][8]=
{
{0,0,0,1,1,1,0,1},
{0,0,0,1,0,1,1,1},
{0,0,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0},
{1,0,0,0,1,0,0,0},
{1,0,0,0,1,0,0,0},
{0,1,1,0,0,1,1,0},
{0,0,0,0,0,0,0,0}
}; 复制代码
接着再定义如下几个变量,其中oldTime 用来和 millis() 配合实现一个定时的功能,而变量 valueH 、 valueM 、 valueS 则是用来保存小时、分钟、秒 3 个量。
unsigned long oldTime;
int valueH,valueM,valueS; 复制代码
程序的主体如下。
void setup(void)
{
//初始化连接LED矩阵的管脚
LED_Init();
//初始化连接轻触按键的管脚
pinMode(pushButton, INPUT);
oldTime=millis();
//串口波特率为9600,用于校对时间
Serial.begin(9600);
}
void loop(void)
{
//控制显示区
Draw_Pic();
//利用函数millis()和变量oldTime来定时,这里我的定时时间为1秒,即1000毫秒
if(millis()-oldTime>=1000)
{
oldTime = millis();
//秒的变量加1
valueS = valueS + 1;
//判断秒的变量是否达到60秒,如果达到60秒就要进位,将分钟数加1
if(valueS >= 60)
{
valueS = 0;
valueM = valueM + 1;
//判断分钟的变量是否达到60,如果达到60就要进位,将小时数加1
if(valueM>=60)
{
valueM = 0;
valueH = valueH + 1;
//判断小时的变量是否达到24
if(valueH>=24)
{
valueH = 0;
}
}
//以二进制方式显示小时和分钟的值
Show_Bin(valueH,valueM);
}
//判断轻触按键的状态,以决定数字显示区是显示小时还是分钟
if(digitalRead(pushButton))
{
//如果按键抬起则显示分钟值,同时切换左上角的显示指示
picture[0][0]=0;
picture[1][0]=1;
Show_Num(valueM);
}
else
{
//如果按键按下则显示小时值,同时切换左上角的显示指示
picture[1][0]=0;
picture[0][0]=1;
Show_Num(valueH);
}
}
//查询是否收到数据来设定时间
setTime();
} 复制代码
以下是各个函数的具体实现代码,都是一些基础内容,包括
LED 矩阵的显示,数字的二进制处理、串行数据的接收处理等,有些内容可以参考前两个版本的 binWatch ,有些内容可以参考 Arduino 的基础例程,这里就不详细介绍了。
这里要说明一点,在程序中,除了函数Draw_Pic ,其他显示的操作都是对数组 picture 的修改,实际上程序的执行过程是修改了数组 picture 中的某些变量,然后在函数 Draw_Pic 中调用数组 picture 来实现显示。
/////////////////////////////////////
//设置时间,格式为HxxMxx,xx为两位数字
/////////////////////////////////////
void setTime(void)
{
if( Serial.available())
{
if('H'== Serial.read())
{
while(!Serial.available()); //hour
int recData = Serial.read() ;
while(!Serial.available()); //min
valueH = (recData - 0x30)*10+Serial.read()-0x30;
Serial.println("Hour set ok");
}
if('M'== Serial.read())
{
while(!Serial.available()); //hour
int recData = Serial.read() ;
while(!Serial.available()); //min
valueM = (recData - 0x30)*10+Serial.read()-0x30;
Serial.println("Min set ok");
}
Show_Bin(valueH,valueM);
}
}
/////////////////////////////////////
//连接LED矩阵的管脚初始化
/////////////////////////////////////
void LED_Init(void)
{
for (byte thisPin = 0; thisPin < 8; thisPin++)
{
pinMode(col[thisPin], OUTPUT);
pinMode(row[thisPin], OUTPUT);
digitalWrite(row[thisPin], LOW);
digitalWrite(col[thisPin], HIGH);
}
}
/////////////////////////////////////
//根据变量数组picture绘制显示区
/////////////////////////////////////
void Draw_Pic()
{
for (byte thisRow = 0; thisRow < 8; thisRow++)
{
pinMode(row[thisRow], OUTPUT);
digitalWrite(row[thisRow], HIGH);
for (byte thisCol = 0; thisCol < 8; thisCol++)
{
pinMode(col[thisCol], OUTPUT);
digitalWrite(col[thisCol], !picture[thisRow][thisCol]);
if (picture[thisRow][thisCol] == HIGH)
{
delayMicroseconds(80);
digitalWrite(col[thisCol], HIGH);
}
}
digitalWrite(row[thisRow], LOW);
}
}
/////////////////////////////////////
//以二进制方式显示时间
/////////////////////////////////////
void Show_Bin(int _hour,int _min)
{
int _showTemp = _hour;
int i;
for(i=7;i>2;i--)
{
if(_showTemp%2 == 1)
{
picture[0][i]=1;
}
else
{
picture[0][i]=0;
}
_showTemp = _showTemp/2;
}
_showTemp = _min;
for(i=7;i>1;i--)
{
if(_showTemp%2 == 1)
{
picture[1][i]=1;
}
else
{
picture[1][i]=0;
}
_showTemp = _showTemp/2;
}
}
/////////////////////////////////////
//数字区显示
/////////////////////////////////////
void Show_Num(int _value)
{
for (int i = 0; i < 8; i++)
{
picture[3][i] = 0;
picture[4][i] = 0;
picture[5][i] = 0;
picture[6][i] = 0;
picture[7][i] = 0;
}
switch(_value/10)
{
case 0:
break;
case 1:
picture[3][2]=1;
picture[4][2]=1;
picture[5][2]=1;
picture[6][2]=1;
picture[7][2]=1;
picture[4][1]=1;
picture[7][1]=1;
picture[7][3]=1;
break;
case 2:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][3]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][1]=1;
picture[7][1]=1;
picture[7][2]=1;
picture[7][3]=1;
break;
case 3:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][3]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][3]=1;
picture[7][1]=1;
picture[7][2]=1;
picture[7][3]=1;
break;
case 4:
picture[3][1]=1;
picture[4][1]=1;
picture[3][3]=1;
picture[4][3]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][3]=1;
picture[7][3]=1;
break;
case 5:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][1]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][3]=1;
picture[7][1]=1;
picture[7][2]=1;
picture[7][3]=1;
break;
case 6:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][1]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][1]=1;
picture[6][3]=1;
picture[7][1]=1;
picture[7][2]=1;
picture[7][3]=1;
break;
case 7:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][3]=1;
picture[4][1]=1;
picture[5][3]=1;
picture[6][3]=1;
picture[7][3]=1;
break;
case 8:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][1]=1;
picture[4][3]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][1]=1;
picture[6][3]=1;
picture[7][1]=1;
picture[7][2]=1;
picture[7][3]=1;
break;
case 9:
picture[3][1]=1;
picture[3][2]=1;
picture[3][3]=1;
picture[4][3]=1;
picture[4][1]=1;
picture[5][1]=1;
picture[5][2]=1;
picture[5][3]=1;
picture[6][3]=1;
picture[7][1]=1;
picture[7][2]=1;
picture[7][3]=1;
break;
default:
break;
}
switch(_value%10)
{
case 0:
if(_value!=0)
{
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][5]=1;
picture[4][7]=1;
picture[5][5]=1;
picture[5][7]=1;
picture[6][5]=1;
picture[6][7]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
}
break;
case 1:
picture[3][6]=1;
picture[4][6]=1;
picture[5][6]=1;
picture[6][6]=1;
picture[7][6]=1;
picture[4][5]=1;
picture[7][5]=1;
picture[7][7]=1;
break;
case 2:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][7]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][5]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
break;
case 3:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][7]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][7]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
break;
case 4:
picture[3][5]=1;
picture[4][5]=1;
picture[3][7]=1;
picture[4][7]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][7]=1;
picture[7][7]=1;
break;
case 5:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][5]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][7]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
break;
case 6:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][5]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][5]=1;
picture[6][7]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
break;
case 7:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][7]=1;
picture[4][5]=1;
picture[5][7]=1;
picture[6][7]=1;
picture[7][7]=1;
break;
case 8:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][5]=1;
picture[4][7]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][5]=1;
picture[6][7]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
break;
case 9:
picture[3][5]=1;
picture[3][6]=1;
picture[3][7]=1;
picture[4][7]=1;
picture[4][5]=1;
picture[5][5]=1;
picture[5][6]=1;
picture[5][7]=1;
picture[6][7]=1;
picture[7][5]=1;
picture[7][6]=1;
picture[7][7]=1;
break;
default:
break;
}
} 复制代码
程序完成后,还需要想个办法把这个8x8 点阵模块固定在手腕上, makerpapa 的建伟同学帮我设计了一个类似于手表的外壳,整体完成之后如下图所示。图中所显示的时间是 12 点 49 ,第一排 LED 从右往左,点亮的 LED 是第 3 个和第 4 个,所以就是 2 (3-1 ) +2(4-1 ) =12。分钟的量我们直接看数字显示区就好。而 binWatch 的 表带是用本人用粘扣手工缝纫而成的。
当模块侧面的按键按下是数字显示区就显示的是小时,弹起的话就显示的是分钟。要是想锻炼一下算术的话那就自己数点算时间吧。
带着闪闪发光的手表,我心想要不要自己也尝试设计一下这个手表的外壳,之前在翻译《解析3D 打印机》的书中还是学到一些 3D 建模的知识。
建模之前先要确定一下模块的大小,在DFRobot 的网站上能够查到显示模块的大小是 32mm×32mm×16mm,手表外壳壁厚2mm。有了这两个基本数据就可以开始建模了,建模软件方面本人选择了被Google 收购的 SketchUp 。
打开SketchUp 后,在模板中选择“产品设计与木器加工 - 毫米”这一项。
在软件界面中打开“大工具集”,我们在使用时直接用鼠标选择工具集中的相应工具就可以了,比如绘制方形、圆形,拉伸、移动等等。
首先选择绘制方形的工具,因为模块的尺寸是32mm × 32mm ,而壁厚是 2mm ,所以这个方形的大小应该是 36mm × 36mm ( 32+2+2 ),软件中可以直接在右下角的尺寸框中输入相应的尺寸,这里我们输入 36 , 36 就得到了一个 36mm × 36mm 的正方形 。
接着使用推/ 拉工具将正方形拉高,模块的高度是 16mm ,我们留 2mm 的富余,拉伸高度定位 18mm ,同样的我们直接在右下角的尺寸框中输入 18 就可以了。此时就得到了一个 36mm × 36mm × 18mm 的立方体 。
然后使用偏移工具将上表面的正方形边沿向内偏移2mm,作为外壳的厚度。
还是使用推/拉工具将刚才向内偏移 2mm 的正方形向下推 18mm ,这样就得到了一个只有外壳的方框。我们的显示模块到时就放在这个方框中。
第二步就是要绘制外壳表面两侧用来连接表带的耳朵。在方框一侧的底部绘制一个36mm × 4mm × 2mm 的长方体。
使用偏移工具将长方体的顶面边沿向内偏移2mm,因为顶面的宽度本身只有 4mm ,所以偏移之后会得到一条线段,线段的两个端点距离两边的垂直距离为 2mm 。
使用线条工具将线段的两个端点用垂直线段连接到外壳的外表面上,咱耳朵的上表面形成一个小的长方形。
同样使用推/拉工具将这个面向下推 2mm ,形成一个开口。这样一侧的耳朵就做好了。
用同样的方式在另外一次也制作一个耳朵。
此时我们的外壳整体基本上就算完成了,最后一步是要在外壳没有耳朵的一侧留一个开口。大家注意的话会发现,显示模块上的开关和轻触按键是在32mm × 32mm 的范围之外的,同时我们希望模块的 MicroUSB 端口能漏在外面,方便下载程序和设定时间。外壳上的开口就是要把这些东西漏出来。我们用卡尺测量一下 MicroUSB 端口的厚度,不到 4mm ,我们就按照 4mm 来设计。
在外壳方框的内壁上,在距离底边4mm 位置画一条直线。
使用推/拉工具 将下方形成的小的长方体向外推2mm形成一个开口。
最后用擦除工具将方框和耳朵连接处之间的线段擦除掉,这一点非常重要,这样耳朵和方框才能连接成统一的整体,完成后如下图所示。此时我们的binWatch外壳建模就完成了。
为了能够使用3D 打印机打印我们的手表外壳,需要将这个模型导成 STL 格式。在 SketchUp 中的文件格式是不适用于 3D 打印的,所以我们需要给 SketchUp 安装一个插件进行格式转换。这是由 Nathan Bromham 写的格式转换插件,可以从 Guitar-List 的网站 www.guitar-list.com/download-software/convert-sketchup-skp-files-dxf-or-stl 上下载,下载安装完成后, SketchUp 中在 Tool 菜单下应该能够找到 Export to DXF or STL 选项。
我们在Cura中打开 导出后的stl文件,如下图所示。然后再将这个 stl 文件转换成 3D 打印机可用的 g-code 文件,就能够使用 3D 打印机打印了。
打印完成后,在两个耳朵上缝上一段粘扣。
最后把两个粘扣粘在一个表带上,Matrix版的 binWatch 就完成了。一起来作一个吧