Initial commit - sync AliceRC to Gitea

This commit is contained in:
2026-08-03 15:54:25 +08:00
commit 493b7d81d7
1510 changed files with 185319 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRCS "main.c" "app_main.c" "adc.c" "battery.c" "led.c" "ui_task.c" "rf_task.c"
INCLUDE_DIRS "."
REQUIRES nrf24l01 oled audio protocol ui driver nvs_flash
)
+58
View File
@@ -0,0 +1,58 @@
/**
* @file adc.c
* @brief ADC reading task for joystick channels and battery voltage
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/adc.h"
#include "app_main.h"
static const char *TAG = "adc";
/* ADC channel to GPIO mapping for ESP32S3 */
static const adc2_channel_t adc_channels[] = {
ADC2_CHANNEL_0, /* GPIO 4 - CH1 */
ADC2_CHANNEL_1, /* GPIO 5 - CH2 */
ADC2_CHANNEL_2, /* GPIO 6 - CH3 */
ADC2_CHANNEL_3, /* GPIO 7 - CH4 */
ADC2_CHANNEL_4, /* GPIO 15 - CH5 */
ADC2_CHANNEL_5, /* GPIO 16 - CH6 */
ADC2_CHANNEL_6, /* GPIO 17 - CH7 */
ADC2_CHANNEL_7, /* GPIO 9 - CH8 */
};
void adc_task(void *pvParameters)
{
adc_data_t adc_data;
int adc_raw = 0;
ESP_LOGI(TAG, "ADC task started");
while (1) {
/* Read all 8 joystick channels */
for (int i = 0; i < 8; i++) {
#if CONFIG_IDF_TARGET_ESP32S3
adc2_get_raw(adc_channels[i], ADC_WIDTH_BIT_12, &adc_raw);
#else
/* ESP32 uses ADC1 for these pins */
adc_raw = adc1_get_raw(ADC1_CHANNEL_0 + i);
#endif
adc_data.channels[i] = (uint16_t)adc_raw;
}
/* Read battery voltage (ADC1_CHANNEL_0 = GPIO 8) */
int bat_raw = adc1_get_raw(ADC1_CHANNEL_0);
/* Convert ADC reading to mV: 12-bit, 3.3V reference, voltage divider */
uint32_t bat_mv = (bat_raw * 3300 * 2) / 4096; /* Simple conversion */
adc_data.battery_mv = (uint16_t)bat_mv;
/* Send to queue (non-blocking) */
if (adc_data_queue) {
xQueueOverwrite(adc_data_queue, &adc_data);
}
vTaskDelay(pdMS_TO_TICKS(20)); /* 50Hz sample rate */
}
}
+120
View File
@@ -0,0 +1,120 @@
/**
* @file app_main.c
* @brief AliceRC application initialization and task management
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "nvs_flash.h"
#include "app_main.h"
/* Queue handles for inter-task communication */
QueueHandle_t adc_data_queue = NULL;
QueueHandle_t rf_command_queue = NULL;
QueueHandle_t ui_event_queue = NULL;
static const char *TAG = "app_main";
void app_init_gpio(void)
{
/* Configure button GPIOs as inputs with pull-ups */
gpio_config_t btn_conf = {
.pin_bit_mask = (1ULL<<BTN_UP_GPIO) | (1ULL<<BTN_DOWN_GPIO) |
(1ULL<<BTN_SEL_GPIO) | (1ULL<<BTN_BACK_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&btn_conf);
/* Configure status LED */
gpio_config_t led_conf = {
.pin_bit_mask = (1ULL << STATUS_LED_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&led_conf);
gpio_set_level(STATUS_LED_GPIO, 0);
/* Configure PWR_EN (ESP32S3 only) */
#if CONFIG_IDF_TARGET_ESP32S3
gpio_config_t pwr_conf = {
.pin_bit_mask = (1ULL << PWR_EN_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&pwr_conf);
gpio_set_level(PWR_EN_GPIO, 1); /* Enable peripheral power */
#endif
ESP_LOGI(TAG, "GPIO initialized");
}
void app_init_adc(void)
{
/* Initialize ADC1 for battery voltage */
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_12); /* GPIO 8 */
/* Initialize ADC2 for joystick channels (ESP32S3) */
#if CONFIG_IDF_TARGET_ESP32S3
/* ADC2 channels: 4,5,6,7,15,16,17,9 */
adc2_config_channel_atten(ADC2_CHANNEL_0, ADC_ATTEN_DB_12); /* GPIO 4 */
adc2_config_channel_atten(ADC2_CHANNEL_1, ADC_ATTEN_DB_12); /* GPIO 5 */
adc2_config_channel_atten(ADC2_CHANNEL_2, ADC_ATTEN_DB_12); /* GPIO 6 */
adc2_config_channel_atten(ADC2_CHANNEL_3, ADC_ATTEN_DB_12); /* GPIO 7 */
#endif
ESP_LOGI(TAG, "ADC initialized");
}
void app_init_nvs(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "NVS initialized");
}
void app_create_tasks(void)
{
/* Create queues */
adc_data_queue = xQueueCreate(2, sizeof(adc_data_t));
ui_event_queue = xQueueCreate(10, sizeof(ui_event_t));
/* Create tasks */
xTaskCreatePinnedToCore(adc_task, "adc_task", TASK_STACK_ADC, NULL,
TASK_PRIO_ADC, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(ui_task, "ui_task", TASK_STACK_UI, NULL,
TASK_PRIO_UI, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(rf_task, "rf_task", TASK_STACK_RF, NULL,
TASK_PRIO_RF, NULL, tskNO_AFFINITY);
ESP_LOGI(TAG, "Tasks created");
}
void app_init(void)
{
ESP_LOGI(TAG, "Initializing AliceRC...");
app_init_nvs();
app_init_gpio();
app_init_adc();
app_create_tasks();
ESP_LOGI(TAG, "AliceRC initialized successfully");
}
+140
View File
@@ -0,0 +1,140 @@
/**
* @file app_main.h
* @brief AliceRC application header
*/
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef __cplusplus
extern "C" {
#endif
/* GPIO Pin Mapping - ESP32S3 */
/* ADC Channels */
#define ADC_CH1_GPIO 4
#define ADC_CH2_GPIO 5
#define ADC_CH3_GPIO 6
#define ADC_CH4_GPIO 7
#define ADC_CH5_GPIO 15
#define ADC_CH6_GPIO 16
#define ADC_CH7_GPIO 17
#define ADC_CH8_GPIO 9
#define ADC_BAT_GPIO 8
/* NRF24L01 - ESP32S3 */
#define NRF_MOSI_GPIO 1
#define NRF_MISO_GPIO 40
#define NRF_SCLK_GPIO 39
#define NRF_CS_GPIO 38
#define NRF_CE_GPIO 2
#define NRF_IRQ_GPIO 42
/* LLCC68 (E220) - ESP32S3 only */
#define LLCC_CS_GPIO 10
#define LLCC_SCLK_GPIO 11
#define LLCC_MISO_GPIO 12
#define LLCC_MOSI_GPIO 13
#define LLCC_NRST_GPIO 14
#define LLCC_BUSY_GPIO 45
#define LLCC_IRQ_GPIO 45 /* shared with BUSY? Check datasheet */
/* OLED SSD1315 - I2C */
#define OLED_SCL_GPIO 37
#define OLED_SDA_GPIO 36
/* Buttons */
#define BTN_UP_GPIO 35
#define BTN_DOWN_GPIO 48
#define BTN_SEL_GPIO 47
#define BTN_BACK_GPIO 3
/* Other peripherals */
#define USB_DM_GPIO 19
#define USB_DP_GPIO 20
#define AUDIO_PWM_GPIO 26 /* ESP32S3: PWM audio */
#define AUDIO_DAC_GPIO 26 /* ESP32: DAC audio */
#define PWR_EN_GPIO 41
#define STATUS_LED_GPIO 0
/* ESP32 (basic) GPIO mapping */
/* ADC Channels */
#define ESP32_ADC_CH1_GPIO 36
#define ESP32_ADC_CH2_GPIO 39
#define ESP32_ADC_CH3_GPIO 34
#define ESP32_ADC_CH4_GPIO 35
#define ESP32_ADC_CH5_GPIO 32
#define ESP32_ADC_CH6_GPIO 33
#define ESP32_ADC_CH7_GPIO 25
#define ESP32_ADC_CH8_GPIO 13
/* NRF24L01 - ESP32 */
#define ESP32_NRF_MOSI_GPIO 23
#define ESP32_NRF_MISO_GPIO 19
#define ESP32_NRF_SCLK_GPIO 18
#define ESP32_NRF_CS_GPIO 5
#define ESP32_NRF_CE_GPIO 22
#define ESP32_NRF_IRQ_GPIO 21
/* OLED - ESP32 */
#define ESP32_OLED_SCL_GPIO 17
#define ESP32_OLED_SDA_GPIO 16
/* Buttons - ESP32 */
#define ESP32_BTN_UP_GPIO 4
#define ESP32_BTN_DOWN_GPIO 2
#define ESP32_BTN_SEL_GPIO 15
/* Battery */
#define BAT_ADC_CHANNEL ADC_CHANNEL_0
#define BAT_DIVIDER_RATIO 2.0f /* Voltage divider ratio */
#define BAT_FULL_MV 4200 /* Full battery mV */
#define BAT_EMPTY_MV 3200 /* Empty battery mV */
/* Task priorities */
#define TASK_PRIO_RF 5
#define TASK_PRIO_UI 4
#define TASK_PRIO_ADC 3
#define TASK_PRIO_AUDIO 2
/* Task stack sizes */
#define TASK_STACK_RF 4096
#define TASK_STACK_UI 4096
#define TASK_STACK_ADC 2048
#define TASK_STACK_AUDIO 2048
/* Function declarations */
void app_init(void);
void app_init_gpio(void);
void app_create_tasks(void);
uint8_t battery_get_percent(uint16_t mv);
/* Data type definitions */
typedef struct adc_data_s {
uint16_t channels[8];
uint16_t battery_mv;
} adc_data_t;
typedef struct {
uint8_t key; /* KEY_UP, KEY_DOWN, KEY_SEL, KEY_BACK */
uint8_t action; /* 0=press, 1=release, 2=long_press */
} ui_event_t;
/* Queue handles (defined in app_main.c) */
extern QueueHandle_t adc_data_queue;
extern QueueHandle_t rf_command_queue;
extern QueueHandle_t ui_event_queue;
/* RF task */
void rf_task(void *pvParameters);
/* UI task */
void ui_task(void *pvParameters);
/* ADC task */
void adc_task(void *pvParameters);
/* Audio task */
void audio_task(void *pvParameters);
#ifdef __cplusplus
}
#endif
+18
View File
@@ -0,0 +1,18 @@
/**
* @file battery.c
* @brief Battery monitoring utilities
*/
#include "esp_log.h"
#include "app_main.h"
static const char *TAG = "battery";
/* Battery percentage lookup table (simple linear for now) */
uint8_t battery_get_percent(uint16_t mv)
{
if (mv >= BAT_FULL_MV) return 100;
if (mv <= BAT_EMPTY_MV) return 0;
/* Linear interpolation between EMPTY and FULL */
return (uint8_t)((mv - BAT_EMPTY_MV) * 100 / (BAT_FULL_MV - BAT_EMPTY_MV));
}
+28
View File
@@ -0,0 +1,28 @@
/**
* @file led.c
* @brief Status LED control
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "app_main.h"
static const char *TAG = "led";
void led_set(uint8_t on)
{
gpio_set_level(STATUS_LED_GPIO, on ? 1 : 0);
}
void led_blink(int times, int delay_ms)
{
for (int i = 0; i < times; i++) {
led_set(1);
vTaskDelay(pdMS_TO_TICKS(delay_ms));
led_set(0);
if (i < times - 1) {
vTaskDelay(pdMS_TO_TICKS(delay_ms));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
/**
* @file main.c
* @brief AliceRC ESP32/ESP32S3 遥控器主程序入口
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_main.h"
static const char *TAG = "AliceRC";
void app_main(void)
{
ESP_LOGI(TAG, "AliceRC Transmitter v1.0 starting...");
// Initialize all subsystems
app_init();
// Main loop runs in app_main_task
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
+112
View File
@@ -0,0 +1,112 @@
/**
* @file rf_task.c
* @brief RF task - NRF24L01 communication
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_main.h"
static const char *TAG = "rf";
/* NRF24L01 protocol constants (matching AliceRX_STC) */
#define FRAME_ID_CHANNELS 0xA0
#define FRAME_ID_TELEMETRY 0xB0
#define RF_PAYLOAD_SIZE 32
#define RF_CHANNEL_DEFAULT 100
#define RF_DATA_RATE RF_1MBPS
#define RF_TX_POWER RF_PA_MAX
/* RF frame structure */
typedef struct __attribute__((packed)) {
uint8_t frame_id; /* 0xA0 = FRAME_ID_CHANNELS */
uint8_t seq; /* Sequence number */
uint8_t model_id; /* Model ID */
uint8_t flags; /* Flags */
uint8_t sbus_data[22]; /* 16 channels × 11bit = 22 bytes little-endian bitstream */
uint8_t reserved[6]; /* Reserved */
} rf_payload_t;
/* Telemetry data from receiver */
typedef struct __attribute__((packed)) {
uint8_t frame_id; /* 0xB0 = FRAME_ID_TELEMETRY */
uint8_t seq; /* Sequence number */
uint16_t battery_mv; /* Battery voltage (big-endian, mV) */
uint8_t reserved[28]; /* Reserved */
} rf_telemetry_t;
/* RF state */
static uint8_t rf_seq = 0;
static uint8_t rf_model_id = 0;
void rf_send_channels(adc_data_t *adc_data)
{
rf_payload_t payload;
memset(&payload, 0, sizeof(payload));
payload.frame_id = FRAME_ID_CHANNELS;
payload.seq = rf_seq++;
payload.model_id = rf_model_id;
/* Convert 8 ADC channels to SBUS-like 11-bit format */
/* Map 12-bit ADC (0-4095) to SBUS range (200-1844) */
uint16_t sbus_channels[16] = {0};
for (int i = 0; i < 8; i++) {
uint32_t val = adc_data->channels[i];
/* Map 0-4095 to 200-1844 */
sbus_channels[i] = 200 + (val * 1644 / 4096);
if (sbus_channels[i] > 1844) sbus_channels[i] = 1844;
}
/* Center unused channels */
for (int i = 8; i < 16; i++) {
sbus_channels[i] = 1024;
}
/* Pack 16 × 11bit into 22 bytes little-endian */
for (int i = 0; i < 16; i++) {
uint16_t ch = sbus_channels[i];
int byte_idx = (i * 11) / 8;
int bit_off = (i * 11) % 8;
payload.sbus_data[byte_idx] |= (ch << bit_off) & 0xFF;
payload.sbus_data[byte_idx + 1] |= (ch >> (8 - bit_off)) & 0xFF;
if (bit_off > 5) {
payload.sbus_data[byte_idx + 2] |= (ch >> (16 - bit_off)) & 0xFF;
}
}
/* TODO: Send via NRF24L01 hardware */
ESP_LOGD(TAG, "RF TX seq=%d ch1=%d", payload.seq, sbus_channels[0]);
}
void rf_task(void *pvParameters)
{
ESP_LOGI(TAG, "RF task started");
/* TODO: Initialize NRF24L01 hardware */
/* nrf24_init(); */
/* nrf24_set_channel(100); */
/* nrf24_set_data_rate(RF_DATA_RATE); */
/* nrf24_set_power(RF_TX_POWER); */
while (1) {
/* Read latest ADC data */
adc_data_t adc_data;
if (adc_data_queue && xQueuePeek(adc_data_queue, &adc_data, 0)) {
/* Send channel data over RF */
rf_send_channels(&adc_data);
}
/* TODO: Check for ACK/telemetry from receiver */
/* rf_telemetry_t telemetry; */
/* if (nrf24_read_ack(&telemetry)) { */
/* linked = 1; */
/* lost_count = 0; */
/* } else { */
/* if (++lost_count > 50) linked = 0; */
/* } */
vTaskDelay(pdMS_TO_TICKS(20)); /* 50Hz RF update rate */
}
}
+108
View File
@@ -0,0 +1,108 @@
/**
* @file ui_task.c
* @brief UI task - button handling and OLED display
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "app_main.h"
static const char *TAG = "ui";
/* Button debounce parameters */
#define DEBOUNCE_MS 50
#define LONG_PRESS_MS 1000
/* Button state tracking */
typedef struct {
uint8_t gpio;
uint8_t last_state;
uint32_t last_change_ms;
uint32_t press_start_ms;
uint8_t long_press_sent;
} btn_state_t;
static btn_state_t buttons[] = {
{BTN_UP_GPIO, 1, 0, 0, 0},
{BTN_DOWN_GPIO, 1, 0, 0, 0},
{BTN_SEL_GPIO, 1, 0, 0, 0},
{BTN_BACK_GPIO, 1, 0, 0, 0},
};
static void handle_button(btn_state_t *btn, uint8_t idx)
{
uint8_t state = gpio_get_level(btn->gpio);
uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
if (state != btn->last_state) {
btn->last_change_ms = now;
btn->last_state = state;
if (state == 0) { /* Pressed */
btn->press_start_ms = now;
btn->long_press_sent = 0;
ui_event_t evt = {.key = idx, .action = 0}; /* press */
if (ui_event_queue) {
xQueueSend(ui_event_queue, &evt, 0);
}
} else { /* Released */
if (!btn->long_press_sent) {
ui_event_t evt = {.key = idx, .action = 1}; /* release */
if (ui_event_queue) {
xQueueSend(ui_event_queue, &evt, 0);
}
}
}
} else if (state == 0 && !btn->long_press_sent) {
/* Check for long press */
if ((now - btn->press_start_ms) >= LONG_PRESS_MS) {
btn->long_press_sent = 1;
ui_event_t evt = {.key = idx, .action = 2}; /* long press */
if (ui_event_queue) {
xQueueSend(ui_event_queue, &evt, 0);
}
}
}
}
void ui_task(void *pvParameters)
{
ESP_LOGI(TAG, "UI task started");
/* OLED initialization would go here */
/* display_init(); */
/* Button state initialization */
for (int i = 0; i < 4; i++) {
buttons[i].last_state = gpio_get_level(buttons[i].gpio);
}
while (1) {
/* Scan buttons */
for (int i = 0; i < 4; i++) {
handle_button(&buttons[i], i);
}
/* Process UI events */
ui_event_t evt;
while (xQueueReceive(ui_event_queue, &evt, 0)) {
const char *key_names[] = {"UP", "DOWN", "SEL", "BACK"};
const char *actions[] = {"press", "release", "long_press"};
ESP_LOGI(TAG, "Button: %s - %s", key_names[evt.key], actions[evt.action]);
}
/* Read ADC data for display */
adc_data_t adc_data;
if (adc_data_queue && xQueuePeek(adc_data_queue, &adc_data, 0)) {
/* Update OLED display with ADC data */
/* display_update(&adc_data); */
}
vTaskDelay(pdMS_TO_TICKS(20)); /* 50Hz scan rate */
}
}