mothercup/mcm-ddc-ble/src/app_display.c

396 lines
10 KiB
C

/**
* @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));
}
}
/**
* @}
*/