396 lines
10 KiB
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));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @}
|
|
*/
|