Wren'sTech

Projects and Technical Ramblings

20140901_214133


Leave a comment

Megameter Gets a New(ish) Pair Of Shoes

I’m doing quite a bit of travelling at the moment, and the megameter is the kind of project I can stuff in my rucksack and take with me.

It would be nice to have some sort of case to keep all those vulnerable electronic doodads covered up; as luck would have it, the case for an old pack of Beano playing cards is the exact same size as the PCB.

20140901_213914

I used a spare soldering iron tip to make a hole and slot for the LED and logic header, and then rolled a very thin blu-tac sausage to stick the edge of the case to the PCB. Surprisingly, it’s rock-solid; blu-tac sticks very well to solder mask, and the case is near-impossible to prise off.

That should keep things safe for now!


Leave a comment

Measure ALL the things

My multimeter has been all nicely soldered together for a while now, but it didn’t actually do anything. Doing something is obviously an important criterion for actually being a multimeter, so this was an issue that I needed to address.

I need to measure voltage, current, capacitance and resistance, over large ranges and with reasonable precision. I’m doing all of this with the AVR’s 10 bit successive-approximation analogue-to-digital converter (ADC), although I’m getting 12 bits of precision out of it by oversampling.

I’ll post the schematic here – you might want to keep it open in another tab, because I’ll refer back to it. A lot.

Voltage

Voltage was the first on the chopping block. To get a decent range, I used the battery supply voltage as the reference for the ADC; this means that the converter measures the scaled voltage as a fraction of the battery voltage, between 0 and 100%.

The problem with this is that the battery voltage is by nature not constant! I needed to measure the battery voltage. To do this, I delved deep into the ancient and terrible tome that is the AVR datasheet.

Pictured: the first copy of the AVR datasheet. Printed on human skin, blood for ink

The ATmega32PA (the microcontroller on my meter) has a rarely-used but very useful feature: an internal bandgap voltage reference. In principal it’s similar to an LED and a current source of a few microamps. What this does is give me a known voltage of 1.1V: if I use the ADC to measure this reference, as a fraction of the battery voltage, I can then calculate the battery voltage. This gives me a transfer standard with which to measure everything else.

Reading the datasheet (page 255) I eventually came up with the magical incantation which makes this measurement work:

#define VIN_BANDGAP 0x1e
uint16_t get_adc_10bit_fast(uint8_t pin)
{
    ADMUX = 0x40 | pin; // AREF = VCC
    ADCSRA = 0x87;      // Enabled, clk/64 = 125kHz
    ADCSRA |= 0x40;
    while (ADCSRA & 0x40);
    uint8_t result = ADCL;
    return result | (((uint16_t)ADCH) << 8);
}

uint16_t get_battery_voltage()
{
    ADMUX = 0x5e;   // AREF = VCC, input = 1.1v bandgap reference
    ADCSRA = 0x86;  // ADC enabled, don't start conversion yet, auto trigger off, no interrupt, ck/128 = 62.5 kHz
    _delay_us(500);
    uint32_t result = 0;
    for (int i = 16; i; --i)
    {
        result += get_adc_10bit_fast(VIN_BANDGAP);
    }
    return 4538487 / result;   // = 1024 (number of ADC steps) * 1.1 (bandgap voltage) * 16 (number of readings) * 256 (fxp88 fixed point)
}

Taking 16 readings and averaging them increases the effective number of bits by two, because it increases the signal-to-noise ratio by four. (See: Wikipedia)

We then divide a big integer constant by this sum, and the result is the battery voltage in an 8.8 fixed point format. The exact value of this constant is not quite the one described in the comments: it varies slightly with each chip, and was derived by cross-referencing the value from this function with an actual measurement of the battery voltage. Sorted.

The 0.5ms delay is to allow the bandgap reference (which has a very high output impedance) to charge the AVR’s internal capacitance to the correct value – not giving it time to settle led to a very peculiar problem. More on this later!

We can now actually measure the voltage.

 

#define PORT_VATTEN PORTA
#define DDR_VATTEN DDRA
#define VATTEN_1M 0x20
#define VATTEN_100k 0x10
#define VATTEN_5k 0x04
#define VATTEN_ALL (VATTEN_1M | VATTEN_100k | VATTEN_5k)

#define VIN_V 6

uint16_t get_adc_12bit(uint8_t pin)
{
    uint16_t result = 0;
    for (uint8_t i = 16; i; --i)
    {
        result += get_adc_10bit_fast(pin);
    }
    return result >> 2;
}

uint8_t voltage_range = 0;
void set_voltage_range(uint8_t range)
{
    uint8_t range_attens[] = {VATTEN_1M, VATTEN_100k, VATTEN_5k};
    DDR_VATTEN |= VATTEN_ALL;
    PORT_VATTEN &= ~VATTEN_ALL;
    PORT_VATTEN |= range_attens[range];
}

float get_voltage()
{
    set_voltage_range(voltage_range);
    uint16_t lower_limits[] = {0, 151, 49};
    uint16_t upper_limits[] = {941, 805, 1024};
    uint16_t multipliers[] = {2, 11, 201};

    uint16_t result = get_adc_10bit_fast(VIN_V);
    if (result < lower_limits[voltage_range])
        voltage_range--;
    else if (result > upper_limits[voltage_range])
        voltage_range++;

    _delay_us(10);

    uint32_t reading = get_adc_12bit(VIN_V);
    uint32_t battery = get_battery_voltage();
    reading = (reading * multipliers[voltage_range] * battery);
    reading >>= 7;
    return reading * (1/16384.f);
}

get_adc_12bit() is a quick-and-easy function which gets a 12-bit reading from the AVR’s 10-bit ADC by averaging 16 readings – see Atmel’s app note on oversampling and decimation.

set_voltage_range() is for controlling the attenuation circuitry at the front end of the multimeter – see the upper left of the schematic. All voltage measurements go through a fixed-gain op amp circuit, but switching the MOSFETs allows the voltage to be attenuated first by different voltage dividers, so large voltages can be scaled down in order to measure  them.

get_voltage() needs only to select the appropriate range and then scale the 12-bit reading to get the final voltage. This was originally all fixed-point maths, which worked perfectly, but I gave in and used floats because it’s just easier.

I stuck a 1602 display and a shift register onto the logic analyser header (which should be input but I’m using it as an output for now) so that I can have some form of readout until the proper screens arrive. Here I’m measuring the 32-ish volt output from my power supply:

Looks good! There is a problem though:

Voltage seems to drop off significantly towards the upper end of each range (the meter clicks up a range at about 3.8 volts). Due to the very high impedance of the attenuator circuitry, I initially assumed this was due to leakage currents in the MOSFETs, and was dreaming up all sorts of compensation schemes. As it turned out, it was due to this line I mentioned earlier in the battery voltage measurement:

_delay_us(500);

Without this line (or when the delay is smaller, on the order of 10us) we have the problem above; with it, we do not. When the ADC input is switched from the voltage input (at a higher voltage) to the reference, if it is not given time to settle then the reference will read as being a higher voltage than it is, and so the calculated battery voltage will be smaller. The measured voltage will be scaled down accordingly. This effect becomes more pronounced at higher voltages, which explains the downward nosedive seen above.

Current

Current measurement was much more straightforward:

void switch_current(bool onoff)
{
    DDR_IGATE |= IGATE;
    if (onoff)
        PORT_IGATE |= IGATE;
    else
        PORT_IGATE &= ~IGATE;
}

float get_current()
{
    switch_current(CURRENT_ON);
    _delay_us(50);
    uint32_t reading = get_adc_12bit(VIN_I);
    reading = (reading * 19 * get_battery_voltage()) / 46;
    reading >>= 6;
    return reading * (1/16384.f);
}

Again, this was originally all fixed point maths, but I converted to a float at the end. I’m after peace of mind, not efficiency!

The meter can measure up to about 1.7A, and will happily take 1 amp continuously with little or no heating effects.

The Megameter agrees with my Extech EX330 to within 1% across its range, and measures with a resolution (and pretty much an accuracy) of 1mA, which is “good enough”.

In between these two photos I had an accident which involved having to reflow, remove and replace soldered SMD parts with a frying pan – hence the missing buttons!

The measurement circuitry consists of nothing more than a 0.22ohm shunt resistor, the beefiest MOSFET I have ever seen in a SOT23 package (put in as many vias as I could so that I could use the planes as a heatsink), and a fixed 11x gain op amp circuit. Does the job!

Capacitance

The final measurement which I have tackled so far is capacitance. The meter has a useful range of 1nF -> >600uF, which is far better than my EX330, as well as being about as accurate. It’s based on the well-known principle of RC discharge curves – by discharging the capacitor, charging it through one of a range of resistors (for various ranges of capacitance value), and then timing how long it takes to reach a certain threshold voltage, it’s possible to calculate the capacitance. I derived this formula:

 

C = \frac{-t}{R \ln(1 - \frac{V_{trig}}{V_{cc}})}

 

Vtrig is the threshold voltage at which the capacitor stops charging. To give this a known value, I used the internal bandgap reference, and the AVR’s analogue comparator to detect when the capacitor reaches this voltage. This involved yet more datasheet black magic:

inline void set_rc_range(uint8_t range)
{
    uint8_t ranges[] = {RC_LOW, RC_MID, RC_HI};
    DDR_RC &= ~(RC_LOW | RC_MID | RC_HI);
    PORT_RC &= ~(RC_LOW | RC_MID | RC_HI);
    DDR_RC |= ranges[range];
    PORT_RC |= ranges[range];
}

uint8_t cap_range = 2;
volatile uint8_t cap_aco_mask = 0x20;

// Timer 1 overflow vector: stop the timer, set its result back to a large value,
// jump up a range, and force the wait loop to exit.
ISR(TIMER1_OVF_vect) {
    TCCR1B = 0x00;
    cap_aco_mask = 0x00;
    cap_range--;
    TCNT1 = 0xfff0;
}

void choose_cap_range(float result)
{
    // all values are in microfarads
    switch(cap_range)
    {
    case 0:
        cap_range++;
        break;
    case 1:
        if (result < 5.f)
            cap_range++;
        break;
    case 2:
        if (result > 6.f)
            cap_range--;
        else if (result < 0.5f)
            cap_range++;
        break;
    case 3:
        if (result > 0.6f)
            cap_range--;
        break;
    }
}

float get_capacitance()
{
    uint16_t resistances[] = {47, 470, 20000, 20000};
    uint8_t divisors[] = {1, 1, 1, 8};          // compensates for timer clock frequency differences
    uint8_t tccrs[] = {0x02, 0x02, 0x02, 0x01}; // selects the clock frequency input to the timer
    uint8_t subtractors[] = {1, 1, 1, 10};      // compensates for code execution time

    // let the RC_SENSE net float by switching off the current pass MOSFET
    switch_current(CURRENT_OFF);

    // Get values ready to plug into registers (timing is critical later)
    uint8_t rc_range = cap_range;
    if (rc_range > 2)
        rc_range = 2;
    uint8_t tccr_start = tccrs[cap_range];
    cap_aco_mask = 0x20;

    // Switch off the ADC so we can use its multiplexer, and set up the comparator:
    ADCSRA = 0x00;  // ADC off
    ADCSRB = 0x40;  // ACME = 1 (connect ADC multiplexer to the comparator)
    ADMUX = VIN_RC; // RC pin connected to comparator negative input
    ACSR = 0x40;    // 1.1v bandgap reference connected to comparator positive input

    // connect RC -> 47 ohms -> ground, float other pins, then wait 50ms for discharge
    set_rc_range(0);
    PORT_RC &= ~RC_LOW;
    DDR_RCSENSE &= ~RC_SENSE;
    PORT_RCSENSE &= ~RC_SENSE;
    _delay_ms(50);

    // set up timer1
    TCCR1A = 0x00;
    TCCR1B = 0x00;
    TCNT1 = 0;
    TIMSK1 = 0x01; // overflow interrupt enabled
    sei();

    // connect rc -> resistance -> vcc and start timer.
    set_rc_range(rc_range);
    TCCR1B = tccr_start;

    // exits when comparator outputs 0 (voltage has reached 1.1v) or the timer overflow interrupt has fired
    while (ACSR & cap_aco_mask);
    // stop timer and float pins
    TCCR1B = 0x00;
    DDR_RC |= RC_LOW;
    PORT_RC &= ~RC_LOW;
    // C = -t / (R * ln(1 - Vtrig / Vcc)
    float result = -((float)(TCNT1 - subtractors[cap_range]) / divisors[cap_range]) / (log(1 - 1.03f / (get_battery_voltage() * (1/256.f))) * (float)resistances[cap_range]);
    choose_cap_range(result);
    return result;
}

I initially had trouble making the comparator code work. I was doing this in the wait loop, to wait until the analogue comparator output (ACO) went low: (capacitor is connected to the inverting input)

 

while (ACSR & ACO);

 

The “gotcha” here is that the constant ACO, defined by avr/io.h, is the bit position (5), not the bit value (0x20). The idiomatic way of doing this is to write

 

while (ACSR & (1 << ACO));

 

But this looks stupid, so I just used the hex constant (in a named variable, whose purpose will be explained shortly).

The only other complicating factor is that of timer overflows. The timer is a 16 bit counter, clocked at either 1 or 8MHz depending on range, so it will overflow after either 65ms or 8ms. You could detect this overflow by doing a comparison against a value suitably lower than 0xffff in the wait loop, but every single instruction in this loop reduces the precision of the measurement.

The comparison can be made faster by comparing only the high byte, but this still reduces the precision. If only a processor had a way of handling asynchronous events separate to the main flow of execution… wait.

The timer is set up to trigger an interrupt when it overflows. The interrupt itself is handled by separate silicon, and does not slow down execution until it is actually triggered, at which point execution jumps to the service routine.

The interrupt stops the timer, changes the ACO mask to zero (which forces the while loop to exit), sets the count back to a very large value, and bumps the capacitance range up by one.

I stuck all of this together into a main function that lets me choose measurement by clicking on the buttons.

Up next: resistance measurement, SPI flash and TV remote!

 


Leave a comment

Megameter Boards, SMD Soldering and Manchester Encoding!

A few days ago I received a package from China, with the customary “gift” marking to avoid import tax (no wonder shipping is so cheap). After eagerly ripping it open:

20140817_162351

Boards! I have never used iTead Studio before (or designed any PCB at all prior to this one) but the quality is absolutely gorgeous.

This is my first time doing surface mount soldering, but after watching a few YouTube videos I decided to just have at it. I populated the board in stages, testing as I went, but neglected to take photos as I went – oops. Here is its current state:

I have discovered the secret to SMD soldering: flux. Lots of it. Flux is the magic sauce that makes larger packages like TQFPs super easy to do by hand.

The LED hanging off of the logic header is just for debugging purposes. All of the logic pins have 1k series resistors on them (“lazy man’s level conversion”) so it’s safe to just solder it on there.

A few things I noticed about the boards:

  • My silkscreen is in general a little messed up, in particular my logic header markings (must have been late at night when I did that one…)
  • The terminals cover up the V / I/R/C / GND legends
  • I ought really to have a pullup resistor on the flash chip’s CS pin, because I can’t drive that pin high when the microcontroller is reset for programming, at which point I really don’t want multiple devices on the SPI bus
  • Some of the creepage distances are not too great, especially once the terminals were screwed on

After a lot of fiddling about with avrdude I eventually managed to get some code onto the device, and blink that LED. Never before has a blinking LED been so satisfying. I’ve finally managed to get away from the walled garden of the Arduino IDE.

Until the screens arrive, I need some way of getting data off of the device. The hard UART is not exposed through the logic header, so I wrote this function:

void bpmc_send(uint8_t *data, uint8_t count)
{
    DDRB |= 0x01;
    for (uint8_t i = count; i; --i)
    {
        uint8_t byte = *data;
        ++data;
        for (uint8_t bit = 8; bit; --bit)
        {
            PORTB ^= 0x01;
            _delay_us(4);
            if (byte & 0x01)
                PORTB ^= 0x01;
            byte <<= 1;
            _delay_us(4);
        }
    }
}

Let’s take a closer look at that blinking LED waveform.

multilogic01

Looks a bit fuzzy on the rising edge. Enhance!

multilogic03

Hello, world!


Leave a comment

Morse Code Input On An AVR

After doing 11 12 hour night shifts back to back, I needed to stay up for 12 hours to get myself back onto a day cycle. What do you do during that time? Science! Well, nothing really scientific going on here, but at least vaguely technical.

My “neat little demo” was hampered by the fact that I am truly terrible at Morse code. The technical side of this is fairly interesting though – here’s the source code:

char letters[] =
{
  0,  0,  'E',  'T',  'I',  'A',  'N',  'M',  'S',  'U',  'R',  'W',  'D',  'K',  'G',  'O',  'H',  'V',  'F',  '-',  'L',
  '#',  'P',  'J',  'B',  'X',  'C',  'Y',  'Z',  'Q',  '.',  '-',  '5',  '4',  '3',  '2',  '1',  '6',  '7',  '8',  '9',  '0'
};

#define COUNTER_STOPPED 0x00
#define COUNTER_RUNNING 0x05 // ck / 1024 =&gt; 64us per tick

// Letter format: all zeroes, then a 1 to mark the start of the letter, then 0 for dot and 1 for dash
// for the remainder of the letter.
void printletter(uint8_t letter)
{
  uint8_t count = 8;
  if (!letter)
    return;
  Serial.print(letters[letter]);
  while(!(letter & 0x80))
  {
    letter <<= 1;
    --count;
  }
  letter <<= 1;

  while (--count)
  {
    if (letter & 0x80)
      Serial.print("-");
    else
      Serial.print(".");
    letter <<= 1;
  }
  Serial.print(" ");
}

void setup()
{
  Serial.begin(9600);

  DDRB = 0x08;
  PORTB = 0x10;
  TCCR2A = 0x41;  // Toggle OC2A (PB3), don't touch OC2B, CTC mode
  TCCR2B = 0x06;  // CTC, ck/256, WGM22 = 1 (for toggle).
  OCR2A = 61;
  TCNT2 = 0;
  ASSR = 0;

  TCCR1A = 0x00;  // Normal operation, no wavegen
  TCCR1B = COUNTER_STOPPED;
}

void loop()
{
  uint16_t dotlength = 1500;
  uint16_t lastspace = 0;
  uint8_t currentletter = 0x01;
  while (true)
  {
    TCNT1 = 0;
    TCCR1B = COUNTER_RUNNING;
    bool newword = false;
    while (PINB & 0x10)
    {
      lastspace = TCNT1;
      if (lastspace > dotlength * 7)
      {
        newword = true;
        break;
      }
    }

    if (lastspace > dotlength * 5 / 2)
    {
      printletter(currentletter);
      currentletter = 0x01;
      if (newword)
      {
        Serial.println(" ");
        while (PINB & 0x10);
      }
    }
    TCNT1 = 0;
    TCCR1B = COUNTER_RUNNING;
    TCCR2B |= 0x08;
    while (!(PINB & 0x10));
    TCCR1B = COUNTER_STOPPED;
    TCCR2B &= ~0x08;
    //Serial.println(TCNT1);
    if (TCNT1 < dotlength * 5 / 3)  // dot
    {
      currentletter = currentletter &lt;&lt; 1;
      dotlength = (dotlength * 2 + TCNT1) / 3;
    }
    else                        // dash
    {
      currentletter = (currentletter << 1) | 0x01;
      dotlength = (dotlength * 2 + TCNT1 / 3) / 3;
    }
  }
}

It does unfortunately show all the hallmarks of being written at a time when I should really have been asleep. However, it does function rather well! My morse code ability is regrettably not up to scratch.

One feature to note is that the speed (dot length) is adaptive – the faster you enter code, the faster it will expect the code to be. Using the AVR’s TIMER1 hardware, I can obtain a time resolution of 64 microseconds, meaning the AVR can input Morse code pretty damn fast. Think >100 characters per second, or Marty McFly on a hoverboard sort of fast.

When this baby hits 88 CPS, you’re going to see some serious shit.

TIMER2 in CTC (Clear Timer on Compare match) mode generates the 1kHz beep tone without CPU supervision, so all the CPU does is sit in a loop and wait for the button input to change.

My real reason for doing this is to learn how to use the timer/counter hardware – it should be handy for carrying out capacitance and frequency measurements on the multimeter.

The character encoding is fairly neat – taking advantage of the fact that morse code is essentially a binary tree structure, symbols can be packed into a rather compact array. I’m glad that I typed out that array correctly the first time, because a mistake in that would have been a pain.

iTead Studio tells me that the PCBs have been shipped, so hopefully I can show an assembled multimeter within the next couple of weeks!


5 Comments

Estimating BB Muzzle Velocity With A Voice Recorder And A Curtain

If you’re reading this blog, the chances are that at some point you’ve had a hankerin’ to do some science.

There’s a certain type of mind that looks at things and just wonders about them. Why does that transformer make that buzzing sound? What’s the muzzle energy of this BB gun lying on my desk here? Whether this is a useful activity or just intellectual navel-gazing is difficult to say.

The BB gun in question is pretty terrible – without a piece of electrical tape in the right place it tends to explode in every direction apart from the one in which it’s pointed. However, this does mean there’s a pretty hefty spring hiding in there, which is what got me wondering. How would I measure the muzzle velocity though?

One way of measuring projectile speed is to record it from the side with a high-speed camera against a backdrop of known feature size, à la Mythbusters:

121786041305012580402601197_DietCokeAndMentos

I want to do this with the things in my desk and kitchen though (science right now), and my phone camera only goes up to 60fps, so that’s right out.

Walter Lewin has a great demo at the end of this lecture where he fires a rifle through two wires with a current flowing through them, and then measures the time between the wires breaking to calculate the speed. Unfortunately I don’t have this lying around in my kitchen either.

This is the method I came up with:

  • Rest the BB gun on a chair, a known distance (4m) from a curtain (use a tape measure)
  • Turn on my phone’s voice recorder
  • Fire the BB gun at the curtain a bunch of times

This is all the data I need to find the average velocity. Primary school science: speed = \frac{distance}{time}

Opening up the recordings in Audacity:

We see two features:

  • When the gun is fired
  • When the BB hits the curtain.

I measured the times between these using the cursors in Audacity, and stuck it all into a spreadsheet. Subtracting the time for the sound to propagate back from the curtain (4m / 330 m/s = 19 ms) we get an average Time of Flight (ToF) of ~112ms.

SCIENCE

Using Student’s t distribution it was straightforward to calculate a 95% confidence interval for the projectile’s average speed: between 33.6 and 38.3 metres per second. Ballin’.

After counting 100 of the BBs into a little jar and weighing with my kitchen scales, I also knew that the average projectile mass was 0.11g +- 0.05.

However. Substituting this value into E_{K}=\frac{1}{2}mv^{2} does not give you the muzzle energy, because the BB’s speed is not constant, due to air drag. Neither does it give you the average KE. In fact, it’s pretty useless. We’re going to need to do some modelling.

I assumed that the drag force experienced by the ball was equal to its cross sectional area multiplied by the dynamic pressure:

F = -\frac{1}{2}\rho v^{2} A

Where \rho is air density and v is speed.

Newton’s second law then gives us

m\ddot{x} + \frac{1}{2}\rho \dot{x}^{2}A = 0

Where m is the projectile’s mass and x is its horizontal displacement from the muzzle.

Then:

\textup{Let }R = \frac{\rho A}{2m}

\ddot{x} + R\dot{x}^{2} = 0

At first this looked nasty, so I stuck everything into a spreadsheet and did Euler integration (difference equations).

Then I realised you can substitute \dot{x} for v. Oops.

\dot{v} + Rv^{2} = 0

\frac{dv}{dt} = -Rv^{2}

Separate variables:

\int{\frac{1}{v^2}dv} = -R\int{dt}

\frac{1}{2v} = Rt + c

To find the constant, we’ll want to find the position in terms of time, so we rearrange and integrate:

v = \frac{2}{Rt + c}

x =\frac{2}{R}ln(Rt + c) + d

After measuring a line of BBs with a ruler, I found my constants and the specific function, shown in red above. Making the time step smaller shows that this is pretty much a perfect fit. Success! Using our model, we can now predict the BB’s velocity at every point in its 112 millisecond flight.

The answer to the original question? About 63m/s, with a KE of 0.22 Joules. Enough energy to lift an apple 20cm off of a table. Ouch. However, by the time the BB reaches the curtain 4 metres away, we can predict that it will have only 1/9th of its initial energy.

Using kitchen scales and a ruler to calculate the gun’s spring constant, it’s quick and easy to find the work of travel (about 1.4 Joules) and determine that the gun is approximately 15% efficient at turning spring energy into muzzle energy.


Leave a comment

The Megameter: Multimeter/Oscilloscope/Logic Analyser/TV Remote, Also Irons Your Shirts

I’ve spent a lot of time fiddling about with electronics, but all the good parts these days are surface-mount, so I decided it was about time I spin my own PCB.

I used the free version of Eagle. Ignore the part numbers on the op amps, MOSFETs and flash chip; I have actually chosen appropriately specced parts… hopefully.

Hopefully I haven’t done anything too stupid here, although not catching fire is never a guarantee with my projects. As my first PCB I’m pretty proud of it – I did actually rip it up and start again at one point. Note to self and readers: never, ever use autoroute!

It has:

  • An ATmega324PA microcontroller running at a blistering 8MHz
  • A capacious 2MiB flash chip for data logging
  • One of those 5110 black/white LCDs you can find on eBay (84*48 pixels)
  • A lithium charger IC, with charge current selectable from 100mA and 280mA using a solder jumper, and a charge indicator LED
  • The worst analogue front end in history

It should be able to:

  • Measure voltage up to 250v with ~1% accuracy across the ranges
  • Measure current up to 1.5A with ~3mA precision (may be able to improve this with e.g. oversampling and decimation of the ADC reading)
  • Measure capacitance from 20nF into the 100 microfarad region (need to spec out this accuracy)
  • Measure resistance from 100 mohm to >1Mohm with ~1% accuracy
  • Measure frequencies up to 3.2MHz with 10Hz resolution from the logic header (may be able to improve this to ~2.5Hz)
  • 4 bit logic header: at least 1k sample depth, >1MHz max sample frequency
  • Learn, store and replay IR remote codes from most TV remotes transmitting with a 38kHz carrier
  • Oscilloscope sampling frequency of at least 150 kSa/s, hopefully 300 (AVR ADC gets ~6 ENOB at 4MHz clock, which is loads for a 48 pixel screen (6 bits is 2^6 = 64 pixel rows))

The TV remote was a stupid addition, the thought process went something like “Hey, I have 3 pins free, what would be really cool?” – it’s just an LED and a receiver. The receiver draws about 1mA quiescent so its VCC pin is hooked up to a GPIO so that it can be switched on and off. (There’s no power button – I’m instead trying to get the standby current as low as possible.)

Hopefully this will be a nice tool to use at uni. If not, I can just program it to play Tetris and Snake.

I’m having this case 3D printed at some point:

 The battery and buzzer will fit into the case, and the board will push into the front. I’ve now added some cutouts for the IR LED and receiver.

Boards are currently being processed by iTeadstudio – hopefully that will go without a hitch, and I can update with some nice fresh assembled boards!


Leave a comment

Documentation For My Homemade Processor, Compiler And OS!

For those of you who haven’t been following the ‘blog (because I’ve just made it… oops…) I have:

  • Designed a simple CPU architecture (emphasis on simple!)
  • Built it out of wires and logic parts from various shady websites (the microcode ROMs came from a washing machine)
  • Written a compiler for a C-like language, compiling down to the machine’s single-instruction instruction set
  • Written a functional OS in this language
  • Written Windows development tools: IDE, emulator

Which I think is all pretty darn spiffy!

The development is more or less done and dusted (there are a couple of hardware things to tidy up, which I will post about on this blog), but that’s not what this post is about. Although the project was originally for fun, it became my A-level computing project, which implied copious, perhaps excessive amounts of documentation.

I was wary of posting the documentation before results day, because exam boards tend to have some pretty draconian plagiarism checks. It’s close enough now though that eh, who cares. I’ll post it.

Writeup (191 pages, but almost 100 of that is just code listings):

example page:

There’s also a programming manual / tutorial, which explains some of the basic concepts behind how the computer works, such as the Von Neumann machine, and then guides you through writing simple programs in my language. (30-odd pages)

The documentation is available in all of its (dubious) glory on my GitHub repo:

PDFs are here.

So, if homemade compilers and processors float your boat, take a look!