Sunday, August 24, 2014

A 5c lithium ion battery charger


My step-daughter lost the battery charger for her camera (for the second time in 2 yrs).  It takes a few weeks for a new one to arrive from DealExtreme, and she was hoping to use the camera over the weekend.  So I decided to hack something together.

As various sites explain, lithium-ion rechargeable batteries should be charged to 4.2 volts.  USB ports provide 5V, so all I needed was a way to drop 5V down to 4.2 or less.  Standard diodes have a voltage drop of 0.6 to 1.0 volts, so I pulled up the datasheet for a 1n4148, and looked at the I-V curve:
A standard USB port should provide up to 500mA of current, enough for charging a small camera battery.  A fully-discharged li-ion battery is 3V, and will climb to 3.8V within minutes of the start of charging.  Line 2 in the graph indicates a 1.2V drop at 350mA of current.  Under load the voltage output of a USB port will drop a bit, so with 4.9V from the USB port and 3.8V drop at the battery, the charging current will be around 250mA (where 1.1V intersects line 2).  Looking at the numbers, a single 1n4148 diode would work as a battery charge controller.

Connecting to the battery was the hardest part of the problem.  I tried making some contacts out of 24Awg copper wire, but that didn't work.  I though of bending a couple 90-degree header pins to fit the battery contact spacing, but I couldn't find my prototyping board to solder it into.  I ended up tack-sodering a couple 26Awg wires to plug into a breadboard.

For a charge status indicator, I used a couple LEDs I had on hand.  A 3V green LED in series with a 1.7V red LED start to glow visibly at 4V, and are moderately bright by 4.2V.  The few mA of current bleed from the LEDs over 4V would ensure enough current through the diode to keep the forward voltage above 0.8V, and therefore keeping the charge voltage from going over 4.2V.

The results were quite satisfactory.  After a few hours of charging, the voltage plateaued at 4.21V.  I removed the wires I tack soldered to the tabs, and the battery was ready to be used.  The same technique could be used with higher capacity batteries by using a different diode - a 1N4004 for example has a voltage drop of 1.0V at around 2A.

Thursday, August 21, 2014

Writing a library for the internal temperature sensor on AVR MCUs

Most modern AVR MCU's have an on-chip temperature sensor, however neither avr-libc nor Arduino provides a simple way to read the temperature sensor.  I'm building wireless nodes which I want to be able to sense temperature.  In addition to the ATtiny88's I'm currently using, I want to be able to use other AVRs like the ATmega328.  With that in mind I decided to write a small library to read the on-chip temperature sensor.

I found a couple people who already did some work with the on-chip temperature sensor.  Connor tested the Atmega32u4, and Albert tested the Atmega328.  As can be seen from their code, each AVR seems to have slightly different ways of setting up the ADC to read the temperature.  Neither the MUX bits nor the reference is consistent across different parts.  For example on the ATtiny88, the internal voltage reference is selected by clearing the ADMUX REFS0 bit, while on the ATmega328 it is selected by setting both REFS0 and REFS1.

One way of writing code that compiles on different MCUs is to use #ifdef statements based on the type of MCU.  For example, when compiling for the ATmega328, avr-gcc defines, "__AVR_ATmega328__", and when compiling for the ATmega168 it defines, "__AVR_ATmega168__".  Both MCUs are in the same family (along with the ATmega48 & ATmega88), and therefore have the same ADC settings.  Facing the prospect of a big list of #ifdef statements, I decided to look for a simpler way to code the ADC settings.

I looked through the avr-libc headers in the include/avr directory.  Although there is no definitions for the MUX settings for various ADC inputs (i.e. ADC8 for temperature measurement on the ATtiny88), there are definitions for the individual reference and mux bits.  After comparing the datasheets, I came up with the following code to define the ADC input for temperature measurement:
#if defined (REFS1) && !defined(REFS2) && !defined(MUX4)
    // m48 family
    #define ADCINPUT (1<<REFS0) | (1<<REFS1) | (1<<MUX3)
#elif !defined(REFS1) && !defined(MUX4)
    // tinyx8
    #define ADCINPUT (0<<REFS0) | (1<<MUX3)
#elif defined(REFS2)
    // tinyx5 0x0f = MUX0-3
    #define ADCINPUT (0<<REFS0) | (1<<REFS1) | (0x0f)
#elif defined(MUX5)
    // tinyx4 0x0f = MUX0-3
    #define ADCINPUT (0<<REFS0) | (1<<REFS1) | (1<<MUX5) | (1<<MUX1)
#else
    #error unsupported MCU
#endif

From previous experiments I had done with the ATtiny85, I knew that the ADC temperature input is quite noisy, with the readings often varying by a few degrees from one to the next.  The datasheets refer to ADC noise reduction sleep mode as one way to reduce noise, which would require enabling interrupts and making an empty ADC interrupt.  I decided averaging over a number of samples would be easier way.

I don't want my library to take up a lot of code space, so I needed to be careful with how I do math.  Douglas Jones wrote a great analysis of doing efficient math on small CPUs.  To take an average requires adding a number of samples and then dividing.  To correct for the ADC gain error requires dividing by a floating-point number such as 1.06, something that would be very slow to do at runtime.  Dividing a 16-bit number by 256 is very fast on an AVR - avr-gcc just takes the high 8 bits.  I could do the floating-point divide at compile time by making the number of additions I do equal to 256 divided by the gain:
#define ADC_GAIN 1.06
#define SAMPLE_COUNT ((256/ADC_GAIN)+0.5)

The ADC value is a 10-bit value representing the approximate temperature in Kelvin.  AVRs are only rated for -40C to +85C operation, so a signed 8-bit value representing the temperature in Celcius is more practical.   Subtracting 273 from the ADC value before adding it is all that is needed to do the conversion.

Calibration
I think one of the reasons people external thermistors or I2C temperature sensing chips instead of the internal AVR temperature sensor is the lack of factory calibration.  As explained in Application Note AVR122, the uncalibrated readings from an AVR can be off significantly.  Without ADC noise reduction mode and running at 16Mhz, I have observed results that were off by 50C.

My first thought was to write a calibration program which would be run when the AVR is a known temperature, and write the temperature offset value to EEPROM.  Then when the end application code is flashed, the temperature library code would read the offset from EEPROM whenever the temperature is read.  But a better way would be to automatically run the calibration when the application code is flashed.  However, how could I do that?

In my post, Trimming the fat from avr-gcc code, I showed how main() isn't actually the first code to run after an AVR is reset.  Not only does avr-gcc insert code that runs before main, it allows you to add your own code that runs before main.  With that technique, I wrote a calibration function that will automatically get run before main:
// temperature at programming time
#define AIR_TEMPERATURE 25
__attribute__ ((naked))\
__attribute__ ((used))\
__attribute__ ((section (".init8")))\
void calibrate_temp (void)
{
    if ( eeprom_read_byte(&temp_offset) == 0xff)
    {
        // temperature uncalibrated
        char tempVal = temperature();   // throw away 1st sample
        tempVal = temperature();
        // 0xff == -1 so final offset is reading - AIR_TEMPERATURE -1
        eeprom_write_byte( &temp_offset, (tempVal - AIR_TEMPERATURE) -1);
    }
}

The complete code is available in my google code repository.  To use it, include temperature.h, and call the temperature function from your code.  You'll have to link in temperature.o as well, or just use my Makefile which creates a library containing temperature.o that gets linked with the target code.  See test_temperature.c for the basic example program.

In my testing with a Pro Mini, the temperature readings were very stable, with no variation between dozens of readings taken one second apart.  I also used the ice cube technique (in a plastic bag so the water doesn't drip on the board), and got steady readings of 0C after about 30 seconds.

Saturday, August 9, 2014

Global variables are good

It's a rather absolute statement, to the point of being ridiculous.  However many embedded systems "experts" say global variables are evil, and they're not saying it tongue-in-cheek.  In all seriousness though, I will explain how global variables are not only necessary in embedded systems, but also how they can be better than the alternatives.

Every embedded MCU I'm aware of, ARM, PIC, AVR, etc., uses globals for I/O.  Flashing a LED on PB5?  You're going to use PORTB, which is a global variable defining a specific I/O port address.  Even if you're using the Wiring API in Arduino, the code for digitalWrite ultimately refers to PORTB, and other global IO port variables as well.  Instead of avoiding global variables, I think a good programmer should localize their use when it can be done efficiently.

When using interrupt service routines, global variables are the only way to pass data.  An example of this is in my post Writing AVR interrupt service routines in assembler with avr-gcc.  The system seconds counter is stored in a global variable __system_time.  Access to the global can be encapsulated in a function:
uint32_t getSeconds()
{
    uint32_t long system_time;
    cli();
    system_time = __system_time;
    sei();
    return system_time;
}

On a platform such as the ARM where 32-bit memory reads are atomic, the function can simply return __system_time.

Global constants

Pin mappings in embedded systems are sometimes defined by global constants.  When working with nrf24l01 modules, I saw code that would define pin mappings with globals like:
uint8_t CE_PIN = 3;
uint8_t CSN_PIN = 4;

While gcc link-time optimization can eliminate the overhead of such code, LTO is not a commonly-used compiler option, and many people are still using old versions of gcc which don't support LTO.  While writing a bit-bang uart in assembler, I also wrote a version that could be used in an Arduino sketch.  The functions to transmit and receive a byte took a parameter which indicated the bit timing.  I wanted to avoid the overhead of parameter passing and use a compile-time global constant.

Compile-time global constants are something assemblers and linkers have supported for years.  In gnu assembler, the following directives will define a global constant:
.global answer
.equ answer 42

When compiled, the symbol table for the object file will contain an (A)bsolute symbol:
$ nm constants.o | grep answer
0000002a A answer

Another assembler file can refer to the external constant as follows:
.extern answer
 ldi, r0, answer

There's no construct in C to define absolute symbols, so for a while I didn't have a good solution.  Gcc supports inline assembler.  I find the syntax rather convoluted, but after reading the documentation over, and looking at some other inline assembler code, I found something that works:
// dummy function defines no code
// hack to define absolute linker symbols using C macro calculations
static void dummy() __attribute__ ((naked));
static void dummy() __attribute__ ((used));
static void dummy(){
asm (
    ".equ TXDELAY, %[txdcount]\n"
    ::[txdcount] "M" (TXDELAYCOUNT)
    );
asm (
    ".equ RXSTART, %[rxscount]\n"
    ::[rxscount] "M" (RXSTARTCOUNT)
    );
asm (
    ".equ RXDELAY, %[rxdcount]\n"
    ::[rxdcount] "M" (RXDELAYCOUNT)
    );
}

The inline assembler I used does not work outside function definitions, so I had to put it inside a dummy function.  The naked attribute keeps the compiler from adding a return instruction at the end of the dummy function, and therefore no code is generated for the function.  The used attribute tells the compiler not to optimize away the function even though it is never called.

Build constants

The last type of constants I'll refer to are what I think are best defined as build constants.  One example would be conditionally compiled debug code, enabled by a compile flag such as -DDEBUG=1.  Serial baud rate is another thing I think is best defined as a build constant, such as how it is done in optiboot's Makefile.

Wednesday, August 6, 2014

Breaking out a QFP Attiny88 AVR


Several months ago I noticed the Attiny88.  It has several more I/O than the Atmega328, with an extra Port A  and PC7.  And unlike most of the other Attiny series, it has real SPI instead of USI, so libraries using SPI don't have to be re-written.  At just 86c for qty 1, it is the also the cheapest AVR with 8KB flash.  Since QFP-32 parts aren't easy to work with, I searched for breakout boards and found QFP32 to DIP32 boards that would allow me to use them in a small breadboard.

I had lots of experience soldering through-hole parts, but not surface-mount.  With the pin spacing of only 0.8mm, soldering individual pins with a standard soldering iron initially seemed like an impossibility.  After reading some guides and watching a couple youtube videos, I realized I should be able to solder the QFP-32 chips with my trusty old pencil-style soldering iron.

Besides the QFP Atiny, I figured I'd get some passive SMD parts as well.  I was surprised how cheap they are - 50c for 100 0.1uF ceramic capacitors and $3 for 1000 0805 resistors.  I got a little carried away and even ordered a full reel of 5000 15K 0603 resistors that were on special for $5.  Besides being more than I'll probably ever use, the 0603 size is almost too small for hand soldering.  Even the 0805 parts, at .08" or 2mm long are a bit tricky to handle.  The 0603 parts, at 1.6 by 08.mm, are the size of a bread crumb.

After all the parts arrived, I started by tinning the pads on the breakout board.  That turned out to be a mistake since the leads from the tiny88 would slide off the solder bumps when I tried to solder the first lead.  A dab of flux on the bottom of the chip helped keep it in place, but for the second chip I did I only tinned the pads in to opposite corners.  I tack soldered one lead in one corner, adjusted it until it was straight, and then soldered the other corner.

Once the chip is held in place with two leads (double and triple-check it while it is easy to adjust), the rest of the leads can be soldered.  On the first chip I tried I used too much solder, which caused bridging between some of the leads.  So have some solder wick on hand.  When I soldered the second board, I only tinned the tip of my iron, which was enough solder for about 4 leads, and avoided bridging.  After the soldering is done check continuity between the leads and the DIP holes with a multimeter.  Also check for shorts by testing the adjacent dip holes.

By my second chip I had no shorts or lack of continuity between leads and the breakout pads.  What I did have was weak shorts - between 20 and 200K Ohms of resistance between some pins.  More flux and re-soldering didn't help.  The problem turned out to be the flux.  For the second chip I couldn't find my new flux, so I used an old can of flux.  Flux can be slightly conductive, but on old DIP parts with 1.5 to 2mm between leads, it's rarely an issue.  The space between the pads on the breakout boards is only 0.2-0.3mm, and along their 3mm length the conductivity of the flux residue can add up.  I was able to clean up the residue with acetone and an old toothbrush, and in the future I'll make sure to use low-conductivity flux designed for fine-pitch surface-mount parts.

On the side opposite the chip, the board has a ground plane and pads running along the breakout pins.  The pad spacing is perfect for 0805 parts, so I was able to solder a 0.1uF cap between Vcc and the ground plane.  Again I encountered a weak short, even though I hadn't used any flux.  At first I wondered if my cheap soldering iron may be too hot and could have damaged the MLCC.  This time the problem turned out to be a black residue on the the capacitor.  Surface tension can cause small parts to pull up when they are soldered, so I had used a toothpick to press the capacitor to the board while I soldered the ends.  The heat from the soldering iron charred the toothpick, leaving a black semi-conductive residue.  Getting out the acetone and toothbrush again cleaned it up, and a note to get some anti-static tweezers the next time I order parts.

Among the SMD parts I ordered were some 0603 yellow LEDs.  These were even worse to work with than the resistors.  First, reading the polarity marks is difficult with the naked eye (or at least with my middle-aged eyes).  Second they're much more fragile than resistors and capacitors.  While trying to solder one of them, my iron slipped and melted off the plastic covering the LED die.  On my first board I failed at soldering one of the surface-mount LEDs and a resistor between PB5 and Gnd.  For the second board I used a through-hole red LED.  I clipped the negative lead to go into the ground plane hole at one end of the board. I bent and clipped the positive lead so it could line up with a resistor on PB5.  To avoid a short to the ground plane pad adjacent to the PB5 pin, I insulated it with some nail polish.  Here's the finished board:

You might notice that the pin numbers don't seem to match up - AVcc is pin 18 on the tiny88, not 26.  I intentionally rotated the chip 90 degrees so the SPI pins and AVcc were all on the same side.  This way it's easy to use my breadboard programming cable.

Since I probably won't use all the breakout boards I have, I'm willing to sell some of the extras.  For $3 in bitcoin I'll send you 5 of the breakout boards, including postage to Canada/US.  I'll also throw in a few dozen of the 0603 resistors so you can see if you are any better at soldering the miniscule things than I am.  If you're interested email your shipping info to ralphdoncaster at gmail, and I'll email back with my bitcoin wallet ID.

Thursday, July 31, 2014

Busting up a breadboard

A few months ago I bought 10 mini breadboards for prototyping small electronics projects. I've noticed lots of other projects using these boards as well.  In the past couple weeks I've encountered strange problems with voltage drops and transient signal fluctuations, which I initially thought were problems with my circuits.  Eventually I started suspecting the breadboards.

One of the first things I did was measure resistance between pins that were connected by a 24AWG copper jumper wire.  The resistance of the jumper wire is no more than 0.1Ohms, but to my surprise I found the resistance from pin to pin was 6.4Ohms.  In case a bit of corrosion possibly reducing the conductance, I unplugged and plugged the jumper wire and pins a couple times.  I even tried putting some acid flux on the pins and jumper wire, but could not get a significant change in resistance.  Just from one end of a strip to the other (5 contact points) I was measuring as low as 0.4Ohms to as much as 2.1Ohms.

Most leads and connectors are made from copper, with tin or gold plating.  Copper conducts very well, but oxidizes easily so tin or gold is used to protect the copper from corrosion.  For the past number of years, the cost of copper has averaged over $3/lb, while stainless steel is about half the price.  While stainless steel resists corrosion, it's resistance is about 40 times higher than copper.  Since a breadboard with poor conductivity has limited usefulness, I decided to break apart one of the worst ones in the batch using a pair of wire cutters.

I pulled out one of the metal contact strips, and bent apart the fingers.  It certainly felt less malleable than copper.  I tried scraping the surface and snipped one of the fingers off, and the metal looked homogeneous.  Most kitchen cutlery is made of stainless steel, and if you or your friends have ever done hot knives, you probably noticed the discoloration caused by heating.  I took one of the metal strips outside and heated it with a propane torch.  Here's the result:


Another possibility besides stainless steel is nickel plated phosphor bronze, like these mini breadboards sold by dipmicro.  Phosphor bronze is close to copper color, and since the core of the metal looks the same as the outside, so I suspect the ones I received are not phosphor bronze.  It conducts about 3 times better than stainless steel so this may be one of those situations where paying a bit more is worth the money...

Monday, July 28, 2014

RK2928 wireless TV dongle

I recently purchased a wireless TV dongle for $18 (10% off the regular $20 price).  Now they're even selling for $16 on Aliexpress.  For power a microUSB-USB cable is included to plug it into a USB port on the TV or into a USB power supply.  If your TV supplies 5V power to the HDMI connector (most TVs don't), the dongle will draw power directly from the HDMI port.

It came with a single sheet double-sided "user guide".  There's no reference to the manufacturer, though after some searching I found it is functionally identical to the Mocreo MCast.  I found the setup somewhat confusing, as the dongle works in either miracast or DLNA/AirPlay mode.  Pressing the Fn button switches between modes.

The miracast mode is used to mirror your tablet or phone display to the TV.  Android 4.2 and above supports miracast.  In 4.4, it's a display option called "Cast screen".  The dongle appears as a wifi access point (with an SSID of Lollipop), and to use miracast you must connect to this access point.  This would be quite useful for presentations.  I used to do corporate training, and a dongle that can plug into the back of a projector avoids the problems associated with long VGA or HDMI cables.  Miracast is not fast enough for smooth video playback - for that you need UPnP/DLNA.

To setup DLNA, it is necessary to first connect to the Lollipop access point, and then browse to the IP address (192.168.49.1) of the dongle.  The configuration page allows you to scan for your wifi router, and provide the password to connect.  When you are done, you'll have to switch your tablet connection to your wifi router.

At this point, if you don't have a UPnP/DLNA server and control point, you won't be able to do much with the dongle, since it's not Chromecast compatible.  XBMC is a popular DLNA server, and even Windows 7 includes a DLNA server.  Media players like the old Seagate Theatre+ will also work as a DLNA server if you have an attached hard drive.

Once you have a server, you'll also need a controller aka control point app for your android device.  The manual that came with my dongle recommended iMedia Share, but this app only supports sharing media that is already on your tablet.  Finding a decent app was rather frustrating, as the first couple free apps I tried, such as Allcast, are basically a teaser for the paid app.

After some searching I found Controldlna which does (mostly) work.  I was able to browse my DLNA server, and direct the dongle to stream video from the DLNA server.  The play/pause function in controldlna was flaky (frequently stopping the video rather than pause), so I had to use the dongle's web page controls.  Similar to the dongle's setup page, there's a page that has play/pause/stop buttons.

Playback of a 1mbps h.264 encoded HD video was very smooth.  There was a problem with the aspect ratio though.  The video was 2.25:1 aspect ratio, but the dongle displayed it at full screen 16:9, making the video look vertically stretched.

What is lacking is the ability to browse online videos (like youtube) and direct the dongle to play them.  The DLNA protocol supports arbitrary URLs, so the only barrier to playing online video is a control point app that allows selecting videos from the web.  If I can't find one, it may be time to see how my Java coding experience translates into writing Android apps.

The dongle has lots of potential, but the software is lacking at this point.  Although it's not something for your average person who wants to watch digital video on their TV, for the technical folks I think it's worth the money.

Tuesday, July 15, 2014

Testing 433Mhz RF antennas with RTL-SDR

A couple months ago I picked up a RTL2832U dongle to use with SDR#.  I've been testing 433Mhz RF modules, and wanted to figure out what kind of wire antenna works best.

Antenna theory is rather complicated, and designing an efficient antenna involves a number of factors including matching the output impedance of the transmitter.  Since I don't have detailed specs on the RF transmitter modules, I decided to try a couple different antenna designs, and use RTL-SDR to measure their performance.

I started with a ~16.5cm (6.5" for those who are stuck on imperial measurements) long piece of 24awg copper wire (from a spool of ethernet cable).  One-quarter wavelength would be 17.3cm (300m/433.9Mhz), however a resonant quarter-wave monopole antenna is supposed to be slightly shorter.  I started up SDR#, turned off AGC and set the gain fixed at 12.5dB.  The signal peaked at almost -10db:

The next thing I tried was coiling the antenna around a pen in order to make it a helical antenna.  This made the performance a lot (>10dB) worse:

I also tried a couple uncommon variations like a loop and bowtie antenna.  All were worse than the monopole.

The last thing I tried was a dipole, by adding another 16.5cm piece of wire soldered to the ground pin on the module.This gave the best performance of all, nearly 10dB better than the monopole.  An impedance-matched half-wave dipole is supposed to have about 3dB wrose gain than a quarter-wave monopole.  Given the improvement, I suspect the output impedance on the 433Mhz transmit modules is much closer to the ~70Ohm impedance of a half-wave dipole than it is to the ~35Ohm impedance of a quarter-wave monopole.

Have any other ideas on how to improve the antenna design?  Leave a comment.

Last minute update: I tried a 1/4-wave monopole wire antenna on the RTL dongle, and got 2-3dB better signal reception at 433Mhz than the stock antenna.  I tried a full-wave (69cm) wire antenna, and it performed better than the stock antenna, but slightly worse than the 1/4-wave monopole.