Compare commits

...

10 commits

14 changed files with 25581 additions and 17 deletions

19
.gitignore vendored
View file

@ -5,14 +5,27 @@
*.bin
*.map
*.out
*.log
*.old
# Build folders
# Build folders & caches
build/
bin/
.cache/
managed_components/
dependencies.lock
# VS Code local workspace settings
# KiCad temporary & local files
*.kicad_prl
*-bak
*-bak.kicad_*
.history/
# IDE & Editor files
.vscode/
.idea/
# Arch Linux / System files
# OS / System temporary files
*~
.directory
.DS_Store

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Ertunç Özbay
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

187
README.md
View file

@ -1,27 +1,186 @@
# Multifunctional Controller Project(In Progress)
# Multi-Functional Controller (MFC)
A custom handheld controller that built for both gaming and conrolling RC devices.
[![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.x-E7352C?style=for-the-badge&logo=espressif&logoColor=white)](https://github.com/espressif/esp-idf)
[![Controller MCU](https://img.shields.io/badge/MCU-ESP32--S3--N16R8-red?style=for-the-badge&logo=espressif)](https://www.espressif.com/en/products/socs/esp32-s3)
[![KiCad](https://img.shields.io/badge/EDA-KiCad%2010-314CB0?style=for-the-badge&logo=kicad&logoColor=white)](https://www.kicad.org/)
[![Language](https://img.shields.io/badge/Firmware-C99%20%2F%20C%2B%2B-00599C?style=for-the-badge&logo=c)](https://en.wikipedia.org/wiki/C_(programming_language))
[![RTOS](https://img.shields.io/badge/OS-FreeRTOS-008080?style=for-the-badge)](https://www.freertos.org/)
**NOTE:** This project is still in development and some features and modules could be changed in the future.
## 1. Project Overview & Aim
## FEATURES
- **Primary Goal:** Engineer a high-performance, bare-metal Multi-Functional Controller (**MFC**) from scratch using an **ESP32-S3-N16R8** for low-latency 2.4 GHz RF control (such as custom RC platforms) and **PC Gamepad (`USB HID / Bluetooth`)** support.
- **Secondary Objectives:** Extend the controller's capabilities to include a **cloud-connected AI voice assistant**, **local FAT32 SD card telemetry logging**, and **multi-transducer acoustic feedback**.
- **Tech Stack:** C/C++, ESP-IDF v5.x (FreeRTOS), bare-metal register manipulation, SPI, Hardware Pulse Counter (`PCNT`), and One-Shot `ADC1`.
* Low latency on both bluetooth and radio signal connection
---
* -----**New features will be added in the future**-----
## 2. Current Situation & Early Prototype Showcase (Rev 0.1)
> [!NOTE]
> **Early Prototype Notice (Rev 0.1 Carrier Build):**
> The photographs and schematic below represent the **very early initial stage** of the physical hardware build. At this stage, only the core carrier foundation is soldered onto the 6×9 cm perfboard: the **ESP32-S3-N16R8 microcontroller**, **nRF24L01+ 2.4 GHz radio module**, **100 µF electrolytic burst-smoothing capacitor**, **signal male pin headers**, and **power rail male headers**.
> The repository's firmware (`firmware/main/mmcontroller.c`) and schematic (`hardware/mfc-1.kicad_sch`) are intentionally kept matched to this verified **Rev 0.1 hardware baseline**. As the new controller modules (`1.8" ST7789 TFT`, `K-Silver PS5 Hall-Effect Joysticks`, `MicroSD`, `MAX4466`, `2N2222` buzzer stage, and enclosure) are physically integrated, both the hardware photos and firmware will be updated step-by-step.
## COMPONENTS
- **Controller Prototype (Rev 0.1):** Prototyped on a **6×9 cm perfboard**. The `ESP32-S3-N16R8` communicates over a shared `SPI2_HOST` bus with a Nordic `nRF24L01+` 2.4 GHz transceiver and a temporary benchtop 1.3" SH1106 OLED display connected via the male breakout headers.
- **Current Baseline Firmware:** A deterministic FreeRTOS state machine (`firmware/main/mmcontroller.c`) handles hardware quadrature decoding (`PCNT`) for a `KY-040` rotary encoder menu UI, `300 ms` software button debouncing, and live 2.4 GHz RF channel configuration (`0–125`).
- **Display Hardware Pivot (`1.3" OLED` $\rightarrow$ `1.8" ST7789 TFT`):** Bench testing revealed that the 7-pin 1.3" OLED module lacks a physical Chip Select (`CS`) pin (hard-tied to GND internally), causing SPI bus collisions when sharing the bus. We are upgrading to a **1.8" ST7789 Color TFT LCD** (which exposes a dedicated hardware `CS` pin on `GPIO 10`) to share the SPI bus safely without bus contention.
- **Manufacturing & Enclosure Status:** Paused cutting the rigid ABS plastic enclosure (`DT-100`, `190 × 137 × 59 mm`) until next month when a variable 3-speed rotary tool (`Dremel/E108`) and `2.35 mm` diamond cutting wheels arrive to ensure clean, professional cutouts without melting the ABS plastic.
- High performance ESP32-S3-N16R8 microcontroller
- Affordable NRF24L01+ radio module for short comminications(Will be changed in future)
* 1 Rotary Encoder
* 1 100 microfarad electrolytic capacitor
* Male and female headers
* 6x9 cm perfboard
<p align="center">
<img src="images/TOP_VIEW.png" alt="Early Prototype Rev 0.1 Top View" width="700">
<br>
<em><b>Figure 1 (Early Build — Rev 0.1):</b> Initial 6×9 cm perfboard carrier top view — ESP32-S3-N16R8 module, nRF24L01+ 2.4 GHz transceiver, 100 µF decoupling capacitor, peripheral male pin headers, and power rail male headers. (Will be updated as Phase 2 modules are installed).</em>
</p>
---**Some of them could be changed due to various reasons in the future**----
<p align="center">
<img src="images/SIDE_VIEW_WITH_ANTENNA.png" alt="Early Prototype Rev 0.1 Side Profile & RF Antenna" width="700">
<br>
<em><b>Figure 2 (Early Build — Rev 0.1):</b> Side profile showing the external 2.4 GHz SMA dipole antenna connected to the nRF24L01+ transceiver and dual USB-C ports (Native USB-OTG & UART).</em>
</p>
<p align="center">
<img src="images/BOTTOM_VIEW.png" alt="Early Prototype Rev 0.1 Underbody Perfboard Soldering" width="700">
<br>
<em><b>Figure 3 (Early Build — Rev 0.1):</b> Underbody point-to-point solder bridges, common ground/power rails, and shared SPI2 signal routing on the 6×9 cm perfboard.</em>
</p>
### Rev 0.1 Core Carrier Schematic (`hardware/mfc-1.kicad_sch`)
<p align="center">
<img src="images/mfc-1.svg" alt="Rev 0.1 KiCad Schematic" width="750">
<br>
<em><b>Figure 4:</b> KiCad 10 schematic of the Rev 0.1 Core Carrier (`U1: ESP32-S3`, `U2: nRF24L01+`, `C1: 100 µF` decoupling capacitor).</em>
</p>
---
## 3. Controller Hardware Architecture & Bill of Materials (BOM)
```mermaid
flowchart LR
subgraph MFC["Multi-Functional Controller (ESP32-S3-N16R8)"]
MCU["ESP32-S3-N16R8 (FreeRTOS Dual-Core)"]
HALL["2x K-Silver PS5 Hall-Effect Joysticks"] -->|ADC1| MCU
MIC["MAX4466 Electret Mic Pre-Amp"] -->|ADC1| MCU
ENC["KY-040 Rotary Encoder"] -->|Hardware PCNT| MCU
MCU -->|SPI2_HOST + CS GPIO10| TFT["1.8-inch ST7789 Color TFT"]
MCU -->|SPI2_HOST + CS GPIO18| SD["MicroSD Card (FAT32)"]
MCU -->|2N2222 NPN Drivers| BUZ["1x Passive + 2x Active Buzzers"]
MCU -->|SPI2_HOST + CSN GPIO14| NRF_TX["nRF24L01+ 2.4GHz Transceiver"]
end
```
| Subsystem | Component | Technical Details & Role | Build State |
| :--- | :--- | :--- | :---: |
| **MCU** | **Espressif ESP32-S3-N16R8** | Dual-core Xtensa® LX7 @ 240 MHz, 16 MB Quad SPI Flash, 8 MB Octal PSRAM. | **Installed (Rev 0.1)** |
| **RF Telemetry** | **Nordic nRF24L01+ Module** | 2.4 GHz ISM band transceiver over shared `SPI2_HOST`. | **Installed (Rev 0.1)** |
| **Bulk Decoupling**| **100 µF Electrolytic + Male Headers**| Suppresses nRF24 PTX current spikes; modular header rails for peripherals. | **Installed (Rev 0.1)** |
| **Display** | **1.8" ST7789 TFT Color LCD** | 3.3V logic, full-color SPI display **with dedicated `CS` pin** (replacing 7-pin CS-less 1.3" bench OLED). | **Next Upgrade** |
| **Joysticks** | **2× K-Silver (KS) PS5 Hall-Effect Modules** | 3.3V active magnetic contactless gimbals for zero stick drift. | **Next Upgrade** |
| **UI Input** | **KY-040 Rotary Encoder** | Quadrature encoder (`CLK`, `DT`, `SW`) decoded in hardware via ESP32-S3 `PCNT`. | **Bench Verified** |
| **Voice AI** | **MAX4466 Electret Microphone Module** | Analog pre-amplifier with adjustable gain for cloud-connected AI voice assistant input. | **Next Upgrade** |
| **Data Logging** | **Arduino Micro SD Card Module** | SPI interface, formatted `FAT32` for local telemetry and configuration logging. | **Next Upgrade** |
| **Audio Output** | **1× Passive Buzzer + 2× Active Buzzers** | Passive buzzer for melodies + 2× active buzzers for percussive UI clicks, driven by **2N2222 NPN Transistors**. | **Next Upgrade** |
| **Enclosure** | **DT-100 Project Box** | Rigid black ABS desktop/console instrument enclosure (`190 × 137 × 59 mm`). | **Next Upgrade** |
| **Switches** | **Waterproof Main + KTS-303 Toggle + Slide** | 12–24V illuminated push-button switch, KTS-303 3-pin toggle switch, and 180° vertical slide switch. | **Next Upgrade** |
| **Power System** | **Dual 18650 Li-ion + TP4056 + MT3608** | Dual 18650 cells (4800 mAh), `TP4056` charger, `MT3608` DC-DC boost converter, `1N5817` Schottky & `1N4001` diodes, `100 nF` ceramic caps. | **Next Upgrade** |
---
## 4. Pinout Definitions (ESP32-S3 Controller)
### 4.1 Current Verified Pin Allocation (`firmware/main/mmcontroller.c`)
| Bus / Peripheral | Signal | ESP32-S3 Pin | Interface / Mode | Notes |
| :--- | :--- | :---: | :--- | :--- |
| **SPI Hub (`SPI2_HOST`)** | `MOSI` | `GPIO 11` | SPI Master Output | Shared data line (`nRF24L01+`, Display `SDA`, MicroSD) |
| **SPI Hub (`SPI2_HOST`)** | `MISO` | `GPIO 13` | SPI Master Input | Shared data input (`nRF24L01+`, MicroSD) |
| **SPI Hub (`SPI2_HOST`)** | `SCK` | `GPIO 12` | SPI Master Clock | Shared clock line (`nRF24L01+`, Display `CLK`, MicroSD) |
| **nRF24L01+ Radio** | `CSN` | `GPIO 14` | SPI Chip Select | Dedicated radio bus enable |
| **nRF24L01+ Radio** | `CE` | `GPIO 21` | Digital Output | Radio TX/RX power state activation |
| **TFT Display (1.8" ST7789)**| `CS` | `GPIO 10` | SPI Chip Select | Dedicated display bus enable |
| **TFT Display (1.8" ST7789)**| `DC` | `GPIO 9` | Digital Output | Command vs. frame data selection |
| **TFT Display (1.8" ST7789)**| `RES` | `GPIO 8` | Digital Output | Hardware display reset (`BLK` tied to `3.3V`) |
| **KY-040 Rotary Encoder** | `CLK` | `GPIO 15` | Hardware `PCNT` | Quadrature channel A (1000 ns glitch filter) |
| **KY-040 Rotary Encoder** | `DT` | `GPIO 16` | Hardware `PCNT` | Quadrature channel B (direction sensing) |
| **KY-040 Rotary Encoder** | `SW` | `GPIO 17` | Input (`PULLUP`) | Push-button switch (300 ms software debounce) |
| **K-Silver Hall Joystick 1**| `VRx` | `GPIO 4` | `ADC1_CH3` (`12 dB`) | Analog X-axis (0–3.3V active Hall sensor) |
| **K-Silver Hall Joystick 1**| `VRy` | `GPIO 5` | `ADC1_CH4` (`12 dB`) | Analog Y-axis (0–3.3V active Hall sensor) |
### 4.2 Planned Pin Allocation for Phase 2 Modules (Safe ESP32-S3 Matrix)
To avoid ESP32-S3 Octal Flash/PSRAM pins (`GPIO 26–37`), USB-OTG pins (`GPIO 19–20`), strapping pins (`GPIO 0, 3, 45, 46`), and `ADC2` Wi-Fi conflicts, the remaining controller modules map cleanly to:
| Upcoming Module | Signal | Recommended GPIO | Hardware Resource |
| :--- | :--- | :---: | :--- |
| **K-Silver Hall Joystick 2** | `VRx2` / `VRy2` | `GPIO 6` / `GPIO 7` | `ADC1_CH5` / `ADC1_CH6` (`12 dB` Atten) |
| **MAX4466 Microphone** | `OUT` (Analog) | `GPIO 1` | `ADC1_CH0` (DMA / Oneshot audio sampling) |
| **Battery Sense Divider** | `VBAT_SNS` | `GPIO 2` | `ADC1_CH1` (100kΩ / 100kΩ voltage divider) |
| **MicroSD Card Module** | `CS_SD` | `GPIO 18` | Shared `SPI2_HOST` Chip Select #3 |
| **Passive Buzzer (Melody)** | `BUZ_PWM` | `GPIO 38` | `LEDC` Hardware Timer PWM via `2N2222` |
| **Active Buzzers 1 & 2** | `ACT_BUZ_1/2` | `GPIO 39` / `GPIO 40`| Digital Output via `2N2222` NPN low-side switches |
| **Toggle / Slide Switches** | `SW_MODE` | `GPIO 41` / `GPIO 42`| Digital Input (`GPIO_PULLUP_ENABLE`) |
---
## 5. Hardware Safety & Engineering Rules
1. **Enclosure Machining RPM Rule:** Do **NOT** use standard fixed `16,000 RPM` rotary tools on the `DT-100` ABS plastic enclosure; high friction melts ABS instead of cutting it. Machining is paused until the variable 3-speed rotary tool with `2.35 mm` diamond cutting discs arrives.
2. **Hall-Effect Gimbal Polarity Protection:** Do **NOT** wire the K-Silver (KS) PS5 Hall-effect joystick `VCC` backward. Unlike passive carbon potentiometers, these are active 3.3V CMOS magnetic sensors and will burn out immediately under reverse polarity.
3. **nRF24L01+ Burst Decoupling:** Always keep the `100 µF` electrolytic capacitor across the `nRF24L01+` `VCC`/`GND` pins (alongside a `100 nF` ceramic cap for high-frequency noise) to prevent 3.3V rail brown-outs during PTX transmission bursts.
4. **2N2222 Base Resistor & Flyback Protection:** Always place a **`1 kΩ` series resistor** between the ESP32-S3 GPIO pin and the Base (`B`) of each `2N2222` NPN transistor (an unresisted BJT base clamps the GPIO to `0.7V` and draws excessive current). Place a **`1N4001` flyback diode** in reverse-parallel across inductive buzzer coils.
5. **Multi-Slave SPI Bus Initialization Rule:** When sharing `SPI2_HOST` across `nRF24L01+` (`GPIO 14`), `TFT Display` (`GPIO 10`), and `MicroSD` (`GPIO 18`), configure all three `CS` pins as outputs and drive them **HIGH (`1`)** prior to initializing the first SPI device so uninitialized slaves do not corrupt `MISO` (`GPIO 13`).
---
## 6. Immediate Next Steps & Engineering Roadmap
1. **1.8" ST7789 TFT Migration:** Rewrite the display initialization and frame buffer logic using ESP-IDF's `esp_lcd` component to replace the custom 5×8 monochrome OLED driver once the CS-equipped TFT module is wired.
2. **MicroSD Card SPI & FATFS Setup:** Configure the Arduino MicroSD card module on the shared SPI bus and initialize the `FATFS` virtual filesystem (`VFS`) for reading/writing telemetry logs.
3. **NVS Hall-Effect Joystick Calibration:** Write the calibration routine for the K-Silver PS5 Hall-effect joysticks—measuring resting center, minimum, and maximum voltages and saving them persistently to ESP32-S3 Non-Volatile Storage (`NVS`) flash.
4. **USB HID & Bluetooth Gamepad Stack:** Expose the calibrated Hall-effect axes and buttons as a standard gamepad interface over the ESP32-S3 native USB-OTG port and BLE.
---
## 7. Repository Structure & Build Instructions
```text
multi-functional-controller/
├── firmware/ # ESP-IDF v5.x C99 Firmware Project (ESP32-S3 Rev 0.1 Baseline)
│ ├── CMakeLists.txt # Top-level CMake project configuration
│ ├── sdkconfig # Verified ESP32-S3 target configuration
│ └── main/
│ ├── CMakeLists.txt # Component registration
│ └── mmcontroller.c # Master firmware: Shared SPI2, PCNT menu UI, nRF24 tuning
├── hardware/ # KiCad 10 Electronic Design Automation (EDA)
│ ├── mfc-1.kicad_pro # KiCad project file
│ ├── mfc-1.kicad_sch # Rev 0.1 carrier schematic (ESP32-S3, nRF24L01+, 100uF cap)
│ └── mfc-1.kicad_pcb # Board layout & routing rules
├── images/ # Rev 0.1 early prototype photography & vector schematic
│ ├── TOP_VIEW.png # Early build (Rev 0.1) carrier top view
│ ├── SIDE_VIEW_WITH_ANTENNA.png # Early build (Rev 0.1) RF antenna & profile view
│ ├── BOTTOM_VIEW.png # Early build (Rev 0.1) perfboard underside wiring
│ └── mfc-1.svg # Vector export of Rev 0.1 KiCad schematic
├── .gitignore # Clean repository filter (builds, caches, local configs)
├── LICENSE # MIT License
└── README.md # Master engineering documentation
```
### Build & Flash (ESP32-S3 Controller)
```bash
. $HOME/esp/esp-idf/export.sh
cd firmware
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyACM0 flash monitor
```
---
## Author & Project Links
- **Author:** [Ertunç Özbay](https://github.com/ertunc-ozbay)
- **Personal Workshop & Portfolio:** [ertuncozbay.dev](https://ertuncozbay.dev)
- **Project Detail Page:** [ertuncozbay.dev/projects/multi-functional-controller.html](https://ertuncozbay.dev/projects/multi-functional-controller.html)
- **License:** [MIT License](LICENSE)

8
firmware/CMakeLists.txt Normal file
View file

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mmcontroller)

View file

@ -0,0 +1,2 @@
idf_component_register(SRCS "mmcontroller.c"
INCLUDE_DIRS ".")

View file

@ -0,0 +1,367 @@
#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"
// ==========================================
// VERIFIED HARDWARE PIN MAPPINGS
// ==========================================
#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;
// Global Radio State
int current_radio_channel = 76;
typedef enum {
SCREEN_MAIN_MENU = 0,
SCREEN_DRIVE,
SCREEN_RADIO_CONFIG,
SCREEN_CALIBRATE,
SCREEN_SYSTEM_INFO
} screen_state_t;
// ==========================================
// 5x8 BARE-METAL FONT LIBRARY
// ==========================================
const uint8_t font_AZ[26][5] = {
{0x7E, 0x11, 0x11, 0x11, 0x7E}, {0x7F, 0x49, 0x49, 0x49, 0x36}, {0x3E, 0x41, 0x41, 0x41, 0x22},
{0x7F, 0x41, 0x41, 0x22, 0x1C}, {0x7F, 0x49, 0x49, 0x49, 0x41}, {0x7F, 0x09, 0x09, 0x09, 0x01},
{0x3E, 0x41, 0x49, 0x49, 0x7A}, {0x7F, 0x08, 0x08, 0x08, 0x7F}, {0x00, 0x41, 0x7F, 0x41, 0x00},
{0x20, 0x40, 0x41, 0x3F, 0x01}, {0x7F, 0x08, 0x14, 0x22, 0x41}, {0x7F, 0x40, 0x40, 0x40, 0x40},
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, {0x7F, 0x04, 0x08, 0x10, 0x7F}, {0x3E, 0x41, 0x41, 0x41, 0x3E},
{0x7F, 0x09, 0x09, 0x09, 0x06}, {0x3E, 0x41, 0x51, 0x21, 0x5E}, {0x7F, 0x09, 0x19, 0x29, 0x46},
{0x46, 0x49, 0x49, 0x49, 0x31}, {0x01, 0x01, 0x7F, 0x01, 0x01}, {0x3F, 0x40, 0x40, 0x40, 0x3F},
{0x1F, 0x20, 0x40, 0x20, 0x1F}, {0x3F, 0x40, 0x38, 0x40, 0x3F}, {0x63, 0x14, 0x08, 0x14, 0x63},
{0x03, 0x04, 0x78, 0x04, 0x03}, {0x61, 0x51, 0x49, 0x45, 0x43}
};
const uint8_t font_nums[10][5] = {
{0x3E, 0x51, 0x49, 0x45, 0x3E}, {0x00, 0x42, 0x7F, 0x40, 0x00}, {0x42, 0x61, 0x51, 0x49, 0x46},
{0x21, 0x41, 0x45, 0x4B, 0x31}, {0x18, 0x14, 0x12, 0x7F, 0x10}, {0x27, 0x45, 0x45, 0x45, 0x39},
{0x3C, 0x4A, 0x49, 0x49, 0x30}, {0x01, 0x71, 0x09, 0x05, 0x03}, {0x36, 0x49, 0x49, 0x49, 0x36},
{0x06, 0x49, 0x49, 0x29, 0x1E}
};
const uint8_t* get_font_data(char c) {
static const uint8_t f_spc[5] = {0,0,0,0,0};
static const uint8_t f_ptr[5] = {0x08, 0x04, 0x7E, 0x04, 0x08}; // '>'
static const uint8_t f_lt[5] = {0x08, 0x14, 0x22, 0x41, 0x00}; // '<'
static const uint8_t f_lbr[5] = {0x00, 0x7F, 0x41, 0x41, 0x00}; // '['
static const uint8_t f_rbr[5] = {0x00, 0x41, 0x41, 0x7F, 0x00}; // ']'
static const uint8_t f_lpar[5] = {0x00, 0x1C, 0x22, 0x41, 0x00}; // '('
static const uint8_t f_rpar[5] = {0x00, 0x41, 0x22, 0x1C, 0x00}; // ')'
static const uint8_t f_slash[5] = {0x20, 0x10, 0x08, 0x04, 0x02}; // '/'
static const uint8_t f_dash[5] = {0x08, 0x08, 0x08, 0x08, 0x08}; // '-'
static const uint8_t f_col[5] = {0x00, 0x36, 0x36, 0x00, 0x00}; // ':'
static const uint8_t f_dot[5] = {0x00, 0x60, 0x60, 0x00, 0x00}; // '.'
if (c >= 'A' && c <= 'Z') { return font_AZ[c - 'A']; }
if (c >= '0' && c <= '9') { return font_nums[c - '0']; }
if (c == '>') { return f_ptr; }
if (c == '<') { return f_lt; }
if (c == '[') { return f_lbr; }
if (c == ']') { return f_rbr; }
if (c == '(') { return f_lpar; }
if (c == ')') { return f_rpar; }
if (c == '/') { return f_slash; }
if (c == '-') { return f_dash; }
if (c == ':') { return f_col; }
if (c == '.') { return f_dot; }
return f_spc;
}
// ==========================================
// OLED & NRF SPI DRIVERS
// ==========================================
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; // SH1106 center offset
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 }; // Write to RF_CH register (0x05)
spi_transaction_t t = { .length = 16, .tx_buffer = tx };
spi_device_polling_transmit(nrf_spi, &t);
}
// ==========================================
// UI SCREEN RENDERING
// ==========================================
void draw_menu(int selected_index) {
char* items[] = {"DRIVE VEHICLE", "RADIO CONFIG", "CALIBRATE", "SYSTEM INFO"};
char buffer[32];
oled_draw_string(0, 20, "- MAIN MENU -");
for (int i = 0; i < NUM_MENU_ITEMS; i++) {
if (i == selected_index) {
sprintf(buffer, "> %-14s", items[i]);
} else {
sprintf(buffer, " %-14s", items[i]);
}
oled_draw_string(2 + (i * 2), 5, buffer);
}
}
void draw_system_info() {
char buffer[32];
oled_draw_string(0, 15, "- SYSTEM INFO -");
sprintf(buffer, "CHIP: ESP32-S3");
oled_draw_string(2, 5, buffer);
sprintf(buffer, "HEAP: %lu KB", (unsigned long)(esp_get_free_heap_size() / 1024));
oled_draw_string(4, 5, buffer);
oled_draw_string(6, 10, "[CLICK TO BACK]");
}
void draw_radio_config_screen(int channel) {
char buffer[32];
oled_draw_string(0, 5, "- RADIO CONFIG -");
if (nrf_check_connection()) {
sprintf(buffer, "CH: <%03d> (2.4G)", channel);
oled_draw_string(2, 2, buffer);
oled_draw_string(4, 2, "TURN DIAL TO TUNE");
} else {
oled_draw_string(2, 2, "STATUS: FAILED ");
oled_draw_string(4, 2, "CHECK WIRING ");
}
oled_draw_string(6, 5, "[CLICK TO SAVE]");
}
void draw_placeholder_screen(const char* title) {
oled_draw_string(0, 15, title);
oled_draw_string(3, 20, "NOT CONNECTED");
oled_draw_string(6, 10, "[CLICK TO BACK]");
}
// ==========================================
// MAIN APPLICATION ENTRY
// ==========================================
void app_main(void) {
// 1. Initialize Shared SPI Bus
spi_bus_config_t buscfg = {
.miso_io_num = PIN_NUM_MISO,
.mosi_io_num = PIN_NUM_MOSI,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096
};
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
// 2. Add OLED Device
spi_device_interface_config_t oled_cfg = {
.clock_speed_hz = 10 * 1000 * 1000,
.mode = 0,
.spics_io_num = OLED_PIN_CS,
.queue_size = 1
};
spi_bus_add_device(SPI2_HOST, &oled_cfg, &oled_spi);
gpio_reset_pin(OLED_PIN_DC); gpio_set_direction(OLED_PIN_DC, GPIO_MODE_OUTPUT);
gpio_reset_pin(OLED_PIN_RES); gpio_set_direction(OLED_PIN_RES, GPIO_MODE_OUTPUT);
oled_init();
oled_clear();
// 3. Add NRF24 Device
spi_device_interface_config_t nrf_cfg = {
.clock_speed_hz = 1 * 1000 * 1000,
.mode = 0,
.spics_io_num = NRF_PIN_CS,
.queue_size = 1
};
spi_bus_add_device(SPI2_HOST, &nrf_cfg, &nrf_spi);
// 4. Init Rotary Encoder Switch
gpio_config_t sw_cfg = {
.pin_bit_mask = (1ULL << PIN_ENCODER_SW),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&sw_cfg);
// 5. Init PCNT Hardware Decoder for Quadrature Encoder
pcnt_unit_handle_t pcnt_unit = NULL;
pcnt_unit_config_t unit_config = { .high_limit = 10000, .low_limit = -10000 };
pcnt_new_unit(&unit_config, &pcnt_unit);
pcnt_glitch_filter_config_t filter_config = { .max_glitch_ns = 1000 };
pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config);
pcnt_channel_handle_t pcnt_chan_a = NULL, pcnt_chan_b = NULL;
pcnt_chan_config_t chan_a_config = { .edge_gpio_num = PIN_ENCODER_CLK, .level_gpio_num = PIN_ENCODER_DT };
pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a);
pcnt_chan_config_t chan_b_config = { .edge_gpio_num = PIN_ENCODER_DT, .level_gpio_num = PIN_ENCODER_CLK };
pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b);
pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE);
pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE);
pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE);
pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE);
pcnt_unit_enable(pcnt_unit);
pcnt_unit_clear_count(pcnt_unit);
pcnt_unit_start(pcnt_unit);
ESP_LOGI(TAG, "MFC Multi-Tasking OS State Machine Online.");
// 6. Master UI State Machine Loop
int count = 0;
int last_menu_index = -1;
int 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;
int last_tuned_channel = current_radio_channel;
while (1) {
pcnt_unit_get_count(pcnt_unit, &count);
int sw_state = gpio_get_level(PIN_ENCODER_SW);
if (current_screen == SCREEN_MAIN_MENU) {
int raw_steps = count / 4;
int current_menu_index = ((raw_steps % NUM_MENU_ITEMS) + NUM_MENU_ITEMS) % NUM_MENU_ITEMS;
if (current_menu_index != last_menu_index || screen_needs_redraw) {
oled_clear();
draw_menu(current_menu_index);
last_menu_index = current_menu_index;
screen_needs_redraw = false;
}
// Click to Select with 300ms Debounce
if (sw_state == 0 && last_sw_state == 1) {
if ((xTaskGetTickCount() - last_button_press) > pdMS_TO_TICKS(300)) {
current_screen = (screen_state_t)(current_menu_index + 1);
screen_needs_redraw = true;
if (current_screen == SCREEN_RADIO_CONFIG) {
radio_start_count = count;
last_tuned_channel = current_radio_channel;
}
last_button_press = xTaskGetTickCount();
}
}
}
else {
// Sub-screen handling
if (current_screen == SCREEN_RADIO_CONFIG) {
int encoder_delta = (count - radio_start_count) / 4;
int target_channel = current_radio_channel + encoder_delta;
if (target_channel < 0) target_channel = 0;
if (target_channel > 125) target_channel = 125;
if (target_channel != last_tuned_channel || screen_needs_redraw) {
current_radio_channel = target_channel;
set_nrf_channel(current_radio_channel);
oled_clear();
draw_radio_config_screen(current_radio_channel);
last_tuned_channel = target_channel;
screen_needs_redraw = false;
}
}
else {
if (screen_needs_redraw) {
oled_clear();
if (current_screen == SCREEN_SYSTEM_INFO) {
draw_system_info();
} else if (current_screen == SCREEN_DRIVE) {
draw_placeholder_screen("- DRIVE VEHICLE -");
} else if (current_screen == SCREEN_CALIBRATE) {
draw_placeholder_screen("- CALIBRATION -");
}
screen_needs_redraw = false;
}
}
// Click to return to Main Menu with 300ms Debounce
if (sw_state == 0 && last_sw_state == 1) {
if ((xTaskGetTickCount() - last_button_press) > pdMS_TO_TICKS(300)) {
current_screen = SCREEN_MAIN_MENU;
screen_needs_redraw = true;
last_button_press = xTaskGetTickCount();
}
}
}
last_sw_state = sw_state;
vTaskDelay(pdMS_TO_TICKS(30));
}
}

2081
firmware/sdkconfig Normal file

File diff suppressed because it is too large Load diff

2
hardware/mfc-1.kicad_pcb Normal file
View file

@ -0,0 +1,2 @@
(kicad_pcb (version 20260206) (generator "pcbnew") (generator_version "10.0")
)

680
hardware/mfc-1.kicad_pro Normal file
View file

@ -0,0 +1,680 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 1.27
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 0.0,
"corner_smoothing": 0,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.5,
"min_island_area": 10.0,
"min_thickness": 0.25,
"pad_connection": 1,
"remove_islands": 0,
"thermal_relief_gap": 0.5,
"thermal_relief_spoke_width": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.2,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "mfc-1.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"idf": "",
"netlist": "",
"plot": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Simulation",
"name": "${EXCLUDE_FROM_SIM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Position Files",
"name": "${EXCLUDE_FROM_POS_FILES}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "mfc-1.kicad_sch",
"name": "Root",
"uuid": "859e05b1-62ca-4191-b0be-c155eab457bb"
}
],
"used_designators": "C1,U1-2",
"variants": []
},
"sheets": [
[
"859e05b1-62ca-4191-b0be-c155eab457bb",
"Root"
]
],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

2441
hardware/mfc-1.kicad_sch Normal file

File diff suppressed because it is too large Load diff

BIN
images/BOTTOM_VIEW.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

BIN
images/TOP_VIEW.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 MiB

19790
images/mfc-1.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 368 KiB