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