|
|
@ -1,3 +1,144 @@ |
|
|
|
#include <Arduino.h>
|
|
|
|
#include <SPI.h>
|
|
|
|
#include <LoRa.h>
|
|
|
|
#include <U8g2lib.h>
|
|
|
|
#include <Wire.h>
|
|
|
|
|
|
|
|
|
|
|
|
typedef enum { |
|
|
|
SCREEN_INTRO = 0, |
|
|
|
SCREEN_SELECTIONS, |
|
|
|
SCREEN_CONTROL |
|
|
|
} OPENFLIGHT_SCREEN; |
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Icon configuration |
|
|
|
Width and height must match the icon font size |
|
|
|
GAP: Space between the icons |
|
|
|
BGAP: Gap between the display border and the cursor. |
|
|
|
*/ |
|
|
|
#define ICON_WIDTH 32
|
|
|
|
#define ICON_HEIGHT 32
|
|
|
|
#define ICON_GAP 4
|
|
|
|
#define ICON_BGAP 16
|
|
|
|
#define ICON_Y 32+ ICON_GAP
|
|
|
|
|
|
|
|
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); |
|
|
|
|
|
|
|
struct menu_entry_type { |
|
|
|
const uint8_t *font; |
|
|
|
uint16_t icon; |
|
|
|
const char *name; |
|
|
|
}; |
|
|
|
|
|
|
|
struct menu_state { |
|
|
|
int16_t menu_start; /* in pixel */ |
|
|
|
int16_t frame_position; /* in pixel */ |
|
|
|
uint8_t position; /* position, array index */ |
|
|
|
}; |
|
|
|
|
|
|
|
struct menu_entry_type menu_entry_alpha_list[] = { |
|
|
|
{ u8g2_font_open_iconic_all_4x_t, 129, "Configuration"}, |
|
|
|
{ u8g2_font_open_iconic_all_4x_t, 104, "Debug"}, |
|
|
|
{ u8g2_font_open_iconic_all_4x_t, 90, "Battery"}, |
|
|
|
{ u8g2_font_open_iconic_all_4x_t, 65, "Exit"}, |
|
|
|
{ NULL, 0, NULL } |
|
|
|
}; |
|
|
|
|
|
|
|
struct menu_state current_alpha_state = { ICON_BGAP, ICON_BGAP, 0 }; |
|
|
|
struct menu_state destination_alpha_state = { ICON_BGAP, ICON_BGAP, 0 }; |
|
|
|
|
|
|
|
void draw_menu(struct menu_state *state) { |
|
|
|
int16_t x; |
|
|
|
uint8_t i; |
|
|
|
x = state->menu_start; |
|
|
|
i = 0; |
|
|
|
while( menu_entry_alpha_list[i].icon > 0 ) { |
|
|
|
if ( x >= -ICON_WIDTH && x < u8g2.getDisplayWidth()) { |
|
|
|
u8g2.setFont(menu_entry_alpha_list[i].font); |
|
|
|
u8g2.drawGlyph(x, ICON_Y, menu_entry_alpha_list[i].icon ); |
|
|
|
} |
|
|
|
i++; |
|
|
|
x += ICON_WIDTH + ICON_GAP; |
|
|
|
} |
|
|
|
//u8g2.drawFrame(state->frame_position-1, ICON_Y-ICON_HEIGHT-1, ICON_WIDTH+2, ICON_WIDTH+2);
|
|
|
|
//u8g2.drawFrame(state->frame_position-2, ICON_Y-ICON_HEIGHT-2, ICON_WIDTH+4, ICON_WIDTH+4);
|
|
|
|
u8g2.drawFrame(state->frame_position-3, ICON_Y-ICON_HEIGHT-3, ICON_WIDTH+6, ICON_WIDTH+6); |
|
|
|
} |
|
|
|
|
|
|
|
void to_right(struct menu_state *state) { |
|
|
|
if(menu_entry_alpha_list[state->position+1].font != NULL) { |
|
|
|
if((int16_t)state->frame_position+ 2*(int16_t)ICON_WIDTH + (int16_t)ICON_BGAP < (int16_t)u8g2.getDisplayWidth()) { |
|
|
|
state->position++; |
|
|
|
state->frame_position += ICON_WIDTH + (int16_t)ICON_GAP; |
|
|
|
} else { |
|
|
|
state->position++; |
|
|
|
state->frame_position = (int16_t)u8g2.getDisplayWidth() - (int16_t)ICON_WIDTH - (int16_t)ICON_BGAP; |
|
|
|
state->menu_start = state->frame_position - state->position*((int16_t)ICON_WIDTH + (int16_t)ICON_GAP); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void to_left(struct menu_state *state){ |
|
|
|
if(state->position > 0){ |
|
|
|
if ( (int16_t)state->frame_position >= (int16_t)ICON_BGAP+(int16_t)ICON_WIDTH+ (int16_t)ICON_GAP ){ |
|
|
|
state->position--; |
|
|
|
state->frame_position -= ICON_WIDTH + (int16_t)ICON_GAP; |
|
|
|
} else { |
|
|
|
state->position--; |
|
|
|
state->frame_position = ICON_BGAP; |
|
|
|
state->menu_start = state->frame_position - state->position*((int16_t)ICON_WIDTH + (int16_t)ICON_GAP); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t towards_int16(int16_t *current, int16_t dest) { |
|
|
|
if ( *current < dest ){ |
|
|
|
(*current)++; |
|
|
|
return 1; |
|
|
|
} else if ( *current > dest ) { |
|
|
|
(*current)--; |
|
|
|
return 1; |
|
|
|
} |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
uint8_t towards(struct menu_state *current, struct menu_state *destination) { |
|
|
|
uint8_t r = 0; |
|
|
|
uint8_t i; |
|
|
|
for( i = 0; i < 6; i++ ) { |
|
|
|
r |= towards_int16( &(current->frame_position), destination->frame_position); |
|
|
|
r |= towards_int16( &(current->menu_start), destination->menu_start); |
|
|
|
} |
|
|
|
return r; |
|
|
|
} |
|
|
|
|
|
|
|
// For Menu Navigation and Setup
|
|
|
|
#define MENU_STICK_ROLL_MAX 3000
|
|
|
|
#define MENU_STICK_ROLL_MIN 800
|
|
|
|
#define MENU_STICK_PITCH_MAX 3000
|
|
|
|
#define MENU_STICK_PITCH_MIN 800
|
|
|
|
|
|
|
|
typedef enum { |
|
|
|
MENU_NONE = 0, |
|
|
|
MENU_LEFT, |
|
|
|
MENU_RIGHT, |
|
|
|
MENU_BACK, |
|
|
|
MENU_OK |
|
|
|
} MENU_NAVIGATION; |
|
|
|
|
|
|
|
MENU_NAVIGATION stick_navigation_position; |
|
|
|
bool menu_entry_alpha_selected = false; |
|
|
|
|
|
|
|
//define the pins used by the transceiver module
|
|
|
|
#define SS 5
|
|
|
|
#define RST 14
|
|
|
|
#define DIO0 2
|
|
|
|
|
|
|
|
int counter = 0; |
|
|
|
|
|
|
|
#define BUZZER_PIN 15
|
|
|
|
#define THROTTLE_PIN 36
|
|
|
|
#define YAW_PIN 39
|
|
|
@ -23,6 +164,9 @@ uint16_t roll_value = 0; |
|
|
|
#define PITCH 2
|
|
|
|
#define ROLL 3
|
|
|
|
|
|
|
|
uint64_t last_update = 0; |
|
|
|
uint16_t last_menu_stick_read = 0; |
|
|
|
|
|
|
|
uint16_t throttle_pool[MEDIAN_TOTAL]; |
|
|
|
uint16_t yaw_pool[MEDIAN_TOTAL]; |
|
|
|
uint16_t pitch_pool[MEDIAN_TOTAL]; |
|
|
@ -171,6 +315,41 @@ uint16_t roll_insert(uint16_t val) { |
|
|
|
return median_get(ROLL); |
|
|
|
} |
|
|
|
|
|
|
|
/*
|
|
|
|
* pool_insert: |
|
|
|
* To insert the data into the median pool depending on type of the variable |
|
|
|
* |
|
|
|
* E.g.: |
|
|
|
* throttle_value = pool_insert(THROTTLE); |
|
|
|
*/ |
|
|
|
uint16_t pool_insert(uint8_t pool_type, uint16_t val) { |
|
|
|
// Insert to the array...at the last of the array
|
|
|
|
for(int i = 1; i < MEDIAN_TOTAL; i++) { |
|
|
|
// Shift to left
|
|
|
|
if(pool_type == THROTTLE) { |
|
|
|
throttle_pool[i - 1] = throttle_pool[i]; |
|
|
|
} else if(pool_type == YAW) { |
|
|
|
yaw_pool[i - 1] = yaw_pool[i]; |
|
|
|
} else if(pool_type == PITCH) { |
|
|
|
pitch_pool[i - 1] = pitch_pool[i]; |
|
|
|
} else if(pool_type == ROLL) { |
|
|
|
roll_pool[i - 1] = roll_pool[i]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Find median....
|
|
|
|
if(pool_type == THROTTLE) { |
|
|
|
throttle_pool[MEDIAN_TOTAL - 1] = val; |
|
|
|
} else if(pool_type == YAW) { |
|
|
|
yaw_pool[MEDIAN_TOTAL - 1] = val; |
|
|
|
} else if(pool_type == ROLL) { |
|
|
|
roll_pool[MEDIAN_TOTAL - 1] = val; |
|
|
|
} else if(pool_type == PITCH) { |
|
|
|
pitch_pool[MEDIAN_TOTAL - 1] = val; |
|
|
|
} |
|
|
|
return median_get(pool_type); |
|
|
|
} |
|
|
|
|
|
|
|
void intro() { |
|
|
|
for(int i = 0; i < 2; i++) { |
|
|
|
digitalWrite(BUZZER_PIN, HIGH); |
|
|
@ -187,10 +366,10 @@ void read_all_voltage() { |
|
|
|
pitch_raw = analogRead(PITCH_PIN); |
|
|
|
roll_raw = analogRead(ROLL_PIN); |
|
|
|
|
|
|
|
throttle_value = throttle_insert(throttle_raw); |
|
|
|
yaw_value = yaw_insert(yaw_raw); |
|
|
|
pitch_value = pitch_insert(pitch_raw); |
|
|
|
roll_value = roll_insert(roll_raw); |
|
|
|
throttle_value = pool_insert(THROTTLE, throttle_raw); |
|
|
|
yaw_value = pool_insert(YAW, yaw_raw); |
|
|
|
pitch_value = pool_insert(PITCH, pitch_raw); |
|
|
|
roll_value = pool_insert(ROLL, roll_raw); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -200,16 +379,50 @@ void setup() { |
|
|
|
// PIN Initialization
|
|
|
|
pinMode(BUZZER_PIN, OUTPUT); |
|
|
|
|
|
|
|
// Display Setup
|
|
|
|
u8g2.begin(); |
|
|
|
u8g2.setFont(u8g2_font_6x12_tr); |
|
|
|
|
|
|
|
median_initialize(); |
|
|
|
|
|
|
|
// Startup Sound
|
|
|
|
intro(); |
|
|
|
// intro();
|
|
|
|
|
|
|
|
// LoRA Setup
|
|
|
|
// setup LoRa transceiver module
|
|
|
|
LoRa.setPins(SS, RST, DIO0); |
|
|
|
|
|
|
|
// Replace the LoRa.begin(---E-) argument with your location's frequency
|
|
|
|
// 433E6 for Asia
|
|
|
|
// 866E6 for Europe
|
|
|
|
// 915E6 for North America
|
|
|
|
/*
|
|
|
|
while (!LoRa.begin(923E6)) { |
|
|
|
Serial.println("."); |
|
|
|
delay(500); |
|
|
|
} |
|
|
|
|
|
|
|
// Change sync word (0xF3) to match the receiver
|
|
|
|
// The sync word assures you don't get LoRa messages from other LoRa transceivers
|
|
|
|
// ranges from 0-0xFF
|
|
|
|
LoRa.setSyncWord(0xA0); |
|
|
|
|
|
|
|
// Set to the highest PA Level
|
|
|
|
LoRa.setTxPower(20); |
|
|
|
LoRa.setSpreadingFactor(7); |
|
|
|
LoRa.setSignalBandwidth(125E3); |
|
|
|
// LoRa.setSignalBandwidth(250E3);
|
|
|
|
// LoRa.setCodingRate4(5);
|
|
|
|
// LoRa.setSignalBandwidth(15.6E3);
|
|
|
|
// LoRa.setSpreadingFactor(6);
|
|
|
|
|
|
|
|
Serial.println("LoRa Initializing OK!"); |
|
|
|
*/ |
|
|
|
} |
|
|
|
|
|
|
|
void loop() { |
|
|
|
read_all_voltage(); |
|
|
|
|
|
|
|
/*
|
|
|
|
// Serial.println(throttle_raw + " : " + yaw_raw);
|
|
|
|
Serial.print("T ["); |
|
|
|
Serial.print(throttle_raw); |
|
|
@ -218,7 +431,83 @@ void loop() { |
|
|
|
Serial.print("] P["); |
|
|
|
Serial.print(pitch_raw); |
|
|
|
Serial.print("] R["); |
|
|
|
Serial.print(roll_raw); |
|
|
|
Serial.print(roll_value); |
|
|
|
Serial.println("]"); |
|
|
|
|
|
|
|
if(millis() > last_update) { |
|
|
|
// Determine stick position
|
|
|
|
if(millis() > last_menu_stick_read) { |
|
|
|
if(roll_value > MENU_STICK_ROLL_MAX) { |
|
|
|
stick_navigation_position = MENU_RIGHT; |
|
|
|
} else if(roll_value < MENU_STICK_ROLL_MIN) { |
|
|
|
stick_navigation_position = MENU_LEFT; |
|
|
|
} else if(pitch_value > MENU_STICK_PITCH_MAX) { |
|
|
|
stick_navigation_position = MENU_OK; |
|
|
|
} else if(pitch_value < MENU_STICK_PITCH_MIN) { |
|
|
|
stick_navigation_position = MENU_BACK; |
|
|
|
} else { |
|
|
|
stick_navigation_position = MENU_NONE; |
|
|
|
} |
|
|
|
|
|
|
|
// Next reading...
|
|
|
|
last_menu_stick_read = millis() + 250; |
|
|
|
} else { |
|
|
|
stick_navigation_position = MENU_NONE; |
|
|
|
} |
|
|
|
|
|
|
|
if( |
|
|
|
!menu_entry_alpha_selected |
|
|
|
) { |
|
|
|
// Determine the menu position
|
|
|
|
if(stick_navigation_position == MENU_RIGHT) { |
|
|
|
to_right(&destination_alpha_state); |
|
|
|
} else if(stick_navigation_position == MENU_LEFT) { |
|
|
|
to_left(&destination_alpha_state); |
|
|
|
} else if(stick_navigation_position == MENU_OK) { |
|
|
|
menu_entry_alpha_selected = true; |
|
|
|
} |
|
|
|
|
|
|
|
u8g2.firstPage(); |
|
|
|
draw_menu(¤t_alpha_state); |
|
|
|
u8g2.setFont(u8g2_font_helvB10_tr); |
|
|
|
u8g2.setCursor((u8g2.getDisplayWidth()-u8g2.getStrWidth(menu_entry_alpha_list[destination_alpha_state.position].name))/2, u8g2.getDisplayHeight()-5); |
|
|
|
u8g2.print(menu_entry_alpha_list[destination_alpha_state.position].name); |
|
|
|
u8g2.nextPage(); |
|
|
|
towards(¤t_alpha_state, &destination_alpha_state); |
|
|
|
} else { |
|
|
|
if(stick_navigation_position == MENU_BACK) { |
|
|
|
menu_entry_alpha_selected = false; |
|
|
|
} else { |
|
|
|
u8g2.clearBuffer(); |
|
|
|
u8g2.setFontMode(1); |
|
|
|
u8g2.setFont(u8g2_font_helvB10_tr); |
|
|
|
u8g2.setCursor(0,15); |
|
|
|
u8g2.print(F("Selected:")); |
|
|
|
u8g2.setCursor(0,30); |
|
|
|
u8g2.print(F(menu_entry_alpha_list[destination_alpha_state.position].name)); |
|
|
|
u8g2.sendBuffer(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
last_update = millis() + 10; |
|
|
|
} |
|
|
|
|
|
|
|
/*
|
|
|
|
//if(millis() - last_update >= 2) {
|
|
|
|
// Send LoRa packet to receiver
|
|
|
|
//while(true) {
|
|
|
|
LoRa.setTxPower(20); |
|
|
|
LoRa.beginPacket(); |
|
|
|
// LoRa.print("hello ");
|
|
|
|
LoRa.print(counter); |
|
|
|
LoRa.endPacket(); |
|
|
|
counter++; |
|
|
|
//delay(100);
|
|
|
|
// delayMicroseconds(1500);
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
last_update = millis(); |
|
|
|
//}
|
|
|
|
*/ |
|
|
|
} |