Jump to content

Recommended Posts

Posted

Hi all,

 

in this thread, I will try to explain how to use the Arduino based software DCS-BIOS with I2C devices.

 

The biggest advantage of I2C (from my point of view):

 

 


  • Large number of diffenet devices
  • Easy to configure in arduino code
  • very stable interface if you stay in the limitations
  • Devices are very low-cost
  • no cable mess in your cockpit

So what is I2C

 

I2C was designed to interconnect chip devices in a very convinient way. You just have two cables connecting the devices. SDA and SCL. These lines are connected from device to device. Thats quite all you have to know. In detail, its a bit meore complicated as you have masters and clients and so on. But you just have to know one thing. In our usage, the arduino is always the master and the added chips are the clients. So its the work of the arduino to do all the communication stuff.

 

So what devices are usefüll for a homecockpit?

 

The things you need most are inputs and outputs to connect all your switches, buttons and LEDs to your simulator. So the I2C device I use most in my setup is the MCP23016. This chip is called an Input/Output expander. The work it does is quite simple: Connect one chip to your I2C network and gain 16 Inputs/Outputs. Each MCP23016 has 3 adress bits, so one I2C network can have up to 8 MCP23016 chips. This makes an overall off 128 Inputs / Outputs. And this is exactly what we need.

 

To get all functions of the A-10 working, I use 4 arduinos each with 8 MCP23016 having 512 Inputs / Outputs.

 

I devided the A-10 cockpit in main parts:

 

 


  • left console
  • right console
  • front console
  • "the rest"

In future posts I will send some details about how to set up the hardware side correctly. Or u just google for the MCP 23016 datasheet.

 

Now the problem was how to use the great DCS-BIOS software, which is designed to use the standard Arduino ports, communicating with the I2C devices.

 

First I tried to implement the I2C functions directly into the .cpp and .h files of DCS-BIOS. It worked, but not quite well. I'm just not a good programmer to deal with virtual funktions and so on. So I went the more simple way and added the funktions directly into the arduino code. The biggest advantage for me was, that if you need a new kind of Switch, you just add a funktion in arduino code without changing the DCS-Bios library.

 

Here is an example:

 

void Switch2Pos(char* msg, char* arg, int chip, int bank, int pin) {

char state = State[chip][bank][pin];

if (state != lastState[chip][bank][pin])

{

sendDcsBiosMessage(msg, state == 1 ? "0" : "1");

}

lastState[chip][bank][pin] = State[chip][bank][pin];

}

 

This function is called when one state of an I2C device has changed. Time by time the different kinds of switches I used in my cockpit raised to about 10. and it's very easy to add more.

 

In the next chapter I will try to explain how you get your program to communicate with your MCP's

visit my build thread Gremlin's A-10 :thumbup:

http://forums.eagle.ru/showthread.php?t=86916

Posted

Hadware side - Inputs

 

okay lets have a look at the MCP 23016 chip:

mcp23016.PNG.4487ed2f9adfba1caaedf44d7379339d.PNG

 

the pins you have to use for minimal:

 

VSS (Pins 1,8,19): connect to -5V of arduino board

VDD (Pin 20): connects to +5V of arduino board

SDA (Pin 15): connects to arduino SDA

SCL (Pin 14): connects to arduino SCL

A0-A2(Pins 16-18): connect to either ground or +5V to set the chip adress

 

 

 

now you can just use the GP pins to connect your switches etc. by pulling them on turned on switch to ground. And thats all.

 

the software side:

 

to test, if your circuit works just use the I2C Scanner arduino sketch:

// --------------------------------------
// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    [url]http://www.gammon.com.au/forum/?id=10896[/url]
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// 
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//
#include <Wire.h>

void setup()
{
 Wire.begin();
  Serial.begin(9600);
 Serial.println("\nI2C Scanner");
}

void loop()
{
 byte error, address;
 int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
 for(address = 1; address < 127; address++ ) 
 {
   // The i2c_scanner uses the return value of
   // the Write.endTransmisstion to see if
   // a device did acknowledge to the address.
   Wire.beginTransmission(address);
   error = Wire.endTransmission();
    if (error == 0)
   {
     Serial.print("I2C device found at address 0x");
     if (address<16) 
       Serial.print("0");
     Serial.print(address,HEX);
     Serial.println("  !");
      nDevices++;
   }
   else if (error==4) 
   {
     Serial.print("Unknow error at address 0x");
     if (address<16) 
       Serial.print("0");
     Serial.println(address,HEX);
   }    
 }
 if (nDevices == 0)
   Serial.println("No I2C devices found\n");
 else
   Serial.println("done\n");
  delay(1000);           // wait 5 seconds for next scan
}

 

If you have set all A0-A2 Pins to ground the scanner should find an I2C device at adress 0x20.

visit my build thread Gremlin's A-10 :thumbup:

http://forums.eagle.ru/showthread.php?t=86916

Posted (edited)

Software-Side

 

okay lets just try to implement an arduino sketch that pushes an switch in DCS using DCS-Bios and I2c:

 

To use the Program, please make shure DCS-Bios library and MCPExpander library are installed correctly in Arduino IDE and that your MCP23016 is found by I2CScanner on adress 0x20 (a0-a2 connected to ground)

 

To make sure, that your DCS-BIOS is connected correctly to DCS just use the MasterCaution example of DCS-BIOS connecting a switch to Pin 10 of your arduino:

#include <DcsBios.h>
#include <Servo.h>
/* Instantiate a ProtocolParser object to parse the DCS-BIOS export stream */
DcsBios::ProtocolParser parser;
/* Declare a Master Caution Reset button on pin 10 */
DcsBios::Switch2Pos masterCautionBtn("UFC_MASTER_CAUTION", 10);
/* Make the LED connected to pin 13 into a Master Caution Light */
DcsBios::LED mcLed(0x1012, 0x0800, 13);
void setup() {
 Serial.begin(500000);
}
/*
Your main loop needs to pass data from the DCS-BIOS export
stream to the parser object you instantiated above.
It also needs to call DcsBios::PollingInput::pollInputs()
to detect changes in the state of connected controls and
pass them on to DCS.
*/
void loop() {
 // feed incoming data to the parser
 while (Serial.available()) {
     parser.processChar(Serial.read());
 }
 
 // poll inputs
 DcsBios::PollingInput::pollInputs();
}
/*
You need to define
void sendDcsBiosMessage(const char* msg, const char* arg)
so that the string msg, followed by a space, the string arg
and a newline gets sent to the DCS-BIOS import stream.
In this example we send it to the serial port, so you need to
run socat to read the data from the serial port and send it
over UDP to DCS-BIOS.
If you are using an Ethernet Shield, you would probably want
to send a UDP packet from this subroutine.
*/
void sendDcsBiosMessage(const char* msg, const char* arg) {
 Serial.write(msg);
 Serial.write(' ');
 Serial.write(arg);
 Serial.write('\n');
}
/*
This subroutine gets called every time a write access is received
from the export stream (you need to define it even if it
does nothing).
Use this to handle outputs which are not covered by the
DcsBios Arduino library (e.g. displays).
*/
void onDcsBiosWrite(unsigned int address, unsigned int value) {
 
}
 
 

Now we change the Program a bit to use an I2C Input instead of an arduino input: Connect your button now to MCP23016 GP0.0 (Pin 21) and Ground.

 

#include <DcsBios.h>
#include <Servo.h>
#include <Wire.h>
#include <SPI.h>  
#include "IOexpander.h"
/* Instantiate a ProtocolParser object to parse the DCS-BIOS export stream */
DcsBios::ProtocolParser parser;
/* Declare a Master Caution Reset button on pin 10 */

/* Make the LED connected to pin 13 into a Master Caution Light */
DcsBios::LED mcLed(0x1012, 0x0800, 13);

IOexpander MCP[8]; // Define one MCP
int lastState[8][2][8]; // Store the "old" state of the Input pins, (MCP)(Bank)(Pin)
void setup() {
 Serial.begin(500000);
 MCP[0].init(0x20,MCP23016); // Define adress of MCP23016
 MCP[0].pinModePort(0, INPUT); // Define Bank 0 (GP0) as input
 MCP[0].pinModePort(1, INPUT); // Define Bank 1 (GP1) as input
       
}

void loop() {
 // feed incoming data to the parser
 while (Serial.available()) {
     parser.processChar(Serial.read());
 }
 
 // poll inputs
 DcsBios::PollingInput::pollInputs();
 
 Switch2Pos ("UFC_MASTER_CAUTION","TOGGLE", 0, 0, 0); // Here we define A 2 Position switch on MCP 0, Bank 0, Pin 0
}

void sendDcsBiosMessage(const char* msg, const char* arg) {
 Serial.write(msg);
 Serial.write(' ');
 Serial.write(arg);
 Serial.write('\n');
}
 
 
void onDcsBiosWrite(unsigned int address, unsigned int value) {
 
}
// Define our I2C version of Switch2Pos
void Switch2Pos(char* msg, char* arg, int chip, int bank, int pin) {
char state = MCP[chip].digitalRead(bank, pin); // reads the Pin of the MCP
if (state != lastState[chip][bank][pin]) {
 sendDcsBiosMessage(msg, state == 1 ? "0" : "1");
}
lastState[chip][bank][pin] = state;
}

Okay here we go:

 

First we include the necesary Files for I2C communication:

#include <Wire.h>

#include <SPI.h>

#include "IOexpander.h"

 

Then define one MCPDevice of Class IOExpander:

IOexpander MCP[8]; // Define an array of 8 MCPs

 

Then we generate an array to store the Input values we've read the last cycle:

int lastState[8][2][8]; // Store the "old" state of the Input pins, (MCP)(Bank)(Pin)

 

In the setup() function we define the type of PortExpander we use and what funfionallity we want (in our case all inputs):

MCP[0].init(0x20,MCP23016); // Define adress of MCP23016

MCP[0].pinModePort(0, INPUT); // Define Bank 0 (GP0) as input

MCP[0].pinModePort(1, INPUT); // Define Bank 1 (GP1) as input

 

In the main loop we call our function defined at the end of the program to ask if the switch connected on MCP23016 GP0.0 is pressed. If yes we send a message to DCS:

 

Switch2Pos ("UFC_MASTER_CAUTION","TOGGLE", 0, 0, 0); // Here we define A 2 Position switch on MCP 0, Bank 0, Pin 0

 

and the function definition after the main function:

void Switch2Pos(char* msg, char* arg, int chip, int bank, int pin) {

char state = MCP[chip].digitalRead(bank, pin); // reads the Pin of the MCP

if (state != lastState[chip][bank][pin]) {

sendDcsBiosMessage(msg, state == 1 ? "0" : "1");

}

lastState[chip][bank][pin] = state;

}

 

Hope I could explain the software part a bit understandable:cry:

Edited by Gremlin77

visit my build thread Gremlin's A-10 :thumbup:

http://forums.eagle.ru/showthread.php?t=86916

Posted

Gremlin, thanks for your effort to share this. It'd take me time to digest this. When I'm done doing my panels, and switch to wiring them I'd try this. Initially I thought of one small arduino card per panel but quickly learned the limit of number of usb ports a PC can take. Then Ian said of using buses to join arduinos together. But he said i2c are error prone and RS-485 is the way to go. But you seem to have a working solution with i2c.

Posted

Gremlin: with 128+ I/O pins per Arduino, did you run into any performance problems? If so, a future version of the Arduino library should improve things (I am implementing interrupt-based communication as part of the RS-485 support).

 

Then Ian said of using buses to join arduinos together. But he said i2c are error prone and RS-485 is the way to go. But you seem to have a working solution with i2c.

 

If you run all 128 wires that go to a single Arduino's port expanders to one central location, I2C will be a reliable solution.

 

If you go with one I2C port expander per panel so they are spread out and the total length of your I2C bus increases, you may run into problems at some point, and it is very hard to predict where exactly that point is.

 

Here's a quick back-of-the-envelope calculation:

Cost of a MCP23016: about $1.05, that's $0.06 per I/O pin

Cost of a Pro Mini and MAX487 chip: $2.08 + $0.40 = $2.48, that's $0.14 per useable I/O pin

 

So going RS-485 all the way is roughly twice as expensive as the I2C solution (not including the cost of an Arduino Mega for the bus master). There are two main reasons I am going with RS-485 over I2C as the "officially recommended" solution:

  • It is more modular: you can remove a panel and test it in isolation. Each panel is a self-contained unit. That would make it possible to offer DCS-BIOS compatible panels for sale that would almost be "plug and play" (you'd still have to assign them a unique address somehow).
  • Any solution I endorse will result in questions from beginners when something does not work. RS-485 has a much lower risk to develop intermittent, hard-to-debug issues compared to I2C (as Mike Powell aptly put it, "it can cover a multitude of sins").

 

Both I2C and RS-485 are viable options. Know the trade-offs and go with what works best in your situation. The big disadvantage of RS-485 is that the software is not done yet, but I finally have everything I need to work on it. Now the heat has to go away to get my brain working again...

 

Note:

  • You can mix-and-match RS-485 and I2C
  • There are also I2C A/D converters available if you need analog inputs

Posted

The I2C spec. says a max of 400pf bus capacitance. Wire, solder joints, pcb traces and each device all add to the bus capacitance and you will have to select the correct pullup resistor values. The minimum pullup value I think is around 1.5k and the max for a 400pf bus is around 2k. At some point

due to high capacitance the bus will just not be able to switch between high and low fast enough or even not at all. So playing aroung with the pullup values may be needed. One way around this may be to segment the network by using devices as hubs or repeaters, or better really would be to use rs485

or can.

Posted

you're surely right. I solved the problem of long cables by dividing the pit into quadrants. In every quadrant i installed a knot directly in the middle next to the arduino, where i plug in all mcp modules. So the cable lenght is 40cm at max. The cables from mcp to panels are not critical, so they take the "long distance"

 

This setup works fine for me. But i ordered three arduino pro minis and the bridges to try out your suggestions. I am curious about how it will work.

visit my build thread Gremlin's A-10 :thumbup:

http://forums.eagle.ru/showthread.php?t=86916

Posted

Will you then be running say a few at the top level in multi master mode and they will then feed each segment. Would be handy if the pro mini had two i2c ports.

Posted

okay received three arduino pro minis and three Rs485 today and hooked them together.

 

I tried simple send and receive programs but get lots of transmission errors (missing or changed bytes) in the setup. Also, I think managing a multiple master setup with the RS485 seems quite complicated. Using tokens may work, don't know.

 

For me, my I2C solutions is the simplier way to get to success more quickly and in an easier way. Never came accross with transmission errors in my I2C setup. Maybe because I keep the cable length short and frequency quite low.

  • Like 1

visit my build thread Gremlin's A-10 :thumbup:

http://forums.eagle.ru/showthread.php?t=86916

Posted

Do you people have electric engineering degree? I want to create rudder padels. So which ardujno board is required, which connect to pc as joystick. I know I have to use potentiometer or hall sensor. I want my full circut work as joystick and work directly in DCS World. Thanks for your hard efforts.

By the way I am a c# programmer.

Win10, Intel 3rd Gen. Core i7 3.8Ghz, 20GB ram, Nvidia Geforce 1060 6GB Opentrack (Download it from HERE), PS3 Eye, Saitek x52-pro Joystick,

DIY Rudder Pedals,

Google Cardboard with DCS World

English is not my native language

Posted

If you are having errors with rs485, could be down to bus and terminating resistor value or not using them altogether, for a short bus with only two devices then only one resistor should be ok, but any more evices then each end of the bus should be terminated, generally 120 ohm. Are you bit banging yourself or using an onboard uart, could be down to baud rate settings.

Posted

Gremlin, out of curiosity... Are you using pwms to dim backlighting of panels? If so, I would de xpect I2C to be more prone to picking noise on the data lines (as it is a single ended bus). RS-485, on the other hand, is a robust bus conceived for industrial environment: being differential, has a bigger immunity to common mode noise, and was specified for longer distances than I2C.

 

From my personal experience, you may runinto some problems with I2C when using long wires, so pull-up resistors might need to be adjusted. However, noise might be more difficult to address...

 

That being said, I fully understand your reasons to go I2C, and also think it is the easiest way so, if it's working, don't touch it!!! ;) I only wanted to lnow if you were using it with backlighting-pwm. As I said, just curious :)

Posted

UART's are very unreliable in a bus topology without transceivers, there are a lot of reflections and the uart most probably wont have sufficient drive current to drive a number of receivers. Wifi is doable, but the overhead of decoding and encoding ethernet packets is the only downside. A low overhead protocal like Mifi might be better suited.

  • 4 months later...
Posted

Hi

I have problem to write MultiPosSwitch for I2C.

 

My code:

void SwitchMultiPos(char* msg, const byte* pinsArray, byte chip, char numberOfPins) {
 char i;  
 for (i=0; i<numberOfPins; i++) {  
   char state = MCP[chip].digitalRead(0, pinsArray[i]);
   if (state != lastState[chip][0][pinsArray[i]]) {
     char buf[7];
     utoa(state, buf,10);
     sendDcsBiosMessage(msg, buf);
   }
  
   lastState[chip][0][pinsArray[i]] = state;
 }

} 

With this code multiPosSwitch is set always to second position.

What is wrong in this code?

Posted (edited)
With this code multiPosSwitch is set always to second position.

What is wrong in this code?

 

How many positions does your switch have and what switch did you use to test? If I understand your code correctly, I'd expect it to always be set to the last position that changed state, e.g. if you switch from position 4 to 3, pin 4 changed from low to high, pin 3 changed from high to low, so 3 and 4 changed state, your code would send "YOUR_SWITCH 3" and "YOUR_SWITCH 4" in sequence and the switch would end up in position 4.

 

Without the rest of your code and the schematic it is almost impossible for someone else to properly debug this.

 

A proper bug report always needs these three things:

- What did you do? (including links to source code and schematics)

- What did you expect to happen? (This catches errors in the documentation and/or the user's mental model of how things should work)

- What happened instead? ("It didn't work" only tells us that it didn't do what you thought it should do. But did the Arduino work at all (e.g. other inputs/outputs still work), hang completely, ...)

 

Even if you provide all that, there's still the possibility that your schematic does not match the hardware on your desk, e.g. loose wires, flaky power supply, etc, but if you get these three points right you get the best chance that someone is able to help you.

 

EDIT: A second after hitting submit (while viewing my submitted message) I spotted something: you are sending the "state" variable, which will be 1 or 0 depending on whether the pin is high or low. I'd still expect it to switch between the first and second position depending on the direction you move your switch in.

Edited by [FSF]Ian
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...