……มาถึงคลิปชุดการเรียนรู้ Inverted Pendulum(ลูกตุ้มกลับหัว) ตอนที่ 4 กันแล้ว จากเมื่อตอนก่อนหน้าเราได้เขียนโปรแกรมวัดค่าและแสดงผลพลังงานภายใน Pendulum ในตอนนี้เราจะทำการเขียนโปรแกรมควบคุมการเคลื่อนที่ของ Cart ให้พลังงานภายใน Pendulum เพิ่มขึ้น เพื่อแกว่ง Pendulum ขึ้นไปยังจุดสูงสุดกันครับ….
ตัวอย่าง Code old version
#include <ESP32Encoder.h>
#include <math.h>
hw_timer_t *timer = NULL;
hw_timer_t *timer1 = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
ESP32Encoder encoder;
const int encoderA_pin = 25;
const int encoderB_pin = 26;
const int encoder_CPR = 1600;
const int user_button_pin = 5;
const int driver_dir_pin = 17;
const int driver_step_pin = 16;
const int driver_EN_PIN = 4;
int peroid_time = 0;
volatile uint32_t Counter = 0;
volatile uint8_t printing = 0;
volatile float Td = 0;
volatile float xt1 = 0;
volatile float xt2 = 0;
const int dt = 5; // ms
const float J_inertial = 0.0005242317; // kg.m^2
const float C_damp = 0; // N.m/(rad/s)
const float M_mass = 0.02791; // kg
const float R_cg = 0.115; // m
const float gravity = 9.81; // m/s^2
volatile float theta = 0; // rad
volatile float angular_velo = 0; // rad/s
static volatile float w_tmp1, w_tmp2, w_tmp3, w_tmp4;
int freq = 1;
int channel = 0;
int resolution = 2;
#define EN_PIN 4 //
#define DIR_PIN 17 //
#define STEP_PIN 16 //
#define CS_PIN 23 //
#define SCK_PIN 19 //
#define SDI_PIN 12 //
#define SDO_PIN 14 //
#define sgn(x) ((x) < 0 ? -1 : ((x) > 0 ? 1 : 0))
#include <TMC2130Stepper.h>
TMC2130Stepper driver = TMC2130Stepper(EN_PIN, DIR_PIN, STEP_PIN, CS_PIN, SDI_PIN, SDO_PIN, SCK_PIN);
float cart_ac_max = 0;
float cart_ac = 0;
float cart_velo = 0;
float cart_position = 0;
float Ek = 0; //mJ
float Ep = 0; //mJ
float E_total = 0; //mJ
float driver_frequncy = 0;
void IRAM_ATTR read_angle(void)
{
static volatile int32_t tmp, tmp1, tmp2, tmp3;
portENTER_CRITICAL_ISR(&timerMux);
tmp3 = tmp2;
tmp2 = tmp1;
tmp1 = encoder.getCount();
if (tmp2 > tmp3 && tmp2 > tmp1)
{
printing = 1;
Counter = tmp1;
}
if (tmp1 == 0 && tmp > 0)
{
printing = 2;
Td = (float)timerRead(timer) / 40000000.0f;
timerWrite(timer, 0);
}
tmp = tmp1;
portEXIT_CRITICAL_ISR(&timerMux);
}
void IRAM_ATTR update_angular_velo(void)
{
static volatile int32_t cnt;
portENTER_CRITICAL_ISR(&timerMux);
timerWrite(timer1, 0); // reset encoder wdt
float w_tmp = (float)timerRead(timer) / 40000000.0f;
timerWrite(timer, 0);
if (w_tmp > 0.0000001)
{
w_tmp = -PI * 0.00125f / w_tmp;
if (cnt < encoder.getCount())
{
w_tmp = -w_tmp;
}
cnt = encoder.getCount();
w_tmp4 = w_tmp3;
w_tmp3 = w_tmp2;
w_tmp2 = w_tmp1;
w_tmp1 = w_tmp;
}
portEXIT_CRITICAL_ISR(&timerMux);
}
void IRAM_ATTR onTimer(void)
{
portENTER_CRITICAL_ISR(&timerMux);
w_tmp4 = w_tmp3;
w_tmp3 = w_tmp2;
w_tmp2 = w_tmp1;
w_tmp1 = 0;
portEXIT_CRITICAL_ISR(&timerMux);
}
float count_to_angle(int32_t cnt)
{
float angle = (float)cnt * 360.0f / (float)encoder_CPR;
return angle;
}
float count_to_rad(int32_t cnt)
{
float rad = (float)cnt * 2.0f * PI / (float)encoder_CPR;
return rad;
}
int microstep = 256;
void setup()
{
delay(100);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
delay(100);
driver.begin(); // Initiate pins and registeries
driver.rms_current(600); // Set stepper current to 600mA. The command is the same as command TMC2130.setCurrent(600, 0.11, 0.5);
driver.stealthChop(1); // Enable extremely quiet stepping
driver.stealth_autoscale(1);
driver.microsteps(microstep);
// put your setup code here, to run once:
pinMode(driver_dir_pin, OUTPUT);
pinMode(driver_EN_PIN, OUTPUT);
pinMode(encoderA_pin, INPUT_PULLUP);
pinMode(encoderB_pin, INPUT_PULLUP);
encoder.setCount(0);
encoder.attachFullQuad(encoderA_pin, encoderB_pin);
timer = timerBegin(0, 2, true);
timerAlarmWrite(timer, UINT32_MAX, true);
timerAlarmEnable(timer);
// attachInterrupt(digitalPinToInterrupt(encoderA_pin), read_angle, CHANGE);
// attachInterrupt(digitalPinToInterrupt(encoderB_pin), read_angle, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderA_pin), update_angular_velo, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderB_pin), update_angular_velo, CHANGE);
timer1 = timerBegin(1, 80, true);
timerAttachInterrupt(timer1, &onTimer, true);
timerAlarmWrite(timer1, dt * 1000, true);
timerAlarmEnable(timer1);
pinMode(user_button_pin, INPUT_PULLUP);
ledcSetup(channel, freq, resolution);
ledcAttachPin(driver_step_pin, channel);
ledcWrite(channel, 2);
Serial.begin(115200);
}
uint32_t prevt = 0;
void loop()
{
// Ploting pendulun's internal energy
portENTER_CRITICAL_ISR(&timerMux);
angular_velo = (w_tmp1 + w_tmp2 + w_tmp3 + w_tmp4) / 4;
theta = count_to_rad(encoder.getCount());
/////////////////////////////////////////////////////////////////////////////
// 0.2 m/s^2 ~ ถึง 0-100 km/hr in 139 s
// 0.4 m/s^2 ~ ถึง 0-100 km/hr in 70 s
// 0.8 m/s^2 ~ ถึง 0-100 km/hr in 35 s
cart_ac_max = 0.8; // m/s^2
cart_ac = -cart_ac_max * sgn(angular_velo) * sgn(-cosf(theta)); // m/s^2 From control rule
// making the cart stay center point, + acc25% direction to center
const float acc_offset = 0.25;
if (cart_position > 0.05)
{
if (cart_ac > 0)
cart_ac *= (1.0f - acc_offset);
else
cart_ac *= (1.0f + acc_offset);
}
else if (cart_position < -0.05)
{
if (cart_ac < 0)
cart_ac *= (1.0f - acc_offset);
else
cart_ac *= (1.0f + acc_offset);
}
cart_position = cart_position + cart_velo * (float)dt * 0.001; // m
cart_velo = cart_velo + cart_ac * (float)dt * 0.001; // m/s
// Control minimum speed
if (fabs(cart_velo) > 0)
{
driver_frequncy = fabs(cart_velo) * microstep * 5 * 1000.0f;
ledcSetup(channel, driver_frequncy, 1);
//// Control Direction
if (cart_velo > 0)
digitalWrite(driver_dir_pin, HIGH);
else
digitalWrite(driver_dir_pin, LOW);
ledcWrite(channel, 1);
}
else
ledcWrite(channel, 0);
//// User button reset
if (digitalRead(user_button_pin) == 1)
{
cart_ac = 0;
cart_velo = 0;
cart_position = 0;
digitalWrite(driver_EN_PIN, HIGH);
}
else
digitalWrite(driver_EN_PIN, LOW);
/////////////////////////////////////////////////////////////////////////////
Ek = 0.5f * (J_inertial)*angular_velo * angular_velo * 1000; //mJ
Ep = M_mass * gravity * R_cg * (-cosf(theta) - 1) * 1000; //mJ
E_total = Ek + Ep;
Serial.print(10 * cart_ac);
Serial.print(", ");
Serial.print(Ek);
Serial.print(", ");
Serial.print(Ep);
Serial.print(", ");
Serial.print(E_total);
Serial.println();
portEXIT_CRITICAL_ISR(&timerMux);
while (millis() < prevt) //constant sampling frequncy
;
prevt = millis() + dt;
}
ตัวอย่าง Code New version สำหรับชุดคิท
#include <ESP32Encoder.h> // https://github.com/madhephaestus/ESP32Encoder
#include <math.h>
hw_timer_t *timer = NULL;
hw_timer_t *timer1 = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
ESP32Encoder encoder;
const int encoderA_pin = 21;
const int encoderB_pin = 22;
const int encoder_CPR = 2400;
const int user_button_pin = 19;
const int driver_dir_pin = 14;
const int driver_step_pin = 12;
const int driver_EN_PIN = 13;
const int limit_A = 34;
const int limit_B = 35;
int peroid_time = 0;
volatile uint32_t Counter = 0;
volatile uint8_t printing = 0;
volatile float Td = 0;
volatile float xt1 = 0;
volatile float xt2 = 0;
volatile float limit_locking = 0;
const int dt = 5; // ms
const float J_inertial = 0.0005242317; // kg.m^2
const float C_damp = 0; // N.m/(rad/s)
const float M_mass = 0.02791; // kg
const float R_cg = 0.115; // m
const float gravity = 9.81; // m/s^2
volatile float theta = 0; // rad
volatile float angular_velo = 0; // rad/s
static volatile float w_tmp1, w_tmp2, w_tmp3, w_tmp4;
int freq = 1;
int channel = 0;
int resolution = 2;
#define EN_PIN 4 //
#define DIR_PIN 17 //
#define STEP_PIN 16 //
#define CS_PIN 23 //
#define SCK_PIN 19 //
#define SDI_PIN 12 //
#define SDO_PIN 14 //
#define sgn(x) ((x) < 0 ? -1 : ((x) > 0 ? 1 : 0))
float cart_ac_max = 0;
float cart_ac = 0;
float cart_velo = 0;
float cart_position = 0;
float Ek = 0; //mJ
float Ep = 0; //mJ
float E_total = 0; //mJ
float driver_frequncy = 0;
void IRAM_ATTR read_angle(void)
{
static volatile int32_t tmp, tmp1, tmp2, tmp3;
portENTER_CRITICAL_ISR(&timerMux);
tmp3 = tmp2;
tmp2 = tmp1;
tmp1 = encoder.getCount();
if (tmp2 > tmp3 && tmp2 > tmp1)
{
printing = 1;
Counter = tmp1;
}
if (tmp1 == 0 && tmp > 0)
{
printing = 2;
Td = (float)timerRead(timer) / 40000000.0f;
timerWrite(timer, 0);
}
tmp = tmp1;
portEXIT_CRITICAL_ISR(&timerMux);
}
void IRAM_ATTR update_angular_velo(void)
{
static volatile int32_t cnt;
portENTER_CRITICAL_ISR(&timerMux);
timerWrite(timer1, 0); // reset encoder wdt
float w_tmp = (float)timerRead(timer) / 40000000.0f;
timerWrite(timer, 0);
if (w_tmp > 0.0000001)
{
w_tmp = 2.0f * PI / encoder_CPR / w_tmp;
if (cnt < encoder.getCount())
{
w_tmp = -w_tmp;
}
cnt = encoder.getCount();
w_tmp4 = w_tmp3;
w_tmp3 = w_tmp2;
w_tmp2 = w_tmp1;
w_tmp1 = w_tmp;
}
portEXIT_CRITICAL_ISR(&timerMux);
}
void IRAM_ATTR onTimer(void)
{
portENTER_CRITICAL_ISR(&timerMux);
w_tmp4 = w_tmp3;
w_tmp3 = w_tmp2;
w_tmp2 = w_tmp1;
w_tmp1 = 0;
portEXIT_CRITICAL_ISR(&timerMux);
}
void IRAM_ATTR limit_sw_lock(void) // limit locking interrupt function
{
ledcSetup(channel, driver_frequncy, 0);
limit_locking = 1;
}
float count_to_angle(int32_t cnt)
{
float angle = (float)cnt * 360.0f / (float)encoder_CPR;
return angle;
}
float count_to_rad(int32_t cnt)
{
float rad = (float)cnt * 2.0f * PI / (float)encoder_CPR;
return rad;
}
int microstep = 64; //fixed value
void setup()
{
delay(100);
// put your setup code here, to run once:
pinMode(driver_dir_pin, OUTPUT);
pinMode(driver_EN_PIN, OUTPUT);
pinMode(encoderA_pin, INPUT_PULLUP);
pinMode(encoderB_pin, INPUT_PULLUP);
pinMode(limit_A, INPUT_PULLUP);
pinMode(limit_B, INPUT_PULLUP);
encoder.attachFullQuad(encoderB_pin, encoderA_pin); // reverse encoder direction
encoder.setCount(0);
timer = timerBegin(0, 2, true);
timerAlarmWrite(timer, UINT32_MAX, true);
timerAlarmEnable(timer);
// attachInterrupt(digitalPinToInterrupt(encoderA_pin), read_angle, CHANGE);
// attachInterrupt(digitalPinToInterrupt(encoderB_pin), read_angle, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderA_pin), update_angular_velo, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderB_pin), update_angular_velo, CHANGE);
attachInterrupt(digitalPinToInterrupt(limit_A), limit_sw_lock, FALLING); // add stopping limit switch
attachInterrupt(digitalPinToInterrupt(limit_B), limit_sw_lock, FALLING);
timer1 = timerBegin(1, 80, true);
timerAttachInterrupt(timer1, &onTimer, true);
timerAlarmWrite(timer1, dt * 1000, true);
timerAlarmEnable(timer1);
pinMode(user_button_pin, INPUT_PULLUP);
ledcSetup(channel, freq, resolution);
ledcAttachPin(driver_step_pin, channel);
ledcWrite(channel, 2);
Serial.begin(115200);
}
uint32_t prevt = 0;
void loop()
{
// Ploting pendulun's internal energy
portENTER_CRITICAL_ISR(&timerMux);
angular_velo = (w_tmp1 + w_tmp2 + w_tmp3 + w_tmp4) / 4;
theta = count_to_rad(encoder.getCount());
/////////////////////////////////////////////////////////////////////////////
// 0.2 m/s^2 ~ ถึง 0-100 km/hr in 139 s
// 0.4 m/s^2 ~ ถึง 0-100 km/hr in 70 s
// 0.8 m/s^2 ~ ถึง 0-100 km/hr in 35 s
cart_ac_max = 0.8; // m/s^2
cart_ac = -cart_ac_max * sgn(angular_velo) * sgn(-cosf(theta)); // m/s^2 From control rule
// making the cart stay center point, + acc25% direction to center
const float acc_offset = 0.25;
if (cart_position > 0.05)
{
if (cart_ac > 0)
cart_ac *= (1.0f - acc_offset);
else
cart_ac *= (1.0f + acc_offset);
}
else if (cart_position < -0.05)
{
if (cart_ac < 0)
cart_ac *= (1.0f - acc_offset);
else
cart_ac *= (1.0f + acc_offset);
}
cart_position = cart_position + cart_velo * (float)dt * 0.001; // m
cart_velo = cart_velo + cart_ac * (float)dt * 0.001; // m/s
// Control minimum speed
if (fabs(cart_velo) > 0.001f)
{
driver_frequncy = fabs(cart_velo) * microstep * 5 * 1000.0f;
if (driver_frequncy < 1)
driver_frequncy = 1;
if (limit_locking == 0)
ledcSetup(channel, driver_frequncy, 2);
else
{
if (digitalRead(limit_A) && digitalRead(limit_B))
limit_locking = 0;
}
//// Control Direction
if (cart_velo > 0)
digitalWrite(driver_dir_pin, HIGH);
else
digitalWrite(driver_dir_pin, LOW);
ledcWrite(channel, 1);
}
else
ledcWrite(channel, 0);
//// User button reset
if (digitalRead(user_button_pin) == 1)
{
cart_ac = 0;
cart_velo = 0;
cart_position = 0;
limit_locking = 0;
digitalWrite(driver_EN_PIN, HIGH);
}
else
digitalWrite(driver_EN_PIN, LOW);
/////////////////////////////////////////////////////////////////////////////
Ek = 0.5f * (J_inertial)*angular_velo * angular_velo * 1000; //mJ
Ep = M_mass * gravity * R_cg * (-cosf(theta) - 1) * 1000; //mJ
E_total = Ek + Ep;
Serial.print(10 * cart_ac);
Serial.print(", ");
Serial.print(Ek);
Serial.print(", ");
Serial.print(Ep);
Serial.print(", ");
Serial.print(E_total);
Serial.println();
portEXIT_CRITICAL_ISR(&timerMux);
while (millis() < prevt) //constant sampling frequncy
;
prevt = millis() + dt;
}
สนใจชุดคิทชุดนี้นะสามารถเข้าไปดูรายละเอียดได้ที่ https://www.aiiotshop.com/product/62/control-system-linear-inverted-pendulum-experiment-system