Compare commits
3
Commits
main
..
9633ff1d7a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9633ff1d7a | ||
|
|
0d40b6026c | ||
|
|
4d3a1c0bbc |
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
@@ -0,0 +1,38 @@
|
||||
.. Copyright 2019-present PlatformIO <contact@platformio.org>
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
How to build PlatformIO based project
|
||||
=====================================
|
||||
|
||||
1. [Install PlatformIO Core](https://docs.platformio.org/page/core.html)
|
||||
2. Download [development platform with examples](https://github.com/platformio/platform-ststm32/archive/develop.zip)
|
||||
3. Extract ZIP archive
|
||||
4. Run these commands:
|
||||
|
||||
```shell
|
||||
# Change directory to example
|
||||
$ cd platform-ststm32/examples/zephyr-blink
|
||||
|
||||
# Build project
|
||||
$ pio run
|
||||
|
||||
# Upload firmware
|
||||
$ pio run --target upload
|
||||
|
||||
# Build specific environment
|
||||
$ pio run -e blackpill_f103c8
|
||||
|
||||
# Upload firmware for the specific environment
|
||||
$ pio run -e blackpill_f103c8 --target upload
|
||||
|
||||
# Clean build files
|
||||
$ pio run --target clean
|
||||
```
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
@startuml
|
||||
|
||||
[*] --> Initialization
|
||||
|
||||
state Initialization
|
||||
state Operational
|
||||
|
||||
Initialization : entry / initialize parameters
|
||||
Initialization : entry / send Boot-up
|
||||
Initialization : do / wait for command
|
||||
|
||||
Operational : entry / enable motor control
|
||||
Operational : entry / LED ON
|
||||
Operational : do / update speed controller at 250 Hz
|
||||
Operational : exit / stop motor control
|
||||
Operational : exit / LED OFF
|
||||
|
||||
Initialization --> Operational : set operational / 'o'
|
||||
Operational --> Initialization : reset / 'r'
|
||||
|
||||
@enduml
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
@startuml
|
||||
|
||||
[*] --> Initialization
|
||||
|
||||
state Initialization
|
||||
state Operational
|
||||
state Stopped
|
||||
|
||||
Initialization : entry / initialize parameters
|
||||
Initialization : entry / send Boot-up
|
||||
Initialization : do / wait for command
|
||||
|
||||
Operational : entry / enable motor control
|
||||
Operational : entry / LED ON
|
||||
Operational : do / update speed controller at 250 Hz
|
||||
Operational : exit / stop motor control
|
||||
Operational : exit / LED OFF
|
||||
|
||||
Stopped : entry / brake motor
|
||||
Stopped : entry / disable normal controller activity
|
||||
Stopped : do / LED blink at 2 Hz
|
||||
Stopped : exit / LED OFF
|
||||
|
||||
Initialization --> Operational : set operational / 'o'
|
||||
Operational --> Initialization : reset / 'r'
|
||||
|
||||
Operational --> Stopped : emergency stop / fault
|
||||
Stopped --> Initialization : reset / 'r'
|
||||
|
||||
@enduml
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Part 3 - PI Controller TDD
|
||||
|
||||
## Normal flows
|
||||
|
||||
1. Zero error produces zero output.
|
||||
2. Positive error produces positive control output.
|
||||
3. Negative error produces negative control output.
|
||||
4. Constant error accumulates integral action.
|
||||
5. The PI controller operates through the common Controller interface.
|
||||
|
||||
## Alternate flows
|
||||
|
||||
1. Positive output is limited to +255.
|
||||
2. Negative output is limited to -255.
|
||||
3. Positive saturation does not cause integral windup.
|
||||
4. Negative saturation does not cause integral windup.
|
||||
5. The controller recovers correctly after saturation.
|
||||
6. Reset clears the accumulated integral.
|
||||
|
||||
The tests were written and executed before the PI control law was
|
||||
implemented. The initial run produced the expected failing tests.
|
||||
After implementing the PI control law and anti-windup behavior,
|
||||
the same test suite passed all 11 tests.
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
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 convention is to give header files names that end with `.h'.
|
||||
|
||||
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
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef CONTEXT_H
|
||||
#define CONTEXT_H
|
||||
|
||||
#include <state.h>
|
||||
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
Context(State& initialState);
|
||||
|
||||
void transition_to(State& newState);
|
||||
|
||||
void do_work();
|
||||
|
||||
void reset();
|
||||
void set_operational();
|
||||
void set_pre_operational();
|
||||
void fault();
|
||||
|
||||
void select_p();
|
||||
void select_pi();
|
||||
|
||||
void set_kp(double value);
|
||||
void set_ti(double value);
|
||||
|
||||
const char* current_state_name() const;
|
||||
|
||||
private:
|
||||
State* state;
|
||||
};
|
||||
|
||||
#endif
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#ifndef CONTROL_TIMER_H
|
||||
#define CONTROL_TIMER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class Control_timer
|
||||
{
|
||||
public:
|
||||
void init();
|
||||
|
||||
// Returns true once for every 4 ms control tick.
|
||||
bool take_tick();
|
||||
|
||||
uint32_t tick_count() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#ifndef CONTROLLER_H
|
||||
#define CONTROLLER_H
|
||||
|
||||
class Controller
|
||||
{
|
||||
public:
|
||||
virtual ~Controller() = default;
|
||||
|
||||
virtual double update(
|
||||
double reference,
|
||||
double actual
|
||||
) = 0;
|
||||
|
||||
virtual void reset() = 0;
|
||||
|
||||
virtual const char* name() const = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#ifndef DIGITAL_IN_H
|
||||
#define DIGITAL_IN_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class Digital_in
|
||||
{
|
||||
public:
|
||||
// pin is the PORTB bit number:
|
||||
// PB0 = D8, PB1 = D9, ..., PB5 = D13
|
||||
Digital_in(uint8_t pin);
|
||||
|
||||
void init();
|
||||
|
||||
bool is_hi() const;
|
||||
bool is_lo() const;
|
||||
|
||||
private:
|
||||
uint8_t pinMask;
|
||||
};
|
||||
|
||||
#endif
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef DIGITAL_OUT_H
|
||||
#define DIGITAL_OUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class Digital_out
|
||||
{
|
||||
public:
|
||||
// pin is the PORTB bit number:
|
||||
// PB0 = D8, ..., PB5 = D13
|
||||
Digital_out(uint8_t pin);
|
||||
|
||||
void init();
|
||||
|
||||
void set_hi();
|
||||
void set_lo();
|
||||
void toggle();
|
||||
|
||||
private:
|
||||
uint8_t pinMask;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef ENCODER_H
|
||||
#define ENCODER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <digital_in.h>
|
||||
|
||||
class Encoder
|
||||
{
|
||||
public:
|
||||
Encoder(uint8_t c1Pin, uint8_t c2Pin);
|
||||
|
||||
void init();
|
||||
void update();
|
||||
|
||||
int32_t position() const;
|
||||
|
||||
private:
|
||||
Digital_in c1;
|
||||
Digital_in c2;
|
||||
|
||||
uint8_t c1Pin;
|
||||
|
||||
bool previousC1;
|
||||
|
||||
volatile int32_t count;
|
||||
};
|
||||
|
||||
#endif
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#ifndef INITIALIZATION_H
|
||||
#define INITIALIZATION_H
|
||||
|
||||
#include <state.h>
|
||||
#include <speed_controller.h>
|
||||
#include <digital_out.h>
|
||||
|
||||
class Initialization : public State
|
||||
{
|
||||
public:
|
||||
Initialization(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED
|
||||
);
|
||||
|
||||
const char* name() const override;
|
||||
|
||||
void on_entry() override;
|
||||
void on_do() override;
|
||||
void on_exit() override;
|
||||
|
||||
void set_pre_operational_state(
|
||||
State& preOperationalState
|
||||
);
|
||||
|
||||
private:
|
||||
Speed_controller& speedController;
|
||||
Digital_out& statusLED;
|
||||
|
||||
State* preOperationalState;
|
||||
|
||||
bool transitionPending;
|
||||
};
|
||||
|
||||
#endif
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#ifndef MOTOR_DRIVER_H
|
||||
#define MOTOR_DRIVER_H
|
||||
|
||||
class Motor_driver
|
||||
{
|
||||
public:
|
||||
void init();
|
||||
|
||||
// Range: -255 ... +255
|
||||
void set_command(double command);
|
||||
|
||||
void coast();
|
||||
|
||||
void brake();
|
||||
|
||||
double command() const;
|
||||
|
||||
private:
|
||||
double currentCommand;
|
||||
|
||||
void enable_pwm_outputs();
|
||||
|
||||
unsigned char magnitude(double value) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
#ifndef OPERATIONAL_H
|
||||
#define OPERATIONAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <state.h>
|
||||
#include <speed_controller.h>
|
||||
#include <digital_out.h>
|
||||
|
||||
class Operational : public State
|
||||
{
|
||||
public:
|
||||
Operational(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED
|
||||
);
|
||||
|
||||
const char* name() const override;
|
||||
|
||||
void on_entry() override;
|
||||
void on_do() override;
|
||||
void on_exit() override;
|
||||
|
||||
void reset() override;
|
||||
void set_pre_operational() override;
|
||||
void fault() override;
|
||||
|
||||
void set_initialization_state(
|
||||
State& initializationState
|
||||
);
|
||||
|
||||
void set_pre_operational_state(
|
||||
State& preOperationalState
|
||||
);
|
||||
|
||||
void set_stopped_state(
|
||||
State& stoppedState
|
||||
);
|
||||
|
||||
private:
|
||||
Speed_controller& speedController;
|
||||
Digital_out& statusLED;
|
||||
|
||||
State* initializationState;
|
||||
State* preOperationalState;
|
||||
State* stoppedState;
|
||||
|
||||
uint8_t logCounter;
|
||||
uint32_t sampleCounter;
|
||||
};
|
||||
|
||||
#endif
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#ifndef P_CONTROLLER_H
|
||||
#define P_CONTROLLER_H
|
||||
|
||||
#include <controller.h>
|
||||
|
||||
class P_controller : public Controller
|
||||
{
|
||||
public:
|
||||
explicit P_controller(
|
||||
double kp
|
||||
);
|
||||
|
||||
double update(
|
||||
double reference,
|
||||
double actual
|
||||
) override;
|
||||
|
||||
void reset() override;
|
||||
|
||||
const char* name() const override;
|
||||
|
||||
void set_kp(
|
||||
double value
|
||||
);
|
||||
|
||||
double get_kp() const;
|
||||
|
||||
private:
|
||||
double kp;
|
||||
|
||||
double limit(
|
||||
double value
|
||||
) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#ifndef PI_CONTROLLER_H
|
||||
#define PI_CONTROLLER_H
|
||||
|
||||
#include <controller.h>
|
||||
|
||||
class PI_controller : public Controller
|
||||
{
|
||||
public:
|
||||
PI_controller(
|
||||
double kp,
|
||||
double ti,
|
||||
double timestep
|
||||
);
|
||||
|
||||
double update(
|
||||
double reference,
|
||||
double actual
|
||||
) override;
|
||||
|
||||
void reset() override;
|
||||
|
||||
const char* name() const override;
|
||||
|
||||
void set_kp(double kp);
|
||||
double get_kp() const;
|
||||
|
||||
void set_ti(double ti);
|
||||
double get_ti() const;
|
||||
|
||||
double get_timestep() const;
|
||||
|
||||
private:
|
||||
double kp;
|
||||
double ti;
|
||||
double timestep;
|
||||
|
||||
double integral;
|
||||
|
||||
double limit(double value) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#ifndef PRE_OPERATIONAL_H
|
||||
#define PRE_OPERATIONAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <state.h>
|
||||
#include <speed_controller.h>
|
||||
#include <digital_out.h>
|
||||
|
||||
#include <p_controller.h>
|
||||
#include <pi_controller.h>
|
||||
|
||||
class PreOperational : public State
|
||||
{
|
||||
public:
|
||||
PreOperational(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED,
|
||||
P_controller& pController,
|
||||
PI_controller& piController
|
||||
);
|
||||
|
||||
const char* name() const override;
|
||||
|
||||
void on_entry() override;
|
||||
void on_do() override;
|
||||
void on_exit() override;
|
||||
|
||||
void reset() override;
|
||||
void set_operational() override;
|
||||
|
||||
void select_p() override;
|
||||
void select_pi() override;
|
||||
|
||||
void set_kp(double value) override;
|
||||
void set_ti(double value) override;
|
||||
|
||||
void set_initialization_state(
|
||||
State& initializationState
|
||||
);
|
||||
|
||||
void set_operational_state(
|
||||
State& operationalState
|
||||
);
|
||||
|
||||
private:
|
||||
Speed_controller& speedController;
|
||||
Digital_out& statusLED;
|
||||
|
||||
P_controller& pController;
|
||||
PI_controller& piController;
|
||||
|
||||
State* initializationState;
|
||||
State* operationalState;
|
||||
|
||||
uint8_t ledCounter;
|
||||
|
||||
bool piSelected;
|
||||
|
||||
void print_configuration();
|
||||
};
|
||||
|
||||
#endif
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
#ifndef SPEED_CONTROLLER_H
|
||||
#define SPEED_CONTROLLER_H
|
||||
|
||||
#include <encoder.h>
|
||||
#include <speed_estimator.h>
|
||||
#include <motor_driver.h>
|
||||
#include <controller.h>
|
||||
|
||||
class Speed_controller
|
||||
{
|
||||
public:
|
||||
Speed_controller(
|
||||
Encoder& encoder,
|
||||
Speed_estimator& estimator,
|
||||
Motor_driver& motor,
|
||||
Controller& controller
|
||||
);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void brake();
|
||||
|
||||
void update();
|
||||
|
||||
void set_reference(double rpm);
|
||||
double reference() const;
|
||||
|
||||
void set_controller(Controller& controller);
|
||||
|
||||
double actual_rpm() const;
|
||||
double control_output() const;
|
||||
|
||||
private:
|
||||
Encoder& encoder;
|
||||
Speed_estimator& estimator;
|
||||
Motor_driver& motor;
|
||||
|
||||
Controller* controller;
|
||||
|
||||
bool running;
|
||||
|
||||
double referenceRPM;
|
||||
double actualRPM;
|
||||
double controlOutput;
|
||||
};
|
||||
|
||||
#endif
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#ifndef SPEED_ESTIMATOR_H
|
||||
#define SPEED_ESTIMATOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class Speed_estimator
|
||||
{
|
||||
public:
|
||||
Speed_estimator(
|
||||
double countsPerRevolution,
|
||||
double sampleTimeSeconds
|
||||
);
|
||||
|
||||
void reset(int32_t initialPosition);
|
||||
|
||||
double update(int32_t currentPosition);
|
||||
|
||||
double rpm() const;
|
||||
|
||||
private:
|
||||
static const uint8_t WINDOW_SIZE = 10;
|
||||
|
||||
double countsPerRevolution;
|
||||
double sampleTime;
|
||||
|
||||
int32_t previousPosition;
|
||||
|
||||
int32_t countHistory[WINDOW_SIZE];
|
||||
int32_t rollingCounts;
|
||||
|
||||
uint8_t historyIndex;
|
||||
uint8_t samplesFilled;
|
||||
|
||||
double currentRPM;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef STATE_H
|
||||
#define STATE_H
|
||||
|
||||
class Context;
|
||||
|
||||
class State
|
||||
{
|
||||
protected:
|
||||
Context* context_ = nullptr;
|
||||
|
||||
public:
|
||||
virtual ~State() = default;
|
||||
|
||||
void set_context(Context* context)
|
||||
{
|
||||
context_ = context;
|
||||
}
|
||||
|
||||
virtual const char* name() const = 0;
|
||||
|
||||
virtual void on_entry() = 0;
|
||||
virtual void on_do() = 0;
|
||||
virtual void on_exit() = 0;
|
||||
|
||||
// State-machine events
|
||||
virtual void reset() {}
|
||||
virtual void set_operational() {}
|
||||
virtual void set_pre_operational() {}
|
||||
virtual void fault() {}
|
||||
|
||||
// Pre-operational configuration events
|
||||
virtual void select_p() {}
|
||||
virtual void select_pi() {}
|
||||
virtual void set_kp(double value)
|
||||
{
|
||||
(void)value;
|
||||
}
|
||||
|
||||
virtual void set_ti(double value)
|
||||
{
|
||||
(void)value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef STOPPED_H
|
||||
#define STOPPED_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <state.h>
|
||||
#include <speed_controller.h>
|
||||
#include <digital_out.h>
|
||||
|
||||
class Stopped : public State
|
||||
{
|
||||
public:
|
||||
Stopped(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED
|
||||
);
|
||||
|
||||
const char* name() const override;
|
||||
|
||||
void on_entry() override;
|
||||
void on_do() override;
|
||||
void on_exit() override;
|
||||
|
||||
void reset() override;
|
||||
void set_pre_operational() override;
|
||||
void set_operational() override;
|
||||
|
||||
void set_initialization_state(
|
||||
State& initializationState
|
||||
);
|
||||
|
||||
void set_pre_operational_state(
|
||||
State& preOperationalState
|
||||
);
|
||||
|
||||
void set_operational_state(
|
||||
State& operationalState
|
||||
);
|
||||
|
||||
private:
|
||||
Speed_controller& speedController;
|
||||
Digital_out& statusLED;
|
||||
|
||||
State* initializationState;
|
||||
State* preOperationalState;
|
||||
State* operationalState;
|
||||
|
||||
uint8_t ledCounter;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for 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
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
@@ -0,0 +1,17 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter, extra scripting
|
||||
; Upload options: custom port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:nucleo_l432kc]
|
||||
platform = ststm32
|
||||
board = nucleo_l432kc
|
||||
framework = zephyr
|
||||
|
||||
test_framework = unity
|
||||
build_flags = -I"C:\Users\nokkv\.platformio\packages\framework-zephyr\include\zephyr"
|
||||
monitor_speed = 115200
|
||||
@@ -0,0 +1,98 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/pwm.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
|
||||
//#include <zephyr/dt-bindings/gpio/gpio.h> /* GPIO_ACTIVE_LOW, GPIO_PULL_UP */
|
||||
//#include <zephyr/boards/nucleo_l432kc.overlay>
|
||||
|
||||
|
||||
|
||||
/* 1000 msec = 1 sec */
|
||||
#define SLEEP_TIME_MS 200
|
||||
|
||||
/* The devicetree node identifier for the "led0" alias. */
|
||||
#define LED0_NODE DT_ALIAS(led0)
|
||||
// we add button
|
||||
#define BUTTON_NODE DT_ALIAS(sw1)
|
||||
|
||||
|
||||
/*
|
||||
* A build error on this line means your board is unsupported.
|
||||
* See the sample documentation for information on how to fix this.
|
||||
*/
|
||||
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
|
||||
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios);
|
||||
|
||||
// we add interrupt driven.
|
||||
static struct gpio_callback button_cb_data;
|
||||
|
||||
// Work item
|
||||
static struct k_work button_work;
|
||||
|
||||
bool led_state = true;
|
||||
|
||||
//k work
|
||||
static void button_work_handler(struct k_work *work)
|
||||
{
|
||||
gpio_pin_toggle_dt(&led);
|
||||
led_state = !led_state;
|
||||
printf("LED state: %s\n", led_state ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
static void on_press(const struct device *dev, struct gpio_callback
|
||||
*cb_data, uint32_t pins) {k_work_submit(&button_work);}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!gpio_is_ready_dt(&button)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!gpio_is_ready_dt(&led)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
|
||||
if (ret < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// configure interrupt
|
||||
|
||||
// button = D3, from the sw1 alias
|
||||
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
//initialize work
|
||||
k_work_init(&button_work, button_work_handler);
|
||||
|
||||
// initialize callback
|
||||
gpio_init_callback(&button_cb_data, on_press, BIT(button.pin));
|
||||
|
||||
// Register callback
|
||||
gpio_add_callback(button.port, &button_cb_data);
|
||||
|
||||
while (1) {
|
||||
k_msleep(SLEEP_TIME_MS);
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#include <Arduino.h>
|
||||
#include <context.h>
|
||||
|
||||
Context::Context(State& initialState)
|
||||
: state(nullptr)
|
||||
{
|
||||
transition_to(initialState);
|
||||
}
|
||||
|
||||
void Context::transition_to(State& newState)
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->on_exit();
|
||||
|
||||
Serial.print("Transition: ");
|
||||
Serial.print(state->name());
|
||||
Serial.print(" -> ");
|
||||
Serial.println(newState.name());
|
||||
}
|
||||
|
||||
state = &newState;
|
||||
|
||||
state->set_context(this);
|
||||
|
||||
state->on_entry();
|
||||
}
|
||||
|
||||
void Context::do_work()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->on_do();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::reset()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::set_operational()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->set_operational();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::set_pre_operational()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->set_pre_operational();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::fault()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->fault();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::select_p()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->select_p();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::select_pi()
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->select_pi();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::set_kp(double value)
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->set_kp(value);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::set_ti(double value)
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
state->set_ti(value);
|
||||
}
|
||||
}
|
||||
|
||||
const char* Context::current_state_name() const
|
||||
{
|
||||
if (state == nullptr)
|
||||
{
|
||||
return "None";
|
||||
}
|
||||
|
||||
return state->name();
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#include <control_timer.h>
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
static volatile uint8_t pendingTicks = 0;
|
||||
static volatile uint32_t totalTicks = 0;
|
||||
|
||||
ISR(TIMER1_COMPA_vect)
|
||||
{
|
||||
totalTicks++;
|
||||
|
||||
if (pendingTicks < 255)
|
||||
{
|
||||
pendingTicks++;
|
||||
}
|
||||
}
|
||||
|
||||
void Control_timer::init()
|
||||
{
|
||||
pendingTicks = 0;
|
||||
totalTicks = 0;
|
||||
|
||||
// Timer1 CTC mode, 250 Hz
|
||||
// 16 MHz / 64 / (999 + 1) = 250 Hz
|
||||
|
||||
TCCR1A = 0;
|
||||
TCCR1B = 0;
|
||||
TCNT1 = 0;
|
||||
|
||||
OCR1A = 999;
|
||||
|
||||
TCCR1B |= (1 << WGM12);
|
||||
|
||||
// Prescaler = 64
|
||||
TCCR1B |=
|
||||
(1 << CS11) |
|
||||
(1 << CS10);
|
||||
|
||||
TIMSK1 |= (1 << OCIE1A);
|
||||
}
|
||||
|
||||
bool Control_timer::take_tick()
|
||||
{
|
||||
bool tickAvailable = false;
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
if (pendingTicks > 0)
|
||||
{
|
||||
pendingTicks--;
|
||||
tickAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
return tickAvailable;
|
||||
}
|
||||
|
||||
uint32_t Control_timer::tick_count() const
|
||||
{
|
||||
uint32_t ticks;
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
ticks = totalTicks;
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <digital_in.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
Digital_in::Digital_in(uint8_t pin)
|
||||
{
|
||||
pinMask = static_cast<uint8_t>(1U << pin);
|
||||
}
|
||||
|
||||
void Digital_in::init()
|
||||
{
|
||||
// Input
|
||||
DDRB &= static_cast<uint8_t>(~pinMask);
|
||||
|
||||
// Internal pull-up
|
||||
PORTB |= pinMask;
|
||||
}
|
||||
|
||||
bool Digital_in::is_hi() const
|
||||
{
|
||||
return (PINB & pinMask) != 0;
|
||||
}
|
||||
|
||||
bool Digital_in::is_lo() const
|
||||
{
|
||||
return (PINB & pinMask) == 0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <digital_out.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
Digital_out::Digital_out(uint8_t pin)
|
||||
{
|
||||
pinMask = static_cast<uint8_t>(1U << pin);
|
||||
}
|
||||
|
||||
void Digital_out::init()
|
||||
{
|
||||
DDRB |= pinMask;
|
||||
}
|
||||
|
||||
void Digital_out::set_hi()
|
||||
{
|
||||
PORTB |= pinMask;
|
||||
}
|
||||
|
||||
void Digital_out::set_lo()
|
||||
{
|
||||
PORTB &= static_cast<uint8_t>(~pinMask);
|
||||
}
|
||||
|
||||
void Digital_out::toggle()
|
||||
{
|
||||
PORTB ^= pinMask;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#include <encoder.h>
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
static Encoder* activeEncoder = nullptr;
|
||||
|
||||
ISR(PCINT0_vect)
|
||||
{
|
||||
if (activeEncoder != nullptr)
|
||||
{
|
||||
activeEncoder->update();
|
||||
}
|
||||
}
|
||||
|
||||
Encoder::Encoder(uint8_t c1Pin, uint8_t c2Pin)
|
||||
: c1(c1Pin),
|
||||
c2(c2Pin),
|
||||
c1Pin(c1Pin),
|
||||
previousC1(false),
|
||||
count(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Encoder::init()
|
||||
{
|
||||
c1.init();
|
||||
c2.init();
|
||||
|
||||
activeEncoder = this;
|
||||
|
||||
previousC1 = c1.is_hi();
|
||||
count = 0;
|
||||
|
||||
// Clear pending PORTB pin-change interrupt
|
||||
PCIFR |= (1 << PCIF0);
|
||||
|
||||
// Enable PORTB pin-change interrupts
|
||||
PCICR |= (1 << PCIE0);
|
||||
|
||||
// Enable interrupt on C1
|
||||
PCMSK0 |= (1 << c1Pin);
|
||||
}
|
||||
|
||||
void Encoder::update()
|
||||
{
|
||||
bool currentC1 = c1.is_hi();
|
||||
|
||||
if (currentC1 != previousC1)
|
||||
{
|
||||
bool currentC2 = c2.is_hi();
|
||||
|
||||
if (currentC1 == currentC2)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
count--;
|
||||
}
|
||||
|
||||
previousC1 = currentC1;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Encoder::position() const
|
||||
{
|
||||
uint8_t oldSREG = SREG;
|
||||
|
||||
cli();
|
||||
int32_t currentCount = count;
|
||||
SREG = oldSREG;
|
||||
|
||||
return currentCount;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <initialization.h>
|
||||
#include <context.h>
|
||||
|
||||
Initialization::Initialization(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED
|
||||
)
|
||||
: speedController(speedController),
|
||||
statusLED(statusLED),
|
||||
preOperationalState(nullptr),
|
||||
transitionPending(false)
|
||||
{
|
||||
}
|
||||
|
||||
const char* Initialization::name() const
|
||||
{
|
||||
return "Initialization";
|
||||
}
|
||||
|
||||
void Initialization::on_entry()
|
||||
{
|
||||
speedController.stop();
|
||||
|
||||
statusLED.set_lo();
|
||||
|
||||
Serial.println();
|
||||
Serial.println("Initialization");
|
||||
Serial.println("Boot-up");
|
||||
|
||||
// CANopen-style autonomous transition
|
||||
// into PreOperational.
|
||||
transitionPending = true;
|
||||
}
|
||||
|
||||
void Initialization::on_do()
|
||||
{
|
||||
if (
|
||||
transitionPending &&
|
||||
preOperationalState != nullptr
|
||||
)
|
||||
{
|
||||
transitionPending = false;
|
||||
|
||||
context_->transition_to(
|
||||
*preOperationalState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Initialization::on_exit()
|
||||
{
|
||||
}
|
||||
|
||||
void Initialization::set_pre_operational_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
preOperationalState = &state;
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
#include <motor_driver.h>
|
||||
|
||||
#include <avr/io.h>
|
||||
|
||||
void Motor_driver::init()
|
||||
{
|
||||
currentCommand = 0.0;
|
||||
|
||||
// D11 / PB3 / OC2A -> AIN1
|
||||
DDRB |= (1 << PB3);
|
||||
|
||||
// D3 / PD3 / OC2B -> AIN2
|
||||
DDRD |= (1 << PD3);
|
||||
|
||||
PORTB &= ~(1 << PB3);
|
||||
PORTD &= ~(1 << PD3);
|
||||
|
||||
// Timer2 Fast PWM
|
||||
TCCR2A = 0;
|
||||
TCCR2B = 0;
|
||||
TCNT2 = 0;
|
||||
|
||||
OCR2A = 0;
|
||||
OCR2B = 0;
|
||||
|
||||
TCCR2A =
|
||||
(1 << COM2A1) |
|
||||
(1 << COM2B1) |
|
||||
(1 << WGM21) |
|
||||
(1 << WGM20);
|
||||
|
||||
// Prescaler = 8
|
||||
// PWM frequency ~= 7.8 kHz
|
||||
TCCR2B =
|
||||
(1 << CS21);
|
||||
}
|
||||
|
||||
void Motor_driver::enable_pwm_outputs()
|
||||
{
|
||||
// Non-inverting PWM on OC2A and OC2B
|
||||
TCCR2A |=
|
||||
(1 << COM2A1) |
|
||||
(1 << COM2B1);
|
||||
|
||||
TCCR2A &=
|
||||
~((1 << COM2A0) |
|
||||
(1 << COM2B0));
|
||||
}
|
||||
|
||||
unsigned char Motor_driver::magnitude(
|
||||
double value
|
||||
) const
|
||||
{
|
||||
if (value < 0.0)
|
||||
{
|
||||
value = -value;
|
||||
}
|
||||
|
||||
if (value > 255.0)
|
||||
{
|
||||
value = 255.0;
|
||||
}
|
||||
|
||||
return static_cast<unsigned char>(value);
|
||||
}
|
||||
|
||||
void Motor_driver::set_command(double command)
|
||||
{
|
||||
if (command > 255.0)
|
||||
{
|
||||
command = 255.0;
|
||||
}
|
||||
|
||||
if (command < -255.0)
|
||||
{
|
||||
command = -255.0;
|
||||
}
|
||||
|
||||
currentCommand = command;
|
||||
|
||||
enable_pwm_outputs();
|
||||
|
||||
unsigned char pwm =
|
||||
magnitude(command);
|
||||
|
||||
if (command > 0.0)
|
||||
{
|
||||
// Same direction that previously gave positive RPM:
|
||||
// AIN1 = LOW
|
||||
// AIN2 = PWM
|
||||
OCR2A = 0;
|
||||
OCR2B = pwm;
|
||||
}
|
||||
else if (command < 0.0)
|
||||
{
|
||||
OCR2A = pwm;
|
||||
OCR2B = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
OCR2A = 0;
|
||||
OCR2B = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Motor_driver::coast()
|
||||
{
|
||||
currentCommand = 0.0;
|
||||
|
||||
enable_pwm_outputs();
|
||||
|
||||
OCR2A = 0;
|
||||
OCR2B = 0;
|
||||
}
|
||||
|
||||
void Motor_driver::brake()
|
||||
{
|
||||
currentCommand = 0.0;
|
||||
|
||||
// Disconnect Timer2 PWM from the pins.
|
||||
TCCR2A &=
|
||||
~((1 << COM2A1) |
|
||||
(1 << COM2A0) |
|
||||
(1 << COM2B1) |
|
||||
(1 << COM2B0));
|
||||
|
||||
// DRV8833: both inputs HIGH gives braking.
|
||||
PORTB |= (1 << PB3);
|
||||
PORTD |= (1 << PD3);
|
||||
}
|
||||
|
||||
double Motor_driver::command() const
|
||||
{
|
||||
return currentCommand;
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <operational.h>
|
||||
#include <context.h>
|
||||
|
||||
Operational::Operational(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED
|
||||
)
|
||||
: speedController(speedController),
|
||||
statusLED(statusLED),
|
||||
initializationState(nullptr),
|
||||
preOperationalState(nullptr),
|
||||
stoppedState(nullptr),
|
||||
logCounter(0),
|
||||
sampleCounter(0)
|
||||
{
|
||||
}
|
||||
|
||||
const char* Operational::name() const
|
||||
{
|
||||
return "Operational";
|
||||
}
|
||||
|
||||
void Operational::on_entry()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.println("Operational");
|
||||
|
||||
statusLED.set_hi();
|
||||
|
||||
logCounter = 0;
|
||||
sampleCounter = 0;
|
||||
|
||||
speedController.start();
|
||||
|
||||
Serial.println(
|
||||
"DATA,time_ms,reference_rpm,actual_rpm,control"
|
||||
);
|
||||
}
|
||||
|
||||
void Operational::on_do()
|
||||
{
|
||||
speedController.update();
|
||||
|
||||
sampleCounter++;
|
||||
logCounter++;
|
||||
|
||||
// 5 controller ticks × 4 ms = 20 ms
|
||||
// => 50 Hz logging rate.
|
||||
if (logCounter >= 5)
|
||||
{
|
||||
logCounter = 0;
|
||||
|
||||
const uint32_t time_ms =
|
||||
sampleCounter * 4UL;
|
||||
|
||||
Serial.print("DATA,");
|
||||
Serial.print(time_ms);
|
||||
Serial.print(",");
|
||||
|
||||
Serial.print(
|
||||
speedController.reference(),
|
||||
2
|
||||
);
|
||||
|
||||
Serial.print(",");
|
||||
|
||||
Serial.print(
|
||||
speedController.actual_rpm(),
|
||||
2
|
||||
);
|
||||
|
||||
Serial.print(",");
|
||||
|
||||
Serial.println(
|
||||
speedController.control_output(),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Operational::on_exit()
|
||||
{
|
||||
speedController.stop();
|
||||
|
||||
statusLED.set_lo();
|
||||
}
|
||||
|
||||
void Operational::reset()
|
||||
{
|
||||
if (initializationState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*initializationState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Operational::set_pre_operational()
|
||||
{
|
||||
if (preOperationalState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*preOperationalState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Operational::fault()
|
||||
{
|
||||
if (stoppedState != nullptr)
|
||||
{
|
||||
Serial.println(
|
||||
"FAULT detected"
|
||||
);
|
||||
|
||||
context_->transition_to(
|
||||
*stoppedState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Operational::set_initialization_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
initializationState = &state;
|
||||
}
|
||||
|
||||
void Operational::set_pre_operational_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
preOperationalState = &state;
|
||||
}
|
||||
|
||||
void Operational::set_stopped_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
stoppedState = &state;
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#include <p_controller.h>
|
||||
|
||||
P_controller::P_controller(
|
||||
double kp
|
||||
)
|
||||
: kp(kp)
|
||||
{
|
||||
}
|
||||
|
||||
double P_controller::update(
|
||||
double reference,
|
||||
double actual
|
||||
)
|
||||
{
|
||||
const double error =
|
||||
reference - actual;
|
||||
|
||||
const double output =
|
||||
kp * error;
|
||||
|
||||
return limit(output);
|
||||
}
|
||||
|
||||
void P_controller::reset()
|
||||
{
|
||||
}
|
||||
|
||||
const char* P_controller::name() const
|
||||
{
|
||||
return "P";
|
||||
}
|
||||
|
||||
void P_controller::set_kp(
|
||||
double value
|
||||
)
|
||||
{
|
||||
if (value > 0.0)
|
||||
{
|
||||
kp = value;
|
||||
}
|
||||
}
|
||||
|
||||
double P_controller::get_kp() const
|
||||
{
|
||||
return kp;
|
||||
}
|
||||
|
||||
double P_controller::limit(
|
||||
double value
|
||||
) const
|
||||
{
|
||||
if (value > 255.0)
|
||||
{
|
||||
return 255.0;
|
||||
}
|
||||
|
||||
if (value < -255.0)
|
||||
{
|
||||
return -255.0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
#include <pi_controller.h>
|
||||
|
||||
PI_controller::PI_controller(
|
||||
double kp,
|
||||
double ti,
|
||||
double timestep
|
||||
)
|
||||
: kp(kp),
|
||||
ti(ti),
|
||||
timestep(timestep),
|
||||
integral(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
double PI_controller::update(
|
||||
double reference,
|
||||
double actual
|
||||
)
|
||||
{
|
||||
const double error =
|
||||
reference - actual;
|
||||
|
||||
// Candidate integral:
|
||||
// E(k) = E(k-1) + e(k)T
|
||||
const double candidateIntegral =
|
||||
integral + error * timestep;
|
||||
|
||||
// Candidate PI output:
|
||||
// u = Kp * (e + E/Ti)
|
||||
const double candidateOutput =
|
||||
kp *
|
||||
(
|
||||
error +
|
||||
candidateIntegral / ti
|
||||
);
|
||||
|
||||
// -----------------------------------------
|
||||
// Conditional integration / anti-windup
|
||||
// -----------------------------------------
|
||||
|
||||
// Integrate normally if the candidate
|
||||
// controller output is not saturated.
|
||||
if (
|
||||
candidateOutput <= 255.0 &&
|
||||
candidateOutput >= -255.0
|
||||
)
|
||||
{
|
||||
integral =
|
||||
candidateIntegral;
|
||||
}
|
||||
|
||||
// If positively saturated, only integrate
|
||||
// if the error is driving the output back down.
|
||||
else if (
|
||||
candidateOutput > 255.0 &&
|
||||
error < 0.0
|
||||
)
|
||||
{
|
||||
integral =
|
||||
candidateIntegral;
|
||||
}
|
||||
|
||||
// If negatively saturated, only integrate
|
||||
// if the error is driving the output back up.
|
||||
else if (
|
||||
candidateOutput < -255.0 &&
|
||||
error > 0.0
|
||||
)
|
||||
{
|
||||
integral =
|
||||
candidateIntegral;
|
||||
}
|
||||
|
||||
const double output =
|
||||
kp *
|
||||
(
|
||||
error +
|
||||
integral / ti
|
||||
);
|
||||
|
||||
return limit(output);
|
||||
}
|
||||
|
||||
void PI_controller::reset()
|
||||
{
|
||||
integral = 0.0;
|
||||
}
|
||||
|
||||
const char* PI_controller::name() const
|
||||
{
|
||||
return "PI";
|
||||
}
|
||||
|
||||
void PI_controller::set_kp(
|
||||
double value
|
||||
)
|
||||
{
|
||||
kp = value;
|
||||
}
|
||||
|
||||
double PI_controller::get_kp() const
|
||||
{
|
||||
return kp;
|
||||
}
|
||||
|
||||
void PI_controller::set_ti(
|
||||
double value
|
||||
)
|
||||
{
|
||||
if (value > 0.0)
|
||||
{
|
||||
ti = value;
|
||||
}
|
||||
}
|
||||
|
||||
double PI_controller::get_ti() const
|
||||
{
|
||||
return ti;
|
||||
}
|
||||
|
||||
double PI_controller::get_timestep() const
|
||||
{
|
||||
return timestep;
|
||||
}
|
||||
|
||||
double PI_controller::limit(
|
||||
double value
|
||||
) const
|
||||
{
|
||||
if (value > 255.0)
|
||||
{
|
||||
return 255.0;
|
||||
}
|
||||
|
||||
if (value < -255.0)
|
||||
{
|
||||
return -255.0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <pre_operational.h>
|
||||
#include <context.h>
|
||||
|
||||
PreOperational::PreOperational(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED,
|
||||
P_controller& pController,
|
||||
PI_controller& piController
|
||||
)
|
||||
: speedController(speedController),
|
||||
statusLED(statusLED),
|
||||
pController(pController),
|
||||
piController(piController),
|
||||
initializationState(nullptr),
|
||||
operationalState(nullptr),
|
||||
ledCounter(0),
|
||||
piSelected(false)
|
||||
{
|
||||
}
|
||||
|
||||
const char* PreOperational::name() const
|
||||
{
|
||||
return "PreOperational";
|
||||
}
|
||||
|
||||
void PreOperational::on_entry()
|
||||
{
|
||||
// Motor must not run while configuring.
|
||||
speedController.stop();
|
||||
|
||||
statusLED.set_lo();
|
||||
|
||||
ledCounter = 0;
|
||||
|
||||
Serial.println();
|
||||
Serial.println("PreOperational");
|
||||
Serial.println("Motor control disabled.");
|
||||
|
||||
print_configuration();
|
||||
|
||||
Serial.println();
|
||||
Serial.println("Commands:");
|
||||
Serial.println("p = select P controller");
|
||||
Serial.println("i = select PI controller");
|
||||
Serial.println("k<number> = set Kp");
|
||||
Serial.println("t<number> = set Ti");
|
||||
Serial.println("o = set operational");
|
||||
Serial.println("r = reset");
|
||||
}
|
||||
|
||||
void PreOperational::on_do()
|
||||
{
|
||||
// State activity runs every 4 ms.
|
||||
//
|
||||
// 1 Hz LED:
|
||||
// full period = 1000 ms
|
||||
// toggle every 500 ms
|
||||
//
|
||||
// 500 ms / 4 ms = 125 ticks
|
||||
|
||||
ledCounter++;
|
||||
|
||||
if (ledCounter >= 125)
|
||||
{
|
||||
ledCounter = 0;
|
||||
|
||||
statusLED.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
void PreOperational::on_exit()
|
||||
{
|
||||
statusLED.set_lo();
|
||||
}
|
||||
|
||||
void PreOperational::reset()
|
||||
{
|
||||
if (initializationState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*initializationState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void PreOperational::set_operational()
|
||||
{
|
||||
if (operationalState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*operationalState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void PreOperational::select_p()
|
||||
{
|
||||
piSelected = false;
|
||||
|
||||
speedController.set_controller(
|
||||
pController
|
||||
);
|
||||
|
||||
Serial.println(
|
||||
"Controller selected: P"
|
||||
);
|
||||
|
||||
print_configuration();
|
||||
}
|
||||
|
||||
void PreOperational::select_pi()
|
||||
{
|
||||
piSelected = true;
|
||||
|
||||
speedController.set_controller(
|
||||
piController
|
||||
);
|
||||
|
||||
Serial.println(
|
||||
"Controller selected: PI"
|
||||
);
|
||||
|
||||
print_configuration();
|
||||
}
|
||||
|
||||
void PreOperational::set_kp(
|
||||
double value
|
||||
)
|
||||
{
|
||||
if (value <= 0.0)
|
||||
{
|
||||
Serial.println(
|
||||
"Invalid Kp."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (piSelected)
|
||||
{
|
||||
piController.set_kp(
|
||||
value
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
pController.set_kp(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
Serial.print("Kp set to: ");
|
||||
Serial.println(value, 4);
|
||||
}
|
||||
|
||||
void PreOperational::set_ti(
|
||||
double value
|
||||
)
|
||||
{
|
||||
if (value <= 0.0)
|
||||
{
|
||||
Serial.println(
|
||||
"Invalid Ti."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
piController.set_ti(
|
||||
value
|
||||
);
|
||||
|
||||
Serial.print("Ti set to: ");
|
||||
Serial.print(value, 4);
|
||||
Serial.println(" s");
|
||||
}
|
||||
|
||||
void PreOperational::set_initialization_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
initializationState = &state;
|
||||
}
|
||||
|
||||
void PreOperational::set_operational_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
operationalState = &state;
|
||||
}
|
||||
|
||||
void PreOperational::print_configuration()
|
||||
{
|
||||
Serial.print("Controller: ");
|
||||
|
||||
if (piSelected)
|
||||
{
|
||||
Serial.println("PI");
|
||||
|
||||
Serial.print("Kp = ");
|
||||
Serial.println(
|
||||
piController.get_kp(),
|
||||
4
|
||||
);
|
||||
|
||||
Serial.print("Ti = ");
|
||||
Serial.print(
|
||||
piController.get_ti(),
|
||||
4
|
||||
);
|
||||
Serial.println(" s");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("P");
|
||||
|
||||
Serial.print("Kp = ");
|
||||
Serial.println(
|
||||
pController.get_kp(),
|
||||
4
|
||||
);
|
||||
}
|
||||
}
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/pwm.h> // from A4.1
|
||||
#include <zephyr/sys/printk.h> // so i can print using serial monitor
|
||||
|
||||
//#include <Arduino.h> removed since it was for arduino
|
||||
|
||||
// sdo configuration
|
||||
// rpdo1 real time
|
||||
|
||||
|
||||
#include <digital_in.h>
|
||||
#include <digital_out.h>
|
||||
#include <encoder.h>
|
||||
|
||||
#include <control_timer.h>
|
||||
#include <speed_estimator.h>
|
||||
|
||||
#include <motor_driver.h>
|
||||
|
||||
#include <p_controller.h>
|
||||
#include <pi_controller.h>
|
||||
#include <speed_controller.h>
|
||||
|
||||
#include <context.h>
|
||||
#include <initialization.h>
|
||||
#include <pre_operational.h>
|
||||
#include <operational.h>
|
||||
#include <stopped.h>
|
||||
|
||||
// --------------------------------------------------
|
||||
// Hardware / drivers
|
||||
// --------------------------------------------------
|
||||
|
||||
// Encoder:
|
||||
// PB0 = D8
|
||||
// PB1 = D9
|
||||
Encoder encoder(0, 1);
|
||||
|
||||
// Emergency stop:
|
||||
// PB2 = D10
|
||||
// Internal pull-up enabled.
|
||||
// LOW = emergency stop pressed.
|
||||
Digital_in emergencyStop(2);
|
||||
|
||||
// Status LED:
|
||||
// PB5 = D13
|
||||
Digital_out statusLED(5);
|
||||
|
||||
Control_timer controlTimer;
|
||||
|
||||
Motor_driver motorDriver;
|
||||
|
||||
// Encoder resolution = 2100 counts/revolution
|
||||
// Controller period = 4 ms = 250 Hz
|
||||
Speed_estimator speedEstimator(
|
||||
2100.0,
|
||||
0.004
|
||||
);
|
||||
|
||||
// --------------------------------------------------
|
||||
// Controllers
|
||||
// --------------------------------------------------
|
||||
|
||||
P_controller pController(
|
||||
40.0
|
||||
);
|
||||
|
||||
PI_controller piController(
|
||||
40.0, // Kp
|
||||
0.50, // Ti [s]
|
||||
0.004 // timestep [s]
|
||||
);
|
||||
|
||||
Speed_controller speedController(
|
||||
encoder,
|
||||
speedEstimator,
|
||||
motorDriver,
|
||||
pController
|
||||
);
|
||||
|
||||
// --------------------------------------------------
|
||||
// State machine states
|
||||
// --------------------------------------------------
|
||||
|
||||
Initialization initialization(
|
||||
speedController,
|
||||
statusLED
|
||||
);
|
||||
|
||||
PreOperational preOperational(
|
||||
speedController,
|
||||
statusLED,
|
||||
pController,
|
||||
piController
|
||||
);
|
||||
|
||||
Operational operational(
|
||||
speedController,
|
||||
statusLED
|
||||
);
|
||||
|
||||
Stopped stopped(
|
||||
speedController,
|
||||
statusLED
|
||||
);
|
||||
|
||||
Context* stateMachine = nullptr;
|
||||
|
||||
// --------------------------------------------------
|
||||
// Serial command buffer
|
||||
// --------------------------------------------------
|
||||
|
||||
char commandBuffer[32];
|
||||
uint8_t commandIndex = 0;
|
||||
|
||||
// --------------------------------------------------
|
||||
// Process one complete serial command
|
||||
// --------------------------------------------------
|
||||
|
||||
void processCommand(const char* command)
|
||||
{
|
||||
if (command[0] == '\0')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Single-character commands
|
||||
// --------------------------------------------------
|
||||
|
||||
if (
|
||||
command[0] == 'o' &&
|
||||
command[1] == '\0'
|
||||
)
|
||||
{
|
||||
stateMachine->set_operational();
|
||||
}
|
||||
|
||||
else if (
|
||||
command[0] == 'c' &&
|
||||
command[1] == '\0'
|
||||
)
|
||||
{
|
||||
stateMachine->set_pre_operational();
|
||||
}
|
||||
|
||||
else if (
|
||||
command[0] == 'r' &&
|
||||
command[1] == '\0'
|
||||
)
|
||||
{
|
||||
stateMachine->reset();
|
||||
}
|
||||
|
||||
else if (
|
||||
command[0] == 'p' &&
|
||||
command[1] == '\0'
|
||||
)
|
||||
{
|
||||
stateMachine->select_p();
|
||||
}
|
||||
|
||||
else if (
|
||||
command[0] == 'i' &&
|
||||
command[1] == '\0'
|
||||
)
|
||||
{
|
||||
stateMachine->select_pi();
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// k<number> = set Kp
|
||||
// Example:
|
||||
// k30.625
|
||||
// --------------------------------------------------
|
||||
|
||||
else if (command[0] == 'k')
|
||||
{
|
||||
char* endPointer;
|
||||
|
||||
double value =
|
||||
strtod(
|
||||
command + 1,
|
||||
&endPointer
|
||||
);
|
||||
|
||||
if (
|
||||
endPointer == command + 1 ||
|
||||
*endPointer != '\0'
|
||||
)
|
||||
{
|
||||
Serial.println(
|
||||
"Invalid Kp command."
|
||||
);
|
||||
|
||||
Serial.println(
|
||||
"Example: k30.625"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stateMachine->set_kp(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// t<number> = set Ti
|
||||
// Example:
|
||||
// t0.4
|
||||
// --------------------------------------------------
|
||||
|
||||
else if (command[0] == 't')
|
||||
{
|
||||
char* endPointer;
|
||||
|
||||
double value =
|
||||
strtod(
|
||||
command + 1,
|
||||
&endPointer
|
||||
);
|
||||
|
||||
if (
|
||||
endPointer == command + 1 ||
|
||||
*endPointer != '\0'
|
||||
)
|
||||
{
|
||||
Serial.println(
|
||||
"Invalid Ti command."
|
||||
);
|
||||
|
||||
Serial.println(
|
||||
"Example: t0.4"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stateMachine->set_ti(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Serial.print(
|
||||
"Unknown command: "
|
||||
);
|
||||
|
||||
Serial.println(
|
||||
command
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Non-blocking serial command reader
|
||||
// --------------------------------------------------
|
||||
|
||||
void readSerialCommands()
|
||||
{
|
||||
while (Serial.available() > 0)
|
||||
{
|
||||
char character =
|
||||
Serial.read();
|
||||
|
||||
// Ignore carriage return
|
||||
if (character == '\r')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Newline = end of command
|
||||
if (character == '\n')
|
||||
{
|
||||
commandBuffer[
|
||||
commandIndex
|
||||
] = '\0';
|
||||
|
||||
processCommand(
|
||||
commandBuffer
|
||||
);
|
||||
|
||||
commandIndex = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add character to buffer
|
||||
if (
|
||||
commandIndex <
|
||||
sizeof(commandBuffer) - 1
|
||||
)
|
||||
{
|
||||
commandBuffer[
|
||||
commandIndex
|
||||
] = character;
|
||||
|
||||
commandIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Buffer overflow
|
||||
commandIndex = 0;
|
||||
|
||||
Serial.println(
|
||||
"Command too long."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Setup
|
||||
// --------------------------------------------------
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
statusLED.init();
|
||||
|
||||
emergencyStop.init();
|
||||
|
||||
encoder.init();
|
||||
|
||||
motorDriver.init();
|
||||
|
||||
// Initial target speed
|
||||
speedController.set_reference(
|
||||
50.0
|
||||
);
|
||||
|
||||
// --------------------------------------------------
|
||||
// Wire state transitions
|
||||
// --------------------------------------------------
|
||||
|
||||
initialization.set_pre_operational_state(
|
||||
preOperational
|
||||
);
|
||||
|
||||
preOperational.set_initialization_state(
|
||||
initialization
|
||||
);
|
||||
|
||||
preOperational.set_operational_state(
|
||||
operational
|
||||
);
|
||||
|
||||
operational.set_initialization_state(
|
||||
initialization
|
||||
);
|
||||
|
||||
operational.set_pre_operational_state(
|
||||
preOperational
|
||||
);
|
||||
|
||||
operational.set_stopped_state(
|
||||
stopped
|
||||
);
|
||||
|
||||
stopped.set_initialization_state(
|
||||
initialization
|
||||
);
|
||||
|
||||
stopped.set_pre_operational_state(
|
||||
preOperational
|
||||
);
|
||||
|
||||
stopped.set_operational_state(
|
||||
operational
|
||||
);
|
||||
|
||||
// --------------------------------------------------
|
||||
// Create state machine
|
||||
// --------------------------------------------------
|
||||
|
||||
static Context context(
|
||||
initialization
|
||||
);
|
||||
|
||||
stateMachine = &context;
|
||||
|
||||
// Fixed 250 Hz timing
|
||||
controlTimer.init();
|
||||
|
||||
Serial.println();
|
||||
Serial.println(
|
||||
"Project 3 - Part 4"
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Main loop
|
||||
// --------------------------------------------------
|
||||
|
||||
void loop()
|
||||
{
|
||||
// --------------------------------------------------
|
||||
// Emergency stop
|
||||
// --------------------------------------------------
|
||||
|
||||
if (emergencyStop.is_lo())
|
||||
{
|
||||
stateMachine->fault();
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Serial interface
|
||||
// --------------------------------------------------
|
||||
|
||||
readSerialCommands();
|
||||
|
||||
// --------------------------------------------------
|
||||
// Fixed 250 Hz state-machine activity
|
||||
// --------------------------------------------------
|
||||
|
||||
if (controlTimer.take_tick())
|
||||
{
|
||||
stateMachine->do_work();
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
#include <speed_controller.h>
|
||||
|
||||
Speed_controller::Speed_controller(
|
||||
Encoder& encoder,
|
||||
Speed_estimator& estimator,
|
||||
Motor_driver& motor,
|
||||
Controller& controller
|
||||
)
|
||||
: encoder(encoder),
|
||||
estimator(estimator),
|
||||
motor(motor),
|
||||
controller(&controller),
|
||||
running(false),
|
||||
referenceRPM(0.0),
|
||||
actualRPM(0.0),
|
||||
controlOutput(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
void Speed_controller::start()
|
||||
{
|
||||
estimator.reset(
|
||||
encoder.position()
|
||||
);
|
||||
|
||||
controller->reset();
|
||||
|
||||
actualRPM = 0.0;
|
||||
controlOutput = 0.0;
|
||||
|
||||
running = true;
|
||||
|
||||
motor.coast();
|
||||
}
|
||||
|
||||
void Speed_controller::stop()
|
||||
{
|
||||
running = false;
|
||||
|
||||
controlOutput = 0.0;
|
||||
|
||||
motor.coast();
|
||||
}
|
||||
|
||||
void Speed_controller::brake()
|
||||
{
|
||||
running = false;
|
||||
|
||||
controlOutput = 0.0;
|
||||
|
||||
motor.brake();
|
||||
}
|
||||
|
||||
void Speed_controller::update()
|
||||
{
|
||||
if (!running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
actualRPM =
|
||||
estimator.update(
|
||||
encoder.position()
|
||||
);
|
||||
|
||||
controlOutput =
|
||||
controller->update(
|
||||
referenceRPM,
|
||||
actualRPM
|
||||
);
|
||||
|
||||
motor.set_command(
|
||||
controlOutput
|
||||
);
|
||||
}
|
||||
|
||||
void Speed_controller::set_reference(
|
||||
double rpm
|
||||
)
|
||||
{
|
||||
referenceRPM = rpm;
|
||||
}
|
||||
|
||||
double Speed_controller::reference() const
|
||||
{
|
||||
return referenceRPM;
|
||||
}
|
||||
|
||||
void Speed_controller::set_controller(
|
||||
Controller& newController
|
||||
)
|
||||
{
|
||||
controller = &newController;
|
||||
|
||||
controller->reset();
|
||||
}
|
||||
|
||||
double Speed_controller::actual_rpm() const
|
||||
{
|
||||
return actualRPM;
|
||||
}
|
||||
|
||||
double Speed_controller::control_output() const
|
||||
{
|
||||
return controlOutput;
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
#include <speed_estimator.h>
|
||||
|
||||
Speed_estimator::Speed_estimator(
|
||||
double countsPerRevolution,
|
||||
double sampleTimeSeconds
|
||||
)
|
||||
: countsPerRevolution(countsPerRevolution),
|
||||
sampleTime(sampleTimeSeconds),
|
||||
previousPosition(0),
|
||||
rollingCounts(0),
|
||||
historyIndex(0),
|
||||
samplesFilled(0),
|
||||
currentRPM(0.0)
|
||||
{
|
||||
for (uint8_t i = 0; i < WINDOW_SIZE; i++)
|
||||
{
|
||||
countHistory[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Speed_estimator::reset(int32_t initialPosition)
|
||||
{
|
||||
previousPosition = initialPosition;
|
||||
|
||||
rollingCounts = 0;
|
||||
historyIndex = 0;
|
||||
samplesFilled = 0;
|
||||
currentRPM = 0.0;
|
||||
|
||||
for (uint8_t i = 0; i < WINDOW_SIZE; i++)
|
||||
{
|
||||
countHistory[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
double Speed_estimator::update(int32_t currentPosition)
|
||||
{
|
||||
int32_t deltaCount =
|
||||
currentPosition - previousPosition;
|
||||
|
||||
previousPosition = currentPosition;
|
||||
|
||||
rollingCounts -=
|
||||
countHistory[historyIndex];
|
||||
|
||||
countHistory[historyIndex] =
|
||||
deltaCount;
|
||||
|
||||
rollingCounts += deltaCount;
|
||||
|
||||
historyIndex++;
|
||||
|
||||
if (historyIndex >= WINDOW_SIZE)
|
||||
{
|
||||
historyIndex = 0;
|
||||
}
|
||||
|
||||
if (samplesFilled < WINDOW_SIZE)
|
||||
{
|
||||
samplesFilled++;
|
||||
}
|
||||
|
||||
double windowTime =
|
||||
samplesFilled * sampleTime;
|
||||
|
||||
currentRPM =
|
||||
(rollingCounts * 60.0) /
|
||||
(countsPerRevolution * windowTime);
|
||||
|
||||
return currentRPM;
|
||||
}
|
||||
|
||||
double Speed_estimator::rpm() const
|
||||
{
|
||||
return currentRPM;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <stopped.h>
|
||||
#include <context.h>
|
||||
|
||||
Stopped::Stopped(
|
||||
Speed_controller& speedController,
|
||||
Digital_out& statusLED
|
||||
)
|
||||
: speedController(speedController),
|
||||
statusLED(statusLED),
|
||||
initializationState(nullptr),
|
||||
preOperationalState(nullptr),
|
||||
operationalState(nullptr),
|
||||
ledCounter(0)
|
||||
{
|
||||
}
|
||||
|
||||
const char* Stopped::name() const
|
||||
{
|
||||
return "Stopped";
|
||||
}
|
||||
|
||||
void Stopped::on_entry()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.println("Stopped");
|
||||
Serial.println(
|
||||
"Emergency stop - motor brake active"
|
||||
);
|
||||
|
||||
speedController.brake();
|
||||
|
||||
statusLED.set_hi();
|
||||
|
||||
ledCounter = 0;
|
||||
}
|
||||
|
||||
void Stopped::on_do()
|
||||
{
|
||||
// State activity runs every 4 ms.
|
||||
//
|
||||
// 2 Hz LED:
|
||||
// toggle every ~250 ms
|
||||
//
|
||||
// 250 / 4 = 62.5 ticks
|
||||
|
||||
ledCounter++;
|
||||
|
||||
if (ledCounter >= 63)
|
||||
{
|
||||
ledCounter = 0;
|
||||
|
||||
statusLED.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
void Stopped::on_exit()
|
||||
{
|
||||
statusLED.set_lo();
|
||||
}
|
||||
|
||||
void Stopped::reset()
|
||||
{
|
||||
if (initializationState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*initializationState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Stopped::set_pre_operational()
|
||||
{
|
||||
if (preOperationalState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*preOperationalState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Stopped::set_operational()
|
||||
{
|
||||
if (operationalState != nullptr)
|
||||
{
|
||||
context_->transition_to(
|
||||
*operationalState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Stopped::set_initialization_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
initializationState = &state;
|
||||
}
|
||||
|
||||
void Stopped::set_pre_operational_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
preOperationalState = &state;
|
||||
}
|
||||
|
||||
void Stopped::set_operational_state(
|
||||
State& state
|
||||
)
|
||||
{
|
||||
operationalState = &state;
|
||||
}
|
||||
@@ -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
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include <controller.h>
|
||||
#include <pi_controller.h>
|
||||
|
||||
// --------------------------------------------------
|
||||
// Unity setup
|
||||
// --------------------------------------------------
|
||||
|
||||
void setUp()
|
||||
{
|
||||
}
|
||||
|
||||
void tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Normal-flow tests
|
||||
// --------------------------------------------------
|
||||
|
||||
void test_zero_error_gives_zero_output()
|
||||
{
|
||||
PI_controller controller(
|
||||
10.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
50.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
0.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
void test_positive_error_gives_positive_output()
|
||||
{
|
||||
PI_controller controller(
|
||||
10.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// error = 2
|
||||
// E = 2 * 0.1 = 0.2
|
||||
// u = 10 * (2 + 0.2 / 1)
|
||||
// = 22
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
52.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
22.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
void test_negative_error_gives_negative_output()
|
||||
{
|
||||
PI_controller controller(
|
||||
10.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// error = -2
|
||||
// E = -2 * 0.1 = -0.2
|
||||
// u = 10 * (-2 - 0.2)
|
||||
// = -22
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
48.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
-22.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
void test_integral_accumulates_over_time()
|
||||
{
|
||||
PI_controller controller(
|
||||
10.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
double first =
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
double second =
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
double third =
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
// After first update:
|
||||
// E = 0.1
|
||||
// u = 10 * (1 + 0.1) = 11
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
11.0,
|
||||
first
|
||||
);
|
||||
|
||||
// After second update:
|
||||
// E = 0.2
|
||||
// u = 12
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
12.0,
|
||||
second
|
||||
);
|
||||
|
||||
// After third update:
|
||||
// E = 0.3
|
||||
// u = 13
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
13.0,
|
||||
third
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Saturation tests
|
||||
// --------------------------------------------------
|
||||
|
||||
void test_positive_output_is_limited()
|
||||
{
|
||||
PI_controller controller(
|
||||
100.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
60.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
255.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
void test_negative_output_is_limited()
|
||||
{
|
||||
PI_controller controller(
|
||||
100.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
40.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
-255.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Anti-windup tests
|
||||
// --------------------------------------------------
|
||||
|
||||
void test_positive_saturation_does_not_wind_up()
|
||||
{
|
||||
PI_controller controller(
|
||||
100.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// Repeatedly demand an unreachable positive output.
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
controller.update(
|
||||
60.0,
|
||||
50.0
|
||||
);
|
||||
}
|
||||
|
||||
// With anti-windup the large saturated error
|
||||
// should not have accumulated.
|
||||
//
|
||||
// New error = +1
|
||||
// E = 1 * 0.1 = 0.1
|
||||
// u = 100 * (1 + 0.1) = 110
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
110.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
void test_negative_saturation_does_not_wind_up()
|
||||
{
|
||||
PI_controller controller(
|
||||
100.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// Repeatedly demand an unreachable negative output.
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
controller.update(
|
||||
40.0,
|
||||
50.0
|
||||
);
|
||||
}
|
||||
|
||||
// New error = -1
|
||||
// E = -0.1
|
||||
// u = 100 * (-1 - 0.1) = -110
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
49.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
-110.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
void test_controller_recovers_after_saturation()
|
||||
{
|
||||
PI_controller controller(
|
||||
100.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// Cause positive saturation.
|
||||
controller.update(
|
||||
60.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
// Return to a small error.
|
||||
// Anti-windup should allow immediate recovery.
|
||||
double output =
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
110.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Reset test
|
||||
// --------------------------------------------------
|
||||
|
||||
void test_reset_clears_integral()
|
||||
{
|
||||
PI_controller controller(
|
||||
10.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// Build up integral contribution.
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
// Clear stored integral.
|
||||
controller.reset();
|
||||
|
||||
// This should now behave exactly like
|
||||
// the first +1 error sample again.
|
||||
//
|
||||
// E = 0.1
|
||||
// u = 10 * (1 + 0.1) = 11
|
||||
|
||||
double output =
|
||||
controller.update(
|
||||
51.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
11.0,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Polymorphism test
|
||||
// --------------------------------------------------
|
||||
|
||||
void test_pi_controller_works_through_controller_interface()
|
||||
{
|
||||
PI_controller pi(
|
||||
10.0,
|
||||
1.0,
|
||||
0.1
|
||||
);
|
||||
|
||||
// Access PI controller only through
|
||||
// the common Controller interface.
|
||||
Controller* controller =
|
||||
π
|
||||
|
||||
double output =
|
||||
controller->update(
|
||||
52.0,
|
||||
50.0
|
||||
);
|
||||
|
||||
TEST_ASSERT_DOUBLE_WITHIN(
|
||||
0.001,
|
||||
22.0,
|
||||
output
|
||||
);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"PI",
|
||||
controller->name()
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Test runner
|
||||
// --------------------------------------------------
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(
|
||||
test_zero_error_gives_zero_output
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_positive_error_gives_positive_output
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_negative_error_gives_negative_output
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_integral_accumulates_over_time
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_positive_output_is_limited
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_negative_output_is_limited
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_positive_saturation_does_not_wind_up
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_negative_saturation_does_not_wind_up
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_controller_recovers_after_saturation
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_reset_clears_integral
|
||||
);
|
||||
|
||||
RUN_TEST(
|
||||
test_pi_controller_works_through_controller_interface
|
||||
);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
||||
project(A4.1.1-GPIO-in-Zephyr-RTOS)
|
||||
|
||||
FILE(GLOB app_sources ../src/*.c*)
|
||||
target_sources(app PRIVATE ${app_sources})
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||
#include <zephyr/dt-bindings/pwm/pwm.h>
|
||||
|
||||
/ {
|
||||
aliases {
|
||||
sw1 = &user_button;
|
||||
|
||||
encoder-c1 = &encoder_c1;
|
||||
encoder-c2 = &encoder_c2;
|
||||
|
||||
motor-ain1 = &motor_ain1;
|
||||
motor-ain2 = &motor_ain2;
|
||||
|
||||
motor-flt = &motor_flt;
|
||||
|
||||
status-led = &led0;
|
||||
};
|
||||
|
||||
buttons {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
user_button: button_0 {
|
||||
gpios = <&gpiob 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
label = "External button";
|
||||
};
|
||||
};
|
||||
|
||||
encoders {
|
||||
encoder_c1: c1 {
|
||||
gpios = <&gpioX X GPIO_ACTIVE_HIGH>;
|
||||
label = "Encoder C1";
|
||||
};
|
||||
|
||||
encoder_c2: c2 {
|
||||
gpios = <&gpioX X GPIO_ACTIVE_HIGH>;
|
||||
label = "Encoder C2";
|
||||
};
|
||||
};
|
||||
|
||||
motor {
|
||||
motor_ain1: ain1 {
|
||||
pwms = <&pwm1 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
|
||||
label = "Motor AIN1";
|
||||
};
|
||||
|
||||
motor_ain2: ain2 {
|
||||
pwms = <&pwm2 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
|
||||
label = "Motor AIN2";
|
||||
};
|
||||
|
||||
motor_flt: flt {
|
||||
gpios = <&gpioX X GPIO_ACTIVE_LOW>;
|
||||
label = "Motor FLT";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&timers1 {
|
||||
status = "okay";
|
||||
st,prescaler = <79>;
|
||||
|
||||
pwm1: pwm {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&tim1_ch1_pa8>;
|
||||
pinctrl-names = "default";
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_PWM=y
|
||||
Reference in New Issue
Block a user