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 就完成了。一起来作一个吧