本翻译仅旨在帮助中文用户了解Sensor Watch项目,非广告。如需购买,请前往:CrowdSupply网站Sensor Watch项目网页

由于众所周知的全球芯片紧缺,最新预计2023年5月31日才能发货了。

www.peterguo.net 2022年4月23日

Sensor Watch

一款可黑改经典卡西欧腕表的 ARM Cortex M0+ 电路板

2022年1月27日

第三周:编写一个新表盘程序 & 又一次骑行

-- Joey Castillo 乔伊·卡斯蒂略

本周文章的计划原本是,通过从零开始为Movement固件程序创建一个有用的手表表盘,而展示这个表盘制作过程。不过,问题是,我找不到一个合适的的功能表盘做例子。

然后当我我骑车回家时,我的红色闪光尾灯的电池没电了。

你看,我知道Sensor Watch本身是一块手表,我也很满意它被设计成做手表该做的事儿。但它也是你手腕上一个完全可编程的微控制器--如果有一件事是微控制器最擅长的,那一定就是闪动LED灯!”。这个只闪烁灯光的表盘的想法一直吸引着我。按说,设计之初,Sensor Watch中的LED并没选最亮的。不过,在漆黑的夜里,它稍微发出点光也就足够一些情景使用了。

Sensor Watch 在自行车上闪烁红光

丑话说在前头:实际上,Sensor Watch是一个非常挫的自行车灯。不过,如果我使点劲儿眯起眼睛,仔细寻么下的话,我看这个闪烁的表盘在紧急情况下还是有些用的。想象一下大晚上掉进峡谷,用黄色的LED灯作为紧急信标,帮助救援人员找到我。或者当带着狗在森林中露营时:可以将Sensor Watch挂在他们的项圈上,通过那个绿色的小点跟踪小狗,看它在墨黑的环境中飞奔。

问题:是否有更好的工具来完成这些工作?答案:是的,绝对有。但是,容我追问下:你现在手上就有吗?

我看不一定吧。

点灯实验(Blinky)走起

那么,来吧,点灯实验走起,正好我们利用这个机会来谈谈Sensor Watch软件是如何设计的。我们将为 Movement (“机芯”,Sensor Watch的社区固件)建立一个 单一的点灯(Blinky)表盘 。Movement是一个管理表盘列表的应用程序,佩戴者可以通过按下模式(Mode)按钮来浏览这些表盘。当一个表盘处于激活状态时,它几乎可以完全控制Sensor Watch的硬件,而一旦它退出--通常是在佩戴者按下模式(Mode)按钮后--它就会将控制权交给列表中的下一个表盘。这使佩戴者在与Sensor Watch互动时有一种熟悉的体验:简单地按下模式(Mode)按钮,从一个表盘切换到下一个。

我们将首先生成header文件和主程序文件,来开始我们的点灯(Blinky)表盘之旅。多亏了支持者David Keck的一个新脚本(昨晚刚合并进代码库!),我们可以简单地在终端窗口运行这个命令 python3 watch_face.py complication blink 。这将在 watch-faces/complication 文件夹中生成两个文件,名为 blinky_face.hblinky_face.c

Movement中的watch faces程序就是普通而古老的C语言,我们将在前述脚本为我们生成的四个函数中实现点灯(Blinky)表盘。

脚本还生成了一个结构体来保存我们手表的状态。我们还需要要添加一些东西:一个布尔值来跟踪LED是否应该主动闪烁--相当于一个开关,如果你愿意的话,还可以包含闪烁速度和颜色。它看起来像这样:

typedef struct {
    bool active;
    bool fast;
    uint8_t color;
} blinky_state_t;

接下来,我们来看看我们的实现文件,blinky_face.c,并完成那四个函数。我们的setup函数很简单:当手表启动时,Movement将调用这个函数,并提供一个指针,用于指向存储表盘状态的结构体位置。我们将为此目的分配一些内存,然后将其清空为零:

void blinky_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
    if (*context_ptr == NULL) {
        *context_ptr = malloc(sizeof(blinky_state_t));
        memset(*context_ptr, 0, sizeof(blinky_state_t));
    }
}

然后,我们将实现我们的activate函数。当佩戴者激活我们的表盘时,我们要延用他们之前可能已经设置了的速度和颜色,但在佩戴者指示之前,我们不希望自动开始闪烁灯光。所以我们要确保将active 的状态属性值设置为false。(注意,我们在这里会得到一个无类型指针(void pointer),我们把它指向我们的blinky_state_t结构体类型(type)。这个 "上下文 "指针可能看起来容易混淆,但实际上,它只是Movement给我们返回了我们在上面setup函数中用malloc分配的同一个指针。)

void blinky_face_activate(movement_settings_t *settings, void *context) {
    blinky_state_t *state = (blinky_state_t *)context;
    state->active = false;
}

在声明咱的loop函数之前,我将添加一个小的辅助函数来更新LCD。它只是读取我们的表盘状态信息,并将其转换为LCD上的字母显示:

static void _blinky_face_update_lcd(blinky_state_t *state) {
    char buf[11];
    const char colors[][7] = {" red  ", " Green", " Yello"};
    sprintf(buf, "BL %c%s", state->fast ? 'F' : 'S', colors[state->color]);
    watch_display_string(buf, 0);
}

这个函数格式化了一个十个字符的字符串,看起来像这样:"BL F Green"。佩戴者可以通过这个字符串知道 “点灯(Blinky)表盘,快速闪烁,绿色LED"。然后,watch_display_string函数将其显示在手表的十个可用(段码)位置上:

将显示在Sensor Watch上的点灯表盘

快成功了! 接下来,来编写我们的loop函数。这才是真正任务执行的地方!

bool blinky_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
    blinky_state_t *state = (blinky_state_t *)context;

    switch (event.event_type) {
        // TODO: handle events!
    }

    return true;
}

这个函数的核心只是一个处理事件(events)的大型开关语句。Movement 抽象掉了所有的按钮和计时器中断,只给我们的手表提供了简单的事件(events),来响应按钮的按下或者时钟的滴答。我们所要做的就是实现这些情景。例如,当手表的表盘第一次被激活时,我们得到一个事件。我们可以用它来首次更新LCD显示:

case EVENT_ACTIVATE:
    _blinky_face_update_lcd(state);
    break;

接下来,让我们对按下的按钮做出反应。当佩戴者按下模式Mode按钮时,我们让手表显示移动到下一个表盘:

case EVENT_MODE_BUTTON_UP:
    movement_move_to_next_face();
    break;

简单吧! 现在我们需要一种方法来改变颜色。我们将把这个函数分配给 “发光颜色"按钮:当佩戴者按下该按钮时,假设LED还没有闪烁,我们将换成下一个颜色并更新LCD显示:

case EVENT_LIGHT_BUTTON_UP:
    if (!state->active) {
        state->color = (state->color + 1) % 3;
        _blinky_face_update_lcd(state);
    }
    break;

我们还需要一种方法来启动和停止闪烁。我们将使用闹铃(Alarm)按钮来实现这一点。当佩戴者按下该按钮时,我们要做两件事中的一件:如果闪烁没有激活,我们就把它设置为激活,清除显示,并请求一个与所选速度相匹配的频率(用于闪烁);如果闪烁是激活的,我们将停止它并回到显示状态:

case EVENT_ALARM_BUTTON_UP:
    if (!state->active) {
        state->active = true;
        watch_clear_display();
        movement_request_tick_frequency(state->fast ? 8 : 2);            
    } else {
        state->active = false;
        watch_set_led_off();
        _blinky_face_update_lcd(state);
    }
    break;

当然,我们还需要给佩戴者提供一种选择闪烁速度的方法。但我们已经没有按钮可用了!不用担心,有另一个事件可以解决这个问题。Movement可以检测到一个按钮的 "长按",也就是佩戴者按住按钮超过半秒的时候。我们将使用长按闹铃(Alarm)按钮改变速度(同样,假设LED没有在闪烁中):

case EVENT_ALARM_LONG_PRESS:
    if (!state->active) {
        state->fast = !state->fast;
        _blinky_face_update_lcd(state);
    }
    break;

最后,我们需要让灯光闪烁! 我们将使用tick事件来实现。每当时钟滴答(tick)时,Movement就会发送出这个事件。通常情况下,这是一秒钟一次,但表盘可以要求更快的滴答(tick),就像我们在前面将闪烁状态设置为激活时所做的那样。我们将使用这个滴答函数来开关LED,使其在偶数滴答时关闭,在奇数滴答时打开:

case EVENT_TICK:
    if (state->active) {
        if (event.subsecond % 2 == 0) watch_set_led_off();
        else if (state->color == 0) watch_set_led_red();
        else if (state->color == 1) watch_set_led_green();
        else watch_set_led_yellow();
    }
    break;

loop函数就这么着了!现在只剩下一个函数了,而且是一个很短的函数。我们的resign(退出)函数,负责在将控制权交给下一个表盘之前进行最后的清理。在我们的例子中,佩戴者有可能在LED灯亮着的时候按下模式"Mode"按钮,所以为以防万一,我们需要确保在这里把LED关掉:

void blinky_face_resign(movement_settings_t *settings, void *context) {
    watch_set_led_off();
}

就这样,我们只用了几十行代码就实现了一个完整的表盘! 剩下的就是打开movement_config.h头文件,把它添加到已选表盘列表中:

const watch_face_t watch_faces[] = {
    simple_clock_face,
    blinky_face, // that's us!
    voltage_face,
    preferences_face,
    set_time_face,
};

一旦把它添加进去,剩下的就跟其他表盘一样了:编译进手表固件,把它戴在你的手腕、绑到你的自行车把、或者挂到你家狗狗的项圈上。

Sensor Watch 的其他新闻

如果这个消息你错过了的话:上周五,我与优秀的海伦·利(Helen Leigh)在 Crowd Supply拆机时间(Teardown Session)上讨论了Sensor Watch!我们现场演示了一些新功能,并回答了许多超赞的观众的问题,但最大的亮点是:由支持者 亚历克斯·阿克斯(Alex Akers)推出的 Sensor Watch仿真模拟器

Sensor Watch仿真模拟器

解释一下这里发生的事情:这是可以在网络浏览器中运行的Sensor Watch手表固件,你可以通过点击页面上的按钮来进行交互。亚历克斯(Alex)目前运行的固件包含了简单的时钟、设置、时间设置和TOTP界面,但你也可以克隆Sensor Watch的代码库,并通过在本地运行仿真模拟器来预览任何表盘合集。只要用 cd movement/make 进入目录,并运行 emmake make && python3 -m http.server --directory build 既可。这将在localhost:8000上建立一个本地仿真模拟器,你就可以在你的浏览器上玩耍了。

如果你对为Sensor Watch编写表盘感兴趣,但又等不及硬件到手,可以先用这个开始。我非常感谢亚历克斯(Alex)的贡献,并且迫不及待地想看到大伙儿为Sensor Watch做出多么有趣的表盘。

这次更新就到此为止。随后,我们将尝试在活动期间的每周四发布这些更新信息,听起来好像还有很多次,不过确实,我还有很多要谈的东西。 下周,我们将深入探究Sensor Watch的零件清单(BOM)。我们将研究研究电路板上的每一个零件,更好地了解它们的作用,以及每一个零件是如何推动手表工作的。

就酱紫吧,再次感谢您的支持,下周我们将分享更多信息。

- 乔伊 Joey


订阅以接受Sensor Watch的项目更新消息。

Sensor Watch是 Microchip Get Launched 设计竞赛的一部分!

翻译日志

2022年4月23日:首次翻译,基于Sensor Watch发布于2022年1月27日的第四次项目更新,发布于www.peterguo.net/sensorwatch/updates/4