Arduboy Kickstarter version design discussion

I’ve done some design and testing of this and it appears to work well.

Design:
The specific resistor divider ratio isn’t critical, as long as the output is below 3.3V at the highest battery voltage, since we can do the math in software. However, I though it would be good to have each ADC step be somewhat meaningful, instead of some arbitrary fraction of a volt. By making full scale be 5.115V (5115mV), which is 5 times the ADC maximum count of 1023, each ADC step will be 5mV. This means we want the resistors to divide the voltage by 5115/3300 = 1.55.

The total resistance of the divider also isn’t critical. It should be a good deal lower than the impedance of the ADC input, but high enough as to not draw too much current. The ATmega32U4 datasheet states:

A divider with a 10K impedance would draw a fair amount of current. Since we’ll only be checking the battery voltage occasionally, we can afford longer sampling times. I feel that a total divider resistance of around 500K would be suitable. For accurate readings, the resistor tolerance should be 1% or better. Using a 187K resistor for the high side and 340K for the low side gives us exactly a divide by 1.55 with a total resistance of 527K. These are both standard EIA E96 1% values, so should be readily available.

Testing:
I used a linear variable power supply in place of a battery. I connected the divider circuit to pin A3 of my Arduboy compatible system and loaded the following sketch:

// Battery monitor test

/*
A resistor divider is used to scale the input voltage to be within a
0V to 3.3V measurable range. A 187K resistor goes between the
battery positive terminal and an analog input. A 340K resistor goes
between the same analog input and ground.

With this divider, a regulated 3.3V on AVCC, used as the ADC
reference, will give 5mV per step.
Full scale will be 1023 * 5mV = 5.115V.
*/

#include <SPI.h>
#include <EEPROM.h>
#include <Arduboy.h>

const int batPin = 3;
Arduboy aboy;

void setup() {
 aboy.start(); 
 aboy.setTextSize(2);
}

void loop() {
  uint8_t bgLow, bgHigh;

  // Display battery voltage in millivolts
  aboy.clearDisplay();
  aboy.setCursor(0, 8);
  aboy.print("BAT: ");
  aboy.print(analogRead(batPin) * 5);
  delay(100);

  // Display raw bandgap reading
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  bgLow  = ADCL; // must read ADCL first - it then locks ADCH 
  bgHigh = ADCH; // unlocks both
  aboy.setCursor(0, 32);
  aboy.print("Bgap: ");
  aboy.print((unsigned int)((bgHigh<<8) | bgLow));
  
  aboy.display();
  delay(900);
}

I changed the supply voltage in 0.1V steps and noted the displayed readings. The following is a graph of the results:

All of the readings were stable and repeatable, changing by no more than 1 LSB (with a filter capacitor added. See Additional Design below).

All of the battery readings are about 1% low because the measured output of the voltage regulator, and thus the reference, was 3.33V, which is about 1% higher than the nominal 3.3V.

At a voltage slightly below 2.6V, the program stopped running, presumably due to the CPUs brown out detection circuitry.

The graph shows that the bandgap readings start to rise below around 3.4V input. This is because the input voltage to the regulator is below the dropout voltage, so the reference has gone lower while the bandgap remains the same. In this area, the battery readings stay constant because the reference is now changing directly with the battery voltage.

So, as I expected, seeing a rise in the bandgap reading can be used alone to detect a low battery condition. However, being able to accurately read the battery voltage when it’s above 3.4V would give us the ability to display it, and/or have a battery charge remaining indication.

Here is a table of the actual readings that I took and graphed:

Supply mV  Reading mV  Bandgap raw
 2600        3350          432
 2700        3350          416
 2800        3350          402
 2900        3350          388
 3000        3350          375
 3100        3350          362
 3200        3350          351
 3300        3350          341
 3400        3365          332
 3500        3465          332
 3600        3565          332
 3700        3665          332
 3800        3765          332
 3900        3865          332
 4000        3965          332
 4100        4065          332
 4200        4160          332
 4300        4260          332
 4400        4360          332
 4500        4460          332
 4600        4560          332
 4700        4660          332
 4800        4760          332
 4900        4860          332
 5000        4955          332

Additional design:
Because of the relatively high resistance of the divider, and the fact that the battery voltage will change very slowly, it may be a good idea to put a capacitor between the ADC input pin and ground, as a noise filter. In my testing I found that successive readings of the same input voltage could change by +/- one or two steps. Adding a 0.1uF ceramic capacitor eliminated these changes. I would add a footprint on the circuit board for such a capacitor, which could just be left unpopulated if it were decided it wasn’t needed.

As indicated in the ATmega32U4 datasheet, a 0.1uF capacitor should be placed between the AREF pin and GND. Nothing else shoud be connected to this pin, since we’re using AVCC internally as the reference.

The more precise and stable the ADC reference is (along with the tolerances of the divider resistors), the more accurate the battery readings will be. As with the resistors, the 3.3V regulator output should be within 1% or better. I’d recommend using the Micrel MIC5205-3.3YM5 as the regulator. It has all the desirable qualities, and is inexpensive and widely available.

From a system software standpoint, it would be nice to have a reserved EEPROM location to store the “battery not low” bandgap value constant, since it will vary from unit to unit. Being able to compare the present bandgap reading to a permanent stored value would make it easier to detect a low battery, than looking for a rise in the bandgap over time.

1 Like

Nice work on this. Would be great to see in the final production Arduboy.

I’ve now tried this and can say that it works nicely.

I installed the Arduino Tone Library with an updated Tone.cpp which has been modified to work with the ATmega32U4.

I wrote the following test sketch and ran it with a piezo speaker connected directly across pins A2 and A3:

#include <Tone.h>

Tone tone1, tone2;

void setup() {
  tone1.begin(A2);
  tone2.begin(A3);
}

void loop() {
  tone1.play(NOTE_C4);
  tone2.play(NOTE_E4);
  delay(2000);
  tone1.stop();
  tone1.play(NOTE_G4);
  delay(2000);
  tone2.stop();
  tone2.play(NOTE_C5);
  delay(2000);
 
  tone1.stop();
  tone2.stop();
  delay(1000);
}

Each of the two tones mixed well and sounded good. I tried a few other tone pairs and they also mixed fine.

So, I’d have to say that putting the speaker across two pins to provide mixing, as well as to provide a simple dual volume level capability, is a pretty good idea. Again, the only drawback is, if only a single pin is being used to generate audio, the other pin must be set as an output (or set as an input to mute the speaker).

So, partly in summary - if the external hardware is configured this way, improper configuration or software usage may not produce desired outcomes. But there is no configuration or software usage that can cause hardware damage.

That seems like a fairly reasonable setup. Use it right, and you get sounds. Use it wrong, and you get silence. If you go back to using it right, you get sound, not an audio-bricked Arduboy.

Since this also provides for the option of processor-intense fast PWM sound reproduction, but also low-rate straight audio PWM, developers have a good range of options.

Yes, that is correct.

A bonus eureka thought! (verified) :smile:

I tried setting one pin to INPUT while outputting a tone on the other pin, which muted the speaker as expected.

I then tried changing the pin from input to INPUT_PULLUP, which would place an internal 20K to 50K resistor in series between the speaker and Vcc. This resulted in the tone sounding at a lower volume.

So, by placing the speaker across two pins we can use one of them to control the volume of the other, by setting the control pin as follows:

  • Mute: Set as INPUT
  • Low volume: Set as INPUT_PULLUP
  • Normal volume: Set as OUTPUT
  • High volume: Set to the opposite level (compliment) of the other pin.

There may be other useful possibilities using INPUT_PULLUP (which I haven’t yet tested):

  • A normal volume tone mixed with a low volume tone. The tone on one pin is generated by toggling the output high and low. The tone on the other pin is generated by toggling between a low output and input_pullup.

  • Two low volume tones mixed, by generating each tone by toggling between output low and input_pullup.

  • A single ??? volume tone by toggling one pin between output low and input_pullup and setting the other pin to the opposite.

  • A single ??? volume tone by toggling one pin between low and high output. The second pin is set to output low when the other is high, but set to input_pullup when the other is low

  • Other combinations and techniques?

Wow this thread keeps on rolling, I’m reading through this don’t have time to give detailed comments yet but I know many people have asked about the 8mhz vs 16mhz.

The plan for now is to do my best to support 16mhz by adding a voltage boost controller to bring us up to 5v so that 16mhz will be in spec

Also, I’m planning to put all the input buttons on the same port so they can all be read with one port read command.

And finally, I’ll be moving the speaker to a PWM pin, I think pin 6 was recommended?

Keep on rolling with this I’ll be trying to lock down the design by the end of July early August. Thanks guys!

1 Like

I hope this doesn’t affect your profit margins too much, and you can make one from thin enough components.

Just keep in mind that you will still have to power the display’s VDD logic supply at 1.65V to 3.3V. So, you’ll probably need a regulator for the display, in addition to the 5V booster for the CPU.

And, as I’ve already mentioned previously, the display inputs are only tolerant of voltages up to the VDD supply voltage. Running the ATmega32U4 at 5V means its outputs used to control the display will be 5V when high. This means you will need level shifters on the display inputs: MOSI, SCLK, CS, DC and RST.

If you use a 5V booster then the technique to detect low battery, by seeing a rise in the bandgap vs. VCC, will likely not work. If battery monitoring is still desired, connecting the battery directly to an analog input pin would work if the booster output was exactly 5V with a tight tolerance, and remained so with the battery between full charge voltage and 3.4V.

Alternatively, if the display ends up being powered by a stable, accurate 3.3V regulator (such as a Micrel MIC5205), then both previously discussed battery monitoring methods could be used if the 3.3V output was connected to the CPUs AREF pin.

Only Port D could do all 7 buttons and Port F could do 6, as I summarised in this post. In the following post, @Dreamer3 argues that there may not be much value in doing this, anyway. I tend to agree. I would concentrate more on the value of the secondary functions of the pins.

Most of the following is just reiterating what I’ve said in the original post, with some input and consensus from others. Please refer to it for more details.

It would be desirable to leave pins 0/RXD1(PD2), 1/TXD1(PD3), 2/SDA(PD1), 3/SCL(PD0) and TXLED (PD5) unused and connected to pads for hacking and expanding the Arduboy or creating enhanced devices that are Arduboy software and library compatible. This rules out using Port D, leaving 6 pins on Port F.

Avoiding pins 11, 12, 13, A4, A5 if possible would help maintain compatibility with a SparkFun Pro Micro.

It’s desirable to have the start button on a pin that can generate an external interrupt to wake the unit from a low power sleep mode. It’s also desirable to have that interrupt be a standard Arduino one, so the attachInterrupt() standard library function could be used. Ruling out the above mentioned pins 0 - 3, that leaves pin 7 (int.4).

@Dreamer3 has said that he would like to see buttons A and B be interrupt capable as well. No pins on Port F can generate interrupts.

Abandoning the idea of getting all the buttons on one port, here’s how I would map the buttons:

  • Start: 7 (PE6) Arduino int.4
  • Candidates for A and B: 8 (PB4), 9 (PB5), 10 (PB6)
  • Candidates for the D-pad, after A and B are mapped:
    A0 (PF7), A1 (PF6), A2 (PF5), A3 (PF4) none can generate an interrupt.
    8 (PB4), 9 (PB5), 10 (PB6)

Unless we wanted one D-pad button to be interrupt capable, using A0 - A3 would put all the D-pad buttons on Port F, so they could all be read at once. Buttons A and B could both be read at once from Port B.

No, pin 5 (PC6) is recommended. If we wanted the dual-pin mixing and volume capabilities discussed in previous messages in this thread, the recommended pin for the other lead of the speaker is pin 12 (requiring the display reset signal to be moved to a different pin such as 13 (PC7) or 11 (PB7) ).

1 Like

+1 for added H/W functionality at the cost of doing 2x port reads for button status; a good trade-off.

It should be pretty obvious to most people (who stop to think about it). Unless you need every single BYTE of space, having the buttons on even say 7 ports doesn’t really matter. You only need to poll them during your event loop - and only the simplest games can really use only buttons = SINGLE_PORT type code. A lot of games need to track long/short buttons pressed and either the previous button state or a whole continuation of button states (so you can do things like fire every half second a button is held)… and as soon as you get to that point there is a lot of button management code being run in addition to just button polling. Grouping buttons smartly onto a few ports as mentioned above seems like a great plan.

Even if you argue “every byte matters”, there are MUCH easier places to trim fat from in the current Arduboy core lib than the button code. Places that require changing no hardware at all.

To me A & B make more sense to be hardware interruptible than D-PAD… I can imaging picking it up and hitting those to wake it (without knowing anything about this future start buttons or where it might live).

1 Like

Definitely! I recall a reference that the PDP-8 had a chess program - with only 4K of 12 bit words. That’s one example of the type of program where you might have the Arduboy idle down while the user studies the screen, with a “press A to enter your move” prompt.

While not a big issue, coming from gaming I would still prefer the primary buttons under the same pin for a single read. A button I think could be a prime candidate for interrupt support would be the start button if that still happens (guessing if we keep questioning between 6 & 7 buttons).

I certainly recall games running like this on smaller 8-bit systems, but we have both 32K of program (lots), a high clock rate (lots), and minimal RAM. Getting specialized functionality out of dedicated pins strikes me as more useful, as it lets us leverage the specialized power of the ATmega32U4. Putting all buttons on a single port would help somewhat, but that strikes me as a really steep trade-off, apparently just to save a few cycles. If the plans in the works to go to 16 MHz are realized, then we will have lots of cycles.

If all the D-pad buttons could be on one port, that may help, because those buttons are typically used in closely related ways, and possibly even as an 8-way pad with chording. Even there, though, I would question the ultimate benefit. A slower processor might show some read-to-read latency, but at the the clock speeds we’re looking at - even at 8 Mhz - the delay across reading multiple ports should be very small. My rough understanding is that direct digital reads only take one cycle per port, so reading from three ports in close succession would take less than 1/2 of one millionth of a second.

Ultimately, even keeping the RX/TX pins unused (and brought out for hacking) would seem to be to be more valuable than a single port of buttons.

A lot of older systems acted that way (and defiantly older portables like GBC/GBA, possibly GB but I never worked on it).

It’s a minor gripe but out of everything suggested it is far more relevant to what I will do or use the arduboy for anyways (which is a micro game system and nothing more).

Also a simple register read is cheap/fast but when spread across pins it will add up (if slightly) along with the fact you NEVER want to read fresh from the input and will want to use a value cashed at the start of your frame (leads to unpredictable behavior) and if the input is spread across multiple pins it will ultimately add up.

Plus I don’t expect to see people be efficient on this thing and keep to strict 8bit limits (ie variable type sizes), so that 32krom 1.5k ram (accounting for most will use the 1k vram buffer) will disappear in an instant.

Like I said, it’s a minor gripe that i’ll live with but it’s the only one really relevant to me.

I agree that aesthetically it would feel nice to have all eight buttons on one port, and access them with a single instruction. But the NES used a parallel to serial shift register that had to be clocked eight times to read in all of the buttons - and the GameBoy used a matrix of diodes that couldn’t read them all in a single instruction either:

The same goes for the I2C interface: pin 2 SDA (PD1) and pin 3 SCL (PD0).

Adds up to a few millionths of a second, yes. For button polling or timing (for a single button or combos) one port vs even as many as 8 makes no practical difference. The only big win (at all) is saving a few bytes of flash.

Details from Creator on extra buttons…

Since @bateske has now stated that an additional Start button will not be implemented, would it be a good idea to move either the A or B button to Pin 7 (PE6) or should they both remain, as I previously suggested, on pins 8, 9 or 10?

Putting each on 8, 9 or 10 means they’re both on port B, so they could both be read at the same time.

Moving one to Pin 7 would allow easily attaching it to an interrupt using the Arduino attachInterrupt() function with int 4.

Note that pins 8, 9 and 10 can also generate interrupts but writing the code to handle them is a bit more difficult since they’re not directly supported by the Arduino libraries. I suppose functions could be added to the Arduboy library to make doing it easier.

I’m digging through this today, my hope is to put all the pins on the same port so they can be read with one port read instead of the 2 it currently takes. So now I have to look if port B has 6 pins available?