/*
Simple oscilloscope (_20190212_OLEDoscilloscope.ino)
Setting switch function is starting up 1285byte ram free
2019/02/12 version radio pliers
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <avr/pgmspace.h> // Used to use PROGMEM (probably no need to include)
#include <EEPROM.h>
#define SCREEN_WIDTH 128 // OLED display width
#define SCREEN_HEIGHT 64 // OLED display height
#define REC_LENGTH 200 // Waveform data buffer size
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Save the range display name to flash memory
const char vRangeName[10][5] PROGMEM = {"A50V", "A 5V", " 50V", " 20V", " 10V", " 5V", " 2V", " 1V", "0.5V", "0.2V"}; // Vertical axis display characters (number of characters including \0 is required)
const char * const vstring_table[] PROGMEM = {vRangeName[0], vRangeName[1], vRangeName[2], vRangeName[3], vRangeName[4], vRangeName[5], vRangeName[6], vRangeName[7], vRangeName[8], vRangeName[9]};
const char hRangeName[8][6] PROGMEM = {" 50ms", " 20ms", " 10ms", " 5ms", " 2ms", " 1ms", "500us", "200us"}; // Horizontal axis ( 48 bytes)
const char * const hstring_table[] PROGMEM = {hRangeName[0], hRangeName[1], hRangeName[2], hRangeName[3], hRangeName[4], hRangeName[5], hRangeName[6], hRangeName[7]} ;
int waveBuff[REC_LENGTH]; // Waveform data recording memory (RAM is barely available)
char chrBuff[10]; // Display format buffer
String hScale = "xxxAs";
String vScale = "xxxx";
float lsb5V = 0.0055549; // Sensitivity coefficient for 5V range Standard value: Defined as 0.005371 V/1LSB
float lsb50V = 0.051513; // Sensitivity coefficient for 50V range 0.05371
volatile int vRange; // Vertical range 0:A50V, 1:A 5V, 2:50V, 3:20V, 4:10V, 5:5V, 6:2V, 7:1V, 8:0.5V
volatile int hRange; // Horizontal range 0:50m, 1:20m, 2:10m, 3:5m, 4;2m, 5:1m, 6:500u, 7;200u
volatile int trigD; // Trigger direction (polarity) 0: positive, 1: negative
volatile int scopeP; // Operation scope position 0: Vertical range, 1: Horizontal range, 2: Trigger direction
volatile boolean hold = false; // hold
volatile boolean paraChanged = false; // true when parameter is changed by interrupt
volatile int saveTimer; // Remaining time until EEPROM save
int timeExec; // Estimated execution time of current range (ms)
int dataMin; // Minimum value of buffer (min:0)
int dataMax; // Maximum value of buffer (max:1023)
int dataAve; // Average value of buffer (stored at 10x to ensure accuracy max:10230)
int rangeMax; // Buffer value to make the graph full scale
int rangeMin; // Buffer value that lowers the graph
int rangeMaxDisp; // max display value (specified by 100 times)
int rangeMinDisp; // Min display value
int trigP; // Trigger position on data buffer
boolean trigSync; // Trigger detection flag
int att10x; // Input attenuator (enabled at 1)
void setup() {
pinMode(2, INPUT_PULLUP); // Button input interrupt (int0 interrupt)
pinMode(8, INPUT_PULLUP); // Select button
pinMode(9, INPUT_PULLUP); // Up button
pinMode(10, INPUT_PULLUP); // Down button
pinMode(11, INPUT_PULLUP); // Hold switch
pinMode(12, INPUT); // Attenuator 1/10
pinMode(13, OUTPUT); // Status display
// Serial.begin(115200); // Using this will consume a lot of RAM
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
// Serial.println(F("SSD1306 failed"));
for (;;); // Don't proceed, loop forever
}
loadEEPROM(); // Read the previous settings from EEPROM
analogReference(INTERNAL); // Set to ADC full scale 1.1V (using internal vref)
attachInterrupt(0, pin2IRQ, FALLING); // Interrupt occurs when switch operation is detected
startScreen(); // Draw the common part
}
void loop() {
digitalWrite(13, HIGH);
setConditions(); // Set measurement parameters ← Using this will reduce RAM by 40 bytes
readWave(); // Read waveform (minimum 1.6ms)
digitalWrite(13, LOW); //
dataAnalize(); // Collect various data information (0.4-0.7ms)
writeCommonImage(); // Draw fixed image (4.6ms)
plotData(); // Graph plot (5.4ms+α)
dispInf(); // Display various information (6.2ms)
display.display(); // Transfer buffer and display (37ms)
saveEEPROM(); // Save settings to EEPROM if necessary
while (hold == true) { // Wait if Hold button is pressed
dispHold();
delay(10);
}
}
void setConditions() { // Set measurement conditions
// Get the horizontal axis range from PROGMEM
strcpy_P(chrBuff, (char*)pgm_read_word(&(hstring_table[hRange]))); // Set the horizontal axis range name
hScale = chrBuff; // set to hScale
// Vertical axis range setting
strcpy_P(chrBuff, (char*)pgm_read_word(&(vstring_table[vRange]))); // Set the vertical axis range name
vScale = chrBuff; // set to vScale
switch (vRange) { // Settings for each vertical axis range
case 0: { // Auto50V range
// rangeMax = 1023;
// rangeMin = 0;
att10x = 1; // Use input attenuator
break;
}
case 1: { // Auto 5V range
// rangeMax = 1023;
// rangeMin = 0;
att10x = 0; // Do not use input attenuator
break;
}
case 2: { // 50V range
rangeMax = 50 / lsb50V; // Setting the number of full scale pixels
rangeMaxDisp = 5000; // Vertical scale. Set with 100x value
rangeMin = 0;
rangeMinDisp =0;
att10x = 1; // Use input attenuator
break;
}
case 3: { // 20V range
rangeMax = 20 / lsb50V; // Setting the number of full scale pixels
rangeMaxDisp = 2000;
rangeMin = 0;
rangeMinDisp =0;
att10x = 1; // Use input attenuator
break;
}
case 4: { // 10V range
rangeMax = 10 / lsb50V; // Setting the number of full scale pixels
rangeMaxDisp = 1000;
rangeMin = 0;
rangeMinDisp =0;
att10x = 1; // Use input attenuator
break;
}
case 5: { // 5V range
rangeMax = 5 / lsb5V; // Setting the number of full scale pixels
rangeMaxDisp = 500;
rangeMin = 0;
rangeMinDisp =0;
att10x = 0; // Do not use input attenuator
break;
}
case 6: { // 2V range
rangeMax = 2 / lsb5V; // Setting the number of full scale pixels
rangeMaxDisp = 200;
rangeMin = 0;
rangeMinDisp =0;
att10x = 0; // Do not use input attenuator
break;
}
case 7: { // 1V range
rangeMax = 1 / lsb5V; // Setting the number of full scale pixels
rangeMaxDisp = 100;
rangeMin = 0;
rangeMinDisp =0;
att10x = 0; // Do not use input attenuator
break;
}
case 8: { // 0.5V range
rangeMax = 0.5 / lsb5V; // Setting the number of full scale pixels
rangeMaxDisp = 50;
rangeMin = 0;
rangeMinDisp =0;
att10x = 0; // Do not use input attenuator
break;
}
case 9: { // 0.5V range
rangeMax = 0.2 / lsb5V; // Setting the number of full scale pixels
rangeMaxDisp = 20;
rangeMin = 0;
rangeMinDisp =0;
att10x = 0; // Do not use input attenuator
break;
}
}
}
void writeCommonImage() { // Drawing common screen
display.clearDisplay(); // Clear the entire screen (0.4ms)
display.setTextColor(WHITE); // Draw with white text
display.setCursor(86, 0); // Start at top-left corner
display.println(F("av V")); // 1st line fixed character
display.drawFastVLine(26, 9, 55, WHITE); // Left vertical line
display.drawFastVLine(127, 9, 55, WHITE); // Left vertical line
display.drawFastHLine(24, 9, 7, WHITE); // Max value auxiliary mark
display.drawFastHLine(24, 36, 2, WHITE); //
display.drawFastHLine(24, 63, 7, WHITE); //
display.drawFastHLine(51, 9, 3, WHITE); // Max value auxiliary mark
display.drawFastHLine(51, 63, 3, WHITE); //
display.drawFastHLine(76, 9, 3, WHITE); // Max value auxiliary mark
display.drawFastHLine(76, 63, 3, WHITE); //
display.drawFastHLine(101, 9, 3, WHITE); // Max value auxiliary mark
display.drawFastHLine(101, 63, 3, WHITE); //
display.drawFastHLine(123, 9, 5, WHITE); // Auxiliary mark for rightmost Max value
display.drawFastHLine(123, 63, 5, WHITE); //
for (int x = 26; x <= 128; x += 5) {
display.drawFastHLine(x, 36, 2, WHITE); // Draw the center line (horizontal line) as a dotted line
}
for (int x = (127 - 25); x > 30; x -= 25) {
for (int y = 10; y < 63; y += 5) {
display.drawFastVLine(x, y, 2, WHITE); // Draw three dotted vertical lines
}
}
}
void readWave() { // Record the waveform to memory
if (att10x == 1) { // If you need a 1/10 attenuator
pinMode(12, OUTPUT); // Make the attenuator control pin an output
digitalWrite(12, LOW); // Output LOW
} else { // If attenuator is not used
pinMode(12, INPUT); // Set the attenuator control pin to Hi-Z (make it input)
}
switch (hRange) { // Change recording speed according to range
case 0: { // 50ms range
timeExec = 400 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x07; // Division ratio 128 (arduino original)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 112μs
delayMicroseconds(1888); // Sampling period adjustment
}
break;
}
case 1: { // 20ms range
timeExec = 160 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x07; // Division ratio 128 (arduino original)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 112μs
delayMicroseconds(688); // Sampling period adjustment
}
break;
}
case 2: { // 10 ms range
timeExec = 80 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x07; // Division ratio 128 (arduino original)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 112μs
delayMicroseconds(288); // Sampling period adjustment
}
break;
}
case 3: { // 5 ms range
timeExec = 40 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x07; // Division ratio 128 (arduino original)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 112μs
delayMicroseconds(88); // Sampling period adjustment
}
break;
}
case 4: { // 2 ms range
timeExec = 16 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x06; // Division ratio 64 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 56μs
delayMicroseconds(24); // Sampling period adjustment
}
break;
}
case 5: { // 1 ms range
timeExec = 8 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x05; // Division ratio 16 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 28μs
delayMicroseconds(12); // Sampling period adjustment
}
break;
}
case 6: { // 500us range
timeExec = 4 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x04; // Division ratio 16 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
for (int i = 0; i < REC_LENGTH; i++) { // 200 data
waveBuff[i] = analogRead(0); // Approximately 16μs
delayMicroseconds(4); // Sampling period adjustment
// Time fine adjustment 1.875μs (1 clock per nop, 0.0625μs @16MHz)
asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm ("nop"); asm("nop"); asm("nop");
asm("nop"); asm("nop"); asm("nop");
}
break;
}
case 7: { // 200us range
timeExec = 2 + 50; // Estimated execution time (ms) Used for countdown until saving to EEPROM
ADCSRA = ADCSRA & 0xf8; // Clear the lower 3 bits
ADCSRA = ADCSRA | 0x02; // Division ratio: 4 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
for (int i = 0; i < REC_LENGTH; i++) {
waveBuff[i] = analogRead(0); // Approximately 6μs
// Time fine adjustment 1.875μs (1 clock per nop, 0.0625μs @16MHz)
asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm ("nop"); asm("nop"); asm("nop");
asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm ("nop"); asm("nop"); asm("nop");
asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm ("nop"); asm("nop"); asm("nop");
}
break;
}
}
}
void dataAnalize() { // Set various information for plotting
int d;
long sum = 0;
// Find the maximum and minimum values
dataMin = 1023; // min
dataMax = 0; // Initialize the maximum value recording variable
for (int i = 0; i < REC_LENGTH; i++) { // Find the maximum and minimum values
d = waveBuff[i];
sum = sum + d;
if (d < dataMin) { // Minimum and
dataMin = d;
}
if (d > dataMax) { // Find the maximum value
dataMax = d;
}
}
// find the average value
dataAve = (sum + 10) / 20; // Average value calculation (calculated with 10 times the value to ensure accuracy)
//Determine the display max and min
if (vRange <= 1) { // If Auto range (range number is 1 or less)
rangeMin = dataMin - 20; // Set the display range lower limit to -20 below
rangeMin = (rangeMin / 10) * 10; // Round to 10 steps
if (rangeMin < 0) {
rangeMin = 0; // However, the lower limit is 0
}
rangeMax = dataMax + 20; // Set display range upper limit +20 above
rangeMax = ((rangeMax / 10) + 1) * 10; // Round up to 10 steps
if (rangeMax > 1020) {
rangeMax = 1023; // However, if it is over 1020, limit it to 1023
}
if (att10x == 1) { // with attenuator
rangeMaxDisp = 100 * (rangeMax * lsb50V); // Display range is determined by data. In other words, the upper limit is up to the full scale of the ADC.
rangeMinDisp = 100 * (rangeMin * lsb50V); // The lower limit depends on the measurement result, but is at least zero
} else { // No attenuator
rangeMaxDisp = 100 * (rangeMax * lsb5V);
rangeMinDisp = 100 * (rangeMin * lsb5V);
}
} else { // If fixed range
//Write the necessary processing here (nothing at the moment)
}
// Find the trigger position
for (trigP = ((REC_LENGTH / 2) - 51); trigP < ((REC_LENGTH / 2) + 50); trigP++) { // Find the point in the center of the data range that straddles the median value
if (trigD == 0) { // If the trigger direction is 0 (positive trigger)
if ((waveBuff[trigP - 1] < (dataMax + dataMin) / 2) && (waveBuff[trigP] >= (dataMax + dataMin) / 2)) {
break; // Rising trigger detected!
}
} else { // If not 0 (negative trigger)
if ((waveBuff[trigP - 1] > (dataMax + dataMin) / 2) && (waveBuff[trigP] <= (dataMax + dataMin) / 2)) {
break;
} // Falling trigger detected!
}
}
trigSync = true;
if (trigP >= ((REC_LENGTH / 2) + 50)) { // If no trigger is found, leave it in the center
trigP = (REC_LENGTH / 2);
trigSync = false; // Unsync display flag
}
}
void startScreen() { // Screen display at start
display.clearDisplay();
display.setTextSize(2); // Text in double-width,
display.setTextColor(WHITE); //
display.setCursor(10, 15); //
display.println(F("DSO start")); // Start screen display
display.setCursor(10, 35); //
display.println(F(" v0.8"));
display.display(); // display
delay(1000);
display.clearDisplay();
display.setTextSize(1); // Standard font size thereafter
}
void dispHold() { // Display Hold on screen
display.fillRect(32, 12, 24, 8, BLACK); // Fill 4 characters black
display.setCursor(32, 12);
display.print(F("Hold")); // Hold display
display.display(); //
}
void dispInf() { // Display various information
float voltage;
// Vertical sensitivity display
display.setCursor(2, 0); // At the top left of the screen
display.print(vScale); // Vertical sensitivity always
if (scopeP == 0) { // If the scope is correct
display.drawFastHLine(0, 7, 27, WHITE); // Display the bottom border
display.drawFastVLine(0, 5, 2, WHITE);
display.drawFastVLine(26, 5, 2, WHITE);
}
// Horizontal sensitivity display
display.setCursor(34, 0); //
display.print(hScale); // Horizontal axis scale (time/div) display
if (scopeP == 1) { // If the scope is correct
display.drawFastHLine(32, 7, 33, WHITE); // Display the bottom border
display.drawFastVLine(32, 5, 2, WHITE);
display.drawFastVLine(64, 5, 2, WHITE);
}
// Trigger polarity display
display.setCursor(75, 0); // Above the center of the waveform
if (trigD == 0) {
display.print(char(0x18)); // Trigger polarity display↑
} else {
display.print(char(0x19)); // ↓
}
if (scopeP == 2) { // If the scope is correct
display.drawFastHLine(71, 7, 13, WHITE); // Display the bottom border
display.drawFastVLine(71, 5, 2, WHITE);
display.drawFastVLine(83, 5, 2, WHITE);
}
// average voltage display
if (att10x == 1) { // If a 10x attenuator is included
voltage = dataAve * lsb50V / 10.0; // Voltage calculation with sensitivity coefficient of 50V range
} else {
voltage = dataAve * lsb5V / 10.0; // Voltage calculation with sensitivity coefficient of 5V range
}
dtostrf(voltage, 4, 2, chrBuff); // Convert to x.xx format
display.setCursor(98, 0); // At the top right of the screen
display.print(chrBuff); // Display the average voltage value
// display.print(saveTimer); // This is a convenient place to display for debugging
// Vertical axis scale display
voltage = rangeMaxDisp / 100.0; // Convert Max voltage
if (vRange == 1 || vRange > 4) { // If the sensitivity is less than 5V or Auto5V
dtostrf(voltage, 4, 2, chrBuff); // Convert to *.** format
} else { //
dtostrf(voltage, 4, 1, chrBuff); // Convert to **.* format
}
display.setCursor(0, 9);
display.print(chrBuff); // Max value display
voltage = (rangeMaxDisp + rangeMinDisp) / 200.0; // Calculate the median value
if (vRange == 1 || vRange > 4) { // If the sensitivity is less than 5V or Auto5V
dtostrf(voltage, 4, 2, chrBuff); // 2 decimal places
} else { //
dtostrf(voltage, 4, 1, chrBuff); // 1 decimal place
}
display.setCursor(0, 33);
display.print(chrBuff); // Center value display
voltage = rangeMinDisp / 100.0; // Calculate the Min value
if (vRange == 1 || vRange > 4) { // If the sensitivity is less than 5V or Auto5V
dtostrf(voltage, 4, 2, chrBuff); // 2 digits below the number
} else {
dtostrf(voltage, 4, 1, chrBuff); // 1 decimal place
}
display.setCursor(0, 57);
display.print(chrBuff); // Display Min value
// Trigger detection error display
if (trigSync == false) { // If trigger detection fails
display.setCursor(60, 55); // At the bottom center of the screen
display.print(F("Unsync")); // Display Unsync
}
}
void plotData() { // Plot data based on array values
long y1, y2;
for (int x = 0; x <= 98; x++) {
y1 = map(waveBuff[x + trigP - 50], rangeMin, rangeMax, 63, 9); // Convert to plot coordinates
y1 = constrain(y1, 9, 63); // Crush the protruding part
y2 = map(waveBuff[x + trigP - 49], rangeMin, rangeMax, 63, 9); //
y2 = constrain(y2, 9, 63); //
display.drawLine(x + 27, y1, x + 28, y2, WHITE); // Connect the points with a line
}
}
void saveEEPROM() { // Wait for a while after the button operation is completed, then save the settings to EEPROM
if (paraChanged == true) { // If there is a parameter change
saveTimer = saveTimer - timeExec; // Update countdown timer
if (saveTimer < 0) { // When the time is up
paraChanged = false; // Clear parameter change flag
EEPROM.write(0, vRange); // Save the current configuration state
EEPROM.write(1, hRange);
EEPROM.write(2, trigD);
EEPROM.write(3, scopeP);
}
}
}
void loadEEPROM() { // Read the settings saved in EEPROM
int x;
x = EEPROM.read(0); // vRange
if ((x < 0) || (x > 9)) { // If it is outside the range 0-9
x = 3; // Default value setting
}
vRange = x;
x = EEPROM.read(1); // hRange
if ((x < 0) || (x > 7)) { // If it is outside the range 0-9
x = 3; // Default value setting
}
hRange = x;
x = EEPROM.read(2); // trigD
if ((x < 0) || (x > 1)) { // If it is outside the range 0-9
x = 1; // Default value setting
}
trigD = x;
x = EEPROM.read(3); // scopeP
if ((x < 0) || (x > 2)) { // If it is outside the range 0-9
x = 1; // Default value setting
}
scopeP = x;
}
void pin2IRQ() { // Handling Pin2(int0) interrupt
// Pins 8, 9, 10, 11 of the operation button (tact switch) are tied together with a diode and become pin 2
// It is connected. In other words, if any button is pressed, this routine will be activated.
int x; // port information holding variable
x = PINB; // Read port B information
if ( (x & 0x07) != 0x07) { // If the lower 3 bits are not all high (if any is pressed)
saveTimer = 5000; // EEPROM save timer set (set in ms)
paraChanged = true; // Parameter changed flag ON
}
if ((x & 0x01) == 0) {
scopeP++;
if (scopeP > 2) {
scopeP = 0;
}
}
if ((x & 0x02) == 0) { // UP button is pressed and
if (scopeP == 0) { // If the scope hits the vertical range
vRange++;
if (vRange > 9) {
vRange = 9;
}
}
if (scopeP == 1) { // If the scope hits the horizontal range
hRange++;
if (hRange > 7) {
hRange = 7;
}
}
if (scopeP == 2) { // If the scope hits the trigger direction button
trigD = 0; // plus trigger
}
}
if ((x & 0x04) == 0) { // DOWN button is pressed and
if (scopeP == 0) { // If the scope hits the vertical range
vRange--;
if (vRange < 0) {
vRange = 0;
}
}
if (scopeP == 1) { // If the scope hits the horizontal range
hRange--;
if (hRange < 0) {
hRange = 0;
}
}
if (scopeP == 2) { // If the scope hits the trigger direction button
trigD = 1; // minus trigger
}
}
if ((x & 0x08) == 0) { // If the HOLD button is pressed
hold = ! hold; // invert flag
}
}