first draft

This commit is contained in:
Marcel Peterkau 2024-12-13 13:49:38 +01:00
commit 1ecf83fcc7
12 changed files with 715 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

20
fuses.py Normal file
View File

@ -0,0 +1,20 @@
from os.path import join
Import("env")
conf_path = join(env.PioPlatform().get_package_dir("tool-avrdude"), "avrdude.conf")
env.Replace(
FUSEUPLOADER="avrdude",
FUSEFLAGS=[
"-C", conf_path,
"-p", "atmega328p",
"-Pusb",
"-c", "jtag3isp"
],
FUSECOMMAND="$FUSEUPLOADER $FUSEFLAGS -U lfuse:w:0xE6:m -U hfuse:w:0xDA:m -U efuse:w:0x05:m"
)
def set_fuses(source, target, env):
env.Execute(env.VerboseAction("$FUSECOMMAND", "Setting Fuses..."))
env.AddPreAction("upload", set_fuses)

39
include/README Normal file
View File

@ -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 Normal file
View File

@ -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 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

30
platformio.ini Normal file
View File

@ -0,0 +1,30 @@
; 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:nanoatmega328new]
platform = atmelavr
board = nanoatmega328new
framework = arduino
;upload_protocol = custom
;upload_flags =
; -C
; ${platformio.packages_dir}/tool-avrdude/avrdude.conf
; -p
; atmega328p
; -Pusb
; -c
; jtag3isp
;upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
;extra_scripts = fuses.py
lib_deps =
adafruit/Adafruit SSD1306 @ ^2.5.13

59
src/MotorController.cpp Normal file
View File

@ -0,0 +1,59 @@
// MotorController.cpp
#include "MotorController.h"
const MotorConfig defaultConfig = {
.minPWM = 0,
.maxPWM = 255,
.inverted = false,
.responseSpeed = 10,
.lookupTable = {0, 28, 57, 85, 114, 142, 171, 199, 228, 255}
};
MotorController::MotorController(int motorPin)
: motorPWMPin(motorPin), currentSpeed(0) {
loadDefaults();
pinMode(motorPWMPin, OUTPUT);
stopMotor();
}
void MotorController::loadDefaults() {
config = defaultConfig;
}
void MotorController::setTargetSpeed(int targetSpeedPercent) {
targetSpeedPercent = constrain(targetSpeedPercent, 0, 100);
int tableIndex = map(targetSpeedPercent, 0, 100, 0, MotorConfig::lookupTableSize - 1);
uint8_t targetPWM = config.lookupTable[tableIndex];
uint8_t adjustedPWM = applyDynamicResponse(targetPWM);
setPWM(adjustedPWM);
currentSpeed = targetSpeedPercent;
}
int MotorController::getSpeed() const {
return currentSpeed;
}
void MotorController::stopMotor() {
setTargetSpeed(0);
}
MotorConfig MotorController::getConfig() const {
return config;
}
void MotorController::setConfig(const MotorConfig &newConfig) {
config = newConfig;
}
void MotorController::setPWM(uint8_t pwmValue) {
uint8_t constrainedPWM = constrain(pwmValue, config.minPWM, config.maxPWM);
analogWrite(motorPWMPin, config.inverted ? 255 - constrainedPWM : constrainedPWM);
}
int MotorController::applyDynamicResponse(int targetValue) {
static int smoothedValue = 0;
int delta = targetValue - smoothedValue;
int step = delta / config.responseSpeed;
smoothedValue += step;
return smoothedValue;
}

39
src/MotorController.h Normal file
View File

@ -0,0 +1,39 @@
// MotorController.h
#ifndef MOTORCONTROLLER_H
#define MOTORCONTROLLER_H
#include <Arduino.h>
// Motor configuration structure
struct MotorConfig {
uint8_t minPWM;
uint8_t maxPWM;
bool inverted;
int responseSpeed;
static const int lookupTableSize = 10;
uint8_t lookupTable[lookupTableSize];
};
class MotorController {
private:
int motorPWMPin;
int currentSpeed;
MotorConfig config;
int applyDynamicResponse(int targetValue);
void setPWM(uint8_t pwmValue);
public:
MotorController(int motorPin);
void stopMotor();
void setTargetSpeed(int targetSpeedPercent);
int getSpeed() const;
MotorConfig getConfig() const;
void setConfig(const MotorConfig &newConfig);
void loadDefaults();
};
#endif // MOTORCONTROLLER_H

156
src/PedalController.cpp Normal file
View File

@ -0,0 +1,156 @@
// PedalController.cpp
#include "PedalController.h"
const int minMin = 50; // Minimum allowed raw value for calibration
const int maxMin = 200; // Maximum allowed raw value for calibration
const int minMax = 800; // Minimum allowed maximum value for calibration
const int maxMax = 1000; // Maximum allowed raw value for calibration
const PedalConfig defaultConfig = {
.minRaw = 0,
.maxRaw = 1023,
.calibrated = false
};
PedalController::PedalController(int pin)
: pedalPin(pin)
{
loadDefaults();
pinMode(pedalPin, INPUT);
}
void PedalController::loadDefaults()
{
config = defaultConfig;
}
int PedalController::applySmoothing(int rawValue)
{
filteredValue = (filteredValue * (config.filterStrength - 1) + rawValue) / config.filterStrength;
return filteredValue;
}
CalibrationState PedalController::autoCalibrate(bool reset)
{
static int step = 0;
static unsigned long lastChangeTime = 0;
static int previousValue = 0;
static CalibrationState errorState = IDLE;
if (reset)
{
step = 0;
config.minRaw = 1023;
config.maxRaw = 0;
lastChangeTime = millis();
config.calibrated = false;
errorState = IDLE;
Serial.println("Calibration reset.");
return IDLE;
}
unsigned long currentTime = millis();
int rawValue = applySmoothing(analogRead(pedalPin));
switch (step)
{
case 0: // Initial state: wait for pedal release
if (rawValue == previousValue && currentTime - lastChangeTime > 2000)
{
if (rawValue >= minMin && rawValue <= maxMin)
{
config.minRaw = rawValue;
step++;
lastChangeTime = currentTime;
errorState = RUNNING;
}
else
{
errorState = ERROR;
}
}
else if (rawValue != previousValue)
{
lastChangeTime = currentTime;
}
break;
case 1: // Wait for pedal full press
if (rawValue == previousValue && currentTime - lastChangeTime > 2000)
{
if (rawValue >= minMax && rawValue <= maxMax)
{
config.maxRaw = rawValue;
step++;
lastChangeTime = currentTime;
errorState = RUNNING;
}
else
{
errorState = ERROR;
}
}
else if (rawValue != previousValue)
{
lastChangeTime = currentTime;
}
break;
case 2: // Validate calibration
if (config.maxRaw > config.minRaw + 100)
{ // Ensure valid range
step++;
config.calibrated = true;
errorState = SUCCESS;
}
else
{
errorState = ERROR;
}
break;
default:
errorState = IDLE;
step = 0;
break;
}
previousValue = rawValue;
return errorState;
}
int PedalController::getPedal()
{
if (!config.calibrated || autoCalibrate(false) != IDLE)
{
return 0;
}
int rawValue = applySmoothing(analogRead(pedalPin));
if (rawValue < config.minRaw || rawValue > config.maxRaw)
{
return 0; // Out of bounds
}
return map(rawValue, config.minRaw, config.maxRaw, 0, 100);
}
CalibrationState PedalController::getStatus()
{
return autoCalibrate(false);
}
int PedalController::getRawValue()
{
return analogRead(pedalPin);
}
PedalConfig PedalController::getConfig()
{
return config;
}
void PedalController::setConfig(const PedalConfig &newConfig)
{
config = newConfig;
}

46
src/PedalController.h Normal file
View File

@ -0,0 +1,46 @@
// PedalController.h
#ifndef PEDALCONTROLLER_H
#define PEDALCONTROLLER_H
#include <Arduino.h>
// Calibration state enumeration
enum CalibrationState {
IDLE,
RUNNING,
SUCCESS,
ERROR
};
// Pedal configuration structure
struct PedalConfig {
int minRaw;
int maxRaw;
bool calibrated;
int filterStrength;
};
class PedalController {
private:
int pedalPin;
int filteredValue;
PedalConfig config;
int applySmoothing(int rawValue);
int getRawValue();
public:
PedalController(int pin);
CalibrationState autoCalibrate(bool reset);
CalibrationState getStatus();
int getPedal();
PedalConfig getConfig();
void setConfig(const PedalConfig &newConfig);
void loadDefaults();
};
#endif // PEDALCONTROLLER_H

254
src/main.cpp Normal file
View File

@ -0,0 +1,254 @@
#include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include "MotorController.h"
#include "PedalController.h"
// OLED display setup
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin definitions
const int hallSensor = 2; // Hall sensor input
const int encoderPinA = 3; // Encoder pin A
const int encoderPinB = 4; // Encoder pin B
const int encoderButton = 5; // Encoder button
const int motorPin = 9; // Motor PWM pin
const int pedalPin = A0; // Pedal input pin
// Global objects
MotorController motor(motorPin); // Motor control object
PedalController pedal(pedalPin); // Pedal input object
// Combined settings structure
struct Settings {
MotorConfig motorConfig;
PedalConfig pedalConfig;
uint8_t checksum;
};
Settings settings;
bool autoCalibrating = false;
bool serialControlEnabled = false;
CalibrationState lastCalibrationState = IDLE;
// Function prototypes
void loadSettings();
void saveSettings();
uint8_t calculateChecksum(const Settings &s);
void handleEncoder();
void handleEncoderButton();
void updateDisplay();
void handleSerialInput();
void configurePWMFrequency();
void stopMotorAtNeedleUp();
void printHelp();
void setup() {
// Initialize pins
pinMode(hallSensor, INPUT);
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);
pinMode(encoderButton, INPUT_PULLUP);
// Configure PWM frequency
configurePWMFrequency();
// Attach interrupts for encoder
attachInterrupt(digitalPinToInterrupt(encoderPinA), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderPinB), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderButton), handleEncoderButton, FALLING);
// Initialize serial and load settings
Serial.begin(9600);
loadSettings();
// Initialize OLED display
// if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
// Serial.println(F("SSD1306 allocation failed"));
// }
// display.clearDisplay();
// display.display();
printHelp();
}
void loop() {
// Handle serial input for debugging and manual control
handleSerialInput();
if (autoCalibrating) {
CalibrationState currentState = pedal.autoCalibrate(false);
if (currentState != lastCalibrationState) {
lastCalibrationState = currentState;
switch (currentState) {
case RUNNING:
Serial.println("Calibration running...");
break;
case SUCCESS:
Serial.println("Auto-calibration completed.");
break;
case ERROR:
Serial.println("Auto-calibration failed.");
break;
default:
break;
}
}
motor.setTargetSpeed(0); // Ensure motor stays off during calibration
} else {
int pedalValue = serialControlEnabled ? 0 : pedal.getPedal();
motor.setTargetSpeed(pedalValue);
}
// Update display
// updateDisplay();
delay(10); // Short delay for stability
}
void stopMotorAtNeedleUp() {
motor.stopMotor();
delay(100); // Stabilize
}
void loadSettings() {
EEPROM.get(0, settings);
// Validate checksum
if (calculateChecksum(settings) != settings.checksum) {
Serial.println("Invalid settings checksum, loading defaults.");
motor.loadDefaults();
pedal.autoCalibrate(true);
settings.motorConfig = motor.getConfig();
settings.pedalConfig = pedal.getConfig();
settings.checksum = calculateChecksum(settings);
saveSettings();
} else {
motor.setConfig(settings.motorConfig);
pedal.setConfig(settings.pedalConfig);
Serial.println("Settings loaded from EEPROM.");
}
}
void saveSettings() {
settings.motorConfig = motor.getConfig();
settings.pedalConfig = pedal.getConfig();
settings.checksum = calculateChecksum(settings);
EEPROM.put(0, settings);
Serial.println("Settings saved to EEPROM.");
}
uint8_t calculateChecksum(const Settings &s) {
uint8_t sum = 0;
const uint8_t *data = reinterpret_cast<const uint8_t *>(&s);
for (size_t i = 0; i < sizeof(Settings) - 1; i++) {
sum ^= data[i];
}
return sum;
}
void handleEncoder() {
static uint8_t lastState = 0;
uint8_t currentState = (digitalRead(encoderPinA) << 1) | digitalRead(encoderPinB);
if ((lastState == 0b00 && currentState == 0b01) ||
(lastState == 0b01 && currentState == 0b11) ||
(lastState == 0b11 && currentState == 0b10) ||
(lastState == 0b10 && currentState == 0b00)) {
// Increment
} else if ((lastState == 0b00 && currentState == 0b10) ||
(lastState == 0b10 && currentState == 0b11) ||
(lastState == 0b11 && currentState == 0b01) ||
(lastState == 0b01 && currentState == 0b00)) {
// Decrement
}
lastState = currentState;
}
void handleEncoderButton() {
// Handle encoder button press
}
void updateDisplay() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Pedal: ");
display.println(pedal.getPedal());
display.display();
}
void handleSerialInput() {
if (Serial.available()) {
char input = Serial.read();
switch (input) {
case 'c':
Serial.println("Starting auto-calibration...");
pedal.autoCalibrate(true);
autoCalibrating = true;
lastCalibrationState = IDLE; // Reset state tracking
break;
case 'x':
Serial.println("Stopping auto-calibration or serial control.");
autoCalibrating = false;
serialControlEnabled = false;
motor.stopMotor();
break;
case 'r':
Serial.println("Loading defaults.");
motor.loadDefaults();
pedal.loadDefaults();
break;
case 's':
Serial.println("Saving settings to EEPROM.");
saveSettings();
break;
case 'l':
Serial.println("Loading settings from EEPROM.");
loadSettings();
break;
case 'h':
printHelp();
break;
case '+':
serialControlEnabled = true;
motor.setTargetSpeed(motor.getSpeed() + 1);
Serial.print("Increased Speed to: ");
Serial.println(motor.getSpeed());
break;
case '-':
serialControlEnabled = true;
motor.setTargetSpeed(motor.getSpeed() - 1);
Serial.print("Decreased Speed to: ");
Serial.println(motor.getSpeed());
break;
}
Serial.println(input);
}
}
void printHelp() {
Serial.println("Available commands:");
Serial.println("c - Start auto-calibration");
Serial.println("x - Stop auto-calibration or serial control");
Serial.println("r - Load defaults");
Serial.println("s - Save settings to EEPROM");
Serial.println("l - Load settings from EEPROM");
Serial.println("h - Show this help");
Serial.println("+ - Increment PWM");
Serial.println("- - Decrement PWM");
}
void configurePWMFrequency() {
// Configure Timer1 for higher PWM frequency (e.g., ~8 kHz)
TCCR1B = (TCCR1B & 0b11111000) | 0x01; // Set prescaler to 1
}

11
test/README Normal file
View File

@ -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