altos/telelco-v3.0: Add contrast setting
authorKeith Packard <keithp@keithp.com>
Sat, 27 Jan 2024 01:35:43 +0000 (17:35 -0800)
committerKeith Packard <keithp@keithp.com>
Thu, 1 Feb 2024 01:50:19 +0000 (17:50 -0800)
Provide a UI for setting the LCD contrast.

Signed-off-by: Keith Packard <keithp@keithp.com>
src/drivers/ao_lco.c
src/drivers/ao_lco.h
src/drivers/ao_lco_bits.c
src/drivers/ao_lco_two.c
src/drivers/ao_st7565.c
src/drivers/ao_st7565.h
src/telelco-v2.0/ao_lco_v2.c
src/telelco-v3.0/Makefile
src/telelco-v3.0/ao_lco_v3.c
src/telelco-v3.0/ao_pins.h
src/telelco-v3.0/ao_telelco.c

index d8cf7ba3df9e2ad53d984a4d423d2b393eafc496..c0a52b69e6cac3e43708c256cb6bb9ceb4c5b546 100644 (file)
@@ -66,7 +66,7 @@ ao_lco_show_pad(uint8_t pad)
                                 (0 << 6))
 
 void
-ao_lco_show_box(uint16_t box)
+ao_lco_show_box(int16_t box)
 {
        ao_mutex_get(&ao_lco_display_mutex);
        if (box == AO_LCO_BOX_DRAG) {
index 8f77a8e8e7732fd4d8e1012e1f4ddb3054742a70..50ed068acb2477040fc5630269b181c6db879a7a 100644 (file)
@@ -39,7 +39,7 @@ extern uint8_t        ao_lco_drag_race;       /* true when drag race mode enabled */
 #endif
 
 extern uint8_t ao_lco_pad;             /* Currently selected pad */
-extern uint16_t        ao_lco_box;             /* Currently selected box */
+extern int16_t ao_lco_box;             /* Currently selected box */
 
 extern uint8_t ao_lco_armed;           /* armed mode active */
 extern uint8_t ao_lco_firing;          /* fire button pressed */
@@ -51,12 +51,17 @@ extern struct ao_pad_query  ao_pad_query;   /* Last received QUERY from pad */
 #define AO_LCO_BOX_FIRST       AO_LCO_BOX_DRAG
 #else
 #define AO_LCO_LCO_VOLTAGE     0               /* Box number to show LCO voltage */
-#define AO_LCO_BOX_FIRST       AO_LCO_LCO_VOLTAGE
+# ifdef AO_LCO_HAS_CONTRAST
+#  define AO_LCO_CONTRAST      -1
+#  define AO_LCO_BOX_FIRST     AO_LCO_CONTRAST
+# else
+#  define AO_LCO_BOX_FIRST     AO_LCO_LCO_VOLTAGE
+# endif
 #endif
 #define AO_LCO_PAD_VOLTAGE     0               /* Pad number to show box voltage */
 
 static inline bool
-ao_lco_box_pseudo(uint16_t box)
+ao_lco_box_pseudo(int16_t box)
 {
        switch (box) {
 #ifdef AO_LCO_LCO_VOLTAGE
@@ -66,13 +71,17 @@ ao_lco_box_pseudo(uint16_t box)
 #ifdef AO_LCO_DRAG_RACE_BOX
        case AO_LCO_BOX_DRAG:
                return true;
+#endif
+#ifdef AO_LCO_CONTRAST
+       case AO_LCO_CONTRAST:
+               return true;
 #endif
        default:
                return false;
        }
 }
 
-extern uint16_t        ao_lco_min_box, ao_lco_max_box;
+extern int16_t ao_lco_min_box, ao_lco_max_box;
 
 #define AO_LCO_MASK_SIZE(n)    (((n) + 7) >> 3)
 #define AO_LCO_MASK_ID(n)      ((n) >> 3)
@@ -91,10 +100,10 @@ void
 ao_lco_update(void);
 
 uint8_t
-ao_lco_pad_present(uint16_t box, uint8_t pad);
+ao_lco_pad_present(int16_t box, uint8_t pad);
 
 uint8_t
-ao_lco_pad_first(uint16_t box);
+ao_lco_pad_first(int16_t box);
 
 void
 ao_lco_set_pad(uint8_t new_pad);
@@ -103,7 +112,7 @@ void
 ao_lco_step_pad(int8_t dir);
 
 void
-ao_lco_set_box(uint16_t new_box);
+ao_lco_set_box(int16_t new_box);
 
 void
 ao_lco_step_box(int8_t dir);
@@ -160,7 +169,7 @@ void
 ao_lco_show_pad(uint8_t pad);
 
 void
-ao_lco_show_box(uint16_t box);
+ao_lco_show_box(int16_t box);
 
 void
 ao_lco_show(void);
@@ -169,7 +178,15 @@ void
 ao_lco_init(void);
 
 uint8_t
-ao_lco_box_present(uint16_t box);
+ao_lco_box_present(int16_t box);
+
+#ifdef AO_LCO_HAS_CONTRAST
+void
+ao_lco_set_contrast(int16_t contrast);
+
+int16_t
+ao_lco_get_contrast(void);
+#endif
 
 #ifdef AO_LCO_SEARCH_API
 
@@ -177,10 +194,10 @@ void
 ao_lco_search_start(void);
 
 void
-ao_lco_search_box_check(uint16_t box);
+ao_lco_search_box_check(int16_t box);
 
 void
-ao_lco_search_box_present(uint16_t box);
+ao_lco_search_box_present(int16_t box);
 
 void
 ao_lco_search_done(void);
index b32a56fe02d34f99f7d9a9e0068ba8d89c2dc45a..8e9ea09bf3b63ae32fcd69025dc24572623baad0 100644 (file)
 uint8_t                ao_lco_debug;
 
 uint8_t                ao_lco_pad;
-uint16_t       ao_lco_box;
+int16_t                ao_lco_box;
 
 uint8_t                ao_lco_armed;                                   /* arm active */
 uint8_t                ao_lco_firing;                                  /* fire active */
 
-uint16_t       ao_lco_min_box, ao_lco_max_box;
+int16_t                ao_lco_min_box, ao_lco_max_box;
 
 uint8_t                ao_lco_pretending;
 
@@ -142,7 +142,7 @@ ao_lco_igniter_status(void)
 }
 
 uint8_t
-ao_lco_pad_present(uint16_t box, uint8_t pad)
+ao_lco_pad_present(int16_t box, uint8_t pad)
 {
        /* voltage measurement is always valid */
        if (pad == AO_LCO_PAD_VOLTAGE)
@@ -155,7 +155,7 @@ ao_lco_pad_present(uint16_t box, uint8_t pad)
 }
 
 uint8_t
-ao_lco_pad_first(uint16_t box)
+ao_lco_pad_first(int16_t box)
 {
        uint8_t pad;
 
@@ -166,11 +166,11 @@ ao_lco_pad_first(uint16_t box)
 }
 
 static uint8_t
-ao_lco_get_channels(uint16_t box, struct ao_pad_query *query)
+ao_lco_get_channels(int16_t box, struct ao_pad_query *query)
 {
        int8_t                  r;
 
-       r = ao_lco_query(box, query, &ao_lco_tick_offset[box]);
+       r = ao_lco_query((uint16_t) box, query, &ao_lco_tick_offset[box]);
        if (r == AO_RADIO_CMAC_OK) {
                ao_lco_channels[box] = query->channels;
                ao_lco_valid[box] = AO_LCO_VALID_LAST | AO_LCO_VALID_EVER;
@@ -213,7 +213,7 @@ ao_lco_box_reset_present(void)
 }
 
 static void
-ao_lco_box_set_present(uint16_t box)
+ao_lco_box_set_present(int16_t box)
 {
        if (box < ao_lco_min_box)
                ao_lco_min_box = box;
@@ -232,7 +232,7 @@ ao_lco_set_pad(uint8_t new_pad)
 }
 
 void
-ao_lco_set_box(uint16_t new_box)
+ao_lco_set_box(int16_t new_box)
 {
        ao_lco_box = new_box;
        if (!ao_lco_box_pseudo(ao_lco_box)) {
@@ -252,6 +252,18 @@ ao_lco_step_pad(int8_t dir)
 {
        int16_t new_pad;
 
+#ifdef AO_LCO_HAS_CONTRAST
+       if (ao_lco_box == AO_LCO_CONTRAST) {
+               int16_t contrast = ao_lco_get_contrast();
+
+               contrast += (int16_t) (dir * AO_LCO_CONTRAST_STEP);
+               if (contrast < AO_LCO_MIN_CONTRAST)
+                       contrast = AO_LCO_MIN_CONTRAST;
+               if (contrast > AO_LCO_MAX_CONTRAST)
+                       contrast = AO_LCO_MAX_CONTRAST;
+               ao_lco_set_contrast(contrast);
+       }
+#endif
        new_pad = (int16_t) ao_lco_pad;
        do {
                new_pad += dir;
@@ -267,7 +279,7 @@ ao_lco_step_pad(int8_t dir)
 }
 
 uint8_t
-ao_lco_box_present(uint16_t box)
+ao_lco_box_present(int16_t box)
 {
        if (ao_lco_box_pseudo(box))
                return 1;
@@ -279,19 +291,23 @@ ao_lco_box_present(uint16_t box)
 void
 ao_lco_step_box(int8_t dir)
 {
-       int32_t new_box = (int32_t) ao_lco_box;
+       int16_t new_box = ao_lco_box;
 
        do {
                new_box += dir;
                if (new_box > ao_lco_max_box)
                        new_box = AO_LCO_BOX_FIRST;
+#ifdef AO_LCO_HAS_CONTRAST
+               else if (new_box < AO_LCO_CONTRAST)
+#else
                else if (new_box < 0)
+#endif
                        new_box = ao_lco_max_box;
                if (new_box == ao_lco_box)
                        break;
-       } while (!ao_lco_box_present((uint16_t) new_box));
-       PRINTD("New box %ld\n", new_box);
-       ao_lco_set_box((uint16_t) new_box);
+       } while (!ao_lco_box_present(new_box));
+       PRINTD("New box %d\n", new_box);
+       ao_lco_set_box(new_box);
 }
 
 void
@@ -305,7 +321,7 @@ ao_lco_set_armed(uint8_t armed)
        if (ao_lco_armed) {
 #if AO_LCO_DRAG
                if (ao_lco_drag_race) {
-                       uint16_t        box;
+                       int16_t box;
 
                        for (box = ao_lco_min_box; box <= ao_lco_max_box; box++)
                                if (ao_lco_selected[box])
@@ -338,7 +354,7 @@ ao_lco_search(void)
 {
        int8_t          r;
        int8_t          try;
-       uint16_t        box;
+       int16_t         box;
        uint16_t        boxes = 0;
 
        ao_lco_box_reset_present();
@@ -357,7 +373,7 @@ ao_lco_search(void)
 #endif
                for (try = 0; try < 3; try++) {
                        ao_lco_tick_offset[box] = 0;
-                       r = ao_lco_query(box, &ao_pad_query, &ao_lco_tick_offset[box]);
+                       r = ao_lco_query((uint16_t) box, &ao_pad_query, &ao_lco_tick_offset[box]);
                        PRINTD("box %d result %d offset %d\n", box, r, ao_lco_tick_offset[box]);
                        if (r == AO_RADIO_CMAC_OK) {
                                ++boxes;
@@ -387,7 +403,7 @@ ao_lco_search(void)
 void
 ao_lco_pretend(void)
 {
-       uint16_t box;
+       int16_t box;
 
        ao_lco_pretending = 1;
        ao_lco_min_box = 1;
@@ -403,8 +419,8 @@ ao_lco_pretend(void)
 void
 ao_lco_monitor(void)
 {
-       AO_TICK_TYPE            delay;
-       uint16_t                box;
+       AO_TICK_TYPE    delay;
+       int16_t         box;
 
        for (;;) {
                PRINTD("monitor armed %d firing %d\n",
@@ -420,7 +436,7 @@ ao_lco_monitor(void)
                                                PRINTD("Arming box %d pads %x\n",
                                                       box, ao_lco_selected[box]);
                                                if (ao_lco_valid[box] & AO_LCO_VALID_EVER) {
-                                                       ao_lco_arm(box, ao_lco_selected[box], ao_lco_tick_offset[box]);
+                                                       ao_lco_arm((uint16_t) box, ao_lco_selected[box], ao_lco_tick_offset[box]);
                                                        ao_delay(AO_MS_TO_TICKS(10));
                                                }
                                        }
index 0eb90080acb76dae8d288b8fe466d6d39b06ab32..583aa9ffe7f4df3e27f3e32dc9b7666c4557f55b 100644 (file)
@@ -52,7 +52,7 @@ ao_lco_show_pad(uint8_t pad)
 }
 
 void
-ao_lco_show_box(uint16_t box)
+ao_lco_show_box(int16_t box)
 {
        (void) box;
 }
@@ -85,7 +85,7 @@ ao_lco_input(void)
                case AO_EVENT_BUTTON:
                        switch (event.unit) {
                        case AO_BUTTON_BOX:
-                               ao_lco_set_box((uint16_t) event.value);
+                               ao_lco_set_box((int16_t) event.value);
                                ao_lco_set_armed(0);
                                break;
                        case AO_BUTTON_ARM:
index 6893ccd41e4741c506eee35b7bd57c4225200c71..261ed857b16d840ce7fcd7acc46178845cc5bcbb 100644 (file)
@@ -82,12 +82,23 @@ ao_st7565_data(const void *base, uint16_t len)
        ao_st7565_stop();
 }
 
-static void
+static uint8_t brightness;
+
+void
 ao_st7565_set_brightness(uint8_t val)
 {
+       if (val > 63)
+               val = 63;
+       brightness = val;
        ao_st7565_instruction_param(ST7565_ELECTRONIC_VOLUME_SET, val);
 }
 
+uint8_t
+ao_st7565_get_brightness(void)
+{
+       return brightness;
+}
+
 static bool setup_done;
 
 static void
index b961c56b6b72b6e1e03117390337b2836c8ec704..1dad97cc72d7ca2619a3bf13917f4760a53b2638 100644 (file)
 void
 ao_st7565_update(struct ao_bitmap *bitmap);
 
+void
+ao_st7565_set_brightness(uint8_t val);
+
+uint8_t
+ao_st7565_get_brightness(void);
+
 void
 ao_st7565_init(void);
 
index 59b621039f87ce983d67afebbac753c5ebd1af02..d1e66d05450fa67d4f24cbc5bf1be7d4081ba5f9 100644 (file)
@@ -67,7 +67,7 @@ ao_lco_show_pad(uint8_t pad)
                                 (0 << 6))
 
 void
-ao_lco_show_box(uint16_t box)
+ao_lco_show_box(int16_t box)
 {
        ao_mutex_get(&ao_lco_display_mutex);
        ao_seven_segment_set(AO_LCO_BOX_DIGIT_1, (uint8_t) (box % 10 | (ao_lco_drag_race << 4)));
index b57639728296293fbb065477b1774cf9fa40ad54..5846a8ece71f8c5bb7e14005f21db6684a01509c 100644 (file)
@@ -24,6 +24,7 @@ INC = \
        ao_radio_cmac.h \
        ao_cc1200_CC1200.h \
        ao_cc1200.h \
+       ao_st7565.h \
        ao_font.h \
        ao_logo.h \
        stm32f1.h
index 2bd3c5009e198fc971dd1a73e417de59b6d1f333..7c81a2e5babd3ddaf4c1f5c69c00c88469e0ed1b 100644 (file)
@@ -45,6 +45,7 @@ static const struct ao_transform logo_transform = {
 
 #define BIG_FONT BitstreamVeraSans_Roman_58_font
 #define VOLT_FONT BitstreamVeraSans_Roman_58_font
+#define CONTRAST_FONT BitstreamVeraSans_Roman_58_font
 #define SMALL_FONT BitstreamVeraSans_Roman_12_font
 #define TINY_FONT BitstreamVeraSans_Roman_10_font
 #define LOGO_FONT BenguiatGothicStd_Bold_26_font
@@ -64,6 +65,11 @@ static const struct ao_transform logo_transform = {
 #define FOUND_Y                63
 #define FOUND_X                6
 #define FOUND_WIDTH    (WIDTH - 6)
+#define CONTRAST_LABEL_X       37
+#define CONTRAST_WIDTH 100
+#define CONTRAST_X     (WIDTH - CONTRAST_WIDTH) / 2
+#define CONTRAST_Y     20
+#define CONTRAST_HEIGHT        20
 
 #define AO_LCO_DRAG_RACE_START_TIME    AO_SEC_TO_TICKS(5)
 #define AO_LCO_DRAG_RACE_STOP_TIME     AO_SEC_TO_TICKS(2)
@@ -89,7 +95,7 @@ _ao_lco_show_pad(uint8_t pad)
 }
 
 static void
-_ao_lco_show_box(uint16_t box)
+_ao_lco_show_box(int16_t box)
 {
        char    str[7];
 
@@ -121,6 +127,16 @@ _ao_lco_batt_voltage(void)
        ao_st7565_update(&fb);
 }
 
+static void
+_ao_lco_show_contrast(void)
+{
+       uint8_t brightness = ao_st7565_get_brightness();
+       int16_t contrast = (int16_t) (brightness * CONTRAST_WIDTH / AO_LCO_MAX_CONTRAST);
+
+       ao_text(&fb, &SMALL_FONT, CONTRAST_LABEL_X, LABEL_Y, "Contrast", AO_BLACK, AO_COPY);
+       ao_rect(&fb, CONTRAST_X, CONTRAST_Y, contrast, CONTRAST_HEIGHT, AO_BLACK, AO_COPY);
+}
+
 void
 ao_lco_show(void)
 {
@@ -128,6 +144,8 @@ ao_lco_show(void)
        ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
        if (ao_lco_box == AO_LCO_LCO_VOLTAGE) {
                _ao_lco_batt_voltage();
+       } else if (ao_lco_box == AO_LCO_CONTRAST) {
+               _ao_lco_show_contrast();
        } else if (ao_lco_pad == AO_LCO_PAD_VOLTAGE) {
                _ao_lco_show_voltage(ao_pad_query.battery, "Pad battery");
        } else {
@@ -162,6 +180,18 @@ ao_lco_set_select(void)
 }
 
 
+void
+ao_lco_set_contrast(int16_t contrast)
+{
+       ao_st7565_set_brightness((uint8_t) contrast);
+}
+
+int16_t
+ao_lco_get_contrast(void)
+{
+       return (int16_t) ao_st7565_get_brightness();
+}
+
 static struct ao_task  ao_lco_drag_task;
 
 static void
@@ -277,15 +307,15 @@ ao_lco_search_start(void)
 }
 
 void
-ao_lco_search_box_check(uint16_t box)
+ao_lco_search_box_check(int16_t box)
 {
        if (box > 0)
-               ao_rect(&fb, SCAN_X, SCAN_Y, (int16_t) box, SCAN_HEIGHT, AO_BLACK, AO_COPY);
+               ao_rect(&fb, SCAN_X, SCAN_Y, box, SCAN_HEIGHT, AO_BLACK, AO_COPY);
        ao_st7565_update(&fb);
 }
 
 void
-ao_lco_search_box_present(uint16_t box)
+ao_lco_search_box_present(int16_t box)
 {
        char    str[8];
        if (found_x < FOUND_WIDTH)
@@ -301,20 +331,10 @@ ao_lco_search_done(void)
        ao_st7565_update(&fb);
 }
 
-static void
-ao_lco_batt_voltage(void)
-{
-       ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
-       _ao_lco_batt_voltage();
-       ao_st7565_update(&fb);
-       ao_delay(AO_MS_TO_TICKS(1000));
-}
-
 static void
 ao_lco_main(void)
 {
        ao_lco_display_test();
-       ao_lco_batt_voltage();
        ao_lco_search();
        ao_add_task(&ao_lco_input_task, ao_lco_input, "lco input");
        ao_add_task(&ao_lco_arm_warn_task, ao_lco_arm_warn, "lco arm warn");
index 21d90328e644b59d47687cfc837e8241808aa1e4..08135542d5b1205040610b9e8297a6ecc512fb44 100644 (file)
@@ -296,5 +296,9 @@ struct ao_adc {
 #define AO_ADC_REFERENCE_DV    33
 
 #define AO_LCO_SEARCH_API
+#define AO_LCO_HAS_CONTRAST    1
+#define AO_LCO_MIN_CONTRAST    0
+#define AO_LCO_MAX_CONTRAST    63
+#define AO_LCO_CONTRAST_STEP   1
 
 #endif /* _AO_PINS_H_ */
index 4ce46f3e9178d1ae9f21d54b67904271cb475f3c..708352ca4802eb92baf39b73a4191ffd891cec4f 100644 (file)
 #include <ao_adc_single.h>
 #include <ao_st7565.h>
 
+#define WIDTH  AO_ST7565_WIDTH
+#define HEIGHT AO_ST7565_HEIGHT
+#define STRIDE AO_BITMAP_STRIDE(WIDTH)
+
+static uint32_t        image[STRIDE * HEIGHT];
+
+static struct ao_bitmap fb = {
+       .base = image,
+       .stride = STRIDE,
+       .width = WIDTH,
+       .height = HEIGHT,
+       .damage = AO_BOX_INIT,
+};
+
+static void
+ao_st7565_test(void)
+{
+       ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
+       ao_st7565_update(&fb);
+       ao_text(&fb, &BitstreamVeraSans_Roman_24_font,
+               0, 20, "hello world", AO_BLACK, AO_COPY);
+       ao_st7565_update(&fb);
+}
+
+static int16_t x1 = 32, _y1 = 10, x2 = 32, y2 = 40;
+static int16_t dx1 = 2, dy1 = 2, dx2 = -2, dy2 = -1;
+
+#define bounds(v,m,M,d)        \
+               if (v < m) {                    \
+                       v = m + m - v;          \
+                       d = -d;                 \
+               } else if (v > M) {             \
+                       v = M - (v - M);        \
+                       d = -d;                 \
+               }
+
+static void
+ao_st7565_line(void)
+{
+       int     i;
+
+       for (i = 0; i < 100; i++) {
+               ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
+               ao_line(&fb, x1, _y1, x2, y2, AO_BLACK, AO_COPY);
+               ao_st7565_update(&fb);
+               x1 += dx1;
+               _y1 += dy1;
+               x2 += dx2;
+               y2 += dy2;
+               printf("%d,%d - %d,%d\n", x1, _y1, x2, y2);
+               fflush(stdout);
+               bounds(x1, 0, WIDTH, dx1);
+               bounds(x2, 0, WIDTH, dx2);
+               bounds(_y1, 0, HEIGHT, dy1);
+               bounds(y2, 0, HEIGHT, dy2);
+               ao_delay(AO_MS_TO_TICKS(200));
+       }
+}
+
+static const float pad_volts = 12.3f;
+static const float lco_volts = 4.1f;
+static const int rssi = -30;
+
+static int     boxes[] = { 1, 2, 3, 5, 8, 11, 13, 17, 19, 23, 29, 31, 37, 62, 97 };
+
+//static int   max_box = 97;
+
+#define ARRAYSIZE(a)   (sizeof(a) / sizeof((a)[0]))
+
+static bool
+valid_box(int box)
+{
+       size_t i;
+       if (box == 0)
+               return true;
+       for (i = 0; i < ARRAYSIZE(boxes); i++)
+               if (boxes[i] == box)
+                       return true;
+       return false;
+}
+
+#if 0
+static void
+next_box(void)
+{
+       for (int n = box_number + 1; n <= max_box; n++)
+               if (valid_box(n)) {
+                       box_number = n;
+                       return;
+               }
+       box_number = 0;
+}
+
+static void
+prev_box(void)
+{
+       for (int n = box_number - 1; n >= 0; n--)
+               if (valid_box(n)) {
+                       box_number = n;
+                       return;
+               }
+       box_number = max_box;
+}
+#endif
+
+static const struct ao_transform logo_transform = {
+       .x_scale = 48, .x_off = 2,
+       .y_scale = 48, .y_off = 0,
+};
+
+#define BIG_FONT BitstreamVeraSans_Roman_58_font
+#define VOLT_FONT BitstreamVeraSans_Roman_58_font
+#define SMALL_FONT BitstreamVeraSans_Roman_12_font
+#define TINY_FONT BitstreamVeraSans_Roman_10_font
+#define LOGO_FONT BenguiatGothicStd_Bold_26_font
+
+#define LABEL_Y                (int16_t) (SMALL_FONT.ascent)
+#define VALUE_Y                (int16_t) (LABEL_Y + BIG_FONT.ascent + 5)
+#define BOX_X          2
+#define PAD_X          90
+#define BOX_LABEL_X    30
+#define VOLT_LABEL_X   25
+#define RSSI_LABEL_X   15
+#define PAD_LABEL_X    95
+#define SEP_X          (PAD_X - 8)
+#define SCAN_X         (WIDTH - 100) / 2
+#define SCAN_Y         50
+#define SCAN_HEIGHT    3
+#define FOUND_Y                63
+#define FOUND_X                6
+#define FOUND_WIDTH    17
+#define MAX_VALID      (WIDTH / FOUND_WIDTH)
+
+static int16_t box_number = 88;
+static int16_t pad_number = 8;
+
+static void
+ao_st7565_poly(void)
+{
+       int16_t scan_number;
+       char    str[8];
+       int     i;
+       int     v;
+       int     last_box;
+       int16_t b;
+
+       for (scan_number = 0; scan_number < 100; scan_number++) {
+               ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
+               ao_logo(&fb, &logo_transform, &LOGO_FONT, AO_BLACK, AO_COPY);
+               if (scan_number) {
+                       ao_rect(&fb, SCAN_X, SCAN_Y, (int16_t) scan_number, SCAN_HEIGHT, AO_BLACK, AO_COPY);
+                       b = 0;
+                       v = 0;
+                       last_box = 0;
+                       for (i = scan_number; i > 1; i--) {
+                               if (valid_box(i)) {
+                                       if (!last_box)
+                                               last_box = i;
+                                       v++;
+                                       if (v == MAX_VALID)
+                                               break;
+                               }
+                       }
+                       for (; i <= scan_number; i++) {
+                               if (valid_box(i)) {
+                                       sprintf(str, "%02d%s", i, i == last_box ? "" : ",");
+                                       ao_text(&fb, &TINY_FONT, (int16_t) (FOUND_X + FOUND_WIDTH * b),
+                                               FOUND_Y, str, AO_BLACK, AO_COPY);
+                                       b++;
+                               }
+                       }
+               }
+               ao_st7565_update(&fb);
+               ao_delay(AO_MS_TO_TICKS(50));
+       }
+       ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
+       switch (box_number) {
+       case 0:
+               sprintf(str, "%4.1f", lco_volts);
+               ao_text(&fb, &VOLT_FONT, BOX_X, VALUE_Y, str, AO_BLACK, AO_COPY);
+               ao_text(&fb, &SMALL_FONT, VOLT_LABEL_X, LABEL_Y, "LCO Battery", AO_BLACK, AO_COPY);
+               break;
+       default:
+               switch (pad_number) {
+               case -1:
+                       sprintf(str, "%4.1f", pad_volts);
+                       ao_text(&fb, &VOLT_FONT, BOX_X, VALUE_Y, str, AO_BLACK, AO_COPY);
+                       ao_text(&fb, &SMALL_FONT, VOLT_LABEL_X, LABEL_Y, "Pad Battery", AO_BLACK, AO_COPY);
+                       break;
+               case 0:
+                       sprintf(str, "%4d", rssi);
+                       ao_text(&fb, &VOLT_FONT, BOX_X, VALUE_Y, str, AO_BLACK, AO_COPY);
+                       ao_text(&fb, &SMALL_FONT, RSSI_LABEL_X, LABEL_Y, "Signal Strength", AO_BLACK, AO_COPY);
+                       break;
+               default:
+                       sprintf(str, "%02d", box_number);
+                       ao_text(&fb, &BIG_FONT, BOX_X, VALUE_Y, str, AO_BLACK, AO_COPY);
+                       ao_text(&fb, &SMALL_FONT, BOX_LABEL_X, LABEL_Y, "Box", AO_BLACK, AO_COPY);
+
+                       sprintf(str, "%d", pad_number);
+                       ao_text(&fb, &BIG_FONT, PAD_X, VALUE_Y, str, AO_BLACK, AO_COPY);
+                       ao_text(&fb, &SMALL_FONT, PAD_LABEL_X, LABEL_Y, "Pad", AO_BLACK, AO_COPY);
+
+                       ao_rect(&fb, SEP_X, 0, 2, HEIGHT, AO_BLACK, AO_COPY);
+               }
+               break;
+       }
+       ao_st7565_update(&fb);
+}
+
+const struct ao_cmds ao_st7565_cmds[] = {
+       { ao_st7565_test, "g\0Test ST7565 display" },
+       { ao_st7565_line, "l\0Draw lines" },
+       { ao_st7565_poly, "p\0Draw polygon" },
+       { 0, NULL },
+};
+
 int
 main(void)
 {
@@ -63,6 +280,8 @@ main(void)
        ao_lco_init();
        ao_lco_cmd_init();
 
+//     ao_cmd_register(ao_st7565_cmds);
+
        ao_led_off(LEDS_AVAILABLE);
 
        ao_start_scheduler();