Hardware components | ||||||
| × | 4 | ||||
| × | 10 | ||||
| × | 4 | ||||
| × | 10 | ||||
Software apps and online services | ||||||
|
Touchscreen technology is great, no argument there, but I am sure that I am not the only one that misses the tactile feedback of push buttons and the beauty of a 7-segment display or a blinking LED. After seeing Jeff Highsmith's Mission Control Desk, I decided to make a control panel, old school, with switches, buttons, LEDs, rotaries and whatever I can fit in there; however, I also wanted it to be functional, something like the "DIY Overhead Control Panel" but different. Happens that at the time I was tinkering around with Microsoft's Flight Simulator X, it clicked: Why not make an autopilot panel? I opened MS Paint and mocked my first "design:"
This was the general idea of what I wanted it to look like. not worrying about any limitation I might face. It's what I called "No compromise design". From there I enhanced the quality of the design with an actual 3D rendered model with components sizes to scale:
If you're wondering how I made this 3D model (and all other 3D models in this project), I designed it on a PCB layout software (DipTrace <- awesome program) and lied to it a bit by making custom components and attaching 3D models that I downloaded from here (a shout out to everyone that shares their awesome designs there) as well as DigiKey.ca (many components on DigiKey have STEP files for them), and then onto a tedious job of strategically placing those components on the design and then viewing the 3D view of the complete design, then render them using the build in 3D builder on windows 10. obviously there is a better way to do it, but my knowledge of using 3D software is limited, and with these, most 3D models I download are already 100% to scale with all the measurements done, so a great way to see how things would fit together.
Up until this point, I didn't have any plan on how to make this happen or what challenges I will face. But as you can see, it doesn't resemble any actual plane cockpit. The reason for that is mainly desk space it requires, simplicity, and cost of building. After thinking of the limitations, I realized that it's easier to find a ready made enclosure and build the thing around it. so I redesigned it to this form (the actual form the project ended up taking):
Now that I have the general shape of it, it's time to start making a schematic. But before that, one thing I had (and still have) a problem with, is component heights. To make sure that everything is sitting nice and flush on (or at a nice distance from) the surface of the front panel, I had to split the design into two different boards in a strategic way to be able to mount the PCBs at different distances from the front panel. here is the way I split the design into two different boards:
The red part (which I called the control board) has the USB port, UART chip, the ATmega328P (with Arduino boot-loader on it), a few shift registers, an I/O chip, switches, LEDs and 2 of the rotary encoders. the blue side is called (creatively) the display board. and it has all the 7-segment displays (controlled by an I/O chip) as well as the remaining encoders. both boards are connected to each other using a 10-header connector.
Now that I know how the boards will be split. I finished the schematic and designed the PCB and got it manufactured from seeedstudio. Here is the 3D model of the design:
And the actual boards finally arrived:
Not perfect, but so pretty! Time to assemble:
Not my best hand soldering job, but it works. Notice how the two boards are mounted at different heights from each other (also this image is taken after I got the front panel cut as mentioned in the paragraph below).
For the front panel, even though my 3D models (and the vision) I had was a black panel, I went with a sheet of plywood (or whatever it is, I really know nothing about wood and their types) bought from a hardware store. I Prepared a design for a laser cutter:
One laser cutter session later and some assembly, this is the result:
It looks good, but I still wanted a black panel, so I went and bought myself a few black on white acrylic material from inventables. Another session on the laser cutter, and here it is:
After that, writing firmware for the MC. To extract and send information and commands from and to the flight simulator, Microsoft did a great job with SimConnect SDK and has a very intensive documentation on how to use the SDK. However, Link2FS just removes all this hassle from it, beautifully made program that allows you to chose what information you want to extract, what commands to send to FSX and it's all via Serial COM... just brilliant. (Jim from New Zealand, you are a Legend), anyway, here is a block diagram, everyone needs a block diagram:
The project is fundamentally just simple switches, LEDs and rotary encoders. The challenge was the limited I/O pins on the ATmega328 as well as speed and memory. I could have went with a bigger MC, but I went with shift registers and I/O driver chips instead (might have been cheaper to go with a bigger MC though)
And this is it. That makes one out of four panels!
FSX Radio Stack PanelAfter the success of the first panel, I decided to make another one. this time to handle radio communication, lights, ATC and few other aspects of a plane. I decided to stick with the same form factor of the autopilot and take it from there. Here is a 3D design of what I came up with:
Again, I had the same issue of component heights and the way they should sit on the front panel, so more board separation had to be done, but this time I was more liberal with the board names, here is what I came up with:
- Blue = Display Board
- Purple = Control Board
- Red = Switch Board
Note that I do that on paint using a screenshot of the 3D model I designed. The yellow lines between sections are the number of connections needed to connect the boards. helps with the design as well, lets you know the size of the connectors and their locations.
3 schematics and PCB layouts later:
I sent my gerbers to Seeed studio and got the boards a few weeks later: here it is:
And once again, got the front panel designed and laser cut:
Then time for hand soldering parts again:
After finishing assembly and writing firmware, it worked!!!-ish:
This one did have a few design issues, and some bodge wires were required, also one regret I do have is using one rotary encoder with a push button to control both high and low frequency adjustment (by pushing the button to change the range) instead of using the proper (but expensive) dual rotary encoder, which I ended up buying 5 of them later on anyways, but it was too late to use one for this project. *face palm*. maybe Rev2 is needed.
And now I finally have two working panels:
Now I got greedy, MORE PANELS NEEDED! 7 months of procrastinating later, I started working on yet another panel. This time, to handle electrical, flaps, gears, auto-brakes, and a few other things, so back to "3D modeling" the new panel:
This time I decided to use a couple of OLED diplays, LED bar-graph, and few new things to make it more functional and of course, prettier. This means back to breadboarding, to make sure I can use them the way I want. and what do you know it works (except I ignored input as in buttons and rotaries, I thought I got it to work before, no need to test it again):
Those OLED displays are supposed to have a footprint for a resistor on the back to change their I2C buss address, but for some reason, the ones I got didn't have that. I solved this issue by using one of these. I ended up using it for all the I2C devices not just the OLED displays. I thought, since I already have 8 channels, and I am only using 2 of them, might as well put all the I2C devices, each on it's own channel. I thought this might help a bit with the capacitance on the line; which is something I had an issue with in the previous 2 panels. calculating I2C line capacitance is a pain (to figure out the pull up resistor value range required), it took some trial and error to get it right, but with this multiplexer, it seems like it's not that big of an issue any standard pull up resistor value did the trick..
Although, using this multiplexer caused some issues with interrupts. for example. at a certain moment in the program, the multiplexer selected the display on channel 1 and started communicating with it, but right in the middle of that communication, an interrupt comes from an I/O chip connected on channel 3 which means now the interrupt routine will select channel is 3, and it does what it does with the I/O chip. after it's done it has to remember to select channel 1 again, so the program can continue whatever communication it was doing with the device on channel 1 before the interrupt. It's easy to implement this in code, but it took me a while to think about it while I was having some weird behavior that took a long time to trouble shoot.
But I am getting ahead of myself here, we are still on the design phase at this stage, and as always. board separation is an issue, so here it is for this panel:
- Red = Display Board
- Green= Control Board
- Blue = APU Board
Time to make schematics and pcb layout for those boards, right? No!
Somewhere in the middle of the front panel design it hit me... make a BIG panel, composed of those 3 smaller panels. The problem with that was, 3 is an awkward number, how am I supposed to make a nice looking big panel with an odd number of modules. That was when I decided: a 4th panel is needed, but what should it be?... I am running out of things to do here.... ah!! a GPS panel, even though actual commercial planes don't have this kind of GPS, It was a nice addition, and also as a very newbie "pilot," I do use it a lot.. so back to 3D design:
This one actually resembles the built-in GPS in FSX in terms of layout:
As for the rotary encoder, there is no way around it, I need a dual rotary encoder, so I declared defeat and bought myself a bunch as I mentioned earlier.
At this point, I had 3D designs for four different panels, so naturally I combined them into one 3D model to see how it look. I don't know about you, but it does tingle my nerdy sense for sure:
Now that I had the GPS design, I created the schematic and PCB (no need to separate anything on the GPS). Combined it with the PCB for the electrical panel, and here it is:
And here is the actual board fresh from Seeed Studio:
Time to assemble the electrical control panel:
Now that all the panels have been assembled. Of course 2 of them were already tested for a while, but the new two panels are not, and it would be nice to see how all of them work together:
The box that will hold all 4 panels will be made of wood, and as I mention before, I have no knowledge of wood work. Thankfully my friend, Cameron is great at it and he has all the tools required as well as the mindset for it. We mocked a few designs on the white board, and a week later we started working on it, I helped by being in his way most of the time. with that, a few hours later:
This is still not final, but it's a good start to measure how everything would fit:
Had some issue fitting all the connections, since it wasn't designed to be all in one box, all these bulky USB B connections made it a challenge. This forced me to rearrange the panels to fit the connections better:
Next was cutting small pieces to cover the edges, gluing, filing, painting and here the final result, a fully functional panel that is enough to fly to take off, cruise and land a stock plane on FSX. (I say stock because the are simpler than those proper add-ons modeled after a real plane.)
Here is a video of me using it to fly from JFK to YYZ. (Sorry for bad video editing it's my first time.)
Anyone that tried FSX knows that the stock planes that come with it are lacking and too simple, but this panel makes it more interesting to fly in my opinion. The panel doesn't resemble an actual cockpit as I mentioned before. but it's a good start, an icebreaker if you may. I have learnt a lot from this build and maybe one day I will be able to build something like this build, by Karl Clarke, 737diysim.com, his blog and YouTube channel are very informative, he shares all his designs and techniques. what he does is mind blowing I recommend you check it out.
I am not going to share the schematics because they do have some issues, and it took a lot of work and adjustments on the boards to get things going. but I already started thinking of rev B to fix those issues and enhance on things. I would share the new schematics for RevB once I have them.
/****************************************************************************************/
/* */
/* Autopilot 500-TS | ©Kantooya Inc */
/* */
/* Author: Sam Farah */
/* Email: sam.farah1986@gmail.com */
/* Website: http://samfarah.net */
/* Version: 1.0 */
/* Description: */
/* The firmware for the ATMega microcontroller on board, it controls */
/* the circuit, and handles reading inputs and sends commands to FSX. */
/* */
/****************************************************************************************/
//-------------------------- Headers ----------------------------
#include <AS1115.h>
#include "WSWire.h"
#include "math.h"
#include "Quadrature.h" //for rotary encoders
//---------------------------------------------------------------
//---------------------------- Pins -----------------------------
// Name Arduino Pin Pin on ATMega
// ----------- ----------- -------------
#define LED_DS 2 // 32
#define LED_CLK 3 // 1
#define LED_LTCH 4 // 2
#define ALTEnc_B 6 //
#define ALTEnc_A 7 //
#define VSEnc_B 8 //
#define VSEnc_A 9 //
#define IRQ 13 // 17
#define SpdEnc_B 10 //
#define SpdEnc_A 11 //
#define HdgEnc_A A0 //
#define HdgEnc_B A1 //
#define CrsEnc_A A2 //
#define CrsEnc_B A3 //
#define SDA A4 //
#define SCL A5 //
//---------------------------------------------------------------
//------------------------- Settings ----------------------------
#define SerialTimeout 1000
#define TempDelay 2000
//---------------------------------------------------------------
//-------------------------- Enums ------------------------------
enum EDispID{ ESpeedDisp = 0, EALTDisp = 1, EVSDisp = 2, ECrsDsip = 3, EHdgDisp = 4 };
//---------------------------------------------------------------
//----------------------- Prototypes ----------------------------
int ReadSwitches();
void pciSetup(byte);
void SendLEDData(String);
void(*resetFunc) (void) = 0; //declare reset function @ address 0
void TestLEDs(int);
void InitLEDs(int);
uint8_t I2C_ClearBus();
class SDisplayData;
class TTestDisplayData;
class TDisplay;
class TEncoderData;
class TBlinkLED;
//---------------------------------------------------------------
//------------------------- Objects -----------------------------
AS1115 DispCtrl1 = AS1115(0x01);
AS1115 DispCtrl2 = AS1115(0x02);
AS1115 SwitchCtrl = AS1115(0x03);
TEncoderData *SpeedCtrl, *ALTCtrl, *VSCtrl, *HdgCtrl, *CrsCtrl;
TBlinkLED *RealDataBlink, *SpeedBlink, *AltBlink, *HdgBlink;
//---------------------------------------------------------------
class SDisplayData
{
public:
String SetMem;
String ReadMem;
};
//---------------------- Public Variables -----------------------
int SwLEDList[] = { 3, 1, 2, 0, 9, 10, 11, 4, 0, 0, 14, 5, 0, 12, 8, 13 };
bool BlinIndexArray[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
String FSXCommands[] = { "B09", "B04", "B10", "", "A54", "A54", "B30", "B08", "", "", "Y080", "B05", "", "B34", "B01", "B26" };
String LEDString = "0000000000000000";
String OldLEDs = "0000000000000000";
String BlinkLEDs = "0000000000000000";
int tempVal, CodeIn;
bool TestProgram = false, ShowDP = false, ShowRealValues = false, OldShowRealValues = false;
bool ShowZeros, Connected = false, MasterBattery;
char navMeM, SpeedMeM, ATHRMeM;
SDisplayData ALTData, SpdData, VSData, HdgData, CrsData;
TDisplay *Displays[5];
unsigned long int HoldTime = 0, TimeOutCheck;
//---------------------------------------------------------------
//-------------------------- Classes ----------------------------
class TDisplay
{
private:
byte *Index;
int DigitCount;
EDispID ID;
bool TempValChange;
unsigned long int LastTempChange;
SDisplayData *Data;
void SendValues(byte index, char value)
{
if (index > 7)
{
if (DispCtrl2.digitWrite(index - 8, value, (TestProgram ? ShowZeros : MasterBattery)))
{
Serial.println("I2C Froze (Displays)");
I2C_ClearBus();
Wire.begin();
DispCtrl1.begin();
DispCtrl2.begin();
SwitchCtrl.begin();
}
}
else
{
if (DispCtrl1.digitWrite(index, value, (index == 6 || index == 1 ? ShowDP : 0)))
{
Serial.println("I2C Froze (Displays)");
I2C_ClearBus();
Wire.begin();
DispCtrl1.begin();
DispCtrl2.begin();
SwitchCtrl.begin();
}
}
}
public:
TDisplay(byte *_Index, EDispID _ID, SDisplayData *_Data)
{
Index = _Index;
DigitCount = 3;
ID = _ID;
UpdateDisplay(" ");
TempValChange = false;
Data = _Data;
}
void CheckTempDisplay()
{
if (TempValChange && millis() - LastTempChange > TempDelay)
{
TempValChange = false;
UpdateDisplay();
}
}
void UpdateDisplay()
{
UpdateDisplay(!ShowRealValues || TempValChange ? Data->SetMem : Data->ReadMem);
}
void UpdateDisplay(String Val)
{
bool LeadZeroFound = false;
bool Negative = false;
for (int i = 0; i < DigitCount; i++)
{
if (!MasterBattery && !TestProgram)Val[i] = ' ';
else
{
if (Val[i] == '-')
{
Negative = true;
Val[i] = ' ';
}
else if (Val[i] == '+')Val[i] = ' ';
else
if (Val[i] == '0' && !LeadZeroFound && i != DigitCount - 1 && (!TestProgram || ((Index[i] != 6 && Index[i] != 1) || LEDString[11] == '0')))Val[i] = ' ';
else
{
LeadZeroFound = true;
if (Negative)
{
if (TestProgram && (LEDString[11] == '1' && (Index[i] == 7 || Index[i] == 2)))
{
SendValues(Index[i - 1], '0');
SendValues(Index[i - 2], '-');
}
else SendValues(Index[i - 1], '-');
Negative = false;
}
}
}
SendValues(Index[i], Val[i]);
}
}
void ClearDisplay()
{
UpdateDisplay(" ");
}
EDispID GetID(){ return ID; }
void SetTempValChange(bool val)
{
TempValChange = val;
LastTempChange = millis();
}
};
//--------------------------------------------
//--------------------------------------------
class TTestDisplayData
{
public:
int Value;
TTestDisplayData()
{
Value = 0;
}
String toString()
{
String RetVal = String(Value);
String Buff = "";
for (int i = 0; i < 3 - RetVal.length(); i++)
Buff += '0';
RetVal = Buff + RetVal;
return RetVal;
}
void Inc()
{
Value++;
if (Value>999)Value = 0;
}
void Dec(bool AllowNegative)
{
Value--;
if (AllowNegative){ if (Value < -99)Value = 0; }
else{ if (Value < 0)Value = 0; }
}
};
//--------------------------------------------
class TEncoderData
{
private:
int R;// a variable
int Rold;// the old reading
int Rdif;// the difference since last loop
Quadrature *Encoder;
TTestDisplayData *TestData;
TDisplay *Disp;
public:
TEncoderData(int _PinA, int _PinB, TDisplay *_Disp)
{
Encoder = new Quadrature(_PinA, _PinB);
TestData = new TTestDisplayData();
Disp = _Disp;
}
void ReadEncoder(String IncCommand, String DecCommand)
{
R = (Encoder->position() / 2); //The /2 is to suit the encoder
if (R != Rold) { // checks to see if it different
(Rdif = (R - Rold));// finds out the difference
if (Rdif == 1) Serial.println(IncCommand);
if (Rdif == -1)Serial.println(DecCommand);
Rold = R; // overwrites the old reading with the new one.
Disp->SetTempValChange(true);
}
}
void ReadEncoder(TDisplay *Disp)//used for test program.
{
R = (Encoder->position() / 2); //The /2 is to suit the encoder
if (R != Rold) { // checks to see if it different
(Rdif = (R - Rold));// finds out the difference
if (Rdif == 1)TestData->Inc();
if (Rdif == -1)TestData->Dec(true);
Rold = R; // overwrites the old reading with the new one.
Disp->UpdateDisplay(TestData->toString());
}
}
void RefreshDisplay(TDisplay *Disp)
{
Disp->UpdateDisplay(TestData->toString());
}
};
//--------------------------------------------
class TBlinkLED
{
private:
unsigned long BlinkDelayCounter;
int LEDIndex;
int NormalDelay;
int ActiveDelay;
bool BlinkTemp;
public:
void Activate()
{
BlinkTemp = true;
BlinkDelayCounter = 0;
}
void deactivate()
{
BlinkTemp = false;
}
TBlinkLED(int _LEDIndex, int _NormalDelay, int _ActiveDelay)
{
LEDIndex = _LEDIndex;
NormalDelay = _NormalDelay;
ActiveDelay = _ActiveDelay;
BlinIndexArray[LEDIndex] = true;
}
void Blink(bool pressed)
{
if (millis() - BlinkDelayCounter > (pressed ? ActiveDelay : NormalDelay))
{
BlinkLEDs = GetBlinkString();
BlinkLEDs[LEDIndex] = BlinkTemp ? '1' : '0';
SendLEDData(BlinkLEDs);
BlinkTemp = !BlinkTemp;
BlinkDelayCounter = millis();
}
}
void DontBlink()
{
BlinkLEDs = GetBlinkString();
BlinkLEDs[LEDIndex] = LEDString[LEDIndex];
if (BlinkLEDs != OldLEDs)
{
SendLEDData(BlinkLEDs);
OldLEDs = BlinkLEDs;
}
}
};
//--------------------------------------------
//----------------------- End of Classes ------------------------
String GetBlinkString()
{
String RetVal = "0000000000000000";
for (int i = 0; i < 16; i++)
{
if (!BlinIndexArray[i])RetVal[i] = LEDString[i];
else RetVal[i] = BlinkLEDs[i];
}
return RetVal;
}
//----------------------- Interrupts ----------------------------
ISR(PCINT0_vect) // handle pin change interrupt for D8 to D13 here
{
interrupts();
if (digitalRead(IRQ) == LOW)
{
tempVal = ReadSwitches();
if (tempVal > 0)
{
if (TestProgram) //Execute Test Program when buttons are pressed.
{
if ((LEDString[4] != '1' || tempVal == 8) && tempVal != 1)
{
LEDString[SwLEDList[tempVal - 1]] = (LEDString[SwLEDList[tempVal - 1]] == '0' ? '1' : '0');
SendLEDData(ShowRealValues ? BlinkLEDs : LEDString);
}
switch (tempVal)
{
case 1:
HoldTime = millis();
break;
case 13:
resetFunc(); //exit test program
break;
case 5:
case 6:
case 7:
case 14:
case 15:
ShowZeros = (LEDString[12] == '1');
ShowDP = (LEDString[11] == '1'&& LEDString[8] == '1' && LEDString[5] == '0');
if (LEDString[9] == '0' && LEDString[10] == '1' && LEDString[8] == '1')
{
DispCtrl1.setFont(FONT_CODEB);
DispCtrl2.setFont(FONT_CODEB);
SpeedCtrl->RefreshDisplay(Displays[ESpeedDisp]);
ALTCtrl->RefreshDisplay(Displays[EALTDisp]);
VSCtrl->RefreshDisplay(Displays[EVSDisp]);
CrsCtrl->RefreshDisplay(Displays[ECrsDsip]);
HdgCtrl->RefreshDisplay(Displays[EHdgDisp]);
}
break;
case 8:
if (LEDString[4] == '1')
{
ShowRealValues = false;
LEDString = "1111111111111111";
for (int i = 0; i < 8; i++)DispCtrl1.digitWrite(i, 8, (i == 6 || i == 1));
for (int i = 0; i < 7; i++)DispCtrl2.digitWrite(i, 8, 1);
}
else
{
ShowDP = false;
LEDString = "0000000000000000";
}
SendLEDData(LEDString);
break;
}
}
else if (Connected) //send commands to FSX
{
switch (tempVal)
{
case 1: //B/C Hold Button
HoldTime = millis();
break;
case 2: //heading pressed, fixed bug where it captures real data.
Serial.println(LEDString[1] == '0' ? "Y111" : "Y121");
break;
case 12:// alt pressed, fixed bug where it captures real data
Serial.println(LEDString[5] == '0' ? "Y091" : "Y101");
break;
case 13://Level Button
Serial.println("B05");
Serial.println("B05");
Serial.println("B210000");
break;
default:
Serial.println(FSXCommands[tempVal - 1]);
}
}
}
}
else if (Connected || TestProgram)
{
if (tempVal == 1)
{
if (millis() - HoldTime > 300)
{
BlinkLEDs = LEDString;
RealDataBlink->Activate();
SpeedBlink->Activate();
AltBlink->Activate();
HdgBlink->deactivate();
for (int i = 0; i < 5; i++)Displays[i]->SetTempValChange(false);
ShowRealValues = !ShowRealValues;
if (TestProgram){
if (!ShowRealValues)
{
LEDString[3] == '0';
SendLEDData(LEDString);
}
}
}
else
{
if (TestProgram)
{
LEDString[3] = LEDString[3] == '0' ? '1' : '0';
SendLEDData(LEDString);
}
else
Serial.println("B09");
}
}
}
}
//---------------------------------------------------------------
void setup() {
Serial.begin(115200);
pinMode(IRQ, INPUT);
pinMode(LED_DS, OUTPUT);
pinMode(LED_CLK, OUTPUT);
pinMode(LED_LTCH, OUTPUT);
DispCtrl1.setFont(FONT_CODEB);
DispCtrl2.setFont(FONT_CODEB);
DispCtrl1.setDecode(DECODE_ALL_FONT);
DispCtrl2.setDecode(DECODE_ALL_FONT);
MasterBattery = false;
Displays[ESpeedDisp] = new TDisplay(new byte[3]{3, 4, 5}, ESpeedDisp, &SpdData);;
Displays[EALTDisp] = new TDisplay(new byte[3]{0, 1, 2}, EALTDisp, &ALTData);;
Displays[EVSDisp] = new TDisplay(new byte[3]{14, 6, 7}, EVSDisp, &VSData);;
Displays[ECrsDsip] = new TDisplay(new byte[3]{8, 9, 10}, ECrsDsip, &CrsData);;
Displays[EHdgDisp] = new TDisplay(new byte[3]{11, 12, 13}, EHdgDisp, &HdgData);;
SpeedCtrl = new TEncoderData(SpdEnc_A, SpdEnc_B, Displays[ESpeedDisp]);
ALTCtrl = new TEncoderData(ALTEnc_A, ALTEnc_B, Displays[EALTDisp]);
VSCtrl = new TEncoderData(VSEnc_A, VSEnc_B, Displays[EVSDisp]);
HdgCtrl = new TEncoderData(HdgEnc_A, HdgEnc_B, Displays[EHdgDisp]);
CrsCtrl = new TEncoderData(CrsEnc_A, CrsEnc_B, Displays[ECrsDsip]);
RealDataBlink = new TBlinkLED(3, 250, 125);
SpeedBlink = new TBlinkLED(13, 125, 250);
AltBlink = new TBlinkLED(5, 130, 250);
HdgBlink = new TBlinkLED(1, 135, 250);
SwitchCtrl.begin();
DispCtrl1.begin();
DispCtrl2.begin();
delay(500);
DispCtrl1.setIntensity(0x0F);
DispCtrl2.setIntensity(0x0F);
InitLEDs(1);
if (SwitchCtrl.ReadKeys(NULL) == 13)//go to test program
{
TestProgram = true;
TestLEDs(50);
SendLEDData("0000000000000000");
}
pciSetup(IRQ);
if (TestProgram)InitLEDs(2);
}
//------------------------------------------------------------------------------------------------------------------------------------------
void loop() {
if (TestProgram)
{
if (LEDString[4] == '0')
{
if (LEDString[8] == '1')
{
if (LEDString[9] == '1')
{
for (int i = 0; i < 5; i++)Displays[i]->UpdateDisplay(String(random(0, 1000)));
delay(200);
}
else if (LEDString[10] == '1')
{
SpeedCtrl->ReadEncoder(Displays[ESpeedDisp]);
ALTCtrl->ReadEncoder(Displays[EALTDisp]);
VSCtrl->ReadEncoder(Displays[EVSDisp]);
CrsCtrl->ReadEncoder(Displays[ECrsDsip]);
HdgCtrl->ReadEncoder(Displays[EHdgDisp]);
}
else {
DispCtrl1.setFont(FONT_HEX);
DispCtrl2.setFont(FONT_HEX);
for (int i = 0; i < 8; i++)
if (DispCtrl1.digitWrite(i, i, 0) >= 4)
{
Serial.println("I2C Froze");
I2C_ClearBus();
Wire.begin();
DispCtrl1.begin();
DispCtrl2.begin();
}
for (int i = 0; i < 7; i++)
if (DispCtrl2.digitWrite(i, i + 8, ShowZeros) >= 4)
{
Serial.println("I2C Froze");
I2C_ClearBus();
Wire.begin();
DispCtrl1.begin();
DispCtrl2.begin();
}
delay(200);
}
}
else
{
DispCtrl1.setFont(FONT_CODEB);
DispCtrl2.setFont(FONT_CODEB);
ShowZeros = false;
for (int i = 0; i < 5; i++)Displays[i]->ClearDisplay();
delay(200);
}
}
if (ShowRealValues)
{
RealDataBlink->Blink(LEDString[3] == '1');
if (LEDString[13] == '1')SpeedBlink->Blink(false);
if (LEDString[5] == '1')AltBlink->Blink(false);
if (LEDString[1] == '1')HdgBlink->Blink(false);
}
}
else //real program
{
{ENCODER(); } //Check the Rotary Encoders
if (Serial.available())
{ //Check if anything there
CodeIn = getChar(); //Get a serial read if there is.
if (CodeIn == '=') { EQUALS(); Connected = true; } // The first identifier is "=" ,, goto void EQUALS
else if (CodeIn == '?') { QUESTION(); Connected = true; }// The first identifier is "?" ,, goto void QUESTION
else if (CodeIn == '<') { LESSTHAN(); Connected = true; }// The first identifier is "?" ,, goto void QUESTION
else
{
String ReadBuffer = "";
while (Serial.available())ReadBuffer += char(Serial.read());// Empties the buffer use to show ReadBuffer += char(Serial.read());
if (ReadBuffer.length() > 0)
{
Serial.println("--------------------------");
Serial.println(ReadBuffer);
Serial.println("--------------------------");
}
}
for (int i = 0; i < 5; i++)Displays[i]->UpdateDisplay();
}
if (ShowRealValues != OldShowRealValues)
{
OldShowRealValues = ShowRealValues;
for (int i = 0; i < 5; i++)Displays[i]->UpdateDisplay();
}
if (ShowRealValues && MasterBattery)
{
RealDataBlink->Blink(LEDString[3] == '1');
if (LEDString[13] == '1' && SpdData.ReadMem != SpdData.SetMem)SpeedBlink->Blink(false);
else SpeedBlink->DontBlink();
if (LEDString[5] == '1' && VSData.ReadMem.substring(1, 3) != "00")AltBlink->Blink(false);
else AltBlink->DontBlink();
if (LEDString[1] == '1' && HdgData.ReadMem != HdgData.SetMem && !(HdgData.ReadMem == "360" && HdgData.SetMem == "000"))HdgBlink->Blink(false);
else HdgBlink->DontBlink();
}
else
if (LEDString != OldLEDs)
{
SendLEDData(LEDString);
OldLEDs = LEDString;
}
for (int i = 0; i < 5; i++)Displays[i]->CheckTempDisplay();
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
char getChar()// Get a character from the serial buffer
{
TimeOutCheck = millis();
while (Serial.available() == 0)
if (millis() - TimeOutCheck > SerialTimeout)return '0';//time out command
return((char)Serial.read());
}
//------------------------------------------------------------------------------------------------------------------------------------------
void SendLEDData(String data)
{
digitalWrite(LED_LTCH, HIGH);
digitalWrite(LED_LTCH, LOW);
digitalWrite(LED_LTCH, HIGH);
for (int i = 15; i >= 0; i--)
{
digitalWrite(LED_DS, (data[i] == '1' ? HIGH : LOW));
digitalWrite(LED_CLK, LOW);
digitalWrite(LED_CLK, HIGH);
}
digitalWrite(LED_LTCH, LOW);
digitalWrite(LED_LTCH, HIGH); //fix for double send
digitalWrite(LED_LTCH, LOW);
}
//------------------------------------------------------------------------------------------------------------------------------------------
void pciSetup(byte pin)
{
*digitalPinToPCMSK(pin) |= bit(digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit(digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
PCICR |= bit(digitalPinToPCICRbit(pin)); // enable interrupt for the group
}
//------------------------------------------------------------------------------------------------------------------------------------------
int ReadSwitches()
{
uint8_t ErrorCode;
int val = SwitchCtrl.ReadKeys(&ErrorCode);
if (ErrorCode >= 4)
{
Serial.println("I2C Froze (Switches)");
I2C_ClearBus();
Wire.begin();
DispCtrl1.begin();
DispCtrl2.begin();
SwitchCtrl.begin();
}
if (val > 0 && val < 255)
return val;
return 0;
}
//------------------------------------------------------------------------------------------------------------------------------------------
void TestLEDs(int D)
{
for (int i = 1; i < 15; i++)
{
if (i == 6 || i == 7 || i == 15 || i == 0)continue;
String temp = "";
for (int j = 0; j < 16; j++) temp += (j == i ? "1" : "0");
SendLEDData(temp);
delay(D);
}
for (int i = 13; i >= 2; i--)
{
if (i == 6 || i == 7 || i == 15 || i == 0)continue;
String temp = "";
for (int j = 0; j < 15; j++) temp += (j == i ? "1" : "0");
SendLEDData(temp);
delay(D);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void InitLEDs(int count)
{
int _Delay = 300;
for (int i = 1; i <= count; i++)
{
SendLEDData("1111111111111111");
delay(_Delay);
SendLEDData("0000000000000000");
delay(_Delay);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void EQUALS(){
CodeIn = getChar();
switch (CodeIn) {
case 'a': LEDString[8] = getChar(); break; //Master AP
case 'k': LEDString[5] = getChar(); break; //Altitude Hold
case 'm': LEDString[4] = getChar(); break; //App hold
case 'o': LEDString[2] = getChar(); break; //Loc (Nav) Hold
case 'n': LEDString[3] = getChar(); break; //B/C hold
case 'j': LEDString[1] = getChar(); break; //Heading Hold
case 'q': LEDString[11] = getChar(); break; //Flight Director
case 'v': LEDString[14] = getChar(); break; //Take-Off Power
case 'b': ALTData.SetMem = GetBufferValue(5); break; //ALT Set
case 'f': SpdData.SetMem = GetBufferValue(3); break; //Speed Set
case 'c': VSData.SetMem = GetBufferValue(5); break; //Vertical Speed Set
case 'd': HdgData.SetMem = GetBufferValue(3); break; //Heading Set
case 'e': CrsData.SetMem = GetBufferValue(3); CrsData.ReadMem = CrsData.SetMem; break; //Course Set
case 't': //ATHR
ATHRMeM = getChar();
if (MasterBattery)LEDString[12] = ATHRMeM;
break;
case 'l': //GPS-NAV
navMeM = getChar();
if (MasterBattery)
{
LEDString[9] = navMeM;
LEDString[10] = (LEDString[9] == '0' ? '1' : '0');
}
break;
case 's': //Speed Hold
SpeedMeM = getChar();
if (MasterBattery)LEDString[13] = SpeedMeM;
break;
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void QUESTION(){ // The first identifier was "?"
CodeIn = getChar();
switch (CodeIn) {
case 'U': // Bus Voltage
String buff = GetBufferValue(2);
getChar();
getChar();
if (buff.toInt() == 0)//plain's turned off
{
MasterBattery = false;
LEDString[9] = '0';
LEDString[10] = '0';
LEDString[12] = '0';
LEDString[13] = '0';
for (int i = 0; i < 5; i++)Displays[i]->UpdateDisplay("000");
}
else
{
MasterBattery = true;
LEDString[9] = navMeM;
LEDString[10] = (LEDString[9] == '0' ? '1' : '0');
LEDString[12] = ATHRMeM;
LEDString[13] = SpeedMeM;
for (int i = 0; i < 5; i++)Displays[i]->UpdateDisplay();
}
}
}
void LESSTHAN()
{
CodeIn = getChar();
switch (CodeIn)
{
case 'D': ALTData.ReadMem = GetBufferValue(5); break; //ALT Read
case 'P': SpdData.ReadMem = GetBufferValue(3); break; //Speed Read
case 'J': HdgData.ReadMem = GetBufferValue(3); break; //Heading Read
case 'L': //Vertical Speed Read
VSData.ReadMem = GetBufferValue(6);
VSData.ReadMem = VSData.ReadMem[0] + VSData.ReadMem.substring(2, 6);
break;
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void ENCODER(){
SpeedCtrl->ReadEncoder("B15", "B16");
ALTCtrl->ReadEncoder("B11", "B12");
VSCtrl->ReadEncoder("B13", "B14");
HdgCtrl->ReadEncoder("A57", "A58");
CrsCtrl->ReadEncoder("A56", "A55");
}
//------------------------------------------------------------------------------------------------------------------------------------------
String GetBufferValue(int byteCount)
{
String buff = "";
char temp;
for (int i = 0; i < byteCount; i++)
{
temp = getChar();
if ((temp <= '9' && temp >= '0') || temp == '-' || temp == '+' || temp == '.')
buff += temp;
else
buff += '0';
}
//Serial.println(buff);
return buff;
}
//------------------------------------------------------------------------------------------------------------------------------------------
uint8_t I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(SCL, INPUT_PULLUP);
delay(200);
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
}
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
int clockCount = 20; // > 2x9 clock
while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(SCL, INPUT); // release SCL LOW
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
int counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(100);
SCL_LOW = (digitalRead(SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
}
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
}
// else pull SDA line low for Start or Repeated Start
pinMode(SDA, INPUT); // remove pullup.
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
/// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(SDA, INPUT); // remove output low
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(SCL, INPUT);
Serial.println("I2C Recovered");
return 0; // all ok
}
//------------------------------------------------------------------------------------------------------------------------------------------
/****************************************************************************************/
/* */
/* Radiostack 125-PB | ©Kantooya */
/* */
/* Author: Sam Farah */
/* Email: sam.farah1986@gmail.com */
/* Website: http://samfarah.net */
/* Version: 1.0 */
/* Description: */
/* The firmware for the ATMega microcontroller on board, it controls */
/* the circuit, and handles reading inputs and sends commands to FSX. */
/* */
/****************************************************************************************/
//-------------------------- Headers ----------------------------
#include <AS1115.h>
#include "WSWire.h"
#include "math.h"
#include "Quadrature.h" //for rotary encoders
#include <MemoryFree.h>
#include <avr/pgmspace.h>
//---------------------------------------------------------------
//---------------------------- Pins -----------------------------
// Name Arduino Pin Pin on ATMega
// ----------- ----------- -------------
#define LED1_DS 2 // PD2
#define LED1_CLK 3 // PD3
#define LED1_LTCH 4 // PD4
#define LED2_DS 5 // PD5
#define LED2_CLK 6 // PD6
#define LED2_LTCH 7 // PD7
#define FrqEnc_A 8 //
#define FrqEnc_B 9 //
#define IRQ1 11 //
#define IRQ2 12 //
#define IRQ3 13 //
#define SDA A4 //
#define SCL A5 //
//---------------------------------------------------------------
//------------------------- Settings ----------------------------
#define SerialTimeout 1000
#define TempDelay 3000
#define TWI_FREQ 50L
//---------------------------------------------------------------
//-------------------------- Enums ------------------------------
enum EDispID{ EActiveDisp = 0, EStdByDisp = 1, EATCDisp = 2 };
enum EChannel{ COM1 = 0, COM2 = 1, NAV1 = 2, NAV2 = 3, ADF = 4 };
//---------------------------------------------------------------
//----------------------- Prototypes ----------------------------
class SDisplayData;
class TDisplay;
class TEncoderData;
class TSwitchCtrl;
class TChannel;
//---------------------------------------------------------------
//------------------------- Objects -----------------------------
AS1115 DispCtrl1 = AS1115(0x01), DispCtrl2 = AS1115(0x02);
AS1115 *SwitchCtr1 = new AS1115(0x03), *SwitchCtr2 = &DispCtrl2, *SwitchCtr3 = new AS1115(0x00);
TEncoderData *FreqCtrl;
TChannel *Channel[5];
//---------------------------------------------------------------
//---------------------- Public Variables -----------------------
String LEDString = "";
TDisplay *Displays[3];
TSwitchCtrl *SwitchCtrs[3];
byte tempDigitIndex, CodeIn;
unsigned long TimeOutCheck;
bool MHz = true, MasterPower = false;
uint8_t SelectedMode;
bool ATCMode;
char temp;
short mul;
//---------------------------------------------------------------
//-------------------------- Classes ----------------------------
class TChannel
{
public:
char MHzIncCommand[4];
char MHzDecCommand[4];
char KHzIncCommand[4];
char KHzDecCommand[4];
char SwapCommand[4];
char CallCommand[4];
String Active;
String Standby;
uint8_t Status;
uint8_t LEDIndex;
TChannel(char MHzUp[], char MHzDown[], char KHzUp[], char KHzDown[], char Swap[], char call[], uint8_t LED, uint8_t StatLED)
{
strcpy(MHzIncCommand, strcat(MHzUp, "\0"));
strcpy(MHzDecCommand, strcat(MHzDown, "\0"));
strcpy(KHzIncCommand, strcat(KHzUp, "\0"));
strcpy(KHzDecCommand, strcat(KHzDown, "\0"));
strcpy(SwapCommand, strcat(Swap, "\0"));
strcpy(CallCommand, strcat(call, "\0"));
Status = StatLED;
LEDIndex = LED;
}
};
//---------------------------------------------------------------
class TDisplay
{
private:
byte *Index;
byte DigitCount;
EDispID ID;
bool TempValChange;
unsigned long LastTempChange;
void SendValues(byte index, char value, bool ShowDP)
{
AS1115 *CurrentDriver = index > 7 ? &DispCtrl2 : &DispCtrl1;
index %= 8;
if (CurrentDriver->digitWrite(index, value, ShowDP))
{
// Serial.println("I2C Froze (Displays)");
I2C_ClearBus();
Wire.begin();
}
}
public:
String Data, TempData;
TDisplay(byte *_Index, EDispID _ID, byte _DigitCount)
{
Index = _Index;
DigitCount = _DigitCount;
ID = _ID;
UpdateDisplay(" ");
TempValChange = false;
TempData = "";
}
void CheckTempDisplay()
{
if (TempValChange && millis() - LastTempChange > TempDelay)
{
TempValChange = false;
UpdateDisplay();
tempDigitIndex = 0;
}
}
void UpdateDisplay(){ UpdateDisplay(TempValChange ? TempData : Data); }
void UpdateDisplay(String Val)
{
uint8_t DPFound = 0;
for (byte i = 0; i < DigitCount + DPFound; i++)
{
if (Val[i] == '.')
{
SendValues(Index[i - 1], Val[i - 1], true);
DPFound++;
}
else SendValues(Index[i - DPFound], Val[i], false);
}
}
void ClearDisplay(){ UpdateDisplay(" "); }
EDispID GetID(){ return ID; }
void SetTempValChange(bool val)
{
TempValChange = val;
LastTempChange = millis();
}
};
//--------------------------------------------
class TEncoderData
{
private:
int R;// a variable
int Rold;// the old reading
int Rdif;// the difference since last loop
Quadrature *Encoder;
TDisplay *Disp;
public:
TEncoderData(uint8_t _PinA, uint8_t _PinB, TDisplay *_Disp)
{
pinMode(_PinA, INPUT);
pinMode(_PinB, INPUT);
Encoder = new Quadrature(_PinA, _PinB);
Disp = _Disp;
}
void ReadEncoder(String IncCommand, String DecCommand)
{
R = (Encoder->position() / 2); //The /2 is to suit the encoder
if (R != Rold) { // checks to see if it different
(Rdif = (R - Rold));// finds out the difference
if (Rdif == 1) Serial.println(IncCommand);
if (Rdif == -1)Serial.println(DecCommand);
Rold = R; // overwrites the old reading with the new one.
}
}
void ReadEncoder()
{
R = (Encoder->position() / 2); //The /2 is to suit the encoder
if (R != Rold) { // checks to see if it different
(Rdif = (R - Rold));// finds out the difference
if (Rdif == 1)
{
switch (mul)
{
case 0:Serial.println("A28"); break;
case 1:Serial.println("A27"); break;
case 2:Serial.println("A26"); break;
case 3:Serial.println("A25"); break;
}
}
if (Rdif == -1)
{
switch (mul)
{
case 0:Serial.println("A32"); break;
case 1:Serial.println("A31"); break;
case 2:Serial.println("A30"); break;
case 3:Serial.println("A29"); break;
}
}
Rold = R; // overwrites the old reading with the new one.
}
}
};
//--------------------------------------------
class TSwitchCtrl
{
private:
AS1115 *Chip;
String Data;
String OldData;
public:
byte IRQ;
byte LSB, MSB;
TSwitchCtrl(uint8_t _IRQ, AS1115 *_Chip, uint8_t _LSB, uint8_t _MSB)
{
IRQ = _IRQ;
Chip = _Chip;
LSB = _LSB;
MSB = _MSB;
Data = "";
Chip->begin();
}
void ReadSwitchs()
{
uint8_t ErrorCode;
OldData = Data;
Data = GetLeadingZeros(Chip->ReadKeysMul(&ErrorCode), 16);//potentially not needed (the Leading Zeros, already handled from library)
if (ErrorCode >= 4)
{
I2C_ClearBus();
Wire.begin();
}
}
String GetData(){ return Data; }
String GetOldData(){ return OldData; }
};
//--------------------------------------------
void pciSetup(byte pin)
{
*digitalPinToPCMSK(pin) |= bit(digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit(digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
PCICR |= bit(digitalPinToPCICRbit(pin)); // enable interrupt for the group
}
//----------------------- Interrupt ----------------------------
ISR(PCINT0_vect) // handle pin change interrupt for D8 to D13 here
{
interrupts();
for (byte SwtchCtrlIndex = 0; SwtchCtrlIndex < 3; SwtchCtrlIndex++)
if (digitalRead(SwitchCtrs[SwtchCtrlIndex]->IRQ) == LOW)
{
SwitchCtrs[SwtchCtrlIndex]->ReadSwitchs();
byte ChangedIndex = 0;
char NewValue, InverseValue;
for (byte i = 0; i < 16; i++)
if (SwitchCtrs[SwtchCtrlIndex]->GetData()[i] != SwitchCtrs[SwtchCtrlIndex]->GetOldData()[i])
{
NewValue = SwitchCtrs[SwtchCtrlIndex]->GetData()[i];
InverseValue = NewValue == '0' ? '1' : '0';
//Serial.println(String(48 - i - SwitchCtrs[SwtchCtrlIndex]->LSB) + " to " + NewValue);
ChangedIndex = 48 - i - SwitchCtrs[SwtchCtrlIndex]->LSB;
if (ChangedIndex == 33 || ChangedIndex == 34)break;//priortize Pitot heat, due to HW design error
}
if (!MasterPower && ChangedIndex != 20) return;//if powered off, only listen to power swtich.
if (NewValue == '1') //push buttons
{
switch (ChangedIndex)
{
case 33://Pitot Heat
case 34:
if (LEDString[28] == '0')Serial.println("C05");
else Serial.println("C06");
break;
case 8:ChangeMode(COM1); break; //COM1 Mode
case 16:ChangeMode(COM2); break; //COM2 Mode
case 18:ChangeMode(NAV1); break; //NAV1 Mode
case 23:ChangeMode(NAV2); break; //NAV2 Mode
case 22:ChangeMode(ADF); mul = 0; break; //ADF Mode
case 19: //Range Change
if (SelectedMode == ADF)
{
mul += 1;
mul %= 4;
}
else
{
MHz = !MHz;
LEDString[9] = LEDString[9] == '1' ? '0' : '1';
LEDString[8] = LEDString[9] == '1' ? '0' : '1';
}
SendLEDData(LEDString);
break;
case 40:Serial.println(Channel[SelectedMode]->CallCommand); break; //Call
case 1: //ATC Mode
ATCMode = !ATCMode;
if (ATCMode)Displays[EATCDisp]->UpdateDisplay("----");
else Displays[EATCDisp]->UpdateDisplay();
break;
case 7: KeypadPress("Y131", '1'); break; //Keypad 1
case 15:KeypadPress("Y141", '2'); break; //Keypad 2
case 6: KeypadPress("Y151", '3'); break; //Keypad 3
case 14:KeypadPress("Y181", '4'); break; //Keypad 4
case 5: KeypadPress("Y191", '5'); break; //Keypad 5
case 13:KeypadPress("Y201", '6'); break; //Keypad 6
case 4: KeypadPress("Y211", '7'); break; //Keypad 7
case 12:KeypadPress("Y241", '0'); break; //Keypad 0
case 3:UpdateTempDigits('8'); break; //potenial problem //Keypad CLR
case 11: //Keypad [8]
if (ATCMode)Serial.println("Y221");
else
{
char c[5];
sprintf(c, "%04d", freeMemory());
Displays[EATCDisp]->TempData = String(c);
Displays[EATCDisp]->SetTempValChange(true);
Displays[EATCDisp]->UpdateDisplay();
};
break;
case 2: if (ATCMode)Serial.println("Y231"); //Keypad [9]
else Serial.println("Y131");
break;
}
}
switch (ChangedIndex) //toggle switches
{
case 20: //Master Power
if (NewValue == '1')PowerUp();
else Shutdown();
break;
case 36:Serial.print("C43"); Serial.println(InverseValue); break;
case 44:Serial.print("C44"); Serial.println(InverseValue); break;
case 35:
Serial.print("C49"); Serial.println(InverseValue);
Serial.print("C48"); Serial.println(InverseValue);
break;
case 43:Serial.print("C45"); Serial.println(InverseValue); break;
case 37:
Serial.print("C47"); Serial.println(InverseValue);
Serial.print("C41"); Serial.println(InverseValue);
break;
case 45:Serial.print("C42"); Serial.println(InverseValue); break;
case 38:Serial.print("C46"); Serial.println(InverseValue); break;
case 46:
Serial.print("C50"); Serial.println(InverseValue);
break;
case 39:Serial.println("Y171"); break;
case 47:Serial.println("Y161"); break;
case 48:Serial.println("F12"); break;
case 21:Serial.println(Channel[SelectedMode]->SwapCommand);
MHz = true;
LEDString[9] = '1';
LEDString[8] = '0';
SendLEDData(LEDString);
break;
}
break;//potenial problem
}
}
//******************************************************************************************************************************************
void setup() {
Serial.begin(115200);
for (int i = 0; i < 32; i++)LEDString += "0";
ATCMode = false;
//PinModes
pinMode(IRQ1, INPUT);
pinMode(IRQ2, INPUT);
pinMode(IRQ3, INPUT);
pinMode(LED1_DS, OUTPUT);
pinMode(LED1_CLK, OUTPUT);
pinMode(LED1_LTCH, OUTPUT);
pinMode(LED2_DS, OUTPUT);
pinMode(LED2_CLK, OUTPUT);
pinMode(LED2_LTCH, OUTPUT);
//Display Controlers Settings
DispCtrl1.setFont(FONT_CODEB);
DispCtrl2.setFont(FONT_CODEB);
DispCtrl1.begin();
//Switch Controlers
SwitchCtrs[0] = new TSwitchCtrl(IRQ1, SwitchCtr1, 32, 47);
SwitchCtrs[1] = new TSwitchCtrl(IRQ2, SwitchCtr2, 16, 31);
SwitchCtrs[2] = new TSwitchCtrl(IRQ3, SwitchCtr3, 0, 15);
DispCtrl1.setIntensity(0x06);
DispCtrl2.setIntensity(0x06);
//Display definitions
Displays[EActiveDisp] = new TDisplay(new byte[5]{0, 1, 2, 3, 4}, EActiveDisp, 5);
Displays[EStdByDisp] = new TDisplay(new byte[5]{5, 6, 7, 8, 9}, EStdByDisp, 5);
Displays[EATCDisp] = new TDisplay(new byte[4]{13, 12, 11, 10}, EATCDisp, 4);
//Rotary Encoder Definition
FreqCtrl = new TEncoderData(FrqEnc_A, FrqEnc_B, Displays[EStdByDisp]);
//Modes / Channels
Channel[0] = new TChannel("A02", "A01", "A04", "A03", "A06", "A45", 7, 3);
Channel[1] = new TChannel("A08", "A07", "A10", "A09", "A12", "A46", 6, 2);
Channel[2] = new TChannel("A14", "A13", "A16", "A15", "A18", "A48", 5, 1);
Channel[3] = new TChannel("A20", "A19", "A22", "A21", "A24", "A49", 4, 0);
Channel[4] = new TChannel("A26", "A30", "A27", "A31", "", "A52", 12, 13);
delay(50);
for (byte i = 0; i < 3; i++)SwitchCtrs[i]->ReadSwitchs();//initialize switches - TODO: apply changes to sim accordingly
SendLEDData(LEDString);//turn off all LEDS
//Setup interrupt pins
pciSetup(IRQ1);
pciSetup(IRQ2);
pciSetup(IRQ3);
ChangeMode(SelectedMode);
if (SwitchCtrs[1]->GetData()[12] == '0')Shutdown();
else PowerUp();
}
//------------------------------------------------------------------------------------------------------------------------------------------
void loop() {
ENCODER(); //Check the Rotary Encoders
Displays[EATCDisp]->CheckTempDisplay();//Check if ATC display showing tmep data.
if (!Serial.available())return;//Check if anything there
switch (getChar()) //Get a serial read if there is.
{
case '=':EQUALS(); break;
case '<':LESSTHAN(); break;
default:while (Serial.available())Serial.read(); break;
}
UpdateAllDisplays(); //only if data was available.
}
//******************************************************************************************************************************************
//------------------------------------------------------------------------------------------------------------------------------------------
String GetLeadingZeros(String Bin, short digits)
{
String Padding = "";
for (int i = 0; i < digits - Bin.length(); i++)Padding += "0";
return Padding + Bin;
}
//------------------------------------------------------------------------------------------------------------------------------------------
void UpdateAllDisplays()
{
Displays[EActiveDisp]->Data = Channel[SelectedMode]->Active;
Displays[EStdByDisp]->Data = Channel[SelectedMode]->Standby;
if (MasterPower){
for (int i = 0; i < 3; i++){
if (i == 2 && ATCMode)continue;//to skip updating SQWAK display if in ATC Mode
Displays[i]->UpdateDisplay();
}
SendLEDData(LEDString);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void ChangeMode(uint8_t Mode)
{
SelectedMode = Mode;
for (uint8_t i = 0; i < 5; i++)LEDString[Channel[i]->LEDIndex] = i == Mode ? '1' : '0';
LEDString[11] = LEDString[Channel[SelectedMode]->Status];
UpdateAllDisplays();
}
//------------------------------------------------------------------------------------------------------------------------------------------
void UpdateTempDigits(char val)
{
if (val == '8')Displays[EATCDisp]->TempData = "1200";
else
{
if (tempDigitIndex == 0)Displays[EATCDisp]->TempData = " ";
Displays[EATCDisp]->TempData[tempDigitIndex] = val;
tempDigitIndex++;
Displays[EATCDisp]->SetTempValChange(true);
Displays[EATCDisp]->UpdateDisplay();
}
if (tempDigitIndex > 3 || val == '8')
{
Serial.println("A42" + Displays[EATCDisp]->TempData);
Displays[EATCDisp]->SetTempValChange(false);
if (tempDigitIndex > 0 && tempDigitIndex < 4)
{
Displays[EATCDisp]->Data = "1200";
Displays[EATCDisp]->UpdateDisplay();
}
tempDigitIndex = 0;
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void KeypadPress(String ATCCommand, char KeypadValue)
{
if (ATCMode)Serial.println(ATCCommand);
else UpdateTempDigits(KeypadValue);
}
//------------------------------------------------------------------------------------------------------------------------------------------
void Shutdown()
{
LEDString[10] = '0';
SendLEDData("0000000000000000");
for (byte i = 0; i < 3; i++)Displays[i]->ClearDisplay();
MasterPower = false;
Serial.println("A430");
}
void PowerUp()
{
LEDString[10] = '1';
LEDString[9] = MHz ? '1' : '0';
LEDString[8] = LEDString[9] == '1' ? '0' : '1';
SendLEDData(LEDString);
for (byte i = 0; i < 3; i++)Displays[i]->UpdateDisplay();
MasterPower = true;
Serial.println("A431");
}
void TestLEDs(int D)
{
String temp = "";
for (int j = 0; j < 32; j++)temp += (random(0, 2) == 1 ? "1" : "0");
SendLEDData(temp);
delay(D);
}
//------------------------------------------------------------------------------------------------------------------------------------------
int ReadSwitches()
{
uint8_t ErrorCode;
int val = SwitchCtr1->ReadKeys(&ErrorCode);
if (ErrorCode >= 4)
{
I2C_ClearBus();
Wire.begin();
}
if (val > 0 && val < 255)return val;
return 0;
}
//------------------------------------------------------------------------------------------------------------------------------------------
char getChar()// Get a character from the serial buffer
{
TimeOutCheck = millis();
while (Serial.available() == 0)if (millis() - TimeOutCheck > SerialTimeout)return '0';//time out command - prevent freezing
return((char)Serial.read());
}
//------------------------------------------------------------------------------------------------------------------------------------------
void EQUALS(){
CodeIn = getChar();
switch (CodeIn) {
case 'M':LEDString[3] = getChar(); break; //COM1 Active
case 'N':LEDString[2] = getChar(); break; //COM2 Active
case 'P':LEDString[1] = getChar(); break; //NAV1 Active
case 'Q':LEDString[0] = getChar(); break; //NAV2 Active
case 'S':LEDString[13] = getChar(); break; //ADF Active
case 'A':Channel[COM1]->Active = GetBufferValue(7); break; //COM1 Active
case 'B':Channel[COM1]->Standby = GetBufferValue(7); break; //COM1 Stby
case 'C':Channel[COM2]->Active = GetBufferValue(7); break; //COM2 Active
case 'D':Channel[COM2]->Standby = GetBufferValue(7); break; //COM2 Stby
case 'E':Channel[NAV1]->Active = GetBufferValue(6); break; //NAV1 Active
case 'F':Channel[NAV1]->Standby = GetBufferValue(6); break; //NAV1 Stby
case 'G':Channel[NAV2]->Active = GetBufferValue(6); break; //NAV2 Active
case 'H':Channel[NAV2]->Standby = GetBufferValue(6); break; //NAV2 Stby
case 'I':Channel[ADF]->Standby = GetBufferValue(6); Channel[4]->Active = " "; break; //ADF
case 'J':Displays[EATCDisp]->Data = GetBufferValue(4); break; //SQUAK Set
}
LEDString[11] = LEDString[Channel[SelectedMode]->Status];//update Call Stats LED
}
//------------------------------------------------------------------------------------------------------------------------------------------
void LESSTHAN()
{
CodeIn = getChar();
switch (CodeIn)
{
case 'd': LEDString[26] = getChar(); break;
case 'e': LEDString[27] = getChar(); break;
case 'b': LEDString[28] = getChar(); break;
case 'c': LEDString[29] = getChar(); break;
case 'g': //Avionics Power
temp = getChar();
if (temp == '0' && MasterPower)Serial.println("A431");
else if (temp == '1' && !MasterPower)Serial.println("A430");//force sim to follow phyical switch
break;
case 'f'://LED Status
String LightState = GetBufferValue(10);
LEDString[18] = LightState[2];
LEDString[19] = LightState[3];
LEDString[20] = LightState[8];
LEDString[21] = LightState[4];
LEDString[22] = LightState[6];
LEDString[23] = LightState[1];
LEDString[17] = LightState[5];
LEDString[16] = LightState[9];
break;
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void ENCODER(){
if (SelectedMode == ADF)
{
FreqCtrl->ReadEncoder();
}
else
{
if (MHz)FreqCtrl->ReadEncoder(Channel[SelectedMode]->MHzIncCommand, Channel[SelectedMode]->MHzDecCommand);
else FreqCtrl->ReadEncoder(Channel[SelectedMode]->KHzIncCommand, Channel[SelectedMode]->KHzDecCommand);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
String GetBufferValue(int byteCount)
{
String buff = "";
for (int i = 0; i < byteCount; i++)
{
temp = getChar();
if ((temp <= '9' && temp >= '0') || temp == '.')buff += temp;
else buff += '0';
}
return buff;
}
//------------------------------------------------------------------------------------------------------------------------------------------
uint8_t I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(SCL, INPUT_PULLUP);
delay(20);
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
}
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
int clockCount = 20; // > 2x9 clock
while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(SCL, INPUT); // release SCL LOW
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
int counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(50);
SCL_LOW = (digitalRead(SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
}
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
}
// else pull SDA line low for Start or Repeated Start
pinMode(SDA, INPUT); // remove pullup.
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(SDA, INPUT); // remove output low
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(SCL, INPUT);
//Serial.println("I2C Recovered");
return 0; // all ok
}
//------------------------------------------------------------------------------------------------------------------------------------------
void PulseLEN(uint8_t pin, bool StartLow)
{
if (StartLow)
{
digitalWrite(pin, LOW);
digitalWrite(pin, HIGH); //fix for double send
digitalWrite(pin, LOW);
}
else
{
digitalWrite(pin, HIGH);
digitalWrite(pin, LOW);
digitalWrite(pin, HIGH);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void PulseData(uint8_t DSpin, uint8_t CLKPin, char Data)
{
digitalWrite(DSpin, (Data == '1' ? HIGH : LOW));
digitalWrite(CLKPin, LOW);
digitalWrite(CLKPin, HIGH);
}
//------------------------------------------------------------------------------------------------------------------------------------------
void SendLEDData(String data)
{
PulseLEN(LED1_LTCH, false);
for (int i = 15; i >= 0; i--)PulseData(LED1_DS, LED1_CLK, data[i]);
PulseLEN(LED1_LTCH, true);
PulseLEN(LED2_LTCH, false);
for (int i = 31; i >= 16; i--)PulseData(LED2_DS, LED2_CLK, data[i]);
PulseLEN(LED2_LTCH, true);
}
//------------------------------------------------------------------------------------------------------------------------------------------
/****************************************************************************************/
/* */
/* Electrical Control | ©Kantooya */
/* */
/* Author: Sam Farah */
/* Email: sam.farah1986@gmail.com */
/* Website: http://samfarah.net */
/* Version: 1.0 */
/* Description: */
/* The firmware for the ATMega microcontroller on board, it controls */
/* the circuit, and handles reading inputs and sends commands to FSX. */
/* */
/****************************************************************************************/
//-------------------------- Headers ----------------------------
#include <U8g2lib.h>
#include <AS1115.h>
//#include <Arduino.h>
#include <Quadrature.h> //for rotary encoders
//#ifdef U8X8_HAVE_HW_SPI
//#include <SPI.h>
//#endif
#ifdef U8X8_HAVE_HW_I2C
#include <WSWire.h>
#endif
#include <MemoryFree.h>
#include <avr/pgmspace.h>
//---------------------------------------------------------------
//---------------------------- Pins -----------------------------
// Name Arduino Pin Pin on ATMega
// ----------- ----------- -------------
#define LED2_CLK PD5
#define LED2_DS PD7
#define LED2_LTCH PD6
#define LED1_CLK PD3
#define LED1_DS PD2
#define LED1_LTCH PD4
#define IRQ1 A0
#define IRQ2 A1
//---------------------------------------------------------------
//------------------------- Settings ----------------------------
#define TCAADDR 0x70
#define SerialTimeout 1000
#define TempDelay 3000
#define EBaro 0
#define ENSet 1
//---------------------------------------------------------------
//----------------------- Prototypes ----------------------------
class TDisplay;
class TEncoderData;
class TSwitchCtrl;
class TChannel;
class TPowerData;
class TInstrumentData;
//---------------------------------------------------------------
//------------------------- Objects -----------------------------
AS1115 disp = AS1115(0x00);
AS1115 *SwitchCtr1 = new AS1115(0x00), *SwitchCtr2 = new AS1115(0x00);
TSwitchCtrl *SwitchCtrs[2];
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
Quadrature Encoder1(8, 9);
Quadrature Encoder2(A2, A3);
//---------------------------------------------------------------
//-------------------------- Enums ------------------------------
//enum EDispID { EBaro = 0, ENSet = 1 };
//---------------------------------------------------------------
//---------------------- Public Variables -----------------------
//unsigned long TimeOutCheck;
//byte counter = 0;
byte tempDigitIndex, CodeIn;
String LED1String = "";
String LED2String = "";
char temp;
TDisplay *Displays[2];
TPowerData *PowerData;
TInstrumentData *InstrumentData;
bool MasterPower = true;
bool UpdatePowerDisp = false;
bool UpdateInstrumentDisp = false;
static const byte TickPositions[] PROGMEM = { 39,41,43,50,60,71,92, 102, 124 };
static const byte GraphOrder[] PROGMEM = { 8,9,10,11,12,13,14,15,0,1 };
//static const byte BaroDigits[] = { 3,4,5,6 };
//static const byte NSetDigits[] = { 0, 1, 2 };
bool SpeedSet = false;
bool N1Set = false;
bool Strtr1 = false;
bool Strtr2 = false;
uint8_t Autobreak = 0;
uint8_t LastAddress = 7;
int8_t R1old, R2old, R1dif, R2dif;
//---------------------------------------------------------------
class TDisplay
{
private:
byte *Index;
byte DigitCount;
//byte ID;
//bool TempValChange;
bool ShowADP;
//unsigned long LastTempChange;
void SendValues(byte index, char value, bool ShowDP)
{
//return;
tcaselect(3, true);
AS1115 *CurrentDriver = &disp;// index > 7 ? &DispCtrl2 : &DispCtrl1;
index %= 8;
if (CurrentDriver->digitWrite(index, value, ShowDP))
{
// Serial.println("I2C Froze (Displays)");
I2C_ClearBus();
Wire.begin();
}
}
public:
String Data;// , TempData;
TDisplay(byte *_Index, byte _DigitCount, bool _showADP)
{
Data.reserve(4);
//TempData.reserve(4);
ShowADP = _showADP;
Index = _Index;
DigitCount = _DigitCount;
//ID = _ID;
UpdateDisplay(" ");
//TempValChange = false;
//TempData = "";
}
void UpdateDisplay() { UpdateDisplay(Data); }
void UpdateDisplay(String Val)
{
uint8_t DPFound = 0;
for (byte i = 0; i < DigitCount + DPFound; i++)
{
if (Val[i] == '.')
{
SendValues(Index[i - 1], Val[i - 1], ShowADP);
DPFound++;
}
else SendValues(Index[i - DPFound], Val[i], false);
}
}
void ClearDisplay() { UpdateDisplay(" "); }
// byte GetID() { return ID; }
//void SetTempValChange(bool val)
//{
// TempValChange = val;
// // LastTempChange = millis();
//}
byte GetDigitCount() { return DigitCount; }
};
//--------------------------------------------
class TPowerData
{
public:
String BatteryVoltage;
String BatteryCurrent;
String MainBusVoltage;
String MainBusCurrent;
String APUVoltage;
bool APUGenActive;
TPowerData()
{
BatteryVoltage.reserve(4);
BatteryCurrent.reserve(4);
MainBusVoltage.reserve(4);
MainBusCurrent.reserve(3);
APUVoltage.reserve(2);
}
};
//--------------------------------------------
class TInstrumentData
{
public:
uint8_t Flaps;
uint8_t FlapsHandle;
uint8_t FlapsPoisitonCount;
uint8_t FuelLeft;
uint8_t FuelCentre;
uint8_t FuelRight;
String Autobreak;
TInstrumentData() { Autobreak.reserve(3); }
void SetFlaps(String StrFlaps) { Flaps = atoi(StrFlaps.c_str()); }
void SetFlapsHandle(String strFlapsHandle) { FlapsHandle = atoi(strFlapsHandle.c_str()); }
void SetFlapsPoisitonCount(String strFlapsPoisitonCount) { FlapsPoisitonCount = atoi(strFlapsPoisitonCount.c_str()); }
void SetFuelLeft(String strFuel) { FuelLeft = atoi(strFuel.c_str()); }
void SetFuelCentre(String strFuel) { FuelCentre = atoi(strFuel.c_str()); }
void SetFuelRight(String strFuel) { FuelRight = atoi(strFuel.c_str()); }
void SetAutoBreak(char Val)
{
switch (Val)
{
case '0':Autobreak = "RTO"; break;
case '1':Autobreak = "OFF"; break;
case '2':Autobreak = " 1 "; break;
case '3':Autobreak = " 2 "; break;
case '4':Autobreak = " 3 "; break;
case '5':Autobreak = "MAX"; break;
}
}
};
class TSwitchCtrl
{
private:
AS1115 *Chip;
String Data;
String OldData;
public:
byte MuxAddress;
byte IRQ;
byte LSB, MSB;
TSwitchCtrl(uint8_t _IRQ, AS1115 *_Chip, uint8_t _LSB, uint8_t _MSB, uint8_t _MuxAddress)
{
Data.reserve(16);
OldData.reserve(16);
IRQ = _IRQ;
Chip = _Chip;
LSB = _LSB;
MSB = _MSB;
MuxAddress = _MuxAddress;
Data = "";
tcaselect(MuxAddress, false);
Chip->begin(0x00, 0x00);
Chip->begin(0x01, 0x00);
Chip->begin(0x02, 0x00);
Chip->begin(0x03, 0x00);
}
void ReadSwitchs()
{
tcaselect(MuxAddress, false);
uint8_t ErrorCode;
//Serial.print("Saving ");
//Serial.print(Data);
//Serial.println(" as old");
OldData = Data;
Data = Chip->ReadKeysMul(&ErrorCode);//potentially not needed (the Leading Zeros, already handled from library)
//Serial.print("new val is: ");
//Serial.println (Data);
if (ErrorCode >= 4)
{
I2C_ClearBus();
Wire.begin();
}
tcaselect(LastAddress, false);
}
String GetData() { return Data; }
String GetOldData() { return OldData; }
void EmptyBuffer()
{
tcaselect(MuxAddress, false);
uint8_t ErrorCode;
Chip->ReadKeysMul(&ErrorCode);//potentially not needed (the Leading Zeros, already handled from library)
if (ErrorCode >= 4)
{
I2C_ClearBus();
Wire.begin();
}
tcaselect(LastAddress, false);
}
};
/*
Frame Buffer Examples: clearBuffer/sendBuffer. Fast, but may not work with all Arduino boards because of RAM consumption
Page Buffer Examples: firstPage/nextPage. Less RAM usage, should work with all Arduino boards.
U8x8 Text Only Example: No RAM usage, direct communication with display controller. No graphics, 8x8 Text only.
*/
void tcaselect(uint8_t i, bool SaveAddress) {
if (i > 7) return;
if (SaveAddress == true) LastAddress = i;
Wire.beginTransmission(TCAADDR);
Wire.write(1 << i);
Wire.endTransmission();
//delay(10);
}
void u8g2_prepare(void) {
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.setFontRefHeightExtendedText();
u8g2.setDrawColor(1);
u8g2.setFontPosTop();
u8g2.setFontDirection(0);
}
void PowerDevice()
{
if (MasterPower == 0)
{
SendLED2Data("0000000000000000");
SendLED1Data("0000000000000000");
Displays[EBaro]->UpdateDisplay(" ");
Displays[ENSet]->UpdateDisplay(" ");
tcaselect(6, true);
u8g2.clearDisplay();
tcaselect(7, true);
u8g2.clearDisplay();
}
else
{
UpdateAllDisplays();
UpdateInstrumentDisplay();
UpdatePowerDisplay();
}
}
ISR(PCINT1_vect) // handle pin change interrupt for D8 to D13 here
{
interrupts();
for (byte SwtchCtrlIndex = 0; SwtchCtrlIndex < 2; SwtchCtrlIndex++)
{
if (digitalRead(SwitchCtrs[SwtchCtrlIndex]->IRQ) == LOW)
{
SwitchCtrs[SwtchCtrlIndex]->ReadSwitchs();
byte ChangedIndex = 0;
char NewValue, InverseValue;
for (byte i = 0; i < 16; i++)
{
if (SwitchCtrs[SwtchCtrlIndex]->GetData()[i] != SwitchCtrs[SwtchCtrlIndex]->GetOldData()[i])
{
NewValue = SwitchCtrs[SwtchCtrlIndex]->GetData()[i];
InverseValue = NewValue == '0' ? '1' : '0';
ChangedIndex = 32 - i - SwitchCtrs[SwtchCtrlIndex]->LSB;
break;
}
}
if (!MasterPower && ChangedIndex != 31) return;//if powered off, only listen to power swtich.
if (NewValue == '1') //push buttons
{
switch (ChangedIndex)
{
case 1:Serial.println(F("C15")); break;
case 2:Serial.println(F("C14")); break;
case 8:Serial.println(F("E43")); Serial.println(F("E46")); break;
case 14:Serial.println(F("C02")); break;
case 15:Serial.println(F("C01")); break;
case 22:Serial.println(F("F31")); break;
case 23:Serial.println(F("B31")); break;
case 25:Serial.println(F("B35")); break;
case 27:Serial.println(F("C13")); break;
case 28:Serial.println(LED1String[4] == '1' ? F("C21") : F("C20")); break;
case 29:Serial.println(F("C04")); break;
case 30:Serial.println(F("C16")); break;
//case 26:Serial.println("F34"); break; AUTO BARO??
case 32:Serial.println(F("F34")); break;
}
}
switch (ChangedIndex) //toggle switches
{
case 4:Serial.println(NewValue == '1' ? F("E17") : F("E18")); break;
case 5:Serial.println(NewValue == '1' ? F("E32") : F("E31")); break;
case 6:Serial.println(NewValue == '1' ? F("E30") : F("C11")); break;
case 7:Serial.println(NewValue == '0' ? F("E30") : F("C11")); break;
case 12:Serial.println(NewValue == '1' ? F("E23") : F("E24")); break;
case 13:Serial.println(NewValue == '1' ? F("E20") : F("E21")); break;
//case 13:Serial.println(F("E21")); break;
case 17:Autobreak = 4; SetAB(); break;
case 18:Autobreak = 3; SetAB(); break;
case 19:Autobreak = 2; SetAB(); break;
case 20:Autobreak = 1; SetAB(); break;
case 21:Autobreak = 0; SetAB(); break;
case 24:Autobreak = 5; SetAB(); break;
case 31:MasterPower = NewValue == '1';
PowerDevice();
break;
}
}
}
}
void setup(void) {
LED1String.reserve(16);
LED2String.reserve(16);
//BargraphStirng.reserve(10);
Serial.begin(115200);
pinMode(IRQ1, INPUT_PULLUP);
pinMode(IRQ2, INPUT_PULLUP);
pinMode(LED1_CLK, OUTPUT);
pinMode(LED1_DS, OUTPUT);
pinMode(LED1_LTCH, OUTPUT);
pinMode(LED2_CLK, OUTPUT);
pinMode(LED2_DS, OUTPUT);
pinMode(LED2_LTCH, OUTPUT);
SwitchCtrs[0] = new TSwitchCtrl(IRQ1, SwitchCtr1, 0, 15, 5);
SwitchCtrs[1] = new TSwitchCtrl(IRQ2, SwitchCtr2, 16, 31, 4);
tcaselect(3, false);
disp.setFont(FONT_CODEB);
disp.begin(0x03, 0x00);
disp.setIntensity(0x06);
Displays[EBaro] = new TDisplay(new byte[5]{ 3,4,5,6 }, 4, true);
Displays[ENSet] = new TDisplay(new byte[5]{ 0, 1, 2 }, 3, false);
PowerData = new TPowerData();
InstrumentData = new TInstrumentData;
SwitchCtrs[0]->ReadSwitchs();
SwitchCtrs[1]->ReadSwitchs();
MasterPower = SwitchCtrs[0]->GetData()[1] == '1';
PowerDevice();
delay(50);
pciSetup(IRQ1);
pciSetup(IRQ2);
tcaselect(6, false);
u8g2.begin();
tcaselect(7, false);
u8g2.begin();
UpdatePowerDisp = false;
UpdateInstrumentDisp = false;
LED1String = "000000000000000000000000";
LED2String = "000000000000000000000000";
//u8g2.setFlipMode(0);
}
void loop(void) {
ENCODER(); //Check the Rotary Encoders
if (!Serial.available()) //Check if anything there
{
if (UpdatePowerDisp == true && MasterPower == true) UpdatePowerDisplay();
if (UpdateInstrumentDisp == true && MasterPower == true) UpdateInstrumentDisplay();
return;
}
while (Serial.available())
{
switch (getChar()) //Get a serial read if there is.
{
case '=':EQUALS(); break;
case '<':LESSTHAN(); break;
case '?':QUESTION(); break;
case '#':HASHTAG(); break;
case '$':DOLLAR(); break;
default:break; // ignore floating values //while (Serial.available()) Serial.read(); break; // empty buffer
}
}
while (Serial.available()) Serial.read(); // empty buffer
UpdateAllDisplays();
}
void pciSetup(byte pin)
{
*digitalPinToPCMSK(pin) |= bit(digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit(digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
PCICR |= bit(digitalPinToPCICRbit(pin)); // enable interrupt for the group
}
void SetAB()
{
for (byte i = 0; i < 6; i++)Serial.println(F("Y031"));
for (byte i = 0; i < Autobreak; i++)Serial.println(F("Y021"));
}
//------------------------------------------------------------------------------------------------------------------------------------------
void UpdateAllDisplays()
{
if (MasterPower) {
//for (byte i = 0; i < 2; i++) {
Displays[EBaro]->UpdateDisplay();
Displays[ENSet]->UpdateDisplay();
//}
SendLED2Data(LED2String);
LED1String[7] = !SpeedSet && N1Set ? '1' : '0';
LED1String[6] = MasterPower ? '1' : '0';
LED1String[15] = Displays[EBaro]->Data == "29.92" ? '1' : '0';
SendLED1Data(LED1String);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
char getChar()// Get a character from the serial buffer
{
//TimeOutCheck = millis();
while (Serial.available() == 0);// if (millis() - TimeOutCheck > SerialTimeout)return '0';//time out command - prevent freezing
return((char)Serial.read());
}
//------------------------------------------------------------------------------------------------------------------------------------------
void EQUALS() {
CodeIn = getChar();
switch (CodeIn) {
case 't': LED1String[12] = getChar(); break; //auto throttle
case 's': SpeedSet = getChar() == '1' ? true : false; break;//airspeed
case 'u': N1Set = getChar() == '1' ? true : false; break;//n1set
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void QUESTION() {
CodeIn = getChar();
String GenStats, GearStats;
switch (CodeIn) {
case 'k': Displays[EBaro]->Data = GetBufferValue(5); break; //Kohlsman setting Hg
case 'I': PowerData->BatteryVoltage = GetBufferValue(4); UpdatePowerDisp = true; break; //Battery Voltage
case 'J': PowerData->BatteryCurrent = GetBufferValue(4); UpdatePowerDisp = true; break; //Battery Current
case 'K': PowerData->MainBusVoltage = GetBufferValue(4); UpdatePowerDisp = true; break; //Main Bus Voltage
case 's':
GenStats = GetBufferValue(2);
LED1String[10] = GenStats[0] == '1' ? '0' : '1';
LED1String[11] = GenStats[1] == '1' ? '0' : '1';
break;
case 'Y'://gear
GearStats = GetBufferValue(3);
LED2String[2] = GearStats[2] == '1' ? '1' : '0';//right red
LED2String[4] = GearStats[1] == '1' ? '1' : '0';//left red
LED2String[6] = GearStats[0] == '1' ? '1' : '0';//nose red
LED2String[3] = GearStats[2] == '2' ? '1' : '0';//right grn
LED2String[5] = GearStats[1] == '2' ? '1' : '0';//left grn
LED2String[7] = GearStats[0] == '2' ? '1' : '0';//nose grn
break;
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void LESSTHAN()
{
CodeIn = getChar();
switch (CodeIn)
{
case 'G': InstrumentData->SetFlaps(GetBufferValue(3)); UpdateInstrumentDisp = true; break; //Flaps
case 'X': InstrumentData->SetFuelLeft(GetBufferValue(3)); UpdateInstrumentDisp = true; break; //Flaps
case 'Y': InstrumentData->SetFuelCentre(GetBufferValue(3)); UpdateInstrumentDisp = true; break; //Flaps
case 'Z': InstrumentData->SetFuelRight(GetBufferValue(3)); UpdateInstrumentDisp = true; break; //Flaps
case 'I': LED1String[1] = getChar(); break; //Plane on Ground
case 'q': LED1String[5] = getChar(); break; //Parking Break
case 'i': LED1String[4] = getChar(); break; //Spoiler Arked
case 'r': LED1String[2] = getChar(); break; //Eng 1 Fuel Vales
case 's': LED1String[0] = getChar(); break; //Eng 2 Fuel Vales
case 'k': Strtr1 = (getChar() == '1' ? true : false); LED1String[3] = (Strtr1 || Strtr2) ? '1' : '0'; break; //eng starter
case 'l': Strtr2 = (getChar() == '1' ? true : false); LED1String[3] = (Strtr1 || Strtr2) ? '1' : '0'; break; //eng starter
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void HASHTAG() {
CodeIn = getChar();
switch (CodeIn)
{
case 'H': Displays[ENSet]->Data = GetBufferValue(4); break;
case 'I': PowerData->MainBusCurrent = GetBufferValue(3); UpdatePowerDisp = true; break; //Main Bus Current
case 'L': InstrumentData->SetFlapsHandle(GetBufferValue(2)); UpdateInstrumentDisp = true; break; //Flaps Index
case 'K': InstrumentData->SetFlapsPoisitonCount(GetBufferValue(2)); UpdateInstrumentDisp = true; break; //Flaps Index
case 'J': InstrumentData->SetAutoBreak(getChar()); UpdateInstrumentDisp = true; break; //Flaps Index
case 'N': LED1String[14] = getChar(); break; //HAS G/S
case 'M': LED1String[13] = getChar(); break; //HAS LOC
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void ENCODER() {
int8_t R1 = (Encoder1.position() / 2); //The /2 is to suit the encoder
if (R1 != R1old) { // checks to see if it different
(R1dif = (R1 - R1old));// finds out the difference
//Serial.println(Encoder1.position());
if (R1dif == 1) Serial.println(F("B43"));
if (R1dif == -1)Serial.println(F("B44"));
R1old = R1; // overwrites the old reading with the new one.
//Disp->SetTempValChange(true);
}
int8_t R2 = (Encoder2.position() / 2); //The /2 is to suit the encoder
if (R2 != R2old) { // checks to see if it different
(R2dif = (R2 - R2old));// finds out the difference
if (R2dif == 1) Serial.println(F("C25"));
if (R2dif == -1)Serial.println(F("C26"));
R2old = R2; // overwrites the old reading with the new one.
//Disp->SetTempValChange(true);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void DOLLAR() {
CodeIn = getChar();
switch (CodeIn)
{
case 'g': GETBarLED(GetBufferValue(3)); break;
case 'i': PowerData->APUVoltage = GetBufferValue(2); UpdatePowerDisp = true; break; //APU Voltage
case 'k': PowerData->APUGenActive = getChar() == '1' ? true : false; break; //APU Gen Active (considers APU RPM)
case 'j': LED1String[9] = getChar(); break; //APU Gen
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
String GetBufferValue(int byteCount)
{
String buff = "";
for (byte i = 0; i < byteCount; i++)
{
temp = getChar();
if ((temp <= '9' && temp >= '0') || temp == '.' || temp == '-' || temp == '+')buff += temp;
else buff += '0';
}
return buff;
}
//------------------------------------------------------------------------------------------------------------------------------------------
uint8_t I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(SCL, INPUT_PULLUP);
delay(20);
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
}
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
byte clockCount = 20; // > 2x9 clock
while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(SCL, INPUT); // release SCL LOW
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
byte counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(50);
SCL_LOW = (digitalRead(SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
}
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
}
// else pull SDA line low for Start or Repeated Start
pinMode(SDA, INPUT); // remove pullup.
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(SDA, INPUT); // remove output low
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(SCL, INPUT);
//Serial.println("I2C Recovered");
return 0; // all ok
}
//------------------------------------------------------------------------------------------------------------------------------------------
void PulseLEN(uint8_t pin, bool StartLow)
{
if (StartLow)
{
digitalWrite(pin, LOW);
digitalWrite(pin, HIGH); //fix for double send
digitalWrite(pin, LOW);
}
else
{
digitalWrite(pin, HIGH);
digitalWrite(pin, LOW);
digitalWrite(pin, HIGH);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
void PulseData(uint8_t DSpin, uint8_t CLKPin, char Data)
{
digitalWrite(DSpin, (Data == '1' ? HIGH : LOW));
digitalWrite(CLKPin, LOW);
digitalWrite(CLKPin, HIGH);
}
//------------------------------------------------------------------------------------------------------------------------------------------
void SendLED1Data(String data)
{
PulseLEN(LED1_LTCH, false);
for (byte i = 0; i < 16; i++)PulseData(LED1_DS, LED1_CLK, data[i]);
PulseLEN(LED1_LTCH, true);
/* PulseLEN(LED2_LTCH, false);
for (int i = 31; i >= 16; i--)PulseData(LED2_DS, LED2_CLK, data[i]);
PulseLEN(LED2_LTCH, true);*/
}
//------------------------------------------------------------------------------------------------------------------------------------------
void SendLED2Data(String data)
{
PulseLEN(LED2_LTCH, false);
for (byte i = 0; i < 16; i++) {
PulseData(LED2_DS, LED2_CLK, data[i]); //delay(10);
}
PulseLEN(LED2_LTCH, true);
/* PulseLEN(LED2_LTCH, false);
for (int i = 31; i >= 16; i--)PulseData(LED2_DS, LED2_CLK, data[i]);
PulseLEN(LED2_LTCH, true);*/
}
//------------------------------------------------------------------------------------------------------------------------------------------
void GETBarLED(String APURPMVal)
{
String BargraphStirng = "";
if (APURPMVal[0] == '1')
{
BargraphStirng = "1111111111";
}
else
{
byte val = APURPMVal[1] - '0';
BargraphStirng = "";
for (byte i = 0; i < val; i++)
BargraphStirng += '1';
for (byte i = 0; i < 10 - val; i++)
BargraphStirng += '0';
}
for (byte i = 0; i < 10; i++)
LED2String[pgm_read_byte(&(GraphOrder[i]))] = BargraphStirng[i];
}
//------------------------------------------------------------------------------------------------------------------------------------------
void UpdatePowerDisplay()
{
//return;
tcaselect(6, true);
u8g2.firstPage();
do
{
u8g2.drawHLine(0, 17, 128);
u8g2.drawHLine(0, 42, 128);
u8g2.drawVLine(64, 0, 17);
u8g2.drawVLine(43, 42, 22);
u8g2.drawVLine(86, 42, 22);
u8g2_prepare();
u8g2.setFont(u8g2_font_freedoomr10_tu);
u8g2.drawStr(10, 0, (PowerData->BatteryVoltage + "V").c_str());
u8g2.drawStr(80, 0, (PowerData->BatteryCurrent + "A").c_str());
u8g2.drawStr(3, 50, (PowerData->MainBusVoltage + "V").c_str());
u8g2.drawStr(51, 50, (PowerData->MainBusCurrent + "A").c_str());
u8g2.drawStr(97, 50, (PowerData->APUVoltage + "V").c_str());
if (PowerData->BatteryCurrent[0] == '-')
{
u8g2.setFont(u8g2_font_crox3hb_tf);
u8g2.drawStr(2, 23, "LOW BATTERY");
}
u8g2.setFont(u8g2_font_6x10_tf);
} while (u8g2.nextPage());
UpdatePowerDisp = false;
}
//------------------------------------------------------------------------------------------------------------------------------------------
void UpdateInstrumentDisplay()
{
//return;
uint8_t i;
uint8_t CentrePos;
uint8_t actualpos;
tcaselect(7, true);
u8g2.firstPage();
u8g2_prepare();
do
{
u8g2.drawVLine(34, 0, 44);
u8g2.drawHLine(0, 44, 128);
u8g2.drawVLine(42, 45, 21);
u8g2.drawVLine(86, 45, 21);
// Fuel
u8g2.setFont(u8g2_font_4x6_tf);
u8g2.drawStr(13, 47, "LEFT");
u8g2.drawStr(55, 47, "CENTRE");
u8g2.drawStr(100, 47, "RIGHT");
u8g2.drawStr(40, 2, "FLAPS:");
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.drawStr(13, 56, (String(InstrumentData->FuelLeft) + "%").c_str());
u8g2.drawStr(55, 56, (String(InstrumentData->FuelCentre) + "%").c_str());
u8g2.drawStr(100, 56, (String(InstrumentData->FuelRight) + "%").c_str());
// //Autobreak
u8g2.drawStr(8, 15, InstrumentData->Autobreak.c_str());
// //Flaps Demo
u8g2.setFont(u8g2_font_micro_mn);
u8g2.drawStr(42, 35, "0");//save ram
u8g2.drawStr(40, 35, "1");
//u8g2.drawStr(44, 35, "2");
u8g2.drawStr(48, 35, "5");
u8g2.drawStr(56, 35, "10");
u8g2.drawStr(67, 35, "15");
u8g2.drawStr(88, 35, "25");
u8g2.drawStr(98, 35, "30");
u8g2.drawStr(120, 35, "40");//save ram ends
//u8g2.setFont(u8g2_font_4x6_tf);
//u8g2.setFont(u8g2_font_6x10_tf);
u8g2.drawHLine(44, 30, 81);
for (i = 0; i < 9; i++)u8g2.drawVLine(pgm_read_byte(&(TickPositions[i])), 29, 3); //SAVE RAM
actualpos = map(InstrumentData->Flaps, 0, 100, 0, 85);
//u8g2.drawStr(50, 5, String(39 + actualpos).c_str());
u8g2.drawVLine(39, 20, 7);
u8g2.drawBox(39, 20, actualpos, 7);
if (InstrumentData->FlapsPoisitonCount == 8)
{
CentrePos = pgm_read_byte(&(TickPositions[InstrumentData->FlapsHandle]));
u8g2.drawVLine(CentrePos, 10, 4);
u8g2.drawTriangle(CentrePos - 2, 14, CentrePos, 18, CentrePos + 3, 14); //tales waaayyy tooo much RAM
}
// //u8g2.drawVLine(i , 20, 8);
} while (u8g2.nextPage());
UpdateInstrumentDisp = false;
}
//------------------------------------------------------------------------------------------------------------------------------------------
/****************************************************************************************/
/* */
/* GPS | �Kantooya */
/* */
/* Author: Sam Farah */
/* Email: sam.farah1986@gmail.com */
/* Website: http://samfarah.net */
/* Version: 1.0 */
/* Description: */
/* The firmware for the ATMega microcontroller on board, it controls */
/* the circuit, and handles reading inputs and sends commands to FSX. */
/* */
/****************************************************************************************/
//-------------------------- Headers ----------------------------
#include <Quadrature.h>
#include <AS1115.h>
#include <WSWire.h>
//---------------------------------------------------------------
//---------------------------- Pins -----------------------------
// Name Arduino Pin Pin on ATMega
// ----------- ----------- -------------
#define IRQ1 A0 //
#define Encoder1A A3 //
#define Encoder1B A2 //
#define Encoder2A 8 //
#define Encoder2B 9 //
//---------------------------------------------------------------
//------------------------ SETTINGS -----------------------------
#define HoldDelay 700
//---------------------------------------------------------------
//----------------------- Prototypes ----------------------------
class TSwitchCtrl;
uint8_t I2C_ClearBus();
//---------------------------------------------------------------
//------------------------- Objects -----------------------------
Quadrature Encoder1(Encoder1A, Encoder1B);
Quadrature Encoder2(Encoder2A, Encoder2B);
TSwitchCtrl *SwitchCtr;
AS1115 *SwitchCtr1;
//---------------------------------------------------------------
//---------------------- Public Variables -----------------------
int R1old;
int R2old;
int R1dif;
int R2dif;
unsigned long int HoldTime = 0;
bool ClearPressed = false;
//---------------------------------------------------------------
//-------------------------- Classes ----------------------------
class TSwitchCtrl
{
private:
AS1115 * Chip;
String Data;
String OldData;
public:
byte IRQ;
byte LSB, MSB;
TSwitchCtrl(uint8_t _IRQ, AS1115 *_Chip, uint8_t _LSB, uint8_t _MSB)
{
IRQ = _IRQ;
Chip = _Chip;
LSB = _LSB;
MSB = _MSB;
Data = "";
Chip->begin(0x03,0x00);
}
void ReadSwitchs()
{
uint8_t ErrorCode;
OldData = Data;
Data = Chip->ReadKeysMul(&ErrorCode);// GetLeadingZeros(, 16);//potentially not needed (the Leading Zeros, already handled from library)
if (ErrorCode >= 4)
{
I2C_ClearBus();
Wire.begin();
}
}
String GetData() { return Data; }
String GetOldData() { return OldData; }
};
//---------------------------------------------------------------
//------------------------ Functions -----------------------------
String GetLeadingZeros(String Bin, short digits)
{
String Padding = "";
for (int i = 0; i < digits - Bin.length(); i++)Padding += "0";
return Padding + Bin;
}
//---------------------------------------------------------------
//----------------------- Interrupt ----------------------------
ISR(PCINT1_vect) // handle pin change interrupt for D8 to D13 here
{
interrupts();
if (digitalRead(SwitchCtr->IRQ) == LOW)
{
SwitchCtr->ReadSwitchs();
byte ChangedIndex = 0;
char NewValue, InverseValue;
for (byte i = 0; i < 16; i++)
if (SwitchCtr->GetData()[i] != SwitchCtr->GetOldData()[i])
{
NewValue = SwitchCtr->GetData()[i];
InverseValue = NewValue == '0' ? '1' : '0';
ChangedIndex = i;
}
//Serial.println(ChangedIndex);
//if (!MasterPower && ChangedIndex != 20) return;//if powered off, only listen to power swtich.
if (NewValue == '1') //push buttons
{
switch (ChangedIndex)
{
case 1: Serial.println("G11"); break;
case 2: Serial.println("G10"); break;
case 3: Serial.println("G12"); break;
case 4: Serial.println("G13"); break;
case 5: HoldTime = millis(); ClearPressed = true; break;
case 6: Serial.println("G18"); break;
case 7: Serial.println("G19"); break;
case 9: Serial.println("G02"); break;
case 10: Serial.println("G03"); break;
case 11: Serial.println("G04"); break;
case 12: Serial.println("G07"); break;
case 13: Serial.println("G08"); break;
case 14: Serial.println("G09"); break;
}
}
else
{
if (ChangedIndex == 5)
{
ClearPressed = false;
if (millis() - HoldTime < HoldDelay)Serial.println("G14");
}
}
}
}
//******************************************************************************************************************************************
void setup() {
pinMode(IRQ1, INPUT_PULLUP);
Serial.begin(115200);
SwitchCtr1 = new AS1115(0x00);
SwitchCtr = new TSwitchCtrl(IRQ1, SwitchCtr1, 0, 15);
delay(50);
pciSetup(IRQ1);
delay(50);
SwitchCtr->ReadSwitchs();
SwitchCtr->GetData();
}
void loop() {
ENCODER();
if (ClearPressed == true && millis() - HoldTime >= HoldDelay)
{
Serial.println("G15");
ClearPressed = false;
}
}
//******************************************************************************************************************************************
//------------------------------------------------------------------------------------------------------------------------------------------
void pciSetup(byte pin)
{
*digitalPinToPCMSK(pin) |= bit(digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit(digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
PCICR |= bit(digitalPinToPCICRbit(pin)); // enable interrupt for the group
}
//------------------------------------------------------------------------------------------------------------------------------------------
void ENCODER() {
int R1 = (Encoder1.position()); //The /2 is to suit the encoder
if (R1 != R1old) { // checks to see if it different
(R1dif = (R1 - R1old));// finds out the difference
//Serial.println(Encoder1.position());
if (R1dif == 1) Serial.println("G22");
if (R1dif == -1)Serial.println("G23");
R1old = R1; // overwrites the old reading with the new one.
//Disp->SetTempValChange(true);
}
int R2 = (Encoder2.position()); //The /2 is to suit the encoder
if (R2 != R2old) { // checks to see if it different
(R2dif = (R2 - R2old));// finds out the difference
if (R2dif == 1) Serial.println("G20");
if (R2dif == -1)Serial.println("G21");
R2old = R2; // overwrites the old reading with the new one.
//Disp->SetTempValChange(true);
}
}
//------------------------------------------------------------------------------------------------------------------------------------------
uint8_t I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(SCL, INPUT_PULLUP);
delay(20);
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
}
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
int clockCount = 20; // > 2x9 clock
while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(SCL, INPUT); // release SCL LOW
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
int counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(50);
SCL_LOW = (digitalRead(SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
}
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
}
// else pull SDA line low for Start or Repeated Start
pinMode(SDA, INPUT); // remove pullup.
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(SDA, INPUT); // remove output low
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(SCL, INPUT);
//Serial.println("I2C Recovered");
return 0; // all ok
}
//------------------------------------------------------------------------------------------------------------------------------------------
Comments