这是我的极简主义手表系列中最新的一款,它使用12个LED,排列成钟面,以显示时间模拟风格。这款产品利用了MicroChip新的1系列ATTiny微控制器集成了实时时钟的事实,该时钟可以在主处理器休眠时从外部晶体运行以保持准确的时间:
为了显示时间,您只需按下表面上的按钮,它就会点亮LED来显示时间,就像时钟上的时针和分针一样。LED闪烁以区分小时和分钟,并显示中间时间。这款手表的时间保持在每月几秒以内,同时电池续航时间可达数年。
我早先推出的Tmall Time 2 Watch使用Maxim Integrated的DS2417 RTC芯片在处理器休眠时计时,以节省电量。自从我设计以来,MicroChip已经发布了他们的1系列ATTiny芯片,其中包含了一个晶体控制的实时时钟;这可以在处理器处于睡眠状态时计时,而不需要单独的RTC芯片。斯宾塞·孔德(Spence Konde)称这款手表为Mega Tmall系列,因此这款手表也因此得名。
我根据14针ATtiny414设计了这款手表,它有4K字节的闪存,并且有一个支持外部晶体的RTC外设。在此设计中,有几个备用的I/O引脚,因此您可以使用其他功能扩展手表;请参阅下面的进一步建议。
就像我以前的手表一样,为了显示你按下表面上的按钮的时间,时间随后会显示五秒钟。它点亮一个LED以显示小时,并通过改变每个LED的闪烁速率来闪烁一个或两个其他LED以显示分钟到最近的分钟。以下是一些例子:
为了与模拟手表保持一致,Tmall Time Watch通过点亮下一个小时数字来显示晚于半小时的时间。如果只有一个LED亮起,您就知道两只手指向同一小时标记。
没有显示屏的总功耗仅为1µA,单块CR2016电池的电池寿命估计超过10年!显然,如果你经常检查时间,这个时间就会减少。
这是这款迷你Time 2手表的电路,布局像电路板:
晶体是标准的32.768千赫石英表晶体;我选择了MS1V-T1K微晶体[1],但我预计任何32.768千赫的微晶体都会合适。一块3.2X1.5 mm的SMD晶体也可以横跨PCB上的焊盘。
LED的大小是0805,对于这款手表,我选择了白色LED来与黑色电路板形成对比。我的是从英国的Bright Components那里买的,他们以低于GB1的价格出售,售价10美元[2]。任何颜色的LED都可以,但它们应该是相同的颜色。
我安排了LED来简化PCB布局,这解释了相当随意的顺序,但这很容易通过软件中的阵列来实现。下表显示了当您将一条I/O线路设置为高电平,而将另一条I/O线路设置为低电平时,哪些LED会亮起:
该按钮是一个小型SMD按钮,可以从SparkFun[3]购买,也可以从英国的Proto-PIC购买[4]。
电池是20毫米硬币电池。考虑到低电流消耗,我决定使用更薄的CR2016电池,并在Mouser[5]上找到了一个合适的SMD电池座。或者,您也可以使用CR2032电池,带有SparkFun[6]或英国Proto-PIC[7]提供的SMD 20毫米投币式电池座。
注意:如果孩子可以玩这块手表,请弯曲硬币盒支架上的卡舌,或者用胶水粘在硬币盒里,这样硬币盒就不会被取出和吞咽。
我在Eagle设计了电路板,并将其发送到PCBWay进行制造。以下是布局(来自OSH Park预览):
有关下载电路板文件或订购电路板的链接,请参阅本文末尾。
我用SMD元件制作了这款手表,除了电池座以外的所有元件都焊接到了电路板的前面。ATtiny414是SOIC,我使用的是0805电阻和LED,所以它们应该相对容易手工焊接:
LED应以相同的方向焊接,反面应朝向电路板的中心。
我使用250°C的优悦858D+热风枪将SMD元件焊接到电路板的正面,最后使用传统的烙铁将电池座焊接到电路板的背面。如果你没有热风枪,你应该能够稍微小心地使用尖端的烙铁来焊接SMD组件。
就像我早先的项目“New ATiny Low Power”一样,其目的是让处理器大部分时间处于睡眠状态,以最大限度地降低手表的功耗。
从ATtiny414数据表中,我意识到有两种可能的方式在此应用中使用1系列ATTINY实时时钟外设。它可以作为实时计数器运行,在处理器处于休眠状态时,在RTC CNT计数寄存器中保持16位的已用秒数计数。或者,它可以作为定期中断定时器(PIT)运行,每秒生成一个中断来短暂唤醒处理器,并递增一个变量来计算秒数。
从理论上讲,CNT方法的功耗应该会稍微低一些,因为只需要在您重新显示时间时唤醒处理器,但我无法让它工作;CNT寄存器似乎没有在处理器休眠时递增。然而,PIT方法工作得很好,功耗的差异可能很小。如果我让CNT方法正常工作,我会将其作为更新发布。
将RTC设置为使用外部晶体比您想象的要复杂,因为时钟控制器受到配置更改保护(CCP)的保护,以防意外更改,因此在每次更改之前,您必须先启用操作。幸运的是,有一个应用笔记解释了如何做到这一点,我的代码基于笔记中的一个示例[9]。
这12个LED由四条I/O线PA1、PA4、PPA5和PA6驱动。阵列引脚[6][6]指定LED如何连接到五条I/O线路:
INT引脚[6][6]={{-1,-1,-1,8,6,4},{-1,-1,-1,-1,-1},{-1,-1,-1,-1,-1,-1,-1},{7,-1,-1,-1,-1,11,9},{0,-1,-1,10,-1,2},{5,-1,-1,3,1,-1}};
阵列的第一行指定哪些LED的阴极连接到PA1:8点钟的LED的阳极连接到PA4,6点钟的LED的阳极连接到PA5,以及4点钟的LED的阳极连接到PA6。对应于PA2和PA3的数组中的值被设置为-1,因为它们没有连接到任何LED;使用更大的数组来补偿这一点要比编写代码简单。
我使用运行在250 Hz的定时器/计数器TCB来多路复用显示器。这是由例程DisplaySetup()设置的,如下所示:
void DisplaySetup(){//设置计时器/计数器TCB以复用显示器TCB0.CCMP=19999;//将5 MHz除以20000=250 Hz TCB0.CTRLA=TCB_CLKSEL_CLKDIV1_GC|TCB_ENABLE_BM;//启用计时器,除以1 TCB0.CTRLB=0;//定期中断模式TCB0.INTCTRL=TCB_CAPT_BM;//启用中断}。
中断服务例程只需调用DisplayNextRow(),递减全局变量超时,并递增初始设置时间时使用的全局变量偏移量:
void DisplayOn(){//关闭所有上拉PORTA.PIN0CTRL=0;PORTA.PIN1CTRL=0;PORTA.PIN4CTRL=0;PORTA.PIN5CTRL=0;PORTA.PIN6CTRL=0;TCB0.INTCTRL=TCB_CAPT_BM;//启用中断}void DisplayOff(){TCB0.INTCTRL=0;/。PORTA.PIN4CTRL=PORT_PULLUPEN_BM;PORTA.PIN5CTRL=PORT_PULLUPEN_BM;PORTA.PIN6CTRL=PORT_PULLUPEN_BM;}。
void DisplayNextRow(){Cycle++;uint8_trow=Cycle&;0x03;If(row>;0)row=row+2;//跳过PA2和PA3 uint8_tBits=0;for(int i=0;i<;6;i++){if(Hours==Pins[row][i])Bits=Bits|1<;<;(i+1);If(Fivemins。(ROW+1)|BITS;//输出点亮的LED PORTA.OUT=BITS;//将输出设置为高电平}。
最多可同时点亮两个LED。由可变小时数指定的LED用于显示小时数,并且连续显示。由变量Fivemins“指定的LED用于分钟。
变量周期的底部两位确定显示哪一行。对于给定行,检查阵列管脚[行][i]以查看该行中是否需要显示任何LED。如果是,则在可变比特中设置适当的比特。然后将其写入端口。
要测试LED,您可以运行以下循环()函数,而不是清单中的函数:
void loop(){Fivemins=12;小时=(小时+1)%12;延迟(1000);}。
当您第一次给手表通电时,它会运行SetTime()以允许您将时间设置为最接近的秒数。它的工作原理如下:等待当前时间精确到5分钟的倍数,然后插入电池。然后,手表将从12:00开始,一次5分钟地穿过显示屏。当手表显示您插入电池的时间时,按显示时间按钮。手表将说明您设置时间所用的额外时间,然后显示当前时间。它现在可以使用了。
SetTime()例程将变量secs递增300(相当于5分钟),以显示两步之间一秒的时间。在每个步骤中,它将secs的值写入全局变量secs,并添加一个偏移量来说明自该过程启动以来经过的时间。
void SetTime(){unsign int secs=0;ButtonEnable();While(!Showtime){Fivemins=(无符号长时间)(秒/300)%12;小时=(无符号长时间)((秒+1799)/3600)%12;//向全局秒写入时间=秒+(偏移/秒);DisplayOn();MyDelay(Tickpersec);DisplayOff();DisplayOff();秒。
ATtiny414通常处于休眠模式,可以忽略不计的电流。Show Time(显示时间)按钮连接到PA2,PA2由ButtonEnable()定义为上拉和引脚感测中断:
ISR(PORTA_PORT_VECT){PORTA.PIN2CTRL=PORT_PULLUPEN_BM;//禁用按钮PORTA.INTFLAGS=PORT_INT2_BM;//清除PA2中断标志Showtime=true;}
这会禁用引脚检测中断,以避免触点反弹,并将全局变量ShowTime设置为TRUE。
void loop(){unsign int secs;if(Showtime){cli();secs=secs;sei();hr=((secs+1800)/3600)%12;Fivemins=12;int mins=(secs/60)%60;int from=mins/5;int count=mins%5;DisplayOn();for(int i=0;i<;5-count;i++){Fivemins=(secs/60)%60;int from=mins/5;int count=mins%5;DisplayOn();for(int i=0;i<;5-count;i++){Fivemins=。i++){Fivemins=(1+From)%12;MyDelay(Tickpersec/5);Fivemins=12;MyDelay(Tickpersec/5);}DisplayOff();Showtime=false;ButtonEnable();}Sleep_CPU();}。
这首先将全局变量secs复制到一个禁用中断的局部变量secs中,以确保在发生这种情况时,rtc中断不会更新secs。然后,它计算变量小时和分钟的值。
第一个for循环闪烁前一个5分钟标记,第二个for循环根据两个标记之间的时间位置闪烁下一个5分钟标记,总共闪烁5次。
如上所述,Mega Tmall Time Watch通过点亮下一个小时数来显示晚于半小时的时间;这是通过小时计算中的校正+1800实现的。
在GitHub上使用Spence Konde的megaTiny Core编译该程序。选择主板菜单上MeggaTinyCore标题下的ATtiny1614/1604/814/804/441/404/241/204选项。检查后续选项是否按如下方式设置(忽略任何其他选项):
然后使用UPDI编程器将程序上传到手表电路。我使用了我的UPDI编程器棒;或者,您也可以从Arduino Uno或其他基于ATmega328P的电路板上制作UPDI编程器,如制作UPDI编程器中所述。UPDI引脚被带到PCB前面的一个小焊盘上,我在编程电路板时将UPDI线缆与此保持接触,以避免需要永久焊接导线。
从giHub获取印制电路板的EAGLE文件,这样您就可以在以下网址为自己制作电路板:https://github.com/technoblogy/mega-tiny-time-watch.
此设计中有四个未使用的I/O引脚,因此您可以扩展手表以添加其他功能。例如,您可以添加AM/PM LED或其他按钮来选择其他模式,如秒表、日期显示或使用压电蜂鸣器的闹钟。
由Disqus提供支持的博客评论