반응형

앞서 정의한 요구사항으로부터 무엇을 했고 해야하는지를 정리했다.

그렇다면 어떤 UI를 통해서 사용할지를 정의할 필요가 있다.

 

디스플레이 속성

표현할 수 있는 크기 21 개 X 8 줄 이며

최상단 두 줄은 노란색, 나머지는 파란색이다.

최상단 두 줄은 가장 중요한 정보, 항상 출력할 정보를 띄우는 것으로 하자.

나머지 여섯 줄은 필요한 정보를 출력한다.

 

중요 정보는 배터리 전압과 시간, 적경 회전 속도인 것 같다.

 

사용하는 주요 기능들을 정리하면 다음과 같다.

  • 적경 회전 속도 조절
  • GPS 상태
  • IMU 상태 (가속도, 각속도, 지자계)
  • Attitude Estimator
  • Rotary Encoder (Rotation, Click)
  • 스케쥴러 상태
  • 달 관측용 회전 축 알려주기

 

메뉴를 선택하는 방법은 좌측에 꺽쇄 '>'를 0.5초 주기로 깜빡이면서 메뉴 상태를 출력한다.

여기서 클릭하면 내려간다.

 

UI 그려보기

대충 모사한 모습은 다음과 같다.

실질적으로 조작기능이 있는 메뉴는 1RA Speed와 Do IMU Calib 뿐이다.

 

 

코드 구현

 

더보기
/************************************/
// Include Headers
/************************************/
#include "oled.h"
#include "gps.h"
#include "tictok_us.h"
#include "mpu6500.h" // 6.0.3
#include "esp_12f.h"
#define min(x, y) (x) < (y) ? (x) : (y)// -> x, y중에 작은값 반환
#define max(x, y) (x) > (y) ? (x) : (y)// -> x, y중에 큰값 반환

void setRotaryRight();
void setRotaryLeft();
void setRotaryNeutral();
void updateRotary();
void updateMenuCursor();


/************************************/
// Define Devices
/************************************/
#define GPS_PORT Serial
// #define DEBUG_PORT Serial1

/************************************/
// Display Settings
/************************************/
OLED* monitor           = new OLED();
GPS* gps                = new GPS(&GPS_PORT);
bfs::Mpu6500* imu       = new bfs::Mpu6500();

Tictok_us* perform_main = new Tictok_us();
Tictok_us* perform_rest = new Tictok_us();
Tictok_us* perform_imu  = new Tictok_us();
// Simple Scheduler
float hz = 10.0f;
unsigned long last_loop_time  = micros();
unsigned long now_loop_time   = micros();
unsigned long size_loop_time  = 1000 * (1000 / hz);
unsigned long delta_loop_time = 0;

/************************************/
// Rotary Button 
/************************************/
// Rotary Encoder Inputs
#define BUTTON_KEY  GPIO_D4
#define BUTTON_S1   GPIO_D6
#define BUTTON_S2   GPIO_D7
#define PWM1        GPIO_D5
#define PWM2        GPIO_D8
#define PWM3        GPIO_D3

volatile int rotary_pulse_s1 = 0;
volatile int rotary_pulse_s2 = 0;
volatile unsigned long rotary_last_pulse_s1 = 0;
volatile unsigned long rotary_last_pulse_s2 = 0;
volatile int rotary_state = 0;
volatile int rotary_state_prev = 0;
volatile int rotary_state_s1 = 0;
volatile int rotary_state_s2 = 0;
volatile int rotary_counter = 0;
volatile int rotary_counter_prev = 0;
volatile int rotary_counter_interval = 0;
volatile int rotary_right = false;
volatile int rotary_direction = 0;
volatile int rotary_left = false;
volatile int rotary_event = true;

volatile int button_counter = 0;
volatile int button_clicked = false;
volatile int did_button_clicked = false;
volatile unsigned long button_last_press = 0;

int rotation_speed_cnt_prev = 0;
float rotation_speed = 0.0f;
float rotation_speed_prev = 0.0f;

char main_menu_string[10][21]={
  "%c1RA Speed",
  "%c2Status - GPS",
  "%c3Status - Acc/Gyro",
  "%c4Status - Mag/Alt",
  "%c5Status - IMUCalib",
  "%c6Do IMU Calib.",
  "%c7Att.Estimator",
  "%c8Health",
  "%c9Axis for Moon",
  "%cSleep"
};

char cursor[6];
char cursor_icon = '>';
int cursor_pos = 0;
int menu_index = 0;
int menu_index_max = 0;
int menu_index_first = 0;

typedef enum Menu{
  main_menu = 0,
  
  ra_speed = 1,
  status_gps = 2,
  status_acc_gyro = 3,
  status_mag_alt = 4,
  status_imu_calib = 5,
  do_imu_calib = 6,
  att_est = 7,
  health = 8,
  moon_axis = 9,
  sleep = 10,

  ra_speed_select = 100,
}Menu;

void setup() {
  int system_on = true;
  /************************************/
  // Display Initialization
  /************************************/
  int monitor_on  = monitor->setup_display();
  system_on = system_on & monitor_on;
  sprintf(monitor->msg[2], "1. Display is ON");
  monitor->print();

  /************************************/
  // IMU Setup
  /************************************/
  sprintf(monitor->msg[3], "2. Intialize IMU");
  monitor->print();
  /* I2C bus,  0x68 address */
  imu->Config(&Wire, bfs::Mpu6500::I2C_ADDR_PRIM);
  int imu_on = imu->Begin()
              // Set the accel range to 16G by default
            && imu->ConfigAccelRange(bfs::Mpu6500::ACCEL_RANGE_4G)
              // Set the gyro range to 2000DPS by default
            && imu->ConfigGyroRange(bfs::Mpu6500::GYRO_RANGE_250DPS)
              // Set the DLPF to 184HZ by default
            && imu->ConfigDlpfBandwidth(bfs::Mpu6500::DLPF_BANDWIDTH_92HZ)
              // Set the sample rate divider
            && imu->ConfigSrd(19);                              
  system_on = system_on & imu_on;
  if(imu_on == true)    sprintf(monitor->msg[3], "2. IMU is ON");
  else                  sprintf(monitor->msg[3], ">> IMU failed");

  /************************************/
  // GPS Setup
  /************************************/
  sprintf(monitor->msg[4], "3. Intialize GPS");
  monitor->print();
  int gps_on      = gps->setup_gps();
  system_on = system_on & gps_on;
  if(gps_on == true)    sprintf(monitor->msg[4], "3. GPS is ON");
  else                  sprintf(monitor->msg[4], ">> GPS is timeout");
  monitor->print();
  /************************************/
  // Rotary Button Setup
  /************************************/
	// Set encoder pins as inputs
  sprintf(monitor->msg[5], "4. Intialize Buttons");
  monitor->print();
	pinMode(BUTTON_S1,  INPUT);
	pinMode(BUTTON_S2,  INPUT);
	pinMode(BUTTON_KEY, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_S1),  isr_rotary_s1,     FALLING);
  attachInterrupt(digitalPinToInterrupt(BUTTON_S2),  isr_rotary_s2,     FALLING);
  attachInterrupt(digitalPinToInterrupt(BUTTON_KEY), isr_button_click,  RISING);
  sprintf(monitor->msg[5], "4. Buttons are ON");
  monitor->print();
  /************************************/
  // PWM
  /************************************/
  pinMode(PWM2, OUTPUT);
  analogWrite(PWM2, 120);
  sprintf(monitor->msg[6], "5. PWM is ON");
  monitor->print();
}

void loop() {	
  perform_main->tic();
  gps->read_all();
  perform_imu->tic();
  int imu_health = imu->Read();
  perform_imu->toc();


  float ax = imu->accel_x_mps2();
  float ay = imu->accel_y_mps2();
  float az = imu->accel_z_mps2();
  float grav = sqrt(ax*ax + ay*ay + az*az);
  float phi_acc   = atan2(ay, -az) * 57.296f;
  float theta_acc = asin(ax/grav)  * 57.296f;
  double latitude  = ((double)gps->gps_device->latitude_fixed)*1E-7;
  double longitude = ((double)gps->gps_device->longitude_fixed)*1E-7;

  /************************************/
  // Display
  /************************************/
  // Top Window
  sprintf(monitor->msg[0], "T %2d:%2d:%2d.%1d V %5.2f",
          gps->gps_device->hour, gps->gps_device->minute,
          gps->gps_device->seconds, gps->gps_device->milliseconds/100, 12.60f);
  // sprintf(monitor->msg[1], "Button %2d Rotary %2d",
  //         button_counter, rotary_counter);

  // Sub Window
  switch(menu_index)
  {
    case Menu::main_menu:
    {
      menu_index_max = 10;
      updateMenuCursor();
      menu_index_first = max(0, rotary_counter_interval - cursor_pos - 1);
      
      sprintf(monitor->msg[1], "Main Menu");
      sprintf(monitor->msg[2], main_menu_string[0 + menu_index_first], cursor[0]);
      sprintf(monitor->msg[3], main_menu_string[1 + menu_index_first], cursor[1]);
      sprintf(monitor->msg[4], main_menu_string[2 + menu_index_first], cursor[2]);
      sprintf(monitor->msg[5], main_menu_string[3 + menu_index_first], cursor[3]);
      sprintf(monitor->msg[6], main_menu_string[4 + menu_index_first], cursor[4]);
      sprintf(monitor->msg[7], main_menu_string[5 + menu_index_first], cursor[5]);
      
      if(did_button_clicked == true)
      {
        menu_index = menu_index_first + cursor_pos + 1;
        cursor_pos = 0;
      }
    }
    break;
    case Menu::ra_speed:
    {
      menu_index_max = 2;
      updateMenuCursor();
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], "%cRRcmd %9.4fDEG", cursor[0], rotation_speed);
      sprintf(monitor->msg[3], "%cRRate %9.4fDEG", cursor[1], imu->gyro_x_radps());
      sprintf(monitor->msg[4], "%cQuit"           , cursor[2]);
      sprintf(monitor->msg[5], " ROL %9.4f  DEG",  phi_acc);
      sprintf(monitor->msg[6], " LAT %c%10.6fDEG", gps->gps_device->lat, latitude);
      sprintf(monitor->msg[7], " PIT %9.4f  DEG",  theta_acc);
      if(did_button_clicked == true)
      {
        if(cursor_pos == 0)
        {
          menu_index = Menu::ra_speed_select;
          rotary_counter_prev = rotary_counter;
        }
        else if(cursor_pos == menu_index_max)
        {
          rotary_counter_prev = rotary_counter - menu_index + 1;
          menu_index = Menu::main_menu;
        }
      }
    }
    break;
    case Menu::status_gps:
    {
      menu_index_max = 0;
      char fixqual[5];
      if(gps->gps_device->fixquality == 0)
        sprintf(fixqual, "NoFix");
      else if (gps->gps_device->fixquality == 1)
        sprintf(fixqual, "GPS");
      else if (gps->gps_device->fixquality == 2)
        sprintf(fixqual, "DGPS");

      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], "Date: 20%02d/%02d/%02d",      gps->gps_device->year, gps->gps_device->month, gps->gps_device->day);
      sprintf(monitor->msg[3], "Time:   %02d:%02d:%02d.%03d", gps->gps_device->hour, gps->gps_device->minute, gps->gps_device->seconds, gps->gps_device->milliseconds);
      sprintf(monitor->msg[4], "%5s Sat %2d",                 fixqual, (int)gps->gps_device->satellites);
      sprintf(monitor->msg[5], " ");
      sprintf(monitor->msg[6], " %c %12.8f DEG",              gps->gps_device->lat, latitude);
      sprintf(monitor->msg[7], " %c %12.8f DEG",              gps->gps_device->lon, longitude);
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index + 1;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::status_acc_gyro:
    {
      menu_index_max = 0;
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2],"Ax    %9.4f mpss", imu->accel_x_mps2());
      sprintf(monitor->msg[3],"Ay    %9.4f mpss", imu->accel_y_mps2());
      sprintf(monitor->msg[4],"Az    %9.4f mpss", imu->accel_z_mps2());
      sprintf(monitor->msg[5],"Wx    %9.4f dps",  imu->gyro_x_radps());
      sprintf(monitor->msg[6],"Wy    %9.4f dps",  imu->gyro_y_radps());
      sprintf(monitor->msg[7],"Wz    %9.4f dps",  imu->gyro_z_radps());
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index + 1;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::status_mag_alt:
    {
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], "Mx   %9.4f uT");
      sprintf(monitor->msg[3], "My   %9.4f uT");
      sprintf(monitor->msg[4], "Mz   %9.4f uT");
      sprintf(monitor->msg[5], "Head %9.4f DEG");
      sprintf(monitor->msg[6], " ");
      sprintf(monitor->msg[7], " ");
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index + 1;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::status_imu_calib:
    {
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], "bAx  %9.4f mpss", 0.0f);
      sprintf(monitor->msg[3], "bAy  %9.4f mpss", 0.0f);
      sprintf(monitor->msg[4], "bAz  %9.4f mpss", 0.0f);
      sprintf(monitor->msg[5], "bWx  %9.4f dps",  0.0f);
      sprintf(monitor->msg[6], "bWy  %9.4f dps",  0.0f);
      sprintf(monitor->msg[7], "bWz  %9.4f dps",  0.0f);
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index + 1;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::do_imu_calib:
    {
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], " ");
      sprintf(monitor->msg[3], " ");
      sprintf(monitor->msg[4], " ");
      sprintf(monitor->msg[5], " ");
      sprintf(monitor->msg[6], " ");
      sprintf(monitor->msg[7], " ");
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::att_est:
    {
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], " ");
      sprintf(monitor->msg[3], " ");
      sprintf(monitor->msg[4], " ");
      sprintf(monitor->msg[5], " ");
      sprintf(monitor->msg[6], " ");
      sprintf(monitor->msg[7], " ");
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::health:
    {
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], "Loop %6d/%5.1fHz", perform_rest->getdt() + perform_main->getdt(), hz);
      sprintf(monitor->msg[3], "MAIN %6d", perform_main->getdt());
      sprintf(monitor->msg[4], "DISP %6d", monitor->perform_print.getdt());
      sprintf(monitor->msg[5], "GPS  %6d", gps->perform_read.getdt());
      sprintf(monitor->msg[6], "IMU  %6d", perform_imu->getdt());
      sprintf(monitor->msg[7], "EST  %6d", 0);
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::moon_axis:
    {
      updateMenuCursor();
      sprintf(monitor->msg[1], main_menu_string[menu_index-1], '*');
      sprintf(monitor->msg[2], "ROLL  %9.4f DEG", phi_acc);
      sprintf(monitor->msg[3], "PITCH %9.4f DEG", theta_acc);
      sprintf(monitor->msg[4], "YAW   %9.4f DEG", 120.0012f);
      sprintf(monitor->msg[5], "-- Moon --------");
      sprintf(monitor->msg[6], "R.A.  %9.4f DEG",  37.0013f);
      sprintf(monitor->msg[7], "DEC.  %9.4f DEG",-120.0014f);
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter - menu_index;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::sleep:
    {
      sprintf(monitor->msg[1], " ");
      sprintf(monitor->msg[2], " ");
      sprintf(monitor->msg[3], " ");
      sprintf(monitor->msg[4], " ");
      sprintf(monitor->msg[5], " ");
      sprintf(monitor->msg[6], " ");
      sprintf(monitor->msg[7], " ");
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter + 1;
        menu_index = Menu::main_menu;
      }
    }
    break;
    case Menu::ra_speed_select:
    {
      rotation_speed = rotation_speed_prev + 0.1f * (rotary_counter - rotation_speed_cnt_prev);
      analogWrite(PWM2, (int)(rotation_speed * 10.0f));
      sprintf(monitor->msg[1], main_menu_string[1], '*');
      sprintf(monitor->msg[2], "%cRRcmd %9.4fDEG", cursor_icon, rotation_speed);
      sprintf(monitor->msg[3], " RRate %9.4fDEG",  imu->gyro_x_radps());
      sprintf(monitor->msg[4], " Exit");
      sprintf(monitor->msg[5], " ROL %9.4f  DEG",  phi_acc);
      sprintf(monitor->msg[6], " LAT %c%10.6fDEG", gps->gps_device->lat, latitude);
      sprintf(monitor->msg[7], " PIT %9.4f  DEG",  theta_acc);
      if(did_button_clicked == true)
      {
        menu_index = Menu::ra_speed;
      }
      rotary_counter_prev = rotary_counter + 1;
      rotation_speed_cnt_prev = rotary_counter;
      rotation_speed_prev = rotation_speed;
    }
    break;
    default:
    {
      sprintf(monitor->msg[1], "ERROR");
      sprintf(monitor->msg[2], " ");
      sprintf(monitor->msg[3], " ");
      sprintf(monitor->msg[4], " ");
      sprintf(monitor->msg[5], " ");
      sprintf(monitor->msg[6], " ");
      sprintf(monitor->msg[7], " ");
      if(did_button_clicked == true)
      {
        rotary_counter_prev = rotary_counter + 1;
        menu_index = Menu::main_menu;
      }
    }
  }
  did_button_clicked = false;
  if(cursor_icon == '>')
    cursor_icon = '-';
  else
    cursor_icon = '>';
  monitor->print();
  
  perform_main->toc();
  /************************************/
  // Simple Scheduler
  /************************************/
  perform_rest->tic();
  while(1){
    now_loop_time = micros();
    // Clock Overflow Case, each time after about 70 mins
    if(now_loop_time < last_loop_time){
      last_loop_time = 0;
      break;
    }
    // Normal Case
    else{
      delta_loop_time = now_loop_time - last_loop_time;
      if(delta_loop_time > size_loop_time){
        last_loop_time = last_loop_time + size_loop_time;
        break;
      }
    }
  }
  perform_rest->toc();
}
//3201 3201
// 0 -> 4 (2(0+1)+2) -> 10 (2*(4+1)+0) -> 23 (2*(10+1)+1)
void setRotaryRight(){
  rotary_counter++;
  rotary_direction = 1; // left
  rotary_event = true;
}
// 3102 3102
// 0 -> 3 (2*(0+1)+1) -> 8 (2*(3+1)+0) -> 20 (2*(8+1)+2)
void setRotaryLeft(){
  rotary_counter--;
  rotary_direction = -1; // right
  rotary_event = true;
}
void setRotaryNeutral(){
  rotary_event = false;
  rotary_direction = 0; // neutral
}
void updateRotary(){
  rotary_state_s1 = digitalRead(BUTTON_S1);
  rotary_state_s2 = digitalRead(BUTTON_S2);
  rotary_state = 2 * rotary_state_s1 + rotary_state_s2;
  if(rotary_state_prev == 3 && rotary_state == 2){
    setRotaryRight();
  }
  if(rotary_state_prev == 0 && rotary_state == 1){
    setRotaryRight();
  }

  if(rotary_state_prev == 1 && rotary_state == 0){
    setRotaryLeft();
  }
  if(rotary_state_prev == 2 && rotary_state == 3){
    setRotaryLeft();
  }
  rotary_state_prev = rotary_state;
}

/************************************/
// Interrupt Service Routines
/************************************/
// CW  (+, R) : S1 true,   S2 Rising
// CCW (-, L) : S1 Rising, S2 true
IRAM_ATTR void isr_rotary_s1(){
  // prevent being clicked over twice at once
  if(rotary_last_pulse_s1 - millis() < 30)
    rotary_pulse_s1 = false;
  else{
    rotary_pulse_s1 = true;
  }
  rotary_last_pulse_s1 = millis();

  // processing rotary action
  if(rotary_pulse_s1 == true){
    rotary_pulse_s1 = false;
    updateRotary();
  }
}

IRAM_ATTR void isr_rotary_s2(){
  // prevent being clicked over twice at once
  if(rotary_last_pulse_s2 - millis() < 30)
    rotary_pulse_s2 = false;
  else{
    rotary_pulse_s2 = true;
  }
  rotary_last_pulse_s2 = millis();

  // processing rotary action
  if(rotary_pulse_s2 == true){
    rotary_pulse_s2 = false;
    updateRotary();
  }
}

// Button Interrupt Routine
IRAM_ATTR void isr_button_click(){
  // prevent being clicked over twice at once
  if(button_last_press - millis() < 500)
    button_clicked = false;
  else
    button_clicked = true;
  button_last_press = millis();

  // processing button action
  // throw main loop the fact being clicked
  if(button_clicked == true){
    button_clicked = false;
    did_button_clicked = true;
    button_counter++;
  }
}
void updateMenuCursor()
{
  rotary_counter_interval = rotary_counter - rotary_counter_prev;
  if(rotary_counter_interval < 0)
    rotary_counter_prev = rotary_counter;
  else if(rotary_counter_interval > menu_index_max - 1)
    rotary_counter_prev = rotary_counter - menu_index_max;
  rotary_counter_interval = rotary_counter - rotary_counter_prev;
  cursor_pos = min(max(0, rotary_counter_interval), 5);

  for (int i = 0; i < 6; i++)
  {
    if (i == cursor_pos)
      cursor[i] = cursor_icon;
    else
      cursor[i] = ' ';
  }
}

 

 

** EOF **

728x90

+ Recent posts