Pular para o conteúdo principal

Multiplexação de pinos com Seeed Studio XIAO nRF54L15 Sense

Para facilitar o uso, todos os seguintes exemplos de multiplexação de pinos são em PlatformIO. Clique neste link para obter um guia de configuração e uso do XIAO nRF54L5

dica

Com base no VS Code, se você quiser usar o seguinte caso 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 arquivo conf.

Botões integrados

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 de Reset e o Botão de Usuário. Entender suas funções é essencial para o uso diário e para atualizações de firmware.


Visão geral de hardware

Frente do XIAO nRF54L15

Verso do XIAO nRF54L15

Botão de Reset

O botão de Reset é usado para executar uma operação de reset forçado 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.
    • Resolver programas travados: Quando o programa em execução no dispositivo trava, entra em um loop infinito ou deixa de responder, pressionar o botão de Reset é a maneira mais rápida de forçá-lo a voltar a um estado operacional normal.
    • Sem impacto no firmware: Uma operação de reset não apaga nem altera o firmware já programado no dispositivo. Ela simplesmente reinicia o aplicativo atualmente em execução.

Botão de Usuário

O botão de Usuário é uma entrada versátil e programável que oferece controle flexível dentro de suas aplicações.

Funcionalidade:

  • Entrada personalizável: Diferente da função fixa do botão de Reset, a ação do botão de Usuário é totalmente definida pelo firmware que você programar.

  • Disparo de eventos: Ele 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 SenseSeeed Studio Expansion Base for XIAO with Grove OLEDGrove - 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 Device Tree

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios);

  • Esta linha de código utiliza o sistema de device tree do Zephyr para obter as informações do dispositivo 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)) and if (!gpio_is_ready_dt(&relay))

  • Antes de o programa começar a executar qualquer operação, o código verifica se os dispositivos botão e relé foram inicializados com sucesso e estão prontos. Esta é uma boa prática na programação de drivers em 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 irá monitorar o nível de tensão do pino para determinar se o botão foi 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. A flag GPIO_OUTPUT_ACTIVE geralmente indica que o pino ficará 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, ao pressionar o botão o pino é conectado 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), então o pino do relé é definido como 1 (alto), o que fecha o relé e liga o dispositivo (por exemplo, lâmpada) ao qual ele está conectado.

else: Se o botão não estiver pressionado (estado é 1), perform 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 ele 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 de anti-ruído. Este é um tratamento simples de anti-ruído que evita múltiplos disparos devido ao ruído 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 SenseGrove-Variable Color LEDGrove-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 dos dispositivos 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): Isto registra um módulo de log chamado pot_pwm_example e 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 da Device Tree que garante a existência de um arquivo overlay válido contendo definições de canais de 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 a Device Tree do Zephyr para obter automaticamente informações de todos os canais de ADC configurados. Essa abordagem torna o código flexível e portátil entre diferentes hardwares sem alterações manuais de configuração.

    • #define POTENTIOMETER_ADC_CHANNEL_IDX 1: Uma macro é definida para especificar a qual canal do array adc_channels o 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 obtém as informações do dispositivo PWM para o alias pwm_led a partir da 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 é 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, é feita uma verificação para confirmar se 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 específico do ADC conectado ao potenciômetro, incluindo sua resolução e ganho.

  • Inicialização do PWM:

    • !device_is_ready(led.dev): Semelhante ao ADC, esta linha verifica se o dispositivo PWM está pronto. Caso não esteja, um erro é registrado e o programa é encerrado.

    • LOG_INF(...): As informações de período e frequência do PWM são impressas para ajudar o usuário a confirmar a configuração.

  • Configuração da Sequência do ADC:

    • struct adc_sequence sequence: Uma struct adc_sequence é definida para descrever uma única operação de conversão do ADC. Ela especifica o buffer (adc_raw_value) para armazenar o resultado, seu tamanho (sizeof(adc_raw_value)) e a resolução do ADC a ser usada.

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 do ADC é inicializada para garantir que a configuração correta seja usada em cada leitura.

    • adc_read(): Isso aciona uma conversão do 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ável sensor_value.

  • Mapeando o Valor do ADC para o Ciclo de Trabalho do 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 principal de mapeamento. Ela escala o valor bruto do ADC (sensor_value) proporcionalmente à faixa do período PWM (PWM_PERIOD_NS) para obter um valor de ciclo de trabalho que ajusta o brilho do LED.

  • Definindo o Ciclo de Trabalho do PWM:

    • pwm_set_dt(): Esta função aplica o novo ciclo de trabalho 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 loop. Isso controla a frequência das leituras do ADC e das atualizações do PWM, evitando carga excessiva da CPU e proporcionando uma experiência estável ao usuário.

Gráfico de resultado

UART

Preparação de Hardware

Seeed Studio XIAO nRF54L15 SenseMó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 envie informações por meio do sistema de log do Zephyr, o que é útil para depuração e monitoramento.

  • Definições de Tipos e Macros:

-UBYTE, UWORD, UDOUBLE: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: 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: Esta struct é usada para armazenar informações-chave analisadas de uma sentença NMEA GNRMC (GPS Recommended Minimum Specific data), incluindo longitude, latitude, hora, status e direções cardeais.

    • Coordinates: Uma struct simples para armazenar a longitude e a latitude de uma coordenada geográfica.

  • Variáveis Globais e Constantes:

    • buff_t: Um buffer global de tamanho BUFFSIZE usado para armazenar dados brutos da UART.

-GPS:Uma instância global de struct GNRMC usada para manter os dados de GPS analisados.

  • uart_dev: Um ponteiro para a struct do dispositivo UART, usado para comunicação UART.

  • new_gnrmc_available: Uma flag booleana volátil que é definida como true quando uma nova sentença GNRMC válida é recebida, notificando o loop principal de que novos dados estão disponíveis para processamento.

  • Função uart_callback():

    • Esta é uma função de callback de interrupção da UART que é acionada quando a UART recebe dados.

    • A função lê o FIFO da 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: Obtém o handle do dispositivo UART e usa device_is_ready() para verificar se o dispositivo está pronto.

    • uart_irq_callback_user_data_set()and uart_irq_rx_enable():Essas funções configuram e habilitam a interrupção de recepção da UART, registrando a função uart_callback como o manipulador de interrupção para garantir a recepção assíncrona dos dados de GPS.

  • Inicialização do Módulo GPS:

    • L76X_Send_Command(SET_NMEA_OUTPUT):Um comando é enviado para configurar o módulo GPS para emitir 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 a flag new_gnrmc_available.

    • Se a flag for true, ele copia a sentença GPS mais recente de latest_gnrmc para buff_t e então chama L76X_Gat_GNRMC() para analisar os dados.

    • Com base no resultado da análise, imprime a hora, a longitude e latitude WGS-84 e as coordenadas convertidas do Baidu e do Google.

    • Se GPS.Status for 0, imprime uma mensagem de "falha no posicionamento".

    • Se nenhum dado novo estiver disponível, 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 resultados

I2C

Preparação de Hardware

Seeed Studio XIAO nRF54L15 SenseSeeed 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, &current_font_width, &current_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 por meio do sistema de log do Zephyr.
  • Função display_init():

    • *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));: Esta linha obtém o dispositivo de exibição escolhido a partir da Device Tree 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 da tela para PIXEL_FORMAT_MONO10. Se isso falhar, ele tenta então PIXEL_FORMAT_MONO01. Isso garante que a tela opere em modo monocromático, o que é necessário para algumas tecnologias de exibição (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 telas.

    • cfb_framebuffer_clear(dev, true):Isso limpa o framebuffer e grava imediatamente seu conteúdo na tela, garantindo uma tela limpa.

    • display_blanking_off(dev):Isso desativa o recurso de blanking da tela, que normalmente é um sinal de que a tela está pronta 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 fácil de ler.

    • Se uma fonte 8x8 nã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 caracteres) em coordenadas de pixels, tornando o posicionamento do texto mais intuitivo.

  • cfb_print(): Esta é a função principal da biblioteca CFB usada para imprimir texto na posição de pixel especificada.

Loop principal A lógica central do código é executada dentro de um loop infinito while (1):

  • Limpeza da tela: cfb_framebuffer_clear(dev, false): No início de cada loop, isso limpa o framebuffer sem atualizar imediatamente a tela. Isso permite que vários elementos sejam desenhados de uma só vez, evitando cintilação na tela.

  • Impressão de texto:

    • Duas strings, line1_text e line2_text, são definidas.

    • print_text_by_row_col(): A função personalizada é usada para imprimir essas duas linhas de texto em posições específicas de linha e coluna na tela. A primeira linha é impressa em (1, 2) e a segunda linha em (2, 1).

    • Atualização da tela: cfb_framebuffer_finalize(dev): Esta função envia todos os comandos de desenho pendentes do framebuffer para a tela de uma só 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 SensePlaca controladora de ePaper para 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 display da device tree 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 para a biblioteca gráfica LVGL. Ele deve ser chamado antes que quaisquer objetos LVGL sejam criados ou quaisquer operações sejam realizadas, 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 para branco usando lv_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() e lv_disp_get_ver_res() são usadas para obter a largura e a altura reais do display, o que é útil para o posicionamento preciso de elementos de UI posteriormente.

  • O código também remove o padding da tela (lv_obj_set_style_pad_all()) e a barra de rolagem (lv_obj_set_scrollbar_mode()) para maximizar a área útil de desenho.

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 usando lv_obj_set_size() e lv_obj_align(). Seu estilo, incluindo o fundo branco e a borda preta, é configurado com funções como lv_obj_set_style_bg_color() e lv_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 do texto e o tamanho da fonte.

  • 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 loop while(1) é a parte de execução contínua do programa.

  • lv_task_handler() é chamado continuamente dentro do loop para processar todas as tarefas internas do LVGL, como atualizar elementos de UI e lidar com eventos.

  • k_sleep(K_MSEC(1000)) pausa a thread por 1000 milissegundos. Para d estático

Gráfico de resultado

Suporte técnico e discussão de produto

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.

Loading Comments...