9 Commits
41 changed files with 2491 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+11
View File
@@ -0,0 +1,11 @@
{
// 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",
"pioarduino.pioarduino-ide"
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"cmake.sourceDirectory": "C:/Users/helgi/H26/ESP/Projects/ESP_Project4_H26/zephyr"
}
+50
View File
@@ -0,0 +1,50 @@
@startuml
' skinparam linetype ortho
' skinparam nodesep 100
skinparam ranksep 80
skinparam state {
BackgroundColor LightBlue
}
[*] --> Initialization
state Initialization
Initialization : Entry / Stop motor
Initialization : Entry / Initialize internal parameters
Initialization : Entry / Reset speed measurement
Initialization : Do / Wait 2 seconds
Initialization : Exit / Send Boot-up message
state PreOperational
PreOperational : Entry / Stop motor
PreOperational : Do / Blink LED at 1 Hz
PreOperational : Do / Accept Kp and Ti configuration
PreOperational : Do / Select P or PI controller [c]
PreOperational : Exit / LED off
state Operational
Operational : Entry / LED on
Operational : Do / Run speed controller
Operational : Exit / Stop motor and LED off
state Stopped
Stopped : Entry / Stop motor
Stopped : Do / Blink LED at 2 Hz
Stopped : Exit / LED off
Initialization -r-> PreOperational : initialization\n complete
' Initialization -d-> Operational : initialization\n complete
PreOperational -d-> Operational : set operational [o]
PreOperational -l--> Initialization : reset [r]
Operational -u-> PreOperational : set pre-operational [p]
Operational -> Initialization : reset [r]
Operational --> Stopped : fault detected
Stopped -l-> Initialization : reset [r]
Stopped -> Operational : set operational [o]
Stopped -u-> PreOperational : set pre-operational [p]
@enduml
+37
View File
@@ -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
+22
View File
@@ -0,0 +1,22 @@
#ifndef DIGITAL_IN_H
#define DIGITAL_IN_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
struct Digital_in {
enum : uint8_t {
ENCODER_C1 = 0,
ENCODER_C2 = 1
};
const struct gpio_dt_spec *gpio;
explicit Digital_in(uint8_t pin_id);
void init();
bool is_hi();
bool is_lo();
};
#endif
+27
View File
@@ -0,0 +1,27 @@
#ifndef DIGITAL_OUT_H
#define DIGITAL_OUT_H
#include<stdint.h>
#include <zephyr/drivers/gpio.h>
struct Digital_out {
// uint8_t pin_mask;
enum : uint8_t {
STATUS_LED = 0
};
explicit Digital_out(uint8_t pin_id);
void init();
void toggle();
void set_hi();
void set_lo();
private:
const struct gpio_dt_spec *gpio;
};
#endif
+31
View File
@@ -0,0 +1,31 @@
#ifndef INITIALIZATION_STATE_H
#define INITIALIZATION_STATE_H
#include <stdint.h>
#include "state.h"
class InitializationState : public State
{
private:
// static const unsigned long WAIT_MS = 2000;
// unsigned long enter_time_ms = 0;
static constexpr int64_t WAIT_MS = 2000;
int64_t enter_time_ms = 0;
public:
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
};
#endif
+98
View File
@@ -0,0 +1,98 @@
#ifndef MOTOR_CONTROLLER_H
#define MOTOR_CONTROLLER_H
#include <stdint.h>
#include "rotary_encoder_driver.h"
#include "motor_driver.h"
#include "p_controller.h"
#include "digital_out.h"
class State;
class MotorController
{
private:
static constexpr double DEFAULT_REFERENCE_RPM = -40.0;
static constexpr double PWM_PER_PERCENT = 255.0 / 100.0;
//static const uint8_t LED_PIN = 5; // PB5 = D13 = LED_BUILTIN
static constexpr uint8_t LED_PIN = Digital_out::STATUS_LED;
State* current_state;
State& initialization_state;
State& pre_operational_state;
State& operational_state;
State& stopped_state;
RotaryEncoderDriver &encoder;
MotorDriver &motor;
P_controller *controller;
Digital_out led;
double reference_rpm;
// Síðasta sýni, notað fyrir CSV-útprentun
double last_actual_rpm;
double last_pwm;
public:
MotorController(
RotaryEncoderDriver &encoder,
MotorDriver &motor,
P_controller &controller,
State &initialization,
State &pre_operational,
State &operational,
State &stopped
);
void begin();
void update();
void command(char c);
void transition_to(State &new_state);
void to_initialization();
void to_pre_operational();
void to_operational();
void to_stopped();
bool fault_active();
// Skilar true þegar nýtt sýni var keyrt í gegnum stýringuna
bool run_speed_controller();
double get_reference_rpm() const { return reference_rpm; }
double get_actual_rpm() const { return last_actual_rpm; }
double get_pwm() const { return last_pwm; }
void stop_motor();
void reset_speed_measurement();
void reset_controller();
void set_Kp(double value);
void set_Ti(double value);
void set_controller(P_controller &new_controller);
void led_on();
void led_off();
};
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef MOTOR_DRIVER_H
#define MOTOR_DRIVER_H
class MotorDriver
{
public:
MotorDriver();
void begin();
void forward();
void reverse();
void stop();
void set(double pwm);
bool fault_active(); // Fault detection for part 2
private:
bool initialized; // Flag that ignores motor commands if setup fails
};
#endif
+29
View File
@@ -0,0 +1,29 @@
#ifndef OPERATIONAL_STATE_H
#define OPERATIONAL_STATE_H
#include "state.h"
#include <stdint.h>
// Sett á 1 til að prenta CSV (time_ms,ref_rpm,actual_rpm,pwm) í Operational,
// t.d. fyrir step og load response. Einnig hægt með build_flags = -D LOG_CSV=1
#ifndef LOG_CSV
#define LOG_CSV 0
#endif
class OperationalState : public State
{
private:
uint32_t sample_count = 0;
public:
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
};
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef P_CONTROLLER_H
#define P_CONTROLLER_H
class P_controller
{
protected:
double Kp;
public:
P_controller(double Kp);
virtual ~P_controller() {}
virtual double update(double ref, double actual);
virtual void reset() {}
virtual void set_Kp(double value)
{
Kp = value;
}
virtual void set_Ti(double value)
{
// P controller does not use Ti
}
};
#endif
+23
View File
@@ -0,0 +1,23 @@
#ifndef PI_CONTROLLER_H
#define PI_CONTROLLER_H
#include "p_controller.h"
class PI_controller : public P_controller
{
private:
static constexpr double U_MAX = 100.0; // %
static constexpr double U_MIN = -100.0; // %
double Ti;
double T;
double integral;
public:
PI_controller(double Kp, double Ti, double T);
double update(double ref, double actual) override;
void reset() override;
void set_Ti(double value) override;
};
#endif
+44
View File
@@ -0,0 +1,44 @@
#ifndef PRE_OPERATIONAL_STATE_H
#define PRE_OPERATIONAL_STATE_H
#include "state.h"
#include "stdint.h"
class P_controller;
class PreOperationalState : public State
{
private:
P_controller &p_controller;
P_controller &pi_controller;
bool pi_selected;
//unsigned long previous_time;
int64_t previous_time;
bool led_state;
//bool waiting_for_kp;
//bool waiting_for_ti;
enum InputMode
{
NONE,
ENTER_KP,
ENTER_TI
};
InputMode input_mode;
char input_buffer[16];
uint8_t input_index;
public:
PreOperationalState(P_controller& p_controller, P_controller& pi_controller);
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
};
#endif
+76
View File
@@ -0,0 +1,76 @@
#ifndef ROTARY_ENCODER_DRIVER_H
#define ROTARY_ENCODER_DRIVER_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
//#include "digital_in.h"
#include "timer_usec.h"
class RotaryEncoderDriver {
public:
static constexpr uint16_t SAMPLE_PERIOD_US = 4000; // Ts = 4 ms
static constexpr float COUNTS_PER_REV = 1050.0f;
RotaryEncoderDriver();
// Returns 0 on success, or a negative Zephyr error code.
int begin();
void update();
int32_t position();
void update_delta();
float count_per_sec();
float rpm();
bool new_speed_available();
void reset_speed_measurement();
private:
static const uint8_t HIST_LEN = 4; // 4 x Ts = 16 ms gluggi fyrir rpm-utreikning
volatile int32_t count;
volatile int16_t delta;
volatile bool speed_ready;
volatile int32_t history[HIST_LEN];
volatile uint8_t hist_idx;
//Digital_in eA;
//Digital_in eB;
struct gpio_callback encoder_c1_callback;
Timer_usec timer;
static void gpio_edge_handler(
const struct device *dev,
struct gpio_callback *callback,
uint32_t pins);
static void timer_expired(void *context);
// public:
// RotaryEncoderDriver();
// void begin();
// void update();
// int32_t position();
// void update_delta();
// float count_per_sec();
// float rpm();
// bool new_speed_available();
// void reset_speed_measurement();
};
#endif
+32
View File
@@ -0,0 +1,32 @@
#ifndef STATE_H
#define STATE_H
class MotorController;
class State
{
protected:
MotorController* context = nullptr;
public:
virtual ~State()
{
}
void set_context(MotorController* context)
{
this->context = context;
}
virtual void enter() = 0; // LED ON
virtual void update() = 0; // run motor controller
virtual void command(char c) = 0; // Listen for reset
virtual void exit() = 0; // Stop motor
};
#endif
+28
View File
@@ -0,0 +1,28 @@
#ifndef STOPPED_STATE_H
#define STOPPED_STATE_H
#include <stdint.h>
#include "state.h"
class StoppedState : public State
{
private:
//unsigned long previous_time; // Used for blinking LED
int64_t previous_time;
bool led_state; // Ditto
public:
StoppedState();
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
};
#endif
+31
View File
@@ -0,0 +1,31 @@
#ifndef TIMER_USEC_H
#define TIMER_USEC_H
//#include <avr/io.h>
#include <stdint.h>
#include <zephyr/kernel.h>
// struct Timer_usec {
// Timer_usec();
// void init(uint16_t period_us);
//};
class Timer_usec {
public:
using callback_t = void (*)(void *context);
Timer_usec();
void set_callback(void (*callback)(void *), void *user_data);
void init(uint16_t period_us);
private:
struct k_timer timer;
callback_t callback;
void *callback_context;
static void expiry_handler(struct k_timer *timer);
};
#endif
+46
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 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
+20
View File
@@ -0,0 +1,20 @@
; 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:nucleo_l432kc]
platform = ststm32
board = nucleo_l432kc
framework = zephyr
monitor_speed = 115200
upload_flags =
-c
reset_config srst_only srst_nogate connect_assert_srst
+47
View File
@@ -0,0 +1,47 @@
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/can.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
/* Devicetree node selected by zephyr,canbus in the overlay */
#define CANBUS_NODE DT_CHOSEN(zephyr_canbus)
int main(void)
{
const struct device *dev = DEVICE_DT_GET(CANBUS_NODE);
if (!device_is_ready(dev)) {
printk("CAN device not ready\n");
return 0;
}
int err = can_set_bitrate(dev, 125000);
if (err != 0) {
printk("Error setting CAN bitrate (err %d)\n", err);
return 0;
}
err = can_start(dev);
if (err != 0) {
printk("Error starting CAN controller (err %d)\n", err);
return 0;
}
struct can_frame frame = {
.flags = 0,
.id = 0x123,
.dlc = 8,
.data = {1, 2, 3, 4, 5, 6, 7, 8},
};
while (1) {
k_msleep(10);
err = can_send(dev, &frame, K_MSEC(100), NULL, NULL);
if (err != 0) {
printk("Sending failed (err %d)\n", err);
return 0;
}
}
}
+64
View File
@@ -0,0 +1,64 @@
//#include <avr/io.h>
#include "digital_in.h"
#include <zephyr/devicetree.h>
// Digital_in::Digital_in(uint8_t pin) : pin_mask(1<<pin) {}
// void Digital_in::init() {
// DDRD &= ~pin_mask;
// PORTD |= pin_mask;
// }
// bool Digital_in::is_hi() { return PIND & pin_mask; }
// bool Digital_in::is_lo() { return !is_hi(); }
static const struct gpio_dt_spec encoder_c1_gpio =
GPIO_DT_SPEC_GET(DT_ALIAS(encoder_c1), gpios);
static const struct gpio_dt_spec encoder_c2_gpio =
GPIO_DT_SPEC_GET(DT_ALIAS(encoder_c2), gpios);
Digital_in::Digital_in(uint8_t pin_id)
: gpio(nullptr)
{
switch (pin_id) {
case ENCODER_C1:
gpio = &encoder_c1_gpio;
break;
case ENCODER_C2:
gpio = &encoder_c2_gpio;
break;
default:
gpio = nullptr;
break;
}
}
void Digital_in::init()
{
if (gpio == nullptr || !gpio_is_ready_dt(gpio)) {
return;
}
// The overlay supplies the pin's GPIO flags.
gpio_pin_configure_dt(gpio, GPIO_INPUT);
}
bool Digital_in::is_hi()
{
if (gpio == nullptr) {
return false;
}
// Read the physical pin level: high is true, low is false.
return gpio_pin_get_raw(gpio->port, gpio->pin) > 0;
}
bool Digital_in::is_lo()
{
return !is_hi();
}
+50
View File
@@ -0,0 +1,50 @@
#include "digital_out.h"
//#include <avr/io.h>
#include <zephyr/devicetree.h>
// Digital_out::Digital_out(uint8_t pin) : pin_mask(1<<pin) {}
// void Digital_out::init() { DDRB |= pin_mask; }
// void Digital_out::toggle() { PORTB ^= pin_mask; }
// void Digital_out::set_hi() { PORTB |= pin_mask; }
// void Digital_out::set_lo() { PORTB &= ~pin_mask; }
static const struct gpio_dt_spec status_led_gpio =
GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
Digital_out::Digital_out(uint8_t pin_id)
: gpio(pin_id == STATUS_LED ? &status_led_gpio : nullptr)
{
}
void Digital_out::init()
{
if (gpio == nullptr || !gpio_is_ready_dt(gpio)) {
return;
}
gpio_pin_configure_dt(gpio, GPIO_OUTPUT_INACTIVE);
}
void Digital_out::toggle()
{
if (gpio != nullptr) {
gpio_pin_toggle_dt(gpio);
}
}
void Digital_out::set_hi()
{
if (gpio != nullptr) {
gpio_pin_set_dt(gpio, 1);
}
}
void Digital_out::set_lo()
{
if (gpio != nullptr) {
gpio_pin_set_dt(gpio, 0);
}
}
+125
View File
@@ -0,0 +1,125 @@
// main.cpp er sleppt i test-build (pio test) svo setup()/loop() rekist ekki a prof
//#ifndef PIO_UNIT_TESTING
//#include <Arduino.h>
//#include <avr/interrupt.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include "rotary_encoder_driver.h"
#include "motor_driver.h"
#include "pi_controller.h"
#include "p_controller.h"
#include "motor_controller.h"
#include "initialization_state.h"
#include "pre_operational_state.h"
#include "operational_state.h"
#include "stopped_state.h"
static RotaryEncoderDriver encoder;
static MotorDriver motor;
const double Kp_P = 10.0; // P-controller from Project 2
const double Kp = 10.125; // Ziegler-Nichols PI
const double Ti = 0.0334;
const double T = 0.004;
static P_controller p_controller(Kp_P);
static PI_controller pi_controller(Kp, Ti, T);
static InitializationState initialization_state;
// Pre-operational velur á milli stýringanna með 'c'
static PreOperationalState pre_operational_state(p_controller, pi_controller);
static OperationalState operational_state;
static StoppedState stopped_state;
// Stöðurnar verða að vera skilgreindar á undan motor_controller
static MotorController motor_controller(
encoder,
motor,
pi_controller, // Sjálfgefin stýring
initialization_state,
pre_operational_state,
operational_state,
stopped_state
);
// int main()
// {
// init();
// Serial.begin(115200);
// cli();
// encoder.begin();
// motor.begin();
// sei();
// motor_controller.begin();
// while (true)
// {
// if (Serial.available() > 0)
// {
// char c = Serial.read();
// motor_controller.command(c);
// }
// motor_controller.update();
// }
// return 0;
// }
// #endif
static const struct device *const console_dev =
DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
extern "C" void motor_application_main(void)
{
if (!device_is_ready(console_dev)) {
printk("Console UART is not ready\n");
return;
}
const int encoder_result = encoder.begin();
if (encoder_result < 0) {
printk("Encoder setup failed: %d\n", encoder_result);
return;
}
motor.begin();
motor_controller.begin();
while (true) {
unsigned char received_char;
// Read a command if a character is waiting.
if (uart_poll_in(console_dev, &received_char) == 0) {
motor_controller.command(
static_cast<char>(received_char));
}
motor_controller.update();
// Give Zephyr's scheduler time to run other threads.
k_msleep(1);
}
}
+61
View File
@@ -0,0 +1,61 @@
#include "initialization_state.h"
#include "motor_controller.h"
//#include <Arduino.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
void InitializationState::enter()
{
// Serial.println();
// Serial.println("State: Initialization");
// context->led_off();
// context->stop_motor();
// context->reset_speed_measurement();
// enter_time_ms = millis();
printk("\nState: Initialization\n");
context->led_off();
context->stop_motor();
context->reset_speed_measurement();
enter_time_ms = k_uptime_get();
}
void InitializationState::update()
{
// // Non-blocking bið svo aðallykkjan haldi áfram að keyra
// if (millis() - enter_time_ms >= WAIT_MS)
// {
// context->to_pre_operational();
// // context->to_operational();
// }
// Check the time without blocking the application.
if ((k_uptime_get() - enter_time_ms) >= WAIT_MS) {
context->to_pre_operational();
}
}
void InitializationState::command(char c)
{
(void)c;
}
void InitializationState::exit()
{
//Serial.println("Boot-up");
printk("Boot-up\n");
}
+12
View File
@@ -0,0 +1,12 @@
// #include <zephyr/kernel.h>
// #ifndef PIO_UNIT_TESTING
// void motor_application_main(void);
// void main(void)
// {
// motor_application_main();
// }
// #endif
+189
View File
@@ -0,0 +1,189 @@
#include "motor_controller.h"
#include "state.h"
//#include <Arduino.h>
MotorController::MotorController(
RotaryEncoderDriver &encoder,
MotorDriver &motor,
P_controller &controller,
State &initialization,
State &pre_operational,
State &operational,
State &stopped
)
: current_state(nullptr),
initialization_state(initialization),
pre_operational_state(pre_operational),
operational_state(operational),
stopped_state(stopped),
encoder(encoder),
motor(motor),
controller(&controller),
led(LED_PIN),
reference_rpm(DEFAULT_REFERENCE_RPM),
last_actual_rpm(0.0),
last_pwm(0.0)
{
initialization_state.set_context(this);
pre_operational_state.set_context(this);
operational_state.set_context(this);
stopped_state.set_context(this);
}
// void MotorController::set_states(
// State* initialization,
// State* operational,
// State* stopped
// )
// initialization_state = initialization;
// operational_state = operational;
// stopped_state = stopped;
void MotorController::begin()
{
led.init();
led.set_lo();
transition_to(initialization_state);
}
void MotorController::transition_to(State &new_state)
{
if (current_state != nullptr) {
current_state->exit();
}
current_state = &new_state;
current_state->enter();
}
void MotorController::update()
{
if (current_state != nullptr)
{
current_state->update();
}
}
void MotorController::command(char c)
{
if (current_state != nullptr)
{
current_state->command(c);
}
}
void MotorController::to_initialization()
{
transition_to(initialization_state);
}
void MotorController::to_pre_operational()
{
transition_to(pre_operational_state);
}
void MotorController::to_operational()
{
transition_to(operational_state);
}
bool MotorController::run_speed_controller()
{
if (!encoder.new_speed_available())
{
return false;
}
const double actual = encoder.rpm();
const double pwm_percent =
controller->update(reference_rpm, actual);
double pwm =
pwm_percent * PWM_PER_PERCENT;
// Klippt hér svo last_pwm sé nákvæmlega það sem fer á mótorinn
if (pwm > 255.0)
{
pwm = 255.0;
}
else if (pwm < -255.0)
{
pwm = -255.0;
}
motor.set(pwm);
last_actual_rpm = actual;
last_pwm = pwm;
return true;
}
void MotorController::stop_motor()
{
motor.stop();
}
void MotorController::reset_speed_measurement()
{
encoder.reset_speed_measurement();
}
void MotorController::reset_controller()
{
controller->reset();
}
void MotorController::set_Kp(double value)
{
controller->set_Kp(value);
}
void MotorController::set_Ti(double value)
{
controller->set_Ti(value);
}
void MotorController::set_controller(P_controller& new_controller)
{
controller = &new_controller;
controller->reset();
}
void MotorController::to_stopped()
{
transition_to(stopped_state);
}
bool MotorController::fault_active()
{
return motor.fault_active();
}
void MotorController::led_on()
{
led.set_hi();
}
void MotorController::led_off()
{
led.set_lo();
}
+243
View File
@@ -0,0 +1,243 @@
#include "motor_driver.h"
//#include <avr/io.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
// Í Fast PWM gefur OCR = 0 samt einn stuttan púls í hverri lotu.
// Til að pinni sé alveg LOW er hann aftengdur frá timernum (COM-bitar)
// og keyrður LOW gegnum PORT-registerið.
// static void ain1_off() // D3 = PD3 = OC2B
// {
// TCCR2A &= ~(1 << COM2B1);
// PORTD &= ~(1 << PORTD3);
// OCR2B = 0;
// }
// static void ain2_off() // D11 = PB3 = OC2A
// {
// TCCR2A &= ~(1 << COM2A1);
// PORTB &= ~(1 << PORTB3);
// OCR2A = 0;
// }
// static void ain1_pwm(uint8_t duty)
// {
// OCR2B = duty;
// TCCR2A |= (1 << COM2B1);
// }
// static void ain2_pwm(uint8_t duty)
// {
// OCR2A = duty;
// TCCR2A |= (1 << COM2A1);
// }
static const struct pwm_dt_spec bin1_pwm =
PWM_DT_SPEC_GET(DT_ALIAS(motor_bin1_pwm));
static const struct gpio_dt_spec bin2 =
GPIO_DT_SPEC_GET(DT_ALIAS(motor_bin2), gpios);
static const struct gpio_dt_spec motor_flt =
GPIO_DT_SPEC_GET(DT_ALIAS(motor_flt), gpios);
static const struct gpio_dt_spec fault_test_button =
GPIO_DT_SPEC_GET(DT_ALIAS(fault_test_button), gpios);
static const struct gpio_dt_spec motor_slp =
GPIO_DT_SPEC_GET(DT_ALIAS(motor_slp), gpios);
MotorDriver::MotorDriver()
: initialized(false)
{
}
void MotorDriver::begin()
{
// Arduino D3 = PD3 = AIN1
// Arduino D11 = PB3 = AIN2
// Arduino D4 = PD4 = FLT
// DDRD |= (1 << DDD3);
// DDRB |= (1 << DDB3);
// DDRD &= ~(1 << DDD4);
// PORTD |= (1 << PORTD4); // Enable pull-up resistor on D4 for part 2
// // Timer2 Fast PWM
// TCCR2A = 0;
// TCCR2B = 0;
// // Fast PWM mode
// TCCR2A |= (1 << WGM20);
// TCCR2A |= (1 << WGM21);
// // Timer2 prescaler = 32
// TCCR2B |= (1 << CS21);
// TCCR2B |= (1 << CS20);
initialized = false;
if (!pwm_is_ready_dt(&bin1_pwm) ||
!gpio_is_ready_dt(&bin2) ||
!gpio_is_ready_dt(&motor_flt) ||
!gpio_is_ready_dt(&fault_test_button) ||
!gpio_is_ready_dt(&motor_slp)) {
return;
}
if (gpio_pin_configure_dt(&bin2, GPIO_OUTPUT_INACTIVE) < 0 ||
gpio_pin_configure_dt(&motor_flt, GPIO_INPUT) < 0 ||
gpio_pin_configure_dt(&fault_test_button, GPIO_INPUT) < 0 ||
gpio_pin_configure_dt(&motor_slp, GPIO_OUTPUT_INACTIVE) < 0) {
return;
}
// SLP is active high, so drive it high to enable the DRV8833.
if (gpio_pin_set_dt(&motor_slp, 1) < 0) {
return;
}
if (pwm_set_dt(&bin1_pwm, bin1_pwm.period, 0) < 0) {
return;
}
// PWM-úttök eru tengd í set() eftir þörfum
initialized = true;
stop();
}
void MotorDriver::forward()
{
set(255.0);
}
void MotorDriver::reverse()
{
set(-255.0);
}
void MotorDriver::stop()
{
// Both LOW
// ain1_off();
// ain2_off();
if (!initialized) {
return;
}
// BIN1 low and BIN2 low puts the DRV8833 in coast mode.
pwm_set_dt(&bin1_pwm, bin1_pwm.period, 0);
gpio_pin_set_dt(&bin2, 0);
}
void MotorDriver::set(double pwm)
{
if (!initialized) {
return;
}
// Limit PWM command
if (pwm > 255.0)
{
pwm = 255.0;
} else if (pwm < -255.0) {
pwm = -255.0;
}
if (pwm == 0.0) {
stop();
return;
}
// uint8_t duty = (uint8_t)(pwm > 0 ? pwm : -pwm);
// if (duty == 0)
// {
// stop();
// }
// else if (pwm > 0)
// {
// // Forward
// ain2_off();
// ain1_pwm(duty);
// }
// else
// {
// // Reverse
// ain1_off();
// ain2_pwm(duty);
// }
const uint32_t magnitude =
static_cast<uint32_t>(pwm > 0.0 ? pwm : -pwm);
// PWM pulse width in nanoseconds. The overlay sets a 50 us period.
const uint32_t requested_pulse =
(bin1_pwm.period * magnitude) / 255U;
// First put BIN1 low so changing BIN2 does not switch direction
// while the previous PWM signal is still active.
pwm_set_dt(&bin1_pwm, bin1_pwm.period, 0);
uint32_t bin1_pulse;
if (pwm > 0.0) {
// Forward: BIN2 low; BIN1 PWM duty equals the command magnitude.
gpio_pin_set_dt(&bin2, 0);
bin1_pulse = requested_pulse;
} else {
// Reverse: BIN2 high; BIN1 pulse is inverted for reverse PWM.
gpio_pin_set_dt(&bin2, 1);
bin1_pulse = bin1_pwm.period - requested_pulse;
}
pwm_set_dt(&bin1_pwm, bin1_pwm.period, bin1_pulse);
}
// bool MotorDriver::fault_active()
// {
// //return !(PIND & (1 << PIND4));
// if (!initialized) {
// return true; // Fail safe if the driver did not initialize.
// }
// // The overlay marks FLT as active low. gpio_pin_get_dt() returns
// // the logical active state, so 1 means a fault is active.
// const int fault = gpio_pin_get_dt(&motor_flt);
// return fault < 0 || fault != 0;
// }
bool MotorDriver::fault_active()
{
if (!initialized) {
return true; // Fail safe if the driver did not initialize.
}
const int driver_fault = gpio_pin_get_dt(&motor_flt);
const int button_fault = gpio_pin_get_dt(&fault_test_button);
// Negative values indicate a GPIO read error, so treat them as a fault.
// With GPIO_ACTIVE_LOW, a pressed button reads as logical 1.
return driver_fault < 0 ||
button_fault < 0 ||
driver_fault != 0 ||
button_fault != 0;
}
+95
View File
@@ -0,0 +1,95 @@
#include "operational_state.h"
#include "motor_controller.h"
//#include <Arduino.h>
#include <zephyr/sys/printk.h>
void OperationalState::enter()
{
//Serial.println("State: Operational");
printk("State: Operational\n");
context->reset_controller();
context->led_on();
sample_count = 0;
#if LOG_CSV
//Serial.println("time_ms,ref_rpm,actual_rpm,pwm");
printk("time_ms,ref_rpm,actual_rpm,pwm\n");
#endif
}
// Part 1
//void OperationalState::update()
//{
// context->run_speed_controller();
//}
// Part 2
void OperationalState::update()
{
if (context->fault_active())
{
//Serial.println("Fault detected");
printk("Fault detected\n");
context->to_stopped();
return;
}
if (context->run_speed_controller())
{
++sample_count;
#if LOG_CSV
// 4 ms á milli sýna, tíminn byrjar á 0 við hverja innkomu í Operational
// Serial.print(sample_count * 4UL);
// Serial.print(',');
// Serial.print(context->get_reference_rpm(), 1);
// Serial.print(',');
// Serial.print(context->get_actual_rpm(), 2);
// Serial.print(',');
// Serial.println(context->get_pwm(), 1);
printk("%u,%.1f,%.2f,%.1f\n",
static_cast<unsigned int>(sample_count * 4U),
context->get_reference_rpm(),
context->get_actual_rpm(),
context->get_pwm());
#endif
}
}
void OperationalState::command(char c)
{
if (c == 'r' || c == 'R')
{
//Serial.println("Command: reset");
printk("Command: reset\n");
context->to_initialization();
}
else if (c == 'p' || c == 'P')
{
//Serial.println("Command: set pre-operational");
printk("Command: set pre-operational\n");
context->to_pre_operational();
}
}
void OperationalState::exit()
{
context->stop_motor();
context->led_off();
}
+13
View File
@@ -0,0 +1,13 @@
#include "p_controller.h"
P_controller::P_controller(double Kp)
: Kp(Kp)
{
}
double P_controller::update(double ref, double actual)
{
double error = ref - actual;
return Kp * error;
}
+37
View File
@@ -0,0 +1,37 @@
#include "pi_controller.h"
PI_controller::PI_controller(double Kp, double Ti, double T) : P_controller(Kp), Ti(Ti), T(T), integral(0.0)
{
}
double PI_controller::update(double ref, double actual)
{
double e = ref - actual;
double new_integral = integral + e * T;
double u = Kp * (e + new_integral / Ti);
if (u > U_MAX)
{
u = U_MAX;
}
else if (u < U_MIN)
{
u = U_MIN;
}
else
{
integral = new_integral;
}
return u;
// return 0.0;
}
void PI_controller::reset()
{
integral = 0.0;
}
void PI_controller::set_Ti(double value)
{
Ti = value;
}
+179
View File
@@ -0,0 +1,179 @@
#include "pre_operational_state.h"
#include "motor_controller.h"
#include "p_controller.h"
//#include <Arduino.h>
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
PreOperationalState::PreOperationalState(P_controller &p_controller, P_controller &pi_controller)
: p_controller(p_controller),
pi_controller(pi_controller),
pi_selected(true),
previous_time(0),
led_state(false),
//waiting_for_kp(false),
//waiting_for_ti(false),
input_mode(NONE),
input_index(0)
{
}
void PreOperationalState::enter()
{
//Serial.println("State: Pre-operational");
printk("State: Pre-operational\n");
context->stop_motor();
context->led_off();
led_state = false;
//previous_time = millis();
previous_time = k_uptime_get();
}
void PreOperationalState::update()
{
//unsigned long current_time = millis();
const int64_t current_time = k_uptime_get();
if (current_time - previous_time >= 500)
{
previous_time = current_time;
led_state = !led_state;
if (led_state)
{
context->led_on();
}
else
{
context->led_off();
}
}
}
void PreOperationalState::command(char c)
{
// We are currently entering a Kp or Ti value
if (input_mode != NONE)
{
// Enter pressed
if (c == '\n' || c == '\r')
{
if (input_index > 0)
{
// End the string
input_buffer[input_index] = '\0';
// Convert text to a number
const double value = atof(input_buffer);
if (input_mode == ENTER_KP)
{
context->set_Kp(value);
// Serial.print("Kp set to: ");
// Serial.println(value);
printk("Kp updated\n");
}
else if (input_mode == ENTER_TI)
{
context->set_Ti(value);
// Serial.print("Ti set to: ");
// Serial.println(value);
printk("Ti updated\n");
}
}
// Finished entering a value
input_index = 0;
input_mode = NONE;
return;
}
// Store number characters
if ((c >= '0' && c <= '9') ||
c == '.' ||
c == '-')
{
if (input_index < sizeof(input_buffer) - 1)
{
input_buffer[input_index++] = c;
//input_index++;
}
}
return;
}
// Normal state commands
if (c == 'o' || c == 'O')
{
//Serial.println("Command: set operational");
printk("Command: set operational\n");
context->to_operational();
}
else if (c == 'r' || c == 'R')
{
//Serial.println("Command: reset");
printk("Command: reset\n");
context->to_initialization();
}
else if (c == 'k' || c == 'K')
{
input_mode = ENTER_KP;
input_index = 0;
//Serial.println("Enter Kp and press Enter:");
printk("Enter Kp and press Enter:\n");
}
else if (c == 't' || c == 'T')
{
input_mode = ENTER_TI;
input_index = 0;
//Serial.println("Enter Ti and press Enter:");
printk("Enter Ti and press Enter:\n");
}
// Select controller algorithm, the controller is passed to the motor controller
else if (c == 'c' || c == 'C')
{
pi_selected = !pi_selected;
if (pi_selected)
{
context->set_controller(pi_controller);
//Serial.println("Controller: PI");
printk("Controller: PI\n");
}
else
{
context->set_controller(p_controller);
//Serial.println("Controller: P");
printk("Controller: P\n");
}
}
}
void PreOperationalState::exit()
{
context->led_off();
}
+175
View File
@@ -0,0 +1,175 @@
#include "rotary_encoder_driver.h"
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
static const struct gpio_dt_spec encoder_c1 =
GPIO_DT_SPEC_GET(DT_ALIAS(encoder_c1), gpios);
static const struct gpio_dt_spec encoder_c2 =
GPIO_DT_SPEC_GET(DT_ALIAS(encoder_c2), gpios);
RotaryEncoderDriver::RotaryEncoderDriver()
: count(0),
delta(0),
speed_ready(false),
hist_idx(0)
{
for (uint8_t i = 0; i < HIST_LEN; ++i) {
history[i] = 0;
}
}
int RotaryEncoderDriver::begin()
{
if (!gpio_is_ready_dt(&encoder_c1) ||
!gpio_is_ready_dt(&encoder_c2)) {
return -ENODEV;
}
int ret = gpio_pin_configure_dt(&encoder_c1, GPIO_INPUT);
if (ret < 0) {
return ret;
}
ret = gpio_pin_configure_dt(&encoder_c2, GPIO_INPUT);
if (ret < 0) {
return ret;
}
gpio_init_callback(
&encoder_c1_callback,
gpio_edge_handler,
BIT(encoder_c1.pin));
ret = gpio_add_callback(encoder_c1.port, &encoder_c1_callback);
if (ret < 0) {
return ret;
}
ret = gpio_pin_interrupt_configure_dt(
&encoder_c1,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
return ret;
}
timer.set_callback(timer_expired, this);
timer.init(SAMPLE_PERIOD_US);
reset_speed_measurement();
return 0;
}
void RotaryEncoderDriver::gpio_edge_handler(
const struct device *dev,
struct gpio_callback *callback,
uint32_t pins)
{
(void)dev; // This handler does not need the device pointer.
auto *encoder = CONTAINER_OF(
callback,
RotaryEncoderDriver,
encoder_c1_callback);
if ((pins & BIT(encoder_c1.pin)) != 0U) {
encoder->update(); // Reads C2 and updates the count
}
}
void RotaryEncoderDriver::timer_expired(void *context)
{
auto *encoder = static_cast<RotaryEncoderDriver *>(context);
encoder->update_delta();
}
void RotaryEncoderDriver::update()
{
// C2's level at the C1 edge tells us the direction.
const int direction = gpio_pin_get_dt(&encoder_c2);
if (direction < 0) {
return;
}
const unsigned int key = irq_lock();
count += direction ? 1 : -1;
irq_unlock(key);
}
int32_t RotaryEncoderDriver::position()
{
const unsigned int key = irq_lock();
const int32_t result = count;
irq_unlock(key);
return result;
}
void RotaryEncoderDriver::update_delta()
{
const unsigned int key = irq_lock();
const int32_t current_count = count;
const int32_t oldest_count = history[hist_idx];
history[hist_idx] = current_count;
hist_idx = (hist_idx + 1) % HIST_LEN;
delta = static_cast<int16_t>(current_count - oldest_count);
speed_ready = true;
irq_unlock(key);
}
float RotaryEncoderDriver::count_per_sec()
{
const unsigned int key = irq_lock();
const int16_t local_delta = delta;
irq_unlock(key);
constexpr float window_seconds =
HIST_LEN * SAMPLE_PERIOD_US * 1e-6f;
return local_delta / window_seconds;
}
float RotaryEncoderDriver::rpm()
{
return count_per_sec() * 60.0f / COUNTS_PER_REV;
}
bool RotaryEncoderDriver::new_speed_available()
{
const unsigned int key = irq_lock();
const bool ready = speed_ready;
if (ready) {
speed_ready = false;
}
irq_unlock(key);
return ready;
}
void RotaryEncoderDriver::reset_speed_measurement()
{
const unsigned int key = irq_lock();
delta = 0;
speed_ready = false;
for (uint8_t i = 0; i < HIST_LEN; ++i) {
history[i] = count;
}
hist_idx = 0;
irq_unlock(key);
}
+132
View File
@@ -0,0 +1,132 @@
// #include "rotary_encoder_driver.h"
// #include <avr/interrupt.h>
// static RotaryEncoderDriver* driver = nullptr;
// RotaryEncoderDriver::RotaryEncoderDriver()
// : count(0)
// , delta(0)
// , speed_ready(false)
// , hist_idx(0)
// , eA(2)
// , eB(5)
// {
// for (uint8_t i = 0; i < HIST_LEN; i++)
// {
// history[i] = 0;
// }
// }
// void RotaryEncoderDriver::begin()
// {
// driver = this;
// eA.init();
// eB.init();
// EICRA |= (1<<ISC01) | (1<<ISC00);
// EIMSK |= (1<<INT0);
// timer.init(SAMPLE_PERIOD_US);
// }
// void RotaryEncoderDriver::update()
// {
// count += eB.is_hi() ? 1 : -1;
// }
// int32_t RotaryEncoderDriver::position()
// {
// uint8_t oldSREG = SREG;
// cli();
// int32_t local_count = count;
// SREG = oldSREG;
// return local_count;
// }
// void RotaryEncoderDriver::update_delta()
// {
// int32_t oldest = history[hist_idx];
// history[hist_idx] = count;
// hist_idx = (hist_idx + 1) % HIST_LEN;
// delta = (int16_t)(count - oldest); // breyting yfir HIST_LEN * Ts (16 ms) glugga
// speed_ready = true;
// }
// float RotaryEncoderDriver::count_per_sec()
// {
// uint8_t oldSREG = SREG;
// cli();
// int16_t local_delta = delta;
// SREG = oldSREG;
// return local_delta / (HIST_LEN * SAMPLE_PERIOD_US * 1e-6f);
// }
// float RotaryEncoderDriver::rpm()
// {
// return count_per_sec() * 60.0f / COUNTS_PER_REV;
// }
// bool RotaryEncoderDriver::new_speed_available()
// {
// uint8_t oldSREG = SREG;
// cli();
// bool ready = speed_ready;
// if (ready)
// {
// speed_ready = false;
// }
// SREG = oldSREG;
// return ready;
// }
// void RotaryEncoderDriver::reset_speed_measurement()
// {
// uint8_t oldSREG = SREG;
// cli();
// delta = 0;
// speed_ready = false;
// for (uint8_t i = 0; i < HIST_LEN; i++)
// {
// history[i] = count;
// }
// hist_idx = 0;
// SREG = oldSREG;
// }
// ISR(INT0_vect)
// {
// driver->update();
// }
// ISR(TIMER1_COMPA_vect)
// {
// driver->update_delta();
// }
+91
View File
@@ -0,0 +1,91 @@
#include "stopped_state.h"
#include "motor_controller.h"
//#include <Arduino.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
StoppedState::StoppedState()
: previous_time(0),
led_state(false)
{
}
void StoppedState::enter()
{
//Serial.println("State: Stopped");
printk("State: Stopped\n");
context->stop_motor();
led_state = false;
context->led_off();
//previous_time = millis();
previous_time = k_uptime_get();
}
// Controlling the blinking of the LED @ 2Hz
void StoppedState::update()
{
//unsigned long current_time = millis();
const int64_t current_time = k_uptime_get();
if (current_time - previous_time >= 250)
{
previous_time = current_time;
led_state = !led_state;
if (led_state)
{
context->led_on();
}
else
{
context->led_off();
}
}
}
// Commands for Stopped [ r = reset and o for operational]
void StoppedState::command(char c)
{
if (c == 'r' || c == 'R')
{
//Serial.println("Command: reset");
printk("Command: reset\n");
context->to_initialization();
}
else if (c == 'o' || c == 'O')
{
//Serial.println("Command: set operational");
printk("Command: set operational\n");
context->to_operational();
}
else if (c == 'p' || c == 'P')
{
//Serial.println("Command: set pre-operational");
printk("Command: set pre-operational\n");
context->to_pre_operational();
}
}
void StoppedState::exit()
{
context->led_off();
}
+52
View File
@@ -0,0 +1,52 @@
#include "timer_usec.h"
Timer_usec::Timer_usec() //= default;
: callback(nullptr),
callback_context(nullptr)
{
}
void Timer_usec::set_callback(
callback_t callback_function,
void *user_data)
{
callback = callback_function;
callback_context = user_data;
}
// void Timer_usec::init(uint16_t period_us) { // for custom duty cycle duration in ms in part 1
// TCCR1A = 0;
// TCCR1B = 0;
// TCNT1 = 0;
// OCR1A = period_us * 2 - 1;
// TCCR1B |= (1<<WGM12);
// TIMSK1 |= (1<<OCIE1A);
// TCCR1B |= (1<<CS11);
// }
void Timer_usec::init(uint16_t period_us)
{
if (period_us == 0) {
return;
}
k_timer_init(&timer, expiry_handler, nullptr);
k_timer_user_data_set(&timer, this);
// The timer fires first after period_us, then repeats every period_us.
k_timer_start(&timer, K_USEC(period_us), K_USEC(period_us));
}
void Timer_usec::expiry_handler(struct k_timer *expired_timer)
{
auto *self =
static_cast<Timer_usec *>(k_timer_user_data_get(expired_timer));
if (self != nullptr && self->callback != nullptr) {
self->callback(self->callback_context);
}
}
+11
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
+150
View File
@@ -0,0 +1,150 @@
#include <unity.h>
// #ifdef ARDUINO
#include <Arduino.h>
// #endif
#include "p_controller.h"
#include "pi_controller.h"
static const double KP = 2.0;
static const double TI = 1.0;
static const double T = 0.1;
static const double TOL = 1e-4; // double er 32-bita float a AVR
// void setUp(void) {}
// void tearDown(void) {}
// Normal flow: engin villa -> ekkert úttak
void test_zero_error_gives_zero_output(void)
{
PI_controller c(KP, TI, T);
TEST_ASSERT_FLOAT_WITHIN(TOL, 0.0, c.update(10.0, 10.0));
}
// Normal flow: fyrsta skref u = Kp * (e + e*T/Ti)
void test_first_step(void)
{
PI_controller c(KP, TI, T);
double e = 5.0;
TEST_ASSERT_FLOAT_WITHIN(TOL, KP * (e + e * T / TI), c.update(e, 0.0));
}
// Normal flow: stöðug villa safnast upp, u_n = Kp * (e + n*e*T/Ti)
void test_integral_accumulates(void)
{
PI_controller c(KP, TI, T);
double e = 5.0;
double u = 0.0;
for (int n = 1; n <= 5; n++)
{
u = c.update(e, 0.0);
TEST_ASSERT_FLOAT_WITHIN(TOL, KP * (e + n * e * T / TI), u);
}
}
// Normal flow: þegar villan er orðin 0 heldur integralið úttakinu uppi
void test_output_held_when_error_zero(void)
{
PI_controller c(KP, TI, T);
double e = 5.0;
for (int n = 0; n < 5; n++)
{
c.update(e, 0.0);
}
double E = 5 * e * T;
TEST_ASSERT_FLOAT_WITHIN(TOL, KP * E / TI, c.update(0.0, 0.0));
}
// Alt flow: villa langt utan getu -> mettun í +100 %
void test_saturates_high(void)
{
PI_controller c(KP, TI, T);
TEST_ASSERT_FLOAT_WITHIN(TOL, 100.0, c.update(10000.0, 0.0));
}
// Alt flow: mettun í -100 %
void test_saturates_low(void)
{
PI_controller c(KP, TI, T);
TEST_ASSERT_FLOAT_WITHIN(TOL, -100.0, c.update(-10000.0, 0.0));
}
// Alt flow: anti-windup. Eftir langa mettun á lítil neikvæð villa
// strax að gefa úttak undir mettun (integralið óx ekki)
void test_no_integral_windup(void)
{
PI_controller c(KP, TI, T);
for (int n = 0; n < 1000; n++)
{
c.update(10000.0, 0.0);
}
double u = c.update(0.0, 1.0); // e = -1
TEST_ASSERT_TRUE(u < 0.0);
}
// Alt flow: reset (stopped -> operational) hreinsar integralið
void test_reset_clears_integral(void)
{
PI_controller c(KP, TI, T);
for (int n = 0; n < 5; n++)
{
c.update(5.0, 0.0);
}
c.reset();
TEST_ASSERT_FLOAT_WITHIN(TOL, 0.0, c.update(0.0, 0.0));
}
// Polymorphism: kall í gegnum P_controller& gefur PI hegðun
void test_polymorphic_update(void)
{
PI_controller pi(KP, TI, T);
P_controller& c = pi;
double e = 5.0;
TEST_ASSERT_FLOAT_WITHIN(TOL, KP * (e + e * T / TI), c.update(e, 0.0));
}
// P_controller hegðar sér óbreytt
void test_p_controller_unchanged(void)
{
P_controller c(KP);
TEST_ASSERT_FLOAT_WITHIN(TOL, KP * 5.0, c.update(5.0, 0.0));
}
void setup()
{
// init();
delay(2000);
UNITY_BEGIN();
RUN_TEST(test_zero_error_gives_zero_output);
RUN_TEST(test_first_step);
RUN_TEST(test_integral_accumulates);
RUN_TEST(test_output_held_when_error_zero);
RUN_TEST(test_saturates_high);
RUN_TEST(test_saturates_low);
RUN_TEST(test_no_integral_windup);
RUN_TEST(test_reset_clears_integral);
RUN_TEST(test_polymorphic_update);
RUN_TEST(test_p_controller_unchanged);
// return UNITY_END();
UNITY_END();
}
void loop() {}
// #ifdef ARDUINO
// void setup()
// {
// delay(2000); // bida eftir serial tengingu eftir reset
// run_tests();
// }
// void loop()
// {
// }
// #else
// int main(int argc, char** argv)
// {
// return run_tests();
// }
// #endif
+6
View File
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(ESP_Project4_H26)
FILE(GLOB app_sources ../src/*.c*)
target_sources(app PRIVATE ${app_sources})
+83
View File
@@ -0,0 +1,83 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
/ {
chosen {
zephyr,canbus = &can1;
};
aliases {
encoder-c1 = &encoder_c1;
encoder-c2 = &encoder_c2;
motor-bin1-pwm = &motor_bin1_pwm;
motor-bin2 = &motor_bin2;
motor-flt = &motor_flt;
motor-slp = &motor_slp;
fault-test-button = &fault_test_button;
};
buttons {
compatible = "gpio-keys";
encoder_c1: encoder_c1 {
gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>; /* D4, PB7 */
label = "Encoder C1";
};
encoder_c2: encoder_c2 {
gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; /* D5, PB6 */
label = "Encoder C2";
};
motor_flt: motor_flt {
gpios = <&gpioa 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; /* A0, PA0 */
label = "Motor fault";
};
fault_test_button: fault_test_button {
gpios = <&gpiob 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; /* D3, PB0 */
label = "Fault test button";
};
};
motor_gpio_outputs {
compatible = "gpio-leds";
motor_bin2: motor_bin2 {
gpios = <&gpiob 1 GPIO_ACTIVE_HIGH>; /* D6, PB1 */
label = "Motor BIN2";
};
motor_slp: motor_slp {
gpios = <&gpioa 1 GPIO_ACTIVE_HIGH>; /* A1, PA1 */
label = "Motor sleep";
};
};
motor_pwm_outputs {
compatible = "pwm-leds";
motor_bin1_pwm: motor_bin1_pwm {
pwms = <&pwm1 1 PWM_USEC(50) 0>; /* D9, PA8; 20 kHz */
label = "Motor BIN1 PWM";
};
};
};
&timers1 {
status = "okay";
st,prescaler = <79>;
pwm1: pwm {
status = "okay";
pinctrl-0 = <&tim1_ch1_pa8>;
pinctrl-names = "default";
};
};
&can1 {
pinctrl-0 = <&can1_rx_pa11 &can1_tx_pa12>;
pinctrl-names = "default";
status = "okay";
};
+12
View File
@@ -0,0 +1,12 @@
CONFIG_CPP=y
CONFIG_GPIO=y
CONFIG_PWM=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_CAN=y