Sunday, October 12, 2014

USB DC boost converters

I recently purchased some USB boost converters and some AA battery holders to make 5V portable power sources.  The boost converters were $6.45 for 10 from AliExpress store XM Electronic trade, and the battery holders were 16c each at Tayda.
A few of the boost converter modules had a piece cracked off the 4.7uF inductor, but otherwise they were in good order.  I trimmed the leads from the battery holder and soldered them to the boost converter input.  I slightly bent the USB connector tabs so they fit into the holes on the back of the battery holder, and hot glued the board to the battery holder.

The modules were advertised as "input voltage: 1-5V" and "Output Current: Rated 1A-1.5A (single lithium input)".  I put in 2 NiMh cells, and the read LED on the boost module lit up.  The input from the batteries was 2.6V and the output voltage with no load was 5.08V.  I measured the current at between 2 and 3 mA.  If the modules output 1-1.5A with a 3.7V lithium battery for input, then I calculated they should output at least 600mA with 2.4V in from a couple of NiMh AA cells.  Other vendors advertise specs of the same modules as, "output current of 500 ~ 600MA with two AA batteries", so my 600mA calculation seems about right.

I started load testing with a 68Ohm resistor, and the output was 5.12V for an output current of 75mA.  With a 34Ohm load the output voltage was 4.89V.  The USB voltage is supposed to be 5V +- .25V, so getting 600mA output without the voltage dropping below 4.75V was looking unlikely.  For the next load test I used an old 15W car speaker with a DC resistance of 13Ohms.  With the speaker connected the voltage was only 4.48V, giving an output current of 345mA.  At this load, the output voltage from the batteries was 2.4V.  Interpolating between the results indicates the modules would output not much more than 200mA before dropping below 4.75V.

The last thing I tried was charging a phone.  When I plugged in my wife's iPhone, nothing happened.  In order to be identified as a USB charging port, the D+ and D- pins need to be shorted, or for a high-power (over 500mA) charging port they need to have a voltage divider from the 5V power.  I checked the pins with a meter, and they were not connected.  I then soldered a small jumper to short D+ and D-, and tried plugging in my wife's iPhone again.  This time the screen indicated the phone was charging.

Since the modules did not perform as advertised, I messaged the AliExpress seller XM Electronic trade/Allen Lau with the details of my testing, and requested a partial refund.  After four days he did not respond so I opened a dispute with AliExpress.  Within 12 hours, he rejected the dispute only stating, "There was no evidence of right".  In the past I've encountered sellers on AliExpress that have even given full refunds after learning their products don't perform as advertised.  With Allen Lau it's the first time I've encountered this kind of "I don't give a shit" attitude.

Although the modules do not perform as advertised, they are good for a 5V power source up to about 250mA.  You can also find boost converters with a beefier 47uF inductor, but from testing results I've read online they're not much better; with 2.4V in, the output voltage of the bigger modules drops below 2.75V at around 300mA.  For backup power for a mobile phone, one of the mobile power banks using a lithium 18650 cell may be a better idea.

If you're just looking to boost the voltage from a battery for a MCU project, check out Sprites mods.

Saturday, October 4, 2014

nRF24l01+ reloaded

Since writing my post on nrf24l01 control with 3 ATtiny85 pins, I've realized these modules are quite popular.  Of over a dozen posts on my blog, it has the most hits.  From the comments in the blog, I can see that despite a lot of online resources about these modules, there's still some not clearly documented problems that people run into.  So I'll go into some more detail on how they work, share some of my tips for testing & debugging, and more.

The biggest source of confusion seems to be with holding CE high.   Section 6.1.6 of the datasheet specifies a 10us high pulse to empty one level of the TX fifo (normal mode) or hold CE high to empty all levels of the TX fifo.  This is borne out in testing - CE can be held high for a transmitting device.

The above is a picture of a $1.50 wireless transmitter node I made.  Sorry about the poor focus - I don't have a good macro mode on my camera.  It's made with an ATtiny88-au glued and soldered with 30AWG wire to a nRF24l01+ module.  The pin arrangement is as follows:

ATtiny88 ------ nRF module
14 (PB2/SS) --- 4 (CSN)
15 (PB3/MO) --- 6 (MOSI)
16 (PB4/MI) --- 7 (MISO)
17 (PB5/SCK) -- 5 (SCK)
29 (PC6/RST) -- 3 (CE)

Connecting reset to CE keeps CE high when the AVR is running, and it also gives me an easy way to program the ATtiny88.  By connecting the CE pin to the RST pin of my programmer (a USBasp), the existing pins on the nRF module can be used as a programming header.  As long as CSN is not grounded, the nRF will not interfere with the communication on the SPI bus.  In my testing it worked with CSN floating, but it would probably be best to tie it high or connect a pullup resistor between CSN and Vcc.  A small 0603 15K chip resistor should work nicely for this.

The module is powered by a CR2032 cell in a holder on the back of the module.  When I wrote my post about Cheap battery-powered micro-controller projects, I hadn't done much experimenting with coin cells.  Although some CR2032 batteries can output 20mA continuous, the no-name cells I bought from DX certainly cannot.  I connected a 20uF electrolytic capacitor to provide enough voltage during transmits (around 12mA), and put the module in power-down mode the rest of the time.

While keeping CE high is fine for a device that is only transmitting or only receiving, you might run into a problem if you try to switch between the two.  The reason can be found in the state diagram from the datasheet: (contrast enhanced to make the state transitions easier to see)
The state diagram shows there is no way to go directly from Rx to Tx mode or the other way around.  The module must go into the standby-I state by setting CE low, or into the power down state by setting PWR_UP=0.  With CE held high, the only way to change between Rx and Tx mode is to go through power down mode.  Another option that doesn't require a separate pin for CE control is to connect CE to CSN.  This would allow you to use some nRF libraries that rely on CE toggling.  A potential drawback to this is if you want to keep the module in receive mode while polling the status (#7) register for incoming packets.  Each time CSN is brought low to poll the status, it will bring CE low as well, which will cause the module to drop out of Rx mode.  It only takes 130uS to return to Rx mode once CE goes high, so this may not be a concern for you.

Debugging

Using a red LED in series with Vcc as my post on nrf24l01 control with 3 ATtiny85 pins makes it possible to quickly see how much power the module is using.  When in power down mode with CSN high, no light is visible from the LED due to the very low power use.  When powered up in Rx or Tx mode, the LED glows brightly, with 10-15mA of current.  By watching the power use I was able to tell that after the Rx fifo is full, the module stops receiving, causing the power consumption to drop.  The diagram in section 7.5.2 of the datasheet indicates that will happen if CE is low, but it still happens even with CE high.  Once a single packet is read out from the Rx fifo, it starts listening for packets again.

I also found connecting a LED to the IRQ pin (#8) helpful to see when the Rx or Tx IRQ fired.

Lastly, when testing connectivity, start with enhanced shockburst disabled (EN_AA = 0), and CRC off.  Then once you've confirmed connectivity, enable the features you want.  If you decide to use CRC, go with 2-byte CRC (EN_CRC and CRC0 in the CONFIG register) since a 1-byte CRC will miss 1 in every 256 bit errors vs 1 in 65,536 for a 2-byte CRC.

Undocumented registers

Register 6 (RF_SETUP) seems to be a 9-bit register.  Attempting to write 2 bytes to the register of all 1s (ff ff) followed by a read results in the following response:
0e ff 01
For other registers documented as being a single byte, attempts to read multiple bytes results in the same byte being repeated after the status byte.

There's also an undocumented multi-byte register at address 1e.  I found some code online that refers to this register as AGC_CONFIG, which implies it is for automatic gain control.  I could not find any documentation on how to use this register.  With my modules, the first three bytes of this register defaulted to 6d 66 05.  Reading them back after writing all 1s resulted in fd ff 07, so some bits are fixed at 0.

Sunday, September 14, 2014

On-chip decoupling capacitors


In virtually all of my micro-controller projects, I'll use 0.1uF ceramic capacitors between Vcc and Gnd.  Depending on the power draw of the MCU and inductance on the power lines, they may not be necessary, but at a cost of a penny or less each there's little reason not to use them.

I remembered seeing CPUs that have on-chip decoupling capacitors, and thought it would be nice if the MCUs I'm using had the same.  When working with small projects on mini breadboards, not having to find space for the decoupling cap would be convenient.  It would also save me the trouble of digging through my disorganized collection of components looking for that extra capacitor.

My first idea was to glue a 0805 (2mm x 1.25mm) MLCC to the top of the chip, and then solder 30AWG wire-wrap to the power and ground leads.  I used contact cement, and although it seemed secure after drying for about 30 minutes, once I added flux and touched it with my soldering iron tip it moved freely.  Then I tried a small drop of super glue, but for some reason it wasn't dry after an hour; maybe it was defective.  If someone knows of a glue that would work well, let me know in the comments or send me an email.

Even after drying for a day, neither the contact cement nor the super glue would securely hold the capacitors while I tried to solder them.  With the help of a pair of tweezers I was able to solder a MLCC to the top of an ATtiny85 as shown in the photo above.

For 28-pin DIP AVR MCUs that have ground and power on adjacent pins, the job is a lot easier.  Here's a ATtiny88-PU with a 0805 MLCC:

The easiest method I came up with doesn't require any glue.  I trimmed, then soldered the leads of a ceramic disc capacitor to the power and ground pins of an ATtiny84a:



Friday, September 12, 2014

Inside the "$10" Rockchip TV dongle

Last year Rockchip demoed a "$10" Miracast/DLNA TV dongle.  The $10 was not a target retail price, but probably the BOM cost.  They can be found for under $20 including shipping from China.  I bought one and posted a review of the Miracast and DLNA functionality.  In this post I'll document a basic teardown of the dongle, along with instructions on setting up a root console connection.


There are no screws holding the dongle together; the top and bottom of the case simply snap together.  The RK2928 is underneath a small heat spreader.  Next to it is a Spectek PE937-15E 256MB DDR3 chip. the rest of the components appear to be for power regulation.


On the bottom of the board is a Winbond serial flash chip and a Realtek rtl8188ETV USB wifi module.  Small pads labeled TX, RX, and GND are visible near the edge of the board.  They are obviously for a serial console.  I soldered some 30AWG wire-wrap wire to the pads, and to some 0.1" header pins.  With my soldering iron I melted an opening in the plastic case where I hot glued the header pins.

After putting the dongle back together, I connected the serial console to a USB-TTL serial adapter, and powered up the module.  I could see the Rx LED on the serial module flickering, indicating it was receiving console output data.  I started a terminal program, and was not seeing anything when I tried 9600 and 19,200bps.  Then I remembered my logic analyzer has a serial baud rate detection.  I hooked it up, ran a capture while the dongle was booting, and found the baud rate was 115,200kbps.

After setting the terminal program to 115,200, I could see what I recognized as the console output of a Linux kernel.  When the output stopped, I saw a "#" indicating a root shell prompt - no password required.  However the prompt wouldn't last long before the dongle seemed to reboot and repeat the start-up console output.

I first checked the board to make sure nothing was shorting from the connections I made to the serial port pads.  I then used a home-made USB cable with exposed power connections to measure the voltage.  I found the voltage was briefly dipping below 4.5V when the dongle was rebooting.  The dongle's peak power draw was too much for the PC USB port.  My solution was a 220uF capacitor connected to my home-made USB cable.  With the capacitor added, the voltage stayed above 4.8V, and there were no reboots.

The Linux installation seems to be a stripped-down android image rather than a standard linux distribution.  While the 256MB of RAM is ample for an embedded linux distribution, the 16MB of flash makes installing something like Picuntu Linux very difficult.

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.