#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <math.h>
#include <robotcontrol.h>
#include "rc_balance_defs.h"
typedef enum drive_mode_t{
NOVICE,
ADVANCED
}drive_mode_t;
typedef enum arm_state_t{
ARMED,
DISARMED
}arm_state_t;
typedef struct setpoint_t{
arm_state_t arm_state; drive_mode_t drive_mode; double theta; double phi; double phi_dot; double gamma; double gamma_dot; }setpoint_t;
typedef struct core_state_t{
double wheelAngleR; double wheelAngleL;
double theta; double phi; double gamma; double vBatt; double d1_u; double d2_u; double d3_u; double mot_drive; } core_state_t;
typedef enum m_input_mode_t{
NONE,
DSM,
STDIN
} m_input_mode_t;
static void __print_usage(void);
static void __balance_controller(void); static void* __setpoint_manager(void* ptr); static void* __battery_checker(void* ptr); static void* __printf_loop(void* ptr); static int __zero_out_controller(void);
static int __disarm_controller(void);
static int __arm_controller(void);
static int __wait_for_starting_condition(void);
static void __on_pause_press(void);
static void __on_mode_release(void);
static core_state_t cstate;
static setpoint_t setpoint;
static rc_filter_t D1 = RC_FILTER_INITIALIZER;
static rc_filter_t D2 = RC_FILTER_INITIALIZER;
static rc_filter_t D3 = RC_FILTER_INITIALIZER;
static rc_mpu_data_t mpu_data;
static m_input_mode_t m_input_mode = DSM;
static void __print_usage(void)
{
printf("\n");
printf("-i {dsm|stdin|none} specify input\n");
printf("-q Don't print diagnostic info\n");
printf("-h print this help message\n");
printf("\n");
}
int main(int argc, char *argv[])
{
int c;
pthread_t setpoint_thread = 0;
pthread_t battery_thread = 0;
pthread_t printf_thread = 0;
bool adc_ok = true;
bool quiet = false;
opterr = 0;
while ((c = getopt(argc, argv, "i:qh")) != -1){
switch (c){
case 'i': if(!strcmp("dsm", optarg)) {
m_input_mode = DSM;
} else if(!strcmp("stdin", optarg)) {
m_input_mode = STDIN;
} else if(!strcmp("none", optarg)){
m_input_mode = NONE;
} else {
__print_usage();
return -1;
}
break;
case 'q':
quiet = true;
break;
case 'h':
__print_usage();
return -1;
break;
default:
__print_usage();
return -1;
break;
}
}
if(rc_kill_existing_process(2.0)<-2) return -1;
if(rc_enable_signal_handler()==-1){
fprintf(stderr,"ERROR: failed to start signal handler\n");
return -1;
}
if(rc_button_init(RC_BTN_PIN_PAUSE, RC_BTN_POLARITY_NORM_HIGH,
RC_BTN_DEBOUNCE_DEFAULT_US)){
fprintf(stderr,"ERROR: failed to initialize pause button\n");
return -1;
}
if(rc_button_init(RC_BTN_PIN_MODE, RC_BTN_POLARITY_NORM_HIGH,
RC_BTN_DEBOUNCE_DEFAULT_US)){
fprintf(stderr,"ERROR: failed to initialize mode button\n");
return -1;
}
rc_button_set_callbacks(RC_BTN_PIN_PAUSE,__on_pause_press,NULL);
rc_button_set_callbacks(RC_BTN_PIN_MODE,NULL,__on_mode_release);
if(rc_encoder_eqep_init()==-1){
fprintf(stderr,"ERROR: failed to initialize eqep encoders\n");
return -1;
}
if(rc_motor_init()==-1){
fprintf(stderr,"ERROR: failed to initialize motors\n");
return -1;
}
rc_motor_standby(1);
if(m_input_mode == DSM){
if(rc_dsm_init()==-1){
fprintf(stderr,"failed to start initialize DSM\n");
return -1;
}
}
if(rc_adc_init()==-1){
fprintf(stderr, "failed to initialize adc\n");
adc_ok = false;
}
rc_make_pid_file();
printf("\nPress and release MODE button to toggle DSM drive mode\n");
printf("Press and release PAUSE button to pause/start the motors\n");
printf("hold pause button down for 2 seconds to exit\n");
if(rc_led_set(RC_LED_GREEN, 0)==-1){
fprintf(stderr, "ERROR in rc_balance, failed to set RC_LED_GREEN\n");
return -1;
}
if(rc_led_set(RC_LED_RED, 1)==-1){
fprintf(stderr, "ERROR in rc_balance, failed to set RC_LED_RED\n");
return -1;
}
rc_mpu_config_t mpu_config = rc_mpu_default_config();
mpu_config.dmp_sample_rate = SAMPLE_RATE_HZ;
mpu_config.orient = ORIENTATION_Y_UP;
if(!rc_mpu_is_gyro_calibrated()){
printf("Gyro not calibrated, automatically starting calibration routine\n");
printf("Let your MiP sit still on a firm surface\n");
rc_mpu_calibrate_gyro_routine(mpu_config);
}
setpoint.arm_state = DISARMED;
setpoint.drive_mode = NOVICE;
double D1_num[] = D1_NUM;
double D1_den[] = D1_DEN;
if(rc_filter_alloc_from_arrays(&D1, DT, D1_num, D1_NUM_LEN, D1_den, D1_DEN_LEN)){
fprintf(stderr,"ERROR in rc_balance, failed to make filter D1\n");
return -1;
}
D1.gain = D1_GAIN;
rc_filter_enable_saturation(&D1, -1.0, 1.0);
rc_filter_enable_soft_start(&D1, SOFT_START_SEC);
double D2_num[] = D2_NUM;
double D2_den[] = D2_DEN;
if(rc_filter_alloc_from_arrays(&D2, DT, D2_num, D2_NUM_LEN, D2_den, D2_DEN_LEN)){
fprintf(stderr,"ERROR in rc_balance, failed to make filter D2\n");
return -1;
}
D2.gain = D2_GAIN;
rc_filter_enable_saturation(&D2, -THETA_REF_MAX, THETA_REF_MAX);
rc_filter_enable_soft_start(&D2, SOFT_START_SEC);
printf("Inner Loop controller D1:\n");
rc_filter_print(D1);
printf("\nOuter Loop controller D2:\n");
rc_filter_print(D2);
if(rc_filter_pid(&D3, D3_KP, D3_KI, D3_KD, 4*DT, DT)){
fprintf(stderr,"ERROR in rc_balance, failed to make steering controller\n");
return -1;
}
rc_filter_enable_saturation(&D3, -STEERING_INPUT_MAX, STEERING_INPUT_MAX);
if (adc_ok) {
if(rc_pthread_create(&battery_thread, __battery_checker, (void*) NULL, SCHED_OTHER, 0)){
fprintf(stderr, "failed to start battery thread\n");
return -1;
}
}
else { cstate.vBatt = V_NOMINAL; }
while(cstate.vBatt<1.0 && rc_get_state()!=EXITING) rc_usleep(10000);
if(isatty(fileno(stdout)) && (quiet == false)){
if(rc_pthread_create(&printf_thread, __printf_loop, (void*) NULL, SCHED_OTHER, 0)){
fprintf(stderr, "failed to start printf thread\n");
return -1;
}
}
if(rc_mpu_initialize_dmp(&mpu_data, mpu_config)){
fprintf(stderr,"ERROR: can't talk to IMU, all hope is lost\n");
rc_led_blink(RC_LED_RED, 5, 5);
return -1;
}
if(rc_pthread_create(&setpoint_thread, __setpoint_manager, (void*) NULL, SCHED_OTHER, 0)){
fprintf(stderr, "failed to start setpoint thread\n");
return -1;
}
rc_mpu_set_dmp_callback(&__balance_controller);
printf("\nHold your MIP upright to begin balancing\n");
rc_set_state(RUNNING);
rc_set_state(RUNNING);
while(rc_get_state()!=EXITING){
rc_usleep(200000);
}
rc_pthread_timed_join(setpoint_thread, NULL, 1.5);
if (battery_thread) rc_pthread_timed_join(battery_thread, NULL, 1.5);
if (printf_thread) rc_pthread_timed_join(printf_thread, NULL, 1.5);
rc_filter_free(&D1);
rc_filter_free(&D2);
rc_filter_free(&D3);
rc_mpu_power_off();
rc_led_set(RC_LED_GREEN, 0);
rc_led_set(RC_LED_RED, 0);
rc_led_cleanup();
rc_encoder_eqep_cleanup();
rc_button_cleanup(); rc_remove_pid_file(); return 0;
}
void* __setpoint_manager(__attribute__ ((unused)) void* ptr)
{
double drive_stick, turn_stick; int i, ch, chan, stdin_timeout = 0; char in_str[11];
__disarm_controller();
rc_usleep(2500000);
rc_set_state(RUNNING);
rc_led_set(RC_LED_RED,0);
rc_led_set(RC_LED_GREEN,1);
while(rc_get_state()!=EXITING){
if(m_input_mode == STDIN) fseek(stdin,0,SEEK_END);
rc_usleep(1000000/SETPOINT_MANAGER_HZ);
if(rc_get_state() != RUNNING || m_input_mode == NONE) continue;
if(setpoint.arm_state == DISARMED){
if(__wait_for_starting_condition()==0){
__zero_out_controller();
__arm_controller();
}
else continue;
}
switch(m_input_mode){
case NONE:
continue;
case DSM:
if(rc_dsm_is_new_data()){
turn_stick = rc_dsm_ch_normalized(DSM_TURN_CH) * DSM_TURN_POL;
drive_stick = rc_dsm_ch_normalized(DSM_DRIVE_CH)* DSM_DRIVE_POL;
rc_saturate_double(&drive_stick,-1,1);
rc_saturate_double(&turn_stick,-1,1);
if(fabs(drive_stick)<DSM_DEAD_ZONE) drive_stick = 0.0;
if(fabs(turn_stick)<DSM_DEAD_ZONE) turn_stick = 0.0;
switch(setpoint.drive_mode){
case NOVICE:
setpoint.phi_dot = DRIVE_RATE_NOVICE * drive_stick;
setpoint.gamma_dot = TURN_RATE_NOVICE * turn_stick;
break;
case ADVANCED:
setpoint.phi_dot = DRIVE_RATE_ADVANCED * drive_stick;
setpoint.gamma_dot = TURN_RATE_ADVANCED * turn_stick;
break;
default: break;
}
}
else if(rc_dsm_is_connection_active()==0){
setpoint.theta = 0;
setpoint.phi_dot = 0;
setpoint.gamma_dot = 0;
continue;
}
break;
case STDIN:
i = 0;
while ((ch = getchar()) != EOF && i < 10){
stdin_timeout = 0;
if(ch == 'n' || ch == '\n'){
if(i > 2){
in_str[i-2] = '\0'; if(chan == DSM_TURN_CH){
turn_stick = strtof(in_str, NULL) * DSM_TURN_POL;
setpoint.gamma_dot = turn_stick;
}
else if(chan == DSM_DRIVE_CH){
drive_stick = strtof(in_str, NULL) * DSM_DRIVE_POL;
setpoint.phi_dot = drive_stick;
}
}
if(ch == 'n') i = 1;
else i = 0;
}
else if(i == 1){
chan = ch - 0x30;
i = 2;
}
else{
in_str[i-2] = ch;
i++;
}
}
if(stdin_timeout >= SETPOINT_MANAGER_HZ){
setpoint.theta = 0;
setpoint.phi_dot = 0;
setpoint.gamma_dot = 0;
}
else{
stdin_timeout++;
}
continue;
break;
default:
fprintf(stderr,"ERROR in setpoint manager, invalid input mode\n");
break;
}
}
__disarm_controller();
return NULL;
}
static void __balance_controller(void)
{
static int inner_saturation_counter = 0;
double dutyL, dutyR;
cstate.theta = mpu_data.dmp_TaitBryan[TB_PITCH_X] + BOARD_MOUNT_ANGLE;
cstate.wheelAngleR = (rc_encoder_eqep_read(ENCODER_CHANNEL_R) * 2.0 * M_PI) \
/(ENCODER_POLARITY_R * GEARBOX * ENCODER_RES);
cstate.wheelAngleL = (rc_encoder_eqep_read(ENCODER_CHANNEL_L) * 2.0 * M_PI) \
/(ENCODER_POLARITY_L * GEARBOX * ENCODER_RES);
cstate.phi = ((cstate.wheelAngleL+cstate.wheelAngleR)/2) + cstate.theta;
cstate.gamma = (cstate.wheelAngleR-cstate.wheelAngleL) \
* (WHEEL_RADIUS_M/TRACK_WIDTH_M);
if(rc_get_state()==EXITING){
rc_motor_set(0,0.0);
return;
}
if(rc_get_state()!=RUNNING && setpoint.arm_state==ARMED){
__disarm_controller();
return;
}
if(setpoint.arm_state==DISARMED){
return;
}
if(fabs(cstate.theta) > TIP_ANGLE){
__disarm_controller();
printf("tip detected \n");
return;
}
if(ENABLE_POSITION_HOLD){
if(fabs(setpoint.phi_dot) > 0.001) setpoint.phi += setpoint.phi_dot*DT;
cstate.d2_u = rc_filter_march(&D2,setpoint.phi-cstate.phi);
setpoint.theta = cstate.d2_u;
}
else setpoint.theta = 0.0;
D1.gain = D1_GAIN * V_NOMINAL/cstate.vBatt;
cstate.d1_u = rc_filter_march(&D1,(setpoint.theta-cstate.theta));
if(fabs(cstate.d1_u)>0.95) inner_saturation_counter++;
else inner_saturation_counter = 0;
if(inner_saturation_counter > (SAMPLE_RATE_HZ*D1_SATURATION_TIMEOUT)){
printf("inner loop controller saturated\n");
__disarm_controller();
inner_saturation_counter = 0;
return;
}
if(fabs(setpoint.gamma_dot)>0.0001) setpoint.gamma += setpoint.gamma_dot * DT;
cstate.d3_u = rc_filter_march(&D3,setpoint.gamma - cstate.gamma);
dutyL = cstate.d1_u - cstate.d3_u;
dutyR = cstate.d1_u + cstate.d3_u;
rc_motor_set(MOTOR_CHANNEL_L, MOTOR_POLARITY_L * dutyL);
rc_motor_set(MOTOR_CHANNEL_R, MOTOR_POLARITY_R * dutyR);
return;
}
static int __zero_out_controller(void)
{
rc_filter_reset(&D1);
rc_filter_reset(&D2);
rc_filter_reset(&D3);
setpoint.theta = 0.0;
setpoint.phi = 0.0;
setpoint.gamma = 0.0;
rc_motor_set(0,0.0);
return 0;
}
static int __disarm_controller(void)
{
rc_motor_standby(1);
rc_motor_free_spin(0);
setpoint.arm_state = DISARMED;
return 0;
}
static int __arm_controller(void)
{
__zero_out_controller();
rc_encoder_eqep_write(ENCODER_CHANNEL_L,0);
rc_encoder_eqep_write(ENCODER_CHANNEL_R,0);
rc_motor_standby(0);
setpoint.arm_state = ARMED;
return 0;
}
static int __wait_for_starting_condition(void)
{
int checks = 0;
const int check_hz = 20; int checks_needed = round(START_DELAY*check_hz);
int wait_us = 1000000/check_hz;
while(rc_get_state()==RUNNING){
if(fabs(cstate.theta) > START_ANGLE) checks++;
else checks = 0;
if(checks >= checks_needed) break;
rc_usleep(wait_us);
}
checks = 0;
while(rc_get_state()==RUNNING){
if(fabs(cstate.theta) < START_ANGLE) checks++;
else checks = 0;
if(checks >= checks_needed) return 0;
rc_usleep(wait_us);
}
return -1;
}
static void* __battery_checker(__attribute__ ((unused)) void* ptr)
{
double new_v;
while(rc_get_state()!=EXITING){
new_v = rc_adc_batt();
if (new_v>9.0 || new_v<5.0) new_v = V_NOMINAL;
cstate.vBatt = new_v;
rc_usleep(1000000 / BATTERY_CHECK_HZ);
}
return NULL;
}
static void* __printf_loop(__attribute__ ((unused)) void* ptr)
{
rc_state_t last_rc_state, new_rc_state; last_rc_state = rc_get_state();
while(rc_get_state()!=EXITING){
new_rc_state = rc_get_state();
if(new_rc_state==RUNNING && last_rc_state!=RUNNING){
printf("\nRUNNING: Hold upright to balance.\n");
printf(" θ |");
printf(" θ_ref |");
printf(" φ |");
printf(" φ_ref |");
printf(" γ |");
printf(" D1_u |");
printf(" D3_u |");
printf(" vBatt |");
printf("arm_state|");
printf("\n");
}
else if(new_rc_state==PAUSED && last_rc_state!=PAUSED){
printf("\nPAUSED: press pause again to start.\n");
}
last_rc_state = new_rc_state;
if(new_rc_state == RUNNING){
printf("\r");
printf("%7.3f |", cstate.theta);
printf("%7.3f |", setpoint.theta);
printf("%7.3f |", cstate.phi);
printf("%7.3f |", setpoint.phi);
printf("%7.3f |", cstate.gamma);
printf("%7.3f |", cstate.d1_u);
printf("%7.3f |", cstate.d3_u);
printf("%7.3f |", cstate.vBatt);
if(setpoint.arm_state == ARMED) printf(" ARMED |");
else printf("DISARMED |");
fflush(stdout);
}
rc_usleep(1000000 / PRINTF_HZ);
}
return NULL;
}
static void __on_pause_press(void)
{
int i=0;
const int samples = 100; const int us_wait = 2000000;
switch(rc_get_state()){
case EXITING:
return;
case RUNNING:
rc_set_state(PAUSED);
__disarm_controller();
rc_led_set(RC_LED_RED,1);
rc_led_set(RC_LED_GREEN,0);
break;
case PAUSED:
rc_set_state(RUNNING);
__disarm_controller();
rc_led_set(RC_LED_GREEN,1);
rc_led_set(RC_LED_RED,0);
break;
default:
break;
}
while(i<samples){
rc_usleep(us_wait/samples);
if(rc_button_get_state(RC_BTN_PIN_PAUSE)==RC_BTN_STATE_RELEASED){
return; }
i++;
}
printf("long press detected, shutting down\n");
rc_led_blink(RC_LED_RED,5,2);
rc_set_state(EXITING);
return;
}
static void __on_mode_release(void)
{
if(setpoint.drive_mode == NOVICE){
setpoint.drive_mode = ADVANCED;
printf("using drive_mode = ADVANCED\n");
}
else {
setpoint.drive_mode = NOVICE;
printf("using drive_mode = NOVICE\n");
}
rc_led_blink(RC_LED_GREEN,5,1);
return;
}