Browse Source

Initial

master
Englebert 2 years ago
parent
commit
0210dfa006
  1. 11
      LITTLEFS.patch
  2. 128
      README.md
  3. 7
      bert2M_fat12M_16MB.csv
  4. 39
      include/README
  5. 46
      lib/README
  6. 307
      openscad/AlienCase.scad
  7. 722
      openscad/BoxMode.scad
  8. 274
      openscad/Case.scad
  9. BIN
      openscad/Case.stl
  10. 300
      openscad/case02.scad
  11. BIN
      openscad/gimbal_ring.stl
  12. BIN
      openscad/gimbalbox-test-001.stl
  13. BIN
      openscad/rc_controller-body.stl
  14. BIN
      openscad/rc_controller-gimbal.stl
  15. BIN
      openscad/rc_controller-m5backcover.stl
  16. BIN
      openscad/rc_controller_body.stl
  17. 21
      platformio.ini
  18. 17
      sounds/AudaCity.aup
  19. BIN
      sounds/AudaCity_data/eff/d1f/eff1f356.au
  20. 10
      sounds/README.md
  21. BIN
      sounds/bluetooth.mp3
  22. BIN
      sounds/bluetooth.raw
  23. BIN
      sounds/pitchdecrease.mp3
  24. BIN
      sounds/pitchdecrease.raw
  25. BIN
      sounds/pitchincrease.mp3
  26. BIN
      sounds/pitchincrease.raw
  27. BIN
      sounds/poweroff.mp3
  28. BIN
      sounds/poweroff.raw
  29. BIN
      sounds/rccontroller.mp3
  30. BIN
      sounds/rccontroller.raw
  31. BIN
      sounds/reboot.mp3
  32. BIN
      sounds/reboot.raw
  33. BIN
      sounds/reset.mp3
  34. BIN
      sounds/reset.raw
  35. BIN
      sounds/rfscanners.mp3
  36. BIN
      sounds/rfscanners.raw
  37. BIN
      sounds/rfsettings.mp3
  38. BIN
      sounds/rfsettings.raw
  39. BIN
      sounds/rolldecrease.mp3
  40. BIN
      sounds/rolldecrease.raw
  41. BIN
      sounds/rollincrease.mp3
  42. BIN
      sounds/rollincrease.raw
  43. BIN
      sounds/rxbinding.mp3
  44. BIN
      sounds/rxbinding.raw
  45. BIN
      sounds/savesettings.mp3
  46. BIN
      sounds/savesettings.raw
  47. BIN
      sounds/stickcalibration.mp3
  48. BIN
      sounds/stickcalibration.raw
  49. BIN
      sounds/stickpositions.mp3
  50. BIN
      sounds/stickpositions.raw
  51. BIN
      sounds/systemrebooting.mp3
  52. BIN
      sounds/systemrebooting.raw
  53. BIN
      sounds/systemshuttingdown.mp3
  54. BIN
      sounds/systemshuttingdown.raw
  55. BIN
      sounds/throttledecrease.mp3
  56. BIN
      sounds/throttledecrease.raw
  57. BIN
      sounds/throttleincrease.mp3
  58. BIN
      sounds/throttleincrease.raw
  59. BIN
      sounds/trims.mp3
  60. BIN
      sounds/trims.raw
  61. 15
      sounds/upload.py
  62. BIN
      sounds/welcome.mp3
  63. BIN
      sounds/welcome.raw
  64. BIN
      sounds/wifi.mp3
  65. BIN
      sounds/wifi.raw
  66. BIN
      sounds/yawdecrease.mp3
  67. BIN
      sounds/yawdecrease.raw
  68. BIN
      sounds/yawincrease.mp3
  69. BIN
      sounds/yawincrease.raw
  70. 131
      src/BLE.cpp
  71. 45
      src/BLE.h
  72. 241
      src/BLEGamepad.cpp
  73. 67
      src/BLEGamepad.h
  74. 33
      src/CustomWiFi.cpp
  75. 24
      src/CustomWiFi.h
  76. 5
      src/GimbalGraph.cpp
  77. 14
      src/GimbalGraph.h
  78. 320
      src/Inputs.cpp
  79. 88
      src/Inputs.h
  80. 302
      src/Screen.cpp
  81. 70
      src/Screen.h
  82. 228
      src/StickCalibration.cpp
  83. 41
      src/StickCalibration.h
  84. 40
      src/StickPositions.cpp
  85. 21
      src/StickPositions.h
  86. 220
      src/Storage.cpp
  87. 42
      src/Storage.h
  88. 225
      src/Web.cpp
  89. 23
      src/Web.h
  90. 207
      src/main.cpp
  91. 30
      src/main.h
  92. 11
      test/README

11
LITTLEFS.patch

@ -0,0 +1,11 @@
--- /tmp/LITTLEFS.cpp 2023-03-17 11:31:01.330618361 +0800
+++ .pio/libdeps/m5stack-core2/LittleFS_esp32/src/LITTLEFS.cpp 2023-03-17 11:31:12.126579098 +0800
@@ -41,7 +41,7 @@
bool LITTLEFSImpl::exists(const char* path)
{
- File f = open(path, "r");
+ File f = open(path, "r", false);
return (f == true);
}

128
README.md

@ -0,0 +1,128 @@
### OBJECTIVE
1. Creating a controller running for RubyFPV [ Using it on Ground Station ]
2. Long Range via 5.8GHz [ Using it on ground station ]
3. Swappable 18650 [ NO NEED. As I had upgraded to 700mAH ]
4. High Power. Running on at least 2S2P (7.4v) for supplying 5V. [ NO NEED! As I am only using it and broadcast via ESPNow to Ground Station. ]
5. Having another M5Core2 as touch switches and interfacing with the gimbala.
6. Having a 800x480 pixel as FPV screen [ No NEED. Because using it at ground station ]
7. Extendable USB for the antennas sticks. [ No NEED. Using on grond station ]
### FEATURES
SILENT MODE:
During power up, push up the PITCH stick to the maximum then press power ON.
### LAYOUT DIAGRAM ###
M5Core2 Pinout:
+---------------+ +-------+-------+
| GND | | ADC | G35 |
+---------------+ +-------+-------+
| GND | | ADC | G36 |
+---------------+ +-------+-------+
| GND | | RST | EN |
+-------+-------+ +-------+-------+
| G23 | MOSI | | DAC | G25 |
+-------+-------+ +-------+-------+
| G38 | MISO | | DAC | G26 |
+-------+-------+ +-------+-------+
| G18 | SCK | | 3.3V |
+-------+-------+ +-------+-------+
| G3 | RXD0 | | TXD0 | G1 |
+-------+-------+ +-------+-------+
| G13 | RXD2 | | TXD2 | G14 |
+-------+-------+ +-------+-------+
| G21 | iSDA | | iSCL | G22 |
+-------+-------+ +-------+-------+
| G32 | pSDA | | pSCL | G33 |
+-------+-------+ +-------+-------+
| G27 | GPIO | | GPIO | G19 |
+-------+-------+ +-------+-------+
| G2 | I2SOUT| | I2SCK | G0 |
+-------+-------+ +-------+-------+
| NC | | PDMDT | G34 |
+---------------+ +-------+-------+
| NC | | 5V |
+---------------+ +-------+-------+
| NC | | BAT |
+---------------+ +-------+-------+
ESP32 Dev Pinout:
+-----------------------+
| O | USB | O |
| ------- |
3V3 | [ ] [ ] | VIN
GND | [ ] [ ] | GND
Touch3 / HSPI_CS0 / ADC2_3 / GPIO15 | [ ] [ ] | GPIO13 / ADC2_4 / HSPI_ID / Touch4
CS / Touch2 / HSPI_WP / ADC2_2 / GPIO2 | [ ] [ ] | GPIO12 / ADC2_5 / HSPI_Q / Touch5
Touch0 / HSPI_HD / ADC2_0 / GPIO4 | [ ] [ ] | GPIO14 / ADC2_6 / HSPI_CLK / Touch6
U2_RXD / GPIO16 | [ ] [ ] | GPIO27 / ADC2_7 / Touch7
U2_TXD / GPIO17 | [ ] [ ] | GPIO26 / ADC2_9 / DAC2
V_SPI_CS0 / GPIO5 | [ ] ___________ [ ] | GPIO25 / ADC2_8 / DAC1
SCK / V_SPI_CLK / GPIO18 | [ ] | | [ ] | GPIO33 / ADC1_5 / Touch8 / XTAL32
U0_CTS / MISO / V_SPI_Q / GPIO19 | [ ] | | [ ] | GPIO32 / ADC1_4 / Touch9 / XTAL32
SDA / V_SPI_HD / GPIO21 | [ ] | | [ ] | GPIO35 / ADC1_7
CLK2 / U0_RXD / GPIO3 | [ ] | | [ ] | GPIO34 / ADC1_6
CLK3 / U0_TXD / GPIO1 | [ ] | | [ ] | GPIO39 / ADC1_3 / SensVN
SCL / U0_RTS / V_SPI_WP / GPIO22 | [ ] | | [ ] | GPIO36 / ADC1_0 / SensVP
MOSI / V_SPI_WP / GPIO23 | [ ] |___________| [ ] | EN
| |
| | | ____ ____ | |
| | | | | | | | |
| |__|__| |__| |__| |
| O O |
+-----------------------+
# REF:
Document on How to Use: https://github.com/m5stack/m5-docs/blob/master/docs/en/api/lcd.md
Pinout and labels - https://docs.m5stack.com/en/core/core2_for_aws
MP3 Sound generator - https://ttsmp3.com/
GPIO35 - Throttle
GPIO36 - Yaw
GPIO32 - Pitch
GPIO33 - Roll
### FLOW
### HOW TO COMPILE
After pio run you will hit into issues. Use the below way to patch the downloaded libraries.
1. Run the below commands:
patch .pio/libdeps/m5stack-core2/LittleFS_esp32/src/LITTLEFS.cpp < LITTLEFS.patch
patching file .pio/libdeps/m5stack-core2/LittleFS_esp32/src/LITTLEFS.cpp
### HOW TO PUSH FILES
1. For all the voice files, go to sound folder and issue the commands below once connected to M5Core2TX WiFi:
for i in $(ls *.raw); do echo "Pushing [$i]..."; ./upload.py http://192.168.1.1/webupload $i; done
Pushing [bluetooth.raw]...
Pushing [pitchdecrease.raw]...
Pushing [pitchincrease.raw]...
Pushing [poweroff.raw]...
Pushing [rccontroller.raw]...
Pushing [reboot.raw]...
Pushing [reset.raw]...
Pushing [rfscanners.raw]...
Pushing [rfsettings.raw]...
Pushing [rolldecrease.raw]...
Pushing [rollincrease.raw]...
Pushing [rxbinding.raw]...
Pushing [savesettings.raw]...
Pushing [stickcalibration.raw]...
Pushing [stickpositions.raw]...
Pushing [systemrebooting.raw]...
Pushing [systemshuttingdown.raw]...
Pushing [throttledecrease.raw]...
Pushing [throttleincrease.raw]...
Pushing [trims.raw]...
Pushing [welcome.raw]...
Pushing [wifi.raw]...
Pushing [yawdecrease.raw]...
Pushing [yawincrease.raw]...
### HOW TO DELETE THE FILE
To delete it is easy:
curl -s http://192.168.1.1/erase?filename=the_filename

7
bert2M_fat12M_16MB.csv

@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x200000,
app1, app, ota_1, 0x210000,0x200000,
spiffs, data, spiffs, 0x410000,0xbf0000,
# 12MB LITTLEFS DiskSpace, 2MB ROM with 2MB OTA

39
include/README

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

307
openscad/AlienCase.scad

@ -0,0 +1,307 @@
round_quality = 100;
// Main Screen Design
translate([0,0,-6]) {
pi_screen();
pi_screen_cover();
}
translate([34,0,0])
gimbal();
translate([236,0,0])
gimbal();
// Tops Left Buttons
left_buttons();
right_buttons();
// Joining - Holders
// main_backbone();
/**** MODULES ****/
module left_buttons() {
translate([-101,42.5,-6]) {
difference() {
cube([70,15,2], center=true);
translate([-33,5,0])
rotate([0,0,45])
cube([45,15,3], center=true);
}
translate([-26.52,-.5,-19])
rotate([0,0,45])
cube([22,2,40], center=true);
translate([7.5,7,-19])
rotate([0,0,0])
cube([54,2,40], center=true);
translate([34,0,-19])
rotate([0,0,90])
cube([16,2,40], center=true);
}
}
module right_buttons() {
translate([101,42.5,-6]) {
difference() {
cube([70,15,2], center=true);
translate([43.5,5,0])
rotate([0,0,45])
cube([30,45,3], center=true);
}
translate([26.5,-.5,-19])
rotate([0,0,-45])
cube([22,2,40], center=true);
translate([-7.5,7,-19])
rotate([0,0,0])
cube([54,2,40], center=true);
translate([-34,0,-19])
rotate([0,0,90])
cube([16,2,40], center=true);
}
}
module gimbal() {
translate([-90,25,0]) {
/**
translate([-45,-25,-30])
cylinder($fn=round_quality, h=50, d=90, center=true);
**/
translate([-45,-25,-25]) {
difference() {
cube([70,70,40], center=true);
// The center hole
translate([0,0,-2])
cube([66,66,40], center=true);
// The Gimbal hole
translate([0,0,0])
cylinder($fn=round_quality, h=60, d=50, center=true);
}
}
translate([2,0,-26])
gimbal_ring();
}
}
module main_backbone() {
translate([0,0,-34])
cube([250,20,10], center=true);
}
module pi_screen_cover() {
translate([0,0,-30]) {
difference() {
cube([132,100,2], center=true);
translate([0,-50,0])
cube([100,10,3], center=true);
translate([0,50,0])
cube([100,10,3], center=true);
translate([-50,52,0])
rotate([0,0,45])
cube([10,10,3], center=true);
translate([-50,-52,0])
rotate([0,0,45])
cube([10,10,3], center=true);
translate([50,-52,0])
rotate([0,0,45])
cube([10,10,3], center=true);
translate([50,52,0])
rotate([0,0,45])
cube([10,10,3], center=true);
}
}
}
// 121mm x 76mm (hole)
module pi_screen() {
difference() {
translate([0,0,0]) {
cube([130,96,2], center=true);
// Holders
translate([-60,43,-2])
cube([10,10,2], center=true);
translate([60,43,-2])
cube([10,10,2], center=true);
translate([-60,-43,-2])
cube([10,10,2], center=true);
translate([60,-43,-2])
cube([10,10,2], center=true);
}
translate([0,0,0])
cube([121,76,3], center=true);
// Top and Bottom Cut
translate([0,53,0])
cube([100,20,3], center=true);
translate([0,-53,0])
cube([100,20,3], center=true);
// 45 degree cut
translate([-50,50,0])
rotate([0,0,45])
cube([10,10,3], center=true);
translate([50,50,0])
rotate([0,0,45])
cube([10,10,3], center=true);
translate([-50,-50,0])
rotate([0,0,45])
cube([10,10,3], center=true);
translate([50,-50,0])
rotate([0,0,45])
cube([10,10,3], center=true);
}
// Sides
translate([-65,0,-14])
rotate([90,0,90])
cube([96,30,2], center=true);
translate([65,0,-14])
rotate([90,0,90])
difference() {
cube([96,30,2], center=true);
// USB Hole
translate([10,0,0])
cube([50,16,3], center=true);
}
translate([0,44,-14])
rotate([90,0,0])
cube([100,30,2], center=true);
translate([0,-44,-14])
rotate([90,0,0])
difference() {
cube([100,30,2], center=true);
translate([40,0,0])
cube([10,10,5], center=true);
translate([15,0,0])
cube([10,10,5], center=true);
translate([-15,0,0])
cube([10,10,5], center=true);
translate([-40,0,0])
cube([10,10,5], center=true);
}
// Sides top and bottom
translate([-60.5,-48.9,-14])
rotate([90,0,0])
cube([11,30,2], center=true);
translate([60.5,-48.9,-14])
rotate([90,0,0])
cube([11,30,2], center=true);
translate([60.5,48.9,-14])
rotate([90,0,0])
cube([11,30,2], center=true);
translate([-60.5,48.9,-14])
rotate([90,0,0])
cube([11,30,2], center=true);
// Sides 45 degree top and bottom
translate([-52.5,46,-14])
rotate([90,0,-45])
cube([9,30,2], center=true);
translate([52.5,46,-14])
rotate([90,0,45])
cube([9,30,2], center=true);
translate([-52.5,-46,-14])
rotate([90,0,45])
cube([9,30,2], center=true);
translate([52.5,-46,-14])
rotate([90,0,-45])
cube([9,30,2], center=true);
}
module gimbal_ring_full() {
translate([-47,-25,23.5])
cylinder(h=5, d=64.5, center=true, $fn=round_quality);
translate([-71,-49,23.5])
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
translate([-23,-49,23.5])
mirror([-1,0,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-71,-0.8,23.5])
mirror([0,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-22.5,-0.5,23.5])
mirror([-1,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
}
module gimbal_ring() {
difference() {
gimbal_ring_full();
translate([-47,-25,23.5])
cylinder(h=6, d1=47.5, d2=57.5, center=true, $fn=round_quality);
}
}

722
openscad/BoxMode.scad

@ -0,0 +1,722 @@
round_quality=500;
draft=true;
show_gimbal=false;
show_body=true;
show_all=false;
show_m5_back=true;
if(show_body) {
controller();
}
if(show_m5_back) {
m5_back();
}
if(show_gimbal) {
translate([0,-41,-8.5])
gimbal_ring();
if(show_all) {
translate([0,91,-8.5])
gimbal_ring();
}
}
/**
// Ruler
translate([-50,-80,0])
rotate([0,0,45])
cube([200,2,2], center=true);
translate([-50,-89.5,13])
rotate([0,0,0])
cube([100,1,1], center=true);
translate([-70.5,-70,13])
rotate([0,0,0])
cube([1,100,1], center=true);
translate([-50,-42.5,13])
rotate([0,0,0])
cube([100,1,1], center=true);
translate([-23.5,-70,13])
rotate([0,0,0])
cube([1,100,1], center=true);
**/
/******* Modules *******/
module m5_back() {
translate([-95,0,-6]) {
difference() {
roundedcube([59.6,59.6,17.5], center = true, 0.5);
translate([0,0,1])
roundedcube([56.8,56.8,17.5], center = true, 0.5);
translate([26,0,-1.5])
cube([10,11,10], center=true);
translate([26,0,5])
cube([10,11,10], center=true);
translate([3.5,-25,5])
cube([50,11,10], center=true);
translate([3.5,25,5])
cube([50,11,10], center=true);
translate([25.5,23,5])
cube([10,5,20], center=true);
translate([25.5,-23,5])
cube([10,5,20], center=true);
}
translate([-28,0,7]) {
difference() {
cube([2,9,2], center=true);
}
}
translate([28,-15,7]) {
difference() {
cube([2,9,2], center=true);
}
}
translate([28,15,7]) {
difference() {
cube([2,9,2], center=true);
}
}
}
}
module holder_base() {
cylinder(h=8, d=11, $fn=round_quality);
translate([-6,0,4])
cube([11,11,5], center=true);
}
module gimbal_top() {
translate([-47,-66,10.5]) {
difference() {
cube([63,63,3], center=true);
cylinder(h=5, d=48.2, center=true, $fn=round_quality);
translate([-26.7,-26.7,0])
cylinder(h=5, d=5.6, center=true, $fn=round_quality);
translate([26.7,-26.7,0])
cylinder(h=5, d=5.6, center=true, $fn=round_quality);
translate([26.7,26.7,0])
cylinder(h=5, d=5.6, center=true, $fn=round_quality);
translate([-26.7,26.7,0])
cylinder(h=5, d=5.6, center=true, $fn=round_quality);
}
}
}
module controller() {
difference() {
controller_base();
translate([-37,0,-9])
cube([6,77.8,4], center=true);
translate([-55,0,-9])
rotate([0,0,90])
cube([6,37.8,4], center=true);
}
}
module controller_base() {
rotate([180,0,270])
m5_body();
translate([0,-14-30,0])
gimbal_box();
translate([0,58+30,0])
gimbal_box();
translate([-25,0,-5]) {
difference() {
holder_base();
translate([0,0,-1])
cylinder(h=11, d=6.5, $fn=round_quality);
translate([-15,0,5])
cube([12,12,12], center=true);
}
}
joints();
gimbal_top();
translate([0,132,0])
gimbal_top();
}
module bracket() {
translate([-100,-48,1])
cube([5,60,10], center=true);
}
module joints() {
translate([-72,-30,0])
cube([10,5,10], center=true);
translate([-72,30,0])
cube([10,5,10], center=true);
translate([-47,0,0])
difference() {
cube([30,65,10], center=true);
for(i = [0:10])
translate([0,-40+i*10,0])
rotate([0,0,45])
cube([30,5,20], center=true);
}
translate([-64,0,0])
cube([6,10,10], center=true);
translate([-49.9,0,-8])
difference() {
cube([35.8,10,8], center=true);
cube([45.8,6,4], center=true);
}
translate([-37,0,-8]) {
difference() {
cube([10,65.8,8], center=true);
cube([6,67.8,4], center=true);
}
}
}
module antenna() {
translate([80,0,0]) {
cube([30,20,8], center=true);
translate([25,65,0])
rotate([90,0,0])
cylinder(h=150, d=15, center=true, $fn=round_quality);
}
translate([80,-25,0]) {
cube([30,20,8], center=true);
translate([92,0,0])
rotate([0,90,0])
cylinder(h=150, d=15, center=true, $fn=round_quality);
}
}
module center_switch() {
translate([0,-100,18])
cube([20,63,2], center=true);
translate([0,-131,0])
cube([20,2,40], center=true);
}
module esp32_box() {
translate([0,-8,-2.5]) {
cube([105,40,35], center=true);
}
}
module box_side() {
translate([0,-50,20]) {
difference() {
cube([140,25,80], center=true);
translate([15,20,0])
rotate([0,0,180])
trapezium_b();
translate([-15,20,0])
rotate([0,0,180])
trapezium_b();
translate([45,20,0])
rotate([0,0,180])
trapezium_b();
translate([-45,20,0])
rotate([0,0,180])
trapezium_b();
// Corner cut
translate([55,0,53])
rotate([0,45,0])
cube([50,35,5], center=true);
translate([-55,0,53])
rotate([0,-45,0])
cube([50,35,5], center=true);
translate([55,0,-53])
rotate([0,-45,0])
cube([50,35,5], center=true);
translate([-55,0,-53])
rotate([0,45,0])
cube([50,35,5], center=true);
// Frame Pattern
translate([-15,0,48])
rotate([90,180,0])
trapezium_a();
translate([15,0,48])
rotate([90,180,0])
trapezium_a();
translate([-45,0,48])
rotate([90,180,0])
trapezium_a();
translate([45,0,48])
rotate([90,180,0])
trapezium_a();
translate([45,0,-48])
rotate([90,0,0])
trapezium_a();
translate([-45,0,-48])
rotate([90,0,0])
trapezium_a();
translate([15,0,-48])
rotate([90,0,0])
trapezium_a();
translate([-15,0,-48])
rotate([90,0,0])
trapezium_a();
// M5Core2
translate([0,-4,0])
rotate([90,0,0])
m5core2();
// Upper Layer
translate([0,-11,0])
rotate([90,0,0])
cube([130,70,5], center=true);
}
}
}
module m5core2() {
cube([54.2,54.2,16.2], center=true);
}
module gimbal_box() {
// translate([0,3,-1])
// gimbal_ring();
difference() {
translate([-47,-22,0])
cube([70,70,24], center=true);
// Corner cut
translate([-86,-52,0])
rotate([0,90,45])
cube([50,35,5], center=true);
translate([-8,-52,0])
rotate([0,90,-45])
cube([50,35,5], center=true);
translate([-86,8,0])
rotate([0,90,-45])
cube([50,35,5], center=true);
translate([-8,8,0])
rotate([0,90,45])
cube([50,35,5], center=true);
// Top corners
translate([-50,-60,15])
rotate([45,0,0])
cube([100,10,10], center=true);
translate([-50,15.5,15])
rotate([45,0,0])
cube([100,10,10], center=true);
translate([-9.5,-25.5,15])
rotate([45,0,90])
cube([100,10,10], center=true);
translate([-84.5,-25.5,15])
rotate([45,0,90])
cube([100,10,10], center=true);
// Latch
translate([-75,0,-7])
cube([10,10,4], center=true);
translate([-75,-43,-7])
cube([10,10,4], center=true);
translate([-18.5,0,-7])
cube([10,10,4], center=true);
translate([-18.5,-43,-7])
cube([10,10,4], center=true);
// Internal
translate([-47,-22,7])
cube([63,63,50], center=true);
}
}
module base_box() {
difference() {
translate([0,0,0])
cube([140,105,20], center=true);
// For display to push in
translate([0,0,3])
cube([121,96,25], center=true);
// Corner cut
translate([-69,-51,0])
rotate([0,90,45])
cube([50,15,5], center=true);
translate([69,-51,0])
rotate([0,90,-45])
cube([50,15,5], center=true);
translate([-69,51,0])
rotate([0,90,-45])
cube([50,15,5], center=true);
translate([69,51,0])
rotate([0,90,45])
cube([50,15,5], center=true);
// Frame Pattern
/**
translate([-45,60,0])
rotate([0,0,180])
trapezium_a();
translate([-15,60,0])
rotate([0,0,180])
trapezium_a();
translate([15,60,0])
rotate([0,0,180])
trapezium_a();
translate([45,60,0])
rotate([0,0,180])
trapezium_a();
translate([-77,25,0])
rotate([0,0,-90])
trapezium_a();
translate([-77,-2,0])
rotate([0,0,-90])
trapezium_a();
translate([-77,-28,0])
rotate([0,0,-90])
trapezium_a();
translate([77,25,0])
rotate([0,0,90])
trapezium_a();
translate([77,-2,0])
rotate([0,0,90])
trapezium_a();
translate([77,-28,0])
rotate([0,0,90])
trapezium_a();
**/
// USB Ports
translate([70,3,0])
rotate([90,0,90])
cube([50,16,30], center=true);
}
}
module trapezium_a() {
difference() {
cube([20,20,25], center=true);
translate([-10,7,0])
rotate([90,0,45])
cube([20,26,5], center=true);
translate([10,7,0])
rotate([90,0,-45])
cube([20,26,5], center=true);
}
}
module trapezium_b() {
difference() {
cube([20,20,125], center=true);
translate([-10,7,0])
rotate([90,0,45])
cube([20,126,5], center=true);
translate([10,7,0])
rotate([90,0,-45])
cube([20,126,5], center=true);
}
}
module gimbal_ring_base() {
translate([-47,-25,23.5])
cylinder(h=5, d=64.5, center=true, $fn=round_quality);
translate([-71,-49,23.5])
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
translate([-23,-49,23.5])
mirror([-1,0,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-71,-0.8,23.5])
mirror([0,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-22.5,-0.5,23.5])
mirror([-1,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
}
module gimbal_ring() {
difference() {
gimbal_ring_base();
translate([-47,-25,23.5])
cylinder(h=6, d1=47.5, d2=57.5, center=true, $fn=round_quality);
}
}
module m5_body() {
translate([0,95,-1]) {
difference() {
if(!draft) {
rounded_corner_cube(8, [56.6,56.6,17.5]);
} else {
roundedcube([56.6,56.6,17.5], center = true, 0.5);
}
if(!draft) {
rounded_corner_cube(8, [50.6,50.6,17.5]);
} else {
roundedcube([50.6,50.6,19.5], center = true, 0.5);
}
translate([0,0,1])
if(!draft) {
rounded_corner_cube(8, [54.6,54.6,17.5]);
} else {
roundedcube([54.6,54.6,17.5], center = true);
}
// Power Button
translate([25,13,0])
rotate([90,90,90])
cylinder(h=10, d=8, center=true, $fn=round_quality);
// Charging Port
translate([25,-1,0])
rotate([0,90,90])
cube([4,18.5,10], center=true);
// Speaker Holes
translate([-25,11,0])
rotate([90,0,90])
cylinder(h=10, d=3, center=true, $fn=round_quality);
translate([-25,5,0])
rotate([90,0,90])
cylinder(h=10, d=3, center=true, $fn=round_quality);
translate([-25,-1,0])
rotate([90,0,90])
cylinder(h=10, d=3, center=true, $fn=round_quality);
translate([-25,-7,0])
rotate([90,0,90])
cylinder(h=10, d=3, center=true, $fn=round_quality);
translate([-25,-13,0])
rotate([90,0,90])
cylinder(h=10, d=3, center=true, $fn=round_quality);
// Latches
translate([0,25,0])
cube([10,10,3], center=true);
translate([-15,-25,0])
cube([10,10,3], center=true);
translate([15,-25,0])
cube([10,10,3], center=true);
}
}
}
module rounded_corner_cube(diameter, dimension) {
cube_length = dimension[0];
cube_width = dimension[1];
cube_thickness = dimension[2];
difference() {
cube(dimension, center=true);
translate([0,0,0])
cube_corners(diameter, dimension);
}
}
module cube_corners(diameter, dimension) {
cube_length = dimension[0];
cube_width = dimension[1];
cube_thickness = dimension[2] + 0.1;
translate([0.1,0.1,0]) {
difference() {
translate([0,0,0])
cube([cube_length, cube_width, cube_thickness], center=true);
translate([cube_length/2-(diameter/2), cube_width/2-(diameter/2), 0])
cylinder(h = cube_thickness+0.1, d = diameter, center = true, $fn=round_quality);
translate([-(diameter/2),0,0])
cube([cube_length+0.1, cube_width+0.1, cube_thickness+0.1], center=true);
translate([0,-(diameter/2),0])
cube([cube_length+0.1, cube_width, cube_thickness+0.1], center=true);
}
}
translate([0.1,-0.1,0]) {
difference() {
translate([0,0,0])
cube([cube_length, cube_width, cube_thickness], center=true);
translate([cube_length/2-(diameter/2), -(cube_width/2-(diameter/2)), 0])
cylinder(h = cube_thickness+0.1, d = diameter, center = true, $fn=round_quality);
translate([-(diameter/2),0,0])
cube([cube_length+0.1, cube_width+0.1, cube_thickness+0.1], center=true);
translate([0,(diameter/2),0])
cube([cube_length+0.1, cube_width, cube_thickness+0.1], center=true);
}
}
translate([-0.1,0.1,0]) {
difference() {
translate([0,0,0])
cube([cube_length, cube_width, cube_thickness], center=true);
translate([-(cube_length/2-(diameter/2)), cube_width/2-(diameter/2), 0])
cylinder(h = cube_thickness+0.1, d = diameter, center = true, $fn=round_quality);
translate([(diameter/2),0,0])
cube([cube_length+0.1, cube_width+0.1, cube_thickness+0.1], center=true);
translate([0,-(diameter/2),0])
cube([cube_length+0.1, cube_width, cube_thickness+0.1], center=true);
}
}
translate([-0.1,-0.1,0]) {
difference() {
translate([0,0,0])
cube([cube_length, cube_width, cube_thickness], center=true);
translate([-(cube_length/2-(diameter/2)), -(cube_width/2-(diameter/2)), 0])
cylinder(h = cube_thickness+0.1, d = diameter, center = true, $fn=round_quality);
translate([(diameter/2),0,0])
cube([cube_length+0.1, cube_width+0.1, cube_thickness+0.1], center=true);
translate([0,(diameter/2),0])
cube([cube_length+0.1, cube_width, cube_thickness+0.1], center=true);
}
}
}
module roundedcube(size = [1, 1, 1], center = false, radius = 0.5, apply_to = "all") {
// If single value, convert to [x, y, z] vector
size = (size[0] == undef) ? [size, size, size] : size;
translate_min = radius;
translate_xmax = size[0] - radius;
translate_ymax = size[1] - radius;
translate_zmax = size[2] - radius;
diameter = radius * 2;
obj_translate = (center == false) ?
[0, 0, 0] : [
-(size[0] / 2),
-(size[1] / 2),
-(size[2] / 2)
];
translate(v = obj_translate) {
hull() {
for (translate_x = [translate_min, translate_xmax]) {
x_at = (translate_x == translate_min) ? "min" : "max";
for (translate_y = [translate_min, translate_ymax]) {
y_at = (translate_y == translate_min) ? "min" : "max";
for (translate_z = [translate_min, translate_zmax]) {
z_at = (translate_z == translate_min) ? "min" : "max";
translate(v = [translate_x, translate_y, translate_z])
if (
(apply_to == "all") ||
(apply_to == "xmin" && x_at == "min") || (apply_to == "xmax" && x_at == "max") ||
(apply_to == "ymin" && y_at == "min") || (apply_to == "ymax" && y_at == "max") ||
(apply_to == "zmin" && z_at == "min") || (apply_to == "zmax" && z_at == "max")
) {
sphere(r = radius);
} else {
rotate =
(apply_to == "xmin" || apply_to == "xmax" || apply_to == "x") ? [0, 90, 0] : (
(apply_to == "ymin" || apply_to == "ymax" || apply_to == "y") ? [90, 90, 0] :
[0, 0, 0]
);
rotate(a = rotate)
cylinder(h = diameter, r = radius, center = true);
}
}
}
}
}
}
}

274
openscad/Case.scad

@ -0,0 +1,274 @@
round_quality = 100;
show_faceplate = true;
if(show_faceplate) {
// Basic structures
translate([-30,-80,0])
gimbal_unit_face();
translate([0,0,0])
pi_screen();
translate([100,0,0])
m5stack();
translate([95,-80,0])
gimbal_unit_face();
// Spacers
difference() {
spacers();
// Power Switch 1
translate([20,-80,0])
cube([11,16,5], center=true);
// Power Switch 2
translate([45,-80,0])
cube([11,16,5], center=true);
// Screw Holes
translate([-70,38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([-70,-38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([-70,-110,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([70,38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([70,-38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([133,-110,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([133,-38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([133,38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([33,-110,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
}
// Ruler
// translate([30,-48,0])
// cube([360,2,2], center=true);
} // End of show_faceplate
// Gimbal Decor
rotate([0,180,0])
translate([77,-55,-20])
gimbal_ring();
rotate([0,180,0])
translate([-47,-55,-20])
gimbal_ring();
/**
translate([-125,-8,0])
gimbal_unit_face();
translate([125,-8,0])
gimbal_unit_face();
translate([0,0,0])
pi_screen();
translate([0,-72,0])
m5stack();
// Bridges
translate([0,0,0])
face_bridges();
**/
module spacers() {
translate([-70,0,0])
cube([10,86,2], center=true);
translate([68,0,0])
cube([10,86,2], center=true);
translate([132,0,0])
cube([10,86,2], center=true);
translate([100,35,0])
cube([55,16,2], center=true);
translate([100,-35,0])
cube([55,16,2], center=true);
translate([31,-44,0])
cube([212,5,2], center=true);
translate([30,-80,0])
cube([60,70,2], center=true);
translate([-70,-80,0])
cube([10,70,2], center=true);
translate([132,-80,0])
cube([10,70,2], center=true);
}
module face_bridges() {
translate([-80,0,0])
cube([30,86,2], center=true);
translate([80,0,0])
cube([30,86,2], center=true);
translate([0,48,0])
cube([190,10,2], center=true);
translate([0,-48,0])
difference() {
cube([320,10,2], center=true);
translate([0,-25,1])
m5stack_solid();
}
}
module m5stack_solid() {
cube([55,55,10], center=true);
}
module m5stack() {
difference() {
translate([0,0,0])
cube([55,55,2], center=true);
translate([0,0,0])
cube([50,50,3], center=true);
}
}
// 121mm x 76mm (hole)
module pi_screen() {
difference() {
translate([0,0,0])
cube([130,86,2], center=true);
translate([0,0,0])
cube([121,76,3], center=true);
}
}
module gimbal_unit_face() {
translate([0,0,0]) {
// Top
difference() {
translate([0,0,0])
rotate([0,0,0])
cube([70,70,2], center=true);
// Frsky M9 Gimbal
translate([0,0,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=49, center=true);
// Frsky M9 Screen Top Left
translate([-27.25,27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
// Frsky M9 Screen Top Right
translate([27.25,27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
// Frsky M9 Screen Bottom Left
translate([-27.25,-27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
// Frsky M9 Screen Bottom Right
translate([27.25,-27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
/*
translate([0,0,0])
rotate([0,0,0])
cube([77,51,4], center=true);
*/
}
}
// Just a ruler
/***
translate([-13.9,-44.4])
cube([100,10,10], center=true);
***/
}
module gimbal_ring_full() {
translate([-47,-25,23.5])
cylinder(h=5, d=64.5, center=true, $fn=round_quality);
translate([-71,-49,23.5])
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
translate([-23,-49,23.5])
mirror([-1,0,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-71,-0.8,23.5])
mirror([0,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-22.5,-0.5,23.5])
mirror([-1,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
}
module gimbal_ring() {
difference() {
gimbal_ring_full();
translate([-47,-25,23.5])
cylinder(h=6, d1=47.5, d2=57.5, center=true, $fn=round_quality);
}
}

BIN
openscad/Case.stl

300
openscad/case02.scad

@ -0,0 +1,300 @@
round_quality = 100;
show_faceplate = true;
if(show_faceplate) {
// Basic structures
translate([-30,-80,0])
gimbal_unit_face();
translate([0,0,0])
pi_screen();
translate([100,0,0])
m5stack();
translate([95,-80,0])
gimbal_unit_face();
// Spacers
difference() {
spacers();
// Power Switch 1
translate([20,-80,0])
cube([11,16,5], center=true);
// Power Switch 2
translate([45,-80,0])
cube([11,16,5], center=true);
// Screw Holes
translate([-70,38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([-70,-38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([-70,-110,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([70,38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([70,-38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([133,-110,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([133,-38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([133,38,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
translate([33,-110,0])
cylinder($fn=round_quality, h=5, d=5, center=true);
}
// Ruler
// translate([30,-48,0])
// cube([360,2,2], center=true);
// Display Decor
translate([0,0,3])
difference() {
cube([130,86,5], center=true);
cube([121,76,8], center=true);
}
// M5Stack Decor
translate([100,0,3])
difference() {
translate([0,0,0])
cube([55,55,5], center=true);
translate([0,0,0])
cube([50,50,8], center=true);
}
// Pi Screen and M5Stack Links
translate([0,0,3]) {
translate([70,0,0])
cube([10,5,2], center=true);
translate([70,10,0])
cube([10,5,2], center=true);
translate([70,20,0])
cube([10,5,2], center=true);
translate([70,-20,0])
cube([10,5,2], center=true);
translate([70,-10,0])
cube([10,5,2], center=true);
}
} // End of show_faceplate
// Gimbal Decor
translate([17,-55,-20])
gimbal_ring();
translate([143,-55,-20])
gimbal_ring();
/**
translate([-125,-8,0])
gimbal_unit_face();
translate([125,-8,0])
gimbal_unit_face();
translate([0,0,0])
pi_screen();
translate([0,-72,0])
m5stack();
// Bridges
translate([0,0,0])
face_bridges();
**/
module spacers() {
translate([-70,0,0])
cube([10,86,2], center=true);
translate([68,0,0])
cube([10,86,2], center=true);
translate([132,0,0])
cube([10,86,2], center=true);
translate([100,35,0])
cube([55,16,2], center=true);
translate([100,-35,0])
cube([55,16,2], center=true);
translate([31,-44,0])
cube([212,5,2], center=true);
translate([30,-80,0])
cube([60,70,2], center=true);
translate([-70,-80,0])
cube([10,70,2], center=true);
translate([132,-80,0])
cube([10,70,2], center=true);
}
module face_bridges() {
translate([-80,0,0])
cube([30,86,2], center=true);
translate([80,0,0])
cube([30,86,2], center=true);
translate([0,48,0])
cube([190,10,2], center=true);
translate([0,-48,0])
difference() {
cube([320,10,2], center=true);
translate([0,-25,1])
m5stack_solid();
}
}
module m5stack_solid() {
cube([55,55,10], center=true);
}
module m5stack() {
difference() {
translate([0,0,0])
cube([55,55,2], center=true);
translate([0,0,0])
cube([50,50,3], center=true);
}
}
// 121mm x 76mm (hole)
module pi_screen() {
difference() {
translate([0,0,0])
cube([130,86,2], center=true);
translate([0,0,0])
cube([121,76,3], center=true);
}
}
module gimbal_unit_face() {
translate([0,0,0]) {
// Top
difference() {
translate([0,0,0])
rotate([0,0,0])
cube([70,70,2], center=true);
// Frsky M9 Gimbal
translate([0,0,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=49, center=true);
// Frsky M9 Screen Top Left
translate([-27.25,27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
// Frsky M9 Screen Top Right
translate([27.25,27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
// Frsky M9 Screen Bottom Left
translate([-27.25,-27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
// Frsky M9 Screen Bottom Right
translate([27.25,-27.25,0])
rotate([0,0,0])
cylinder($fn=round_quality, h=5,d=5.6, center=true);
/*
translate([0,0,0])
rotate([0,0,0])
cube([77,51,4], center=true);
*/
}
}
// Just a ruler
/***
translate([-13.9,-44.4])
cube([100,10,10], center=true);
***/
}
module gimbal_ring_full() {
translate([-47,-25,23.5])
cylinder(h=5, d=64.5, center=true, $fn=round_quality);
translate([-71,-49,23.5])
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
translate([-23,-49,23.5])
mirror([-1,0,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-71,-0.8,23.5])
mirror([0,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
translate([-22.5,-0.5,23.5])
mirror([-1,-1,0]) {
rotate([0,0,45])
difference() {
cube([15,10,5], center=true);
translate([-9,0,2])
rotate([0,-15,0])
cube([25,15,5], center=true);
translate([-4,0,0])
cylinder(h=10, d=3.5, center=true, $fn=round_quality);
translate([-4,0,0])
cylinder(h=3, d=5.6, center=true, $fn=round_quality);
}
}
}
module gimbal_ring() {
difference() {
gimbal_ring_full();
translate([-47,-25,23.5])
cylinder(h=6, d1=47.5, d2=57.5, center=true, $fn=round_quality);
}
}

BIN
openscad/gimbal_ring.stl

BIN
openscad/gimbalbox-test-001.stl

BIN
openscad/rc_controller-body.stl

BIN
openscad/rc_controller-gimbal.stl

BIN
openscad/rc_controller-m5backcover.stl

BIN
openscad/rc_controller_body.stl

21
platformio.ini

@ -0,0 +1,21 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
board_build.mcu = esp32
board_build.f_cpu = 240000000L
board_build.partitions = bert2M_fat12M_16MB.csv
lib_deps =
m5stack/M5Core2
lorol/LittleFS_esp32
lemmingdev/ESP32-BLE-Gamepad@^0.3.4

17
sounds/AudaCity.aup

@ -0,0 +1,17 @@
<?xml version="1.0" standalone="no" ?>
<!DOCTYPE project PUBLIC "-//audacityproject-1.3.0//DTD//EN" "http://audacity.sourceforge.net/xml/audacityproject-1.3.0.dtd" >
<project xmlns="http://audacity.sourceforge.net/xml/" projname="AudaCity_data" version="1.3.0" audacityversion="2.4.2" sel0="0.0000000000" sel1="0.0000000000" vpos="0" h="0.0000000000" zoom="934.0723511654" rate="22050.0" snapto="off" selectionformat="hh:mm:ss + milliseconds" frequencyformat="Hz" bandwidthformat="octaves">
<tags>
<tag name="Software" value="Lavf58.76.100"/>
</tags>
<wavetrack name="savesettings" isSelected="1" height="150" minimized="0" channel="2" linked="0" mute="0" solo="0" rate="22050" gain="1.0" pan="0.0" colorindex="0">
<waveclip offset="0.00000000" colorindex="0">
<sequence maxsamples="262144" sampleformat="262159" numsamples="20207">
<waveblock start="0">
<simpleblockfile filename="eff1f356.au" len="20207" min="-0.344003" max="0.329232" rms="0.068922"/>
</waveblock>
</sequence>
<envelope numpoints="0"/>
</waveclip>
</wavetrack>
</project>

BIN
sounds/AudaCity_data/eff/d1f/eff1f356.au

10
sounds/README.md

@ -0,0 +1,10 @@
### How to upload
This is to upload sounds to the device. In order to speak correctly.
Below is the command to push the files to the M5Core2 once is connected to the device via WiFi.
for i in $(ls *.mp3); do echo "Pushing [$i]...."; ./upload.py http://192.168.1.1/webupload $i; done
### How to delete
To delete it is easy:
curl -s http://192.168.1.1/erase?filename=the_filename

BIN
sounds/bluetooth.mp3

BIN
sounds/bluetooth.raw

BIN
sounds/pitchdecrease.mp3

BIN
sounds/pitchdecrease.raw

BIN
sounds/pitchincrease.mp3

BIN
sounds/pitchincrease.raw

BIN
sounds/poweroff.mp3

BIN
sounds/poweroff.raw

BIN
sounds/rccontroller.mp3

BIN
sounds/rccontroller.raw

BIN
sounds/reboot.mp3

BIN
sounds/reboot.raw

BIN
sounds/reset.mp3

BIN
sounds/reset.raw

BIN
sounds/rfscanners.mp3

BIN
sounds/rfscanners.raw

BIN
sounds/rfsettings.mp3

BIN
sounds/rfsettings.raw

BIN
sounds/rolldecrease.mp3

BIN
sounds/rolldecrease.raw

BIN
sounds/rollincrease.mp3

BIN
sounds/rollincrease.raw

BIN
sounds/rxbinding.mp3

BIN
sounds/rxbinding.raw

BIN
sounds/savesettings.mp3

BIN
sounds/savesettings.raw

BIN
sounds/stickcalibration.mp3

BIN
sounds/stickcalibration.raw

BIN
sounds/stickpositions.mp3

BIN
sounds/stickpositions.raw

BIN
sounds/systemrebooting.mp3

BIN
sounds/systemrebooting.raw

BIN
sounds/systemshuttingdown.mp3

BIN
sounds/systemshuttingdown.raw

BIN
sounds/throttledecrease.mp3

BIN
sounds/throttledecrease.raw

BIN
sounds/throttleincrease.mp3

BIN
sounds/throttleincrease.raw

BIN
sounds/trims.mp3

BIN
sounds/trims.raw

15
sounds/upload.py

@ -0,0 +1,15 @@
#!/usr/bin/python3
import requests
import sys
### print (sys.argv)
### print(len(sys.argv))
if len(sys.argv) < 3:
print("Invalid parameters\n")
exit
filename = sys.argv[2]
url = sys.argv[1]
files = {'file': open(filename, 'rb')}
final_resp = requests.post(url, files=files)

BIN
sounds/welcome.mp3

BIN
sounds/welcome.raw

BIN
sounds/wifi.mp3

BIN
sounds/wifi.raw

BIN
sounds/yawdecrease.mp3

BIN
sounds/yawdecrease.raw

BIN
sounds/yawincrease.mp3

BIN
sounds/yawincrease.raw

131
src/BLE.cpp

@ -0,0 +1,131 @@
#include "BLE.h"
BleGamepad bleGamepad;
BLE::BLE(void) {
updated = true;
}
void BLE::begin(void) {
updated = true;
ble_begin = true;
}
void BLE::end(void) {
ble_begin = false;
ble_started = false;
Serial.printf("BLEGamepad is disconnected");
bleGamepad.end();
}
void BLE::update(void) {
// Starts to connect when receive this...
if(ble_begin) {
if(!ble_started) {
Serial.printf("Starting BLEGamepad.");
bleGamepad.begin();
Serial.printf("BLEGamepad OK");
ble_started = true;
}
if(bleGamepad.isConnected()) {
if((uint32_t)(millis() - last_updated) > 1) {
int16_t throttle = map(inputs.throttle, 0, 4096, -32767, 32767); // Throttle
int16_t yaw = map(inputs.yaw, 0, 4096, -32767, 32767); // Yaw
int16_t pitch = map(inputs.pitch, 0, 4096, -32767, 32767); // Pitch
int16_t roll = map(inputs.roll, 0, 4096, -32767, 32767); // Roll
bleGamepad.setAxes(yaw, throttle, roll, pitch, 0, 0, DPAD_CENTERED);
uint32_t m = millis();
if(button1) {
if((uint32_t)(m - last_button1) > 100) {
bleGamepad.release(BUTTON_1);
button1 = false;
button1_sent = false;
} else {
// Only sent once and delay for 100ms
if(!button1_sent) {
bleGamepad.press(BUTTON_1);
button1_sent = true;
}
}
}
if(button2) {
if((uint32_t)(m - last_button2) > 100) {
bleGamepad.release(BUTTON_2);
button2 = false;
button2_sent = false;
} else {
// Only sent once and delay for 100ms
if(!button2_sent) {
bleGamepad.press(BUTTON_2);
button2_sent = true;
}
}
}
if(button3) {
if((uint32_t)(m - last_button3) > 100) {
bleGamepad.release(BUTTON_3);
button3 = false;
button3_sent = false;
} else {
// Only sent once and delay for 100ms
if(!button3_sent) {
bleGamepad.press(BUTTON_3);
button3_sent = true;
}
}
}
if(button4) {
if((uint32_t)(m - last_button4) > 100) {
bleGamepad.release(BUTTON_4);
button4 = false;
button4_sent = false;
} else {
// Only sent once and delay for 100ms
if(!button4_sent) {
bleGamepad.press(BUTTON_4);
button4_sent = true;
}
}
}
if(button5) {
if((uint32_t)(m - last_button5) > 100) {
bleGamepad.release(BUTTON_5);
button5 = false;
button5_sent = false;
} else {
// Only sent once and delay for 100ms
if(!button5_sent) {
bleGamepad.press(BUTTON_5);
button5_sent = true;
}
}
}
sent_counter_raw++;
last_updated = millis();
// Serial.printf("Sent\n");
}
}
}
// Reset the profiler when hits 1 second mark
if((uint32_t)(millis() - last_seconds) > 1000) {
sent_counter = sent_counter_raw;
sent_counter_raw = 0;
last_seconds = millis();
}
}
BLE ble;

45
src/BLE.h

@ -0,0 +1,45 @@
#ifndef _BLE_H
#define _BLE_H
#include <Arduino.h>
#include <M5Core2.h>
#include <BleGamepad.h>
#include "Inputs.h"
class BLE {
public:
bool ble_begin = false;
bool updated = false;
bool ble_started = false;
int16_t sent_counter_raw = 0;
int16_t sent_counter = 0;
bool button1 = false;
bool button2 = false;
bool button3 = false;
bool button4 = false;
bool button5 = false;
bool button1_sent = false;
bool button2_sent = false;
bool button3_sent = false;
bool button4_sent = false;
bool button5_sent = false;
uint32_t last_button1 = 0;
uint32_t last_button2 = 0;
uint32_t last_button3 = 0;
uint32_t last_button4 = 0;
uint32_t last_button5 = 0;
BLE(void);
void begin(void);
void end(void);
void update(void);
private:
uint32_t last_updated = 0;
uint32_t last_seconds = 0;
};
extern BLE ble;
#endif

241
src/BLEGamepad.cpp

@ -0,0 +1,241 @@
#include "BLEGamepad.h"
BLEGamepad::BLEGamepad(void) {
updated = true;
}
void BLEGamepad::begin(void) {
updated = true;
}
void BLEGamepad::draw_gimbal(TFT_eSprite *m, uint16_t x, uint16_t y) {
// Top
m->drawFastHLine(x, y, 90, TFT_DARKGREEN);
// Bottom
m->drawFastHLine(x, y + 89, 90, TFT_DARKGREEN);
// Left
m->drawFastVLine(x, y, 90, TFT_DARKGREEN);
// Right
m->drawFastVLine(x + 89, y, 90, TFT_DARKGREEN);
// Middle dotted lines
for(uint16_t x1 = x; x1 < x + 90; x1 += 10) {
m->drawFastHLine(x1, y + 45, 5, TFT_DARKGREEN);
}
for(uint16_t y1 = y; y1 < y + 90; y1 += 10) {
m->drawFastVLine(x + 45, y1, 5, TFT_DARKGREEN);
}
}
void BLEGamepad::show(TFT_eSprite *m) {
if(!ble_begin) {
ble_begin = true;
ble.begin();
}
if((uint32_t) (millis() - last_updated) > 50) {
updated = true;
}
if((uint32_t) (millis() - last_voltage_read) > 500) {
voltage = M5.Axp.GetBatVoltage();
current = M5.Axp.GetBatCurrent();
last_voltage_read = millis();
}
if(updated) {
m->fillRect(0, 0, 320, 240, BLACK);
m->setTextColor(WHITE, BLACK);
m->setTextSize(2);
m->setCursor(0, 0);
m->print("Packet(s) Sent: ");
m->print(String(ble.sent_counter));
m->setCursor(0, 17);
m->print("Bat: ");
m->print(String(voltage) + "V " + String(current) + "mA");
// Draw the gimbal box
draw_gimbal(m, 69, 150);
draw_gimbal(m, 161, 150);
// Show the position of the left and right gimbal
// Convert the throttle and raw to x and y
// Temporary assume 4096 = max
uint16_t left_gimbal_y = map(inputs.throttle, stickcalibration.throttle_min, stickcalibration.throttle_max, 90, 0);
uint16_t left_gimbal_x = map(inputs.yaw, stickcalibration.yaw_min, stickcalibration.yaw_max, 90, 0);
m->fillCircle(left_gimbal_x + 69, left_gimbal_y + 150, 5, TFT_YELLOW);
uint16_t right_gimbal_y = map(inputs.pitch, stickcalibration.pitch_min, stickcalibration.pitch_max, 0, 90);
uint16_t right_gimbal_x = map(inputs.roll, stickcalibration.roll_min, stickcalibration.roll_max, 0, 90);
m->fillCircle(right_gimbal_x + 161, right_gimbal_y + 150, 5, TFT_YELLOW);
// Draw the switches
uint32_t last_millis = millis();
if((uint32_t)(last_millis - last_button1) > 500) {
m->drawRect(0, 40, 60, 60, TFT_BLUE); // Button 1
} else {
m->drawRect(0, 40, 60, 60, TFT_BLUE); // Button 1
m->fillRect(1, 41, 60, 60, TFT_BLUE);
}
if((uint32_t)(last_millis - last_button2) > 500) {
m->drawRect(65, 40, 60, 60, TFT_BLUE); // Button 2
} else {
m->drawRect(65, 40, 60, 60, TFT_BLUE); // Button 2
m->fillRect(66, 41, 60, 60, TFT_BLUE);
}
if((uint32_t)(last_millis - last_button3) > 500) {
m->drawRect(130, 40, 60, 60, TFT_BLUE); // Button 3
} else {
m->drawRect(130, 40, 60, 60, TFT_BLUE); // Button 3
m->fillRect(131, 41, 60, 60, TFT_BLUE);
}
if((uint32_t)(last_millis - last_button4) > 500) {
m->drawRect(195, 40, 60, 60, TFT_BLUE); // Button 4
} else {
m->drawRect(195, 40, 60, 60, TFT_BLUE); // Button 4
m->fillRect(196, 41, 60, 60, TFT_BLUE);
}
if((uint32_t)(last_millis - last_button5) > 500) {
m->drawRect(260, 40, 60, 60, TFT_BLUE); // Button 5
} else {
m->drawRect(260, 40, 60, 60, TFT_BLUE); // Button 5
m->fillRect(261, 41, 60, 60, TFT_BLUE);
}
m->setCursor(90, 130);
m->print("BLE GAMEPAD");
m->pushSprite(0,0);
updated = false;
last_updated = millis();
}
// Key Input Handler
if(M5.BtnC.wasPressed()) {
if(ble_begin) {
ble_begin = false;
ble.end();
}
screen.current_screen = SCREEN_MENU;
screen.updated = true;
}
// Temporary connect from here...
if(M5.BtnA.wasPressed()) {
}
// Handling inputs
Event &e = M5.Buttons.event;
coordinate = M5.Touch.getPressPoint();
// Making sure not retrigger...
if((uint32_t)(millis() - touch_time) > 100) {
if(e & (E_TOUCH)) {
Serial.printf("E_TOUCH X:%d, Y:%d\r\n", e.to.x, e.to.y);
// Determine touched positions:
// Left gimbal:
// - Top
// - MinX: 110
// - MinY: 150
// - MaxX: 120
// - MaxY: 180
// - Bottom
// - MinX: 110
// - MinY: 200
// - MaxX: 120
// - MaxY: 220
// - Left
// - MinX: 80
// - MinY: 180
// - MaxX: 105
// - MaxY: 210
// - Right
// - MinX: 110
// - MinY: 150
// - MaxX: 160
// - MaxY: 190
if(e.to.x >= 110 && e.to.x <= 120 && e.to.y >= 140 && e.to.y <= 180) {
// Minus throttle
// Serial.printf("Left: TOP\n");
inputs.trim_throttle-=20;
//// speak.speak_trim_word = TRIM_THROTTLE_INCREASE;
//// speak.speak_now_trim = true;
} else if(e.to.x >= 110 && e.to.x <= 170 && e.to.y >= 200 && e.to.y <= 220) {
// Add throttle
// Serial.printf("Left: BOTTOM\n");
inputs.trim_throttle+=20;
//// speak.speak_trim_word = TRIM_THROTTLE_DECREASE;
//// speak.speak_now_trim = true;
} else if(e.to.x >= 60 && e.to.x <= 105 && e.to.y >= 180 && e.to.y <= 210) {
// Minus Yaw
// Serial.printf("Left: LEFT\n");
inputs.trim_yaw-=20;
//// speak.speak_trim_word = TRIM_YAW_DECREASE;
//// speak.speak_now_trim = true;
} else if(e.to.x >= 110 && e.to.x <= 160 && e.to.y >= 150 && e.to.y <= 190) {
// Add Yaw
// Serial.printf("Left: RIGHT\n");
inputs.trim_yaw+=20;
//// speak.speak_trim_word = TRIM_YAW_INCREASE;
//// speak.speak_now_trim = true;
} else if(e.to.x >= 250 && e.to.x <= 230 && e.to.y >= 187 && e.to.y <= 197) {
// Add Row
inputs.trim_roll+=20;
//// speak.speak_trim_word = TRIM_ROLL_INCREASE;
//// speak.speak_now_trim = true;
} else if(e.to.x >= 180 && e.to.x <= 175 && e.to.y >= 187 && e.to.y <= 197) {
// Minus Row
inputs.trim_roll+=20;
//// speak.speak_trim_word = TRIM_ROLL_DECREASE;
//// speak.speak_now_trim = true;
// TODO: Continue the rest.....
} else if(e.to.x >= 0 && e.to.x <= 60 && e.to.y >= 40 && e.to.y <= 100) {
// Button 1
ble.last_button1 = millis(); // Setting the time so the system knows how long to trigger
last_button1 = ble.last_button1; // For remembering last press on UI
ble.button1 = true; // Send to BLE let the process to run through
} else if(e.to.x >= 65 && e.to.x <= 125 && e.to.y >= 40 && e.to.y <= 100) {
// Button 2
ble.last_button2 = millis(); // Setting the time so the system knows how long to trigger
last_button2 = ble.last_button2; // For remembering last press on UI
ble.button2 = true; // Send to BLE let the process to run through
} else if(e.to.x >= 130 && e.to.x <= 190 && e.to.y >= 40 && e.to.y <= 100) {
// Button 3
ble.last_button3 = millis(); // Setting the time so the system knows how long to trigger
last_button3 = ble.last_button3; // For remembering last press on UI
ble.button3 = true; // Send to BLE let the process to run through
} else if(e.to.x >= 195 && e.to.x <= 255 && e.to.y >= 40 && e.to.y <= 100) {
// Button 4
ble.last_button4 = millis(); // Setting the time so the system knows how long to trigger
last_button4 = ble.last_button4; // For remembering last press on UI
ble.button4 = true; // Send to BLE let the process to run through
} else if(e.to.x >= 260 && e.to.x <= 310 && e.to.y >= 40 && e.to.y <= 100) {
// Button 5
ble.last_button5 = millis(); // Setting the time so the system knows how long to trigger
last_button5 = ble.last_button5; // For remembering last press on UI
ble.button5 = true; // Send to BLE let the process to run through
}
touch_time = millis();
}
}
/***
if(e & (E_RELEASE)) {
Serial.printf("E_MOVE X:%d, Y:%d\r\n", e & E_MOVE ? e.from.x : e.to.x, e & E_MOVE ? e.from.y : e.to.y);
}
if(coordinate.x > 0 || coordinate.y > 0)
Serial.printf("x:%d, y:%d \r\n", coordinate.x, coordinate.y);
***/
}
BLEGamepad blegamepad;

67
src/BLEGamepad.h

@ -0,0 +1,67 @@
#ifndef _BLEGAMEPAD_H
#define _BLEGAMEPAD_H
#include <Arduino.h>
#include <M5Core2.h>
#include "BLE.h"
#include "Inputs.h"
#include "Screen.h"
//// #include "Speak.h"
enum gesture_axis {
NONE_AXIS,
HORIZONTAL,
VERTICAL
};
enum gimbal {
NONE_GIMBAL,
LEFT,
RIGHT
};
enum gesture_meanings {
NONE_GESTURE,
INCREASE,
DECREASE
};
enum trim_names {
NONE_TRIM,
TRIM_YAW_DECREASE,
TRIM_YAW_INCREASE,
TRIM_THROTTLE_DECREASE,
TRIM_THROTTLE_INCREASE,
TRIM_ROLL_INCREASE,
TRIM_ROLL_DECREASE,
TRIM_PITCH_INCREASE,
TRIM_PITCH_DECREASE
};
class BLEGamepad {
public:
TouchPoint_t coordinate;
uint32_t touch_time = 0;
bool ble_begin = false;
bool updated = false;
float voltage = 0.00;
float current = 0.00;
uint32_t last_button1 = 0;
uint32_t last_button2 = 0;
uint32_t last_button3 = 0;
uint32_t last_button4 = 0;
uint32_t last_button5 = 0;
BLEGamepad(void);
void begin(void);
void show(TFT_eSprite *m);
void draw_gimbal(TFT_eSprite *m, uint16_t x, uint16_t y);
private:
uint32_t last_updated = 0;
uint32_t last_voltage_read = 0;
};
extern BLEGamepad blegamepad;
#endif

33
src/CustomWiFi.cpp

@ -0,0 +1,33 @@
#include "CustomWiFi.h"
CustomWiFi::CustomWiFi(void) {
// pinMode(CustomWiFi_INDICATOR, OUTPUT);
last_wifi_check = 0;
wifi_check_duration = 10;
}
void CustomWiFi::set_credential(char *ssid, char *passphrase) {
strcpy(CustomWiFi::_ssid, ssid);
strcpy(CustomWiFi::_passphrase, passphrase);
// Initial value
this->wifi_check_duration = 5000;
}
void CustomWiFi::begin(void) {
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
WiFi.softAP(WIFI_SSID, WIFI_PASSPHRASE);
delay(100);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);
IPAddress IP = WiFi.softAPIP();
// Serial.print(F("AP IP address: "); Serial.prinln(IP);
}
CustomWiFi customwifi;

24
src/CustomWiFi.h

@ -0,0 +1,24 @@
#ifndef CUSTOMWIFI_H_
#define CUSTOMWIFI_H_
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#define WIFI_SSID "M5CoreTX"
#define WIFI_PASSPHRASE "LetMeIn123"
// #define CUSTOMWIFI_INDICATOR 2
class CustomWiFi {
public:
CustomWiFi(void);
void set_credential(char *ssid, char *passphrase);
void begin(void);
char _ssid[32], _passphrase[32];
private:
uint32_t last_wifi_check, wifi_check_duration;
};
extern CustomWiFi customwifi;
#endif

5
src/GimbalGraph.cpp

@ -0,0 +1,5 @@
#include "GimbalGraph.h"
GimbalGraph::GimbalGraph(void) {
}

14
src/GimbalGraph.h

@ -0,0 +1,14 @@
#ifndef _GIMBAL_GRAPH_H
#define _GIMBAL_GRAPH_H
#include <Arduino.h>
class GimbalGraph {
public:
GimbalGraph(void);
private:
};
extern GimbalGraph gimbalgraph;
#endif

320
src/Inputs.cpp

@ -0,0 +1,320 @@
#include "Inputs.h"
Inputs::Inputs(void) {
}
void Inputs::begin(void) {
throttle_raw = 0;
yaw_raw = 0;
pitch_raw = 0;
roll_raw = 0;
// Prepare the storage engine for TYPR
for(int i = 0; i < MEDIAN_TOTAL; i++) {
throttle_pool[i] = 0;
yaw_pool[i] = 0;
pitch_pool[i] = 0;
roll_pool[i] = 0;
}
// Reading trims
String trim_str = "";
String filename = "";
filename = "/TRIM_THROTTLE";
if(!storage.exists(filename)) {
storage.writeFile(LITTLEFS, filename, String(trim_throttle));
} else {
trim_str = storage.readFile(LITTLEFS, filename);
trim_throttle = trim_str.toInt();
}
filename = "/TRIM_YAW";
if(!storage.exists(filename)) {
storage.writeFile(LITTLEFS, filename, String(trim_yaw));
} else {
trim_str = storage.readFile(LITTLEFS, filename);
trim_yaw = trim_str.toInt();
}
filename = "/TRIM_PITCH";
if(!storage.exists(filename)) {
storage.writeFile(LITTLEFS, filename, String(trim_pitch));
} else {
trim_str = storage.readFile(LITTLEFS, filename);
trim_pitch = trim_str.toInt();
}
filename = "/TRIM_ROLL";
if(!storage.exists(filename)) {
storage.writeFile(LITTLEFS, filename, String(trim_roll));
} else {
trim_str = storage.readFile(LITTLEFS, filename);
trim_roll = trim_str.toInt();
}
// Pre-read
read();
// Activate silent mode...
//// if(pitch_raw < 1000) speak.silent = true;
// Activate low volume mode....
//// if(pitch_raw > 3000) speak.low_volume = true;
}
void Inputs::read(void) {
throttle_raw = analogRead(THROTTLE_PIN);
yaw_raw = analogRead(YAW_PIN);
pitch_raw = analogRead(PITCH_PIN);
roll_raw = analogRead(ROLL_PIN);
if(invert_throttle) {
throttle_raw = 4095 - throttle_raw;
}
if(invert_yaw) {
yaw_raw = 4095 - yaw_raw;
}
if(invert_pitch) {
pitch_raw = 4095 - pitch_raw;
}
if(invert_roll) {
roll_raw = 4095 - roll_raw;
}
throttle = pool_insert(THROTTLE, throttle_raw) + trim_throttle;
yaw = pool_insert(YAW, yaw_raw) + trim_yaw;
pitch = pool_insert(PITCH, pitch_raw) + trim_pitch;
roll = pool_insert(ROLL, roll_raw) + trim_roll;
}
/*
* median_get:
* To get the median from the data depending on the median_type
*
* E.g.:
* retval = median_get(THROTTLE);
*/
uint16_t Inputs::median_get(uint8_t median_type) {
bool inserted = false;
uint16_t median_tmp[MEDIAN_TOTAL];
// Loop through the variable to determine position to insert
// [ 12, 32, 4 ,0, 50, 2, 10, 10, 5, 20, 0 ]
/*
[ 12 ]
[ 12, 32 ]
[ 4, 12, 32 ]
[ 0, 4, 12, 32 ]
[ 0, 4, 12, 32, 50 ]
[ 0, 2, 4, 12, 32, 50 ]
*/
// Initial
uint16_t temp_val = 0;
uint16_t total_insert = 1;
// Search on type
if(median_type == THROTTLE) {
median_tmp[0] = throttle_pool[0];
} else if(median_type == YAW) {
median_tmp[0] = yaw_pool[0];
} else if(median_type == PITCH) {
median_tmp[0] = pitch_pool[0];
} else if(median_type == ROLL) {
median_tmp[0] = roll_pool[0];
}
// Loop insert and sort
for(int raw_count = 1; raw_count < MEDIAN_TOTAL; raw_count++) {
inserted = false;
if(median_type == THROTTLE) {
temp_val = throttle_pool[raw_count];
} else if(median_type == YAW) {
temp_val = yaw_pool[raw_count];
} else if(median_type == PITCH) {
temp_val = pitch_pool[raw_count];
} else if(median_type == ROLL) {
temp_val = roll_pool[raw_count];
}
for(int median_count = 0; median_count < total_insert; median_count++) {
if(!inserted) {
if(temp_val < median_tmp[median_count]) {
inserted = true;
// Reverse copy and insert
for(int median_reverse = total_insert + 1; median_reverse > median_count; median_reverse--) {
median_tmp[median_reverse] = median_tmp[median_reverse - 1];
}
// Insert the detected
median_tmp[median_count] = temp_val;
// Increase total insertion
total_insert++;
}
}
}
// Nothing was inserted... so put at the last.
if(!inserted) {
median_tmp[total_insert++] = temp_val;
}
}
// Return the result
// return median_tmp[MEDIAN_POS];
// Using RC dead band way....**** NEED TO IMPROvE!!
if(median_type == THROTTLE) {
if(throttle > median_tmp[MEDIAN_POS]) {
if(throttle - median_tmp[MEDIAN_POS] > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
if(median_tmp[MEDIAN_POS] - throttle > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
return throttle;
}
}
}
} else if(median_type == YAW) {
if(yaw > median_tmp[MEDIAN_POS]) {
if(yaw - median_tmp[MEDIAN_POS] > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
if(median_tmp[MEDIAN_POS] - yaw > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
return yaw;
}
}
}
} else if(median_type == PITCH) {
if(pitch > median_tmp[MEDIAN_POS]) {
if(pitch - median_tmp[MEDIAN_POS] > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
if(median_tmp[MEDIAN_POS] - pitch > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
return pitch;
}
}
}
} else if(median_type == ROLL) {
if(roll > median_tmp[MEDIAN_POS]) {
if(roll - median_tmp[MEDIAN_POS] > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
if(median_tmp[MEDIAN_POS] - roll > RC_DEADBAND) {
return median_tmp[MEDIAN_POS];
} else {
return roll;
}
}
}
}
}
uint16_t Inputs::throttle_insert(uint16_t val) {
// Insert to the array...at the last
for(int i = 1; i < MEDIAN_TOTAL; i++)
// Shift to left
throttle_pool[i - 1] = throttle_pool[i];
throttle_pool[MEDIAN_TOTAL - 1] = val;
// Find median....
return median_get(THROTTLE);
}
uint16_t Inputs::yaw_insert(uint16_t val) {
// Insert to the array...at the last
for(int i = 1; i < MEDIAN_TOTAL; i++)
// Shift to left
yaw_pool[i - 1] = yaw_pool[i];
yaw_pool[MEDIAN_TOTAL - 1] = val;
// Find median....
return median_get(YAW);
}
uint16_t Inputs::pitch_insert(uint16_t val) {
// Insert to the array...at the last
for(int i = 1; i < MEDIAN_TOTAL; i++)
// Shift to left
pitch_pool[i - 1] = pitch_pool[i];
pitch_pool[MEDIAN_TOTAL - 1] = val;
// Find median....
return median_get(PITCH);
}
uint16_t Inputs::roll_insert(uint16_t val) {
// Insert to the array...at the last
for(int i = 1; i < MEDIAN_TOTAL; i++)
// Shift to left
roll_pool[i - 1] = roll_pool[i];
roll_pool[MEDIAN_TOTAL - 1] = val;
// Find median....
return median_get(ROLL);
}
/*
* pool_insert:
* To insert the data into the median pool depending on type of the variable
*
* E.g.:
* throttle_value = pool_insert(THROTTLE);
*/
uint16_t Inputs::pool_insert(uint8_t pool_type, uint16_t val) {
// Insert to the array...at the last of the array
for(int i = 1; i < MEDIAN_TOTAL; i++) {
// Shift to left
if(pool_type == THROTTLE) {
throttle_pool[i - 1] = throttle_pool[i];
} else if(pool_type == YAW) {
yaw_pool[i - 1] = yaw_pool[i];
} else if(pool_type == PITCH) {
pitch_pool[i - 1] = pitch_pool[i];
} else if(pool_type == ROLL) {
roll_pool[i - 1] = roll_pool[i];
}
}
// Find median....
if(pool_type == THROTTLE) {
throttle_pool[MEDIAN_TOTAL - 1] = val;
} else if(pool_type == YAW) {
yaw_pool[MEDIAN_TOTAL - 1] = val;
} else if(pool_type == ROLL) {
roll_pool[MEDIAN_TOTAL - 1] = val;
} else if(pool_type == PITCH) {
pitch_pool[MEDIAN_TOTAL - 1] = val;
}
return median_get(pool_type);
}
Inputs inputs;

88
src/Inputs.h

@ -0,0 +1,88 @@
#ifndef _INPUTS_H
#define _INPUTS_H
#include <Arduino.h>
#include <M5Core2.h>
#include "Storage.h"
#define THROTTLE_PIN 35
#define YAW_PIN 36
#define PITCH_PIN 32
#define ROLL_PIN 33
#define MEDIAN_TOTAL 11
#define MEDIAN_POS MEDIAN_TOTAL/2
// For RC Deadband
#define RC_DEADBAND 5
/***
enum input_names {
THROTTLE = 0,
YAW,
PITCH,
ROLL
};
***/
// Enumerators - User Defined
enum rc {
ROLL,
PITCH,
THROTTLE,
YAW,
AUX1,
AUX2,
AUX3,
AUX4,
AUX5,
AUX6,
AUX7,
AUX8,
AUX9
};
class Inputs {
public:
bool invert_throttle = false;
bool invert_yaw = false;
bool invert_pitch = false;
bool invert_roll = false;
uint16_t throttle = 0;
uint16_t yaw = 0;
uint16_t pitch = 0;
uint16_t roll = 0;
int16_t trim_throttle = 0;
int16_t trim_yaw = 0;
int16_t trim_pitch = 0;
int16_t trim_roll = 0;
Inputs(void);
void begin(void);
void read(void);
private:
uint32_t last_voltage_read = 0;
uint16_t throttle_pool[MEDIAN_TOTAL];
uint16_t yaw_pool[MEDIAN_TOTAL];
uint16_t pitch_pool[MEDIAN_TOTAL];
uint16_t roll_pool[MEDIAN_TOTAL];
uint16_t tmp_median_store[MEDIAN_TOTAL];
uint16_t throttle_raw;
uint16_t yaw_raw;
uint16_t pitch_raw;
uint16_t roll_raw;
uint16_t median_get(uint8_t median_type);
uint16_t throttle_insert(uint16_t val);
uint16_t yaw_insert(uint16_t val);
uint16_t pitch_insert(uint16_t val);
uint16_t roll_insert(uint16_t val);
uint16_t pool_insert(uint8_t pool_type, uint16_t val);
};
extern Inputs inputs;
#endif

302
src/Screen.cpp

@ -0,0 +1,302 @@
// REF:
// https://github.com/m5stack/m5-docs/blob/master/docs/en/api/lcd.md
// https://github.com/Bodmer/TFT_eSPI/blob/master/TFT_eSPI.h
#include "Screen.h"
TFT_eSprite mainscreen_buffer = TFT_eSprite(&M5.Lcd);
Screen::Screen(void) {
}
void Screen::begin(void) {
M5.begin(true, true, true, true); //Init M5Core2. Initialize M5Core2
/*
* Power chip connected to gpio21, gpio22, I2C device
* Set battery charging voltage and current
* If used battery, please call this function in your project
*/
// Initial value
updated = true;
stickcalibration.begin();
M5.Lcd.fillScreen(BLACK);
mainscreen_buffer.createSprite(320, 240);
}
void Screen::update(void) {
M5.update();
// Update screen based on selected menu
if((uint32_t)(millis() - last_screen_update) > REFRESH_TIME) {
if(current_screen == SCREEN_MENU) {
menu();
} else if(current_screen == SCREEN_MENU_REBOOT) {
menu_reboot();
} else if(current_screen == SCREEN_MENU_POWEROFF) {
menu_poweroff();
} else if(current_screen == SCREEN_MENU_BLUETOOTH) {
blegamepad.show(&mainscreen_buffer);
} else if(current_screen == SCREEN_MENU_STICK_CALIBRATION) {
stickcalibration.show(&mainscreen_buffer);
} else if(current_screen == SCREEN_MENU_STICK_POSITIONS) {
stickpositions.main(&mainscreen_buffer);
}
last_screen_update = millis();
}
/***
if(M5.BtnA.wasPressed()) {
M5.Lcd.println("Button A Pressed");
}
if(M5.BtnB.wasPressed()) {
M5.Lcd.println("Button B Pressed");
}
if(M5.BtnC.wasPressed()) {
M5.Lcd.println("Button C Pressed");
}
***/
}
void Screen::menu_bluetooth(void) {
}
void Screen::intro(void) {
// M5.Lcd.drawRect(100, 100, 50, 50, BLUE);
//
//
// vTaskDelay(INTRO_TIME);
current_screen = SCREEN_MENU;
// M5.Lcd.fillScreen(BLACK);
}
// Main Menu
void Screen::menu(void) {
static uint8_t current_menu = MENU_BLUETOOTH;
static uint8_t menu_start_position = 0;
static uint8_t menu_max_position = MENU_MAX_ITEMS;
static String menu_items_name[] = {
"Bluetooth ",
"Stick Calibration ",
"Stick Positions ",
"Trims ",
"WiFi ",
"RX Binding ",
"RF Settings ",
"RF Scanners ",
"Save Settings ",
"Reset ",
"Reboot ",
"Power Off "
};
// Serial.println((int)sizeof(menu_items_name)/sizeof(menu_items_name[0]));
uint8_t menu_total = (int)sizeof(menu_items_name)/sizeof(menu_items_name[0]);
uint8_t menu_count = 0;
uint8_t menu_pointer = 0;
if(updated) {
// List all menus
mainscreen_buffer.fillRect(0, 0, 320, 240, BLACK);
mainscreen_buffer.setTextColor(WHITE, BLACK);
mainscreen_buffer.setTextSize(2);
// for(uint8_t y = 0; y < menu_total * MENU_HEIGHT; y+=MENU_HEIGHT) {
menu_pointer = menu_start_position;
for(uint8_t y = menu_start_position; y < menu_max_position * MENU_HEIGHT; y+=MENU_HEIGHT) {
if((current_menu - menu_start_position) == menu_count) {
mainscreen_buffer.setTextColor(BLACK, YELLOW);
// Trigger voice
// peak.speak_words = current_menu;
// speak.speak_now = true;
//// M5.Speaker.tone(7000, 200);
} else {
mainscreen_buffer.setTextColor(WHITE, BLACK);
}
mainscreen_buffer.setCursor(10, y);
mainscreen_buffer.print(menu_items_name[menu_pointer]);
menu_count++;
menu_pointer++;
}
// Draw the touch button lines
mainscreen_buffer.drawFastHLine(50, 239, 20, TFT_WHITE);
mainscreen_buffer.drawFastHLine(150, 239, 20, TFT_WHITE);
mainscreen_buffer.drawFastHLine(250, 239, 20, TFT_WHITE);
mainscreen_buffer.pushSprite(0,0);
updated = false;
}
// Key Input Handler
if(M5.BtnC.wasPressed()) {
current_menu++;
if(current_menu == menu_total) current_menu--;
if(current_menu >= menu_max_position) {
if(menu_start_position + MENU_MAX_ITEMS < menu_total) {
menu_start_position++;
}
}
updated = true;
}
if(M5.BtnA.wasPressed()) {
current_menu--;
if(current_menu == 255) current_menu = 0;
if(menu_start_position > 0) {
if(current_menu == (menu_start_position - 1)) {
menu_start_position--;
}
}
updated = true;
}
// Handling Button B
if(M5.BtnB.wasPressed()) {
if(current_menu == MENU_REBOOT) {
current_screen = SCREEN_MENU_REBOOT;
} else if(current_menu == MENU_POWER_OFF) {
current_screen = SCREEN_MENU_POWEROFF;
} else if(current_menu == MENU_BLUETOOTH) {
current_screen = SCREEN_MENU_BLUETOOTH;
} else if(current_menu == MENU_STICK_CALIBRATION) {
current_screen = SCREEN_MENU_STICK_CALIBRATION;
stickcalibration.updated = true;
} else if(current_menu == MENU_STICK_POSITIONS) {
current_screen = SCREEN_MENU_STICK_POSITIONS;
}
updated = true;
}
}
// Reboot Confirmation
void Screen::menu_reboot(void) {
static uint8_t current_menu_reboot_selection = SELECTION_YES;
static uint8_t menu_reboot_selection_total = 2;
if(updated) {
// Draw box
mainscreen_buffer.drawRoundRect(50, 50, 220, 140, 10, DIALOGBOX_FOREGROUND);
mainscreen_buffer.fillRoundRect(51, 51, 218, 138, 10, DIALOGBOX_BACKGROUND);
// Show Text
mainscreen_buffer.setTextColor(DIALOGBOX_FOREGROUND, DIALOGBOX_BACKGROUND);
mainscreen_buffer.setCursor(68, 110);
mainscreen_buffer.print("Reboot Now? ");
mainscreen_buffer.setTextColor(GREEN);
if(current_menu_reboot_selection == SELECTION_YES) {
mainscreen_buffer.print("YES");
} else {
mainscreen_buffer.print("NO");
}
mainscreen_buffer.pushSprite(0,0);
updated = false;
}
// Key Input Handler
if(M5.BtnC.wasPressed() || M5.BtnA.wasPressed()) { // Either up or down is the same because only two options
current_menu_reboot_selection++;
if(current_menu_reboot_selection == menu_reboot_selection_total) current_menu_reboot_selection = 0;
updated = true;
}
if(M5.BtnB.wasPressed()) {
if(current_menu_reboot_selection == SELECTION_YES) {
// Trigger voice
// speak.speak_miscs = PHRASE001;
// speak.speak_now_other = true;
///// M5.Speaker.tone(6000, 200);
vTaskDelay(1500);
ESP.restart();
} else if(current_menu_reboot_selection == SELECTION_NO) {
// Return back to main menu
current_screen = SCREEN_MENU;
}
updated = true;
}
}
// Power Off Confirmation
void Screen::menu_poweroff(void) {
static uint8_t current_menu_poweroff_selection = SELECTION_YES;
static uint8_t menu_poweroff_selection_total = 2;
if(updated) {
// Draw box
mainscreen_buffer.drawRoundRect(50, 50, 220, 140, 10, DIALOGBOX_FOREGROUND);
mainscreen_buffer.fillRoundRect(51, 51, 218, 138, 10, DIALOGBOX_BACKGROUND);
// Show Text
mainscreen_buffer.setTextColor(DIALOGBOX_FOREGROUND, DIALOGBOX_BACKGROUND);
mainscreen_buffer.setCursor(68, 110);
mainscreen_buffer.print("Power Off? ");
mainscreen_buffer.setTextColor(GREEN);
if(current_menu_poweroff_selection == SELECTION_YES) {
mainscreen_buffer.print("YES");
} else {
mainscreen_buffer.print("NO");
}
mainscreen_buffer.pushSprite(0,0);
updated = false;
}
// Key Input Handler
if(M5.BtnC.wasPressed() || M5.BtnA.wasPressed()) { // Either up or down is the same because only two options
current_menu_poweroff_selection++;
if(current_menu_poweroff_selection == menu_poweroff_selection_total) current_menu_poweroff_selection = 0;
updated = true;
}
if(M5.BtnB.wasPressed()) {
if(current_menu_poweroff_selection == SELECTION_YES) {
// Trigger voice
// speak.speak_miscs = PHRASE000;
// speak.speak_now_other = true;
/***
M5.Speaker.tone(6000, 200);
M5.Speaker.tone(11000, 200);
***/
vTaskDelay(1200);
M5.shutdown();
} else if(current_menu_poweroff_selection == SELECTION_NO) {
// Return back to main menu
current_screen = SCREEN_MENU;
}
updated = true;
}
}
Screen screen;

70
src/Screen.h

@ -0,0 +1,70 @@
#ifndef _SCREEN_H
#define _SCREEN_H
#include <Arduino.h>
#include <M5Core2.h>
#include "BLEGamepad.h"
#include "StickCalibration.h"
#include "StickPositions.h"
#define REFRESH_TIME 1
#define INTRO_TIME 100
#define MENU_HEIGHT 20
#define MENU_MAX_ITEMS 12
#define DIALOGBOX_FOREGROUND TFT_WHITE
#define DIALOGBOX_BACKGROUND TFT_NAVY
enum screen_names {
SCREEN_INTRO,
SCREEN_MENU,
SCREEN_MENU_REBOOT,
SCREEN_MENU_POWEROFF,
SCREEN_MENU_BLUETOOTH,
SCREEN_MENU_STICK_CALIBRATION,
SCREEN_MENU_STICK_POSITIONS,
SCREEN_MENU_RC_CONTROLLER
};
enum menu_items {
MENU_BLUETOOTH,
MENU_STICK_CALIBRATION,
MENU_STICK_POSITIONS,
MENU_TRIMS,
MENU_WIFI,
MENU_RX_BINDING,
MENU_RF_SETTINGS,
MENU_RF_SCANNERS,
MENU_SAVE_SETTINGS,
MENU_RESET,
MENU_REBOOT,
MENU_POWER_OFF
};
enum selection_choice_yesno {
SELECTION_YES,
SELECTION_NO
};
class Screen {
public:
uint8_t current_screen = SCREEN_INTRO;
bool updated = false;
Screen(void);
void begin(void);
void intro(void);
void menu(void);
void menu_reboot(void);
void menu_poweroff(void);
void menu_bluetooth(void);
void update(void);
private:
uint32_t last_screen_update = 0;
};
extern Screen screen;
#endif

228
src/StickCalibration.cpp

@ -0,0 +1,228 @@
#include "StickCalibration.h"
StickCalibration::StickCalibration(void) {
updated = true;
}
void StickCalibration::begin(void) {
updated = true;
// Loading the default values...
String filename;
String tmp_str;
filename = "/THROTTLE_MAX";
tmp_str = storage.readFile(LITTLEFS, filename);
throttle_max = tmp_str.toInt();
filename = "/THROTTLE_MIN";
tmp_str = storage.readFile(LITTLEFS, filename);
throttle_min = tmp_str.toInt();
filename = "/YAW_MAX";
tmp_str = storage.readFile(LITTLEFS, filename);
yaw_max = tmp_str.toInt();
filename = "/YAW_MIN";
tmp_str = storage.readFile(LITTLEFS, filename);
yaw_min = tmp_str.toInt();
filename = "/PITCH_MAX";
tmp_str = storage.readFile(LITTLEFS, filename);
pitch_max = tmp_str.toInt();
filename = "/PITCH_MIN";
tmp_str = storage.readFile(LITTLEFS, filename);
pitch_min = tmp_str.toInt();
filename = "/ROLL_MAX";
tmp_str = storage.readFile(LITTLEFS, filename);
roll_max = tmp_str.toInt();
filename = "/ROLL_MIN";
tmp_str = storage.readFile(LITTLEFS, filename);
roll_min = tmp_str.toInt();
}
void StickCalibration::show(TFT_eSprite *m) {
if((uint32_t) (millis() - last_updated) > 50) {
updated = true;
}
if(inputs.throttle > throttle_max) throttle_max = inputs.throttle;
if(inputs.throttle < throttle_min) throttle_min = inputs.throttle;
if(inputs.yaw > yaw_max) yaw_max = inputs.yaw;
if(inputs.yaw < yaw_min) yaw_min = inputs.yaw;
if(inputs.pitch > pitch_max) pitch_max = inputs.pitch;
if(inputs.pitch < pitch_min) pitch_min = inputs.pitch;
if(inputs.roll > roll_max) roll_max = inputs.roll;
if(inputs.roll < roll_min) roll_min = inputs.roll;
if(updated) {
m->fillRect(0, 0, 320, 240, BLACK);
m->setTextColor(WHITE, BLACK);
m->setTextSize(2);
m->setCursor(0,0);
m->print(String(inputs.throttle));
m->setCursor(0,16);
m->print(String(inputs.yaw));
m->setCursor(0,32);
m->print(String(inputs.pitch));
m->setCursor(0,48);
m->print(String(inputs.roll));
m->setCursor(0,160);
m->setCursor(180,0);
m->print(String(throttle_min) + " " + String(throttle_max));
m->setCursor(180,16);
m->print(String(yaw_min) + " " + String(yaw_max));
m->setCursor(180,32);
m->print(String(pitch_min) + " " + String(pitch_max));
m->setCursor(180,48);
m->print(String(roll_min) + " " + String(roll_max));
m->setCursor(180,160);
m->print("Keep rolling both sticks to get the minimum and maximum values.");
uint32_t last_millis = millis();
if((uint32_t)(last_millis - last_button_reset) > 500) {
m->drawRect(60, 60, 60, 60, TFT_WHITE); // Button RESET
m->fillRect(61, 61, 58, 58, TFT_WHITE); // Button RESET
} else {
m->drawRect(60, 60, 60, 60, TFT_WHITE); // Button RESET
}
m->setTextColor(WHITE, BLACK);
m->setCursor(60, 125);
m->print("RESET");
if((uint32_t)(last_millis - last_button_save) > 500) {
m->drawRect(180, 60, 60, 60, TFT_WHITE); // Button SAVE
m->fillRect(181, 61, 58, 58, TFT_WHITE); // Button SAVE
} else {
m->drawRect(180, 60, 60, 60, TFT_WHITE); // Button SAVE
}
m->setTextColor(WHITE, BLACK);
m->setCursor(180, 125);
m->print("SAVE");
m->pushSprite(0,0);
updated = false;
last_updated = millis();
}
// Key Input Handler
if(M5.BtnC.wasPressed()) {
screen.current_screen = SCREEN_MENU;
screen.updated = true;
}
// Handling Inputs
Event &e = M5.Buttons.event;
coordinate = M5.Touch.getPressPoint();
if((uint32_t)(millis() - touch_time) > 100) {
if(e & (E_TOUCH)) {
Serial.printf("E_TOUCH X:%d, Y:%d\r\n", e.to.x, e.to.y);
if(e.to.x > 60 && e.to.x < 120 && e.to.y > 60 && e.to.y < 120) {
last_button_reset = millis();
Serial.printf("RESET BUTTON\n");
reset_values();
} else if(e.to.x > 180 && e.to.x < 240 && e.to.y > 60 && e.to.y < 120) {
last_button_save = millis();
Serial.printf("SAVE BUTTON\n");
save_values();
}
touch_time = millis();
}
}
}
void StickCalibration::reset_values(void) {
throttle_max = 0;
throttle_min = 4096;
yaw_max = 0;
yaw_min = 4096;
pitch_max = 0;
pitch_min = 4096;
roll_max = 0;
roll_min = 4096;
}
void StickCalibration::save_values(void) {
/*
throttle_max = 0;
throttle_min = 4096;
yaw_max = 0;
yaw_min = 4096;
pitch_max = 0;
pitch_min = 4096;
roll_max = 0;
roll_min = 4096;
*/
String filename;
filename = "/THROTTLE_MAX";
storage.writeFile(LITTLEFS, filename, String(throttle_max));
filename = "/THROTTLE_MIN";
storage.writeFile(LITTLEFS, filename, String(throttle_min));
filename = "/YAW_MAX";
storage.writeFile(LITTLEFS, filename, String(yaw_max));
filename = "/YAW_MIN";
storage.writeFile(LITTLEFS, filename, String(yaw_min));
filename = "/PITCH_MAX";
storage.writeFile(LITTLEFS, filename, String(pitch_max));
filename = "/PITCH_MIN";
storage.writeFile(LITTLEFS, filename, String(pitch_min));
filename = "/ROLL_MAX";
storage.writeFile(LITTLEFS, filename, String(roll_max));
filename = "/ROLL_MIN";
storage.writeFile(LITTLEFS, filename, String(roll_min));
}
void StickCalibration::mydebug(TFT_eSprite *m) {
if((uint32_t) (millis() - last_updated) > 50) {
updated = true;
}
if(updated) {
m->fillRect(0, 0, 320, 240, BLACK);
m->setTextColor(WHITE, BLACK);
m->setTextSize(2);
m->setCursor(0,0);
m->print(String(inputs.throttle));
m->setCursor(0,16);
m->print(String(inputs.yaw));
m->setCursor(0,32);
m->print(String(inputs.pitch));
m->setCursor(0,48);
m->print(String(inputs.roll));
m->pushSprite(0,0);
updated = false;
last_updated = millis();
}
// Key Input Handler
if(M5.BtnC.wasPressed()) {
screen.current_screen = SCREEN_MENU;
screen.updated = true;
}
}
StickCalibration stickcalibration;

41
src/StickCalibration.h

@ -0,0 +1,41 @@
#ifndef _STICK_CALIBRATION_H
#define _STICK_CALIBRATION_H
#include <Arduino.h>
#include <M5Core2.h>
#include "Inputs.h"
#include "Screen.h"
#include "Storage.h"
class StickCalibration {
public:
StickCalibration(void);
void begin(void);
void mydebug(TFT_eSprite *m);
void show(TFT_eSprite *m);
void reset_values(void);
void save_values(void);
bool updated = false;
bool last_button_reset = 0;
bool last_button_save = 0;
uint16_t throttle_max = 0;
uint16_t throttle_min = 4096;
uint16_t yaw_max = 0;
uint16_t yaw_min = 4096;
uint16_t pitch_max = 0;
uint16_t pitch_min = 4096;
uint16_t roll_max = 0;
uint16_t roll_min = 4096;
private:
uint32_t last_updated = 0;
uint32_t touch_time = 0;
TouchPoint_t coordinate;
};
extern StickCalibration stickcalibration;
#endif

40
src/StickPositions.cpp

@ -0,0 +1,40 @@
#include "StickPositions.h"
StickPositions::StickPositions(void) {
updated = true;
}
void StickPositions::begin(void) {
updated = true;
}
void StickPositions::main(TFT_eSprite *m) {
if((uint32_t) (millis() - last_updated) > 50) {
updated = true;
}
if(updated) {
m->fillRect(0, 0, 320, 240, BLACK);
m->setTextColor(WHITE, BLACK);
m->setTextSize(2);
m->setCursor(0,0);
m->print("Stick Positions");
// Update screen
m->pushSprite(0,0);
updated = false;
last_updated = millis();
}
// Key Input Handler
if(M5.BtnC.wasPressed()) {
screen.current_screen = SCREEN_MENU;
screen.updated = true;
}
}
StickPositions stickpositions;

21
src/StickPositions.h

@ -0,0 +1,21 @@
#ifndef _STICK_POSITIONS_H
#define _STICK_POSITIONS_H
#include <Arduino.h>
#include <M5Core2.h>
#include "Inputs.h"
#include "Screen.h"
class StickPositions {
public:
StickPositions(void);
void begin(void);
void main(TFT_eSprite *m);
bool updated = false;
private:
uint32_t last_updated = 0;
};
extern StickPositions stickpositions;
#endif

220
src/Storage.cpp

@ -0,0 +1,220 @@
#include "Storage.h"
// REF:
// https://github.com/pbecchi/ESP32-Sprinkler/blob/master/Eeprom_ESP.cpp
Storage::Storage(void) {
// Just some initialization
}
bool Storage::format(void) {
if(LITTLEFS.format()) {
return true;
} else {
return false;
}
}
bool Storage::exists(String path) {
if(LITTLEFS.exists(path)) {
return true;
} else {
return false;
}
}
bool Storage::begin(void) {
if(!LITTLEFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
Serial.println("LITTLEFS mount failed");
return false;
}
// Demo and checking only
Storage::listDir(LITTLEFS, "/", 0);
if(Storage::exists("/MSCoreTX")) {
Serial.println(F("MSCoreTX FOUND"));
} else {
Serial.println(F("MSCoreTX NOT FOUND - Formatting now"));
Storage::format();
Storage::writeFile(LITTLEFS, "/MSCoreTX", "");
load_defaults = true;
}
// Check Total storage
Serial.print(F("Storage size: "));
Serial.print(LITTLEFS.totalBytes());
Serial.println(F(" Bytes"));
Serial.print(F("Storage used: " ));
Serial.print(LITTLEFS.usedBytes());
Serial.println(F(" Bytes"));
return true;
}
void Storage::listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\r\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println(F("- failed to open directory"));
return;
}
if(!root.isDirectory()){
Serial.println(F(" - not a directory"));
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(F(" DIR : "));
#ifdef CONFIG_LITTLEFS_FOR_IDF_3_2
Serial.println(file.name());
#else
Serial.print(file.name());
time_t t= file.getLastWrite();
struct tm * tmstruct = localtime(&t);
Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec);
#endif
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(F(" FILE: "));
Serial.print(file.name());
Serial.print(F(" SIZE: "));
#ifdef CONFIG_LITTLEFS_FOR_IDF_3_2
Serial.println(file.size());
#else
Serial.print(file.size());
time_t t= file.getLastWrite();
struct tm * tmstruct = localtime(&t);
Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec);
#endif
}
file = root.openNextFile();
}
}
String Storage::readFile(fs::FS &fs, String path){
// Serial.printf("Reading file: %s\r\n", path);
String return_str = "";
File file = fs.open(path, "r");
if(!file || file.isDirectory()){
//// Serial.println(F("- failed to open file for reading"));
return "";
}
while(file.available()){
return_str = return_str + String((char)file.read());
}
file.close();
return return_str;
}
void Storage::appendFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Appending to file: %s\r\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file){
Serial.println("- failed to open file for appending");
return;
}
if(file.print(message)){
Serial.println("- message appended");
} else {
Serial.println("- append failed");
}
file.close();
}
void Storage::deleteFile(fs::FS &fs, const char * path){
Serial.printf("Deleting file: %s\r\n", path);
if(fs.remove(path)){
Serial.println("- file deleted");
} else {
Serial.println("- delete failed");
}
}
void Storage::writeFile(fs::FS &fs, String path, String message){
File file = fs.open(path, "w+");
if(!file){
return;
}
file.println(message);
file.close();
}
//void Storage::write_block(uint8_t * data, const char * path, uint32_t len){
void Storage::write_block(const void * data, const char * path, uint32_t len){
uint32_t i;
fs::FS &fs = LITTLEFS;
// Serial.println("write block 1");
// Serial.print("FILE: ");
// Serial.println(path);
// Serial.println(ESP.getFreeHeap());
File file = fs.open(path, FILE_WRITE);
// Serial.println("write block 2");
if(!file) {
Serial.println("write_block append failed");
return;
}
// Serial.println("write block 3");
/**
for(i = 0; i < len; i++){
// EEPROM.write(address+i, data[i]);
byte b = *((unsigned char*) data + i);
Serial.print("Wrote: "); Serial.println(b);
}
**/
file.write((byte *)&data, sizeof(len));
// Serial.println("write block 4");
file.close();
}
void Storage::read_block(void * data, const char * path, uint32_t len){
uint32_t i;
fs::FS &fs = LITTLEFS;
File file = fs.open(path);
if(!file) {
Serial.println("- File not exists.");
return;
}
// for(i = 0; i < len; i++){
/**
i = 0;
while(file.available()) {
// data[i] = EEPROM.read(address+i);
// data[i++] = file.read();
// uint8_t b = file.read()
*((char *)data + i) = file.read();
Serial.print("read_block: "); Serial.println(*((char *)data + i));
i++;
}
**/
file.read((byte *)&data, sizeof(len));
file.close();
}
Storage storage;

42
src/Storage.h

@ -0,0 +1,42 @@
#ifndef _Storage_H
#define _Storage_H
#include <Arduino.h>
#include <LITTLEFS.h>
#ifndef CONFIG_LITTLEFS_FOR_IDF_3_2
#include <time.h>
#endif
/* You only need to format LITTLEFS the first time you run a
test or else use the LITTLEFS plugin to create a partition
https://github.com/lorol/arduino-esp32littlefs-plugin */
#define FORMAT_LITTLEFS_IF_FAILED true
class Storage {
public:
Storage(void);
bool begin(void);
void listDir(fs::FS &fs, const char * dirname, uint8_t levels);
String readFile(fs::FS &fs, String path);
void appendFile(fs::FS &fs, const char * path, const char * message);
void deleteFile(fs::FS &fs, const char * path);
void writeFile(fs::FS &fs, String path, String message);
// Emulate EEPROM write to LITTLEFS
// void write_block(uint8_t * data, const char * path, uint32_t len);
void write_block(const void * data, const char * path, uint32_t len);
void read_block(void * data, const char * path, uint32_t len);
bool format(void);
bool exists(String path);
bool load_defaults = false; // Default false. Only during reset this will auto set to true
private:
};
extern Storage storage;
#endif

225
src/Web.cpp

@ -0,0 +1,225 @@
#include "Web.h"
WebServer server(PORT_HTTP);
bool update_status = false;
bool opened = false;
File root;
String header_html_str = "<html><meta charset=\"UTF-8\"><title>M5Core2TX</title><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><body><pre>";
WEB::WEB(void){
}
void WEB::begin(void) {
// Allow CORS
server.enableCORS();
// Index
server.on("/", HTTP_GET, []() {
// File html_handler = LITTLEFS.open("/index.html.gz", FILE_READ);
// server.streamFile(html_handler, "text/html");
// html_handler.close();
server.send(200, "text/html", "MSCoreTX Version 0.1");
server.sendHeader("Connection", "close");
});
// Multiple configuration
server.on("/set", HTTP_GET, []() {
bool update_channel = false;
uint8_t channel_raw = 0;
bool update_sbus_type = false;
bool sbus_slow = false;
String msg = "";
// Server parameters
for(uint8_t i = 0; i < server.args(); i++) {
String value = server.arg(i);
if(server.argName(i) == "channel") {
channel_raw = value.toInt();
update_channel = true;
} else if(server.argName(i) == "sbus") {
sbus_slow = true;
update_sbus_type = true;
} else if(server.argName(i) == "sbusfast") {
sbus_slow = false;
update_sbus_type = true;
}
}
/****
if(update_channel) {
// Making sure is channel 1 ~ 125.
if(channel_raw > 0 && channel_raw < 126) {
String filename = "/NRF_CHANNEL";
msg = "CHANNEL SET: " + String(channel_raw);
storage.writeFile(LITTLEFS, filename, String(channel_raw));
//// File file = LittleFS.open(filename, "w+");
//// file.println(channel_raw);
//// file.close();
rccontroller.channel = channel_raw;
tx.setChannel(channel_raw); // Instant Change
} else {
msg = "ERROR UPDATE CHANNEL";
}
}
****/
String index_html_str = header_html_str + msg;
server.sendHeader("Connection", "close");
server.send(200, "text/html", index_html_str);
});
// Getting configuration
server.on("/get", HTTP_GET, []() {
bool update_channel = false;
uint8_t channel_raw = 0;
String msg = "";
// Server parameters
for(uint8_t i = 0; i < server.args(); i++) {
String value = server.arg(i);
if(server.argName(i) == "channel") {
String filename = "/NRF_CHANNEL";
msg = storage.readFile(LITTLEFS, filename);
msg = "Channel (From FILE): " + msg;
}
}
String index_html_str = header_html_str + msg;
server.sendHeader("Connection", "close");
server.send(200, "text/html", index_html_str);
});
// Update
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK. Device is rebooting");
if(Update.hasError()) {
update_status = false;
// web.set_update_status(false);
} else {
delay(1000);
ESP.restart();
}
}, []() {
HTTPUpload& upload = server.upload();
if(!update_status)
update_status = true;
if(upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
// Start with maximum available size
if(!Update.begin(UPDATE_SIZE_UNKNOWN))
Update.printError(Serial);
} else if(upload.status == UPLOAD_FILE_WRITE) {
// Flashing firmware to ESP
if(
Update.write(upload.buf, upload.currentSize) != upload.currentSize
)
Update.printError(Serial);
} else if(upload.status == UPLOAD_FILE_END) {
if(Update.end(true))
// True to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
else
Update.printError(Serial);
}
});
// Reset
server.on("/reset", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", "1");
storage.deleteFile(LITTLEFS, "/MSCoreTX");
delay(1000);
ESP.restart();
});
// Reboot
server.on("/reboot", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", "1");
delay(1000);
ESP.restart();
});
// Remove files
server.on("/erase", HTTP_GET, []() {
String filename;
uint8_t detected = 0;
for(uint8_t i = 0; i < server.args(); i++) {
String value = server.arg(i);
if(server.argName(i) == "filename") {
filename = value;
detected++;
}
}
server.sendHeader("Connection", "close");
if(detected) {
LITTLEFS.remove("/" + filename);
server.send(200, "text/plain", "REMOVED - [" + filename + "]");
} else {
server.send(200, "text/plain", "Nothing remove.");
}
});
// TODO: create a way to upload files to the ESP32 and also handling it
// REF: https://techtutorialsx.com/2019/03/04/esp32-arduino-serving-bootstrap/
// SPIFFS File upload
server.on("/webupload", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", "webupload Done");
}, []() {
HTTPUpload& upload = server.upload();
if(opened == false) {
opened = true;
root = LITTLEFS.open((String("/") + upload.filename).c_str(), FILE_WRITE);
if(!root) {
Serial.printf("Failed to open file for writing. [%s]\n", upload.filename.c_str());
return;
} else {
Serial.println("Webupload file start.");
}
}
if(upload.status == UPLOAD_FILE_START) {
Serial.printf("webupload: %s\n", upload.filename.c_str());
} else if(upload.status == UPLOAD_FILE_WRITE) {
// Writting to SPIFFS
if(root.write(upload.buf, upload.currentSize) != upload.currentSize) {
Serial.printf("Failed to write. [%s]\n", upload.filename.c_str());
return;
}
} else if(upload.status == UPLOAD_FILE_END) {
root.close();
opened = false;
Serial.printf("webupload done. [%s]\n", upload.filename.c_str());
#ifdef DEBUG_MODE
Serial.printf("SPIFFS Files:\n");
// list_files();
storage.listDir(LITTLEFS, "/", 0);
#endif
}
});
server.begin();
}
void WEB::handler(void) {
server.handleClient();
}
WEB web;

23
src/Web.h

@ -0,0 +1,23 @@
#ifndef WEB_H_
#define WEB_H_
#include <Arduino.h>
#include <LITTLEFS.h>
#include <WebServer.h>
#include <Update.h>
#include "Storage.h"
#define PORT_HTTP 80
class WEB {
public:
WEB(void);
void begin(void);
void handler(void);
private:
};
extern WEB web;
#endif

207
src/main.cpp

@ -0,0 +1,207 @@
#include <Arduino.h>
#include "main.h"
// Screen screen;
// Speak speak;
// Storage storage;
// CustomWiFi customwifi;
void setup() {
Serial.begin(115200);
// Just a debug and also trigger the PSRAM prevent reboot
Serial.printf("Total heap: %d\n", ESP.getHeapSize());
Serial.printf("Free heap: %d\n", ESP.getFreeHeap());
Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize());
Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram());
delay(100);
screen.begin();
M5.Spk.begin();
// M5.Spk.setVolume(100);
// M5.Spk.DingDong();
// speak.begin();
// speak.dingdong();
storage.begin();
customwifi.begin();
inputs.begin();
stickcalibration.begin();
// tx.begin();
web.begin();
// speak.welcome();
// Task Creation
xTaskCreatePinnedToCore(
taskCharger,
"TaskCharger", // Name of the process
4096, // This stack size can be checked & adjusted by reading the Stack Highwater
NULL,
4, // Priority
NULL,
CPU_1
);
xTaskCreatePinnedToCore(
taskScreen,
"TaskScreen", // Name of the process
4096, // This stack size can be checked & adjusted by reading the Stack Highwater
NULL,
4, // Priority
NULL,
CPU_1
);
xTaskCreatePinnedToCore(
taskWeb,
"TaskWeb", // Name of the process
4096, // This stack size can be checked & adjusted by reading the Stack Highwater
NULL,
4, // Priority
NULL,
CPU_0
);
/****
xTaskCreatePinnedToCore(
taskSpeak,
"TaskSpeak", // Name of the process
8192, // This stack size can be checked & adjusted by reading the Stack Highwater
NULL,
5, // Priority
NULL,
CPU_1
);
****/
xTaskCreatePinnedToCore(
taskInput,
"TaskInput", // Name of the process
8192, // This stack size can be checked & adjusted by reading the Stack Highwater
NULL,
4, // Priority
NULL,
CPU_0
);
xTaskCreatePinnedToCore(
taskBLE,
"TaskBLE", // Name of the process
8192, // This stack size can be checked & adjusted by reading the Stack Highwater
NULL,
4, // Priority
NULL,
CPU_0
);
}
void loop() {
}
void taskScreen(void *pvParameters) {
(void) pvParameters;
screen.intro();
for(;;) {
screen.update();
vTaskDelay(2);
}
}
void taskCharger(void *pvParameters) {
(void) pvParameters;
bool charging = false;
bool high_current_charging = false;
uint32_t last_charging_time = 0;
for(;;) {
if(M5.Axp.isCharging()) {
// Just flipped...
if(!charging) {
charging = true;
last_charging_time = millis();
} else {
// Only after 5 seconds then put to 1A...
if(!high_current_charging) {
if((uint32_t)(millis() - last_charging_time) > 5000) {
high_current_charging = true;
M5.Axp.SetCHGCurrent(M5.Axp.kCHG_1000mA);
}
}
}
} else {
if(charging) {
// Setting back to 100mA
charging = false;
high_current_charging = false;
last_charging_time = millis();
M5.Axp.SetCHGCurrent(M5.Axp.kCHG_100mA);
}
}
vTaskDelay(1000);
}
}
/***
void taskSpeak(void *pvParameters) {
(void) pvParameters;
for(;;) {
if(speak.speak_now == true) {
speak.speak_menu(speak.speak_words);
speak.speak_now = false;
}
if(speak.speak_now_other == true) {
speak.speak_misc(speak.speak_miscs);
speak.speak_now_other = false;
}
if(speak.speak_now_trim == true) {
speak.speak_trims(speak.speak_trim_word);
speak.speak_now_trim = false;
}
vTaskDelay(100);
}
}
***/
void taskWeb(void *pvParameters) {
(void) pvParameters;
for(;;) {
web.handler();
vTaskDelay(100);
}
}
void taskInput(void *pvParameters) {
(void) pvParameters;
for(;;) {
inputs.read();
vTaskDelay(10);
}
}
void taskBLE(void *pvParameters) {
(void) pvParameters;
for(;;) {
ble.update();
vTaskDelay(5);
}
}

30
src/main.h

@ -0,0 +1,30 @@
#ifndef MAIN_H
#define MAIN_H
// CPU NAMES
#define CPU_1 1
#define CPU_0 0
// VERSION
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define VERSION_BUILD 9
#include <M5Core2.h>
#include "Inputs.h"
#include "BLE.h"
// #include "Speak.h"
#include "Screen.h"
#include "Storage.h"
#include "CustomWiFi.h"
#include "Web.h"
// Task Handler
void taskScreen(void *pvParameters);
// void taskSpeak(void *pvParameters);
void taskWeb(void *pvParameters);
void taskInput(void *pvParameters);
void taskBLE(void *pvParameters);
void taskCharger(void *pvParameters);
#endif

11
test/README

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
Loading…
Cancel
Save