/** * @file app_display.c * @brief YF30137 7-segment display driver (Charlieplexing, 7 GPIOs). * * The YF30137 is a 7-pin Charlieplexed display: every segment is a LED wired * between a unique ordered pair of the 7 pins. Lighting one segment means * driving its "high" pin HIGH, its "low" pin LOW and leaving the other five * pins high-impedance, one segment at a time (the shared pins forbid lighting * several segments of one digit simultaneously). * * Pin mapping (net name -> MCU pin): * pin1 = LED_1 = PB0 pin2 = LED_2 = PB1 pin3 = LED_3 = PA0 * pin4 = LED_4 = PA1 pin5 = LED_5 = PB11 pin6 = LED_6 = PB13 * pin7 = LED_7 = PA6 * * Segment (s) of digit (d) is lit by pin HIGH -> pin LOW: * HIGH = ((s + d) % 7) + 1 * LOW = d + (s + d) / 7 (s: A=0..G=6, d: DIG1=1..DIG5=5) * * DIG6 indicators: * A6 (deg F) = pin7 HIGH, pin6 LOW * B6 (dot) = pin1 HIGH, pin7 LOW * * Other tasks update the display via display_set(); this task only reads the * volatile globals and scans, so it never blocks the callers. */ #include "app_display.h" #include "n32wb03x.h" #include "ns_delay.h" #include "FreeRTOS.h" #include "task.h" #include "app_wdt.h" /** @addtogroup * @{ */ /* Private typedef -----------------------------------------------------------*/ /** One display pin (GPIO port + pin). */ typedef struct { GPIO_Module *port; uint16_t pin; } disp_pin_t; /* Private define ------------------------------------------------------------*/ /** Per-segment on-time in us (sets brightness and scan rate). */ #define DISP_SEG_ON_US 250u /** Number of full seven-segment digits. */ #define DISP_DIGITS 5u /* Segment bit mask, bit0=A .. bit6=G. */ #define SEG_BLANK 0x00u #define SEG_MINUS 0x40u /* '-' = G only */ /* Decode marker codes (not a real digit). */ #define CHAR_MINUS 0x10u #define CHAR_BLANK 0xFFu /* Private constants ----------------------------------------------------------*/ /** Display pin table, index 0..6 = display pin 1..7. */ static const disp_pin_t s_disp_pin[7] = { { GPIOB, GPIO_PIN_0 }, /* pin1 = LED_1 = PB0 */ { GPIOB, GPIO_PIN_1 }, /* pin2 = LED_2 = PB1 */ { GPIOA, GPIO_PIN_0 }, /* pin3 = LED_3 = PA0 */ { GPIOA, GPIO_PIN_1 }, /* pin4 = LED_4 = PA1 */ { GPIOB, GPIO_PIN_11 }, /* pin5 = LED_5 = PB11 */ { GPIOB, GPIO_PIN_13 }, /* pin6 = LED_6 = PB13 */ { GPIOA, GPIO_PIN_6 }, /* pin7 = LED_7 = PA6 */ }; /* Seven-segment codes, bit0=A .. bit6=G. */ static const uint8_t s_seg_digit[10] = { 0x3Fu, /* 0 = ABCDEF */ 0x06u, /* 1 = BC */ 0x5Bu, /* 2 = ABDEG */ 0x4Fu, /* 3 = ABCDG */ 0x66u, /* 4 = BCFG */ 0x6Du, /* 5 = ACDFG */ 0x7Du, /* 6 = ACDEFG */ 0x07u, /* 7 = ABC */ 0x7Fu, /* 8 = ABCDEFG */ 0x6Fu, /* 9 = ABCDFG */ }; /* Hex digit codes, index 0..5 = A..F. */ static const uint8_t s_seg_hex[6] = { 0x77u, /* A = ABCEFG */ 0x7Cu, /* b = CDEFG */ 0x39u, /* C = ADEF */ 0x5Eu, /* d = BCDEG */ 0x79u, /* E = ADEFG */ 0x71u, /* F = AEFG */ }; /* Private variables ----------------------------------------------------------*/ /** Display value written by other tasks through display_set(). */ static volatile float g_disp_value = 0.0f; /** Display unit: DISP_TYPE_CELSIUS / DISP_TYPE_FAHRENHEIT. */ static volatile uint8_t g_disp_type = DISP_TYPE_CELSIUS; /** Number base: DISP_BASE_DECIMAL / DISP_BASE_HEX. */ static volatile uint8_t g_disp_base = DISP_BASE_DECIMAL; /* Private function prototypes -----------------------------------------------*/ /* Private functions ----------------------------------------------------------*/ /** * @brief Set a display pin to input (high-impedance). */ static void pin_input(uint8_t idx) { GPIO_InitType s; GPIO_InitStruct(&s); s.Pin = s_disp_pin[idx].pin; s.GPIO_Mode = GPIO_MODE_INPUT; s.GPIO_Pull = GPIO_NO_PULL; GPIO_InitPeripheral(s_disp_pin[idx].port, &s); } /** * @brief Set a display pin to push-pull output HIGH. */ static void pin_high(uint8_t idx) { GPIO_InitType s; GPIO_InitStruct(&s); s.Pin = s_disp_pin[idx].pin; s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(s_disp_pin[idx].port, &s); GPIO_SetBits(s_disp_pin[idx].port, s_disp_pin[idx].pin); } /** * @brief Set a display pin to push-pull output LOW. */ static void pin_low(uint8_t idx) { GPIO_InitType s; GPIO_InitStruct(&s); s.Pin = s_disp_pin[idx].pin; s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(s_disp_pin[idx].port, &s); GPIO_ResetBits(s_disp_pin[idx].port, s_disp_pin[idx].pin); } /** * @brief Put all seven display pins into high-impedance (input). */ static void all_pins_input(void) { uint8_t i; for (i = 0; i < 7; i++) { pin_input(i); } } /** * @brief Light one segment: pin h HIGH, pin l LOW, others stay high-Z, then * release both pins back to high-Z. */ static void seg_pair_on(uint8_t h, uint8_t l) { pin_high(h); pin_low(l); delay_n_10us(DISP_SEG_ON_US / 10u); pin_input(h); pin_input(l); } /** * @brief Light segment s (A=0..G=6) of digit d (1..5). */ static void seg_on(uint8_t s, uint8_t d) { uint8_t h = (uint8_t)((s + d) % 7u); uint8_t l = (uint8_t)((d - 1u) + (s + d) / 7u); seg_pair_on(h, l); } /** * @brief Light the DIG6 "deg F" indicator (A6). */ static void dig6_degf_on(void) { seg_pair_on(6u, 5u); /* pin7 HIGH, pin6 LOW */ } /** * @brief Light the DIG6 decimal-point indicator (B6). */ static void dig6_dp_on(void) { seg_pair_on(0u, 6u); /* pin1 HIGH, pin7 LOW */ } /** * @brief Map a decode character to its segment bit mask. */ static uint8_t seg_of_char(uint8_t c) { if (c <= 9u) { return s_seg_digit[c]; } if (c < 0x10u) { return s_seg_hex[c - 0xAu]; } if (c == CHAR_MINUS) { return SEG_MINUS; } return SEG_BLANK; } /** * @brief Decode the display value into 5 digit codes + a decimal-point flag. * @param value value to display * @param base DISP_BASE_DECIMAL (1 decimal place) or DISP_BASE_HEX * @param digits out: 5 codes (0..9, 0xA..0xF for hex, CHAR_MINUS, CHAR_BLANK) * @param dp_flag out: 1 = light the DIG6 decimal point */ static void display_decode(float value, uint8_t base, uint8_t *digits, uint8_t *dp_flag) { uint8_t i, j; *dp_flag = 0u; for (i = 0; i < DISP_DIGITS; i++) { digits[i] = CHAR_BLANK; } if (base == DISP_BASE_HEX) { uint32_t iv = (uint32_t)(int32_t)value; /* integer part */ uint8_t hx[DISP_DIGITS]; uint8_t lead = 1u; for (j = 0; j < DISP_DIGITS; j++) { hx[DISP_DIGITS - 1u - j] = (uint8_t)(iv & 0xFu); iv >>= 4u; } for (j = 0; j < DISP_DIGITS; j++) { if (lead && hx[j] == 0u && j < (DISP_DIGITS - 1u)) { digits[j] = CHAR_BLANK; /* blank leading zeros */ } else { lead = 0u; digits[j] = hx[j]; } } } else /* DISP_BASE_DECIMAL */ { int neg = (value < 0.0f); float a = neg ? -value : value; int32_t scaled; uint8_t dc[DISP_DIGITS]; uint8_t lead = 1u; if (a > 9999.9f) { a = 9999.9f; } scaled = (int32_t)(a * 10.0f + 0.5f); /* value * 10, 1 decimal */ for (j = 0; j < DISP_DIGITS; j++) { dc[DISP_DIGITS - 1u - j] = (uint8_t)(scaled % 10); scaled /= 10; } for (j = 0; j < DISP_DIGITS; j++) { if (lead && dc[j] == 0u && j < (DISP_DIGITS - 1u)) { digits[j] = CHAR_BLANK; /* blank leading zeros */ } else { lead = 0u; digits[j] = dc[j]; } } *dp_flag = 1u; /* decimal point always on */ if (neg) { for (j = 0; j < DISP_DIGITS; j++) { if (digits[j] != CHAR_BLANK) { break; } } if (j > 0u) { digits[j - 1u] = CHAR_MINUS; /* '-' just before the value */ } } } } /* Public functions -----------------------------------------------------------*/ /** * @brief Update the display content. * @param value floating value to display * @param type DISP_TYPE_CELSIUS / DISP_TYPE_FAHRENHEIT * @param base DISP_BASE_DECIMAL / DISP_BASE_HEX */ void display_set(float value, uint8_t type, uint8_t base) { /* A single scan frame may observe a partially-updated triple; that is * harmless for a display and keeps the setter lock-free. */ g_disp_value = value; g_disp_type = type; g_disp_base = base; } /** * @brief Display driver task: decode the global value and scan every lit * segment, one at a time, forever. */ void display_task(void *pvParameters) { (void)pvParameters; all_pins_input(); for (;;) { float value = g_disp_value; uint8_t type = g_disp_type; uint8_t base = g_disp_base; uint8_t digits[DISP_DIGITS]; uint8_t dp_flag; uint8_t d, s; uint8_t segmask; display_decode(value, base, digits, &dp_flag); app_wdt_heartbeat(); /* DIG1..DIG5 */ for (d = 1u; d <= DISP_DIGITS; d++) { segmask = seg_of_char(digits[d - 1u]); if (segmask == SEG_BLANK) { continue; } for (s = 0; s < 7u; s++) { if (segmask & (1u << s)) { seg_on(s, d); } } } /* DIG6 indicators */ if (type != DISP_TYPE_CELSIUS) { dig6_degf_on(); } if (dp_flag) { dig6_dp_on(); } /* one scan frame done: yield; a 20 ms refresh is enough for the display */ vTaskDelay(pdMS_TO_TICKS(20)); } } /** * @} */