8 Commits
33 changed files with 1781 additions and 2 deletions
+3
View File
@@ -3,3 +3,6 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
scripts
__pycache__/
*.typ
+1 -1
View File
@@ -1,3 +1,3 @@
# ESP_Project4_H26
A repository for project 3, done in the Embedded System Programming class in Reykjavík University in the Fall semester of 2026
A repository for project 4, done in the Embedded System Programming class in Reykjavík University in the Fall semester of 2026
+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
+31
View File
@@ -0,0 +1,31 @@
#ifndef DIGITAL_IN_H
#define DIGITAL_IN_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
class Digital_in {
public:
enum : uint8_t {
ENCODER_C1 = 0,
ENCODER_C2 = 1
};
explicit Digital_in(uint8_t pin_id);
void init();
bool is_hi();
bool is_lo();
// Calls handler on every rising edge of the pin. The caller owns the
// gpio_callback struct so it can find its own object with CONTAINER_OF.
// Returns 0 on success, or a negative Zephyr error code.
int enable_rising_edge_interrupt(
struct gpio_callback *callback,
gpio_callback_handler_t handler);
private:
const struct gpio_dt_spec *gpio;
};
#endif
+23
View File
@@ -0,0 +1,23 @@
#ifndef DIGITAL_OUT_H
#define DIGITAL_OUT_H
#include<stdint.h>
#include <zephyr/drivers/gpio.h>
struct Digital_out {
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
+27
View File
@@ -0,0 +1,27 @@
#ifndef INITIALIZATION_STATE_H
#define INITIALIZATION_STATE_H
#include <stdint.h>
#include "state.h"
class InitializationState : public State
{
private:
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
+97
View File
@@ -0,0 +1,97 @@
#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 = 25.0;
static constexpr double PWM_PER_PERCENT = 255.0 / 100.0;
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
+41
View File
@@ -0,0 +1,41 @@
#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;
int64_t previous_time;
bool led_state;
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
+58
View File
@@ -0,0 +1,58 @@
#ifndef ROTARY_ENCODER_DRIVER_H
#define ROTARY_ENCODER_DRIVER_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.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();
// Blocks until the next speed sample, or until the timeout expires.
// Lets the main loop run in step with the 4 ms sampling.
void wait_for_sample(k_timeout_t timeout);
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;
struct k_sem sample_sem;
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);
};
#endif
+30
View File
@@ -0,0 +1,30 @@
#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
+26
View File
@@ -0,0 +1,26 @@
#ifndef STOPPED_STATE_H
#define STOPPED_STATE_H
#include <stdint.h>
#include "state.h"
class StoppedState : public State
{
private:
int64_t previous_time;
bool led_state;
public:
StoppedState();
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
};
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef TIMER_USEC_H
#define TIMER_USEC_H
#include <stdint.h>
#include <zephyr/kernel.h>
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
+5
View File
@@ -13,6 +13,11 @@ platform = ststm32
board = nucleo_l432kc
framework = zephyr
monitor_speed = 115200
upload_flags =
-c
reset_config srst_only srst_nogate connect_assert_srst
; CSV-útprentun í Operational fyrir step/load response (sjá capture_serial.py)
; build_flags = -D LOG_CSV=1
+115
View File
@@ -0,0 +1,115 @@
#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
);
static const struct device *const console_dev =
DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
// Received characters are queued by the UART ISR, like the RX buffer that
// Arduino's Serial had. The USART has a single receive register, so
// polling it from the main loop loses characters that arrive in a burst.
K_MSGQ_DEFINE(rx_queue, sizeof(char), 64, 1);
static void uart_rx_isr(const struct device *dev, void *user_data)
{
(void)user_data;
if (!uart_irq_update(dev)) {
return;
}
while (uart_irq_rx_ready(dev)) {
unsigned char c;
if (uart_fifo_read(dev, &c, 1) != 1) {
break;
}
// Drop the character if the queue is full.
(void)k_msgq_put(&rx_queue, &c, K_NO_WAIT);
}
}
extern "C" void motor_application_main(void)
{
if (!device_is_ready(console_dev)) {
printk("Console UART is not ready\n");
return;
}
uart_irq_callback_user_data_set(console_dev, uart_rx_isr, nullptr);
uart_irq_rx_enable(console_dev);
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) {
char received_char;
// Handle every character the ISR has queued since the last pass.
while (k_msgq_get(&rx_queue, &received_char, K_NO_WAIT) == 0) {
motor_controller.command(received_char);
}
motor_controller.update();
// Sleep until the encoder has a new speed sample (every 4 ms), so
// the controller runs right after sampling. The timeout only
// matters if the timer stops; the thread is idle while it waits.
encoder.wait_for_sample(K_MSEC(10));
}
}
+74
View File
@@ -0,0 +1,74 @@
#include "digital_in.h"
#include <errno.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/util.h>
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();
}
int Digital_in::enable_rising_edge_interrupt(
struct gpio_callback *callback,
gpio_callback_handler_t handler)
{
if (gpio == nullptr || !gpio_is_ready_dt(gpio)) {
return -ENODEV;
}
gpio_init_callback(callback, handler, BIT(gpio->pin));
const int ret = gpio_add_callback(gpio->port, callback);
if (ret < 0) {
return ret;
}
// Physical rising edge, independent of the active level in the overlay.
return gpio_pin_interrupt_configure(
gpio->port, gpio->pin, GPIO_INT_EDGE_RISING);
}
+41
View File
@@ -0,0 +1,41 @@
#include "digital_out.h"
#include <zephyr/devicetree.h>
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);
}
}
+37
View File
@@ -0,0 +1,37 @@
#include "initialization_state.h"
#include "motor_controller.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
void InitializationState::enter()
{
printk("\nState: Initialization\n");
context->led_off();
context->stop_motor();
context->reset_speed_measurement();
enter_time_ms = k_uptime_get();
}
void InitializationState::update()
{
// 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()
{
printk("Boot-up\n");
}
+5 -1
View File
@@ -1,5 +1,9 @@
#include <zephyr/kernel.h>
void main(void)
void motor_application_main(void);
int main(void)
{
motor_application_main();
return 0;
}
+174
View File
@@ -0,0 +1,174 @@
#include "motor_controller.h"
#include "state.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::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();
}
+156
View File
@@ -0,0 +1,156 @@
#include "motor_driver.h"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
static const struct pwm_dt_spec bin1_pwm =
PWM_DT_SPEC_GET(DT_ALIAS(motor_bin1_pwm));
static const struct pwm_dt_spec bin2_pwm =
PWM_DT_SPEC_GET(DT_ALIAS(motor_bin2_pwm));
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()
{
initialized = false;
if (!pwm_is_ready_dt(&bin1_pwm) ||
!pwm_is_ready_dt(&bin2_pwm) ||
!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(&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 ||
pwm_set_dt(&bin2_pwm, bin2_pwm.period, 0) < 0) {
return;
}
initialized = true;
stop();
}
void MotorDriver::forward()
{
set(255.0);
}
void MotorDriver::reverse()
{
set(-255.0);
}
void MotorDriver::stop()
{
if (!initialized) {
return;
}
// BIN1 low and BIN2 low puts the DRV8833 in coast mode.
pwm_set_dt(&bin1_pwm, bin1_pwm.period, 0);
pwm_set_dt(&bin2_pwm, bin2_pwm.period, 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;
}
const double magnitude = pwm > 0.0 ? pwm : -pwm;
// Pulse width in nanoseconds, kept as a double until the end so the
// 4000-count timer resolution is not lost to integer truncation.
const uint32_t pulse = static_cast<uint32_t>(
static_cast<double>(bin1_pwm.period) * magnitude / 255.0);
// Same PWM scheme as Project 3: one input PWM, the other input low,
// so both directions use fast decay (coast during the off time).
// The idle input is set low first; both channels share TIM1 with
// preloaded compare registers, so the change takes effect together
// at the next timer update.
if (pwm > 0.0) {
// Forward: BIN1 PWM, BIN2 low.
pwm_set_dt(&bin2_pwm, bin2_pwm.period, 0);
pwm_set_dt(&bin1_pwm, bin1_pwm.period, pulse);
} else {
// Reverse: BIN1 low, BIN2 PWM.
pwm_set_dt(&bin1_pwm, bin1_pwm.period, 0);
pwm_set_dt(&bin2_pwm, bin2_pwm.period, 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.
}
// 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 driver_fault = gpio_pin_get_dt(&motor_flt);
const int button_fault = gpio_pin_get_dt(&fault_test_button);
return driver_fault < 0 ||
button_fault < 0 ||
driver_fault != 0 ||
button_fault != 0;
}
+72
View File
@@ -0,0 +1,72 @@
#include "operational_state.h"
#include "motor_controller.h"
#include <zephyr/sys/printk.h>
void OperationalState::enter()
{
printk("State: Operational\n");
context->reset_controller();
context->led_on();
sample_count = 0;
#if LOG_CSV
printk("time_ms,ref_rpm,actual_rpm,pwm\n");
#endif
}
void OperationalState::update()
{
if (context->fault_active())
{
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
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')
{
printk("Command: reset\n");
context->to_initialization();
}
else if (c == 'p' || c == 'P')
{
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;
}
+36
View File
@@ -0,0 +1,36 @@
#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;
}
void PI_controller::reset()
{
integral = 0.0;
}
void PI_controller::set_Ti(double value)
{
Ti = value;
}
+162
View File
@@ -0,0 +1,162 @@
#include "pre_operational_state.h"
#include "motor_controller.h"
#include "p_controller.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),
input_mode(NONE),
input_index(0)
{
}
void PreOperationalState::enter()
{
printk("State: Pre-operational\n");
// The selection survives a reset, so report it (used by capture_serial.py).
printk("Controller: %s\n", pi_selected ? "PI" : "P");
context->stop_motor();
context->led_off();
led_state = false;
previous_time = k_uptime_get();
}
void PreOperationalState::update()
{
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);
printk("Kp set to: %.4f\n", value);
}
else if (input_mode == ENTER_TI)
{
context->set_Ti(value);
printk("Ti set to: %.4f\n", value);
}
}
// 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;
}
}
return;
}
// Normal state commands
if (c == 'o' || c == 'O')
{
printk("Command: set operational\n");
context->to_operational();
}
else if (c == 'r' || c == 'R')
{
printk("Command: reset\n");
context->to_initialization();
}
else if (c == 'k' || c == 'K')
{
input_mode = ENTER_KP;
input_index = 0;
printk("Enter Kp and press Enter:\n");
}
else if (c == 't' || c == 'T')
{
input_mode = ENTER_TI;
input_index = 0;
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);
printk("Controller: PI\n");
}
else
{
context->set_controller(p_controller);
printk("Controller: P\n");
}
}
}
void PreOperationalState::exit()
{
context->led_off();
}
+160
View File
@@ -0,0 +1,160 @@
#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>
//#define ISR_PROBE_NODE DT_PATH(zephyr_user)
static const struct gpio_dt_spec isr_probe =
GPIO_DT_SPEC_GET(DT_ALIAS(encoder_isr_probe), gpios);
RotaryEncoderDriver::RotaryEncoderDriver()
: count(0),
delta(0),
speed_ready(false),
hist_idx(0),
eA(Digital_in::ENCODER_C1),
eB(Digital_in::ENCODER_C2)
{
for (uint8_t i = 0; i < HIST_LEN; ++i) {
history[i] = 0;
}
}
int RotaryEncoderDriver::begin()
{
eA.init();
eB.init();
// Max count 1: the main loop only needs to know that a new sample
// exists, not how many it missed.
k_sem_init(&sample_sem, 0, 1);
const int ret = eA.enable_rising_edge_interrupt(
&encoder_c1_callback,
gpio_edge_handler);
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)
{
// Zephyr only calls this handler for C1, the one pin in the callback.
(void)dev;
(void)pins;
auto *encoder = CONTAINER_OF(
callback,
RotaryEncoderDriver,
encoder_c1_callback);
encoder->update();
}
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 bool forward = eB.is_hi();
const unsigned int key = irq_lock();
count += forward ? 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);
// Wake the main loop so the controller runs right after the sample.
k_sem_give(&sample_sem);
}
void RotaryEncoderDriver::wait_for_sample(k_timeout_t timeout)
{
(void)k_sem_take(&sample_sem, timeout);
}
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);
}
+81
View File
@@ -0,0 +1,81 @@
#include "stopped_state.h"
#include "motor_controller.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
StoppedState::StoppedState()
: previous_time(0),
led_state(false)
{
}
void StoppedState::enter()
{
printk("State: Stopped\n");
context->stop_motor();
led_state = false;
context->led_off();
previous_time = k_uptime_get();
}
// Controlling the blinking of the LED @ 2Hz
void StoppedState::update()
{
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')
{
printk("Command: reset\n");
context->to_initialization();
}
else if (c == 'o' || c == 'O')
{
printk("Command: set operational\n");
context->to_operational();
}
else if (c == 'p' || c == 'P')
{
printk("Command: set pre-operational\n");
context->to_pre_operational();
}
}
void StoppedState::exit()
{
context->led_off();
}
+39
View File
@@ -0,0 +1,39 @@
#include "timer_usec.h"
Timer_usec::Timer_usec()
: 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)
{
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);
}
}
+86
View File
@@ -0,0 +1,86 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/pwm/stm32_pwm.h>
/ {
aliases {
encoder-c1 = &encoder_c1;
encoder-c2 = &encoder_c2;
motor-bin1-pwm = &motor_bin1_pwm;
motor-bin2-pwm = &motor_bin2_pwm;
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_slp: motor_slp {
gpios = <&gpioa 1 GPIO_ACTIVE_HIGH>; /* A1, PA1 */
label = "Motor sleep";
};
encoder_isr_probe: encoder_isr_probe {
gpios = <&gpioa 12 GPIO_ACTIVE_HIGH>; /* D2, PA12 */
label = "Encoder ISR probe";
};
};
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";
};
/* PB1 has no regular TIM1 channel, only the complementary output
* TIM1_CH3N. With CH3 itself disabled, CH3N outputs the
* non-inverted CH3 reference, so it behaves like a normal channel. */
motor_bin2_pwm: motor_bin2_pwm {
pwms = <&pwm1 3 PWM_USEC(50) (PWM_POLARITY_NORMAL | STM32_PWM_COMPLEMENTARY)>; /* D6, PB1; 20 kHz */
label = "Motor BIN2 PWM";
};
};
zephyr,user {
encoder-isr-probe-gpios = <&gpioa 12 GPIO_ACTIVE_HIGH>; /* D2, PA12 */
};
};
&timers1 {
status = "okay";
/* No prescaler: 80 MHz timer clock, so a 50 us period is 4000 counts. */
st,prescaler = <0>;
pwm1: pwm {
status = "okay";
pinctrl-0 = <&tim1_ch1_pa8 &tim1_ch3n_pb1>;
pinctrl-names = "default";
};
};
+11
View File
@@ -0,0 +1,11 @@
CONFIG_CPP=y
CONFIG_GPIO=y
CONFIG_PWM=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_PRINTK=y
CONFIG_CBPRINTF_FP_SUPPORT=y