Initial commit - sync AliceRC to Gitea
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "audio.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver
|
||||
)
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @file audio.c
|
||||
* @brief Audio driver implementation
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_log.h"
|
||||
#include "audio.h"
|
||||
|
||||
static const char *TAG = "audio";
|
||||
|
||||
static uint8_t audio_volume = 50;
|
||||
|
||||
esp_err_t audio_init(int gpio)
|
||||
{
|
||||
/* Configure LEDC PWM for audio output */
|
||||
ledc_timer_config_t timer_cfg = {
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.duty_resolution = LEDC_TIMER_8_BIT,
|
||||
.timer_num = LEDC_TIMER_0,
|
||||
.freq_hz = AUDIO_SAMPLE_RATE,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
};
|
||||
ESP_ERROR_CHECK(ledc_timer_config(&timer_cfg));
|
||||
|
||||
ledc_channel_config_t channel_cfg = {
|
||||
.gpio_num = gpio,
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.channel = LEDC_CHANNEL_0,
|
||||
.timer_sel = LEDC_TIMER_0,
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(ledc_channel_config(&channel_cfg));
|
||||
|
||||
ESP_LOGI(TAG, "Audio initialized on GPIO %d", gpio);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void audio_beep(uint16_t freq_hz, uint16_t duration_ms)
|
||||
{
|
||||
if (freq_hz == 0) return;
|
||||
|
||||
/* Set PWM frequency for the beep */
|
||||
ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, freq_hz);
|
||||
|
||||
/* Set duty cycle based on volume */
|
||||
uint32_t duty = (audio_volume * 128) / 100;
|
||||
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty);
|
||||
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(duration_ms));
|
||||
|
||||
/* Stop */
|
||||
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
|
||||
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
||||
}
|
||||
|
||||
void audio_play_startup(void)
|
||||
{
|
||||
audio_beep(880, 100);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
audio_beep(1320, 150);
|
||||
}
|
||||
|
||||
void audio_play_warning(void)
|
||||
{
|
||||
for (int i = 0; i < 3; i++) {
|
||||
audio_beep(440, 100);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
void audio_set_volume(uint8_t volume)
|
||||
{
|
||||
if (volume > 100) volume = 100;
|
||||
audio_volume = volume;
|
||||
}
|
||||
|
||||
uint8_t audio_get_volume(void)
|
||||
{
|
||||
return audio_volume;
|
||||
}
|
||||
|
||||
void audio_task(void *pvParameters)
|
||||
{
|
||||
ESP_LOGI(TAG, "Audio task started");
|
||||
|
||||
/* Audio processing loop - handles audio queue */
|
||||
while (1) {
|
||||
/* TODO: Process audio queue for WAV playback */
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file audio.h
|
||||
* @brief Audio driver for PWM/DAC audio output
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Audio sample rate */
|
||||
#define AUDIO_SAMPLE_RATE 8000
|
||||
|
||||
/* Initialize audio output (PWM for ESP32S3, DAC for ESP32) */
|
||||
esp_err_t audio_init(int gpio);
|
||||
|
||||
/* Play a beep/tone */
|
||||
void audio_beep(uint16_t freq_hz, uint16_t duration_ms);
|
||||
|
||||
/* Play a startup sound */
|
||||
void audio_play_startup(void);
|
||||
|
||||
/* Play a warning beep */
|
||||
void audio_play_warning(void);
|
||||
|
||||
/* Set volume (0-100) */
|
||||
void audio_set_volume(uint8_t volume);
|
||||
|
||||
/* Get current volume */
|
||||
uint8_t audio_get_volume(void);
|
||||
|
||||
/* Audio task for continuous audio playback */
|
||||
void audio_task(void *pvParameters);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "nrf24l01.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver
|
||||
)
|
||||
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* @file nrf24l01.c
|
||||
* @brief NRF24L01+ driver implementation using SPI
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "nrf24l01.h"
|
||||
|
||||
static const char *TAG = "nrf24";
|
||||
|
||||
/* SPI device handle */
|
||||
static spi_device_handle_t nrf_spi = NULL;
|
||||
static int nrf_ce_gpio = -1;
|
||||
|
||||
/* Default 5-byte address */
|
||||
static const uint8_t default_addr[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
|
||||
|
||||
/* SPI transaction helper */
|
||||
static uint8_t nrf_spi_transfer(uint8_t byte)
|
||||
{
|
||||
spi_transaction_t t = {
|
||||
.length = 8,
|
||||
.tx_buffer = &byte,
|
||||
.rx_buffer = &byte,
|
||||
};
|
||||
spi_device_transmit(nrf_spi, &t);
|
||||
return byte;
|
||||
}
|
||||
|
||||
/* Write a register */
|
||||
static void nrf_write_reg(uint8_t reg, uint8_t value)
|
||||
{
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_W_REGISTER | reg);
|
||||
nrf_spi_transfer(value);
|
||||
}
|
||||
|
||||
/* Read a register */
|
||||
static uint8_t nrf_read_reg(uint8_t reg)
|
||||
{
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_R_REGISTER | reg);
|
||||
return nrf_spi_transfer(NRF_CMD_NOP);
|
||||
}
|
||||
|
||||
/* Write multiple bytes to a register (for addresses) */
|
||||
static void nrf_write_multi(uint8_t reg, const uint8_t *data, uint8_t len)
|
||||
{
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_W_REGISTER | reg);
|
||||
for (int i = 0; i < len; i++) {
|
||||
nrf_spi_transfer(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read multiple bytes from a register */
|
||||
static void nrf_read_multi(uint8_t reg, uint8_t *data, uint8_t len)
|
||||
{
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_R_REGISTER | reg);
|
||||
for (int i = 0; i < len; i++) {
|
||||
data[i] = nrf_spi_transfer(NRF_CMD_NOP);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t nrf24_init(int mosi, int miso, int sclk, int cs, int ce, int irq)
|
||||
{
|
||||
nrf_ce_gpio = ce;
|
||||
|
||||
/* Configure SPI bus */
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = mosi,
|
||||
.miso_io_num = miso,
|
||||
.sclk_io_num = sclk,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 32,
|
||||
};
|
||||
|
||||
spi_device_interface_config_t dev_cfg = {
|
||||
.mode = 0, /* SPI mode 0: CPOL=0, CPHA=0 */
|
||||
.clock_speed_hz = 10 * 1000 * 1000, /* 10 MHz */
|
||||
.spics_io_num = cs,
|
||||
.queue_size = 1,
|
||||
.flags = SPI_DEVICE_HALFDUPLEX,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO));
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev_cfg, &nrf_spi));
|
||||
|
||||
/* Configure CE pin */
|
||||
gpio_config_t ce_conf = {
|
||||
.pin_bit_mask = (1ULL << ce),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&ce_conf);
|
||||
gpio_set_level(ce, 0);
|
||||
|
||||
/* Wait for power-on */
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
|
||||
/* Configure NRF24L01 */
|
||||
/* Disable auto-ack on all pipes (we use ACK payload) */
|
||||
nrf_write_reg(NRF_REG_EN_AA, 0x01); /* Only pipe 0 auto-ack */
|
||||
nrf_write_reg(NRF_REG_EN_RXADDR, 0x01); /* Enable pipe 0 */
|
||||
nrf_write_reg(NRF_REG_SETUP_AW, 0x03); /* 5-byte address */
|
||||
nrf_write_reg(NRF_REG_SETUP_RETR, 0x1A); /* 250us retry, 10 retransmits */
|
||||
nrf_write_reg(NRF_REG_RF_CH, 100); /* Default channel */
|
||||
nrf_write_reg(NRF_REG_RF_SETUP, 0x06); /* 1Mbps, 0dBm */
|
||||
nrf_write_reg(NRF_REG_STATUS, 0x70); /* Clear interrupts */
|
||||
|
||||
/* Set default address */
|
||||
nrf_write_multi(NRF_REG_TX_ADDR, default_addr, 5);
|
||||
nrf_write_multi(NRF_REG_RX_ADDR_P0, default_addr, 5);
|
||||
|
||||
/* Set payload width for pipe 0 */
|
||||
nrf_write_reg(NRF_REG_RX_PW_P0, 32);
|
||||
|
||||
/* Enable dynamic payload length and ACK payload */
|
||||
nrf_write_reg(NRF_REG_FEATURE, 0x06); /* EN_DPL | EN_ACK_PAY */
|
||||
nrf_write_reg(NRF_REG_DYNPD, 0x01); /* DPL for pipe 0 */
|
||||
|
||||
/* Power up in RX mode by default */
|
||||
nrf_write_reg(NRF_REG_CONFIG, NRF_CONFIG_EN_CRC | NRF_CONFIG_CRCO | NRF_CONFIG_PWR_UP | NRF_CONFIG_PRIM_RX);
|
||||
gpio_set_level(ce, 1);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
|
||||
ESP_LOGI(TAG, "NRF24L01 initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void nrf24_set_channel(uint8_t channel)
|
||||
{
|
||||
if (channel > 125) channel = 125;
|
||||
nrf_write_reg(NRF_REG_RF_CH, channel);
|
||||
}
|
||||
|
||||
void nrf24_set_data_rate(nrf_data_rate_t rate)
|
||||
{
|
||||
uint8_t rf_setup = nrf_read_reg(NRF_REG_RF_SETUP);
|
||||
rf_setup &= 0xD7; /* Clear RF_DR bits */
|
||||
|
||||
switch (rate) {
|
||||
case NRF_1MBPS:
|
||||
rf_setup |= (0 << 3); /* RF_DR_LOW=0, RF_DR_HIGH=0 */
|
||||
break;
|
||||
case NRF_2MBPS:
|
||||
rf_setup |= (1 << 3); /* RF_DR_HIGH=1 */
|
||||
break;
|
||||
case NRF_250KBPS:
|
||||
rf_setup |= (1 << 5) | (0 << 3); /* RF_DR_LOW=1 */
|
||||
break;
|
||||
}
|
||||
nrf_write_reg(NRF_REG_RF_SETUP, rf_setup);
|
||||
}
|
||||
|
||||
void nrf24_set_power(nrf_pa_power_t power)
|
||||
{
|
||||
uint8_t rf_setup = nrf_read_reg(NRF_REG_RF_SETUP);
|
||||
rf_setup &= 0xF9; /* Clear PWR bits */
|
||||
rf_setup |= (power << 1);
|
||||
nrf_write_reg(NRF_REG_RF_SETUP, rf_setup);
|
||||
}
|
||||
|
||||
void nrf24_set_tx_addr(const uint8_t *addr)
|
||||
{
|
||||
nrf_write_multi(NRF_REG_TX_ADDR, addr, 5);
|
||||
}
|
||||
|
||||
void nrf24_set_rx_addr(const uint8_t *addr, uint8_t pipe)
|
||||
{
|
||||
if (pipe == 0) {
|
||||
nrf_write_multi(NRF_REG_RX_ADDR_P0, addr, 5);
|
||||
} else if (pipe <= 5) {
|
||||
nrf_write_reg(NRF_REG_RX_ADDR_P0 + pipe, addr[4]); /* Only LSB */
|
||||
}
|
||||
}
|
||||
|
||||
void nrf24_power_up_tx(void)
|
||||
{
|
||||
uint8_t config = nrf_read_reg(NRF_REG_CONFIG);
|
||||
config &= ~NRF_CONFIG_PRIM_RX; /* Set PTX */
|
||||
config |= NRF_CONFIG_PWR_UP;
|
||||
nrf_write_reg(NRF_REG_CONFIG, config);
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
}
|
||||
|
||||
void nrf24_power_up_rx(void)
|
||||
{
|
||||
uint8_t config = nrf_read_reg(NRF_REG_CONFIG);
|
||||
config |= NRF_CONFIG_PWR_UP | NRF_CONFIG_PRIM_RX;
|
||||
nrf_write_reg(NRF_REG_CONFIG, config);
|
||||
gpio_set_level(nrf_ce_gpio, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
}
|
||||
|
||||
void nrf24_power_down(void)
|
||||
{
|
||||
uint8_t config = nrf_read_reg(NRF_REG_CONFIG);
|
||||
config &= ~NRF_CONFIG_PWR_UP;
|
||||
nrf_write_reg(NRF_REG_CONFIG, config);
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
}
|
||||
|
||||
esp_err_t nrf24_tx_payload(const uint8_t *data, uint8_t len)
|
||||
{
|
||||
if (len > 32) len = 32;
|
||||
|
||||
nrf24_power_up_tx();
|
||||
|
||||
/* Write payload */
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_W_TX_PAYLOAD);
|
||||
for (int i = 0; i < len; i++) {
|
||||
nrf_spi_transfer(data[i]);
|
||||
}
|
||||
|
||||
/* Pulse CE to start transmission */
|
||||
gpio_set_level(nrf_ce_gpio, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
|
||||
/* Wait for TX_DS or MAX_RT */
|
||||
int timeout = 100;
|
||||
while (timeout--) {
|
||||
uint8_t status = nrf24_get_status();
|
||||
if (status & (1 << 5)) { /* TX_DS */
|
||||
nrf_write_reg(NRF_REG_STATUS, 0x20); /* Clear TX_DS */
|
||||
return ESP_OK;
|
||||
}
|
||||
if (status & (1 << 4)) { /* MAX_RT */
|
||||
nrf_write_reg(NRF_REG_STATUS, 0x10); /* Clear MAX_RT */
|
||||
nrf24_flush_tx();
|
||||
return ESP_FAIL;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
esp_err_t nrf24_rx_payload(uint8_t *data, uint8_t *len)
|
||||
{
|
||||
if (!nrf24_data_available()) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* Get payload width */
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_R_RX_PL_WID);
|
||||
*len = nrf_spi_transfer(NRF_CMD_NOP);
|
||||
|
||||
if (*len > 32 || *len == 0) {
|
||||
nrf24_flush_rx();
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
/* Read payload */
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_R_RX_PAYLOAD);
|
||||
for (int i = 0; i < *len; i++) {
|
||||
data[i] = nrf_spi_transfer(NRF_CMD_NOP);
|
||||
}
|
||||
|
||||
/* Clear RX_DR */
|
||||
nrf_write_reg(NRF_REG_STATUS, 0x40);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t nrf24_data_available(void)
|
||||
{
|
||||
uint8_t status = nrf24_get_status();
|
||||
return (status & (1 << 6)) ? 1 : 0; /* RX_DR */
|
||||
}
|
||||
|
||||
uint8_t nrf24_get_status(void)
|
||||
{
|
||||
return nrf_spi_transfer(NRF_CMD_NOP);
|
||||
}
|
||||
|
||||
void nrf24_flush_tx(void)
|
||||
{
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_FLUSH_TX);
|
||||
}
|
||||
|
||||
void nrf24_flush_rx(void)
|
||||
{
|
||||
gpio_set_level(nrf_ce_gpio, 0);
|
||||
nrf_spi_transfer(NRF_CMD_FLUSH_RX);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @file nrf24l01.h
|
||||
* @brief NRF24L01+ driver header
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* NRF24L01 registers */
|
||||
#define NRF_REG_CONFIG 0x00
|
||||
#define NRF_REG_EN_AA 0x01
|
||||
#define NRF_REG_EN_RXADDR 0x02
|
||||
#define NRF_REG_SETUP_AW 0x03
|
||||
#define NRF_REG_SETUP_RETR 0x04
|
||||
#define NRF_REG_RF_CH 0x05
|
||||
#define NRF_REG_RF_SETUP 0x06
|
||||
#define NRF_REG_STATUS 0x07
|
||||
#define NRF_REG_OBSERVE_TX 0x08
|
||||
#define NRF_REG_RPD 0x09
|
||||
#define NRF_REG_RX_ADDR_P0 0x0A
|
||||
#define NRF_REG_RX_ADDR_P1 0x0B
|
||||
#define NRF_REG_RX_ADDR_P2 0x0C
|
||||
#define NRF_REG_RX_ADDR_P3 0x0D
|
||||
#define NRF_REG_RX_ADDR_P4 0x0E
|
||||
#define NRF_REG_RX_ADDR_P5 0x0F
|
||||
#define NRF_REG_TX_ADDR 0x10
|
||||
#define NRF_REG_RX_PW_P0 0x11
|
||||
#define NRF_REG_RX_PW_P1 0x12
|
||||
#define NRF_REG_RX_PW_P2 0x13
|
||||
#define NRF_REG_RX_PW_P3 0x14
|
||||
#define NRF_REG_RX_PW_P4 0x15
|
||||
#define NRF_REG_RX_PW_P5 0x16
|
||||
#define NRF_REG_FIFO_STATUS 0x17
|
||||
#define NRF_REG_DYNPD 0x1C
|
||||
#define NRF_REG_FEATURE 0x1D
|
||||
|
||||
/* Commands */
|
||||
#define NRF_CMD_R_REGISTER 0x00
|
||||
#define NRF_CMD_W_REGISTER 0x20
|
||||
#define NRF_CMD_R_RX_PAYLOAD 0x61
|
||||
#define NRF_CMD_W_TX_PAYLOAD 0xA0
|
||||
#define NRF_CMD_FLUSH_TX 0xE1
|
||||
#define NRF_CMD_FLUSH_RX 0xE2
|
||||
#define NRF_CMD_REUSE_TX_PL 0xE3
|
||||
#define NRF_CMD_ACTIVATE 0x50
|
||||
#define NRF_CMD_R_RX_PL_WID 0x60
|
||||
#define NRF_CMD_W_ACK_PAYLOAD 0xA8
|
||||
#define NRF_CMD_W_TX_PAYLOAD_NOACK 0xB0
|
||||
#define NRF_CMD_NOP 0xFF
|
||||
|
||||
/* Config register bits */
|
||||
#define NRF_CONFIG_MASK_RX_DR (1<<6)
|
||||
#define NRF_CONFIG_MASK_TX_DS (1<<5)
|
||||
#define NRF_CONFIG_MASK_MAX_RT (1<<4)
|
||||
#define NRF_CONFIG_EN_CRC (1<<3)
|
||||
#define NRF_CONFIG_CRCO (1<<2)
|
||||
#define NRF_CONFIG_PWR_UP (1<<1)
|
||||
#define NRF_CONFIG_PRIM_RX (1<<0)
|
||||
|
||||
/* RF data rates */
|
||||
typedef enum {
|
||||
NRF_1MBPS = 0,
|
||||
NRF_2MBPS = 1,
|
||||
NRF_250KBPS = 2
|
||||
} nrf_data_rate_t;
|
||||
|
||||
/* RF power levels */
|
||||
typedef enum {
|
||||
NRF_PA_MIN = 0,
|
||||
NRF_PA_LOW = 1,
|
||||
NRF_PA_HIGH = 2,
|
||||
NRF_PA_MAX = 3
|
||||
} nrf_pa_power_t;
|
||||
|
||||
/* Initialize NRF24L01 with GPIO configuration */
|
||||
esp_err_t nrf24_init(int mosi, int miso, int sclk, int cs, int ce, int irq);
|
||||
|
||||
/* Set RF channel (0-125) */
|
||||
void nrf24_set_channel(uint8_t channel);
|
||||
|
||||
/* Set data rate */
|
||||
void nrf24_set_data_rate(nrf_data_rate_t rate);
|
||||
|
||||
/* Set TX power */
|
||||
void nrf24_set_power(nrf_pa_power_t power);
|
||||
|
||||
/* Set TX/RX address (5 bytes) */
|
||||
void nrf24_set_tx_addr(const uint8_t *addr);
|
||||
void nrf24_set_rx_addr(const uint8_t *addr, uint8_t pipe);
|
||||
|
||||
/* Power up in TX or RX mode */
|
||||
void nrf24_power_up_tx(void);
|
||||
void nrf24_power_up_rx(void);
|
||||
void nrf24_power_down(void);
|
||||
|
||||
/* Transmit a payload and wait for ACK (blocking) */
|
||||
esp_err_t nrf24_tx_payload(const uint8_t *data, uint8_t len);
|
||||
|
||||
/* Read received payload */
|
||||
esp_err_t nrf24_rx_payload(uint8_t *data, uint8_t *len);
|
||||
|
||||
/* Check if data is available in RX FIFO */
|
||||
uint8_t nrf24_data_available(void);
|
||||
|
||||
/* Get status */
|
||||
uint8_t nrf24_get_status(void);
|
||||
|
||||
/* Flush FIFOs */
|
||||
void nrf24_flush_tx(void);
|
||||
void nrf24_flush_rx(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "oled.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver
|
||||
)
|
||||
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* @file oled.c
|
||||
* @brief SSD1315 OLED 128x64 I2C driver implementation
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "oled.h"
|
||||
|
||||
static const char *TAG = "oled";
|
||||
|
||||
/* I2C configuration */
|
||||
#define I2C_PORT I2C_NUM_0
|
||||
#define OLED_ADDR 0x3C
|
||||
#define I2C_CLOCK_HZ 400000
|
||||
|
||||
/* Display buffer (128x64 / 8 = 1024 bytes) */
|
||||
static uint8_t display_buffer[OLED_WIDTH * OLED_PAGES];
|
||||
|
||||
/* Simple 8x16 font (ASCII 0x20-0x7F) */
|
||||
/* Using a basic 5x7 font doubled to 8x16 by row replication */
|
||||
static const uint8_t font_8x8[][8] = {
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* space */
|
||||
{0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00}, /* ! */
|
||||
{0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00}, /* " */
|
||||
{0x14,0x7F,0x14,0x7F,0x14,0x00,0x00,0x00}, /* # */
|
||||
{0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00,0x00}, /* $ */
|
||||
{0x23,0x13,0x08,0x64,0x62,0x00,0x00,0x00}, /* % */
|
||||
{0x36,0x49,0x55,0x22,0x50,0x00,0x00,0x00}, /* & */
|
||||
{0x00,0x05,0x03,0x00,0x00,0x00,0x00,0x00}, /* ' */
|
||||
{0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00}, /* ( */
|
||||
{0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00}, /* ) */
|
||||
{0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00,0x00}, /* * */
|
||||
{0x08,0x08,0x3E,0x08,0x08,0x00,0x00,0x00}, /* + */
|
||||
{0x00,0x50,0x30,0x00,0x00,0x00,0x00,0x00}, /* , */
|
||||
{0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00}, /* - */
|
||||
{0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00}, /* . */
|
||||
{0x20,0x10,0x08,0x04,0x02,0x00,0x00,0x00}, /* / */
|
||||
{0x3E,0x51,0x49,0x45,0x3E,0x00,0x00,0x00}, /* 0 */
|
||||
{0x00,0x42,0x7F,0x40,0x00,0x00,0x00,0x00}, /* 1 */
|
||||
{0x42,0x61,0x51,0x49,0x46,0x00,0x00,0x00}, /* 2 */
|
||||
{0x21,0x41,0x45,0x4B,0x31,0x00,0x00,0x00}, /* 3 */
|
||||
{0x18,0x14,0x12,0x7F,0x10,0x00,0x00,0x00}, /* 4 */
|
||||
{0x27,0x45,0x45,0x45,0x39,0x00,0x00,0x00}, /* 5 */
|
||||
{0x3C,0x4A,0x49,0x49,0x30,0x00,0x00,0x00}, /* 6 */
|
||||
{0x01,0x71,0x09,0x05,0x03,0x00,0x00,0x00}, /* 7 */
|
||||
{0x36,0x49,0x49,0x49,0x36,0x00,0x00,0x00}, /* 8 */
|
||||
{0x06,0x49,0x49,0x29,0x1E,0x00,0x00,0x00}, /* 9 */
|
||||
{0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00}, /* : */
|
||||
{0x00,0x56,0x36,0x00,0x00,0x00,0x00,0x00}, /* ; */
|
||||
{0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00}, /* < */
|
||||
{0x14,0x14,0x14,0x14,0x14,0x00,0x00,0x00}, /* = */
|
||||
{0x41,0x22,0x14,0x08,0x00,0x00,0x00,0x00}, /* > */
|
||||
{0x02,0x01,0x51,0x09,0x06,0x00,0x00,0x00}, /* ? */
|
||||
{0x32,0x49,0x79,0x41,0x3E,0x00,0x00,0x00}, /* @ */
|
||||
{0x7E,0x11,0x11,0x11,0x7E,0x00,0x00,0x00}, /* A */
|
||||
{0x7F,0x49,0x49,0x49,0x36,0x00,0x00,0x00}, /* B */
|
||||
{0x3E,0x41,0x41,0x41,0x22,0x00,0x00,0x00}, /* C */
|
||||
{0x7F,0x41,0x41,0x22,0x1C,0x00,0x00,0x00}, /* D */
|
||||
{0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00}, /* E */
|
||||
{0x7F,0x09,0x09,0x01,0x01,0x00,0x00,0x00}, /* F */
|
||||
{0x3E,0x41,0x41,0x51,0x32,0x00,0x00,0x00}, /* G */
|
||||
{0x7F,0x08,0x08,0x08,0x7F,0x00,0x00,0x00}, /* H */
|
||||
{0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00}, /* I */
|
||||
{0x20,0x40,0x41,0x3F,0x01,0x00,0x00,0x00}, /* J */
|
||||
{0x7F,0x08,0x14,0x22,0x41,0x00,0x00,0x00}, /* K */
|
||||
{0x7F,0x40,0x40,0x40,0x40,0x00,0x00,0x00}, /* L */
|
||||
{0x7F,0x02,0x04,0x02,0x7F,0x00,0x00,0x00}, /* M */
|
||||
{0x7F,0x04,0x08,0x10,0x7F,0x00,0x00,0x00}, /* N */
|
||||
{0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00}, /* O */
|
||||
{0x7F,0x09,0x09,0x09,0x06,0x00,0x00,0x00}, /* P */
|
||||
{0x3E,0x41,0x51,0x21,0x5E,0x00,0x00,0x00}, /* Q */
|
||||
{0x7F,0x09,0x19,0x29,0x46,0x00,0x00,0x00}, /* R */
|
||||
{0x46,0x49,0x49,0x49,0x31,0x00,0x00,0x00}, /* S */
|
||||
{0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00}, /* T */
|
||||
{0x3F,0x40,0x40,0x40,0x3F,0x00,0x00,0x00}, /* U */
|
||||
{0x1F,0x20,0x40,0x20,0x1F,0x00,0x00,0x00}, /* V */
|
||||
{0x7F,0x20,0x18,0x20,0x7F,0x00,0x00,0x00}, /* W */
|
||||
{0x63,0x14,0x08,0x14,0x63,0x00,0x00,0x00}, /* X */
|
||||
{0x03,0x04,0x78,0x04,0x03,0x00,0x00,0x00}, /* Y */
|
||||
{0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x00}, /* Z */
|
||||
};
|
||||
|
||||
/* I2C write command */
|
||||
static esp_err_t oled_write_cmd(uint8_t cmd)
|
||||
{
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, (OLED_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||
i2c_master_write_byte(i2c_cmd, 0x00, true); /* Co=0, D/C#=0 (command) */
|
||||
i2c_master_write_byte(i2c_cmd, cmd, true);
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, pdMS_TO_TICKS(100));
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* I2C write data */
|
||||
static esp_err_t oled_write_data(const uint8_t *data, size_t len)
|
||||
{
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, (OLED_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||
i2c_master_write_byte(i2c_cmd, 0x40, true); /* Co=0, D/C#=1 (data) */
|
||||
i2c_master_write(i2c_cmd, data, len, true);
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, pdMS_TO_TICKS(100));
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t oled_init(int sda, int scl)
|
||||
{
|
||||
/* Initialize I2C */
|
||||
i2c_config_t conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = sda,
|
||||
.scl_io_num = scl,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = I2C_CLOCK_HZ,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2c_param_config(I2C_PORT, &conf));
|
||||
ESP_ERROR_CHECK(i2c_driver_install(I2C_PORT, conf.mode, 0, 0, 0));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
/* SSD1315 initialization sequence */
|
||||
oled_write_cmd(0xAE); /* Display off */
|
||||
oled_write_cmd(0xD5); /* Set display clock divide ratio */
|
||||
oled_write_cmd(0x80);
|
||||
oled_write_cmd(0xA8); /* Set multiplex ratio */
|
||||
oled_write_cmd(0x3F); /* 64 lines */
|
||||
oled_write_cmd(0xD3); /* Set display offset */
|
||||
oled_write_cmd(0x00);
|
||||
oled_write_cmd(0x40); /* Set display start line */
|
||||
oled_write_cmd(0x8D); /* Charge pump setting */
|
||||
oled_write_cmd(0x14); /* Enable */
|
||||
oled_write_cmd(0x20); /* Memory addressing mode */
|
||||
oled_write_cmd(0x00); /* Horizontal */
|
||||
oled_write_cmd(0xA1); /* Segment remap (column 127 mapped to SEG0) */
|
||||
oled_write_cmd(0xC8); /* COM output scan direction */
|
||||
oled_write_cmd(0xDA); /* COM pins hardware configuration */
|
||||
oled_write_cmd(0x12); /* Alternative pin configuration */
|
||||
oled_write_cmd(0x81); /* Set contrast */
|
||||
oled_write_cmd(0x7F);
|
||||
oled_write_cmd(0xD9); /* Set pre-charge period */
|
||||
oled_write_cmd(0xF1);
|
||||
oled_write_cmd(0xDB); /* Set VCOMH deselect level */
|
||||
oled_write_cmd(0x40);
|
||||
oled_write_cmd(0xA4); /* Display on (resume) */
|
||||
oled_write_cmd(0xA6); /* Normal display (not inverted) */
|
||||
oled_write_cmd(0xAF); /* Display on */
|
||||
|
||||
/* Clear display buffer */
|
||||
memset(display_buffer, 0, sizeof(display_buffer));
|
||||
oled_update();
|
||||
|
||||
ESP_LOGI(TAG, "OLED initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void oled_clear(void)
|
||||
{
|
||||
memset(display_buffer, 0, sizeof(display_buffer));
|
||||
}
|
||||
|
||||
void oled_clear_area(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
|
||||
{
|
||||
for (uint8_t i = y; i < y + h; i++) {
|
||||
for (uint8_t j = x; j < x + w; j++) {
|
||||
if (j < OLED_WIDTH && i < OLED_HEIGHT) {
|
||||
uint16_t idx = (i / 8) * OLED_WIDTH + j;
|
||||
if (idx < sizeof(display_buffer)) {
|
||||
display_buffer[idx] &= ~(1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void oled_draw_pixel(uint8_t x, uint8_t y, uint8_t color)
|
||||
{
|
||||
if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;
|
||||
|
||||
uint16_t idx = (y / 8) * OLED_WIDTH + x;
|
||||
if (idx >= sizeof(display_buffer)) return;
|
||||
|
||||
if (color) {
|
||||
display_buffer[idx] |= (1 << (y % 8));
|
||||
} else {
|
||||
display_buffer[idx] &= ~(1 << (y % 8));
|
||||
}
|
||||
}
|
||||
|
||||
void oled_draw_char(uint8_t x, uint8_t y, char c, uint8_t color)
|
||||
{
|
||||
if (c < 0x20 || c > 0x5A) c = 0x20; /* Space for non-printable */
|
||||
c -= 0x20;
|
||||
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint8_t line = font_8x8[(uint8_t)c][i];
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
if (line & (0x80 >> j)) {
|
||||
oled_draw_pixel(x + j, y + i, color);
|
||||
} else {
|
||||
oled_draw_pixel(x + j, y + i, !color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void oled_draw_string(uint8_t x, uint8_t y, const char *str, uint8_t color)
|
||||
{
|
||||
while (*str) {
|
||||
oled_draw_char(x, y, *str, color);
|
||||
x += 8;
|
||||
if (x + 8 > OLED_WIDTH) {
|
||||
x = 0;
|
||||
y += 8;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
}
|
||||
|
||||
void oled_draw_top_string(const char *str, uint8_t color)
|
||||
{
|
||||
oled_draw_string(0, 0, str, color);
|
||||
}
|
||||
|
||||
void oled_draw_hline(uint8_t x, uint8_t y, uint8_t w, uint8_t color)
|
||||
{
|
||||
for (uint8_t i = 0; i < w; i++) {
|
||||
oled_draw_pixel(x + i, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
void oled_draw_vline(uint8_t x, uint8_t y, uint8_t h, uint8_t color)
|
||||
{
|
||||
for (uint8_t i = 0; i < h; i++) {
|
||||
oled_draw_pixel(x, y + i, color);
|
||||
}
|
||||
}
|
||||
|
||||
void oled_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
|
||||
{
|
||||
for (uint8_t i = y; i < y + h; i++) {
|
||||
for (uint8_t j = x; j < x + w; j++) {
|
||||
oled_draw_pixel(j, i, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void oled_update(void)
|
||||
{
|
||||
/* Set column address range */
|
||||
oled_write_cmd(0x21);
|
||||
oled_write_cmd(0);
|
||||
oled_write_cmd(127);
|
||||
|
||||
/* Set page address range */
|
||||
oled_write_cmd(0x22);
|
||||
oled_write_cmd(0);
|
||||
oled_write_cmd(7);
|
||||
|
||||
/* Write buffer */
|
||||
oled_write_data(display_buffer, sizeof(display_buffer));
|
||||
}
|
||||
|
||||
void oled_display_on(void)
|
||||
{
|
||||
oled_write_cmd(0xAF);
|
||||
}
|
||||
|
||||
void oled_display_off(void)
|
||||
{
|
||||
oled_write_cmd(0xAE);
|
||||
}
|
||||
|
||||
void oled_set_contrast(uint8_t value)
|
||||
{
|
||||
oled_write_cmd(0x81);
|
||||
oled_write_cmd(value);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @file oled.h
|
||||
* @brief SSD1315 OLED 128x64 I2C driver
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Screen dimensions */
|
||||
#define OLED_WIDTH 128
|
||||
#define OLED_HEIGHT 64
|
||||
#define OLED_PAGES 8
|
||||
|
||||
/* OLED colors */
|
||||
#define OLED_COLOR_BLACK 0
|
||||
#define OLED_COLOR_WHITE 1
|
||||
#define OLED_COLOR_YELLOW 2 /* Top 16px */
|
||||
#define OLED_COLOR_BLUE 3 /* Bottom 48px */
|
||||
|
||||
/* Initialize OLED */
|
||||
esp_err_t oled_init(int sda, int scl);
|
||||
|
||||
/* Clear entire display */
|
||||
void oled_clear(void);
|
||||
|
||||
/* Clear specific area */
|
||||
void oled_clear_area(uint8_t x, uint8_t y, uint8_t w, uint8_t h);
|
||||
|
||||
/* Set pixel at (x, y) */
|
||||
void oled_draw_pixel(uint8_t x, uint8_t y, uint8_t color);
|
||||
|
||||
/* Draw character at (x, y) using 8x16 font */
|
||||
void oled_draw_char(uint8_t x, uint8_t y, char c, uint8_t color);
|
||||
|
||||
/* Draw string at (x, y) */
|
||||
void oled_draw_string(uint8_t x, uint8_t y, const char *str, uint8_t color);
|
||||
|
||||
/* Draw string in top yellow area (16px high) */
|
||||
void oled_draw_top_string(const char *str, uint8_t color);
|
||||
|
||||
/* Draw horizontal line */
|
||||
void oled_draw_hline(uint8_t x, uint8_t y, uint8_t w, uint8_t color);
|
||||
|
||||
/* Draw vertical line */
|
||||
void oled_draw_vline(uint8_t x, uint8_t y, uint8_t h, uint8_t color);
|
||||
|
||||
/* Fill rectangle */
|
||||
void oled_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color);
|
||||
|
||||
/* Update display (flush buffer to OLED) */
|
||||
void oled_update(void);
|
||||
|
||||
/* Turn display on/off */
|
||||
void oled_display_on(void);
|
||||
void oled_display_off(void);
|
||||
|
||||
/* Set contrast */
|
||||
void oled_set_contrast(uint8_t value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "protocol.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nrf24l01
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @file protocol.c
|
||||
* @brief AliceRC communication protocol implementation
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "protocol.h"
|
||||
|
||||
void protocol_init(void)
|
||||
{
|
||||
/* Nothing to initialize for now */
|
||||
}
|
||||
|
||||
void protocol_pack_sbus(const uint16_t *channels, uint8_t *sbus_data)
|
||||
{
|
||||
memset(sbus_data, 0, 22);
|
||||
|
||||
/* Pack 16 × 11-bit channels into 22 bytes, little-endian bitstream */
|
||||
for (int i = 0; i < SBUS_CHANNEL_COUNT; i++) {
|
||||
uint16_t ch = channels[i] & 0x07FF; /* Mask to 11 bits */
|
||||
int byte_idx = (i * SBUS_CHANNEL_BITS) / 8;
|
||||
int bit_off = (i * SBUS_CHANNEL_BITS) % 8;
|
||||
|
||||
sbus_data[byte_idx] |= (ch << bit_off) & 0xFF;
|
||||
sbus_data[byte_idx + 1] |= (ch >> (8 - bit_off)) & 0xFF;
|
||||
if (bit_off > 5) {
|
||||
/* 11-bit crosses 3-byte boundary when bit_off >= 6 */
|
||||
sbus_data[byte_idx + 2] |= (ch >> (16 - bit_off)) & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void protocol_unpack_sbus(const uint8_t *sbus_data, uint16_t *channels)
|
||||
{
|
||||
for (int i = 0; i < SBUS_CHANNEL_COUNT; i++) {
|
||||
int byte_idx = (i * SBUS_CHANNEL_BITS) / 8;
|
||||
int bit_off = (i * SBUS_CHANNEL_BITS) % 8;
|
||||
|
||||
uint16_t ch = sbus_data[byte_idx] >> bit_off;
|
||||
ch |= (uint16_t)(sbus_data[byte_idx + 1]) << (8 - bit_off);
|
||||
if (bit_off > 5) {
|
||||
ch |= (uint16_t)(sbus_data[byte_idx + 2]) << (16 - bit_off);
|
||||
}
|
||||
|
||||
channels[i] = ch & 0x07FF;
|
||||
}
|
||||
}
|
||||
|
||||
void protocol_build_tx_payload(rf_tx_payload_t *payload,
|
||||
const uint16_t *adc_channels,
|
||||
uint8_t model_id,
|
||||
uint8_t seq)
|
||||
{
|
||||
uint16_t sbus_channels[SBUS_CHANNEL_COUNT];
|
||||
|
||||
/* Convert 8 ADC channels to SBUS range, center the rest */
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sbus_channels[i] = protocol_map_to_sbus(adc_channels[i]);
|
||||
}
|
||||
for (int i = 8; i < SBUS_CHANNEL_COUNT; i++) {
|
||||
sbus_channels[i] = SBUS_CENTER;
|
||||
}
|
||||
|
||||
/* Build payload */
|
||||
memset(payload, 0, sizeof(rf_tx_payload_t));
|
||||
payload->frame_id = FRAME_ID_CHANNELS;
|
||||
payload->seq = seq;
|
||||
payload->model_id = model_id;
|
||||
payload->flags = 0;
|
||||
|
||||
protocol_pack_sbus(sbus_channels, payload->sbus_data);
|
||||
}
|
||||
|
||||
void protocol_parse_telemetry(const rf_rx_telemetry_t *telemetry,
|
||||
uint16_t *battery_mv)
|
||||
{
|
||||
if (telemetry->frame_id == FRAME_ID_TELEMETRY) {
|
||||
/* Battery voltage is big-endian */
|
||||
*battery_mv = (telemetry->battery_mv >> 8) |
|
||||
(telemetry->battery_mv << 8);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t protocol_map_to_sbus(uint16_t adc_value)
|
||||
{
|
||||
/* Map 12-bit ADC (0-4095) to SBUS range (200-1844) */
|
||||
uint32_t result = SBUS_MIN + ((uint32_t)adc_value * (SBUS_MAX - SBUS_MIN) / 4095);
|
||||
if (result > SBUS_MAX) result = SBUS_MAX;
|
||||
return (uint16_t)result;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @file protocol.h
|
||||
* @brief AliceRC communication protocol definitions
|
||||
*
|
||||
* Matches the AliceRX_STC receiver protocol:
|
||||
* - NRF24L01 32-byte payload
|
||||
* - Frame ID 0xA0 for channel data
|
||||
* - ACK payload 0xB0 for telemetry
|
||||
* - 16 SBUS channels packed as 11-bit little-endian bitstream
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Protocol constants */
|
||||
#define FRAME_ID_CHANNELS 0xA0
|
||||
#define FRAME_ID_TELEMETRY 0xB0
|
||||
#define RF_PAYLOAD_SIZE 32
|
||||
#define RF_ADDR_SIZE 5
|
||||
#define SBUS_CHANNEL_COUNT 16
|
||||
#define SBUS_CHANNEL_BITS 11
|
||||
|
||||
/* NRF24L01 TX payload (32 bytes) */
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t frame_id; /* 0xA0 = FRAME_ID_CHANNELS */
|
||||
uint8_t seq; /* Sequence number, wraps around */
|
||||
uint8_t model_id; /* Currently selected model ID */
|
||||
uint8_t flags; /* Flags (reserved for future use) */
|
||||
uint8_t sbus_data[22]; /* 16 channels × 11 bit = 176 bit, little-endian */
|
||||
uint8_t reserved[6]; /* Reserved for future use */
|
||||
} rf_tx_payload_t;
|
||||
|
||||
/* NRF24L01 ACK payload from receiver (32 bytes) */
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t frame_id; /* 0xB0 = FRAME_ID_TELEMETRY */
|
||||
uint8_t seq; /* Sequence number */
|
||||
uint16_t battery_mv; /* Battery voltage in mV (big-endian) */
|
||||
uint8_t reserved[28]; /* Reserved for future use */
|
||||
} rf_rx_telemetry_t;
|
||||
|
||||
/* SBUS channel value range */
|
||||
#define SBUS_MIN 200
|
||||
#define SBUS_CENTER 1024
|
||||
#define SBUS_MAX 1844
|
||||
|
||||
/* Function declarations */
|
||||
|
||||
/* Initialize protocol module */
|
||||
void protocol_init(void);
|
||||
|
||||
/* Pack SBUS channels into 22-byte bitstream */
|
||||
void protocol_pack_sbus(const uint16_t *channels, uint8_t *sbus_data);
|
||||
|
||||
/* Unpack SBUS channels from 22-byte bitstream */
|
||||
void protocol_unpack_sbus(const uint8_t *sbus_data, uint16_t *channels);
|
||||
|
||||
/* Build TX payload from ADC channel values */
|
||||
void protocol_build_tx_payload(rf_tx_payload_t *payload,
|
||||
const uint16_t *adc_channels,
|
||||
uint8_t model_id,
|
||||
uint8_t seq);
|
||||
|
||||
/* Parse telemetry data from receiver */
|
||||
void protocol_parse_telemetry(const rf_rx_telemetry_t *telemetry,
|
||||
uint16_t *battery_mv);
|
||||
|
||||
/* Map ADC value (0-4095) to SBUS range (200-1844) */
|
||||
uint16_t protocol_map_to_sbus(uint16_t adc_value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "sbus.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file sbus.c
|
||||
* @brief SBUS decoder implementation
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "sbus.h"
|
||||
|
||||
void sbus_init(void)
|
||||
{
|
||||
/* Nothing to initialize */
|
||||
}
|
||||
|
||||
void sbus_decode(const sbus_frame_t *frame, uint16_t *channels, uint8_t *flags)
|
||||
{
|
||||
if (frame->start_byte != 0x0F) {
|
||||
/* Invalid frame */
|
||||
if (flags) *flags = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unpack 16 × 11-bit channels from 22 bytes, little-endian */
|
||||
const uint8_t *data = frame->channel_data;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int byte_idx = (i * 11) / 8;
|
||||
int bit_off = (i * 11) % 8;
|
||||
|
||||
uint16_t ch = data[byte_idx] >> bit_off;
|
||||
ch |= (uint16_t)(data[byte_idx + 1]) << (8 - bit_off);
|
||||
if (bit_off > 5) {
|
||||
ch |= (uint16_t)(data[byte_idx + 2]) << (16 - bit_off);
|
||||
}
|
||||
|
||||
channels[i] = ch & 0x07FF;
|
||||
}
|
||||
|
||||
/* Extract flags */
|
||||
if (flags) {
|
||||
*flags = frame->flags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file sbus.h
|
||||
* @brief SBUS decoder for receiver telemetry input
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* SBUS frame structure (25 bytes) */
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t start_byte; /* 0x0F */
|
||||
uint8_t channel_data[22]; /* 16 channels × 11 bit = 176 bit */
|
||||
uint8_t flags; /* CH17, CH18, failsafe, lost frame */
|
||||
uint8_t end_byte; /* 0x00 */
|
||||
} sbus_frame_t;
|
||||
|
||||
/* SBUS flags */
|
||||
#define SBUS_FLAG_CH17 (1 << 0)
|
||||
#define SBUS_FLAG_CH18 (1 << 1)
|
||||
#define SBUS_FLAG_LOST_FRAME (1 << 2)
|
||||
#define SBUS_FLAG_FAILSAFE (1 << 3)
|
||||
|
||||
/* Initialize SBUS decoder */
|
||||
void sbus_init(void);
|
||||
|
||||
/* Decode SBUS frame into channel values */
|
||||
void sbus_decode(const sbus_frame_t *frame, uint16_t *channels, uint8_t *flags);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "menu.c" "display.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES oled driver
|
||||
PRIV_REQUIRES main
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @file display.c
|
||||
* @brief Display manager - main screen and OLED rendering
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "oled.h"
|
||||
#include "display.h"
|
||||
#include "app_main.h"
|
||||
|
||||
static const char *TAG = "display";
|
||||
|
||||
/* Display state */
|
||||
static display_mode_t current_mode = DISPLAY_MODE_CHANNELS;
|
||||
|
||||
/* Format buffer (unused currently) */
|
||||
//static char line_buf[24];
|
||||
|
||||
void display_init(void)
|
||||
{
|
||||
oled_init(OLED_SDA_GPIO, OLED_SCL_GPIO);
|
||||
ESP_LOGI(TAG, "Display initialized");
|
||||
}
|
||||
|
||||
void display_set_mode(display_mode_t mode)
|
||||
{
|
||||
current_mode = mode;
|
||||
}
|
||||
|
||||
void display_update(adc_data_t *adc_data)
|
||||
{
|
||||
oled_clear();
|
||||
|
||||
/* Draw top bar (yellow area - first 16px) */
|
||||
/* Signal strength, flight battery, TX battery */
|
||||
oled_draw_string(0, 0, "ALICERC v1.0", OLED_COLOR_WHITE);
|
||||
|
||||
/* Draw horizontal separator */
|
||||
oled_draw_hline(0, 16, OLED_WIDTH, OLED_COLOR_WHITE);
|
||||
|
||||
/* Draw main content based on mode */
|
||||
switch (current_mode) {
|
||||
case DISPLAY_MODE_CHANNELS:
|
||||
display_draw_channels(adc_data);
|
||||
break;
|
||||
case DISPLAY_MODE_TELEMETRY:
|
||||
display_draw_telemetry(adc_data);
|
||||
break;
|
||||
case DISPLAY_MODE_MENU:
|
||||
/* Menu drawing handled by menu system */
|
||||
break;
|
||||
}
|
||||
|
||||
oled_update();
|
||||
}
|
||||
|
||||
void display_draw_channels(adc_data_t *adc_data)
|
||||
{
|
||||
char buf[32];
|
||||
uint8_t y = 20;
|
||||
|
||||
/* Draw 8 channel values in 4 rows of 2 */
|
||||
for (int i = 0; i < 8; i += 2) {
|
||||
snprintf(buf, sizeof(buf), "CH%d:%4d CH%d:%4d",
|
||||
i + 1, adc_data->channels[i],
|
||||
i + 2, adc_data->channels[i + 1]);
|
||||
oled_draw_string(0, y, buf, OLED_COLOR_WHITE);
|
||||
y += 10;
|
||||
}
|
||||
|
||||
/* Draw battery voltage */
|
||||
snprintf(buf, sizeof(buf), "BAT:%4dmV %3d%%",
|
||||
adc_data->battery_mv,
|
||||
battery_get_percent(adc_data->battery_mv));
|
||||
oled_draw_string(0, y, buf, OLED_COLOR_WHITE);
|
||||
}
|
||||
|
||||
void display_draw_telemetry(adc_data_t *adc_data)
|
||||
{
|
||||
char buf[32];
|
||||
|
||||
/* Placeholder for telemetry display */
|
||||
snprintf(buf, sizeof(buf), "RSSI: -- Link: --");
|
||||
oled_draw_string(0, 20, buf, OLED_COLOR_WHITE);
|
||||
|
||||
snprintf(buf, sizeof(buf), "RX Batt: -- mV");
|
||||
oled_draw_string(0, 30, buf, OLED_COLOR_WHITE);
|
||||
}
|
||||
|
||||
void display_show_message(const char *msg, uint16_t duration_ms)
|
||||
{
|
||||
oled_clear();
|
||||
oled_draw_string(0, 28, msg, OLED_COLOR_WHITE);
|
||||
oled_update();
|
||||
vTaskDelay(pdMS_TO_TICKS(duration_ms));
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file display.h
|
||||
* @brief Display manager header
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Forward declaration - actual struct defined in app_main.h */
|
||||
struct adc_data_s;
|
||||
typedef struct adc_data_s adc_data_t;
|
||||
|
||||
/* Display modes */
|
||||
typedef enum {
|
||||
DISPLAY_MODE_CHANNELS, /* Show channel values */
|
||||
DISPLAY_MODE_TELEMETRY, /* Show telemetry data */
|
||||
DISPLAY_MODE_MENU, /* Menu system */
|
||||
} display_mode_t;
|
||||
|
||||
/* Initialize display subsystem */
|
||||
void display_init(void);
|
||||
|
||||
/* Set display mode */
|
||||
void display_set_mode(display_mode_t mode);
|
||||
|
||||
/* Update display with ADC data */
|
||||
void display_update(adc_data_t *adc_data);
|
||||
|
||||
/* Draw channel data on display */
|
||||
void display_draw_channels(adc_data_t *adc_data);
|
||||
|
||||
/* Draw telemetry data on display */
|
||||
void display_draw_telemetry(adc_data_t *adc_data);
|
||||
|
||||
/* Show a temporary message */
|
||||
void display_show_message(const char *msg, uint16_t duration_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @file menu.c
|
||||
* @brief Menu system implementation
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "menu.h"
|
||||
|
||||
/* Menu item callbacks - forward declarations */
|
||||
static void menu_cb_model(void);
|
||||
static void menu_cb_rf(void);
|
||||
static void menu_cb_channel(void);
|
||||
static void menu_cb_mixer(void);
|
||||
static void menu_cb_usb(void);
|
||||
static void menu_cb_audio(void);
|
||||
static void menu_cb_other(void);
|
||||
|
||||
/* Sub-menu definitions */
|
||||
|
||||
static const menu_item_t menu_items_main[] = {
|
||||
{"模型选择", menu_cb_model, NULL},
|
||||
{"高频头配置", menu_cb_rf, NULL},
|
||||
{"通道映射", menu_cb_channel, NULL},
|
||||
{"混控设置", menu_cb_mixer, NULL},
|
||||
{"USB设置", menu_cb_usb, NULL},
|
||||
{"音频配置", menu_cb_audio, NULL},
|
||||
{"其他", menu_cb_other, NULL},
|
||||
{"返回", menu_back, NULL},
|
||||
};
|
||||
|
||||
static menu_t menu_main = {
|
||||
.title = "主菜单",
|
||||
.items = menu_items_main,
|
||||
.item_count = 8,
|
||||
.current = 0,
|
||||
};
|
||||
|
||||
/* Menu stack for navigation */
|
||||
#define MENU_STACK_DEPTH 8
|
||||
static menu_t *menu_stack[MENU_STACK_DEPTH];
|
||||
static uint8_t menu_stack_depth = 0;
|
||||
|
||||
static menu_t *current_menu = &menu_main;
|
||||
|
||||
void menu_init(void)
|
||||
{
|
||||
current_menu = &menu_main;
|
||||
current_menu->current = 0;
|
||||
menu_stack_depth = 0;
|
||||
}
|
||||
|
||||
void menu_up(void)
|
||||
{
|
||||
if (current_menu->current > 0) {
|
||||
current_menu->current--;
|
||||
}
|
||||
}
|
||||
|
||||
void menu_down(void)
|
||||
{
|
||||
if (current_menu->current < current_menu->item_count - 1) {
|
||||
current_menu->current++;
|
||||
}
|
||||
}
|
||||
|
||||
void menu_select(void)
|
||||
{
|
||||
const menu_item_t *item = ¤t_menu->items[current_menu->current];
|
||||
|
||||
if (item->submenu) {
|
||||
/* Push to sub-menu */
|
||||
if (menu_stack_depth < MENU_STACK_DEPTH) {
|
||||
menu_stack[menu_stack_depth++] = current_menu;
|
||||
}
|
||||
current_menu = item->submenu;
|
||||
current_menu->current = 0;
|
||||
} else if (item->callback) {
|
||||
item->callback();
|
||||
}
|
||||
}
|
||||
|
||||
void menu_back(void)
|
||||
{
|
||||
if (menu_stack_depth > 0) {
|
||||
current_menu = menu_stack[--menu_stack_depth];
|
||||
}
|
||||
}
|
||||
|
||||
const menu_t* menu_get_current(void)
|
||||
{
|
||||
return current_menu;
|
||||
}
|
||||
|
||||
const char* menu_get_current_item_name(void)
|
||||
{
|
||||
return current_menu->items[current_menu->current].name;
|
||||
}
|
||||
|
||||
void menu_draw(void)
|
||||
{
|
||||
/* TODO: Implement OLED drawing */
|
||||
/* Will display:
|
||||
- Top bar: menu title
|
||||
- Up to 5 visible items with cursor indicator
|
||||
- Scroll indicator if more items
|
||||
*/
|
||||
}
|
||||
|
||||
/* Callback stubs */
|
||||
static void menu_cb_model(void) { }
|
||||
static void menu_cb_rf(void) { }
|
||||
static void menu_cb_channel(void) { }
|
||||
static void menu_cb_mixer(void) { }
|
||||
static void menu_cb_usb(void) { }
|
||||
static void menu_cb_audio(void) { }
|
||||
static void menu_cb_other(void) { }
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file menu.h
|
||||
* @brief Menu system for OLED display
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Menu item callback */
|
||||
typedef void (*menu_callback_t)(void);
|
||||
|
||||
/* Menu item structure */
|
||||
typedef struct {
|
||||
const char *name; /* Display name (max 16 chars visible) */
|
||||
menu_callback_t callback; /* Callback when selected */
|
||||
struct menu_t *submenu; /* Sub-menu (NULL if leaf) */
|
||||
} menu_item_t;
|
||||
|
||||
/* Menu structure */
|
||||
typedef struct menu_t {
|
||||
const char *title; /* Menu title */
|
||||
const menu_item_t *items; /* Array of menu items */
|
||||
uint8_t item_count; /* Number of items */
|
||||
uint8_t current; /* Current selection index */
|
||||
} menu_t;
|
||||
|
||||
/* Initialize menu system */
|
||||
void menu_init(void);
|
||||
|
||||
/* Navigate up */
|
||||
void menu_up(void);
|
||||
|
||||
/* Navigate down */
|
||||
void menu_down(void);
|
||||
|
||||
/* Select current item */
|
||||
void menu_select(void);
|
||||
|
||||
/* Go back to parent menu */
|
||||
void menu_back(void);
|
||||
|
||||
/* Get current menu for display */
|
||||
const menu_t* menu_get_current(void);
|
||||
|
||||
/* Draw current menu on OLED */
|
||||
void menu_draw(void);
|
||||
|
||||
/* Get current menu item name */
|
||||
const char* menu_get_current_item_name(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user