Multiplexação de Pinos com Seeed Studio XIAO nRF54L15 Sense
Para facilitar o uso, todos os exemplos a seguir de multiplexação de pinos estão no PlatformIO. Clique neste link para um guia de configuração e uso do XIAO nRF54L5
Baseado no VS Code, se você quiser usar o caso a seguir no nRF Connect SDK, consulte a conexão fornecida, adicione o arquivo app.overlay e modifique o conteúdo em prj.conf
XIAO nRF54L15 Adicionar arquivo overlay e modificar o arquivo conf.
Teclas de Bordo
O XIAO nRF54L15(Sense) vem equipado com dois botões físicos importantes que desempenham papéis cruciais na operação do dispositivo e na programação de firmware: o Botão Reset e o Botão do Usuário. Compreender suas funções é essencial para o uso diário e atualizações de firmware.
Botão Reset
O botão Reset é usado para realizar uma operação de reinicialização forçada no dispositivo.
- Funcionalidade:
- Reinicialização Forçada: Pressionar este botão interrompe imediatamente todas as operações atuais do dispositivo e faz com que ele reinicie, semelhante a um ciclo de energia.
- Resolução de Programas Travados: Quando o programa em execução no dispositivo trava, entra em um loop infinito ou fica sem resposta, pressionar o botão Reset é a maneira mais rápida de forçá-lo de volta a um estado de operação normal.
- Sem Impacto no Firmware: Uma operação de reset não apaga nem altera o firmware já programado no dispositivo. Ele simplesmente reinicia o aplicativo em execução.
Botão do Usuário
O botão do usuário é uma entrada versátil e programável que oferece controle flexível dentro de suas aplicações.
Funcionalidade:
-
Entrada Personalizável:Ao contrário da função fixa do botão Reset, a ação do botão do usuário é inteiramente definida pelo firmware programado.
-
Acionamento de Eventos: Pode ser programado para acionar eventos específicos, controlar diferentes funcionalidades ou atuar como uma entrada de uso geral para suas aplicações.
Digital
Preparação de Hardware
| Seeed Studio XIAO nRF54L15 Sense | Seeed Studio Expansion Base for XIAO with Grove OLED | Grove - Relay |
|---|---|---|
![]() | ![]() | ![]() |
Implementação de Software
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main_app, CONFIG_LOG_DEFAULT_LEVEL);
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios); // Get the button device from the device tree alias
static const struct gpio_dt_spec relay = GPIO_DT_SPEC_GET(DT_ALIAS(relay0), gpios); // Get the relay device from the device tree alias
int main(void)
{
int ret;
LOG_INF("Starting Zephyr button and relay example...");
/* Check if GPIO devices are ready */
if (!gpio_is_ready_dt(&button)) {
LOG_ERR("Button device %s is not ready", button.port->name);
return -1;
}
if (!gpio_is_ready_dt(&relay)) {
LOG_ERR("Relay device %s is not ready", relay.port->name);
return -1;
}
/* Configure button pin as input mode */
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Failed to configure %s pin %d (error %d)", button.port->name, button.pin, ret);
return -1;
}
/* Configure relay pin as output mode */
ret = gpio_pin_configure_dt(&relay, GPIO_OUTPUT_ACTIVE);
if (ret != 0) {
LOG_ERR("Failed to configure %s pin %d (error %d)", relay.port->name, relay.pin, ret);
return -1;
}
LOG_INF("Press the button to toggle the relay...");
while (1) {
/* Read button state */
int button_state = gpio_pin_get_dt(&button);
/* Check if read is successful */
if (button_state < 0) {
LOG_ERR("Error reading button pin: %d", button_state);
return -1;
}
if (button_state == 0) { // Button pressed (ACTIVE_LOW)
gpio_pin_set_dt(&relay, 1); // Turn on relay (high level)
} else { // Button not pressed
gpio_pin_set_dt(&relay, 0); // Turn off relay (low level)
}
k_msleep(10); /* Short delay to avoid busy looping */
}
return 0;
}
Configuração da Árvore de Dispositivos
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios);
- Esta linha de código utiliza o sistema de árvore de dispositivos do Zephyr para obter as informações do GPIO do botão por meio de um alias chamado sw1. Essa abordagem desacopla o código dos pinos de hardware específicos e melhora a portabilidade.
static const struct gpio_dt_spec relay = GPIO_DT_SPEC_GET(DT_ALIAS(relay0), gpios);
- Novamente, esta linha de código obtém informações sobre o dispositivo GPIO do relé chamado relay0.
Verificação de prontidão do dispositivo
if (!gpio_is_ready_dt(&button)) e if (!gpio_is_ready_dt(&relay))
- Antes de o programa começar a executar qualquer operação, o código verifica se os dispositivos de botão e relé foram inicializados com sucesso e estão prontos. Esta é uma prática recomendada na programação de drivers Zephyr e evita que o programa trave se os dispositivos não estiverem configurados corretamente.
Configuração de Pinos
gpio_pin_configure_dt(&button, GPIO_INPUT);
- Esta linha de código configura o pino GPIO do botão para o modo de entrada. Este é um passo necessário para ler o nível do pino, e o programa monitorará o nível de tensão do pino para determinar se o botão está pressionado.
gpio_pin_configure_dt(&relay, GPIO_OUTPUT_ACTIVE);
- Esta linha de código configura o pino GPIO do relé para o modo de saída. O sinalizador
GPIO_OUTPUT_ACTIVEgeralmente indica que o pino estará ativo após a configuração, em preparação para controlar o relé.
Lógica do Loop Principal
while (1): O código entra em um loop infinito que executa continuamente as seguintes ações.
int button_state = gpio_pin_get_dt(&button);: Em cada loop, o programa lê o estado de nível atual dos pinos do botão.
if (button_state == 0): Esta lógica verifica se o botão está pressionado. Em muitos projetos de circuito, pressionar um botão conecta o pino ao terra (GND), resultando em um nível 0 (ou seja, baixo).
gpio_pin_set_dt(&relay, 1);: Se o estado do botão for 0 (pressionado), o pino do relé é definido como 1 (alto), o que fecha o relé e liga o dispositivo (por exemplo, lâmpada) ao qual está conectado.
else: Se o botão não estiver pressionado (estado é 1), execute gpio_pin_set_dt(&relay, 0); para definir o pino do relé como 0 (baixo), o que fecha o relé e desliga o dispositivo ao qual está conectado.
k_msleep(10);: o código adiciona um pequeno atraso de 10 milissegundos ao final de cada loop para evitar que a CPU fique ocupada, etc. Este é um tratamento simples anti-jitter que evita múltiplos acionamentos devido ao jitter físico dos botões e também reduz o consumo de energia.
Gráfico de Resultado

Analógico
Preparação de Hardware
| Seeed Studio XIAO nRF54L15 Sense | Grove-Variable Color LED | Grove-Rotary Angle Sensor | Seeed Studio Grove Base for XIAO |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
Implementação de Software
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/logging/log.h>
// Register log module
LOG_MODULE_REGISTER(pot_pwm_example, CONFIG_LOG_DEFAULT_LEVEL);
// --- ADC Configuration ---
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \
!DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels)
#error "No suitable devicetree overlay specified for ADC channels"
#endif
#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
ADC_DT_SPEC_GET_BY_IDX(node_id, idx),
static const struct adc_dt_spec adc_channels[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA)
};
// Define the index of the potentiometer ADC channel in the adc_channels array
#define POTENTIOMETER_ADC_CHANNEL_IDX 1
// --- PWM Configuration ---
// Get PWM LED device
static const struct pwm_dt_spec led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led));
// Define PWM period as 1 millisecond (1,000,000 nanoseconds)
// This corresponds to a 1 kHz PWM frequency, suitable for LED brightness adjustment without visible flicker
#define PWM_PERIOD_NS 1000000UL
int main(void)
{
int ret;
uint16_t adc_raw_value;
int32_t adc_millivolts;
LOG_INF("Starting Zephyr Potentiometer to PWM example...");
// --- ADC initialization and setup ---
if (!adc_is_ready_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX])) {
LOG_ERR("ADC controller device %s not ready", adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].dev->name);
return 0;
}
ret = adc_channel_setup_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX]);
if (ret < 0) {
LOG_ERR("Could not setup ADC channel for potentiometer (%d)", ret);
return 0;
}
LOG_INF("ADC device %s, channel %d ready for potentiometer.",
adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].dev->name,
adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].channel_id);
// --- PWM initialization and setup ---
if (!device_is_ready(led.dev)) {
LOG_ERR("Error: PWM device %s is not ready", led.dev->name);
return 0;
}
LOG_INF("PWM Period for LED set to %lu ns (%.1f Hz)",
PWM_PERIOD_NS, (double)NSEC_PER_SEC / PWM_PERIOD_NS); // Use PWM_PERIOD_NS instead of led.period
// ADC sequence configuration
struct adc_sequence sequence = {
.buffer = &adc_raw_value,
.buffer_size = sizeof(adc_raw_value),
.resolution = adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].resolution,
};
// --- Main loop ---
while (1) {
(void)adc_sequence_init_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX], &sequence);
ret = adc_read(adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].dev, &sequence);
if (ret < 0) {
LOG_ERR("Error %d: ADC read failed for channel %d",
ret, adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].channel_id);
k_msleep(100);
continue;
}
int sensor_value = adc_raw_value;
uint32_t max_adc_raw = (1U << adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].resolution) - 1;
// --- Map ADC raw value to PWM duty cycle ---
uint32_t output_duty_ns = (PWM_PERIOD_NS * sensor_value) / max_adc_raw;
// Set PWM duty cycle
ret = pwm_set_dt(&led, PWM_PERIOD_NS, output_duty_ns);
if (ret < 0) {
LOG_ERR("Error %d: failed to set PWM duty cycle.", ret);
}
// --- Print information ---
adc_millivolts = sensor_value;
ret = adc_raw_to_millivolts_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX], &adc_millivolts);
if (ret < 0) {
LOG_WRN("ADC to mV conversion not supported/failed: %d", ret);
LOG_INF("Sensor Raw Value = %d\tOutput Duty (ns) = %u", sensor_value, output_duty_ns);
} else {
LOG_INF("Sensor Raw Value = %d (%d mV)\tOutput Duty (ns) = %u",
sensor_value, adc_millivolts, output_duty_ns);
}
k_msleep(100);
}
return 0;
}
Configuração de Dispositivo ADC (Conversor Analógico-Digital) e PWM (Modulação por Largura de Pulso)
-
Módulo de Log pot_pwm_example:
LOG_MODULE_REGISTER(pot_pwm_example, CONFIG_LOG_DEFAULT_LEVEL): Isso registra um módulo de log chamadopot_pwm_examplee define seu nível de log para a configuração padrão do sistema, o que facilita a depuração.
-
Configuração do ADC:
-
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) ... #endif: Esta diretiva de pré-processador é uma verificação do Device Tree que garante a existência de um arquivo overlay válido contendo definições de canais ADC. Isso exige que o usuário forneça a configuração correta para o hardware específico. -
static const struct adc_dt_spec adc_channels[];: Esta parte do código utiliza o Device Tree do Zephyr para recuperar automaticamente informações de todos os canais ADC configurados. Essa abordagem torna o código flexível e portável entre diferentes hardwares sem necessidade de alterações manuais na configuração. -
#define POTENTIOMETER_ADC_CHANNEL_IDX 1: Uma macro é definida para especificar a qual canal no arrayadc_channelso potenciômetro está conectado.
-
-
Configuração do PWM:
-
static const struct pwm_dt_spec led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led));: Esta linha recupera as informações do dispositivo PWM para o aliaspwm_leddo Device Tree. Esta é uma prática padrão do Zephyr para localizar e referenciar dispositivos de hardware. -
#define PWM_PERIOD_NS 1000000UL: Isso define o período do sinal PWM como 1 milissegundo (1.000.000 nanossegundos), o que corresponde a uma frequência de 1 kHz. Essa frequência é bem adequada para o controle de brilho de LEDs, pois é alta o suficiente para evitar cintilação visível.
-
Inicialização e Configuração
-
Informações de Log:
LOG_INF("Starting Zephyr Potentiometer to PWM example...");: Uma mensagem de log informativa é impressa no início do programa para notificar o usuário de que o exemplo foi iniciado.
-
Inicialização do ADC:
-
!adc_is_ready_dt(): Antes de tentar usar o dispositivo ADC, é realizada uma verificação para confirmar que ele está pronto. Se o dispositivo não estiver pronto, um erro é registrado e o programa é encerrado. -
adc_channel_setup_dt(): Esta função configura o canal ADC específico conectado ao potenciômetro, incluindo sua resolução e ganho.
-
-
Inicialização do PWM:
-
!device_is_ready(led.dev): De forma semelhante ao ADC, esta linha verifica se o dispositivo PWM está pronto. Caso contrário, um erro é registrado e o programa é encerrado. -
LOG_INF(...): O período e a frequência do PWM são impressos para ajudar o usuário a confirmar a configuração.
-
-
Configuração da Sequência ADC:
struct adc_sequence sequence: Uma structadc_sequenceé definida para descrever uma única operação de conversão ADC. Ela especifica o buffer (adc_raw_value) para armazenar o resultado, seu tamanho (sizeof(adc_raw_value)) e a resolução ADC a ser utilizada.
Loop Principal
A lógica central do código é executada dentro de um loop infinito while (1):
-
Leitura do ADC:
-
adc_sequence_init_dt(): A sequência ADC é inicializada para garantir que a configuração correta seja usada em cada leitura. -
adc_read(): Isso aciona uma conversão ADC para ler o valor analógico do potenciômetro. Se a leitura falhar, um erro é registrado e o programa pausa por 100 milissegundos antes de continuar. -
int sensor_value = adc_raw_value;: O valor bruto do ADC é atribuído à variávelsensor_value.
-
-
Mapeando o Valor ADC para o Ciclo de Trabalho PWM:
-
uint32_t max_adc_raw: Isso calcula o valor bruto máximo possível do ADC. -
uint32_t output_duty_ns = (PWM_PERIOD_NS * sensor_value) / max_adc_raw;: Esta é a lógica central de mapeamento. Ela escala o valor bruto do ADC (sensor_value) proporcionalmente ao intervalo do período PWM (PWM_PERIOD_NS) para obter um valor de ciclo de trabalho que ajusta o brilho do LED.
-
-
Configurando o Ciclo de Trabalho PWM:
pwm_set_dt(): Esta função aplica o ciclo de trabalho recém-calculado (output_duty_ns) ao dispositivo PWM, alterando instantaneamente o brilho do LED.
-
Atraso:
k_msleep(100): O programa pausa por 100 milissegundos após cada iteração do loop. Isso controla a frequência das leituras ADC e atualizações PWM, evitando carga excessiva da CPU e proporcionando uma experiência de usuário estável.
Gráfico de resultado

UART
Preparação de Hardware
| Seeed Studio XIAO nRF54L15 Sense | Módulo L76K GNSS para Seeed Studio XIAO |
|---|---|
![]() | ![]() |
Implementação de Software
Software
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>
#include <nrfx_power.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
// Register log module
LOG_MODULE_REGISTER(gps_app, LOG_LEVEL_INF);
// Type definitions
#define UBYTE uint8_t
#define UWORD uint16_t
#define UDOUBLE uint32_t
// Buffer sizes
#define SENTENCE_SIZE 100
#define BUFFSIZE 800
// NMEA Commands
#define HOT_START "$PMTK101"
#define WARM_START "$PMTK102"
#define COLD_START "$PMTK103"
#define FULL_COLD_START "$PMTK104"
#define SET_PERPETUAL_STANDBY_MODE "$PMTK161"
#define SET_PERIODIC_MODE "$PMTK225"
#define SET_NORMAL_MODE "$PMTK225,0"
#define SET_PERIODIC_BACKUP_MODE "$PMTK225,1,1000,2000"
#define SET_PERIODIC_STANDBY_MODE "$PMTK225,2,1000,2000"
#define SET_PERPETUAL_BACKUP_MODE "$PMTK225,4"
#define SET_ALWAYSLOCATE_STANDBY_MODE "$PMTK225,8"
#define SET_ALWAYSLOCATE_BACKUP_MODE "$PMTK225,9"
#define SET_POS_FIX "$PMTK220"
#define SET_POS_FIX_100MS "$PMTK220,100"
#define SET_POS_FIX_200MS "$PMTK220,200"
#define SET_POS_FIX_400MS "$PMTK220,400"
#define SET_POS_FIX_800MS "$PMTK220,800"
#define SET_POS_FIX_1S "$PMTK220,1000"
#define SET_POS_FIX_2S "$PMTK220,2000"
#define SET_POS_FIX_4S "$PMTK220,4000"
#define SET_POS_FIX_8S "$PMTK220,8000"
#define SET_POS_FIX_10S "$PMTK220,10000"
#define SET_SYNC_PPS_NMEA_OFF "$PMTK255,0"
#define SET_SYNC_PPS_NMEA_ON "$PMTK255,1"
#define SET_NMEA_BAUDRATE "$PMTK251"
#define SET_NMEA_BAUDRATE_115200 "$PMTK251,115200"
#define SET_NMEA_BAUDRATE_57600 "$PMTK251,57600"
#define SET_NMEA_BAUDRATE_38400 "$PMTK251,38400"
#define SET_NMEA_BAUDRATE_19200 "$PMTK251,19200"
#define SET_NMEA_BAUDRATE_14400 "$PMTK251,14400"
#define SET_NMEA_BAUDRATE_9600 "$PMTK251,9600"
#define SET_NMEA_BAUDRATE_4800 "$PMTK251,4800"
#define SET_REDUCTION "$PMTK314,-1"
#define SET_NMEA_OUTPUT "$PMTK314,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
// Struct definitions
typedef struct
{
double Lon; // GPS Longitude
double Lat; // GPS Latitude
char Lon_area; // E or W
char Lat_area; // N or S
UBYTE Time_H; // Time Hour
UBYTE Time_M; // Time Minute
UBYTE Time_S; // Time Second
UBYTE Status; // 1: Successful positioning, 0: Positioning failed
} GNRMC;
typedef struct
{
double Lon;
double Lat;
} Coordinates;
// Global variables and constants
char const Temp[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
static const double pi = 3.14159265358979324;
static const double a = 6378245.0;
static const double ee = 0.00669342162296594323;
static const double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
static char buff_t[BUFFSIZE] = {0};
static GNRMC GPS;
// UART device and buffers
static const struct device *uart_dev;
static char latest_gnrmc[SENTENCE_SIZE];
static volatile bool new_gnrmc_available = false;
// Function prototypes
void DEV_Uart_SendByte(char data);
void DEV_Uart_SendString(char *data);
void L76X_Send_Command(char *data);
GNRMC L76X_Gat_GNRMC(void);
Coordinates L76X_Baidu_Coordinates(void);
Coordinates L76X_Google_Coordinates(void);
static double transformLat(double x, double y);
static double transformLon(double x, double y);
static Coordinates bd_encrypt(Coordinates gg);
static Coordinates transform(Coordinates gps);
// UART interrupt callback
static void uart_callback(const struct device *dev, void *user_data)
{
ARG_UNUSED(user_data);
static char temp_buffer[SENTENCE_SIZE];
static int temp_index = 0;
while (uart_irq_update(dev) && uart_irq_is_pending(dev))
{
if (uart_irq_rx_ready(dev))
{
uint8_t byte;
if (uart_fifo_read(dev, &byte, 1) == 1)
{
if (byte == '\n')
{
temp_buffer[temp_index] = '\0';
if (strncmp(temp_buffer, "$GNRMC", 6) == 0 || strncmp(temp_buffer, "$PNRMC", 6) == 0)
{
strncpy(latest_gnrmc, temp_buffer, SENTENCE_SIZE);
new_gnrmc_available = true;
}
temp_index = 0;
}
else
{
if (temp_index < SENTENCE_SIZE - 1)
{
temp_buffer[temp_index++] = byte;
}
else
{
temp_index = 0; // Reset on overflow
}
}
}
}
}
}
// Main function
int main(void)
{
// Request constant latency mode for power management
nrfx_power_constlat_mode_request();
LOG_INF("Starting L76X GPS Module Example");
// Initialize UART device
uart_dev = DEVICE_DT_GET(DT_NODELABEL(xiao_serial));
if (!device_is_ready(uart_dev))
{
LOG_ERR("UART device not ready!");
return -1;
}
LOG_INF("UART device initialized.");
// Configure UART interrupt
if (uart_irq_callback_user_data_set(uart_dev, uart_callback, NULL) != 0)
{
LOG_ERR("Failed to set UART callback!");
return -1;
}
uart_irq_rx_enable(uart_dev);
LOG_INF("UART interrupt enabled.");
// Initialize GPS module
L76X_Send_Command(SET_NMEA_OUTPUT);
k_msleep(100);
L76X_Send_Command(SET_POS_FIX_1S);
k_msleep(100);
LOG_INF("GPS module initialized. Waiting for data...");
while (true)
{
// Check for new GNRMC sentence
if (new_gnrmc_available)
{
strncpy(buff_t, latest_gnrmc, BUFFSIZE);
new_gnrmc_available = false;
// Log raw GNRMC sentence for debugging
LOG_INF("Raw GNRMC: %s", buff_t);
// Parse GNRMC data
GPS = L76X_Gat_GNRMC();
// Output GPS data
LOG_INF("\n--- GPS Data ---");
LOG_INF("Time (GMT+8): %02d:%02d:%02d", GPS.Time_H, GPS.Time_M, GPS.Time_S);
if (GPS.Status == 1)
{
LOG_INF("Latitude (WGS-84): %.6f %c", GPS.Lat, GPS.Lat_area);
LOG_INF("Longitude (WGS-84): %.6f %c", GPS.Lon, GPS.Lon_area);
// Coordinate conversion
Coordinates baidu_coords = L76X_Baidu_Coordinates();
LOG_INF("Baidu Latitude: %.6f", baidu_coords.Lat);
LOG_INF("Baidu Longitude: %.6f", baidu_coords.Lon);
Coordinates google_coords = L76X_Google_Coordinates();
LOG_INF("Google Latitude: %.6f", google_coords.Lat);
LOG_INF("Google Longitude: %.6f", google_coords.Lon);
LOG_INF("GPS positioning successful.");
}
else
{
LOG_INF("GPS positioning failed or no valid data.");
}
}
else
{
LOG_INF("No new GNRMC data available.");
}
k_msleep(2000); // Wait 2 seconds before next reading
}
return 0;
}
// Send a single byte
void DEV_Uart_SendByte(char data)
{
uart_poll_out(uart_dev, data);
}
// Send a string
void DEV_Uart_SendString(char *data)
{
while (*data)
{
DEV_Uart_SendByte(*data++);
}
}
// Send L76X command with checksum
void L76X_Send_Command(char *data)
{
char Check = data[1], Check_char[3] = {0};
UBYTE i = 0;
DEV_Uart_SendByte('\r');
DEV_Uart_SendByte('\n');
for (i = 2; data[i] != '\0'; i++)
{
Check ^= data[i]; // Calculate checksum
}
Check_char[0] = Temp[Check / 16 % 16];
Check_char[1] = Temp[Check % 16];
Check_char[2] = '\0';
DEV_Uart_SendString(data);
DEV_Uart_SendByte('*');
DEV_Uart_SendString(Check_char);
DEV_Uart_SendByte('\r');
DEV_Uart_SendByte('\n');
}
// Parse GNRMC data
GNRMC L76X_Gat_GNRMC(void)
{
GNRMC gps = {0}; // Initialize with zeros
UWORD add = 0, x = 0, z = 0, i = 0;
UDOUBLE Time = 0;
add = 0;
while (add < BUFFSIZE)
{
// Look for GNRMC or PNRMC sentence
if (buff_t[add] == '$' && buff_t[add + 1] == 'G' && (buff_t[add + 2] == 'N' || buff_t[add + 2] == 'P') &&
buff_t[add + 3] == 'R' && buff_t[add + 4] == 'M' && buff_t[add + 5] == 'C')
{
x = 0;
for (z = 0; x < 12; z++)
{
if (buff_t[add + z] == '\0')
{
break;
}
if (buff_t[add + z] == ',')
{
x++;
if (x == 1)
{ // Time field
if (buff_t[add + z + 1] != ',')
{ // Check if time field is not empty
Time = 0;
for (i = 0; buff_t[add + z + i + 1] != '.'; i++)
{
if (buff_t[add + z + i + 1] == '\0' || buff_t[add + z + i + 1] == ',')
{
break;
}
Time = (buff_t[add + z + i + 1] - '0') + Time * 10;
}
gps.Time_H = Time / 10000 + 8; // Adjust for GMT+8
gps.Time_M = (Time / 100) % 100;
gps.Time_S = Time % 100;
if (gps.Time_H >= 24)
{
gps.Time_H = gps.Time_H - 24;
}
}
}
else if (x == 2)
{ // Status field
if (buff_t[add + z + 1] == 'A')
{
gps.Status = 1; // Position successful
}
else
{
gps.Status = 0; // Positioning failed
break; // Exit early if invalid
}
}
else if (x == 3)
{ // Latitude field
if (buff_t[add + z + 1] != ',')
{ // Check if latitude field is not empty
double latitude_val = 0;
UBYTE decimal_found = 0;
double decimal_multiplier = 0.1;
int k = 1;
while (buff_t[add + z + k] != ',' && buff_t[add + z + k] != '\0')
{
if (buff_t[add + z + k] == '.')
{
decimal_found = 1;
k++;
continue;
}
if (!decimal_found)
{
latitude_val = latitude_val * 10 + (buff_t[add + z + k] - '0');
}
else
{
latitude_val = latitude_val + (buff_t[add + z + k] - '0') * decimal_multiplier;
decimal_multiplier *= 0.1;
}
k++;
}
gps.Lat = latitude_val;
gps.Lat_area = buff_t[add + z + k + 1]; // N or S
z += k + 1;
}
else
{
gps.Status = 0; // Invalid data
break;
}
}
else if (x == 5)
{ // Longitude field
if (buff_t[add + z + 1] != ',')
{ // Check if longitude field is not empty
double longitude_val = 0;
UBYTE decimal_found = 0;
double decimal_multiplier = 0.1;
int k = 1;
while (buff_t[add + z + k] != ',' && buff_t[add + z + k] != '\0')
{
if (buff_t[add + z + k] == '.')
{
decimal_found = 1;
k++;
continue;
}
if (!decimal_found)
{
longitude_val = longitude_val * 10 + (buff_t[add + z + k] - '0');
}
else
{
longitude_val = longitude_val + (buff_t[add + z + k] - '0') * decimal_multiplier;
decimal_multiplier *= 0.1;
}
k++;
}
gps.Lon = longitude_val;
gps.Lon_area = buff_t[add + z + k + 1]; // E or W
z += k + 1;
break;
}
else
{
gps.Status = 0; // Invalid data
break;
}
}
}
}
break;
}
add++;
}
return gps;
}
// Convert to Baidu coordinates (BD-09)
Coordinates L76X_Baidu_Coordinates(void)
{
Coordinates wgs84_coords;
wgs84_coords.Lat = GPS.Lat;
wgs84_coords.Lon = GPS.Lon;
Coordinates gcj02_coords = transform(wgs84_coords);
Coordinates bd09_coords = bd_encrypt(gcj02_coords);
return bd09_coords;
}
// Convert to Google coordinates (GCJ-02)
Coordinates L76X_Google_Coordinates(void)
{
Coordinates wgs84_coords;
wgs84_coords.Lat = GPS.Lat;
wgs84_coords.Lon = GPS.Lon;
Coordinates gcj02_coords = transform(wgs84_coords);
return gcj02_coords;
}
// Coordinate transformation helper functions
static double transformLat(double x, double y)
{
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
ret += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
return ret;
}
static double transformLon(double x, double y)
{
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
ret += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
return ret;
}
static Coordinates bd_encrypt(Coordinates gg)
{
Coordinates bd;
double x = gg.Lon, y = gg.Lat;
double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
bd.Lon = z * cos(theta) + 0.0065;
bd.Lat = z * sin(theta) + 0.006;
return bd;
}
static Coordinates transform(Coordinates gps)
{
Coordinates gg;
double dLat = transformLat(gps.Lon - 105.0, gps.Lat - 35.0);
double dLon = transformLon(gps.Lon - 105.0, gps.Lat - 35.0);
double radLat = gps.Lat / 180.0 * pi;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi);
gg.Lat = gps.Lat + dLat;
gg.Lon = gps.Lon + dLon;
return gg;
}
Configuração e Inicialização do Módulo GPS
- Módulo de Log
gps_app:
-LOG_MODULE_REGISTER(gps_app, LOG_LEVEL_INF): Isso registra um módulo de log chamado gps_app e define seu nível de log como INFO. Isso permite que o programa produza informações através do sistema de logging do Zephyr, o que é útil para depuração e monitoramento.
- Definições de Tipos e Macros:
-UBYTE, UWORD, UDOUBLE: Esses são aliases de tipos inteiros sem sinal personalizados que melhoram a legibilidade do código ao esclarecer o tamanho esperado das variáveis.
-
SENTENCE_SIZE, BUFFSIZE:Esses definem tamanhos fixos para buffers usados para armazenar sentenças NMEA e buffers de dados maiores. -
Macros como
HOT_START, SET_NMEA_OUTPUT:Essas macros definem vários comandos do protocolo NMEA enviados ao módulo GPS L76X para configurar seu modo de operação, frequência de saída, taxa de baud e assim por diante. -
Definições de Struct:
-
GNRMC:Essa struct é usada para armazenar informações-chave analisadas de uma sentença NMEA GNRMC (Dados Mínimos Específicos Recomendados do GPS), incluindo longitude, latitude, hora, status e direções cardeais. -
Coordinates:Uma struct simples para armazenar a longitude e latitude de uma coordenada geográfica.
-
-
Variáveis Globais e Constantes:
buff_t:Um buffer global de tamanho BUFFSIZE usado para armazenar dados UART brutos.
-GPS: Uma instância global da struct GNRMC usada para armazenar os dados GPS analisados.
-
uart_dev:Um ponteiro para a struct do dispositivo UART, usado para comunicação UART. -
new_gnrmc_available:Um sinalizador booleano volátil que é definido como verdadeiro quando uma nova sentença GNRMC válida é recebida, notificando o loop principal que novos dados estão disponíveis para processamento. -
Função
uart_callback():-
Esta é uma função de callback de interrupção UART que é acionada quando o UART recebe dados.
-
A função lê o FIFO UART byte a byte e processa os dados como uma sentença completa quando um caractere de nova linha \n é encontrado.
-
Função Principal main()
-
Inicialização do Sistema:
-
nrfx_power_constlat_mode_request():Solicita um modo de latência constante para garantir que o gerenciamento de energia não interfira nas operações em tempo real. -
uart_dev = DEVICE_DT_GET:Recupera o identificador do dispositivo UART e usa device_is_ready() para verificar se o dispositivo está pronto. -
uart_irq_callback_user_data_set()euart_irq_rx_enable():Esses configuram e habilitam a interrupção de recepção UART, registrando a função uart_callback como o manipulador de interrupção para garantir a recepção assíncrona dos dados GPS.
-
-
Inicialização do Módulo GPS:
L76X_Send_Command(SET_NMEA_OUTPUT):Um comando é enviado para configurar o módulo GPS para produzir apenas sentenças NMEA especificadas como GNRMC, reduzindo o tráfego de dados desnecessário.
-L76X_Send_Command(SET_POS_FIX_1S): Define a frequência de atualização de posição do módulo GPS para 1 segundo.
-
Loop Principal:
-
O loop é executado indefinidamente, verificando continuamente o sinalizador new_gnrmc_available.
-
Se o sinalizador for verdadeiro, ele copia a sentença GPS mais recente de latest_gnrmc para buff_t e, em seguida, chama L76X_Gat_GNRMC() para analisar os dados.
-
Com base no resultado da análise, ele imprime a hora, longitude e latitude WGS-84, e as coordenadas convertidas do Baidu e Google.
-
Se GPS.Status for 0, ele imprime uma mensagem de "falha no posicionamento".
-
Se nenhum dado novo estiver disponível, ele imprime "No new GNRMC data available."
-
k_msleep(2000):O programa pausa por 2 segundos após cada loop para controlar a frequência de saída.
-
Gráfico de Resultado
![]() | ![]() |
I2C
Preparação de Hardware
| Seeed Studio XIAO nRF54L15 Sense | Seeed Studio Expansion Board Base for XIAO |
|---|---|
![]() | ![]() |
Implementação de Software
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/display/cfb.h>
#include <stdio.h>
#include <string.h>
#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main_app, LOG_LEVEL);
/**
* @brief Initializes the display device.
* @param[out] dev Pointer to the display device struct.
* @return 0 on success, -1 on failure.
*/
static int display_init(const struct device **dev) {
*dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(*dev)) {
LOG_ERR("Device %s not ready", (*dev)->name);
return -1;
}
if (display_set_pixel_format(*dev, PIXEL_FORMAT_MONO10) != 0) {
if (display_set_pixel_format(*dev, PIXEL_FORMAT_MONO01) != 0) {
LOG_ERR("Failed to set required pixel format");
return -1;
}
}
LOG_INF("Initialized %s", (*dev)->name);
return 0;
}
/**
* @brief Initializes the Compact Framebuffer (CFB) and display blanking.
* @param dev Pointer to the display device struct.
* @return 0 on success, -1 on failure.
*/
static int framebuffer_setup(const struct device *dev) {
if (cfb_framebuffer_init(dev)) {
LOG_ERR("Framebuffer initialization failed!");
return -1;
}
cfb_framebuffer_clear(dev, true);
display_blanking_off(dev);
return 0;
}
/**
* @brief Selects a suitable font for the display.
* @param dev Pointer to the display device struct.
* @param[out] font_width Pointer to store the width of the selected font.
* @param[out] font_height Pointer to store the height of the selected font.
* @return 0 on success, -1 if no suitable font is found.
*/
static int select_font(const struct device *dev, uint8_t *font_width, uint8_t *font_height) {
int chosen_font_idx = -1;
uint8_t current_font_width, current_font_height;
for (int idx = 0; idx < 42; idx++) {
if (cfb_get_font_size(dev, idx, ¤t_font_width, ¤t_font_height) == 0) {
if (current_font_width == 8 && current_font_height == 8) {
chosen_font_idx = idx;
*font_width = current_font_width;
*font_height = current_font_height;
cfb_framebuffer_set_font(dev, chosen_font_idx);
LOG_INF("Selected font idx: %d, width: %d, height: %d", chosen_font_idx, *font_width, *font_height);
break;
}
if (chosen_font_idx == -1 && current_font_width > 0 && current_font_height > 0) {
chosen_font_idx = idx;
*font_width = current_font_width;
*font_height = current_font_height;
cfb_framebuffer_set_font(dev, chosen_font_idx);
LOG_INF("Defaulting to font idx: %d, width: %d, height: %d", chosen_font_idx, *font_width, *font_height);
}
} else {
break;
}
}
if (chosen_font_idx == -1) {
LOG_ERR("No suitable font found or loaded!");
return -1;
}
return 0;
}
/**
* @brief Prints a single line of text at specified row and column.
* @param dev Pointer to the display device struct.
* @param text The string to print.
* @param row The row number (0-indexed) where the text should start.
* @param col The column number (0-indexed) where the text should start.
* @param font_width The width of the currently selected font in pixels.
* @param font_height The height of the currently selected font in pixels.
*/
static void print_text_by_row_col(const struct device *dev, const char *text, int row, int col,
uint8_t font_width, uint8_t font_height) {
int pixel_x = col * font_width;
int pixel_y = row * font_height;
if (cfb_print(dev, text, pixel_x, pixel_y)) {
LOG_ERR("Failed to print text: \"%s\" at row %d, col %d", text, row, col);
}
}
int main(void) {
const struct device *dev;
uint8_t font_width = 0;
uint8_t font_height = 0;
uint16_t x_res, y_res;
if (display_init(&dev) != 0) {
return 0;
}
if (framebuffer_setup(dev) != 0) {
return 0;
}
if (select_font(dev, &font_width, &font_height) != 0) {
return 0;
}
x_res = cfb_get_display_parameter(dev, CFB_DISPLAY_WIDTH);
y_res = cfb_get_display_parameter(dev, CFB_DISPLAY_HEIGHT);
LOG_INF("Display resolution: %dx%d", x_res, y_res);
cfb_set_kerning(dev, 0);
while (1) {
cfb_framebuffer_clear(dev, false);
const char *line1_text = "nRF54L15";
// Print line1 at row 1, column 2
print_text_by_row_col(dev, line1_text, 1, 2, font_width, font_height);
const char *line2_text = "Hello World";
// Print line2 at row 2, column 1
print_text_by_row_col(dev, line2_text, 2, 1, font_width, font_height);
cfb_framebuffer_finalize(dev);
k_sleep(K_MSEC(1000));
}
return 0;
}
Configuração e Inicialização do Dispositivo de Exibição
-
Módulo de Log
main_app:- #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL e LOG_MODULE_REGISTER(main_app, LOG_LEVEL) registram um módulo de log chamado main_app e definem seu nível de log para a configuração padrão do sistema. Isso permite que os desenvolvedores depurem e exibam informações facilmente através do sistema de logging do Zephyr.
-
Função
display_init():-
*dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));:Esta linha recupera o dispositivo de exibição escolhido da Árvore de Dispositivos do Zephyr. Essa abordagem garante que o código seja independente de hardware. -
display_set_pixel_format(*dev, PIXEL_FORMAT_MONO10):O código tenta definir o formato de pixel do display para PIXEL_FORMAT_MONO10. Se isso falhar, ele tenta PIXEL_FORMAT_MONO01. Isso garante que o display opere em modo monocromático, o que é necessário para algumas tecnologias de display (por exemplo, OLED ou e-Paper).
-
-
Função
framebuffer_setup():-
cfb_framebuffer_init(dev):Isso inicializa o Compact Framebuffer (CFB). O CFB é uma biblioteca gráfica leve no Zephyr usada para desenhar texto e gráficos simples em displays. -
cfb_framebuffer_clear(dev, true):Isso limpa o framebuffer e imediatamente grava seu conteúdo no display, garantindo uma tela limpa. -
display_blanking_off(dev):Isso desativa o recurso de blanking do display, que normalmente é um sinal de que o display está pronto para receber dados e exibir uma imagem.
-
-
Função
select_font():-
cfb_get_font_size():Esta função percorre as fontes disponíveis para encontrar uma adequada. -
O código prioriza uma fonte de
8x8pixels, pois é uma fonte pequena comum e de fácil leitura. -
Se uma fonte
8x8não for encontrada, ele seleciona a primeira fonte disponível com tamanho diferente de zero como alternativa. -
cfb_framebuffer_set_font(dev, chosen_font_idx):Uma vez encontrada uma fonte adequada, ela é definida como a fonte atual para o framebuffer.
-
-
Função
print_text_by_row_col():
-int pixel_x = col * font_width;e int pixel_y = row * font_height;: Esta função converte as coordenadas de linha e coluna do texto (em unidades de caractere) para coordenadas de pixel, tornando o posicionamento do texto mais intuitivo.
cfb_print():Esta é a função principal da biblioteca CFB usada para imprimir texto na localização de pixel especificada.
Loop Principal
A lógica central do código é executada dentro de um loop infinito while (1):
-
Limpando a Tela:
cfb_framebuffer_clear(dev, false):No início de cada loop, isso limpa o framebuffer sem atualizar imediatamente o display. Isso permite que múltiplos elementos sejam desenhados de uma vez, evitando cintilação da tela. -
Imprimindo Texto:
-
Duas strings,
line1_texteline2_text, são definidas. -
print_text_by_row_col(): A função personalizada é usada para imprimir essas duas linhas de texto em posições de linha e coluna especificadas na tela. A primeira linha é impressa em
(1, 2)e a segunda linha em(2, 1). -
Atualizando o Display:
cfb_framebuffer_finalize(dev): Esta função envia todos os comandos de desenho pendentes do framebuffer para o display de uma vez, fazendo com que todo o conteúdo apareça simultaneamente. -
Atraso:
k_sleep(K_MSEC(1000)):Após cada loop, o programa pausa por 1000 milissegundos (1 segundo). Isso controla a frequência de atualização da tela, o que é adequado para aplicações que exibem informações estáticas, como um relógio ou dados de sensores, de forma estável.
-
Gráfico de Resultado

SPI
Preparação de Hardware
| Seeed Studio XIAO nRF54L15 Sense | ePaper Driver Board for Seeed Studio XIAO |
|---|---|
![]() | ![]() |
Implementação de Software
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <lvgl.h>
#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(epaper_simple);
int main(void)
{
// Get display device
const struct device *display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(display_dev)) {
LOG_ERR("Display device not ready!");
return 0;
}
LOG_INF("Display device ready.");
// Initialize LVGL
// Must be called before any LVGL object creation or operation
lv_init();
// Turn off display blanking (for ePaper, this usually triggers a full refresh to clear old content)
if (display_blanking_off(display_dev)) {
LOG_ERR("Failed to turn off display blanking!");
return 0;
}
LOG_INF("Display blanking is off. Screen should be cleared by full refresh.");
// Get the current active screen and set its background to white
// This is also an LVGL-level "clear" operation to ensure the canvas is white
lv_obj_t *scr = lv_scr_act();
lv_obj_set_style_bg_color(scr, lv_color_white(), LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, LV_STATE_DEFAULT);
// Remove screen padding and scrollbar
lv_obj_set_style_pad_all(scr, 0, LV_STATE_DEFAULT);
lv_obj_set_scrollbar_mode(scr, LV_SCROLLBAR_MODE_OFF);
// Get display width and height (for layout)
lv_disp_t *disp = lv_disp_get_default();
lv_coord_t width = lv_disp_get_hor_res(disp);
lv_coord_t height = lv_disp_get_ver_res(disp);
LOG_INF("Display width: %d, height: %d", width, height);
// Create a centered panel
lv_obj_t *panel = lv_obj_create(scr);
lv_obj_set_size(panel, 300, 100);
lv_obj_align(panel, LV_ALIGN_CENTER, 0, 0);
// Set panel background to white, border to black for visibility
lv_obj_set_style_bg_color(panel, lv_color_white(), LV_STATE_DEFAULT);
lv_obj_set_style_border_color(panel, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_border_width(panel, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(panel, 10, LV_STATE_DEFAULT);
// Add text to the panel
lv_obj_t *label = lv_label_create(panel);
lv_label_set_text(label, "HELLO EPAPER");
// Set text color to black for visibility on white background
lv_obj_set_style_text_color(label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(label, &lv_font_montserrat_24, LV_STATE_DEFAULT);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
// Add a time label at the top right
lv_obj_t *time_label = lv_label_create(scr);
lv_label_set_text(time_label, "Time 07:21 PM");
lv_obj_set_style_text_color(time_label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(time_label, &lv_font_montserrat_18, LV_STATE_DEFAULT);
lv_obj_align(time_label, LV_ALIGN_TOP_RIGHT, -20, 10);
// Add a Zephyr logo at the top left
lv_obj_t *zephyr_label = lv_label_create(scr);
lv_label_set_text(zephyr_label, "Powered by Zephyr");
lv_obj_set_style_text_color(zephyr_label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(zephyr_label, &lv_font_montserrat_24, LV_STATE_DEFAULT);
lv_obj_align(zephyr_label, LV_ALIGN_BOTTOM_LEFT, 20, -10);
// Add author label at the bottom right
lv_obj_t *author_label = lv_label_create(scr);
lv_label_set_text(author_label, "Author: Stellar");
lv_obj_set_style_text_color(author_label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(author_label, &lv_font_montserrat_16, LV_STATE_DEFAULT);
lv_obj_align(author_label, LV_ALIGN_BOTTOM_RIGHT, -20, -10);
// Add four squares at the top left with a for loop
lv_obj_t *squares[4];
int square_offsets = 20;
for (int i = 0; i < 4; i++) {
squares[i] = lv_obj_create(scr);
lv_obj_set_size(squares[i], 30, 30);
lv_obj_set_style_bg_color(squares[i], lv_color_white(), LV_STATE_DEFAULT);
lv_obj_set_style_border_color(squares[i], lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_border_width(squares[i], 2, LV_STATE_DEFAULT);
lv_obj_set_style_radius(squares[i], 0, LV_STATE_DEFAULT);
lv_obj_align(squares[i], LV_ALIGN_TOP_LEFT, square_offsets, 20);
square_offsets+=40;
}
while (1) {
lv_task_handler();
k_sleep(K_MSEC(1000)); // Lower refresh rate, suitable for ePaper
}
return 0;
}
Inicialização do Dispositivo:
-
O código primeiro obtém o dispositivo de exibição da árvore de dispositivos usando
DEVICE_DT_GET(DT_CHOSEN(zephyr_display)). -
Em seguida, chama
device_is_ready()para verificar se o dispositivo está devidamente inicializado e pronto para operação. Este é um primeiro passo crucial para qualquer interação com hardware.
Inicialização do LVGL:
lv_init()é o ponto de entrada da biblioteca gráfica LVGL. Deve ser chamado antes que qualquer objeto LVGL seja criado ou qualquer operação seja realizada, pois inicializa o estado interno da biblioteca.
Limpeza da Tela:
-
A função
display_blanking_off()é chamada. Para displays de E-Paper, isso normalmente aciona uma atualização completa para limpar qualquer conteúdo antigo na tela. -
Para garantir ainda mais uma tela limpa, o código usa
lv_scr_act()para obter a tela ativa atual e define sua cor de fundo como branco usandolv_obj_set_style_bg_color(), cobrindo toda a área de exibição.
Preparação do Layout da Tela:
-
As funções
lv_disp_get_hor_res()elv_disp_get_ver_res()são usadas para obter a largura e altura reais do display, o que é útil para o posicionamento preciso de elementos de UI posteriormente. -
O código também remove o preenchimento da tela
(lv_obj_set_style_pad_all())e a barra de rolagem(lv_obj_set_scrollbar_mode())para maximizar a área de desenho utilizável.
Criação e Configuração de Elementos de UI:
-
Painel: Um objeto de painel é criado com
lv_obj_create(scr). Seu tamanho e alinhamento centralizado são definidos usandolv_obj_set_size()elv_obj_align(). Seu estilo, incluindo o fundo branco e a borda preta, é configurado com funções comolv_obj_set_style_bg_color()elv_obj_set_style_border_color(). -
Rótulos:
-
lv_label_create()é usado para criar rótulos de texto. -
lv_label_set_text()define o conteúdo de texto dos rótulos. -
lv_obj_set_style_text_color()e lv_obj_set_style_text_font() são usados para definir a cor e o tamanho da fonte do texto.
-
-
A função
lv_obj_align()posiciona cada rótulo em um local específico na tela, como centro, canto superior direito, canto inferior esquerdo e canto inferior direito.
Quadrados: Um laço for é usado para criar quatro pequenos objetos quadrados. Seu tamanho, estilo (preenchimento branco com borda preta) e posição são definidos sequencialmente, organizando-os horizontalmente no canto superior esquerdo da tela.
Loop Principal:
-
O laço
while(1)é a parte de execução contínua do programa. -
lv_task_handler()é chamado continuamente dentro do laço para processar todas as tarefas internas do LVGL, como atualizar elementos de UI e tratar eventos. -
k_sleep(K_MSEC(1000))pausa a thread por 1000 milissegundos. Para displays estáticos d
Gráfico de Resultado

Suporte Técnico e Discussão sobre Produtos
Obrigado por escolher nossos produtos! Estamos aqui para fornecer diferentes tipos de suporte para garantir que sua experiência com nossos produtos seja a mais tranquila possível. Oferecemos vários canais de comunicação para atender a diferentes preferências e necessidades.









