$Id: a07cf90837a3c4373b82d6724b97593810766af7 $
This post is part of a series on my quest to find the perfect watch.
Take a look at this watch, it’s just some boring watch for runners, right?
Nope, I think this might be the best ultra-low power consumer digital watch ever produced!
Let me explain…
This is the Timex m851. It uses an 8-bit Seiko SC188 CPU, has 48KB of ROM, 2KB of RAM and a 42x11 dot matrix main display.
The cpu is designed for ultra-low power operation - a single battery can last 3 years!
This is a big selling point for me, I don’t think they’re making consumer watches like this any more.
Those pins on the side? That’s USB. Naturally, you can synchronize things like appointments, and so on (there’s a neat linux library for that).
Here’s where it get’s interesting… this thing has an SDK, and it is surprisingly good!
You can just $ cc helloworld.c
, and upload it to your watch.
Timex provided a Windows XP-era wizard to develop applications, and it is painful. However, it turned out that was just driving a UNIX-like toolchain behind the scenes. After pulling that out, hacking this started to become pretty fun!
Just want to see some code? No problem, I’ve uploaded a hello world and a Makefile
to build it to github:
https://github.com/taviso/timex/
If you want to browse the full manual, there are some links below.
You can do some fun things with this, people wrote all kinds of games, utilities, and tools.
If you want a TL;DR of development, I’ll try!
You need to split your application into states.
A state is just a way to swap code in and out as needed. When you switch states, the active code is discarded and the new code is swapped in.
You could write an app that uses up to ~30kb of code and/or data, but there is only ~2kb of RAM. This is why you need to use states, there is no paging and you couldn’t fit that in!
There is room for some common code that all your states share, and of course there is space for variables, along with a database API for persistent data.
The first few states are reserved for handling common events. The rest you can use for anything you want - you just ask the kernel to switch you whenever you need it!
if (foo > bar) {
coreRequestStateChange(FOOBAR_STATE); }
A mode is just a foreground application.
Your app is (probably) providing a mode, but you can also add background tasks, periodic tasks, and so on.
Requesting a mode change is sort of like exit()
, the next app will gain control.
coreRequestModeChangeNext();
Each state needs an event handler. A super simple one would be like this:
void default_state_manager(void)
{switch (CORECurrentEvent) {
// This is called when this state becomes active.
case COREEVENT_STATEENTRY:
coreEnableSwitchReleaseEvents();
coreAllowKeys(COREALLOWALLSWITCHES);break;
// This is called when the user pulls the crown out.
// When they push it back in, you get a CROWN_HOME event.
// It can be rotated in either home or set positions.
case COREEVENT_CROWN_SET:
coreRequestStateChange(CORESETBANNERSTATE);break;
// You can get button events too, like start/split:
case COREEVENT_STARTSPLITDEPRESS:
show_message();break;
// The mode button probably means the user wants to exit...
case COREEVENT_MODEDEPRESS:
coreRequestModeChangeNext();break;
}
return;
}
In general, you can ignore any event you don’t care about.
The kernel takes care of handling the hardware, dispatching events to you and provides various services.
Things like timers, generating tones (beeps), scrolling the display, access to database records, etc.
uint8_t banner[] = {
LCDBANNER_COL10,
DM5_H, DM5_E, DM5_L, DM5_L, DM5_O, DM5_EXCLAMATION,
LCD_END_BANNER
};
lcdClearDisplay();
// Draw "hello!" using kernel services.
lcdDispBannerMsg(&banner);
The watch has a crown - and you can ask the kernel to configure it in a few different ways. For example, you might want pulse mode so you can monitor how quickly the user is turning it.
It has a backlight, you can control that from software too.
There’s no memory protection or anything, you can just clobber the kernel if you want… so what happens if you crash?
Well, one thing the kernel does is notify a watchdog every 2 seconds that it’s still alive. If the watchdog doesn’t see that notification, it will just reset the watch… so try not to do that! 😀
If you’re doing some heavy computation you could accidentally trigger the watchdog. Just remember to let the watchdog know you’re still alive:
// Let the watchdog know we're not dead.
hwResetWatchdog();
Luckily there’s a really good simulator available, so you don’t have to sit through tedious resets everytime you make a typo. It’s actually an open source 3rd party tool, Virtual Datalink (source).
Sadly it’s for Windows only. I would port it to Linux, but it’s written in Delphi… is it feasible to port it to free pascal? Let me know!
It has conditional breakpoints, a dissasembler, save states, resource and power analysis. I’ve found it pretty capable.
The main app I’m working on right now is integrating my watch with remind.
Hacking this little watch has been a super fun project!
Now that I’ve told you how much I love this thing - the bad news.
They’re long out of production, and getting hard to come by.
I bought a set of two on eBay, they just needed new batteries and were as good as new.
If this is the sort of thing that appeals to you, setup an alert and then help me hack on it 😀