[讨论] DIY一个用LED做的怀表

fish001   2009-11-11 13:51 楼主
来源:PIC16 lxyppc


用LED实现的一个怀表
http://www.eng.yale.edu/pep5/pocket_watch.html

简单的翻译一下技术部分

Clock Face and Circuit
I had the idea of using LEDs to represent the position of the hands, although I've since learned that it's been implemented in larger clocks before, but I've yet to find an example this small. There are 133 surface mount LEDs on the front face, all hand-soldered: 60 for seconds, 60 for minutes, 12 for hours and one for charging status. The great challenge was fitting all the 0603 LEDs and mosfets on the front dial; obviously, surface mount components were a must. The PCB is 44mm across.

我想到了用LED来显示表的指针,有人做过大尺寸的,但是没人做过小的。我用了133个表贴的LED,全是手工焊接的。60个做为秒针,60个做分针,12个做时针,还有一个用来表示充电的情况。最麻烦的就是焊这些0603的LED和三极管(mosfet?)。 PCB直径为44mm。

To reduce the pin count, each set of 60 LEDs is divided up into six banks of ten, selected by a mosfet. Long circular tracks run around the outside of the board, connecting one LED from each bank to a drive pin. Each hour pin is controlled invidually. Everything is controlled by a PIC 16F946 - an 8-bit micro with plenty of IO pins. The spiralling via pattern this produces is quite aesthetically pleasing.

为了减少使用的IO,60颗灯分为6组,用mosfet来进行组选。时针的LED是用IO单独控制的.所有的这些都是用PIC 16F946实现的。布线产生的螺旋线非常好看。(布线也是一种艺术 )

1.jpg

2.jpg

3.jpg

4.jpg


The clock face itself includes hour marks and Roman numerals for reading the time, as well as pin number markers on the centre IC. The outer-most LED ring is red for seconds, the middle is blue for minutes and the inner-most is blue for hours - this is in line with the standard relative lengths of clock hands. Underneath the chip is stenciled a tiger eye (a subtle reference to my grandfather's long-time association with the Freemasons). The watch is watching you!
表盘有罗马数字读数和IC的引脚数字。最外层红色LED是秒针,中间蓝色的是分针,最里蓝色的是时针。和传统的钟表一样。


On the reverse side is room for all the power regulation, charging, programing, precision timing and IO hardware. The watch runs off a single-cell 110 mAh Lipo cell, recharged through a jack underneath the back cover. The bottom side also has pads for a Temperature Controlled Crystal Oscillator (TCXO) capable of 1 part per million timing precision, although this hasn't been tested yet. A cell-phone vibrator is attached to the inside back cover; every second the vibrator pulses, using the cover as a sounding board to produce an audible 'tick'. The vibrator also serves as an alarm buzzer.
表的背面放着电源转换模块,充电模块,编程模块,时间调整等硬件模块。这块表由一个100mAh的锂电池供电,通过背后的插孔充电。底部还有一个1ppm的温度补偿晶体振荡器的焊盘,但是还没有测试过。后盖中有一个手机的振动电机,每一秒都会被通上电,用来模拟秒针的滴答声。同时这个振动电机也做为闹钟的蜂鸣器。

11.jpg

12.jpg

13.jpg

14.jpg


User Interface and Programming

The watch is controlled by an encoder and two switches. The encoder (from Alps - the tiniest I have ever seen) sits in the slot milled in the PCB, and connects to the stem via a hexagonal channel sized to fit its square end, milled by hand. When the crown is depressed, the stem slides through the encoder and pushes a rotating disc against a switch. A collet and grub-screw hold the stem in place. The second switch is positioned behind the time-set button, although it has not yet been implemented.
这只表由一个编码器和两个开关控制。编码器(来自Alps-我见过的最小的)放在PCB的槽中,通过六角管道连在表柄上。当表冠按下时,表柄会触发一个开关。另一个开关在时间设置开关旁,虽然还没有实现。

The microprocessor was programmed in C using the MikroC IDE for 8-bit PIC microprocessors. Unfortunately, I only have the demo version of this software, which has a 2KB code-size limit. By far the hardest part of this project was writing my code to fit within this limitation; almost without exception, though, it made my code better, more streamlined and more elegant. I can easily say it's made me a better coder.
芯片代码用C语言编写,在MikroC中开发。我只有2KB限制版本,所以花了很多功夫去写代码满足这个要求。这使我的代码更优秀,更精简,更美观。

Most of the time, the watch will be in standby mode, with the LEDs turned off to save power. When the crown is pressed to open the case the stem switch is also depressed, bringing the watch out of standby and into display mode. The LEDs will progress each second, and when a minute or hour is incremented, a the LEDs will light through 360 degrees to advance to the next position, producing a decorative swirl. After 15 seconds of display, the LEDs will turn off and the watch will go back into standby mode.
大部分时间表工作在待机模式,为减少耗电所有的LED都会关掉。当用按下表冠去打开表盖时,连在表柄上的开关也会按下,这会让表从待机模式退出工作在显示模式下。LED在每秒会更新。当分钟或小时更新时,LED会绕一圈后更新到下一个位置,产生一个旋涡的特效。15秒后会自动进入待机模式。

To set the time, the stem is depressed and held in for 3 seconds to enter programming mode. The second hand will then stop incrementing and be set to the 12 o'clock position. Twisting the crown will cause the minute and hour hands to increment or decrement. Pressing the button for another second will exit programming mode.
时间设置:按下表柄3秒进入设置模式。此时秒针会停在12点处。转动表冠可以增加或减少时针和分针。再按一次会退出设置模式。

The pocketwatch also has an alarm function. The alarm is set by holding depressing the stem for 1.5 seconds, whereupon the LEDs will begin to blink. Setting the alarm is handled identically to setting the time. Entering alarm mode sets the alarm on; it will stay on until programming mode is entered, or until the alarm rings. When the alarm goes off, the vibrator inside will pulse in time with flashing LEDs displaying the time.
这块怀表还有闹钟的功能。按住表柄1.5秒进入闹钟设置模式,这些LED会开始闪烁。与设置时间相同。闹钟会被打开直到再次进入闹钟设置模式或是闹钟响起。当闹钟响的时候,表会振动,LED会闪烁。

111.jpg

112.jpg

113.jpg


The LEDs are very bright at night - so much so that they can be dazzling when you turn the watch on. To avoid this, an optical sensor was placed on the front dial. In display mode, the sensor reads the ambient light level and dims the lights if it's too dark, or brightens them if it's light. This way, the display is comfortably readable even in complete darkness or direct sunlight.
LED在晚上很亮,以致于会看不清时间。为避免这种情况,前面板装了一个光学传感器。在显示时,传感器先得到前环境光亮度,如果环境光太暗就减弱LED的亮度,如果环境光很强就增加LED亮度。这样无论在黑夜或是阳光下都能清楚的看见时间。

[ 本帖最后由 fish001 于 2009-11-11 14:01 编辑 ]

回复评论 (22)

来源:PIC16 lxyppc


可能是文中提到的带开关的编码器
http://www.alps.com/WebObjects/catalog.woa/E/HTML/Encoder/Automotive/EC09E/EC09E1524405.html

在上海北京东路的店铺
http://item.taobao.com/auction/item_detail-0db1-ed4afb05285acc33c0b762dda20486c9.htm?cm_cat=0

1.jpg

2.jpg


怀表仿真
用了一个编码器和按钮开关来模拟输入
开发环境   MPLAB    8.40
编译环境   PICC     9.65
仿真       Protues  7.1 SP4
器件       PIC16F916
用了一个开关和一个编码器来模拟输入
按下开关进行设置模式与正常工作模式的切换
在设置模式下,编码器的转动方向控制的分针转动方向

111.jpg

22222.jpg


LED连线如下
任一时刻,12个IO只中有两个为输出,其余为高阻输入,以此控制60+60+12个LED
连线说明
R1A -> R Red红色 1 1号LED  A LED的正极,B表示负极

333.jpg
点赞  2009-11-11 13:57
仿真代码:

main.c



  1. #include
  2. #include "LedDriver.h"

  3. // Device configuration
  4. __CONFIG(UNPROTECT& WDTDIS & MCLREN &  
  5. DEBUGDIS & UNPROTECT & UNPROTECT & BORDIS & PWRTEN & INTIO);

  6. void InitDevice();

  7. #define     MODE_TIME       0x01
  8. #define     MODE_SET        0x02
  9. #define     MODE_DBG        0x04
  10. unsigned char displayMode;

  11. unsigned char second;
  12. unsigned char minute;
  13. unsigned char hour;
  14. unsigned char hideSecond;
  15. unsigned char keyCntTmp;
  16. unsigned char keyCnt;

  17. extern const unsigned char dirTable[16];
  18. unsigned char encValue;
  19. // 正转
  20. #define     POS     1
  21. // 反转
  22. #define     NEG     2
  23. // 无效
  24. #define     NAC     0

  25. /// 中断处理函数
  26. void interrupt ISR()
  27. {
  28.     if(T0IF){
  29.         // Timer0 中断 约8ms产生一次,用来刷新显示,检测按键
  30.         T0IF=0;
  31.         TMR0 = 0xC0;
  32.         if(displayMode & MODE_TIME){
  33.             // 刷新显示
  34.             static unsigned char scanIndex = 0;
  35.             TRISC = (unsigned char)dispBuffer[scanIndex].dirData;
  36.             TRISA = (unsigned char)(dispBuffer[scanIndex].dirData>>8);
  37.             PORTC = (unsigned char)dispBuffer[scanIndex].ioData;
  38.             PORTA = (unsigned char)(dispBuffer[scanIndex].ioData>>8);
  39.             scanIndex++;
  40.             if(scanIndex >=3)scanIndex = hideSecond;
  41.         }
  42.         if(RB1){
  43.             // 检测按键
  44.             if(keyCntTmp){
  45.                 keyCnt = keyCntTmp;
  46.             }
  47.             keyCntTmp = 0;
  48.         }else{
  49.             if(keyCntTmp & 0x80){
  50.             }else{
  51.                 keyCntTmp++;
  52.             }
  53.         }
  54.     }
  55.     if(TMR1IF){
  56.         // Timer1 中断 1s产生一次,用来刷新显示,检测按键
  57.         TMR1IF = 0;
  58.         TMR1H|=0x80;
  59.         if(displayMode & MODE_SET){
  60.             //  闪烁秒针
  61.             hideSecond = (hideSecond+1) & 1;
  62.             second = 0;
  63.             FormatSecond(second);
  64.         }else{
  65.             //  更新时间
  66.             second++;
  67.             if(second >= 60){
  68.                 second = 0;
  69.                 minute++;
  70.                 if(minute >= 60){
  71.                     minute = 0;
  72.                     hour++;
  73.                     if(hour >= 12){
  74.                         hour = 0;
  75.                     }
  76.                     FormatHour(hour);
  77.                 }
  78.                 FormatMinute(minute);
  79.             }
  80.             FormatSecond(second);
  81.         }
  82.     }
  83.     if(RBIF){
  84.         // RB6,RB7输入变化中断,检测编码器输入
  85.         unsigned char encDir;
  86.         encValue |= (PORTB & 0b11000000);
  87.         RBIF = 0;
  88.         if(displayMode & MODE_SET){
  89.             // 检测编码器位置
  90.             encDir = dirTable[encValue>>4];
  91.             encValue>>=2;
  92.             if(encDir & POS){
  93.                 //  编码器顺时针旋转
  94.                 minute++;
  95.                 if(minute >= 60){
  96.                     minute = 0;
  97.                     hour++;
  98.                     if(hour >= 12){
  99.                         hour = 0;
  100.                     }
  101.                     FormatHour(hour);
  102.                 }
  103.                 FormatMinute(minute);
  104.             }else if(encDir & NEG){
  105.                 //  编码器逆时针旋转
  106.                 /// Negative rotate
  107.                 if(minute == 0){
  108.                     minute = 60;
  109.                     if(hour == 0){
  110.                         hour = 12;
  111.                     }
  112.                     hour--;
  113.                     FormatHour(hour);
  114.                 }
  115.                 minute--;
  116.                 FormatMinute(minute);
  117.             }
  118.         }
  119.     }
  120. }

  121. void main()
  122. {
  123.     // 变量初始化
  124.     displayMode = MODE_TIME;
  125.     second = 0;
  126.     minute = 0;
  127.     hour = 0;
  128.     hideSecond = 0;
  129.     FormatHour(hour);
  130.     FormatMinute(minute);
  131.     FormatSecond(second);
  132.     keyCntTmp = 0;
  133.     keyCnt = 0;
  134.     // 器件初始化
  135.     InitDevice();
  136.     while(1){
  137.         if(keyCnt>10){
  138.             // 如果有按键按下,切换工作模式
  139.             keyCnt = 0;
  140.             if(displayMode & MODE_SET){
  141.                 displayMode &= (~MODE_SET);
  142.             }else{
  143.                 displayMode |= MODE_SET;
  144.             }     
  145.         }
  146.     }
  147. }

  148. // Initial device
  149. void InitDevice()
  150. {
  151.     // Internal 8MHz clock
  152.     OSCCON = 0b01110001;
  153.      
  154.     //1:256 Timer0
  155.     OPTION= 0B00000111; // enable RB wake pull up
  156.     // 5ms = 5000us 256*20 = 5120  256-20=236;
  157.     TMR0=236;
  158.      
  159.     // Config PORTC, all output
  160.     TRISC = 0b0000000;
  161.     PORTC = 0x00;
  162.     LCDCON = 0;
  163.      
  164.     // Config PORTB, <7:0> input, wake pull up enable
  165.     PORTB = 0;
  166.     TRISB = 0xFF;
  167.     WPUB = 0xFF;
  168.     IOCB = 0b11000000; // <7:6> interrupt on change enable
  169.     RBIE = 1; // Enable interrupt on change of PORTB
  170.      
  171.     // Config PORTA
  172.     CMCON0 = 0x07;
  173.     ANSEL = 0x03;  // <1:0> set as analog input
  174.     PORTA = 0;
  175.     TRISA = 0xFF;
  176.      
  177.     //Enable GI, Enable TMR0 Int, PEIE enable
  178.     INTCON=0B11100000;
  179.     //Enable recive Int
  180.     //Set TMR1
  181.     T1CON=0B00001111;
  182.     //Enable TMR1 Int
  183.     TMR1H|=0x80;
  184.     TMR1IE=1;
  185. }

  186. // Direction table for the encoder
  187. /**
  188.   Encoder output format:
  189.    Clockwise:           00 01 11 10 00 01 11 10 ...
  190.    counterClockwise:    00 10 11 01 00 10 11 01 ...
  191. */
  192. const unsigned char dirTable[16] =  
  193. {
  194.         NAC,    // 0000
  195.         NEG,    // 0001
  196.         POS,    // 0010
  197.         NAC,    // 0011
  198.         POS,    // 0100
  199.         NAC,    // 0101
  200.         NAC,    // 0110
  201.         NEG,    // 0111
  202.         NEG,    // 1000
  203.         NAC,    // 1001
  204.         NAC,    // 1010
  205.         POS,    // 1011
  206.         NAC,    // 1100
  207.         POS,    // 1101
  208.         NEG,    // 1110
  209.         NAC,    // 1111
  210. };


点赞  2009-11-11 13:58
LedDriver.h



  1. #ifndef LEDDRIVER_H
  2. #define LEDDRIVER_H

  3. typedef struct _DisplayData
  4. {
  5.     unsigned short dirData; // io direction data
  6.     unsigned short ioData;  // io data
  7. }DisplayData, *PDisplayData;

  8. void    TurnOnLed(unsigned char ledIndex);
  9. void    FormatSecond(unsigned char second);
  10. void    FormatMinute(unsigned char minute);
  11. void    FormatHour(unsigned char hour);

  12. #define     dispSecond      dispBuffer[0]
  13. #define     dispMinute      dispBuffer[1]
  14. #define     dispHour        dispBuffer[2]

  15. extern  DisplayData     dispBuffer[10];

  16. #endif




文件 LedDriver.c



  1. #include
  2. #include "LedDriver.h"
  3. // Led matrix dirver
  4. #define     CP1             1
  5. #define     CP2             2
  6. #define     CP3             4
  7. #define     CP4             8
  8. #define     CP5             16
  9. #define     CP6             32
  10. #define     CP7             64
  11. #define     CP8             128
  12. #define     CP9             (4<<8)
  13. #define     CP10            (8<<8)
  14. #define     CP11            (16<<8)
  15. #define     CP12            (32<<8)

  16. #define     LED_MASK        (CP1|CP2|CP3|CP4|CP5|CP6|CP7|CP8|CP9|CP10|CP11|CP12)
  17. #define     SetP1Dir(x)     TRISC = (x)
  18. #define     SetP2Dir(x)     TRISA = (x)
  19. #define     LED_PORT1       PORTC
  20. #define     LED_PORT2       PORTA

  21. const unsigned short Connection[] =
  22. {
  23.     0,CP1,CP2,CP3,CP4,CP5,CP6,CP7,CP8,CP9,CP10,CP11,CP12
  24. };

  25. const unsigned char LightTable[] =  
  26. {
  27.     0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,
  28.     0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,
  29.     0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,
  30.     0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,
  31.     0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,
  32.     0x67,0x68,0x69,0x6a,0x6b,0x6c,
  33.     0x78,0x79,0x7a,0x7b,0x7c,
  34.     0x89,0x8a,0x8b,0x8c,
  35.     0x9a,0x9b,0x9c,
  36.     0xab,0xac,
  37.     0xbc
  38. };

  39. // Zero based led index
  40. unsigned char  index;
  41. unsigned short  type,type0,type1;
  42. unsigned char  ledPort1;
  43. unsigned char  ledPort2;
  44. unsigned char  IOs;

  45. // Turn on the specify led
  46. void    TurnOnLed(unsigned char ledIndex)
  47. {
  48.     index = ledIndex>>1;
  49.     IOs = LightTable[index];
  50.     type0 = Connection[IOs&0xF];
  51.     type1 = Connection[IOs>>4];
  52.     type = type0 | type1;
  53.     ledPort1 = (unsigned char)(LED_MASK ^ type);
  54.     ledPort2 = (unsigned char)((LED_MASK ^ type)>>8);
  55.     SetP1Dir(ledPort1);
  56.     SetP2Dir(ledPort2);
  57.     if(ledIndex&1){
  58.         LED_PORT1 = (unsigned char)type0;
  59.         LED_PORT2 = (unsigned char)(type0>>8);
  60.     }else{
  61.         LED_PORT1 = (unsigned char)type1;
  62.         LED_PORT2 = (unsigned char)(type1>>8);
  63.     }
  64. }

  65. DisplayData     dispBuffer[10];
  66. void    FormatSecond(unsigned char ledIndex)
  67. {
  68.     index = ledIndex>>1;
  69.     IOs = LightTable[index];
  70.     type0 = Connection[IOs&0xF];
  71.     type1 = Connection[IOs>>4];
  72.     dispSecond.dirData = LED_MASK ^ (type0 | type1);
  73.     if(ledIndex&1){
  74.         dispSecond.ioData = type0;
  75.     }else{
  76.         dispSecond.ioData = type1;
  77.     }
  78. }
  79. void    FormatMinute(unsigned char ledIndex)
  80. {
  81.     ledIndex+=60;
  82.     index = ledIndex>>1;
  83.     IOs = LightTable[index];
  84.     type0 = Connection[IOs&0xF];
  85.     type1 = Connection[IOs>>4];
  86.     dispMinute.dirData = LED_MASK ^ (type0 | type1);
  87.     if(ledIndex&1){
  88.         dispMinute.ioData = type0;
  89.     }else{
  90.         dispMinute.ioData = type1;
  91.     }
  92. }
  93. void    FormatHour(unsigned char ledIndex)
  94. {
  95.     ledIndex+=120;
  96.     index = ledIndex>>1;
  97.     IOs = LightTable[index];
  98.     type0 = Connection[IOs&0xF];
  99.     type1 = Connection[IOs>>4];
  100.     dispHour.dirData = LED_MASK ^ (type0 | type1);
  101.     if(ledIndex&1){
  102.         dispHour.ioData = type0;
  103.     }else{
  104.         dispHour.ioData = type1;
  105.     }
  106. }




2009102322295343001.zip (49.79 KB)
(下载次数: 58, 2009-11-11 13:59 上传)
点赞  2009-11-11 13:59

回复 4楼 fish001 的帖子

看起来很好玩~~~就是不知道成本多少?
点赞  2009-11-11 14:11
哈哈哈!!好啊!!!来个DIY如何?
点赞  2009-11-11 14:21
看起来不错 像个艺术品 作为礼物相当有新意
点赞  2009-11-11 14:26
看起来很好玩~~~作为礼物相当有新意
点赞  2009-11-12 10:27
看起来很好玩
点赞  2009-11-14 23:46
呵呵,高手啊,学习了
不过这个怀表比较耗电吧
点赞  2009-11-15 08:46
很有创意,很有艺术感
点赞  2009-11-17 21:26
强大。支持这样的作品,好羡慕啊
点赞  2009-11-19 23:05
看来楼主很有思想
点赞  2009-11-20 21:26
看来楼主很有思想
点赞  2009-11-20 21:30
100MAH 的电用不了多久吧?要是像手机似的,几天就要充电那就没意思了!
点赞  2009-11-20 22:58
高手啊  顶一下!!!!!!!!!
点赞  2009-11-24 12:10
漂亮
点赞  2009-11-28 13:17
在ourdev上有人做出来了。。
点赞  2009-11-29 11:34
很好,很强大。就是光比较散,散光眼的人会看不清,如果是指一个表盘,让光精确得到一点,就更好了。。。
点赞  2009-11-30 10:42
这个想法不错,就是不太美观,如果在PCB上加一个有标度的不锈钢罩就完美了,那个显示时的LED应该不要正面发光,铡面发光最好.
点赞  2009-12-15 12:56
12下一页
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复