173 lines
No EOL
8.8 KiB
Markdown
173 lines
No EOL
8.8 KiB
Markdown
# Project Handoff: Multi-Functional Controller (MFC) & RC Vehicle
|
|
|
|
## 1. Project Overview & Aim
|
|
**Goal:** Engineer a high-performance, bare-metal remote controller (MFC) from scratch using an ESP32-S3, paired with a custom 4WD RC vehicle.
|
|
**Secondary Objectives:** Extend the controller's capabilities to include a cloud-connected AI voice assistant, local telemetry logging, and PC gamepad (USB HID/Bluetooth) support.
|
|
**Tech Stack:** C/C++, ESP-IDF v5.x (FreeRTOS), Bare-metal register manipulation, SPI, I2C, Hardware PCNT, ADC Oneshot.
|
|
|
|
## 2. Current Situation
|
|
* **Controller Prototype:** Currently prototyped on a 6x9cm perfboard. The ESP32-S3 successfully communicates over a shared SPI bus with an NRF24L01+ transceiver and a temporary 1.3" OLED display.
|
|
* **Firmware:** A robust FreeRTOS state machine is running. It handles analog ADC joystick polling, hardware quadrature decoding (PCNT) for a rotary encoder menu UI, software button debouncing, and blind RF transmission on 2.4GHz.
|
|
* **Hardware Pivot:** We recently discovered the 7-pin 1.3" OLED lacks a Chip Select (CS) pin, causing SPI collisions. We have decided to upgrade to a 1.8" ST7789 color TFT (which has a CS pin) to share the SPI bus safely.
|
|
* **Manufacturing:** Paused cutting the rigid ABS plastic project box (`DT-100`) until next month when a proper rotary tool (Dremel/E108) and 2.35mm cutting wheels arrive to ensure clean, professional cutouts.
|
|
|
|
## 3. Hardware Architecture & Bill of Materials (BOM)
|
|
|
|
### 3.1 Handheld Controller (MFC)
|
|
* **MCU:** Espressif ESP32-S3-N16R8 (16MB Flash, 8MB PSRAM).
|
|
* **RF Telemetry:** Nordic nRF24L01+ 2.4GHz Module.
|
|
* **Display:** 1.8" ST7789 TFT Color LCD (3.3V logic, SPI with CS pin).
|
|
* **Joysticks:** 2x K-Silver (KS) PS5 Magnetic Hall-Effect modules (3.3V active sensors).
|
|
* **UI Input:** KY-040 Rotary Encoder (CLK, DT, SW).
|
|
* **Voice AI:** MAX4466 Electret Microphone Module (Analog output).
|
|
* **Data Logging:** Arduino Micro SD Card Module (SPI interface, formatted FAT32).
|
|
* **Audio Output:** 1x Passive Buzzer (Melody), 2x Active Buzzers (Percussive clicks) driven by 2N2222 NPN Transistors.
|
|
* **Power:** Dual 18650 Li-ion cells, TP4056 Charger, MT3608 DC-DC Boost Converter (5V rail), 100µF Electrolytic Capacitor (for NRF24 burst smoothing), 100nF Ceramic Caps.
|
|
|
|
### 3.2 RC Vehicle (Receiver Node)
|
|
* **MCU:** GD32F103C8T6 ARM Cortex-M3 Mini Board.
|
|
* **RF Telemetry:** nRF24L01+ PA+LNA (with external SMA antenna for 1km range).
|
|
* **IMU / Navigation:** MPU9250 9-DOF Sensor (Gyro, Accelerometer, Magnetometer for tilt-compensated heading).
|
|
* **Drivetrain:** 4x JGB37-520 12V 333RPM Gear Motors (with built-in encoders for PID velocity control).
|
|
* **Chassis/Enclosure:** DT-100 Project Box (190x137x59mm).
|
|
* **Power:** 12V Battery, Heavy-duty Motor Drivers, 12-24V Waterproof Main Switch.
|
|
|
|
## 4. Pinout Definitions (ESP32-S3 Controller)
|
|
* **SPI Hub:** MOSI (`GPIO11`), MISO (`GPIO13`), SCK (`GPIO12`)
|
|
* **NRF24:** CSN (`GPIO14`), CE (`GPIO21`)
|
|
* **TFT Display (Planned):** CS (`GPIO10`), DC (`GPIO9`), RES (`GPIO8`), BLK (`3.3V`)
|
|
* **Rotary Encoder:** CLK (`GPIO15`), DT (`GPIO16`), SW (`GPIO17` - Pull-up)
|
|
* **Joysticks:** VRx (`GPIO4`), VRy (`GPIO5`) via ADC1. (Second joystick pins pending).
|
|
|
|
## 5. Master Codebase (Latest Working Version)
|
|
This code currently uses the monochrome OLED logic, but handles PCNT menu decoding and NRF transmission perfectly.
|
|
|
|
```c
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "driver/spi_master.h"
|
|
#include "driver/gpio.h"
|
|
#include "driver/pulse_cnt.h"
|
|
#include "esp_system.h"
|
|
#include "esp_log.h"
|
|
|
|
// Hardware Pins
|
|
#define PIN_NUM_MOSI 11
|
|
#define PIN_NUM_MISO 13
|
|
#define PIN_NUM_CLK 12
|
|
#define NRF_PIN_CS 14
|
|
#define NRF_PIN_CE 21
|
|
#define OLED_PIN_CS 10
|
|
#define OLED_PIN_DC 9
|
|
#define OLED_PIN_RES 8
|
|
#define PIN_ENCODER_CLK 15
|
|
#define PIN_ENCODER_DT 16
|
|
#define PIN_ENCODER_SW 17
|
|
|
|
#define NUM_MENU_ITEMS 4
|
|
|
|
static const char *TAG = "MFC_CORE";
|
|
spi_device_handle_t oled_spi;
|
|
spi_device_handle_t nrf_spi;
|
|
int current_radio_channel = 76;
|
|
|
|
typedef enum { SCREEN_MAIN_MENU = 0, SCREEN_DRIVE, SCREEN_RADIO_CONFIG, SCREEN_CALIBRATE, SCREEN_SYSTEM_INFO } screen_state_t;
|
|
|
|
// [Font arrays omitted in handoff for brevity, but exist in master file: font_AZ, font_nums, etc.]
|
|
const uint8_t* get_font_data(char c) { /* ... */ return NULL; }
|
|
|
|
void oled_cmd(uint8_t cmd) {
|
|
gpio_set_level(OLED_PIN_DC, 0);
|
|
spi_transaction_t t = { .length = 8, .tx_buffer = &cmd };
|
|
spi_device_polling_transmit(oled_spi, &t);
|
|
}
|
|
void oled_data(uint8_t data) {
|
|
gpio_set_level(OLED_PIN_DC, 1);
|
|
spi_transaction_t t = { .length = 8, .tx_buffer = &data };
|
|
spi_device_polling_transmit(oled_spi, &t);
|
|
}
|
|
void oled_draw_string(uint8_t page, uint8_t col, const char* str) {
|
|
oled_cmd(0xB0 + page);
|
|
uint8_t phys_col = col + 2;
|
|
oled_cmd(0x00 | (phys_col & 0x0F));
|
|
oled_cmd(0x10 | ((phys_col >> 4) & 0x0F));
|
|
while (*str) {
|
|
const uint8_t* pixels = get_font_data(*str++);
|
|
for (int i = 0; i < 5; i++) { oled_data(pixels[i]); }
|
|
oled_data(0x00);
|
|
}
|
|
}
|
|
void oled_clear() {
|
|
for (uint8_t p = 0; p < 8; p++) {
|
|
oled_cmd(0xB0 + p); oled_cmd(0x02); oled_cmd(0x10);
|
|
for (uint8_t c = 0; c < 128; c++) { oled_data(0x00); }
|
|
}
|
|
}
|
|
void oled_init() {
|
|
gpio_set_level(OLED_PIN_RES, 0); vTaskDelay(pdMS_TO_TICKS(50));
|
|
gpio_set_level(OLED_PIN_RES, 1); vTaskDelay(pdMS_TO_TICKS(50));
|
|
uint8_t init_cmds[] = {0xAE,0xD5,0x80,0xA8,0x3F,0xD3,0x00,0x40,0x8D,0x14,0xA1,0xC8,0xDA,0x12,0x81,0xCF,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF};
|
|
for(int i = 0; i < sizeof(init_cmds); i++) { oled_cmd(init_cmds[i]); }
|
|
}
|
|
|
|
bool nrf_check_connection() {
|
|
uint8_t tx[2] = { 0x03, 0xFF };
|
|
uint8_t rx[2] = { 0 };
|
|
spi_transaction_t t = { .length = 16, .tx_buffer = tx, .rx_buffer = rx };
|
|
spi_device_polling_transmit(nrf_spi, &t);
|
|
return (rx[1] == 0x03);
|
|
}
|
|
void set_nrf_channel(uint8_t channel) {
|
|
uint8_t tx[2] = { 0x25, channel };
|
|
spi_transaction_t t = { .length = 16, .tx_buffer = tx };
|
|
spi_device_polling_transmit(nrf_spi, &t);
|
|
}
|
|
|
|
void draw_menu(int selected_index) { /* Renders Main Menu items */ }
|
|
void draw_system_info() { /* Renders ESP32 Free Heap */ }
|
|
void draw_radio_config_screen(int channel) { /* Interactive channel tuning UI */ }
|
|
void draw_placeholder_screen(const char* title) { /* Basic placeholder */ }
|
|
|
|
void app_main(void) {
|
|
// 1. SPI Init
|
|
spi_bus_config_t buscfg = { .miso_io_num = PIN_NUM_MISO, .mosi_io_num = PIN_NUM_MOSI, .sclk_io_num = PIN_NUM_CLK, .max_transfer_sz = 4096 };
|
|
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
|
|
|
// 2. Devices (OLED & NRF)
|
|
spi_device_interface_config_t oled_cfg = { .clock_speed_hz = 10000000, .mode = 0, .spics_io_num = OLED_PIN_CS, .queue_size = 1 };
|
|
spi_bus_add_device(SPI2_HOST, &oled_cfg, &oled_spi);
|
|
|
|
spi_device_interface_config_t nrf_cfg = { .clock_speed_hz = 1000000, .mode = 0, .spics_io_num = NRF_PIN_CS, .queue_size = 1 };
|
|
spi_bus_add_device(SPI2_HOST, &nrf_cfg, &nrf_spi);
|
|
|
|
// 3. PCNT Setup for Rotary Encoder
|
|
// [PCNT Init logic omitted for brevity in handoff, but fully functional in master]
|
|
|
|
// 4. FreeRTOS State Machine Loop
|
|
int count = 0, last_menu_index = -1, last_sw_state = 1;
|
|
TickType_t last_button_press = 0;
|
|
screen_state_t current_screen = SCREEN_MAIN_MENU;
|
|
bool screen_needs_redraw = true;
|
|
int radio_start_count = 0, last_tuned_channel = current_radio_channel;
|
|
|
|
while (1) {
|
|
// Core polling loop handling screen state, PCNT steps, debouncing, and NRF register updating.
|
|
vTaskDelay(pdMS_TO_TICKS(30));
|
|
}
|
|
}
|
|
```
|
|
|
|
## 6. Immediate Next Steps (For the Assisting Agent)
|
|
1. **ST7789 TFT Migration:** Rewrite the display initialization and frame buffer logic using ESP-IDF's `esp_lcd` component to replace the custom 5x8 monochrome OLED code.
|
|
2. **SD Card SPI Setup:** Configure the SD card module on the shared SPI bus (or a new SPI bus) and initialize the `FATFS` VFS so we can read/write data logs.
|
|
3. **NVS Calibration:** Write the calibration routine for the upcoming K-Silver Hall-effect joysticks. We need to measure resting, min, and max voltages and save them permanently to the ESP32's flash memory.
|
|
4. **GD32 Vehicle Receiver Code:** Begin structuring the C code for the ARM Cortex-M3 GD32 to poll the NRF24 PA+LNA module and read the MPU9250 I2C data.
|
|
|
|
## 7. Known Context & Hardware Rules
|
|
* Do NOT use standard `16000 RPM` tools on the plastic case; we are waiting for a 3-speed drill with 2.35mm diamond cutting discs.
|
|
* Do NOT wire the Hall-effect joystick `VCC` backward; it is an active component and will burn out.
|
|
* Do NOT wire the HC-SR04 `ECHO` pin directly to the GD32; it requires a resistor voltage divider. (Though we are considering the VL53L0X I2C ToF instead).
|
|
* Must use a 100µF capacitor on the NRF24 power lines to prevent brown-outs during PTX bursts. |