Skip to content

Commit

Permalink
NanoFOC/ESP32-S3 support (#117)
Browse files Browse the repository at this point in the history
- New PlatformIO env (`nanofoc`) for the NanoFOC hardware
  - Huge thanks to @lgc00 for the initial NanoFOC configuration and prototyping!
  - Uses `HWCDC` rather than `UartStream` as the serial `Stream` interface (ESP32-S3 specific change)
  - Support for MAQ430 magnetic encoder sensor
  - Motor-specific PID tuning constants moved to separate header files

---------

Co-authored-by: lgc00 <lgcasd1+1@gmail.com>
  • Loading branch information
scottbez1 and lgc00 committed May 30, 2023
1 parent 4a1d2be commit 9903ac6
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 29 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,17 @@ Latest auto-generated (untested and likely broken!) artifacts⚠️:
⚠️ For tested/stable/recommended artifacts, use a [release](https://github.com/scottbez1/smartknob/releases) instead.


## SmartKnob Mini
Planned for the future.
## NanoFOC (3rd party)
If you're looking to tinker with FOC/haptic feedback, but don't want to build a full SmartKnob View yourself, I can
recommend the NanoFOC DevKit++, an [open-source design](https://github.com/katbinaris/nanofoc_devkit) made and
[sold](https://store.binaris.io/products/nanofoc-devkit) by a member of the SmartKnob community! It's super compact
and is a great testbed or core for building your own BLDC-based haptic input device.

![Image of the NanoFOC PCB](https://cdn.shopify.com/s/files/1/0729/7433/6335/products/IMG_20230418_120554-01.jpg?width=416)


The NanoFOC uses an ESP32-S3, and the SmartKnob firmware works on it out of the box; just select the `nanofoc`
environment in PlatformIO rather than the `view` environment when uploading.

# Frequently Asked Questions (FAQ)

Expand Down
4 changes: 4 additions & 0 deletions firmware/src/interface_task.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ class InterfaceTask : public Task<InterfaceTask>, public Logger {
void run();

private:
#ifdef CONFIG_IDF_TARGET_ESP32S3
HWCDC stream_;
#else
UartStream stream_;
#endif
MotorTask& motor_task_;
DisplayTask* display_task_;
char buf_[64];
Expand Down
13 changes: 13 additions & 0 deletions firmware/src/maq430_sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#include <sensors/MagneticSensorSPI.h>

/** Configured fro 12bit MA710 and MAQ430 magnetic sensor over SPI interface*/
MagneticSensorSPIConfig_s MAQ430_SPI = {
.spi_mode = SPI_MODE3,
.clock_speed = 1000000,
.bit_resolution = 12,
.angle_register = 0x0000,
.data_start_bit = 15,
.command_rw_bit = 0, // not required
.command_parity_bit = 17 // parity not implemented
};
42 changes: 27 additions & 15 deletions firmware/src/motor_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
#include "motor_task.h"
#if SENSOR_MT6701
#include "mt6701_sensor.h"
#endif
#if SENSOR_TLV
#elif SENSOR_TLV
#include "tlv_sensor.h"
#elif SENSOR_MAQ430
#include "maq430_sensor.h"
#endif
#include "util.h"

#include "motors/motor_config.h"
#include "util.h"

// ####
// Hardware-specific motor calibration constants.
Expand Down Expand Up @@ -41,6 +43,8 @@ MotorTask::~MotorTask() {}
TlvSensor encoder = TlvSensor();
#elif SENSOR_MT6701
MT6701Sensor encoder = MT6701Sensor();
#elif SENSOR_MAQ430
MagneticSensorSPI encoder = MagneticSensorSPI(MAQ430_SPI, PIN_MAQ_SS);
#endif

void MotorTask::run() {
Expand All @@ -50,26 +54,32 @@ void MotorTask::run() {

#if SENSOR_TLV
encoder.init(&Wire, false);
#endif

#if SENSOR_MT6701
#elif SENSOR_MT6701
encoder.init();
#elif SENSOR_MAQ430
SPIClass* spi = new SPIClass(HSPI);
spi->begin(PIN_MAQ_SCK, PIN_MAQ_MISO, PIN_MAQ_MOSI, PIN_MAQ_SS);
encoder.init(spi);
#endif

motor.linkDriver(&driver);

motor.controller = MotionControlType::torque;
motor.voltage_limit = 5;
motor.voltage_limit = FOC_VOLTAGE_LIMIT;
motor.velocity_limit = 10000;
motor.linkSensor(&encoder);

// Not actually using the velocity loop built into SimpleFOC; but I'm using those PID variables
// to run PID for torque (and SimpleFOC studio supports updating them easily over serial for tuning)
motor.PID_velocity.P = 4;
motor.PID_velocity.I = 0;
motor.PID_velocity.D = 0.04;
motor.PID_velocity.output_ramp = 10000;
motor.PID_velocity.limit = 10;
motor.PID_velocity.P = FOC_PID_P;
motor.PID_velocity.I = FOC_PID_I;
motor.PID_velocity.D = FOC_PID_D;
motor.PID_velocity.output_ramp = FOC_PID_OUTPUT_RAMP;
motor.PID_velocity.limit = FOC_PID_LIMIT;

#ifdef FOC_LPF
motor.LPF_angle.Tf = FOC_LPF;
#endif

motor.init();

Expand Down Expand Up @@ -371,14 +381,16 @@ void MotorTask::calibrate() {
log("NO, Direction=CCW");
motor.initFOC(0, Direction::CCW);
}
snprintf(buf_, sizeof(buf_), " (start was %.1f, end was %.1f)", start_sensor, end_sensor);
log(buf_);


// #### Determine pole-pairs
// Rotate 20 electrical revolutions and measure mechanical angle traveled, to calculate pole-pairs
uint8_t electrical_revolutions = 20;
snprintf(buf_, sizeof(buf_), "Going to measure %d electrical revolutions...", electrical_revolutions);
log(buf_);
motor.voltage_limit = 5;
motor.voltage_limit = FOC_VOLTAGE_LIMIT;
motor.move(a);
log("Going to electrical zero...");
float destination = a + _2PI;
Expand Down Expand Up @@ -428,7 +440,7 @@ void MotorTask::calibrate() {

// #### Determine mechanical offset to electrical zero
// Measure mechanical angle at every electrical zero for several revolutions
motor.voltage_limit = 5;
motor.voltage_limit = FOC_VOLTAGE_LIMIT;
motor.move(a);
float offset_x = 0;
float offset_y = 0;
Expand Down Expand Up @@ -478,7 +490,7 @@ void MotorTask::calibrate() {
// TODO: save to non-volatile storage
motor.pole_pairs = measured_pole_pairs;
motor.zero_electric_angle = avg_offset_angle + _3PI_2;
motor.voltage_limit = 5;
motor.voltage_limit = FOC_VOLTAGE_LIMIT;
motor.controller = MotionControlType::torque;

log("\n\nRESULTS:\n Update these constants at the top of " __FILE__);
Expand Down
12 changes: 12 additions & 0 deletions firmware/src/motors/mad2804.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

// Tuning parameters for the MAD2804 motor (orange stator).

#define FOC_PID_P 1
#define FOC_PID_I 0
#define FOC_PID_D 0.148
#define FOC_PID_OUTPUT_RAMP 5000
#define FOC_PID_LIMIT 3

#define FOC_VOLTAGE_LIMIT 3
#define FOC_LPF 0.0075
9 changes: 9 additions & 0 deletions firmware/src/motors/motor_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#if MOTOR_WANZHIDA_ONCE_TOP
#include "motors/wanzhida_once_top.h"
#elif MOTOR_MAD2804
#include "motors/mad2804.h"
#else
#error "No motor configuration specified!"
#endif
12 changes: 12 additions & 0 deletions firmware/src/motors/wanzhida_once_top.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

// Tuning parameters for the Wanzhida/Oncetop OT-EM3215D2450Y1R,
// sold by SparkFun (ROB-20441).

#define FOC_PID_P 4
#define FOC_PID_I 0
#define FOC_PID_D 0.04
#define FOC_PID_OUTPUT_RAMP 10000
#define FOC_PID_LIMIT 10

#define FOC_VOLTAGE_LIMIT 5
87 changes: 75 additions & 12 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,29 @@ monitor_flags =
--eol=CRLF
--echo
--filter=esp32_exception_decoder
upload_speed = 921600
lib_deps =
askuric/Simple FOC @ 2.2.0
infineon/TLV493D-Magnetic-Sensor @ 1.0.3
bxparks/AceButton @ 1.9.1
bakercp/PacketSerial @ 1.4.0
nanopb/Nanopb @ 0.4.6 ; Ideally this would reference the nanopb submodule, but that would require
; everyone to check out submodules to just compile, so we use the library
; registry for the runtime. The submodule is available for manually updating
; the pre-compiled (checked in) .pb.h/c files when proto files change, but is
; otherwise not used during application firmware compilation.
build_flags =
-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
-DMONITOR_SPEED=921600
-DMONITOR_SPEED=921600

[env:view]
extends = base_config
board = esp32doit-devkit-v1
lib_deps =
${base_config.lib_deps}
askuric/Simple FOC @ 2.2.0
bodmer/TFT_eSPI@2.4.25
fastled/FastLED @ 3.5.0
bogde/HX711 @ 0.7.5
adafruit/Adafruit VEML7700 Library @ 1.1.1
bakercp/PacketSerial @ 1.4.0
nanopb/Nanopb @ 0.4.6 ; Ideally this would reference the nanopb submodule, but that would require
; everyone to check out submodules to just compile, so we use the library
; registry for the runtime. The submodule is available for manually updating
; the pre-compiled (checked in) .pb.h/c files when proto files change, but is
; otherwise not used during application firmware compilation.

build_flags =
${base_config.build_flags}
Expand All @@ -58,13 +58,16 @@ build_flags =
-DSK_LEDS=1
; Number of LEDs
-DNUM_LEDS=8
-DSENSOR_MT6701=1
; Strain-gauge press input enabled: 1=enable, 0=disable
-DSK_STRAIN=1
; Invert direction of angle sensor (motor direction is detected relative to angle sensor as part of the calibration procedure)
-DSK_INVERT_ROTATION=1
; Ambient light sensor (VEML7700) enabled: 1=enable (display/LEDs match ambient brightness), 0=disable (100% brightness all the time)
-DSK_ALS=1
; Use MT6701 magnetic encoder
-DSENSOR_MT6701=1
; Invert direction of angle sensor (motor direction is detected relative to angle sensor as part of the calibration procedure)
-DSK_INVERT_ROTATION=1

-DMOTOR_WANZHIDA_ONCE_TOP=1

; Pin configurations
-DPIN_UH=26
Expand Down Expand Up @@ -108,6 +111,8 @@ build_flags =
; Reduce loop task stack size (only works on newer IDF Arduino core)
; -DARDUINO_LOOP_STACK_SIZE=2048

-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG

; FastLED setup
; Modify the default unusable pin mask to allow GPIO 7 (allowed to use on ESP32-PICO-V3-02)
; Unusable bits: 6, 8, 9, 10, 20
Expand All @@ -119,3 +124,61 @@ build_flags =
; GPIO >= 34 are input only
; (SOC_GPIO_VALID_GPIO_MASK & ~(0ULL | _FL_BIT(34) | _FL_BIT(35) | _FL_BIT(36) | _FL_BIT(37) | _FL_BIT(38) | _FL_BIT(39)))
-DSOC_GPIO_VALID_OUTPUT_GPIO_MASK=0x30EFFFFFF


[env:nanofoc]
extends = base_config
platform = espressif32
board = adafruit_feather_esp32s3
lib_deps =
${base_config.lib_deps}
askuric/Simple FOC@^2.3.0
bodmer/TFT_eSPI@2.5.0

build_flags =
${base_config.build_flags}
; Display enabled: 1=enable, 0=disable
-DSK_DISPLAY=0
; Display orientation: 0=usb bottom, 2=usb top
-DSK_DISPLAY_ROTATION=0
; LEDs enabled: 1=enable, 0=disable
-DSK_LEDS=0
; Number of LEDs
-DNUM_LEDS=8
; Strain-gauge press input enabled: 1=enable, 0=disable
-DSK_STRAIN=0
; Ambient light sensor (VEML7700) enabled: 1=enable (display/LEDs match ambient brightness), 0=disable (100% brightness all the time)
-DSK_ALS=0

-DSENSOR_MAQ430=1
-DPIN_MAQ_SCK=6
-DPIN_MAQ_MISO=7
-DPIN_MAQ_MOSI=5
-DPIN_MAQ_SS=4
; Invert direction of angle sensor (motor direction is detected relative to angle sensor as part of the calibration procedure)
-DSK_INVERT_ROTATION=1

-DMOTOR_MAD2804=1

; Pin configurations
-DPIN_UH=21
-DPIN_UL=12
-DPIN_VH=14
-DPIN_VL=10
-DPIN_WH=13
-DPIN_WL=11
-DPIN_BUTTON_NEXT=-1
-DPIN_BUTTON_PREV=-1
-DPIN_LED_DATA=7
-DPIN_LCD_BACKLIGHT=08

-DPIO_FRAMEWORK_ARDUINO_ENABLE_CDC=1
-DUSBCON=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DCORE_DEBUG_LEVEL=2
-DHSPI_SPEED=100000 ; MA/MAQ Nominal SPI Speed in Mhz (HSPI)
-DVSPI_SPEED=400000 ; TFt Nominal SPI Speed in Mhz (VSPI)

; Reduce loop task stack size (only works on newer IDF Arduino core)
; -DARDUINO_LOOP_STACK_SIZE=2048

0 comments on commit 9903ac6

Please sign in to comment.