Compare commits
2 Commits
b5433f0403
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 908e7a0a4d | |||
| 2356cb4fe8 |
@@ -10,6 +10,7 @@ project(new_kbd)
|
||||
|
||||
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
|
||||
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
|
||||
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/ui)
|
||||
|
||||
zephyr_compile_definitions(
|
||||
LV_LVGL_H_INCLUDE_SIMPLE=1
|
||||
@@ -56,6 +57,7 @@ target_sources(app PRIVATE
|
||||
src/modules/time_manager_module.c
|
||||
src/modules/usb_hid_module.c
|
||||
src/modules/ble_hid_module.c
|
||||
src/ui/display_ui.c
|
||||
src/ui/fonts/ui_font_keyboard_small_18.c
|
||||
src/ui/fonts/ui_font_keyboard_time_48.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
config BOARD_ATGUIGU_KEYBOARD_DONGLE
|
||||
select SOC_NRF52833_QDAA
|
||||
@@ -0,0 +1,2 @@
|
||||
&pinctrl {
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/dts-v1/;
|
||||
#include <nordic/nrf52833_qdaa.dtsi>
|
||||
#include "atguigu_keyboard_dongle-pinctrl.dtsi"
|
||||
|
||||
/ {
|
||||
model = "Keyboard Dongle";
|
||||
compatible = "atguigu,atguigu-keyboard-dongle";
|
||||
|
||||
chosen {
|
||||
zephyr,sram = &sram0;
|
||||
zephyr,flash = &flash0;
|
||||
zephyr,code-partition = &slot0_partition;
|
||||
};
|
||||
};
|
||||
|
||||
&flash0 {
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
boot_partition: partition@0 {
|
||||
label = "mcuboot";
|
||||
reg = <0x00000000 DT_SIZE_K(48)>;
|
||||
};
|
||||
|
||||
slot0_partition: partition@c000 {
|
||||
label = "image-0";
|
||||
reg = <0x0000c000 DT_SIZE_K(220)>;
|
||||
};
|
||||
|
||||
slot1_partition: partition@43000 {
|
||||
label = "image-1";
|
||||
reg = <0x00043000 DT_SIZE_K(220)>;
|
||||
};
|
||||
|
||||
storage_partition: partition@7a000 {
|
||||
label = "storage";
|
||||
reg = <0x0007a000 DT_SIZE_K(24)>;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
identifier: atguigu_keyboard_dongle/nrf52833
|
||||
name: Keyboard Dongle
|
||||
vendor: atguigu
|
||||
type: mcu
|
||||
arch: arm
|
||||
ram: 128
|
||||
flash: 512
|
||||
toolchain:
|
||||
- zephyr
|
||||
supported: []
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_ARM_MPU=y
|
||||
CONFIG_HW_STACK_PROTECTION=y
|
||||
9
boards/atguigu/atguigu_keyboard_dongle/board.cmake
Normal file
9
boards/atguigu/atguigu_keyboard_dongle/board.cmake
Normal file
@@ -0,0 +1,9 @@
|
||||
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
|
||||
board_runner_args(jlink "--device=nRF52833_xxAA" "--speed=4000")
|
||||
board_runner_args(pyocd "--target=nrf52833" "--frequency=4000000")
|
||||
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
|
||||
5
boards/atguigu/atguigu_keyboard_dongle/board.yml
Normal file
5
boards/atguigu/atguigu_keyboard_dongle/board.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
board:
|
||||
name: atguigu_keyboard_dongle
|
||||
vendor: atguigu
|
||||
socs:
|
||||
- name: nrf52833
|
||||
@@ -0,0 +1,2 @@
|
||||
# Suppress "unique_unit_address_if_enabled" to handle some overlaps
|
||||
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")
|
||||
@@ -0,0 +1,2 @@
|
||||
config BOARD_ATGUIGU_MINI_KEYBOARD
|
||||
select SOC_NRF52840_QFAA
|
||||
@@ -0,0 +1,73 @@
|
||||
&pinctrl {
|
||||
qdec_default: qdec_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(QDEC_A, 0, 10)>,
|
||||
<NRF_PSEL(QDEC_B, 1, 6)>;
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
|
||||
qdec_sleep: qdec_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(QDEC_A, 0, 10)>,
|
||||
<NRF_PSEL(QDEC_B, 1, 6)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
led_spi_default: led_spi_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_MOSI, 0, 20)>;
|
||||
};
|
||||
};
|
||||
|
||||
led_spi_sleep: led_spi_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_MOSI, 0, 20)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
lcd_spi_default: lcd_spi_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 0, 28)>;
|
||||
};
|
||||
};
|
||||
|
||||
lcd_spi_sleep: lcd_spi_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 0, 28)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
i2c1_default: i2c1_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 1, 0)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 24)>;
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
|
||||
i2c1_sleep: i2c1_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 1, 0)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 24)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
pwm0_default: pwm0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
|
||||
};
|
||||
};
|
||||
pwm0_sleep: pwm0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
240
boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.dts
Normal file
240
boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.dts
Normal file
@@ -0,0 +1,240 @@
|
||||
/dts-v1/;
|
||||
#include <nordic/nrf52840_qiaa.dtsi>
|
||||
#include "atguigu_mini_keyboard-pinctrl.dtsi"
|
||||
#include <zephyr/dt-bindings/pinctrl/nrf-pinctrl.h>
|
||||
#include <zephyr/dt-bindings/led/led.h>
|
||||
|
||||
/ {
|
||||
model = "Mini Keyboard, 17 keys";
|
||||
compatible = "atguigu,atguigu-mini-keyboard";
|
||||
|
||||
chosen {
|
||||
zephyr,sram = &sram0;
|
||||
zephyr,flash = &flash0;
|
||||
zephyr,code-partition = &slot0_partition;
|
||||
zephyr,display = &st7789v3;
|
||||
};
|
||||
|
||||
aliases {
|
||||
backlight = &backlight;
|
||||
};
|
||||
|
||||
mode_sense: mode-sense {
|
||||
compatible = "voltage-divider";
|
||||
io-channels = <&adc 5>;
|
||||
output-ohms = <1>;
|
||||
full-ohms = <1>;
|
||||
};
|
||||
|
||||
battery_sense: battery-sense {
|
||||
compatible = "voltage-divider";
|
||||
io-channels = <&adc 7>;
|
||||
output-ohms = <100000>;
|
||||
full-ohms = <200000>;
|
||||
power-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
|
||||
power-on-sample-delay-us = <100>;
|
||||
};
|
||||
|
||||
pwm_leds: pwm_leds {
|
||||
compatible = "pwm-leds";
|
||||
status = "okay";
|
||||
backlight: pwm_led_0 {
|
||||
pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_INVERTED>;
|
||||
};
|
||||
};
|
||||
|
||||
led_0: led_0 {
|
||||
compatible = "gpio-leds";
|
||||
status = "okay";
|
||||
|
||||
chan0 {
|
||||
gpios = <&gpio0 17 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
};
|
||||
|
||||
led_1: led_1 {
|
||||
compatible = "gpio-leds";
|
||||
status = "okay";
|
||||
|
||||
chan0 {
|
||||
gpios = <&gpio1 2 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
};
|
||||
|
||||
mipi_dbi: mipi_dbi {
|
||||
compatible = "zephyr,mipi-dbi-spi";
|
||||
status = "okay";
|
||||
spi-dev = <&spi3>;
|
||||
dc-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
|
||||
reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
|
||||
write-only;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
st7789v3: st7789v@0 {
|
||||
compatible = "sitronix,st7789v";
|
||||
status = "okay";
|
||||
reg = <0>;
|
||||
mipi-max-frequency = <32000000>;
|
||||
width = <320>;
|
||||
height = <172>;
|
||||
x-offset = <0>;
|
||||
y-offset = <34>;
|
||||
vcom = <0x2b>;
|
||||
gctrl = <0x35>;
|
||||
vrhs = <0x11>;
|
||||
vdvs = <0x20>;
|
||||
mdac = <0xA0>;
|
||||
lcm = <0x2c>;
|
||||
colmod = <0x55>;
|
||||
gamma = <0x01>;
|
||||
porch-param = [ 0c 0c 00 33 33 ];
|
||||
cmd2en-param = [ 5a 69 02 01 ];
|
||||
pwctrl1-param = [ a4 a1 ];
|
||||
pvgam-param = [ d0 00 02 07 0a 28 32 44 42 06 0e 12 14 17 ];
|
||||
nvgam-param = [ d0 00 02 07 0a 28 31 54 47 0e 1c 17 1b 1e ];
|
||||
ram-param = [ 00 f0 ];
|
||||
rgb-param = [ c0 02 14 ];
|
||||
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
&spi2 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&led_spi_default>;
|
||||
pinctrl-1 = <&led_spi_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
cs-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>;
|
||||
|
||||
led_strip: ws2812@0 {
|
||||
compatible = "worldsemi,ws2812-spi";
|
||||
supply-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
|
||||
reg = <0>;
|
||||
spi-max-frequency = <8000000>;
|
||||
chain-length = <17>;
|
||||
color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>;
|
||||
spi-one-frame = <0xFC>;
|
||||
spi-zero-frame = <0xC0>;
|
||||
};
|
||||
};
|
||||
|
||||
&i2c1 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&i2c1_default>;
|
||||
pinctrl-1 = <&i2c1_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
clock-frequency = <400000>;
|
||||
|
||||
ip5306: pmic@75 {
|
||||
status = "okay";
|
||||
compatible = "injoinic,ip5306";
|
||||
reg = <0x75>;
|
||||
keepalive-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
|
||||
keepalive-interval-ms = <10000>;
|
||||
keepalive-offload;
|
||||
};
|
||||
};
|
||||
|
||||
/* 编码器 */
|
||||
&qdec {
|
||||
status = "okay";
|
||||
/* 引用上面定义的标签 */
|
||||
pinctrl-0 = <&qdec_default>;
|
||||
pinctrl-1 = <&qdec_sleep>;
|
||||
/* 指定 pinctrl-0 为默认,pinctrl-1 为睡眠 */
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
/* 别忘了 QDEC 必须的两个属性 */
|
||||
steps = <40>;
|
||||
led-pre = <0>;
|
||||
};
|
||||
|
||||
&flash0 {
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
boot_partition: partition@0 {
|
||||
label = "mcuboot";
|
||||
reg = <0x00000000 DT_SIZE_K(48)>;
|
||||
};
|
||||
|
||||
slot0_partition: partition@c000 {
|
||||
label = "image-0";
|
||||
reg = <0x0000c000 DT_SIZE_K(472)>;
|
||||
};
|
||||
|
||||
slot1_partition: partition@82000 {
|
||||
label = "image-1";
|
||||
reg = <0x00082000 DT_SIZE_K(472)>;
|
||||
};
|
||||
|
||||
storage_partition: partition@f8000 {
|
||||
label = "storage";
|
||||
reg = <0x000f8000 DT_SIZE_K(32)>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&spi3 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&lcd_spi_default>;
|
||||
pinctrl-1 = <&lcd_spi_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
cs-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
&adc {
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
channel@5 {
|
||||
reg = <5>;
|
||||
zephyr,gain = "ADC_GAIN_1_6";
|
||||
zephyr,reference = "ADC_REF_INTERNAL";
|
||||
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 20)>;
|
||||
zephyr,input-positive = <NRF_SAADC_AIN5>;
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
|
||||
channel@7 {
|
||||
reg = <7>;
|
||||
zephyr,gain = "ADC_GAIN_1_6";
|
||||
zephyr,reference = "ADC_REF_INTERNAL";
|
||||
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 20)>;
|
||||
zephyr,input-positive = <NRF_SAADC_AIN7>;
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
};
|
||||
|
||||
&pwm0 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&pwm0_default>;
|
||||
pinctrl-1 = <&pwm0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
};
|
||||
|
||||
&gpio0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&gpio1 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&gpiote {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&usbd {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&uicr {
|
||||
nfct-pins-as-gpios;
|
||||
gpio-as-nreset;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
identifier: atguigu_mini_keyboard/nrf52840
|
||||
name: Mini Keyboard, 17 keys
|
||||
vendor: atguigu
|
||||
type: mcu
|
||||
arch: arm
|
||||
ram: 256
|
||||
flash: 1024
|
||||
toolchain:
|
||||
- zephyr
|
||||
supported: []
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_ARM_MPU=y
|
||||
CONFIG_HW_STACK_PROTECTION=y
|
||||
9
boards/atguigu/atguigu_mini_keyboard/board.cmake
Normal file
9
boards/atguigu/atguigu_mini_keyboard/board.cmake
Normal file
@@ -0,0 +1,9 @@
|
||||
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
|
||||
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
|
||||
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
|
||||
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
|
||||
5
boards/atguigu/atguigu_mini_keyboard/board.yml
Normal file
5
boards/atguigu/atguigu_mini_keyboard/board.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
board:
|
||||
name: atguigu_mini_keyboard
|
||||
vendor: atguigu
|
||||
socs:
|
||||
- name: nrf52840
|
||||
2
boards/atguigu/atguigu_mini_keyboard/pre_dt_board.cmake
Normal file
2
boards/atguigu/atguigu_mini_keyboard/pre_dt_board.cmake
Normal file
@@ -0,0 +1,2 @@
|
||||
# Suppress "unique_unit_address_if_enabled" to handle some overlaps
|
||||
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")
|
||||
5
modules/ip5306/CMakeLists.txt
Normal file
5
modules/ip5306/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
zephyr_library()
|
||||
zephyr_library_sources_ifdef(CONFIG_IP5306 drivers/power/ip5306.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IP5306 drivers/power/ip5306_keepalive_sw.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IP5306_KEEPALIVE_HW_NRF drivers/power/ip5306_keepalive_nrf.c)
|
||||
zephyr_include_directories(include)
|
||||
5
modules/ip5306/Kconfig
Normal file
5
modules/ip5306/Kconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
menu "IP5306 Module"
|
||||
|
||||
rsource "drivers/power/Kconfig"
|
||||
|
||||
endmenu
|
||||
17
modules/ip5306/drivers/power/Kconfig
Normal file
17
modules/ip5306/drivers/power/Kconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
config IP5306
|
||||
bool "Injoinic IP5306 PMIC driver"
|
||||
depends on I2C && GPIO
|
||||
default n
|
||||
help
|
||||
Enable IP5306 PMIC driver over I2C.
|
||||
|
||||
config IP5306_KEEPALIVE_HW_NRF
|
||||
bool "Enable nRF keepalive HW offload backend"
|
||||
depends on IP5306 && SOC_FAMILY_NORDIC_NRF
|
||||
select NRFX_GPIOTE
|
||||
select NRFX_GPPI
|
||||
select NRFX_RTC2
|
||||
default y
|
||||
help
|
||||
Enable low-power keepalive offload backend using RTC2 + GPIOTE + GPPI.
|
||||
Runtime selection is controlled by devicetree property keepalive-offload.
|
||||
152
modules/ip5306/drivers/power/ip5306.c
Normal file
152
modules/ip5306/drivers/power/ip5306.c
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/power/ip5306.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "ip5306_priv.h"
|
||||
|
||||
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
|
||||
#include <soc_nrf_common.h>
|
||||
#define IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) \
|
||||
.keepalive_psel = NRF_DT_GPIOS_TO_PSEL_OR(DT_DRV_INST(inst), keepalive_gpios, 0),
|
||||
#else
|
||||
#define IP5306_CFG_KEEPALIVE_PSEL_INIT(inst)
|
||||
#endif
|
||||
|
||||
LOG_MODULE_REGISTER(ip5306, LOG_LEVEL_INF);
|
||||
|
||||
#define DT_DRV_COMPAT injoinic_ip5306
|
||||
|
||||
#define IP5306_REG_READ0 0x70
|
||||
#define IP5306_REG_READ1 0x71
|
||||
#define IP5306_STATUS_BIT BIT(3)
|
||||
|
||||
static int ip5306_read_reg(const struct device *dev, uint8_t reg, uint8_t *val)
|
||||
{
|
||||
const struct ip5306_config *cfg = dev->config;
|
||||
|
||||
return i2c_reg_read_byte_dt(&cfg->i2c, reg, val);
|
||||
}
|
||||
|
||||
static int ip5306_get_status_bit(const struct device *dev, uint8_t reg, bool *flag)
|
||||
{
|
||||
uint8_t value = 0U;
|
||||
int ret;
|
||||
|
||||
if (flag == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = ip5306_read_reg(dev, reg, &value);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
*flag = ((value & IP5306_STATUS_BIT) != 0U);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ip5306_api_is_charging(const struct device *dev, bool *charging)
|
||||
{
|
||||
return ip5306_get_status_bit(dev, IP5306_REG_READ0, charging);
|
||||
}
|
||||
|
||||
static int ip5306_api_is_charge_full(const struct device *dev, bool *full)
|
||||
{
|
||||
return ip5306_get_status_bit(dev, IP5306_REG_READ1, full);
|
||||
}
|
||||
|
||||
static void ip5306_keepalive_start(const struct device *dev)
|
||||
{
|
||||
struct ip5306_data *data = dev->data;
|
||||
const struct ip5306_config *cfg = dev->config;
|
||||
|
||||
if (!cfg->has_keepalive_gpio || (data->keepalive_interval_ms == 0U)) {
|
||||
data->backend = IP5306_KEEPALIVE_BACKEND_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 选择策略:
|
||||
* 1) DTS 显式请求硬件 offload 时,先尝试硬件后端;
|
||||
* 2) 若硬件依赖不可用或资源被占用,则告警后回退软件后端。
|
||||
*/
|
||||
if (cfg->keepalive_offload) {
|
||||
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
|
||||
int ret = ip5306_keepalive_hw_nrf_start(dev);
|
||||
|
||||
if (ret == 0) {
|
||||
LOG_INF("Keepalive backend=HW(nRF), pulse=%ums interval=%ums",
|
||||
data->keepalive_pulse_ms, data->keepalive_interval_ms);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_WRN("HW keepalive unavailable (%d), fallback to SW backend", ret);
|
||||
#else
|
||||
LOG_WRN("HW keepalive requested but HW backend is not built, fallback to SW backend");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
ip5306_keepalive_sw_start(dev);
|
||||
LOG_INF("Keepalive backend=SW, pulse=%ums interval=%ums",
|
||||
data->keepalive_pulse_ms, data->keepalive_interval_ms);
|
||||
}
|
||||
|
||||
static int ip5306_init(const struct device *dev)
|
||||
{
|
||||
const struct ip5306_config *cfg = dev->config;
|
||||
struct ip5306_data *data = dev->data;
|
||||
|
||||
if (!i2c_is_ready_dt(&cfg->i2c)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (cfg->has_keepalive_gpio) {
|
||||
if (!gpio_is_ready_dt(&cfg->keepalive_gpio)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (gpio_pin_configure_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_INACTIVE) != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
data->keepalive_high = false;
|
||||
data->dev = dev;
|
||||
data->backend = IP5306_KEEPALIVE_BACKEND_NONE;
|
||||
data->keepalive_pulse_ms = (cfg->keepalive_pulse_ms != 0U) ?
|
||||
cfg->keepalive_pulse_ms : IP5306_KEEPALIVE_DEFAULT_PULSE_MS;
|
||||
data->keepalive_interval_ms = (cfg->keepalive_interval_ms != 0U) ?
|
||||
cfg->keepalive_interval_ms : IP5306_KEEPALIVE_DEFAULT_INTERVAL_MS;
|
||||
|
||||
ip5306_keepalive_start(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ip5306_driver_api ip5306_api = {
|
||||
.is_charging = ip5306_api_is_charging,
|
||||
.is_charge_full = ip5306_api_is_charge_full,
|
||||
};
|
||||
|
||||
#define IP5306_DEFINE(inst) \
|
||||
static struct ip5306_data ip5306_data_##inst; \
|
||||
static const struct ip5306_config ip5306_cfg_##inst = { \
|
||||
.i2c = I2C_DT_SPEC_INST_GET(inst), \
|
||||
.keepalive_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, keepalive_gpios, {0}), \
|
||||
IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) \
|
||||
.keepalive_pulse_ms = DT_INST_PROP(inst, keepalive_pulse_ms), \
|
||||
.keepalive_interval_ms = DT_INST_PROP(inst, keepalive_interval_ms), \
|
||||
.has_keepalive_gpio = DT_INST_NODE_HAS_PROP(inst, keepalive_gpios), \
|
||||
.keepalive_offload = DT_INST_PROP_OR(inst, keepalive_offload, false), \
|
||||
}; \
|
||||
DEVICE_DT_INST_DEFINE(inst, ip5306_init, NULL, &ip5306_data_##inst, \
|
||||
&ip5306_cfg_##inst, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
||||
&ip5306_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(IP5306_DEFINE)
|
||||
198
modules/ip5306/drivers/power/ip5306_keepalive_nrf.c
Normal file
198
modules/ip5306/drivers/power/ip5306_keepalive_nrf.c
Normal file
@@ -0,0 +1,198 @@
|
||||
#include <errno.h>
|
||||
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include <nrfx_gpiote.h>
|
||||
#include <nrfx_rtc.h>
|
||||
|
||||
#include <helpers/nrfx_gppi.h>
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "ip5306_priv.h"
|
||||
|
||||
LOG_MODULE_DECLARE(ip5306, LOG_LEVEL_INF);
|
||||
|
||||
#define IP5306_KEEPALIVE_RTC_FREQUENCY_HZ 32768U
|
||||
#define IP5306_KEEPALIVE_RTC_CC_HIGH 0U
|
||||
#define IP5306_KEEPALIVE_RTC_CC_LOW 1U
|
||||
#define IP5306_KEEPALIVE_RTC_CC_PERIOD 2U
|
||||
#define IP5306_KEEPALIVE_MIN_PERIOD_TICKS 3U
|
||||
|
||||
/* nRF52 单核场景默认使用 GPIOTE0 + RTC2。 */
|
||||
static nrfx_gpiote_t ip5306_keepalive_gpiote = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE_INST_GET(0));
|
||||
static const nrfx_rtc_t ip5306_keepalive_rtc = NRFX_RTC_INSTANCE(2);
|
||||
static bool ip5306_keepalive_hw_claimed;
|
||||
|
||||
static void ip5306_keepalive_rtc_handler(nrfx_rtc_int_type_t int_type)
|
||||
{
|
||||
/*
|
||||
* 这里故意不处理任何中断逻辑:
|
||||
* 本模块的保活脉冲由 RTC event -> GPPI/PPI -> GPIOTE task 的硬件链路完成,
|
||||
* 不依赖线程或 ISR 参与,从而减少 CPU 唤醒次数、降低功耗。
|
||||
*/
|
||||
ARG_UNUSED(int_type);
|
||||
}
|
||||
|
||||
static uint32_t ip5306_keepalive_ms_to_rtc_ticks(uint32_t time_ms)
|
||||
{
|
||||
/* 四舍五入到最近 tick,降低长时间运行时累计漂移。 */
|
||||
uint64_t ticks = ((uint64_t)time_ms * IP5306_KEEPALIVE_RTC_FREQUENCY_HZ + 500U) / 1000U;
|
||||
|
||||
return (uint32_t)MAX(ticks, 1U);
|
||||
}
|
||||
|
||||
int ip5306_keepalive_hw_nrf_start(const struct device *dev)
|
||||
{
|
||||
struct ip5306_data *data = dev->data;
|
||||
const struct ip5306_config *cfg = dev->config;
|
||||
const bool active_high = ((cfg->keepalive_gpio.dt_flags & GPIO_ACTIVE_LOW) == 0U);
|
||||
const uint32_t period_ticks = ip5306_keepalive_ms_to_rtc_ticks(data->keepalive_interval_ms);
|
||||
uint32_t high_ticks = ip5306_keepalive_ms_to_rtc_ticks(data->keepalive_pulse_ms);
|
||||
int ret;
|
||||
|
||||
if (ip5306_keepalive_hw_claimed) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (period_ticks < IP5306_KEEPALIVE_MIN_PERIOD_TICKS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (high_ticks >= (period_ticks - 1U)) {
|
||||
high_ticks = period_ticks - 1U;
|
||||
}
|
||||
|
||||
if (!nrfx_gpiote_init_check(&ip5306_keepalive_gpiote)) {
|
||||
ret = nrfx_gpiote_init(&ip5306_keepalive_gpiote, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
|
||||
if ((ret != 0) && (ret != -EALREADY)) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = nrfx_gpiote_channel_alloc(&ip5306_keepalive_gpiote, &data->keepalive_gpiote_channel);
|
||||
if (ret != 0) {
|
||||
/* GPIOTE 通道不足通常意味着被其他驱动占用。 */
|
||||
LOG_ERR("Failed to allocate GPIOTE channel");
|
||||
return ret;
|
||||
}
|
||||
|
||||
const nrfx_gpiote_output_config_t output_config = {
|
||||
.drive = NRF_GPIO_PIN_S0S1,
|
||||
.input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
.pull = NRF_GPIO_PIN_NOPULL,
|
||||
};
|
||||
const nrfx_gpiote_task_config_t task_config = {
|
||||
.task_ch = data->keepalive_gpiote_channel,
|
||||
.polarity = NRF_GPIOTE_POLARITY_TOGGLE,
|
||||
.init_val = active_high ? NRF_GPIOTE_INITIAL_VALUE_LOW : NRF_GPIOTE_INITIAL_VALUE_HIGH,
|
||||
};
|
||||
|
||||
ret = nrfx_gpiote_output_configure(&ip5306_keepalive_gpiote, cfg->keepalive_psel,
|
||||
&output_config, &task_config);
|
||||
if (ret != 0) {
|
||||
goto err_free_channel;
|
||||
}
|
||||
|
||||
nrfx_gpiote_out_task_enable(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
|
||||
nrfx_gpiote_out_task_force(&ip5306_keepalive_gpiote, cfg->keepalive_psel, active_high ? 0U : 1U);
|
||||
|
||||
/*
|
||||
* RTC2 初始化前先检查是否已被外部占用:
|
||||
* 若占用则直接返回 -EBUSY,让上层走软件保活回退。
|
||||
*/
|
||||
if (nrfx_rtc_init_check(&ip5306_keepalive_rtc)) {
|
||||
ret = -EBUSY;
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG;
|
||||
|
||||
ret = nrfx_rtc_init(&ip5306_keepalive_rtc, &rtc_cfg, ip5306_keepalive_rtc_handler);
|
||||
if ((ret != 0) && (ret != -EALREADY)) {
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
nrfx_rtc_disable(&ip5306_keepalive_rtc);
|
||||
nrfx_rtc_counter_clear(&ip5306_keepalive_rtc);
|
||||
|
||||
/*
|
||||
* RTC 角色:时间基准,按 compare 点产生事件。
|
||||
* - CC0 到点 -> 产生 COMPARE0 event(脉冲上升沿触发点)
|
||||
* - CC1 到点 -> 产生 COMPARE1 event(脉冲下降沿触发点)
|
||||
* - CC2 到点 -> 触发周期边界,配合 SHORT 实现自动循环
|
||||
*/
|
||||
ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_HIGH, period_ticks - high_ticks - 10U, false);
|
||||
if (ret != 0) {
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_LOW, period_ticks - 10U, false);
|
||||
if (ret != 0) {
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_PERIOD, period_ticks, false);
|
||||
if (ret != 0) {
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
const uint32_t set_task = active_high ?
|
||||
nrfx_gpiote_set_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel) :
|
||||
nrfx_gpiote_clr_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
|
||||
const uint32_t clr_task = active_high ?
|
||||
nrfx_gpiote_clr_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel) :
|
||||
nrfx_gpiote_set_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
|
||||
const uint32_t high_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_0);
|
||||
const uint32_t low_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_1);
|
||||
const uint32_t period_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_2);
|
||||
const uint32_t clear_task = nrfx_rtc_task_address_get(&ip5306_keepalive_rtc, NRF_RTC_TASK_CLEAR);
|
||||
|
||||
/*
|
||||
* GPPI/PPI 角色:事件路由器。
|
||||
* 将 RTC compare event 直接连接到 GPIOTE task,不经过线程/ISR。
|
||||
*/
|
||||
ret = nrfx_gppi_conn_alloc(high_event, set_task, &data->keepalive_set_handle);
|
||||
if (ret != 0) {
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
ret = nrfx_gppi_conn_alloc(low_event, clr_task, &data->keepalive_clr_handle);
|
||||
if (ret != 0) {
|
||||
nrfx_gppi_conn_free(high_event, set_task, data->keepalive_set_handle);
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
/*
|
||||
* 某些 SoC/SDK 组合下 RTC SHORT 宏不可用,因此这里用第三条 GPPI 链路实现“周期自动清零”。
|
||||
* 这样仍保持纯硬件闭环:COMPARE2 event -> RTC CLEAR task,不需要 CPU/ISR 参与。
|
||||
*/
|
||||
ret = nrfx_gppi_conn_alloc(period_event, clear_task, &data->keepalive_period_handle);
|
||||
if (ret != 0) {
|
||||
nrfx_gppi_conn_free(low_event, clr_task, data->keepalive_clr_handle);
|
||||
nrfx_gppi_conn_free(high_event, set_task, data->keepalive_set_handle);
|
||||
goto err_disable_task;
|
||||
}
|
||||
|
||||
/*
|
||||
* GPIOTE 角色:GPIO 执行器。
|
||||
* 收到 task 后立即对目标 pin 执行 SET/CLR,输出保活脉冲波形。
|
||||
*/
|
||||
nrfx_gppi_conn_enable(data->keepalive_set_handle);
|
||||
nrfx_gppi_conn_enable(data->keepalive_clr_handle);
|
||||
nrfx_gppi_conn_enable(data->keepalive_period_handle);
|
||||
nrfx_rtc_enable(&ip5306_keepalive_rtc);
|
||||
|
||||
ip5306_keepalive_hw_claimed = true;
|
||||
data->backend = IP5306_KEEPALIVE_BACKEND_HW_NRF;
|
||||
data->dev = dev;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_task:
|
||||
nrfx_gpiote_out_task_disable(&ip5306_keepalive_gpiote, cfg->keepalive_psel);
|
||||
err_free_channel:
|
||||
(void)nrfx_gpiote_channel_free(&ip5306_keepalive_gpiote, data->keepalive_gpiote_channel);
|
||||
return ret;
|
||||
}
|
||||
54
modules/ip5306/drivers/power/ip5306_keepalive_sw.c
Normal file
54
modules/ip5306/drivers/power/ip5306_keepalive_sw.c
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "ip5306_priv.h"
|
||||
|
||||
LOG_MODULE_DECLARE(ip5306, LOG_LEVEL_INF);
|
||||
|
||||
void ip5306_keepalive_sw_work_handler(struct k_work *work)
|
||||
{
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
struct ip5306_data *data = CONTAINER_OF(dwork, struct ip5306_data, keepalive_work);
|
||||
const struct ip5306_config *cfg = data->dev->config;
|
||||
int ret;
|
||||
|
||||
if ((data->backend != IP5306_KEEPALIVE_BACKEND_SW) || (data->keepalive_interval_ms == 0U))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data->keepalive_high)
|
||||
{
|
||||
ret = gpio_pin_set_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_ACTIVE);
|
||||
if (ret != 0)
|
||||
{
|
||||
LOG_ERR("SW keepalive set high failed: %d", ret);
|
||||
(void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms));
|
||||
return;
|
||||
}
|
||||
|
||||
data->keepalive_high = true;
|
||||
(void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_pulse_ms));
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = gpio_pin_set_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret != 0)
|
||||
{
|
||||
LOG_ERR("SW keepalive set low failed: %d", ret);
|
||||
}
|
||||
|
||||
data->keepalive_high = false;
|
||||
(void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms));
|
||||
}
|
||||
}
|
||||
|
||||
int ip5306_keepalive_sw_start(const struct device *dev)
|
||||
{
|
||||
struct ip5306_data *data = dev->data;
|
||||
|
||||
data->backend = IP5306_KEEPALIVE_BACKEND_SW;
|
||||
data->dev = dev;
|
||||
k_work_init_delayable(&data->keepalive_work, ip5306_keepalive_sw_work_handler);
|
||||
return (int)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms));
|
||||
}
|
||||
58
modules/ip5306/drivers/power/ip5306_priv.h
Normal file
58
modules/ip5306/drivers/power/ip5306_priv.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef IP5306_KEEPALIVE_PRIV_H_
|
||||
#define IP5306_KEEPALIVE_PRIV_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
|
||||
#include <helpers/nrfx_gppi.h>
|
||||
#endif
|
||||
|
||||
#define IP5306_KEEPALIVE_DEFAULT_PULSE_MS 100U
|
||||
#define IP5306_KEEPALIVE_DEFAULT_INTERVAL_MS 20000U
|
||||
|
||||
enum ip5306_keepalive_backend {
|
||||
IP5306_KEEPALIVE_BACKEND_NONE,
|
||||
IP5306_KEEPALIVE_BACKEND_SW,
|
||||
IP5306_KEEPALIVE_BACKEND_HW_NRF,
|
||||
};
|
||||
|
||||
struct ip5306_config {
|
||||
struct i2c_dt_spec i2c;
|
||||
struct gpio_dt_spec keepalive_gpio;
|
||||
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
|
||||
uint32_t keepalive_psel;
|
||||
#endif
|
||||
uint32_t keepalive_pulse_ms;
|
||||
uint32_t keepalive_interval_ms;
|
||||
bool has_keepalive_gpio;
|
||||
bool keepalive_offload;
|
||||
};
|
||||
|
||||
struct ip5306_data {
|
||||
struct k_work_delayable keepalive_work;
|
||||
bool keepalive_high;
|
||||
uint32_t keepalive_pulse_ms;
|
||||
uint32_t keepalive_interval_ms;
|
||||
enum ip5306_keepalive_backend backend;
|
||||
const struct device *dev;
|
||||
|
||||
#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF)
|
||||
uint8_t keepalive_gpiote_channel;
|
||||
nrfx_gppi_handle_t keepalive_set_handle;
|
||||
nrfx_gppi_handle_t keepalive_clr_handle;
|
||||
nrfx_gppi_handle_t keepalive_period_handle;
|
||||
#endif
|
||||
};
|
||||
|
||||
int ip5306_keepalive_sw_start(const struct device *dev);
|
||||
|
||||
int ip5306_keepalive_hw_nrf_start(const struct device *dev);
|
||||
|
||||
#endif /* IP5306_KEEPALIVE_PRIV_H_ */
|
||||
33
modules/ip5306/dts/bindings/power/injoinic,ip5306.yaml
Normal file
33
modules/ip5306/dts/bindings/power/injoinic,ip5306.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
title: Injoinic IP5306 PMIC
|
||||
|
||||
description: |
|
||||
Injoinic IP5306 power management IC accessed over I2C.
|
||||
This binding exposes two status bits and an optional KEY pin keepalive
|
||||
pulse output.
|
||||
|
||||
compatible: "injoinic,ip5306"
|
||||
|
||||
include: i2c-device.yaml
|
||||
|
||||
properties:
|
||||
keepalive-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
GPIO connected to KEY pin. The driver sends periodic pulses on this pin
|
||||
to keep boost output alive.
|
||||
|
||||
keepalive-pulse-ms:
|
||||
type: int
|
||||
default: 100
|
||||
description: Pulse width in milliseconds for each keepalive pulse.
|
||||
|
||||
keepalive-interval-ms:
|
||||
type: int
|
||||
default: 20000
|
||||
description: Interval in milliseconds between keepalive pulses.
|
||||
|
||||
keepalive-offload:
|
||||
type: boolean
|
||||
description: |
|
||||
Prefer hardware keepalive offload backend when available on this SoC.
|
||||
If not set, driver always uses software keepalive backend.
|
||||
1
modules/ip5306/dts/bindings/vendor-prefixes.txt
Normal file
1
modules/ip5306/dts/bindings/vendor-prefixes.txt
Normal file
@@ -0,0 +1 @@
|
||||
injoinic Injoinic
|
||||
48
modules/ip5306/include/zephyr/drivers/power/ip5306.h
Normal file
48
modules/ip5306/include/zephyr/drivers/power/ip5306.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_
|
||||
#define ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int (*ip5306_get_flag_t)(const struct device *dev, bool *flag);
|
||||
|
||||
struct ip5306_driver_api {
|
||||
ip5306_get_flag_t is_charging;
|
||||
ip5306_get_flag_t is_charge_full;
|
||||
};
|
||||
|
||||
static inline int ip5306_is_charging(const struct device *dev, bool *charging)
|
||||
{
|
||||
const struct ip5306_driver_api *api =
|
||||
(const struct ip5306_driver_api *)dev->api;
|
||||
|
||||
if (api == NULL || api->is_charging == NULL) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
return api->is_charging(dev, charging);
|
||||
}
|
||||
|
||||
static inline int ip5306_is_charge_full(const struct device *dev, bool *full)
|
||||
{
|
||||
const struct ip5306_driver_api *api =
|
||||
(const struct ip5306_driver_api *)dev->api;
|
||||
|
||||
if (api == NULL || api->is_charge_full == NULL) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
return api->is_charge_full(dev, full);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_ */
|
||||
5
modules/ip5306/zephyr/module.yml
Normal file
5
modules/ip5306/zephyr/module.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
build:
|
||||
cmake: .
|
||||
kconfig: Kconfig
|
||||
settings:
|
||||
dts_root: .
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "battery_status_event.h"
|
||||
#include "display_theme_event.h"
|
||||
#include "display_ui.h"
|
||||
#include "keyboard_led_event.h"
|
||||
#include "mode_event.h"
|
||||
#include "time_manager.h"
|
||||
@@ -39,19 +40,6 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
#define DISPLAY_DEMO_BASE_HOUR 14
|
||||
#define DISPLAY_DEMO_BASE_MIN 28
|
||||
#define DISPLAY_DEMO_BASE_SEC 36
|
||||
#define DISPLAY_SYMBOL_PLUG "\xEF\x87\xA6" /* U+F1E6, custom plug glyph in ui_font_keyboard_small_18 */
|
||||
|
||||
LV_FONT_DECLARE(ui_font_keyboard_small_18);
|
||||
LV_FONT_DECLARE(ui_font_keyboard_time_48);
|
||||
|
||||
enum display_status_id
|
||||
{
|
||||
DISPLAY_STATUS_USB = 0,
|
||||
DISPLAY_STATUS_BLE,
|
||||
DISPLAY_STATUS_NUMLOCK,
|
||||
DISPLAY_STATUS_CAPSLOCK,
|
||||
DISPLAY_STATUS_COUNT,
|
||||
};
|
||||
|
||||
enum display_pm_state
|
||||
{
|
||||
@@ -59,39 +47,6 @@ enum display_pm_state
|
||||
DISPLAY_PM_STATE_OFF,
|
||||
};
|
||||
|
||||
struct display_ui_state
|
||||
{
|
||||
lv_color_t theme_color;
|
||||
lv_color_t inactive_border_color;
|
||||
uint8_t battery_level;
|
||||
mode_type_t mode;
|
||||
uint8_t led_mask;
|
||||
uint8_t battery_flags;
|
||||
bool status_enabled[DISPLAY_STATUS_COUNT];
|
||||
lv_obj_t *status_badges[DISPLAY_STATUS_COUNT];
|
||||
lv_obj_t *status_labels[DISPLAY_STATUS_COUNT];
|
||||
lv_obj_t *battery_icon;
|
||||
lv_obj_t *battery_label;
|
||||
lv_obj_t *battery_state_label;
|
||||
lv_obj_t *date_label;
|
||||
lv_obj_t *time_label;
|
||||
};
|
||||
|
||||
struct display_ctx
|
||||
{
|
||||
const struct device *dev;
|
||||
struct display_capabilities caps;
|
||||
struct k_work_delayable update_work;
|
||||
struct k_work_delayable idle_work;
|
||||
struct k_work_delayable theme_save_work;
|
||||
struct display_ui_state ui;
|
||||
uint32_t tick_count;
|
||||
enum display_pm_state pm_state;
|
||||
bool initialized;
|
||||
bool theme_storage_dirty;
|
||||
bool theme_storage_loaded;
|
||||
};
|
||||
|
||||
struct display_theme_storage {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
@@ -99,6 +54,23 @@ struct display_theme_storage {
|
||||
uint8_t valid_marker;
|
||||
};
|
||||
|
||||
struct display_ctx
|
||||
{
|
||||
const struct device *dev;
|
||||
struct display_capabilities caps;
|
||||
struct k_work_delayable update_work;
|
||||
struct k_work_delayable idle_work;
|
||||
struct k_work_delayable theme_save_work;
|
||||
struct display_ui_model ui;
|
||||
uint32_t tick_count;
|
||||
enum display_pm_state pm_state;
|
||||
bool initialized;
|
||||
bool theme_storage_dirty;
|
||||
bool theme_storage_loaded;
|
||||
char date_text[16];
|
||||
char time_text[16];
|
||||
};
|
||||
|
||||
static struct display_ctx disp = {
|
||||
.dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)),
|
||||
.ui.theme_color = LV_COLOR_MAKE(0x4C, 0xC9, 0xF0),
|
||||
@@ -106,7 +78,7 @@ static struct display_ctx disp = {
|
||||
.ui.battery_level = 15U,
|
||||
.ui.battery_flags = 0U,
|
||||
.ui.mode = MODE_TYPE_USB,
|
||||
.ui.status_enabled = {true, true, false, true},
|
||||
.ui.led_mask = 0U,
|
||||
.pm_state = DISPLAY_PM_STATE_OFF,
|
||||
};
|
||||
|
||||
@@ -115,15 +87,6 @@ static struct display_theme_storage display_theme_storage;
|
||||
static const struct led_dt_spec display_backlight =
|
||||
LED_DT_SPEC_GET(DT_NODELABEL(backlight));
|
||||
|
||||
static const char *const g_status_texts[DISPLAY_STATUS_COUNT] = {
|
||||
LV_SYMBOL_USB,
|
||||
LV_SYMBOL_BLUETOOTH,
|
||||
"1",
|
||||
"A",
|
||||
};
|
||||
|
||||
static void display_refresh_all_locked(void);
|
||||
|
||||
static int display_theme_store(const struct display_theme_storage *storage)
|
||||
{
|
||||
char key[] = MODULE_NAME "/" DISPLAY_THEME_STORAGE_KEY;
|
||||
@@ -226,7 +189,6 @@ static void display_schedule_idle_timeout(k_timeout_t delay)
|
||||
k_work_reschedule(&disp.idle_work, delay);
|
||||
}
|
||||
|
||||
/* 背光初始化独立处理,避免 UI 创建逻辑里混入硬件使能细节。 */
|
||||
static int display_backlight_set(uint8_t brightness)
|
||||
{
|
||||
int err;
|
||||
@@ -252,7 +214,59 @@ static bool display_is_active(void)
|
||||
return disp.pm_state == DISPLAY_PM_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
/* 只负责保活屏幕空闲计时,不隐式点亮屏幕。 */
|
||||
static void display_update_datetime_text(void)
|
||||
{
|
||||
struct time_manager_snapshot snapshot;
|
||||
int err = time_manager_get_snapshot(&snapshot);
|
||||
|
||||
if (!err)
|
||||
{
|
||||
time_t local_seconds;
|
||||
struct tm tm_buf;
|
||||
struct tm *tm_info;
|
||||
|
||||
local_seconds = (time_t)(snapshot.utc_ms / 1000ULL) +
|
||||
(time_t)((int32_t)snapshot.timezone_min * 60);
|
||||
tm_info = gmtime_r(&local_seconds, &tm_buf);
|
||||
|
||||
if (tm_info)
|
||||
{
|
||||
unsigned int year = (unsigned int)(tm_info->tm_year + 1900);
|
||||
unsigned int month = (unsigned int)(tm_info->tm_mon + 1);
|
||||
unsigned int day = (unsigned int)tm_info->tm_mday;
|
||||
unsigned int hour = (unsigned int)tm_info->tm_hour;
|
||||
unsigned int minute = (unsigned int)tm_info->tm_min;
|
||||
unsigned int second = (unsigned int)tm_info->tm_sec;
|
||||
|
||||
snprintk(disp.date_text, sizeof(disp.date_text), "%04u/%02u/%02u",
|
||||
year, month, day);
|
||||
snprintk(disp.time_text, sizeof(disp.time_text), "%02u:%02u:%02u",
|
||||
hour, minute, second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t seconds = disp.tick_count;
|
||||
uint32_t hour = (DISPLAY_DEMO_BASE_HOUR + (seconds / 3600U)) % 24U;
|
||||
uint32_t minute = (DISPLAY_DEMO_BASE_MIN + ((seconds / 60U) % 60U)) % 60U;
|
||||
uint32_t second = (DISPLAY_DEMO_BASE_SEC + (seconds % 60U)) % 60U;
|
||||
|
||||
snprintk(disp.date_text, sizeof(disp.date_text), "%04d/%02d/%02d",
|
||||
DISPLAY_DEMO_BASE_YEAR,
|
||||
DISPLAY_DEMO_BASE_MONTH,
|
||||
DISPLAY_DEMO_BASE_DAY);
|
||||
snprintk(disp.time_text, sizeof(disp.time_text), "%02u:%02u:%02u",
|
||||
hour, minute, second);
|
||||
}
|
||||
}
|
||||
|
||||
static void display_refresh_all_locked(void)
|
||||
{
|
||||
display_update_datetime_text();
|
||||
display_ui_refresh_all(&disp.ui, disp.date_text, disp.time_text);
|
||||
}
|
||||
|
||||
static void display_kick_idle_timer(void)
|
||||
{
|
||||
if (!disp.initialized || !display_is_active())
|
||||
@@ -261,7 +275,6 @@ static void display_kick_idle_timer(void)
|
||||
display_schedule_idle_timeout(K_MINUTES(DISPLAY_IDLE_TIMEOUT_MIN));
|
||||
}
|
||||
|
||||
/* 熄屏时同时关闭刷新和背光,并将模块状态切到 OFF。 */
|
||||
static void display_sleep(void)
|
||||
{
|
||||
int err;
|
||||
@@ -287,7 +300,6 @@ static void display_sleep(void)
|
||||
module_set_state(MODULE_STATE_OFF);
|
||||
}
|
||||
|
||||
/* 唤醒屏幕后立刻刷新 UI,并重新启动定时刷新和空闲超时。 */
|
||||
static void display_wake(void)
|
||||
{
|
||||
int err;
|
||||
@@ -323,309 +335,6 @@ static void display_idle_timeout_fn(struct k_work *work)
|
||||
display_sleep();
|
||||
}
|
||||
|
||||
/* 电量颜色与 PC 原型保持一致,顶部状态区能快速表达健康度。 */
|
||||
static lv_color_t display_get_battery_color(uint8_t battery_level)
|
||||
{
|
||||
if (battery_level > 70U)
|
||||
return lv_color_hex(0x8BD450);
|
||||
|
||||
if (battery_level >= 20U)
|
||||
return lv_color_hex(0xF4D35E);
|
||||
|
||||
return lv_color_hex(0xE63946);
|
||||
}
|
||||
|
||||
/* 电池图标由精简图标字体提供,不再依赖 LVGL 内建字体资源。 */
|
||||
static const char *display_get_battery_symbol(uint8_t battery_level)
|
||||
{
|
||||
if (battery_level > 85U)
|
||||
return LV_SYMBOL_BATTERY_FULL;
|
||||
|
||||
if (battery_level > 60U)
|
||||
return LV_SYMBOL_BATTERY_3;
|
||||
|
||||
if (battery_level > 35U)
|
||||
return LV_SYMBOL_BATTERY_2;
|
||||
|
||||
if (battery_level >= 20U)
|
||||
return LV_SYMBOL_BATTERY_1;
|
||||
|
||||
return LV_SYMBOL_BATTERY_EMPTY;
|
||||
}
|
||||
|
||||
/* 模式事件只需要驱动 USB/BLE 两个 badge,2.4G 模式两者都灭。 */
|
||||
static void display_update_mode_state(mode_type_t mode)
|
||||
{
|
||||
disp.ui.mode = mode;
|
||||
disp.ui.status_enabled[DISPLAY_STATUS_USB] = (mode == MODE_TYPE_USB);
|
||||
disp.ui.status_enabled[DISPLAY_STATUS_BLE] = (mode == MODE_TYPE_BLE);
|
||||
}
|
||||
|
||||
/* 最新原型只显示 NumLock 和 CapsLock,不再展示 ScrollLock。 */
|
||||
static void display_update_keyboard_led_state(uint8_t led_mask)
|
||||
{
|
||||
disp.ui.led_mask = led_mask;
|
||||
disp.ui.status_enabled[DISPLAY_STATUS_NUMLOCK] =
|
||||
(led_mask & KEYBOARD_LED_MASK_NUM_LOCK) != 0U;
|
||||
disp.ui.status_enabled[DISPLAY_STATUS_CAPSLOCK] =
|
||||
(led_mask & KEYBOARD_LED_MASK_CAPS_LOCK) != 0U;
|
||||
}
|
||||
|
||||
/* 底部状态条的亮灭与边框颜色联动更新,保持原型机视觉语言。 */
|
||||
static void display_refresh_status_bar_locked(void)
|
||||
{
|
||||
for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++)
|
||||
{
|
||||
lv_obj_t *badge = disp.ui.status_badges[i];
|
||||
lv_obj_t *label = disp.ui.status_labels[i];
|
||||
bool active = disp.ui.status_enabled[i];
|
||||
|
||||
if (!badge || !label)
|
||||
continue;
|
||||
|
||||
lv_obj_set_style_border_width(badge, 4, 0);
|
||||
lv_obj_set_style_border_color(badge,
|
||||
active ? disp.ui.theme_color : disp.ui.inactive_border_color,
|
||||
0);
|
||||
lv_obj_set_style_bg_color(badge,
|
||||
active ? lv_color_hex(0x1D2735) : lv_color_hex(0x161A20),
|
||||
0);
|
||||
lv_obj_set_style_text_color(label,
|
||||
active ? lv_color_white() : lv_color_hex(0x7C8798),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 电池图标、百分比和状态图标分开更新,便于独立配色。 */
|
||||
static void display_refresh_battery_locked(void)
|
||||
{
|
||||
char battery_text[8];
|
||||
lv_color_t battery_color;
|
||||
const char *state_symbol = "";
|
||||
lv_color_t state_color = lv_color_white();
|
||||
|
||||
if (!disp.ui.battery_icon || !disp.ui.battery_label || !disp.ui.battery_state_label)
|
||||
return;
|
||||
|
||||
battery_color = display_get_battery_color(disp.ui.battery_level);
|
||||
snprintk(battery_text, sizeof(battery_text), "%u%%", disp.ui.battery_level);
|
||||
|
||||
if ((disp.ui.battery_flags & BATTERY_STATUS_FLAG_FULL) != 0U)
|
||||
{
|
||||
state_symbol = DISPLAY_SYMBOL_PLUG;
|
||||
state_color = lv_color_hex(0x4C9EF5);
|
||||
}
|
||||
else if ((disp.ui.battery_flags & BATTERY_STATUS_FLAG_CHARGING) != 0U)
|
||||
{
|
||||
state_symbol = LV_SYMBOL_CHARGE;
|
||||
state_color = lv_color_hex(0xF4D35E);
|
||||
}
|
||||
|
||||
lv_label_set_text(disp.ui.battery_icon,
|
||||
display_get_battery_symbol(disp.ui.battery_level));
|
||||
lv_obj_set_style_text_color(disp.ui.battery_icon, battery_color, 0);
|
||||
lv_label_set_text(disp.ui.battery_label, battery_text);
|
||||
lv_label_set_text(disp.ui.battery_state_label, state_symbol);
|
||||
lv_obj_set_style_text_color(disp.ui.battery_state_label, state_color, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* 时间优先显示 time_manager 的真实快照。
|
||||
* 如果当前尚未同步,则退回到固定基准上的 demo 时间,保证 UI 结构始终可见。
|
||||
*/
|
||||
static void display_refresh_datetime_locked(void)
|
||||
{
|
||||
struct time_manager_snapshot snapshot;
|
||||
char date_text[16];
|
||||
char time_text[16];
|
||||
int err = time_manager_get_snapshot(&snapshot);
|
||||
|
||||
if (!disp.ui.date_label || !disp.ui.time_label)
|
||||
return;
|
||||
|
||||
if (!err)
|
||||
{
|
||||
time_t local_seconds;
|
||||
struct tm tm_buf;
|
||||
struct tm *tm_info;
|
||||
|
||||
local_seconds = (time_t)(snapshot.utc_ms / 1000ULL) +
|
||||
(time_t)((int32_t)snapshot.timezone_min * 60);
|
||||
tm_info = gmtime_r(&local_seconds, &tm_buf);
|
||||
|
||||
if (tm_info)
|
||||
{
|
||||
unsigned int year = (unsigned int)(tm_info->tm_year + 1900);
|
||||
unsigned int month = (unsigned int)(tm_info->tm_mon + 1);
|
||||
unsigned int day = (unsigned int)tm_info->tm_mday;
|
||||
unsigned int hour = (unsigned int)tm_info->tm_hour;
|
||||
unsigned int minute = (unsigned int)tm_info->tm_min;
|
||||
unsigned int second = (unsigned int)tm_info->tm_sec;
|
||||
|
||||
snprintk(date_text, sizeof(date_text), "%04u/%02u/%02u",
|
||||
year, month, day);
|
||||
snprintk(time_text, sizeof(time_text), "%02u:%02u:%02u",
|
||||
hour, minute, second);
|
||||
lv_label_set_text(disp.ui.date_label, date_text);
|
||||
lv_label_set_text(disp.ui.time_label, time_text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t seconds = disp.tick_count;
|
||||
uint32_t hour = (DISPLAY_DEMO_BASE_HOUR + (seconds / 3600U)) % 24U;
|
||||
uint32_t minute = (DISPLAY_DEMO_BASE_MIN + ((seconds / 60U) % 60U)) % 60U;
|
||||
uint32_t second = (DISPLAY_DEMO_BASE_SEC + (seconds % 60U)) % 60U;
|
||||
|
||||
snprintk(date_text, sizeof(date_text), "%04d/%02d/%02d",
|
||||
DISPLAY_DEMO_BASE_YEAR,
|
||||
DISPLAY_DEMO_BASE_MONTH,
|
||||
DISPLAY_DEMO_BASE_DAY);
|
||||
snprintk(time_text, sizeof(time_text), "%02u:%02u:%02u",
|
||||
hour, minute, second);
|
||||
lv_label_set_text(disp.ui.date_label, date_text);
|
||||
lv_label_set_text(disp.ui.time_label, time_text);
|
||||
}
|
||||
}
|
||||
|
||||
/* 一次性把缓存状态刷到 UI,避免控件创建与状态恢复互相耦合。 */
|
||||
static void display_refresh_all_locked(void)
|
||||
{
|
||||
display_refresh_status_bar_locked();
|
||||
display_refresh_battery_locked();
|
||||
display_refresh_datetime_locked();
|
||||
}
|
||||
|
||||
/* 状态 badge 保持原型尺寸和圆角,确保在 320x172 面板上视觉一致。 */
|
||||
static void display_create_status_chip_locked(lv_obj_t *parent, enum display_status_id id)
|
||||
{
|
||||
lv_obj_t *badge = lv_obj_create(parent);
|
||||
lv_obj_t *label;
|
||||
|
||||
lv_obj_remove_style_all(badge);
|
||||
lv_obj_set_size(badge, 50, 32);
|
||||
lv_obj_set_style_radius(badge, 10, 0);
|
||||
lv_obj_set_style_bg_opa(badge, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_pad_all(badge, 0, 0);
|
||||
|
||||
label = lv_label_create(badge);
|
||||
lv_label_set_text(label, g_status_texts[id]);
|
||||
lv_obj_set_width(label, LV_PCT(100));
|
||||
lv_obj_set_style_text_font(label, &ui_font_keyboard_small_18, 0);
|
||||
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_center(label);
|
||||
|
||||
disp.ui.status_badges[id] = badge;
|
||||
disp.ui.status_labels[id] = label;
|
||||
}
|
||||
|
||||
/* UI 直接内联到 display_module,保留原型布局而不引入额外 ui 抽象层。 */
|
||||
static void display_create_ui_locked(void)
|
||||
{
|
||||
lv_obj_t *screen = lv_screen_active();
|
||||
lv_obj_t *content;
|
||||
lv_obj_t *top_row;
|
||||
lv_obj_t *battery_wrap;
|
||||
lv_obj_t *middle_row;
|
||||
lv_obj_t *bottom_row;
|
||||
|
||||
lv_obj_clean(screen);
|
||||
lv_obj_set_style_bg_color(screen, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_grad_color(screen, lv_color_hex(0x1A1F29), 0);
|
||||
lv_obj_set_style_bg_grad_dir(screen, LV_GRAD_DIR_VER, 0);
|
||||
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_text_color(screen, lv_color_white(), 0);
|
||||
lv_obj_set_style_pad_all(screen, 0, 0);
|
||||
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
|
||||
|
||||
content = lv_obj_create(screen);
|
||||
lv_obj_remove_style_all(content);
|
||||
lv_obj_set_size(content, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_style_bg_color(content, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(content, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_pad_left(content, 14, 0);
|
||||
lv_obj_set_style_pad_right(content, 14, 0);
|
||||
lv_obj_set_style_pad_top(content, 8, 0);
|
||||
lv_obj_set_style_pad_bottom(content, 8, 0);
|
||||
lv_obj_set_layout(content, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(content,
|
||||
LV_FLEX_ALIGN_START,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
top_row = lv_obj_create(content);
|
||||
lv_obj_remove_style_all(top_row);
|
||||
lv_obj_set_width(top_row, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(top_row, 1);
|
||||
lv_obj_set_style_bg_color(top_row, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(top_row, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_layout(top_row, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(top_row, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(top_row,
|
||||
LV_FLEX_ALIGN_SPACE_BETWEEN,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
disp.ui.date_label = lv_label_create(top_row);
|
||||
lv_obj_set_style_text_font(disp.ui.date_label, &ui_font_keyboard_small_18, 0);
|
||||
lv_obj_set_style_text_color(disp.ui.date_label, lv_color_hex(0xD8DEE9), 0);
|
||||
|
||||
battery_wrap = lv_obj_create(top_row);
|
||||
lv_obj_remove_style_all(battery_wrap);
|
||||
lv_obj_set_width(battery_wrap, LV_SIZE_CONTENT);
|
||||
lv_obj_set_layout(battery_wrap, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(battery_wrap, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(battery_wrap,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(battery_wrap, 4, 0);
|
||||
|
||||
disp.ui.battery_icon = lv_label_create(battery_wrap);
|
||||
lv_obj_set_style_text_font(disp.ui.battery_icon, &ui_font_keyboard_small_18, 0);
|
||||
|
||||
disp.ui.battery_label = lv_label_create(battery_wrap);
|
||||
lv_obj_set_style_text_font(disp.ui.battery_label, &ui_font_keyboard_small_18, 0);
|
||||
lv_obj_set_style_text_color(disp.ui.battery_label, lv_color_hex(0xD8DEE9), 0);
|
||||
|
||||
disp.ui.battery_state_label = lv_label_create(battery_wrap);
|
||||
lv_obj_set_style_text_font(disp.ui.battery_state_label, &ui_font_keyboard_small_18, 0);
|
||||
|
||||
middle_row = lv_obj_create(content);
|
||||
lv_obj_remove_style_all(middle_row);
|
||||
lv_obj_set_width(middle_row, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(middle_row, 2);
|
||||
lv_obj_set_style_bg_color(middle_row, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(middle_row, LV_OPA_TRANSP, 0);
|
||||
|
||||
disp.ui.time_label = lv_label_create(middle_row);
|
||||
lv_obj_set_style_text_font(disp.ui.time_label, &ui_font_keyboard_time_48, 0);
|
||||
lv_obj_set_style_text_color(disp.ui.time_label, lv_color_white(), 0);
|
||||
lv_obj_center(disp.ui.time_label);
|
||||
|
||||
bottom_row = lv_obj_create(content);
|
||||
lv_obj_remove_style_all(bottom_row);
|
||||
lv_obj_set_width(bottom_row, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(bottom_row, 1);
|
||||
lv_obj_set_style_bg_color(bottom_row, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(bottom_row, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_layout(bottom_row, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(bottom_row, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(bottom_row,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(bottom_row, 6, 0);
|
||||
|
||||
for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++)
|
||||
display_create_status_chip_locked(bottom_row, (enum display_status_id)i);
|
||||
|
||||
display_refresh_all_locked();
|
||||
}
|
||||
|
||||
/* 周期刷新只负责时间区域;状态图标改为事件驱动,避免无谓重绘。 */
|
||||
static void display_update_work_fn(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
@@ -637,15 +346,15 @@ static void display_update_work_fn(struct k_work *work)
|
||||
return;
|
||||
|
||||
disp.tick_count++;
|
||||
display_update_datetime_text();
|
||||
|
||||
lvgl_lock();
|
||||
display_refresh_datetime_locked();
|
||||
display_ui_refresh_datetime(disp.date_text, disp.time_text);
|
||||
lvgl_unlock();
|
||||
|
||||
display_schedule_update(K_MSEC(DISPLAY_UPDATE_PERIOD_MS));
|
||||
}
|
||||
|
||||
/* 显示初始化完成后,后续 UI 更新全部通过事件和定时刷新驱动。 */
|
||||
static int display_init(void)
|
||||
{
|
||||
int err;
|
||||
@@ -667,6 +376,7 @@ static int display_init(void)
|
||||
k_work_init_delayable(&disp.theme_save_work, display_theme_save_work_fn);
|
||||
disp.tick_count = 0U;
|
||||
display_theme_apply_loaded_storage();
|
||||
display_update_datetime_text();
|
||||
|
||||
err = display_blanking_off(disp.dev);
|
||||
if (err)
|
||||
@@ -680,7 +390,7 @@ static int display_init(void)
|
||||
return err;
|
||||
|
||||
lvgl_lock();
|
||||
display_create_ui_locked();
|
||||
display_ui_init(&disp.ui, disp.date_text, disp.time_text);
|
||||
lvgl_unlock();
|
||||
|
||||
disp.initialized = true;
|
||||
@@ -692,54 +402,45 @@ static int display_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 电池事件只缓存最新 SOC,UI 若已就绪则立即刷新顶部电池区域。 */
|
||||
static bool handle_battery_status_event(const struct battery_status_event *event)
|
||||
{
|
||||
disp.ui.battery_level = battery_status_event_get_soc(event);
|
||||
disp.ui.battery_flags = battery_status_event_get_flags(event);
|
||||
|
||||
if (!disp.initialized)
|
||||
return false;
|
||||
|
||||
if (!display_is_active())
|
||||
if (!disp.initialized || !display_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
display_refresh_battery_locked();
|
||||
display_ui_refresh_battery(&disp.ui);
|
||||
lvgl_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 模式事件只影响 USB/BLE 两个 badge 的亮灭。 */
|
||||
static bool handle_mode_event(const struct mode_event *event)
|
||||
{
|
||||
display_update_mode_state(event->mode_type);
|
||||
disp.ui.mode = event->mode_type;
|
||||
|
||||
if (!disp.initialized)
|
||||
return false;
|
||||
|
||||
if (!display_is_active())
|
||||
if (!disp.initialized || !display_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
display_refresh_status_bar_locked();
|
||||
display_ui_refresh_status_bar(&disp.ui);
|
||||
lvgl_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* NumLock/CapsLock/ScrollLock 变化后,底部三个状态 badge 立即更新。 */
|
||||
static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
|
||||
{
|
||||
display_update_keyboard_led_state(keyboard_led_event_get_mask(event));
|
||||
disp.ui.led_mask = keyboard_led_event_get_mask(event);
|
||||
|
||||
if (!disp.initialized)
|
||||
return false;
|
||||
|
||||
if (!display_is_active())
|
||||
if (!disp.initialized || !display_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
display_refresh_status_bar_locked();
|
||||
display_ui_refresh_status_bar(&disp.ui);
|
||||
lvgl_unlock();
|
||||
return false;
|
||||
}
|
||||
@@ -753,12 +454,11 @@ static bool handle_display_theme_event(const struct display_theme_event *event)
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
display_refresh_status_bar_locked();
|
||||
display_ui_refresh_status_bar(&disp.ui);
|
||||
lvgl_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 任意按钮事件都可点亮屏幕并重置 1 分钟空闲计时。 */
|
||||
static bool handle_button_event(const struct button_event *event)
|
||||
{
|
||||
ARG_UNUSED(event);
|
||||
@@ -786,7 +486,7 @@ static bool handle_module_state_event(const struct module_state_event *event)
|
||||
|
||||
if (disp.initialized && display_is_active()) {
|
||||
lvgl_lock();
|
||||
display_refresh_status_bar_locked();
|
||||
display_ui_refresh_status_bar(&disp.ui);
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
|
||||
306
src/ui/display_ui.c
Normal file
306
src/ui/display_ui.c
Normal file
@@ -0,0 +1,306 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
#include "battery_status_event.h"
|
||||
#include "display_ui.h"
|
||||
#include "keyboard_led_event.h"
|
||||
|
||||
#define DISPLAY_SYMBOL_PLUG "\xEF\x87\xA6"
|
||||
|
||||
LV_FONT_DECLARE(ui_font_keyboard_small_18);
|
||||
LV_FONT_DECLARE(ui_font_keyboard_time_48);
|
||||
|
||||
enum display_status_id
|
||||
{
|
||||
DISPLAY_STATUS_USB = 0,
|
||||
DISPLAY_STATUS_BLE,
|
||||
DISPLAY_STATUS_NUMLOCK,
|
||||
DISPLAY_STATUS_CAPSLOCK,
|
||||
DISPLAY_STATUS_COUNT,
|
||||
};
|
||||
|
||||
struct display_ui_ctx
|
||||
{
|
||||
lv_obj_t *status_badges[DISPLAY_STATUS_COUNT];
|
||||
lv_obj_t *status_labels[DISPLAY_STATUS_COUNT];
|
||||
lv_obj_t *battery_icon;
|
||||
lv_obj_t *battery_label;
|
||||
lv_obj_t *battery_state_label;
|
||||
lv_obj_t *date_label;
|
||||
lv_obj_t *time_label;
|
||||
};
|
||||
|
||||
static struct display_ui_ctx g_display_ui;
|
||||
|
||||
static const char *const g_status_texts[DISPLAY_STATUS_COUNT] = {
|
||||
LV_SYMBOL_USB,
|
||||
LV_SYMBOL_BLUETOOTH,
|
||||
"1",
|
||||
"A",
|
||||
};
|
||||
|
||||
static lv_color_t display_ui_get_battery_color(uint8_t battery_level)
|
||||
{
|
||||
if (battery_level > 70U) {
|
||||
return lv_color_hex(0x8BD450);
|
||||
}
|
||||
|
||||
if (battery_level >= 20U) {
|
||||
return lv_color_hex(0xF4D35E);
|
||||
}
|
||||
|
||||
return lv_color_hex(0xE63946);
|
||||
}
|
||||
|
||||
static const char *display_ui_get_battery_symbol(uint8_t battery_level)
|
||||
{
|
||||
if (battery_level > 85U) {
|
||||
return LV_SYMBOL_BATTERY_FULL;
|
||||
}
|
||||
|
||||
if (battery_level > 60U) {
|
||||
return LV_SYMBOL_BATTERY_3;
|
||||
}
|
||||
|
||||
if (battery_level > 35U) {
|
||||
return LV_SYMBOL_BATTERY_2;
|
||||
}
|
||||
|
||||
if (battery_level >= 20U) {
|
||||
return LV_SYMBOL_BATTERY_1;
|
||||
}
|
||||
|
||||
return LV_SYMBOL_BATTERY_EMPTY;
|
||||
}
|
||||
|
||||
static bool display_ui_status_is_active(enum display_status_id id,
|
||||
const struct display_ui_model *model)
|
||||
{
|
||||
switch (id) {
|
||||
case DISPLAY_STATUS_USB:
|
||||
return model->mode == MODE_TYPE_USB;
|
||||
|
||||
case DISPLAY_STATUS_BLE:
|
||||
return model->mode == MODE_TYPE_BLE;
|
||||
|
||||
case DISPLAY_STATUS_NUMLOCK:
|
||||
return (model->led_mask & KEYBOARD_LED_MASK_NUM_LOCK) != 0U;
|
||||
|
||||
case DISPLAY_STATUS_CAPSLOCK:
|
||||
return (model->led_mask & KEYBOARD_LED_MASK_CAPS_LOCK) != 0U;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void display_ui_create_status_chip(lv_obj_t *parent, enum display_status_id id)
|
||||
{
|
||||
lv_obj_t *badge = lv_obj_create(parent);
|
||||
lv_obj_t *label;
|
||||
|
||||
lv_obj_remove_style_all(badge);
|
||||
lv_obj_set_size(badge, 50, 32);
|
||||
lv_obj_set_style_radius(badge, 10, 0);
|
||||
lv_obj_set_style_bg_opa(badge, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_pad_all(badge, 0, 0);
|
||||
|
||||
label = lv_label_create(badge);
|
||||
lv_label_set_text(label, g_status_texts[id]);
|
||||
lv_obj_set_width(label, LV_PCT(100));
|
||||
lv_obj_set_style_text_font(label, &ui_font_keyboard_small_18, 0);
|
||||
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_center(label);
|
||||
|
||||
g_display_ui.status_badges[id] = badge;
|
||||
g_display_ui.status_labels[id] = label;
|
||||
}
|
||||
|
||||
void display_ui_refresh_status_bar(const struct display_ui_model *model)
|
||||
{
|
||||
for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++) {
|
||||
lv_obj_t *badge = g_display_ui.status_badges[i];
|
||||
lv_obj_t *label = g_display_ui.status_labels[i];
|
||||
bool active = display_ui_status_is_active((enum display_status_id)i, model);
|
||||
|
||||
if (!badge || !label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lv_obj_set_style_border_width(badge, 4, 0);
|
||||
lv_obj_set_style_border_color(badge,
|
||||
active ? model->theme_color :
|
||||
model->inactive_border_color,
|
||||
0);
|
||||
lv_obj_set_style_bg_color(badge,
|
||||
active ? lv_color_hex(0x1D2735) :
|
||||
lv_color_hex(0x161A20),
|
||||
0);
|
||||
lv_obj_set_style_text_color(label,
|
||||
active ? lv_color_white() :
|
||||
lv_color_hex(0x7C8798),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
void display_ui_refresh_battery(const struct display_ui_model *model)
|
||||
{
|
||||
char battery_text[8];
|
||||
lv_color_t battery_color;
|
||||
const char *state_symbol = "";
|
||||
lv_color_t state_color = lv_color_white();
|
||||
|
||||
if (!g_display_ui.battery_icon ||
|
||||
!g_display_ui.battery_label ||
|
||||
!g_display_ui.battery_state_label) {
|
||||
return;
|
||||
}
|
||||
|
||||
battery_color = display_ui_get_battery_color(model->battery_level);
|
||||
snprintk(battery_text, sizeof(battery_text), "%u%%", model->battery_level);
|
||||
|
||||
if ((model->battery_flags & BATTERY_STATUS_FLAG_FULL) != 0U) {
|
||||
state_symbol = DISPLAY_SYMBOL_PLUG;
|
||||
state_color = lv_color_hex(0x4C9EF5);
|
||||
} else if ((model->battery_flags & BATTERY_STATUS_FLAG_CHARGING) != 0U) {
|
||||
state_symbol = LV_SYMBOL_CHARGE;
|
||||
state_color = lv_color_hex(0xF4D35E);
|
||||
}
|
||||
|
||||
lv_label_set_text(g_display_ui.battery_icon,
|
||||
display_ui_get_battery_symbol(model->battery_level));
|
||||
lv_obj_set_style_text_color(g_display_ui.battery_icon, battery_color, 0);
|
||||
lv_label_set_text(g_display_ui.battery_label, battery_text);
|
||||
lv_label_set_text(g_display_ui.battery_state_label, state_symbol);
|
||||
lv_obj_set_style_text_color(g_display_ui.battery_state_label, state_color, 0);
|
||||
}
|
||||
|
||||
void display_ui_refresh_datetime(const char *date_text, const char *time_text)
|
||||
{
|
||||
if (!g_display_ui.date_label || !g_display_ui.time_label) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_label_set_text(g_display_ui.date_label, date_text);
|
||||
lv_label_set_text(g_display_ui.time_label, time_text);
|
||||
}
|
||||
|
||||
void display_ui_refresh_all(const struct display_ui_model *model,
|
||||
const char *date_text,
|
||||
const char *time_text)
|
||||
{
|
||||
display_ui_refresh_status_bar(model);
|
||||
display_ui_refresh_battery(model);
|
||||
display_ui_refresh_datetime(date_text, time_text);
|
||||
}
|
||||
|
||||
void display_ui_init(const struct display_ui_model *model,
|
||||
const char *date_text,
|
||||
const char *time_text)
|
||||
{
|
||||
lv_obj_t *screen = lv_screen_active();
|
||||
lv_obj_t *content;
|
||||
lv_obj_t *top_row;
|
||||
lv_obj_t *battery_wrap;
|
||||
lv_obj_t *middle_row;
|
||||
lv_obj_t *bottom_row;
|
||||
|
||||
memset(&g_display_ui, 0, sizeof(g_display_ui));
|
||||
|
||||
lv_obj_clean(screen);
|
||||
lv_obj_set_style_bg_color(screen, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_grad_color(screen, lv_color_hex(0x1A1F29), 0);
|
||||
lv_obj_set_style_bg_grad_dir(screen, LV_GRAD_DIR_VER, 0);
|
||||
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_text_color(screen, lv_color_white(), 0);
|
||||
lv_obj_set_style_pad_all(screen, 0, 0);
|
||||
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
|
||||
|
||||
content = lv_obj_create(screen);
|
||||
lv_obj_remove_style_all(content);
|
||||
lv_obj_set_size(content, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_style_bg_color(content, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(content, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_pad_left(content, 14, 0);
|
||||
lv_obj_set_style_pad_right(content, 14, 0);
|
||||
lv_obj_set_style_pad_top(content, 8, 0);
|
||||
lv_obj_set_style_pad_bottom(content, 8, 0);
|
||||
lv_obj_set_layout(content, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(content,
|
||||
LV_FLEX_ALIGN_START,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
top_row = lv_obj_create(content);
|
||||
lv_obj_remove_style_all(top_row);
|
||||
lv_obj_set_width(top_row, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(top_row, 1);
|
||||
lv_obj_set_style_bg_color(top_row, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(top_row, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_layout(top_row, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(top_row, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(top_row,
|
||||
LV_FLEX_ALIGN_SPACE_BETWEEN,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
g_display_ui.date_label = lv_label_create(top_row);
|
||||
lv_obj_set_style_text_font(g_display_ui.date_label, &ui_font_keyboard_small_18, 0);
|
||||
lv_obj_set_style_text_color(g_display_ui.date_label, lv_color_hex(0xD8DEE9), 0);
|
||||
|
||||
battery_wrap = lv_obj_create(top_row);
|
||||
lv_obj_remove_style_all(battery_wrap);
|
||||
lv_obj_set_width(battery_wrap, LV_SIZE_CONTENT);
|
||||
lv_obj_set_layout(battery_wrap, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(battery_wrap, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(battery_wrap,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(battery_wrap, 4, 0);
|
||||
|
||||
g_display_ui.battery_icon = lv_label_create(battery_wrap);
|
||||
lv_obj_set_style_text_font(g_display_ui.battery_icon, &ui_font_keyboard_small_18, 0);
|
||||
|
||||
g_display_ui.battery_label = lv_label_create(battery_wrap);
|
||||
lv_obj_set_style_text_font(g_display_ui.battery_label, &ui_font_keyboard_small_18, 0);
|
||||
lv_obj_set_style_text_color(g_display_ui.battery_label, lv_color_hex(0xD8DEE9), 0);
|
||||
|
||||
g_display_ui.battery_state_label = lv_label_create(battery_wrap);
|
||||
lv_obj_set_style_text_font(g_display_ui.battery_state_label, &ui_font_keyboard_small_18, 0);
|
||||
|
||||
middle_row = lv_obj_create(content);
|
||||
lv_obj_remove_style_all(middle_row);
|
||||
lv_obj_set_width(middle_row, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(middle_row, 2);
|
||||
lv_obj_set_style_bg_color(middle_row, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(middle_row, LV_OPA_TRANSP, 0);
|
||||
|
||||
g_display_ui.time_label = lv_label_create(middle_row);
|
||||
lv_obj_set_style_text_font(g_display_ui.time_label, &ui_font_keyboard_time_48, 0);
|
||||
lv_obj_set_style_text_color(g_display_ui.time_label, lv_color_white(), 0);
|
||||
lv_obj_center(g_display_ui.time_label);
|
||||
|
||||
bottom_row = lv_obj_create(content);
|
||||
lv_obj_remove_style_all(bottom_row);
|
||||
lv_obj_set_width(bottom_row, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(bottom_row, 1);
|
||||
lv_obj_set_style_bg_color(bottom_row, lv_color_hex(0x0F1115), 0);
|
||||
lv_obj_set_style_bg_opa(bottom_row, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_layout(bottom_row, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(bottom_row, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(bottom_row,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(bottom_row, 6, 0);
|
||||
|
||||
for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++) {
|
||||
display_ui_create_status_chip(bottom_row, (enum display_status_id)i);
|
||||
}
|
||||
|
||||
display_ui_refresh_all(model, date_text, time_text);
|
||||
}
|
||||
34
src/ui/display_ui.h
Normal file
34
src/ui/display_ui.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef DISPLAY_UI_H
|
||||
#define DISPLAY_UI_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "mode_event.h"
|
||||
|
||||
struct display_ui_model
|
||||
{
|
||||
lv_color_t theme_color;
|
||||
lv_color_t inactive_border_color;
|
||||
uint8_t battery_level;
|
||||
mode_type_t mode;
|
||||
uint8_t led_mask;
|
||||
uint8_t battery_flags;
|
||||
};
|
||||
|
||||
void display_ui_init(const struct display_ui_model *model,
|
||||
const char *date_text,
|
||||
const char *time_text);
|
||||
|
||||
void display_ui_refresh_all(const struct display_ui_model *model,
|
||||
const char *date_text,
|
||||
const char *time_text);
|
||||
|
||||
void display_ui_refresh_status_bar(const struct display_ui_model *model);
|
||||
|
||||
void display_ui_refresh_battery(const struct display_ui_model *model);
|
||||
|
||||
void display_ui_refresh_datetime(const char *date_text, const char *time_text);
|
||||
|
||||
#endif /* DISPLAY_UI_H */
|
||||
Reference in New Issue
Block a user