+ ao_logo_poly(&fb, &show_transform, AO_BLACK, AO_COPY);
+
+ decivolt = ao_battery_decivolt(ao_data_cur.adc.v_batt);
+ _ao_format_voltage(battery, sizeof(battery), (uint16_t) decivolt);
+
+ info_y = INFO_START_Y;
+ _ao_lco_info("%s", ao_product);
+ _ao_lco_info("Serial: %d", ao_serial_number);
+ _ao_lco_info("Battery: %sV", battery);
+ _ao_lco_info("Version: %s", ao_version);
+ _ao_lco_info("Callsign: %s", ao_config.callsign);
+ _ao_lco_info("Frequency: %ld.%03d",
+ ao_config.frequency / 1000,
+ (int) (ao_config.frequency % 1000));
+}
+
+static uint8_t
+popcount(uint32_t value)
+{
+ uint8_t count = 0;
+ while(value != 0) {
+ count += value & 1;
+ value >>= 1;
+ }
+ return count;
+}
+
+static void
+_ao_lco_show_pad_info(void)
+{
+ char pad_battery[7];
+
+ ao_logo_poly(&fb, &show_transform, AO_BLACK, AO_COPY);
+ info_y = INFO_START_Y;
+ _ao_lco_info("Bank: %d", ao_lco_box);
+ if (!(ao_lco_valid[ao_lco_box] & AO_LCO_VALID_LAST)) {
+ _ao_lco_info("Contact lost");
+ _ao_lco_info("Last RSSI: %ddBm", ao_radio_cmac_last_rssi);
+ } else {
+ _ao_lco_info("Total pads: %d", popcount(ao_pad_query.channels));
+ _ao_lco_info("RSSI: %ddBm", ao_radio_cmac_rssi);
+ _ao_format_voltage(pad_battery, sizeof(pad_battery), ao_pad_query.battery);
+ _ao_lco_info("Battery: %sV", pad_battery);
+ _ao_lco_info("Arming switch: %s", ao_pad_query.arm_status ? "On" : "Off");
+ }
+}
+
+#define AO_LCO_DIM_BACKLIGHT (AO_LCO_MIN_BACKLIGHT + 3 * AO_LCO_BACKLIGHT_STEP)
+#define AO_AUTO_BACKLIGHT_RANGE (AO_LCO_MAX_BACKLIGHT - AO_LCO_DIM_BACKLIGHT)
+#define AO_AUTO_BACKLIGHT_GAP AO_ADC_MAX / 6
+
+static struct {
+ int16_t v_als;
+ int32_t backlight;
+} ao_lco_backlight_map[] = {
+ { .v_als = AO_ADC_MAX / 6, .backlight = AO_LCO_DIM_BACKLIGHT },
+ { .v_als = AO_ADC_MAX / 3, .backlight = (AO_LCO_MAX_BACKLIGHT - AO_LCO_MIN_BACKLIGHT) / 2 },
+ { .v_als = AO_ADC_MAX / 2, .backlight = AO_LCO_MAX_BACKLIGHT },
+ { .v_als = AO_ADC_MAX * 3 / 4, .backlight = 0 },
+};
+
+#define NUM_BACKLIGHT_MAP sizeof(ao_lco_backlight_map)/sizeof(ao_lco_backlight_map[0])
+
+static unsigned ao_backlight_prev = NUM_BACKLIGHT_MAP - 1;
+
+static void
+ao_auto_backlight(int16_t als_min, int16_t als_max)
+{
+ unsigned ao_backlight;
+
+ PRINTD("ao_auto_backlight min %d max %d\n", als_min, als_max);
+ ao_backlight = ao_backlight_prev;
+ while (als_min > ao_lco_backlight_map[ao_backlight].v_als + AO_AUTO_BACKLIGHT_GAP) {
+ if (ao_backlight == NUM_BACKLIGHT_MAP - 1)
+ break;
+ ao_backlight++;
+ }
+ while (als_max < ao_lco_backlight_map[ao_backlight].v_als - AO_AUTO_BACKLIGHT_GAP) {
+ if (ao_backlight == 0)
+ return;
+ ao_backlight--;
+ }
+ if (ao_backlight != ao_backlight_prev)
+ {
+ PRINTD(" set backlight to %ld\n", ao_lco_backlight_map[ao_backlight].backlight);
+ ao_lco_set_backlight(ao_lco_backlight_map[ao_backlight].backlight);
+ ao_backlight_prev = ao_backlight;
+ }
+}
+
+#define AO_LCO_BACKLIGHT_INTERVAL AO_SEC_TO_TICKS(2)
+
+static void
+ao_lco_data(void)
+{
+ AO_TICK_TYPE backlight_tick = ao_time() + AO_LCO_BACKLIGHT_INTERVAL;
+ AO_TICK_TYPE now;
+ int16_t als_min = INT16_MAX;
+ int16_t als_max = INT16_MIN;
+
+ ao_timer_set_adc_interval(AO_MS_TO_TICKS(100));
+ for (;;) {
+ ao_sleep((void *) &ao_data_head);
+
+ while (ao_sample_data != ao_data_head) {
+ struct ao_data *ao_data;
+
+ /* Capture a sample */
+ ao_data = (struct ao_data *) &ao_data_ring[ao_sample_data];
+
+ ao_data_cur = *ao_data;
+ if (ao_data_cur.adc.v_als < als_min)
+ als_min = ao_data_cur.adc.v_als;
+ if (ao_data_cur.adc.v_als > als_max)
+ als_max = ao_data_cur.adc.v_als;
+ ao_sample_data = ao_data_ring_next(ao_sample_data);
+ }
+ now = ao_time();
+ if ((AO_TICK_SIGNED) (backlight_tick - now) < 0) {
+ backlight_tick = now + AO_LCO_BACKLIGHT_INTERVAL;
+ ao_auto_backlight(als_min, als_max);
+ als_min = INT16_MAX;
+ als_max = INT16_MIN;
+ }
+ }