17 Commits
Author SHA1 Message Date
helgie22 e9f9a6d15c bætti aftur við lyklaborðinu til þess að geta prófað allt saman 2026-10-16 16:04:29 +00:00
helgie22 0ee6879f99 undirbúningur falla fyrir can samskipti og prófun með lyklaborði 2026-10-16 15:12:07 +00:00
helgie22 6fdf57cca7 bætti við can móttökuprófi með síu (Part 2.1) 2026-10-15 15:51:19 +00:00
helgie22 13a89d7311 gerði breytingar pi megin og þarf að pusha fyrir tölvuna 2026-10-15 15:29:08 +00:00
helgie22 b4f274a65c testa can með l4.4 2026-10-15 15:24:42 +00:00
helgie22 9762afb003 can send test bætt við 2026-10-15 14:16:14 +00:00
helgie22 e41fed4ea0 Byrja á að gera can_test 2026-10-15 13:14:46 +00:00
helgie22 1341c423cb Merge branch 'Isak_part1' of ssh://gitea.cs.ru.is:2222/helgie22/ESP_Project4_H26 into Isak_part1 2026-10-15 12:18:35 +00:00
helgie22 6d384265eb Heljarinnar breytingar til þess að laga nokkra hluti í part 1. 2026-10-15 12:15:50 +00:00
isakg22 b90b84d94c Bætti við interrupt (pin D2) til að skoða encoder lestur, umb fyrir Part 1 í verkefni 2026-10-14 16:02:44 +00:00
isakg22 1250860efe Uppfærði kóðann til að virkja Fault takka 2026-10-14 12:21:45 +00:00
isakg22 782feb2d04 vantaði baud rate inn í ini.
Allt virkar upp að vissu marki.
2026-10-08 15:39:42 +00:00
isakg22 edd5e40e15 lagaði villu, vantaði bil á einum stað í rotary encoder driver h og svo lagði aðeins í overlay 2026-10-08 15:24:04 +00:00
isakg22 c2d8780478 Nýjir kóðar og uppfærðir gamlir kóðar fyrir arduino yfir í zephyr 2026-10-08 15:18:32 +00:00
isakg22 53091f68df Setti inn allar skrár frá Project 3 og bætti við overlay í samræmi við controlloerinn og pin out. 2026-10-08 13:11:39 +00:00
helgie22 9194d71f10 Merge branch 'master'
Merge á master og main því ég byrjaði local git og tengdi svo við repo á
netinu
2026-10-08 12:40:00 +00:00
helgie22 215bb450e3 Byrjaði zephyr project með platformio 2026-10-08 12:32:16 +00:00
47 changed files with 2453 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
* text=auto eol=lf
*.bat text eol=crlf
+8
View File
@@ -0,0 +1,8 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
scripts
__pycache__/
*.typ
+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"
}
+1 -1
View File
@@ -1,3 +1,3 @@
# ESP_Project4_H26 # 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
+80
View File
@@ -0,0 +1,80 @@
@startuml P4-architecture
' Project 4, Part 2.2: Layers of the reduced CANopen node (Node ID 1).
' Arrows point from the user to the thing it uses.
' skinparam packageStyle rectangle
skinparam classAttributeIconSize 0
hide empty members
package "Application layer (uses OSI 7)" {
class MotorController {
+ to_operational()
+ to_pre_operational()
+ to_stopped()
}
abstract class State
class SetpointGenerator
class PI_controller
}
package "CANopen layer (OSI 5-7)" {
class CanopenNode {
+ on_frame(frame)
+ poll(now_ms)
}
class SdoServer
class ObjectDictionary
interface NodeApplication {
+ on_nmt_command(cmd)
+ set_target_velocity(rpm)
+ get_velocity() : rpm
+ get_duty() : 0.1 %
}
interface CanBus {
+ send(frame)
}
}
package "Platform layer (Zephyr)" {
class ZephyrCanBus
class MotorDriver
class RotaryEncoderDriver
}
package "Hardware (OSI 1-2)" {
class "CAN controller + transceiver" as CAN
}
MotorController -> State
MotorController --> SetpointGenerator
MotorController --> PI_controller
MotorController ..|> NodeApplication
MotorController --> MotorDriver
MotorController --> RotaryEncoderDriver
CanopenNode --> SdoServer
SdoServer --> ObjectDictionary
CanopenNode --> ObjectDictionary
CanopenNode --> NodeApplication
CanopenNode --> CanBus
ZephyrCanBus ..|> CanBus
ZephyrCanBus --> CAN
note top of CanopenNode
NMT (0x000), boot-up (0x701),
RPDO1 (0x201), TPDO1 (0x181)
end note
note left of SdoServer
0x601 / 0x581, expedited only
abort codes 0x06020000, 0x08000022
end note
note right of ObjectDictionary
0x1000, 0x2000-0x2010, 0x606C,
0x6083, 0x6084, 0x60FF
access rules, little-endian packing
end note
@enduml
+29
View File
@@ -0,0 +1,29 @@
@startuml
' skinparam linetype ortho
' skinparam nodesep 100
skinparam ranksep 80
skinparam state {
BackgroundColor LightBlue
}
' hide empty members
Application <|-- CANopen
class Application {
{field} state machine
{field} controllers
{field} setpoint generator
{method} hello world
}
CANopen : Zephyr
Class01 <-- Class02
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class09 -- Class10
@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
+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
+29
View File
@@ -0,0 +1,29 @@
#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;
NodeState id() const override { return NodeState::INITIALIZATION; }
};
#endif
+123
View File
@@ -0,0 +1,123 @@
#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"
#include "node_types.h"
#include "node_application.h"
class State;
class MotorController : public NodeApplication
{
private:
static constexpr double DEFAULT_TARGET_RPM = 0.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 &p_controller;
P_controller &pi_controller;
P_controller *controller; // Bendir á p_controller eða pi_controller
Digital_out led;
double target_rpm; // Það sem beðið er um (RPDO1)
double reference_rpm; // Það sem stýringin fær
uint32_t profile_acceleration; // RPM/s
uint32_t profile_deceleration;
// Síðasta sýni, notað fyrir CSV-útprentun
double last_actual_rpm;
double last_pwm;
public:
MotorController(
RotaryEncoderDriver &encoder,
MotorDriver &motor,
P_controller &p_controller,
P_controller &pi_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() override;
void to_pre_operational() override;
void to_operational() override;
void to_stopped() override;
NodeState get_state() const override;
bool fault_active();
// Skilar true þegar nýtt sýni var keyrt í gegnum stýringuna
bool run_speed_controller();
void set_target_rpm(double rpm) override { target_rpm = rpm; }
double get_target_rpm() const override { return target_rpm; }
double get_reference_rpm() const { return reference_rpm; }
double get_actual_rpm() const override { return last_actual_rpm; }
double get_pwm() const { return last_pwm; }
double get_duty_percent() const override { return last_pwm / PWM_PER_PERCENT; }
void set_profile_acceleration(uint32_t value) override {profile_acceleration = value; }
uint32_t get_profile_acceleration() const override { return profile_acceleration; }
void set_profile_deceleration(uint32_t value) override {profile_deceleration = value; }
uint32_t get_profile_deceleration() const override { return profile_deceleration; }
void stop_motor();
void reset_speed_measurement();
void reset_controller();
void reset_parameters() override;
void set_Kp(double value) override;
double get_Kp() const override { return controller->get_Kp(); }
bool set_Ti(double value) override;
double get_Ti() const override { return controller->get_Ti(); }
void select_controller(ControllerType type) override;
ControllerType get_controller_type() const override;
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
+51
View File
@@ -0,0 +1,51 @@
#ifndef NODE_APPLICATION_H
#define NODE_APPLICATION_H
#include <stdint.h>
#include "node_types.h"
class NodeApplication
{
public:
virtual ~NodeApplication() {}
// NMT
virtual void to_initialization() = 0;
virtual void to_pre_operational() = 0;
virtual void to_operational() = 0;
virtual void to_stopped() = 0;
virtual void reset_parameters() = 0;
virtual NodeState get_state() const = 0;
// 0x2000
virtual void select_controller(ControllerType type) = 0;
virtual ControllerType get_controller_type() const = 0;
// 0x2001
virtual void set_Kp(double value) = 0;
virtual double get_Kp() const = 0;
// 0x2002
virtual bool set_Ti(double value) = 0;
virtual double get_Ti() const = 0;
// 0x2010
virtual double get_duty_percent() const = 0;
// 0x606C
virtual double get_actual_rpm() const = 0;
// 0x6083
virtual void set_profile_acceleration(uint32_t value) = 0;
virtual uint32_t get_profile_acceleration() const = 0;
// 0x6084
virtual void set_profile_deceleration(uint32_t value) = 0;
virtual uint32_t get_profile_deceleration() const = 0;
// 0x60FF (RPDO1)
virtual void set_target_rpm(double value) = 0;
virtual double get_target_rpm() const = 0;
};
#endif
+18
View File
@@ -0,0 +1,18 @@
#ifndef NODE_TYPES_H
#define NODE_TYPES_H
enum class ControllerType
{
P,
PI
};
enum class NodeState
{
INITIALIZATION,
PRE_OPERATIONAL,
OPERATIONAL,
STOPPED
};
#endif
+31
View File
@@ -0,0 +1,31 @@
#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;
NodeState id() const override { return NodeState::OPERATIONAL; }
};
#endif
+32
View File
@@ -0,0 +1,32 @@
#ifndef P_CONTROLLER_H
#define P_CONTROLLER_H
class P_controller
{
protected:
double Kp;
double default_Kp; // Gildið úr constructornum, notað af restore_defaults()
public:
P_controller(double Kp);
virtual ~P_controller() {}
virtual double update(double ref, double actual);
virtual void reset() {}
virtual void restore_defaults(); //
virtual double get_Kp() const { return Kp; }
virtual double get_Ti() const { return 0.0; }
virtual void set_Kp(double value)
{
Kp = value;
}
virtual bool set_Ti(double value)
{
// P controller does not use Ti
(void)value;
return true;
}
};
#endif
+26
View File
@@ -0,0 +1,26 @@
#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 default_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 restore_defaults() override;
bool set_Ti(double value) override;
double get_Ti() const override { return Ti; }
};
#endif
+45
View File
@@ -0,0 +1,45 @@
#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,
ENTER_RPM
};
InputMode input_mode;
char input_buffer[16];
uint8_t input_index;
public:
// PreOperationalState(P_controller& p_controller, P_controller& pi_controller);
PreOperationalState();
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
NodeState id() const override { return NodeState::PRE_OPERATIONAL; }
};
#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
+34
View File
@@ -0,0 +1,34 @@
#ifndef STATE_H
#define STATE_H
#include "node_types.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
virtual NodeState id() const = 0; //
};
#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:
int64_t previous_time;
bool led_state;
public:
StoppedState();
void enter() override;
void update() override;
void command(char c) override;
void exit() override;
NodeState id() const override { return NodeState::STOPPED; }
};
#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
+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
Executable
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
# Brings up can0 at the same bitrate as the Nucleo (125000 in src/CAN_FRAME_TEST.c and src/can_send_test.c).
# Usage: ./can_up.sh [bitrate] (default 125000)
BITRATE=${1:-125000}
sudo ip link set can0 down 2>/dev/null
sudo ip link set can0 up type can bitrate "$BITRATE"
ip -details -statistics link show can0
+41
View File
@@ -0,0 +1,41 @@
; 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
; CSV-útprentun í Operational fyrir step/load response (sjá capture_serial.py)
; build_flags = -D LOG_CSV=1
; Part 2.1: CAN bring-up test (src/can_send_test.c) instead of the motor app.
; Build/upload: pio run -e can_test -t upload
; [env:can_test]
; extends = env:nucleo_l432kc
; build_flags = -D CAN_SEND_TEST=1
; L4.4 example (src/CAN_FRAME_TEST.c) instead of the motor app.
; Build/upload: pio run -e can_frame_test -t upload
; [env:can_frame_test]
; extends = env:nucleo_l432kc
; build_flags = -D CAN_FRAME_TEST=1
; CAN reception with a filter (src/can_rx_test.c) instead of the motor app.
; Build/upload: pio run -e can_rx_test -t upload
; [env:can_rx_test]
; extends = env:nucleo_l432kc
; build_flags = -D CAN_RX_TEST=1
+49
View File
@@ -0,0 +1,49 @@
#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)
// L4.4 example. Built only with the can_frame_test environment
// (-D CAN_FRAME_TEST=1); main() in main.c calls this function.
void can_frame_test_main(void)
{
const struct device *dev = DEVICE_DT_GET(CANBUS_NODE);
if (!device_is_ready(dev)) {
printk("CAN device not ready\n");
return;
}
int err = can_set_bitrate(dev, 125000);
if (err != 0) {
printk("Error setting CAN bitrate (err %d)\n", err);
return;
}
err = can_start(dev);
if (err != 0) {
printk("Error starting CAN controller (err %d)\n", err);
return;
}
struct can_frame frame = {
.flags = 0,
.id = 0x456,
.dlc = 4,
.data = {1, 2, 3, 4, 5, 6, 7, 8},
};
while (1) {
k_msleep(500);
err = can_send(dev, &frame, K_MSEC(100), NULL, NULL);
if (err != 0) {
printk("Sending failed (err %d)\n", err);
return;
}
}
}
+125
View File
@@ -0,0 +1,125 @@
#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"
#include "node_application.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;
static OperationalState operational_state;
static StoppedState stopped_state;
// Stöðurnar verða að vera skilgreindar á undan motor_controller
static MotorController motor_controller(
encoder,
motor,
p_controller,
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();
static NodeState last_state = NodeState::INITIALIZATION;
const NodeState now = motor_controller.get_state();
if (now != last_state)
{
printk("get_state: %d -> %d\n", (int)last_state, (int)now);
last_state = now;
}
// 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));
}
}
+80
View File
@@ -0,0 +1,80 @@
// Part 2.1: CAN reception with a filter. Only frames with id RX_CAN_ID are
// accepted. The driver puts them in a message queue from interrupt context,
// and the main loop takes them out and prints them.
// Built only with the can_rx_test environment (-D CAN_RX_TEST=1).
//
// Test from the Pi:
// cansend can0 321#DEADBEEF -> printed by the Nucleo
// cansend can0 322#DEADBEEF -> rejected by the filter, nothing printed
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/can.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#define CANBUS_NODE DT_CHOSEN(zephyr_canbus)
#define CAN_BITRATE 125000 // Must match the bitrate of can0 on the Pi
#define RX_CAN_ID 0x321 // The only id this node accepts
// Queue for up to 10 received frames. The CAN driver fills it in the
// receive interrupt; nothing else runs in interrupt context.
CAN_MSGQ_DEFINE(rx_msgq, 10);
void can_rx_test_main(void)
{
const struct device *dev = DEVICE_DT_GET(CANBUS_NODE);
if (!device_is_ready(dev)) {
printk("CAN device not ready\n");
return;
}
int err = can_set_bitrate(dev, CAN_BITRATE);
if (err != 0) {
printk("Error setting CAN bitrate (err %d)\n", err);
return;
}
// Accept only RX_CAN_ID: all 11 bits of the id must match the mask.
const struct can_filter filter = {
.id = RX_CAN_ID,
.mask = CAN_STD_ID_MASK,
#ifdef CAN_FILTER_DATA
// Older Zephyr versions must be told to match data frames.
.flags = CAN_FILTER_DATA,
#else
.flags = 0,
#endif
};
// Returns a filter id (>= 0) on success.
int filter_id = can_add_rx_filter_msgq(dev, &rx_msgq, &filter);
if (filter_id < 0) {
printk("Error adding RX filter (err %d)\n", filter_id);
return;
}
err = can_start(dev);
if (err != 0) {
printk("Error starting CAN controller (err %d)\n", err);
return;
}
printk("CAN RX test: %d bit/s, accepting id 0x%03X\n",
CAN_BITRATE, RX_CAN_ID);
struct can_frame frame;
while (1) {
// Sleeps until a frame is in the queue, then processes it here in
// the main thread, where printing is allowed.
k_msgq_get(&rx_msgq, &frame, K_FOREVER);
printk("Received id 0x%03X [%d]:", frame.id, frame.dlc);
for (int i = 0; i < can_dlc_to_bytes(frame.dlc); i++) {
printk(" %02X", frame.data[i]);
}
printk("\n");
}
}
+61
View File
@@ -0,0 +1,61 @@
// Part 2.1: CAN bring-up. Sends one frame every 100 ms so it can be seen
// on the oscilloscope and with `candump can0` on the Raspberry Pi.
// Built only with the can_test environment (-D CAN_SEND_TEST=1).
#include <zephyr/device.h>
#include <zephyr/drivers/can.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
// Change these to see the difference in candump.
#define TEST_CAN_ID 0x123 // 11-bit standard id (0x000 - 0x7FF)
#define TEST_CAN_DLC 8 // Number of data bytes (0 - 8)
#define CAN_BITRATE 125000 // Must match the bitrate of can0 on the Pi
static const struct device *const can_dev = DEVICE_DT_GET(DT_NODELABEL(can1));
void can_send_test_main(void)
{
if (!device_is_ready(can_dev)) {
printk("CAN device is not ready\n");
return;
}
int err = can_set_bitrate(can_dev, CAN_BITRATE);
if (err != 0) {
printk("can_set_bitrate failed: %d\n", err);
return;
}
err = can_start(can_dev);
if (err != 0) {
printk("can_start failed: %d\n", err);
return;
}
printk("CAN started at %d bit/s, sending id 0x%03X\n",
CAN_BITRATE, TEST_CAN_ID);
struct can_frame frame = {
.id = TEST_CAN_ID,
.dlc = TEST_CAN_DLC,
.flags = 0, // Standard id, data frame
};
uint8_t counter = 0;
while (true) {
// Byte 0 counts up so you can see new frames arriving.
frame.data[0] = counter++;
for (int i = 1; i < TEST_CAN_DLC; i++) {
frame.data[i] = i;
}
// Blocks until the frame is sent (and acknowledged by another node).
err = can_send(can_dev, &frame, K_MSEC(100), NULL, NULL);
if (err != 0) {
printk("can_send failed: %d\n", err);
}
k_sleep(K_MSEC(100));
}
}
+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);
}
}
+35
View File
@@ -0,0 +1,35 @@
#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");
}
+20
View File
@@ -0,0 +1,20 @@
#include <zephyr/kernel.h>
void motor_application_main(void);
void can_send_test_main(void);
void can_frame_test_main(void);
void can_rx_test_main(void);
int main(void)
{
#if CAN_SEND_TEST
can_send_test_main();
#elif CAN_FRAME_TEST
can_frame_test_main();
#elif CAN_RX_TEST
can_rx_test_main();
#else
motor_application_main();
#endif
return 0;
}
+223
View File
@@ -0,0 +1,223 @@
#include "motor_controller.h"
#include "state.h"
MotorController::MotorController(
RotaryEncoderDriver &encoder,
MotorDriver &motor,
P_controller &p_controller,
P_controller &pi_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),
p_controller(p_controller),
pi_controller(pi_controller),
controller(&pi_controller), // PI er sjálfgefin stýring
led(LED_PIN),
target_rpm(DEFAULT_TARGET_RPM),
reference_rpm(DEFAULT_TARGET_RPM),
profile_acceleration(0),
profile_deceleration(0),
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;
}
reference_rpm = target_rpm;
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::reset_parameters()
{
p_controller.restore_defaults();
pi_controller.restore_defaults();
select_controller(ControllerType::PI);
set_target_rpm(DEFAULT_TARGET_RPM);
set_profile_acceleration(0);
set_profile_deceleration(0);
}
void MotorController::set_Kp(double value)
{
controller->set_Kp(value);
}
bool MotorController::set_Ti(double value)
{
return controller->set_Ti(value);
}
void MotorController::select_controller(ControllerType type)
{
if (type == ControllerType::PI)
{
controller = &pi_controller;
}
else
{
controller = &p_controller;
}
controller->reset();
}
ControllerType MotorController::get_controller_type() const
{
if (controller == &pi_controller)
{
return ControllerType::PI;
}
else
{
return ControllerType::P;
}
}
void MotorController::to_stopped()
{
transition_to(stopped_state);
}
NodeState MotorController::get_state() const
{
if (current_state == nullptr)
{
return NodeState::INITIALIZATION;
}
else{
return current_state->id();
}
}
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;
}
+100
View File
@@ -0,0 +1,100 @@
#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;
// Tímabundið til þess að prófa undirbúning CAN communication
printk("Kp=%.4f Ti=%.4f Target=%.0f RPM\n",
context->get_Kp(), context->get_Ti(), context->get_target_rpm());
#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 communication\n"); // Heldur Kp og Ti gildum
context->to_initialization();
}
else if (c == 'p' || c == 'P')
{
printk("Command: set pre-operational\n");
context->to_pre_operational();
}
else if (c == '+') // Bætti þessu við til þess að prófa að rampa rpm í operational state
{
context->set_target_rpm(context->get_target_rpm() + 10);
printk("Target: %.0f RPM\n", context->get_target_rpm());
}
else if (c == '-')
{
context->set_target_rpm(context->get_target_rpm() - 10);
printk("Target: %.0f RPM\n", context->get_target_rpm());
}
// Tímabundið til þess að testa reset parameters
else if (c == 'x' || c == 'X')
{
printk("Command: reset node\n");
context->reset_parameters();
context->to_initialization();
}
else if (c == '?')
{
printk("Actual=%.0f RPM Duty=%.1f\n",
context->get_actual_rpm(), context->get_duty_percent());
}
}
void OperationalState::exit()
{
context->stop_motor();
context->led_off();
}
+20
View File
@@ -0,0 +1,20 @@
#include "p_controller.h"
P_controller::P_controller(double Kp)
: Kp(Kp),
default_Kp(Kp)
{
}
double P_controller::update(double ref, double actual)
{
double error = ref - actual;
return Kp * error;
}
void P_controller::restore_defaults()
{
Kp = default_Kp;
reset();
}
+50
View File
@@ -0,0 +1,50 @@
#include "pi_controller.h"
PI_controller::PI_controller(double Kp, double Ti, double T) : P_controller(Kp), Ti(Ti), default_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;
}
bool PI_controller::set_Ti(double value)
{
if (value > 0.0)
{
Ti = value;
return true;
}
else
{
return false;
}
}
void PI_controller::restore_defaults()
{
P_controller::restore_defaults();
Ti = default_Ti;
}
+183
View File
@@ -0,0 +1,183 @@
#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)
PreOperationalState::PreOperationalState()
// : 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", (context->get_controller_type() == ControllerType::PI) ? "PI" : "P");
// Prófaði þetta, veit ekki hvort þetta skili bool
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)
{
if (context->set_Ti(value))
{
printk("Ti set to: %.4f\n", value);
}
else
{
printk("Ti rejected, must be > 0");
}
}
else if (input_mode == ENTER_RPM)
{
context->set_target_rpm(value);
printk("Target RPM set to: %.0f\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");
}
else if (c == 'v' || c == 'V')
{
input_mode = ENTER_RPM;
input_index = 0;
printk("Enter RPM and press Enter:\n");
}
// Select controller algorithm, the controller is passed to the motor controller
else if (c == 'c' || c == 'C')
{
if (context->get_controller_type() == ControllerType::P)
{
context->select_controller(ControllerType::PI);
printk("Controller: PI, Kp=%.4f, Ti=%.4f\n",
context->get_Kp(), context->get_Ti());
}
else
{
context->select_controller(ControllerType::P);
printk("Controller: P, Kp=%.4f\n",
context->get_Kp());
}
}
}
void PreOperationalState::exit()
{
context->led_off();
}
+155
View File
@@ -0,0 +1,155 @@
#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>
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);
}
+79
View File
@@ -0,0 +1,79 @@
#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);
}
}
+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
+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})
+90
View File
@@ -0,0 +1,90 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/pwm/stm32_pwm.h>
/ {
chosen {
zephyr,canbus = &can1;
};
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";
};
};
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";
};
};
};
&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";
};
};
/* CAN1 to the transceiver: RX on D10 (PA11), TX on D2 (PA12).
* The bitrate is set in the code with can_set_bitrate(). */
&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_UART_INTERRUPT_DRIVEN=y
CONFIG_PRINTK=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_CAN=y