Jump to content

CorporalCarrot

Members
  • Posts

    33
  • Joined

  • Last visited

Everything posted by CorporalCarrot

  1. Hi Peeps, I'm in the process of building an A-10C cockpit. I'm vlogging the build over on my youtube channel: Please let me know if there is anything specific you'd like me to cover in future vids? The next few vids are going to cover the MFCDs and my implementation of DCS Bios and my RS-485 network.
  2. Panel Finished! Hi Guys, Just a quick update with my fuel/flap panel using stepper motors. I ended up using some cheap 28BYJ-48 steppers for the flaps and fuel gauges. They're not paticuarily fast but they're fast enough for these gauges. I used a hall sensor on each gauge with a tiny 2mm diameter / 1mm thick neodymium magnet superglued to the 3D printed pointer shaft for zeoing/calibration. I'[m pretty happy with this panel, but frustrated with the back lighting on the flap gauge. Due to some physical structure of my rig, I had to put the back lights really close to the panel (petty much touching), resulting in an uneven light distribution. This is the panel booting up, zeroing the steppers and then me using DCS Bios debug too to move the fuel gauges; The left and right fuel gauges are running off 1 Arduino nano, and the flap gauge, fuel selector knob, test button and digital fuel display is run off a second nano. As you can see, 1 nano is able to move two steppers at once with no issues. The whole board is connected to the PC via an RS485 netword using a single cat5e cable. I ended up writing my own code, more or less from scatch. I found the zeroing/cal routine a little cumbersome, and didn't use the more efficient 'move quick to the sensor and then move off slowly' to get a fine calibration. Here's my code for the nano controlling both fuel gauges; #define DCSBIOS_RS485_SLAVE 14 #define TXENABLE_PIN 2 #include <AccelStepper.h> #include "DcsBios.h" /* L Fuel Gauge */ AccelStepper stepper1(AccelStepper::HALF4WIRE, 7, 9, 8, 10); long initial_homingL=-1; int pinposL = 0; void onFuelQtyLChange(unsigned int newValue) { pinposL = map (newValue, 0, 65535, 0, 1920); stepper1.moveTo(pinposL); } DcsBios::IntegerBuffer fuelQtyLBuffer(0x10ca, 0xffff, 0, onFuelQtyLChange); /* R Fuel Gauge */ AccelStepper stepper2(AccelStepper::HALF4WIRE, 3, 5, 4, 6); long initial_homingR=-1; int pinposR = 0; void onFuelQtyRChange(unsigned int newValue) { pinposR = map (newValue, 0, 65535, 1920, 0); stepper2.moveTo(pinposR); } DcsBios::IntegerBuffer fuelQtyRBuffer(0x10cc, 0xffff, 0, onFuelQtyRChange); void setup() { /* Left Cal */ pinMode(11,INPUT_PULLUP); stepper1.setMaxSpeed(600.0); stepper1.setAcceleration(1000.0); stepper1.move(150); stepper1.runToPosition(); stepper1.move(-1000000); while (!digitalRead(11)) { // Make the Stepper move CCW until the switch is activated stepper1.run(); // Start moving the stepper } stepper1.setCurrentPosition(0); // Set the current position as zero for now stepper1.setMaxSpeed(100.0); // Set Max Speed of Stepper (Slower to get better accuracy) stepper1.setAcceleration(100.0); // Set Acceleration of Stepper initial_homingL=-1; delay (1000); while (digitalRead(11)) { // Make the Stepper move CW until the switch is deactivated stepper1.moveTo(initial_homingL); stepper1.run(); initial_homingL--; } stepper1.setMaxSpeed(600.0); stepper1.setAcceleration(1000.0); stepper1.move(908); stepper1.runToPosition(); stepper1.setCurrentPosition(960); /* Right Cal */ pinMode(12,INPUT_PULLUP); stepper2.setMaxSpeed(600.0); stepper2.setAcceleration(1000.0); stepper2.move(-500); stepper2.runToPosition(); stepper2.move(1000000); while (!digitalRead(12)) { // Make the Stepper move CCW until the switch is activated stepper2.run(); // Start moving the stepper } stepper2.setCurrentPosition(0); // Set the current position as zero for now stepper2.setMaxSpeed(100.0); // Set Max Speed of Stepper (Slower to get better accuracy) stepper2.setAcceleration(100.0); // Set Acceleration of Stepper initial_homingR=1; delay (1000); while (digitalRead(12)) { // Make the Stepper move CW until the switch is deactivated stepper2.moveTo(initial_homingR); stepper2.run(); initial_homingR++; } stepper2.setMaxSpeed(600.0); stepper2.setAcceleration(1000.0); stepper2.move(-945); stepper2.runToPosition(); stepper2.setCurrentPosition(960); DcsBios::setup(); } void loop() { DcsBios::loop(); stepper1.run(); stepper2.run(); } Things I would do differently for the next time; 1) Either drill a hole in the pointer, or put one in the print, for the magnet to sit flush with with the surface, meaning potentially better alignment of the magnet - I had to use trial and error to get the right spot and then use tweezers to drop it in place. 2) I notice these steppers drift a little on the zeroing. I'm not sure whether it is the hall sensors or the steppers, but sometimes it's a little out (around 2 or 3 degrees). There are free analogue pins on the Nano, that I could use trim pots for the zeroing and put a tiny hole in the front panel to access them. Either use 1 trim pot to set the neutral postion (50% FSD) or could use 2 trim pots to set either end of the scale within the map function. i.e. pinposL = map (newValue, 0, 65535, A, B) where A and B are values detemined from the trim pot. 3) Use male pin headers! I was lazy and used female pin headers ('cos I find it easier to assemble male connectors), but they are weaker than male pin headers. I had to repair my PCB around several headers because of this! 4) Make sure there is space to backlight the switch tags, instead of using printed labels! This is my last A-10C panel, meaning once it is installed, the layout is complete. It's not a replica cockpit, but something usable in the space I had. However I now have cleared some other space in my house and work on a 1:1 replica of an F/A-18 cockpit using a curved screen and projectors starts in the new year! See you in the skies! Cpl. Carrot. :pilotfly:
  3. Thanks Chaps, Just to dig a little deeper, it seems this is the key line of code: inline bool zeroDetected() { return digitalRead(irDetectorPin) == 1; } I was about to ask whether I should replace 'irDetectorPin' with the pin no. of the sensor, as I couldn't see this setup in the code before this line (which is what I was expecting), but I've just noticed the following code towards the end: Vid60Stepper alt100ftPointer(0x107e, // address of stepper data stepper, // name of AccelStepper instance stepperConfig, // StepperConfig struct instance 11, // IR Detector Pin (must be HIGH in zero position) 0, // zero offset [](unsigned int newValue) -> unsigned int { /* this function needs to map newValue to the correct number of steps */ return map(newValue, 0, 65535, 0, stepperConfig.maxSteps-1); }); I presume this code sets up the zeroing device on the input of pin 11 of the Arduino? The 'zero offset' seems to be a metric if the zeroing device isn't actually at the zero position? Is this a number of steps? Finally, in a few gauges I'm making the pointer will need to be limited to 180 degrees of sweep. I can see this code runs the zeroing routine; if (initState == 2) { // zeroing if (!zeroDetected()) { stepper.runSpeed(); } else { stepper.setAcceleration(stepperConfig.acceleration); stepper.runToNewPosition(stepper.currentPosition() + zeroOffset); // tell the AccelStepper library that we are at position zero stepper.setCurrentPosition(0); lastAccelStepperPosition = 0; // set stepper acceleration in steps per second per second // (default is zero) stepper.setAcceleration(stepperConfig.acceleration); Will the stepper motor always be driven in the same direction (i.e. clockwise)? In which case I would imagine that the zeroing sensor needs to be towards the fully clockwise extreme of needle sweep to ensure that the position is sensed every time and doesn't go off and foul the end-stop or another gauge...? Space is limited in my setup, which isn't a replica cockpit, but a pit I share with my F1 wheel (I use the TS-PC pedals as rudders!), so I've ordered some hall effect sensors and some tiny magnets, and I'm going to try to 3D print a gauge pointer with an integrated boss on the rear face for the magnet to superglued in place and then mount the hall effect sensor behind the gauge panel (3mm opal acrylic) and hope the magnet is strong enough to trip the sensor... The magnets are neodymium with a pull of 0.1kg, so I'm expecting this is enough... Will keep you posted on how I get on...
  4. Hi, I'm researching the use of stepper motors for gauges. I've built an A-10 engine panel using servo's... It works, but is a bit clunky as I've bought cheap servo's... I have some x27.168's on the way and looking through the code it shouldn't be difficult to get them to work, but I'm stumped how they are zero'd? As a stepper motor, there is no positional feedback, so how does the code find zero? Lot's of people seem to use the code WarHog wrote with Ian, and there are a few lines in the code regarding zeroing on start up, but I'm struggling to understand how it works. Can someone enlighten me, please? It looks like it is reading a zero from another pin, but I'm not clear how this is working? Is a micro switch used, or perhaps an optical switch sending the pin to ground? Thanks in advance... #define DCSBIOS_IRQ_SERIAL #include <AccelStepper.h> #include "DcsBios.h" struct StepperConfig { unsigned int maxSteps; unsigned int acceleration; unsigned int maxSpeed; }; class Vid60Stepper : public DcsBios::Int16Buffer { private: AccelStepper& stepper; StepperConfig& stepperConfig; inline bool zeroDetected() { return digitalRead(irDetectorPin) == 1; } unsigned int (*map_function)(unsigned int); unsigned char initState; long currentStepperPosition; long lastAccelStepperPosition; unsigned char irDetectorPin; long zeroOffset; bool movingForward; bool lastZeroDetectState; long normalizeStepperPosition(long pos) { if (pos < 0) return pos + stepperConfig.maxSteps; if (pos >= stepperConfig.maxSteps) return pos - stepperConfig.maxSteps; return pos; } void updateCurrentStepperPosition() { // adjust currentStepperPosition to include the distance our stepper motor // was moved since we last updated it long movementSinceLastUpdate = stepper.currentPosition() - lastAccelStepperPosition; currentStepperPosition = normalizeStepperPosition(currentStepperPosition + movementSinceLastUpdate); lastAccelStepperPosition = stepper.currentPosition(); } public: Vid60Stepper(unsigned int address, AccelStepper& stepper, StepperConfig& stepperConfig, unsigned char irDetectorPin, long zeroOffset, unsigned int (*map_function)(unsigned int)): Int16Buffer(address), stepper(stepper), stepperConfig(stepperConfig), irDetectorPin(irDetectorPin), zeroOffset(zeroOffset), map_function(map_function), initState(0), currentStepperPosition(0), lastAccelStepperPosition(0) { } virtual void loop() { if (initState == 0) { // not initialized yet pinMode(irDetectorPin, INPUT); stepper.setMaxSpeed(stepperConfig.maxSpeed); stepper.setSpeed(1000); initState = 1; } if (initState == 1) { // move off zero if already there so we always get movement on reset // (to verify that the stepper is working) if (zeroDetected()) { stepper.runSpeed(); } else { initState = 2; } } if (initState == 2) { // zeroing if (!zeroDetected()) { stepper.runSpeed(); } else { stepper.setAcceleration(stepperConfig.acceleration); stepper.runToNewPosition(stepper.currentPosition() + zeroOffset); // tell the AccelStepper library that we are at position zero stepper.setCurrentPosition(0); lastAccelStepperPosition = 0; // set stepper acceleration in steps per second per second // (default is zero) stepper.setAcceleration(stepperConfig.acceleration); lastZeroDetectState = true; initState = 3; } } if (initState == 3) { // running normally // recalibrate when passing through zero position bool currentZeroDetectState = zeroDetected(); if (!lastZeroDetectState && currentZeroDetectState && movingForward) { // we have moved from left to right into the 'zero detect window' // and are now at position 0 lastAccelStepperPosition = stepper.currentPosition(); currentStepperPosition = normalizeStepperPosition(zeroOffset); } else if (lastZeroDetectState && !currentZeroDetectState && !movingForward) { // we have moved from right to left out of the 'zero detect window' // and are now at position (maxSteps-1) lastAccelStepperPosition = stepper.currentPosition(); currentStepperPosition = normalizeStepperPosition(stepperConfig.maxSteps + zeroOffset); } lastZeroDetectState = currentZeroDetectState; if (hasUpdatedData()) { // convert data from DCS to a target position expressed as a number of steps long targetPosition = (long)map_function(getData()); updateCurrentStepperPosition(); long delta = targetPosition - currentStepperPosition; // if we would move more than 180 degree counterclockwise, move clockwise instead if (delta < -((long)(stepperConfig.maxSteps/2))) delta += stepperConfig.maxSteps; // if we would move more than 180 degree clockwise, move counterclockwise instead if (delta > (stepperConfig.maxSteps/2)) delta -= (long)stepperConfig.maxSteps; movingForward = (delta >= 0); // tell AccelStepper to move relative to the current position stepper.move(delta); } stepper.run(); } } }; /* modify below this line */ /* define stepper parameters multiple Vid60Stepper instances can share the same StepperConfig object */ struct StepperConfig stepperConfig = { 400, // maxSteps 2200, // maxSpeed 1000 // acceleration }; // define AccelStepper instance AccelStepper stepper(AccelStepper::DRIVER, 9, 8); // define Vid60Stepper class that uses the AccelStepper instance defined in the line above // v-- arbitrary name Vid60Stepper alt100ftPointer(0x107e, // address of stepper data stepper, // name of AccelStepper instance stepperConfig, // StepperConfig struct instance 11, // IR Detector Pin (must be HIGH in zero position) 0, // zero offset [](unsigned int newValue) -> unsigned int { /* this function needs to map newValue to the correct number of steps */ return map(newValue, 0, 65535, 0, stepperConfig.maxSteps-1); }); void setup() { DcsBios::setup(); pinMode(13, OUTPUT); } void loop() { PORTB |= (1<<5); PORTB &= ~(1<<5); DcsBios::loop();
  5. I should also express my thanks for the time and effort you've put into DCS Bios! Without it, I wouldn't know where to start, so kudos for the hard work :thumbup:
  6. Thanks for your reply, Ian. I've tried the latest version of DCS Bios and it seems to be working ok now... I've not figured out the hub yet (I tried using it, after following the setup instructions, but it didn't work). I guess I missed a step of something, but I should probably be able to figure it out! I used the web interface and I could control the ILS volume with it, so rebooted with my panels, using socat and it works...! No other change in my code or anything. It's working and I can live with it being a mystery for now! Thanks, I guess installing the new version of DCS bios sorted it? I ran the installer but didn't copy any script files, and the files for DCS bios in the saved games script folder is still date stamped from when I installed it ages ago (version 0.7 or something). Cheers, Dave.
  7. Some pins on some of the boards can have funny interactions if the pins have multiple functions; For example, pin D13 on a nano has the LED like the UNO, but the way this pin is switched between the nano and UNO is different, leading to issues using this pin as an input on a nano. Other problems might be if you are using a pin that is used for serial communication too. I've noticed issues using pins D0 and D1 on some boards for switches, as it can interfere with the communication bus, but I've not figured it out. If in doubt, try your switch on another pin of the Arduino to see if it behaves properly....
  8. Hi, I've nearly finished my cockpit controls, and I'm debugging some issues. I have an problem with controlling the A10-C ILS volume with a rotary encoder.... I've found pots to be noisy on my earlier panels, so opted for rotary's for later panels. The TACAN volume works fine on a rotary, and using the DCS Bios Debug tool, I can see the code being output, but the ILS volume isn't actually changing in-game... It's probably something really simple I've overlooked, but I'm hoping a second (or third, or fourth) will see what I can't! Here's my code, ILS volume is on line 78. Thanks in advance. /* Tell DCS-BIOS to use a serial connection and use the default Arduino Serial library. This will work on the vast majority of Arduino-compatible boards, but you can get corrupted data if you have too many or too slow outputs (e.g. when you have multiple character displays), because the receive buffer can fill up if the sketch spends too much time updating them. If you can, use the IRQ Serial connection instead. */ #define DCSBIOS_IRQ_SERIAL #include <DcsBios.h> #include <SPI.h> #include <Wire.h> #include <grove_alphanumeric_display.h> /*Alpha Numeric Display Setup*/ Seeed_Digital_Tube tube; /* TACAN */ DcsBios::RotaryEncoder tacan10("TACAN_10", "DEC", "INC", A2, A3); DcsBios::RotaryEncoder tacan1("TACAN_1", "DEC", "INC", A5, A6); DcsBios::RotaryEncoder tacanMode("TACAN_MODE", "DEC", "INC", A0, A1); DcsBios::RotaryEncoder tacanVol("TACAN_VOL", "-6500", "+6500", A7, A8); DcsBios::ActionButton tacanXyToggle("TACAN_XY", "TOGGLE", A4); /*Tacan Display*/ void onTacanModeChange(unsigned int newValue) { if (newValue==0) { digitalWrite(15, HIGH); digitalWrite(16, LOW); digitalWrite(17, LOW); digitalWrite(18, LOW); digitalWrite(19, LOW); } if (newValue==1) { digitalWrite(15, LOW); digitalWrite(16, HIGH); digitalWrite(17, LOW); digitalWrite(18, LOW); digitalWrite(19, LOW); } if (newValue==2) { digitalWrite(15, LOW); digitalWrite(16, LOW); digitalWrite(17, HIGH); digitalWrite(18, LOW); digitalWrite(19, LOW); } if (newValue==3) { digitalWrite(15, LOW); digitalWrite(16, LOW); digitalWrite(17, LOW); digitalWrite(18, HIGH); digitalWrite(19, LOW); } if (newValue==4) { digitalWrite(15, LOW); digitalWrite(16, LOW); digitalWrite(17, LOW); digitalWrite(18, LOW); digitalWrite(19, HIGH); } } DcsBios::IntegerBuffer tacanModeBuffer(0x1168, 0x000e, 1, onTacanModeChange); void onTacanChannelChange(char* newValue) { tube.clearBuf(); tube.displayString(newValue,0); tube.display(); } DcsBios::StringBuffer<4> tacanChannelBuffer(0x1162, onTacanChannelChange); /* ILS */ DcsBios::RotaryEncoder ilsMhz("ILS_MHZ", "DEC", "INC", A10, A9); DcsBios::RotaryEncoder ilsKhz("ILS_KHZ", "DEC", "INC", A12, A11); DcsBios::ActionButton ilsPwrToggle("ILS_PWR", "TOGGLE", A15); DcsBios::RotaryEncoder ilsVol("ILS_VOL", "-6500", "+6500", A14, A13); DcsBios::LED ilsPwr1(0x1168, 0x0010, 36); /* CMSP */ DcsBios::RotaryEncoder cmspBrt("CMSP_BRT", "-3200", "+3200", 22, 24); DcsBios::Switch3Pos cmspDisp("CMSP_DISP", 51, 28); DcsBios::Switch3Pos cmspJmr("CMSP_JMR", 50, 52); DcsBios::Switch3Pos cmspMws("CMSP_MWS", 46, 48); DcsBios::Switch3Pos cmspRwr("CMSP_RWR", 47, 49); DcsBios::Switch2Pos cmspJtsn("CMSP_JTSN", 53); DcsBios::Switch2Pos cmspRtn("CMSP_RTN", 26); DcsBios::Switch3Pos cmspUpdn("CMSP_UPDN", 32, 30); /* CMSC */ DcsBios::Switch2Pos cmscJmr("CMSC_JMR", 35); DcsBios::Switch2Pos cmscMws("CMSC_MWS", 27); DcsBios::Switch2Pos cmscPri("CMSC_PRI", 29); DcsBios::Switch2Pos cmscSep("CMSC_SEP", 31); /* Electrical Power */ DcsBios::Switch2Pos eppBatteryPwr("EPP_BATTERY_PWR", 37); DcsBios::Switch3Pos eppInverter("EPP_INVERTER", 41, 39); DcsBios::Switch2Pos eppApuGenPwr("EPP_APU_GEN_PWR", 43); DcsBios::Switch2Pos eppAcGenPwrL("EPP_AC_GEN_PWR_L", 44); DcsBios::Switch2Pos eppAcGenPwrR("EPP_AC_GEN_PWR_R", 45); /* MISC */ DcsBios::Switch2Pos oxySupply("OXY_SUPPLY", 42); DcsBios::Switch2Pos aapEgipwr("AAP_EGIPWR", 40); DcsBios::Switch2Pos aapCdupwr("AAP_CDUPWR", 38); void setup() { DcsBios::setup(); pinMode(15, OUTPUT); pinMode(16, OUTPUT); pinMode(17, OUTPUT); pinMode(18, OUTPUT); pinMode(19, OUTPUT); digitalWrite(15, HIGH); tube.setTubeType(TYPE_4,TYPE_4_DEFAULT_I2C_ADDR); tube.setBrightness(6); tube.setBlinkRate(BLINK_OFF); tube.clearBuf(); tube.setTubeSingleNum(FIRST_TUBE,8); tube.setTubeSingleNum(SECOND_TUBE,8); tube.setTubeSingleNum(THIRD_TUBE,8); tube.setTubeSingleChar(FOURTH_TUBE,'X'); tube.setPoint(false,false); tube.display(); delay(1000); tube.clearBuf(); tube.display(); } void loop() { DcsBios::loop(); delay(5); }
×
×
  • Create New...