diff --git a/movement/watch_faces/clock/minute_repeater_decimal_face.c b/movement/watch_faces/clock/minute_repeater_decimal_face.c index 2cedc3075..02e4dcde5 100644 --- a/movement/watch_faces/clock/minute_repeater_decimal_face.c +++ b/movement/watch_faces/clock/minute_repeater_decimal_face.c @@ -129,8 +129,8 @@ bool minute_repeater_decimal_face_loop(movement_event_t event, movement_settings if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. - watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); - watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9); + watch_display_character_lp('0' + date_time.unit.second / 10, 8); + watch_display_character_lp('0' + date_time.unit.second % 10, 9); break; } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before minutes is the same. diff --git a/movement/watch_faces/clock/repetition_minute_face.c b/movement/watch_faces/clock/repetition_minute_face.c index e9e5e3197..3453f6889 100644 --- a/movement/watch_faces/clock/repetition_minute_face.c +++ b/movement/watch_faces/clock/repetition_minute_face.c @@ -114,8 +114,8 @@ bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *se if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. - watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); - watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9); + watch_display_character_lp('0' + date_time.unit.second / 10, 8); + watch_display_character_lp('0' + date_time.unit.second % 10, 9); break; } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before minutes is the same. diff --git a/movement/watch_faces/clock/simple_clock_bin_led_face.c b/movement/watch_faces/clock/simple_clock_bin_led_face.c index cf39c1886..fa1c90488 100644 --- a/movement/watch_faces/clock/simple_clock_bin_led_face.c +++ b/movement/watch_faces/clock/simple_clock_bin_led_face.c @@ -140,8 +140,8 @@ bool simple_clock_bin_led_face_loop(movement_event_t event, movement_settings_t if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. - watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); - watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9); + watch_display_character_lp('0' + date_time.unit.second / 10, 8); + watch_display_character_lp('0' + date_time.unit.second % 10, 9); break; } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before minutes is the same. diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index fbc2c4b3e..35984472a 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -97,8 +97,8 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. - watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); - watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9); + watch_display_character_lp('0' + date_time.unit.second / 10, 8); + watch_display_character_lp('0' + date_time.unit.second % 10, 9); break; } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before minutes is the same. diff --git a/watch-library/shared/watch/watch_private_display.c b/watch-library/shared/watch/watch_private_display.c index c12957d9c..7a5ab1d97 100644 --- a/watch-library/shared/watch/watch_private_display.c +++ b/watch-library/shared/watch/watch_private_display.c @@ -33,7 +33,9 @@ static const uint32_t IndicatorSegments[] = { SLCD_SEGID(1, 10), // WATCH_INDICATOR_LAP }; -void watch_display_character(uint8_t character, uint8_t position) { +static bool invert = false; + +uint8_t watch_convert_char_to_segdata(uint8_t character, uint8_t position) { // special cases for positions 4 and 6 if (position == 4 || position == 6) { if (character == '7') character = '&'; // "lowercase" 7 @@ -64,65 +66,72 @@ void watch_display_character(uint8_t character, uint8_t position) { } else { if (character == 'R') character = 'r'; // R needs to be lowercase almost everywhere } - if (position == 0) { - watch_clear_pixel(0, 15); // clear funky ninth segment - } else { + if (position != 0) { if (character == 'I') character = 'l'; // uppercase I only works in position 0 } - uint64_t segmap = Segment_Map[position]; - uint64_t segdata = Character_Set[character - 0x20]; + return Character_Set[character - 0x20]; +} - for (int i = 0; i < 8; i++) { - uint8_t com = (segmap & 0xFF) >> 6; - if (com > 2) { - // COM3 means no segment exists; skip it. - segmap = segmap >> 8; - segdata = segdata >> 1; - continue; - } - uint8_t seg = segmap & 0x3F; - - if (segdata & 1) - watch_set_pixel(com, seg); - else - watch_clear_pixel(com, seg); - - segmap = segmap >> 8; - segdata = segdata >> 1; - } +uint8_t watch_convert_char_to_segdata_lp(uint8_t character) { + // i.e. remove any special casing cf watch_convert_char_to_segdata + return Character_Set[character - 0x20]; +} + +void watch_display_character(uint8_t character, uint8_t position) { + watch_display_segdata(watch_convert_char_to_segdata(character, position), position); + + // Handle the segments that are not in our usual map at all. + if (position == 0) { + if (character == 'B' || character == 'D' || character == '@') watch_set_pixel(0, 15); // bottom left serif + else watch_clear_pixel(0, 15); + + } else if (position == 1) { + if (character == 'T') watch_set_pixel(1, 12); // add descender (left hand side vertical line) - if (character == 'T' && position == 1) watch_set_pixel(1, 12); // add descender - else if (position == 0 && (character == 'B' || character == 'D' || character == '@')) watch_set_pixel(0, 15); // add funky ninth segment - else if (position == 1 && (character == 'B' || character == 'D' || character == '@')) watch_set_pixel(0, 12); // add funky ninth segment + if (character == 'B' || character == 'D' || character == '@') watch_set_pixel(0, 12); // top left serif + else watch_clear_pixel(0, 12); + } } -void watch_display_character_lp_seconds(uint8_t character, uint8_t position) { - // Will only work for digits and for positions 8 and 9 - but less code & checks to reduce power consumption +// Allow us to rotate characters 180 degrees (see watch_private_display.h for bit position diagram). +static const uint8_t vert_invert_map[8] = { + 3, 4, 5, 0, 1, 2, 6, 7, +}; + +void watch_display_segment(uint8_t bit_pos, uint8_t position, bool on) { + uint64_t segmap = Segment_Map[position] >> (8 * bit_pos); + uint8_t com = (segmap & 0xFF) >> 6; - uint64_t segmap = Segment_Map[position]; - uint64_t segdata = Character_Set[character - 0x20]; + if (com > 2) { + // COM3 means no segment exists; skip it. + return; + } + uint8_t seg = segmap & 0x3F; + + if (on) { + watch_set_pixel(com, seg); + } else { + watch_clear_pixel(com, seg); + } +} + +void watch_display_segdata(uint8_t segdata, uint8_t position) { for (int i = 0; i < 8; i++) { - uint8_t com = (segmap & 0xFF) >> 6; - if (com > 2) { - // COM3 means no segment exists; skip it. - segmap = segmap >> 8; - segdata = segdata >> 1; - continue; - } - uint8_t seg = segmap & 0x3F; - - if (segdata & 1) - watch_set_pixel(com, seg); - else - watch_clear_pixel(com, seg); - - segmap = segmap >> 8; - segdata = segdata >> 1; + uint8_t bit_pos = invert ? vert_invert_map[i] : i; + watch_display_segment(bit_pos, position, (1 << i) & segdata); } } +void watch_display_invert(bool inv) { + invert = inv; +} + +void watch_display_character_lp(uint8_t character, uint8_t position) { + watch_display_segdata(watch_convert_char_to_segdata_lp(character), position); +} + void watch_display_string(char *string, uint8_t position) { size_t i = 0; while(string[i] != 0) { @@ -133,7 +142,7 @@ void watch_display_string(char *string, uint8_t position) { // uncomment this line to see screen output on terminal, i.e. // FR 29 // 11 50 23 - // note that for partial displays (positon > 0) it will only show the characters that were updated. + // note that for partial displays (position > 0) it will only show the characters that were updated. // printf("________\n %c%c %c%c\n%c%c %c%c %c%c\n--------\n", (position > 0) ? ' ' : string[0], (position > 1) ? ' ' : string[1 - position], (position > 2) ? ' ' : string[2 - position], (position > 3) ? ' ' : string[3 - position], (position > 4) ? ' ' : string[4 - position], (position > 5) ? ' ' : string[5 - position], (position > 6) ? ' ' : string[6 - position], (position > 7) ? ' ' : string[7 - position], (position > 8) ? ' ' : string[8 - position], (position > 9) ? ' ' : string[9 - position]); } diff --git a/watch-library/shared/watch/watch_private_display.h b/watch-library/shared/watch/watch_private_display.h index 606b8dc3e..12c355ef6 100644 --- a/watch-library/shared/watch/watch_private_display.h +++ b/watch-library/shared/watch/watch_private_display.h @@ -27,6 +27,20 @@ #include "hpl_slcd_config.h" #include "driver_init.h" +// Bit position -> segment mapping for Character_Set. +// --0-- +// | | +// 5 1 +// | | +// --6-- +// | | +// 4 2 +// | | +// --3-- +// +// 7 is the middle divider (i.e. only position 0). + + static const uint8_t Character_Set[] = { 0b00000000, // @@ -141,8 +155,4 @@ static const uint64_t Segment_Map[] = { static const uint8_t Num_Chars = 10; -void watch_display_character(uint8_t character, uint8_t position); -void watch_display_character_lp_seconds(uint8_t character, uint8_t position); - - #endif diff --git a/watch-library/shared/watch/watch_slcd.h b/watch-library/shared/watch/watch_slcd.h index 3f550bb07..5dba43c48 100644 --- a/watch-library/shared/watch/watch_slcd.h +++ b/watch-library/shared/watch/watch_slcd.h @@ -74,7 +74,7 @@ void watch_clear_pixel(uint8_t com, uint8_t seg); void watch_clear_display(void); /** @brief Displays a string at the given position, starting from the top left. There are ten digits. - A space in any position will clear that digit. + * A space in any position will clear that digit. * @param string A null-terminated string. * @param position The position where you wish to start displaying the string. The day of week digits * are positions 0 and 1; the day of month digits are positions 2 and 3, and the main @@ -84,6 +84,79 @@ void watch_clear_display(void); */ void watch_display_string(char *string, uint8_t position); +/** @brief Displays a character at the given position, starting from the top left. There are ten digits. + * A space in any position will clear that digit. + * @param character A single character to display. + * @param position The position where you wish to display the character. The day of week digits + * are positions 0 and 1; the day of month digits are positions 2 and 3, and the main + * clock line occupies positions 4-9. + */ +void watch_display_character(uint8_t character, uint8_t position); + +/** @brief Displays a character at the given position, starting from the top left. There are ten digits. + * lp refers to 'low-power', as this does _not_ do any special casing to try to handle the pecularities of each position, + * This means it's best used to display numbers in the expected form. + * @param character A single character to display. + * @param position The position where you wish to display the character. The day of week digits + * are positions 0 and 1; the day of month digits are positions 2 and 3, and the main + * clock line occupies positions 4-9. + */ +void watch_display_character_lp(uint8_t character, uint8_t position); + +/** @brief Attempt to invert the display (i.e. rotate all characters 180 degrees). + * As with watch_display_character_lp, due to the limitations of the display this works best + * with numbers. This affects all subsequent calls to watch_display_string/character. + * @param inv whether to invert (true) or not invert (false). + */ +void watch_display_invert(bool inv); + +/** @brief Display arbitrary segment data at a particular position. + * cf watch_display_character. + * @param segdata Raw bits which are mapped to the position. See below. + * @param position The position where you wish to display the character. The day of week digits + * are positions 0 and 1; the day of month digits are positions 2 and 3, and the main + * clock line occupies positions 4-9. + * @note The bits are mapped as follows: + * + * --0-- + * | | + * 5 1 + * | | + * --6-- + * | | + * 4 2 + * | | + * --3-- + * + * 7 is the middle divider. i.e. only position 0. + * + * If you want to display/clear an individual segment, use watch_display_segment. + */ +void watch_display_segdata(uint8_t segdata, uint8_t position); + +/** @brief Generate segdata for a position that could be used as input to watch_display_segdata. + * @param character A single character to convert. + * @param position The position to generate the character for. + * @note You might wonder why the position is necessary for this function even though + * we don't actually display the character here. This is because we generate different segment + * data depending on the characte to handle the limitiations of the position. + */ +uint8_t watch_convert_char_to_segdata(uint8_t character, uint8_t position); + +/** @brief Generate segdata for a position that could be used as input to watch_display_segdata (lp). + * This is analagous to watch_display_character_lp. + * @param character A single character to convert. + */ +uint8_t watch_convert_char_to_segdata_lp(uint8_t character); + +/** @brief Map an individual segment to a particular position. See watch_display_segdata for segment info. + * This is similar to watch_set_pixel, but maps the segments for an arbitrary spot on the display. + * @param bit_pos Bit position (0 to 7). + * @param position Character position. + * @param on Whether to set or clear at that position. + */ +void watch_display_segment(uint8_t bit_pos, uint8_t position, bool on); + /** @brief Turns the colon segment on. */ void watch_set_colon(void); @@ -147,5 +220,6 @@ bool watch_tick_animation_is_running(void); * @details This will stop the animation and clear all segments in position 8. */ void watch_stop_tick_animation(void); + /// @} #endif