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...