Electronics > Projects, Designs, and Technical Stuff

Nerfo meter - measuring the speed of Nerf darts - final setup

<< < (5/6) > >>

HendriXML:
One thing that happened while using longer leads and my bench power supply was some instability with the comparator output. Because it drives a LED the VCC would drop a bit which would lead to a drop in the ref voltage. This would stop after a few ups and downs because of the hysteresis. Using my 5V regulator this has not happened, it may react just a bit faster on a voltage drop or just the shorter leads make the difference.

However I want to process the signal digitally and like it to behave as designed. Adding C5 on the ref voltage dividers makes it behave just fine.

HendriXML:
I found an AT Mega2560 Pro board on Ali Express, which is a lot smaller and can be soldered on more easily. This one has an Cristal resonator, so it should be pretty accurate. Many times more that the distance part of the speed measurement.

Timing on the AT Mega is discussed in this thread in more depth. (Timers and interrupts, TCXO's)
https://www.eevblog.com/forum/projects/measuring-time-span-very-precise/?all#lastPost

Essentially the script uses 2 timers: timer 4 and timer 5. Each of them has a pin which when triggered saves the current counter to a location and schedules an interrupt.

The counters are made in sync. So one pin can be used to get the starting counter (photo gate A), the other one the ending counter (photo gate B). Some info from the ATMega2560 datasheet that is of importance:

Timer 4
Capture pin: PL0
Board: Pin 49 = PORTL0 (ICP4)

Timer 5
Capture pin: PL1
Board: Pin 48 = PORTL1 (ICP5)

Timer n
Capture register: ICRn
Count register: TCNTn
Control register B: TCCRnB
Bit 7: Noise canceling
Bit 6: Input capture edge select: 0: falling; 1: Rising
Bit 2..0: Input clock source: 000: none; 001: 1x; 110 External clock falling
Compare registers OCRnA, OCRnB, OCRnC
TIMSKn: Interrupt mask register
Bit 5: Capture interrupt: 0: off; 1: on;
Bit 3: Output compare C interrupt: 0: off; 1: on;
Bit 2: Output compare B interrupt: 0: off; 1: on;
Bit 1: Output compare A interrupt: 0: off; 1: on;
Bit 0: Overflow interrupt: 0: off; 1: on;

Global registers
GTCCR: General timer counter register - halt timers
Bit 7: Synchronization mode: 1: sync mode; 0: normal mode;
Bit 1: PRASY: ?
Bit 0: PSRSYNC: Reset for synchronische timers


--- Code: ---#include <U8glib.h>
#include <HardwareSerial.h>

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NO_ACK);

enum  CaptureState_t { csStart, csCapture4, csErrorCapture5 };
enum  DisplayState_t { dsUnused, dsUninitialized, dsInitialized };

typedef uint16_t sequence_t;
typedef uint64_t counter_t;

struct Measurement_t
{
  sequence_t Sequence;
  counter_t DiffCount;
  bool Active;
  Measurement_t* Previous;
  Measurement_t* Next;
};

struct DisplayMeasurements_t
{
  sequence_t Sequence;
  counter_t DiffCount;
  DisplayState_t State;
  char DisplayData[19];
  // 0001: xxx.xxx km/h
};



volatile counter_t TimerCycles4A = 0;
volatile counter_t TimerCycles4B = 0;
volatile counter_t TimerCycles4O = 0;
volatile counter_t TimerCycles5A = 0;
volatile counter_t TimerCycles5B = 0;
volatile counter_t TimerCycles5O = 0;
volatile counter_t CaptureCount4;
volatile counter_t CaptureCountDiff;
volatile uint16_t DisplayUpdateSequence = 0;
volatile CaptureState_t CaptureState = csStart;

sequence_t CurrentMeasurementSequence = 1;
uint16_t PrevDisplayUpdateSequence = 0;
CaptureState_t PrevCaptureState = csStart;
double MilliSecondsTickPeriod = 1000.0 / 16000000.0;

const uint16_t HighCountA = 0xFFFF / 3;
const uint16_t HighCountB = 0xFFFF / 3 * 2;
const uint8_t BufferCount = 10;
const uint8_t MeasurementDisplayCount = 5;

Measurement_t MeasurementBuffers[BufferCount];
Measurement_t* Head = &MeasurementBuffers[0];
DisplayMeasurements_t  MeasurementDisplayBuffers[MeasurementDisplayCount];


ISR(TIMER4_COMPA_vect)
{
  TimerCycles4A++;
}

ISR(TIMER4_COMPB_vect)
{
  TimerCycles4B++;
}

ISR(TIMER4_OVF_vect)
{
  TimerCycles4O++;
}

ISR(TIMER4_CAPT_vect)
{
  DisplayUpdateSequence++;
  uint16_t CaptureCount = ICR4;
  CaptureState = csCapture4;
  // Using the cycle count that is linked to the captured counter
  if (CaptureCount < HighCountA)
    CaptureCount4 = (TimerCycles4B << 16) + CaptureCount;
  else if (CaptureCount < HighCountB)
    CaptureCount4 = (TimerCycles4O << 16) + CaptureCount;
  else
    CaptureCount4 = ((TimerCycles4A - 1) << 16) + CaptureCount;
}



ISR(TIMER5_COMPA_vect)
{
  TimerCycles5A++;
}

ISR(TIMER5_COMPB_vect)
{
  TimerCycles5B++;
}

ISR(TIMER5_OVF_vect)
{
  TimerCycles5O++;
}

ISR(TIMER5_CAPT_vect)
{
  uint16_t CaptureCount = ICR5;
  uint64_t CaptureCount5;
  if (CaptureState != csCapture4)
  {
    CaptureState = csErrorCapture5;
    return;
  }
  // Using the cycle count that is linked to the captured counter
  if (CaptureCount < HighCountA)
    CaptureCount5 = (TimerCycles5B << 16) + CaptureCount;
  else if (CaptureCount < HighCountB)
    CaptureCount5 = (TimerCycles5O << 16) + CaptureCount;
  else
    CaptureCount5 = ((TimerCycles5A - 1) << 16) + CaptureCount;

  CaptureState = csStart;
  DisplayUpdateSequence++;

  Head = Head->Previous;
  Head->Sequence = CurrentMeasurementSequence;
  Head->DiffCount = CaptureCount5 - CaptureCount4;
  Head->Active = true;
  CurrentMeasurementSequence++;
}


// The setup() function runs once each time the micro-controller starts
void setup()
{
  //  Serial.begin(115200);
  //  Serial.println("Setup started");
  noInterrupts();

  // pinMode(48, INPUT);
  // pinMode(49, INPUT);

   // Halt timers
  GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC);

  // enable capture, compare a, compare b, and overflow interrupts
  TIMSK4 = (1 << ICIE4) | (1 << OCIE4A) | (1 << OCIE4B) | (1 << TOIE4);
  TIMSK5 = (1 << ICIE5) | (1 << OCIE5A) | (1 << OCIE5B) | (1 << TOIE5);

  TCCR4A = 0;
  TCCR5A = 0;

  // no noise cancelling, falling edge, no prescaling
  TCCR4B = (0 << ICNC4) | (0 << ICES4) | ((0 << CS42) | (0 << CS41) | (1 << CS40));
  TCCR5B = (0 << ICNC5) | (0 << ICES5) | ((0 << CS52) | (0 << CS51) | (1 << CS50));

  OCR4A = HighCountA;
  OCR5A = HighCountA;

  OCR4B = HighCountB;
  OCR5B = HighCountB;

  TCNT4 = 0;
  TCNT5 = 0;

  // just to be sure..
  TimerCycles4A = 0;
  TimerCycles4B = 0;
  TimerCycles4O = 0;
  TimerCycles5A = 0;
  TimerCycles5B = 0;
  TimerCycles5O = 0;

  // Continue timers
  GTCCR = 0;

  for (uint16_t i = 1; i < BufferCount; i++)
  {
    MeasurementBuffers[i - 1].Next = &MeasurementBuffers[i];
    MeasurementBuffers[i].Previous = &MeasurementBuffers[i - 1];
  }
  MeasurementBuffers[0].Previous = &MeasurementBuffers[BufferCount - 1];
  MeasurementBuffers[BufferCount - 1].Next = &MeasurementBuffers[0];
  MeasurementBuffers[0].Active = false;
  for (uint16_t i = 0; i < MeasurementDisplayCount; i++)
  {
    MeasurementDisplayBuffers[i].State = dsUnused;
    MeasurementDisplayBuffers[i].DisplayData[4] = ':';
    MeasurementDisplayBuffers[i].DisplayData[5] = ' ';

    MeasurementDisplayBuffers[i].DisplayData[14] = 'k';
    MeasurementDisplayBuffers[i].DisplayData[15] = 'm';
    MeasurementDisplayBuffers[i].DisplayData[16] = '/';
    MeasurementDisplayBuffers[i].DisplayData[17] = 'h';
    MeasurementDisplayBuffers[i].DisplayData[18] = 0;
  }

  interrupts();
  u8g.firstPage();
  do {
  } while (u8g.nextPage());
  DisplayUpdateSequence++;
  //  Serial.println("Setup is ready");
}


// Add the main program code into the continuous loop() function
void loop()
{
  // these must be constistent
  noInterrupts();
  uint16_t LocDisplayUpdateSequence = DisplayUpdateSequence;
  CaptureState_t LocCaptureState = CaptureState;
  Measurement_t* RunningMeasurement = Head;
  for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
  {
    if (RunningMeasurement->Active)
    {
      if (!((MeasurementDisplayBuffers[i].State == dsInitialized) && (MeasurementDisplayBuffers[i].Sequence == RunningMeasurement->Sequence)))
      {
        MeasurementDisplayBuffers[i].State = dsUninitialized;
        MeasurementDisplayBuffers[i].DiffCount = RunningMeasurement->DiffCount;
        MeasurementDisplayBuffers[i].Sequence = RunningMeasurement->Sequence;
      }
    }
    else
      MeasurementDisplayBuffers[i].State = dsUnused;
    RunningMeasurement = RunningMeasurement->Next;
  }
  interrupts();

  if ((LocCaptureState != PrevCaptureState) || (LocDisplayUpdateSequence != PrevDisplayUpdateSequence))
  {
    for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
    {
      if (MeasurementDisplayBuffers[i].State == dsUninitialized)
      {
        uint16_t Seq = MeasurementDisplayBuffers[i].Sequence;
        MeasurementDisplayBuffers[i].DisplayData[3] = '0' + Seq % 10;

        Seq = Seq / 10;
        if (Seq == 0)
          MeasurementDisplayBuffers[i].DisplayData[2] = ' ';
        else
          MeasurementDisplayBuffers[i].DisplayData[2] = '0' + Seq % 10;

        Seq = Seq / 10;
        if (Seq == 0)
          MeasurementDisplayBuffers[i].DisplayData[1] = ' ';
        else
          MeasurementDisplayBuffers[i].DisplayData[1] = '0' + Seq % 10;

        Seq = Seq / 10;
        if (Seq == 0)
          MeasurementDisplayBuffers[i].DisplayData[0] = ' ';
        else
          MeasurementDisplayBuffers[i].DisplayData[0] = '0' + Seq % 10;

        double MilliSecs = MeasurementDisplayBuffers[i].DiffCount * MilliSecondsTickPeriod;
        double Speed = 150 * 3.6 / MilliSecs;
        dtostrf(Speed, 7, 3, &MeasurementDisplayBuffers[i].DisplayData[6]);

        MeasurementDisplayBuffers[i].DisplayData[13] = ' ';
        MeasurementDisplayBuffers[i].State = dsInitialized;
      }
    }
    u8g.setFont(u8g_font_profont11);
    u8g.firstPage();
    do {
      switch (LocCaptureState)
      {
      case csStart:
      {
        u8g.drawStr(0, 8, "Shoot!!");
        break;
      }
      case csCapture4:
      {
        //Serial.println();
 //       Serial.println("Timer running..");
        u8g.drawStr(0, 8, "Timer started");
        break;
      }
      case csErrorCapture5:
      {
        //       Serial.println("Timer hasn't  started!!");
        u8g.drawStr(0, 8, "Timer hasn't  started!!");
        break;
      }
      default:
        u8g.drawStr(0, 8, "??");
        //Serial.println("??");
        break;
      }
      uint8_t LineTop = 20;
      for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
      {
        if (MeasurementDisplayBuffers[i].State == dsInitialized)
        {
          u8g.drawStr(4, LineTop, MeasurementDisplayBuffers[i].DisplayData);
          LineTop += 10;
        }
      }
    } while (u8g.nextPage());
    PrevCaptureState = LocCaptureState;
    PrevDisplayUpdateSequence = LocDisplayUpdateSequence;
  }
}


--- End code ---

HendriXML:
The script uses circular measurement buffers, because only 5 can be shown at a time I used a max of 10 measurements, but in theory many more measurements can be stored in FIFO order.

Each measurement is prepared (initialized) before going in to a display loop. To keep things fast that is essential, because anything in that loop is done multiple time when drawing "pages" (which mean parts of the screen buffer). (Thus saving buffer memory)

Because the main loop is light weight the responsiveness is kept high.

HendriXML:
I updated the code to use a more recent library U8g2lib. Also I had some stability issues with the display, turning the busclock down seems to solve that.

This library can now update an entire screenbuffer at once (consuming more memory).


--- Code: --- #include <U8g2lib.h>
 #define DEBUG 0
 #if DEBUG
 #define SERIALOUT;
 #endif
 #ifdef SERIALOUT
 #include <HardwareSerial.h>
 #endif
 #ifdef U8X8_HAVE_HW_I2C
 #include <Wire.h>
 #endif

 #ifdef SERIALOUT
 #define DEBUG_PRINT(x)  Serial.println (x)

 #define DEBUG_PRINT(x)
 #endif

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

enum  CaptureState_t { csStart, csCapture4, csErrorCapture5 };
enum  DisplayState_t { dsUnused, dsUninitialized, dsInitialized };

typedef uint16_t sequence_t;
typedef uint64_t counter_t;

struct Measurement_t
{
  sequence_t Sequence;
  counter_t DiffCount;
  bool Active;
  Measurement_t* Previous;
  Measurement_t* Next;
};

struct DisplayMeasurements_t
{
  sequence_t Sequence;
  counter_t DiffCount;
  DisplayState_t State;
  char DisplayData[19];
  // 0001: xxx.xxx km/h
};



volatile counter_t TimerCycles4A = 0;
volatile counter_t TimerCycles4B = 0;
volatile counter_t TimerCycles4O = 0;
volatile counter_t TimerCycles5A = 0;
volatile counter_t TimerCycles5B = 0;
volatile counter_t TimerCycles5O = 0;
volatile counter_t CaptureCount4;
volatile counter_t CaptureCountDiff;
volatile uint16_t DisplayUpdateSequence = 0;
volatile CaptureState_t CaptureState = csStart;

sequence_t CurrentMeasurementSequence = 1;
uint16_t PrevDisplayUpdateSequence = 0;
CaptureState_t PrevCaptureState = csStart;
double MilliSecondsTickPeriod = 1000.0 / 16000000.0;

const uint16_t HighCountA = 0xFFFF / 3;
const uint16_t HighCountB = 0xFFFF / 3 * 2;
const uint8_t BufferCount = 20;
const uint8_t MeasurementDisplayCount = 5;

Measurement_t MeasurementBuffers[BufferCount];
Measurement_t* Head = &MeasurementBuffers[0];
DisplayMeasurements_t  MeasurementDisplayBuffers[MeasurementDisplayCount];


ISR(TIMER4_COMPA_vect)
{
  TimerCycles4A++;
}

ISR(TIMER4_COMPB_vect)
{
  TimerCycles4B++;
}

ISR(TIMER4_OVF_vect)
{
  TimerCycles4O++;
}

ISR(TIMER4_CAPT_vect)
{
  DisplayUpdateSequence++;
  uint16_t CaptureCount = ICR4;
  CaptureState = csCapture4;
  // Using the cycle count that is linked to the captured counter
  if (CaptureCount < HighCountA)
    CaptureCount4 = (TimerCycles4B << 16) + CaptureCount;
  else if (CaptureCount < HighCountB)
    CaptureCount4 = (TimerCycles4O << 16) + CaptureCount;
  else
    CaptureCount4 = ((TimerCycles4A - 1) << 16) + CaptureCount;
}



ISR(TIMER5_COMPA_vect)
{
  TimerCycles5A++;
}

ISR(TIMER5_COMPB_vect)
{
  TimerCycles5B++;
}

ISR(TIMER5_OVF_vect)
{
  TimerCycles5O++;
}

ISR(TIMER5_CAPT_vect)
{
  uint16_t CaptureCount = ICR5;
  uint64_t CaptureCount5;
  if (CaptureState != csCapture4)
  {
    CaptureState = csErrorCapture5;
    return;
  }
  // Using the cycle count that is linked to the captured counter
  if (CaptureCount < HighCountA)
    CaptureCount5 = (TimerCycles5B << 16) + CaptureCount;
  else if (CaptureCount < HighCountB)
    CaptureCount5 = (TimerCycles5O << 16) + CaptureCount;
  else
    CaptureCount5 = ((TimerCycles5A - 1) << 16) + CaptureCount;

  CaptureState = csStart;
  DisplayUpdateSequence++;

  Head = Head->Previous;
  Head->Sequence = CurrentMeasurementSequence;
  Head->DiffCount = CaptureCount5 - CaptureCount4;
  Head->Active = true;
  CurrentMeasurementSequence++;
}


// The setup() function runs once each time the micro-controller starts
void setup()
{
 #ifdef SERIALOUT
  Serial.begin(115200);
 #endif
  DEBUG_PRINT("Setup started");

  noInterrupts();

  pinMode(48, INPUT_PULLUP);
  pinMode(49, INPUT_PULLUP);

  // Halt timers
  GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC);

  // enable capture, compare a, compare b, and overflow interrupts
  TIMSK4 = (1 << ICIE4) | (1 << OCIE4A) | (1 << OCIE4B) | (1 << TOIE4);
  TIMSK5 = (1 << ICIE5) | (1 << OCIE5A) | (1 << OCIE5B) | (1 << TOIE5);

  TCCR4A = 0;
  TCCR5A = 0;

  // no noise cancelling, falling edge, no prescaling
  TCCR4B = (0 << ICNC4) | (0 << ICES4) | ((0 << CS42) | (0 << CS41) | (1 << CS40));
  TCCR5B = (0 << ICNC5) | (0 << ICES5) | ((0 << CS52) | (0 << CS51) | (1 << CS50));

  OCR4A = HighCountA;
  OCR5A = HighCountA;

  OCR4B = HighCountB;
  OCR5B = HighCountB;

  TCNT4 = 0;
  TCNT5 = 0;

  // just to be sure..
  TimerCycles4A = 0;
  TimerCycles4B = 0;
  TimerCycles4O = 0;
  TimerCycles5A = 0;
  TimerCycles5B = 0;
  TimerCycles5O = 0;

  // Continue timers
  GTCCR = 0;

  for (uint16_t i = 1; i < BufferCount; i++)
  {
    MeasurementBuffers[i - 1].Next = &MeasurementBuffers[i];
    MeasurementBuffers[i].Previous = &MeasurementBuffers[i - 1];
  }
  MeasurementBuffers[0].Previous = &MeasurementBuffers[BufferCount - 1];
  MeasurementBuffers[BufferCount - 1].Next = &MeasurementBuffers[0];
  MeasurementBuffers[0].Active = false;
  for (uint16_t i = 0; i < MeasurementDisplayCount; i++)
  {
    MeasurementDisplayBuffers[i].State = dsUnused;
    MeasurementDisplayBuffers[i].DisplayData[4] = ':';

    MeasurementDisplayBuffers[i].DisplayData[14] = 'k';
    MeasurementDisplayBuffers[i].DisplayData[15] = 'm';
    MeasurementDisplayBuffers[i].DisplayData[16] = '/';
    MeasurementDisplayBuffers[i].DisplayData[17] = 'h';
    MeasurementDisplayBuffers[i].DisplayData[18] = 0;
  }

  interrupts();
  //u8g2.firstPage();
  //do
  //{
  //} while (u8g2.nextPage());

  DisplayUpdateSequence++;
  u8g2.setBusClock(50000);
  //u8g2.setBusClock(200000);
  u8g2.begin();
  DEBUG_PRINT("Setup is ready");

}


// Add the main program code into the continuous loop() function
void loop()
{
  // these must be constistent
  noInterrupts();
  uint16_t LocDisplayUpdateSequence = DisplayUpdateSequence;
  CaptureState_t LocCaptureState = CaptureState;
  Measurement_t* RunningMeasurement = Head;
  for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
  {
    if (RunningMeasurement->Active)
    {
      if (!((MeasurementDisplayBuffers[i].State == dsInitialized) && (MeasurementDisplayBuffers[i].Sequence == RunningMeasurement->Sequence)))
      {
        MeasurementDisplayBuffers[i].State = dsUninitialized;
        MeasurementDisplayBuffers[i].DiffCount = RunningMeasurement->DiffCount;
        MeasurementDisplayBuffers[i].Sequence = RunningMeasurement->Sequence;
      }
    }
    else
      MeasurementDisplayBuffers[i].State = dsUnused;
    RunningMeasurement = RunningMeasurement->Next;
  }
  interrupts();

  if ((LocCaptureState != PrevCaptureState) || (LocDisplayUpdateSequence != PrevDisplayUpdateSequence))
  {
    for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
    {
      if (MeasurementDisplayBuffers[i].State == dsUninitialized)
      {
        uint16_t Seq = MeasurementDisplayBuffers[i].Sequence;
        MeasurementDisplayBuffers[i].DisplayData[3] = '0' + Seq % 10;

        Seq = Seq / 10;
        if (Seq == 0)
          MeasurementDisplayBuffers[i].DisplayData[2] = ' ';
        else
          MeasurementDisplayBuffers[i].DisplayData[2] = '0' + Seq % 10;

        Seq = Seq / 10;
        if (Seq == 0)
          MeasurementDisplayBuffers[i].DisplayData[1] = ' ';
        else
          MeasurementDisplayBuffers[i].DisplayData[1] = '0' + Seq % 10;

        Seq = Seq / 10;
        if (Seq == 0)
          MeasurementDisplayBuffers[i].DisplayData[0] = ' ';
        else
          MeasurementDisplayBuffers[i].DisplayData[0] = '0' + Seq % 10;

        double MilliSecs = MeasurementDisplayBuffers[i].DiffCount * MilliSecondsTickPeriod;
        double Speed = 150 * 3.6 / MilliSecs;
        dtostrf(Speed, 8, 3, &MeasurementDisplayBuffers[i].DisplayData[5]);

        MeasurementDisplayBuffers[i].DisplayData[13] = ' ';
        MeasurementDisplayBuffers[i].State = dsInitialized;
      }
    }
    u8g2.setFont(u8g_font_profont11);
    u8g2.clearBuffer();
    //u8g2.firstPage();
    //do {
    switch (LocCaptureState)
    {
    case csStart:
    {
      u8g2.drawStr(0, 8, "Shoot!!");
      DEBUG_PRINT("Shoot");
      break;
    }
    case csCapture4:
    {
      DEBUG_PRINT("Timer started");
      u8g2.drawStr(0, 8, "Timer started");
      break;
    }
    case csErrorCapture5:
    {
      DEBUG_PRINT("Timer hasn't  started!!");

      u8g2.drawStr(0, 8, "Timer hasn't  started!!");
      break;
  }
    default:
      u8g2.drawStr(0, 8, "??");
      DEBUG_PRINT("??");
      break;
}
    uint8_t LineTop = 20;
    for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
    {
      if (MeasurementDisplayBuffers[i].State == dsInitialized)
      {
        u8g2.drawStr(4, LineTop, MeasurementDisplayBuffers[i].DisplayData);
        DEBUG_PRINT(MeasurementDisplayBuffers[i].DisplayData);
        LineTop += 10;
      }
    }
    //} while (u8g2.nextPage());
    u8g2.sendBuffer();
    PrevCaptureState = LocCaptureState;
    PrevDisplayUpdateSequence = LocDisplayUpdateSequence;
  }
}


--- End code ---

HendriXML:
To finalize the project I printed an enclosure that has the mega board and the display mounted.

I'm glad that printing the 45 deg display mount went ok.

For mounting the board I used the board photo as a reference, which resulted in accurate screw holes locations. The hex screw have heat shrink added to them, to isolate the screw from the board, not to harm it and to give a good fit for the 2 rather large holes.

Under the left-down location of the atmega chip, there's another stand, but without a screw. The board is mounted in such way that every pin could have been used.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod