这是基于ATtiny85系列的简约手表系列中的第三款。该款手表通过在微型64x48 OLED显示屏上绘制模拟的手表来显示时间。它使用独立的晶振控制的低功耗RTC芯片来保持每月几秒钟的时间,并在不显示时间的时候将处理器和显示器置于睡眠状态,以便使得使用寿命超过一年。
当按下手表表面上的按钮时会显示时间,并且会在显示屏上显示模拟的手表,并且带有一个移动的秒针。 30秒后显示屏将自动变暗,以保持电池的使用寿命。
简介
这款手表基于Maxim Integrated的DS2417 RTC芯片,该芯片采用一个小型6脚封装,使用32.768 kHz晶振来保持精确的时间。它可以通过1线接口与主控制器ATtiny85进行通信,该接口仅使用一个I/O引脚来发送和接收数据。因为RTC芯片主要工作是计时,ATtiny85在其不需要实际显示时间时可以在掉电模式下保持睡眠,从而大大降低了功耗。
该显示器使用一个SPI接口的小型单色64x48 OLED显示屏,具有可从Aliexpress购买,或可从Sparkfun购买类似的。显示器需要4个引脚驱动,ATtiny85刚好满足应用要求,剩余一个引脚用于1-Wire接口。您不能读取显示内存,因此要做图形,您需要写入RAM中的缓冲区,然后将其复制到显示器上。因为显示器是64x48像素,因此它需要68x48 / 8或384字节的存储器用于图形缓冲区,这也刚好在ATtiny85的能力之内。
现在显示屏变暗的总功耗约为8μA,单个CR2016电池估计超过一年的电池寿命。
电路
以下是Tiny Face手表的电路:
晶振是标准的32.768 kHz石英表晶体; Maxell指定6pf负载电容,我选择了一个MS1V-T1K晶振,其优点是可以焊接到板上,但我预计任何手表晶振都是可以的。
按钮采用的是广泛使用的微型SMD触觉开关,通常用作处理器电路板上的复位按钮。 使用了一个33kΩ电阻和0.1μF电容,确保在首次施加电源时显示屏能正常复位。
电池是一个20毫米的纽扣电池。鉴于电流消耗低,我决定使用更细小的CR2016电池,并在Mouser找到了合适的SMD电池座。或者,您可以使用CR2032电池,可以从Sparkfun购买SMD 20mm电池座。
表带需要是12mm的螺纹穿透型,我发现了一个德国的供应商。我也尝试过英国的供应商这种比较便宜的替代品,但是请注意这个会更短,如果你手腕比较小,这个表带可能不适合。
装配
对于这个项目,我使用了Seeed Studio的PCB服务,并选择蓝色PCB来匹配显示。这是布局:
手表使用SMD组件,所有组件与电池座分开焊接到电路板前面。我使用SOIC ATtiny85和0805电阻和电容,所以他们应该比较容易用手焊接。 DS2417 RTC芯片采用TSOC封装,可能是最难焊接的元件,因为它的引脚位于封装下面。
我在250°C使用Youyue 858D +热风枪将SMD组件焊接到电路板的前面,然后使用传统的烙铁将电池座焊接到电路板背面。如果没有热风枪,您可以使用细小的烙铁焊接SMD组件。
以下照片显示了安装显示屏前电路板的前面,安装显示屏的电路板和电路板的背面:
我建议在将显示器安装在电路板上之前先测试电路板。您可以通过显示器的7针连接器访问除RST之外的ISP编程所需的所有ATtiny85引脚。
程序
本节将介绍Tiny Face Watch程序的各个部分。
1-Wire接口
为了与RTC进行通信,我使用了简单的1线接口。它将RTC中的五个字节数据读入数组DataBytes [5]。第一个字节是一个配置字节,最后四个字节给出一个长整数的秒数;为了更容易理解,我定义了一个联合体,所以时间字节可以使用类似rtc.seconds进行访问:
static union {
uint8_t DataBytes[5];
struct {
uint8_t control;
long seconds;
} rtc;
};
显示
显示采用的是OLED。时钟表面是使用图形命令绘制线,并且这些都编辑一个缓冲区,该缓冲器为显示器上的每个像素存储一个位。这定义如下:
// Screen buffer
const int Buffersize = 64*6;
unsigned char Buffer[Buffersize];
一旦将时钟面绘制到缓冲区中,DisplayBuffer()函数将字节复制到显示器中即可显示缓冲区的内容:
void DisplayBuffer () {
PINB = 1<
// Set column address range
Command(0x21); Command(32); Command(95);
// Set page address range
Command(0x22); Command(2); Command(7);
for (int i = 0 ; i < Buffersize; i++) Data(Buffer[i]);
PINB = 1<
}
SSD1306可以处理高达128x64的显示,64x48显示位于该区域的中心,因此要解决此问题,您需要选择第32至95列(含)和第2至7页(含)。
画时钟
DrawClock()函数绘制时钟面和表针位置指定的时间,以小时、分钟和秒为单位。使用了’几个技巧来避免需要三角函数,并最小化所需的乘法和除法次数。该程序基本上执行以下迭代程序360次以生成圆上的点:
x = x + Delta * y;
y = y-Delta * x;
其中Delta为弧度1度。使用定点运算通过存储它们乘以因子2 ^ 9来计算x和y的值
void DrawClock (int hour, int minute, int second) {
int x = 0; int y = 23<<9;
for (int i=0; i<360; i++) {
int x9 = x>>9; int y9 = y>>9;
DrawTo(x9, y9);
// Hour marks
if (i%30 == 0) {
MoveTo(x9 - (x9>>3), y9 - (y9>>3));
DrawTo(x9, y9);
}
// Hour hand
if (i == hour * 30 + (minute>>1))
DrawHand(x9 - (x9>>2), y9 - (y9>>2));
// Minute hand
if (i == minute * 6 + second/10) DrawHand(x9, y9);
// Second hand
if (i == second * 6) {
MoveTo(0, 0);
DrawTo(x9, y9);
}
// Border of clock
MoveTo(x9, y9);
// if (x9 > 0) DrawTo(23, y9); else DrawTo (-23, y9);
x = x + (y9 * Delta);
y = y - ((x>>9) * Delta);
}
}
它调用DrawHand()将绘制菱形小时和分针从0、0绘制到x,y:
void DrawHand (int x, int y) {
int v = x/2; int u = y/2;
int w = v/5; int t = u/5;
MoveTo(0, 0);
DrawTo(v-t, u+w);
DrawTo(x, y);
DrawTo(v+t, u-w);
DrawTo(0, 0);
}
线绘制由DrawTo()线绘图程序执行,它使用Bresenham线算法在两点之间画出最佳线,而不需要任何分割或乘法:
void DrawTo (int x1, int y1) {
int sx, sy, e2, err;
int dx = abs(x1 - x0);
int dy = abs(y1 - y0);
if (x0 < x1) sx = 1; else sx = -1;
if (y0 < y1) sy = 1; else sy = -1;
err = dx - dy;
for (;;) {
PlotPoint(x0, y0);
if (x0==x1 && y0==y1) return;
e2 = err<<1;
if (e2 > -dy) {
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx) {
err = err + dx;
y0 = y0 + sy;
}
}
}
设定时间
当您首次向手表供电时,它将检查MCUSR寄存器以检测上电复位,并运行SetTime(),以允许您将时间设置为最接近的秒数。它的工作原理如下:
等待当前时间是一分钟,然后插入电池。手表将从12:00开始,一次逐步显示一分钟。当手表显示插入电池的时间(而不是当前时间)时,按复位按钮。手表将占用您设置时间的额外秒数,并显示当前时间。它现在可以使用了。
SetTime()例程将变量secs递增300,相当于五分钟,显示步骤之间的秒数。在每个步骤中,它将值的值写入RTC,其中添加了一个偏移量,用于计算自启动过程起经过的时间。
void SetTime () {
unsigned long Offset = millis();
unsigned long secs = 0;
for (;;) {
int Mins = (unsigned long)(secs / 60) % 60;
int Hours = (unsigned long)(secs / 3600) % 12;
// Write time to RTC
rtc.control = 0x0C;
rtc.seconds = secs + ((millis()-Offset)/1000);
OneWireReset();
OneWireWrite(SkipROM);
OneWireWrite(WriteClock);
OneWireWriteBytes(5);
ClearBuffer();
DrawClock(Hours, Mins, -1);
DisplayBuffer();
unsigned long Start = millis();
while (millis()-Start < 500);
secs = secs + 60;
}
}
显示时间
ATtiny85通常处于睡眠模式,可忽略不计的电流。按钮连接到复位输入端口,该引脚唤醒处理器并产生复位。
主程序loop()首先读取RTC的时间到变量secs:
OneWireReset();
OneWireWrite(SkipROM);
OneWireWrite(ReadClock);
OneWireReadBytes(5);
OneWireReset();
secs = rtc.seconds;
然后计算变量Hours、Mins和Secs的值,并显示一个动画时钟,直到SleepTime过去(30秒):
int Mins = (unsigned long)(secs / 60) % 60;
int Hours = (unsigned long)(secs / 3600) % 12;
int Secs = secs % 60;
unsigned long Start = millis();
unsigned long Now = Start;
while (Now-Start < SleepTime) {
ClearBuffer();
DrawClock(Hours, Mins, (Secs + (Now-Start)/1000) % 60);
DisplayBuffer();
Now = millis();
}
DisplayOff();
digitalWrite(dc,HIGH);
digitalWrite(clk,HIGH);
digitalWrite(data,HIGH);
digitalWrite(cs,HIGH);
pinMode(OneWirePin, OUTPUT); digitalWrite(OneWirePin,HIGH);
sleep_enable();
sleep_cpu();
然后熄灭显示屏,并将处理器设置成睡眠状态以最小化功耗。
其他选项
时钟面只占据显示屏中间的48x48像素,因此在显示屏的每一侧有两个8x48像素的区域,可用于显示其他信息。例如,您可以使用ATtiny85上的温度传感器来测量环境温度,并以图形显示方式将其显示在显示屏的一边。
编译程序
我使用Spence Konde的ATTiny Core编译了这个程序。在Boards菜单的ATtiny Universal标题下选择ATtinyx5 series选项。然后在子菜单下选择Timer 1 Clock: CPU, B.O.D. Disabled, ATtiny85, 8 MHz (internal)。
我使用Sparkfun的Tiny AVR编程板编程ATtiny85。选择Burn Bootloader设置合适的保险丝位,然后上传程序
上一篇:基于ATtiny841通过I2C总线连接GPS模块
下一篇:使用AVR微控制器控制GSM模块实现发送和接收短信
推荐阅读最新更新时间:2024-11-11 11:23
设计资源 培训 开发板 精华推荐
- TWR-P1025: QorIQ® P1 MPU塔式系统模块
- LTC2938CDE 电源和温度监视器的典型应用电路
- STM32F030C8T6最小系统板和流水灯
- 使用 Richtek Technology Corporation 的 RT8152D 的参考设计
- USB键盘计算器
- 双typeC转TTL模块
- LTC4223-2 演示板,用于 AMC 的双电源热插拔控制器(故障后自动重试)
- LTC3110HUF 2A 双向降压-升压型 DC/DC 稳压器的典型应用电路
- Tuya红外语音助手-立创涂鸦物联网实战营
- MIC94355-MYMT 2.8V 输出电压、500mA LDO 采用纹波阻断技术的典型应用