After using the AVR-ISP mkII for years (actually the cheap eBay knock-offs) to program ATMEL AVR microcontrollers, today I gave the Bus Pirate a shot. Far more than just a microcontroller programmer, this little board is basically a serial interface to basic microcontroller peripherals. In a nutshell, you plug it in via USB and it looks like a serial port which has a command-line interface that lets you do things like turn pins on and off, perform voltage measurements, and it naively supports bidirectional use of common protocols like I2C, SPI, UART, and even HD44780 series LCDs. Note that although you could directly interface with the Bus Pirate using HyperTerminal, I recommend using TeraTerm. It can supply voltages (3.3V and 5V) to power small circuits, and if current draw is too high (indicating something is hooked-up wrong) it automatically turns the supply off. So clever! At <$30, it’s a cool tool to have around. In addition, it’s naively supported as an AVR programmer by AVRDUDE. Although I could write assembly to perform tasks, I almost always write in C for the convenience. For my reference (and that of anyone who may want to do something similar), I’m posting the simplest-case method I use to program AVR microcontrollers with the Bus Pirate on Windows (noting that Linux would be nearly identical). I also wrote a Python script to connect with the Bus Pirate and run simple commands (which turns the power supply on and report the voltage of the VCC line immediately after programming completes).  Yes, there are fancy packages that allow you to interact with Bus Pirate from Python, but the advantage of my method is that it runs from native Python libraries! To get this all up and running for yourself, just install WinAVR (which supplies AVRDUDE and AVR-GCC) and Python 3. I assume this code will work just as well on Python 2, but haven’t tried.

IMG_7092 (1)
the Bus Pirate programming an ATTiny85 microcontroller

 

To ensure my Bus Pirate is working properly, I start off by running the Bus Pirate’s built-in test routine. For full details read the guide. It just involves connecting two pairs of pins together as shown in the picture here, connecting to the Bus Pirate with the serial terminal, and running the command “~”. It will output all sorts of useful information. Once I know my hardware is up and running, I’m good to continue.

Bpv3v2go-pinout

Here’s the code which runs on the microcontroller to twiddle all the pins (saved as main.c). Note that my MCU is an ATTiny85. I’m using standard clock settings (internal RC clock, 8MHz), but if I wanted to modify fuses to do things like use an external clock source or crystal, I’d calculate them with engbedded’s handy dandy fuse calculator (which also shows AVRdude arguments needed to make the change!).

#define	F_CPU (8000000UL)
#include <avr/io.h>
#include <util/delay.h>

int main (void)
{
    DDRB = 255; 
    while(1) 
    {
        PORTB ^= 255;
        _delay_ms(500);
    }
}

To compile the code and program the MCU with it, I always have a bash script in the same folder that I can double-click on to delete old compiled files (so we don’t accidentally re-program our MCU with old code), compile main.c, and load it onto the MCU using the Bus Pirate. You may have to change COM3 to reflect the com port of your Bus Pirate. Note that it is required that you disconnect other terminals from the Bus Pirate before doing this, otherwise you’ll get an “access denied” error.

@echo off
del *.elf
del *.hex
avr-gcc -mmcu=attiny85 -Wall -Os -o main.elf main.c
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c buspirate -p attiny85 -P com3 -e -U flash:w:main.hex
python up.py

Although the programmer briefly supplies my MCU with power from the +5V pin, it’s cut after programming completes. Rather than manually re-opening my terminal program, re-connecting with the bus pirate, re-setting the mode (command “m”) to something random (DIO, command “9”), and re-enableing voltage output (command “W”) just to see my LED blink, I want all that to be automated. Thanks python for making this easy. The last line calls “up.py”. This fancy script even outputs the voltage of the VCC line after it’s turned on!

"""python3 control of buspirate (SWHarden.com)"""

import serial

BUSPIRATE_PORT = 'com3' #customize this! Find it in device manager.

def send(ser,cmd):
    """send the command and listen to the response."""
    ser.write(str(cmd+'\n').encode('ascii')) # send our command
    for line in ser.readlines(): # while there's a response
        print(line.decode('utf-8').strip()) # show it

ser=serial.Serial(BUSPIRATE_PORT, 115200, timeout=1) # is com free?
assert ser.isOpen() #throw an exception if we aren't connected
send(ser,'#') # reset bus pirate (slow, maybe not needed)
send(ser,'m') # change mode (goal is to get away from HiZ)
send(ser,'9') # mode 9 is DIO
send(ser,'W') # turn power supply to ON. Lowercase w for OFF.
send(ser,'v') # show current voltages
ser.close() # disconnect so we can access it from another app
print("disconnected!") # let the user know we're done.

When “burn.cmd” is run, the code is compiled and loaded, the power supply is turned on (and killed if too much current is drawn!), and the voltage on VCC is reported. The output is:

C:\Users\scott\Documents\important\AVR\2016-07-13 ATTiny85 LEDblink>burn.cmd

Detecting BusPirate...
**
**  Bus Pirate v3a
**  Firmware v5.10 (r559)  Bootloader v4.4
**  DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
**  http://dangerousprototypes.com
**
BusPirate: using BINARY mode
avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.12s

avrdude: Device signature = 0x1e930b
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: input file main.hex auto detected as Intel Hex
avrdude: writing flash (84 bytes):

Writing | ################################################## | 100% 3.12s

avrdude: 84 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex auto detected as Intel Hex
avrdude: input file main.hex contains 84 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 2.72s

avrdude: verifying ...
avrdude: 84 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done.  Thank you.

#
RESET

Bus Pirate v3a
Firmware v5.10 (r559)  Bootloader v4.4
DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
http://dangerousprototypes.com
HiZ>
m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>
9
Ready
DIO>
W
Power supplies ON
DIO>
v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk)
GND     3.3V    5.0V    ADC     VPU     AUX     CLK     MOSI    CS      MISO
P       P       P       I       I       I       I       I       I       I
GND     3.17V   5.00V   0.00V   0.00V   L       L       L       H       L
DIO>
disconnected!


This is a minimal-case scenario, but can be obviously expanded to perform some complicated tasks! For example, all commands could be run from a single python program. Considering the Bus Pirate’s ability to communicate with so many different protocols (I2C, 2-write, etc.), being able to naively control it from Python without having to install special additional libraries will certainly prove to be convenient.

PS: I noted there is a surprising delay when initializing programming the AVR with the bus pirate. The process hangs for about 10 seconds after the bus pirate introduces itself with the welcome message, then seems to resume at full speed writing to the flash of the microchip. After a bit of Googling, I believe the delay is due to the Bus Pirate slowly bit-banging SPI to initialize the programming sequence. The AVR has rich SPI functionality, some of which involves its own programming. Satisfied with this answer for now, I’m not going to try to speed it up. It’s a little annoying, but not too bad that I won’t use this to program my AVRs.





Additional Resources

I came across the need for a quick and dirty display to show a 4 digit number from a microcontroller. The right way to do this would be to use a microcontroller in combination with a collection of transistors and current limiting resistors, or even a dedicated 7-segment LED driver IC. The wrong way to do this is to wire LEDs directly to microcontroller IO pins to source and sink current way out of spec of the microcontroller… and that’s exactly what I did! With no current limiting resistors, the AVR is sourcing and sinking current potentially far out of spec for the chip. But, heck, it works! With 2 components (just a microcontroller and a 4 digit, 7-segment LED display) and a piece of ribbon cable, I made something that used to be a nightmare to construct (check out this post from 3 years ago when I accomplished the same thing with a rats nest of wires – it was so much work that I never built one again!) The hacked-together method I show today might not be up to spec for medical grade equipment, but it sure works for my test rig application, and it’s easy and cheap to accomplish… as long as you don’t mind breaking some electrical engineering rules. Consider how important it is to know how to hack projects like this together: Although I needed this device, if it were any harder, more expensive, or less convenient to build, I simply wouldn’t have built it! Sometimes hacking equipment together the wrong way is worth it.

IMG_2316
Segments are both current sourced and sunk directly from AVR IO pins. Digits are multiplexed with 1ms illumination duration. I don’t really have a part number for the component because it was a China eBay special. The display was $6.50 for 4 (free shipping). That’s ~$1.65 each. The microcontroller is ~$1.

SCHEMATIC? If you want it, read this.common cathode 7 segment display lcd It’s so simple I don’t feel like making it. Refer to an ATMega48 pin diagram. The LCD is common anode (not common cathode), and here’s the schematic on the right. I got it from eBay (link) for <$2.  The connections are as follows:

  • Segments (-) A…H are directly wired to PD0…PD7
    – I call the decimal point (dp) segment “H”
    – I don’t use current limiting resistors. I’m not making a consumer product. It works fine, especially multiplexed. Yeah I could use transistors and CLRs to drive the segments to have them bright and within current specifications, but I’m not building an airplane or designing a pacemaker, I’m making a test device at minimum cost! Direct LED wiring to my microcontroller is fine for my purposes.
    – I am multiplexing the characters of my display. I could have used a driver IC to simplify my code and eliminate the current / wiring issues described above. A MAX7219 or MAX7221 would have been easy choices for this (note common anode vs. common cathode drivers / displays). It adds an extra $5 to my project cost though, so I didn’t go with a driver. I drove the segments right out of my IO pins.
  • Characters (+) 1…4 are PC0…PC3
  • Obviously I apply +5V and GND to the appropriate AVR pins

Here it all is together in my microcontroller programming set up. I’ll place this device in a little enclosure and an an appropriate BNC connector and either plan on giving it USB power or run it from 3xAA batteries. For now, it works pretty darn well on the breadboard.

Here is my entire programming setup. On the top left is my eBay special USB AVR programmer. On the right is a little adapter board I made to accomodate a 6 pin ISP cable and provide a small breadboard for adding programming jumpers into a bigger breadboard. The breadboard at the bottom houses the microcontroller and the display. No other components! Well, okay, a 0.1uF decoupling capacitor to provide mild debouncing for the TTL input.
Here is my entire programming setup. On the top left is my eBay special USB AVR programmer. On the right is a little adapter board I made to accomodate a 6 pin ISP cable and provide a small breadboard for adding programming jumpers into a bigger breadboard. The breadboard at the bottom houses the microcontroller and the display. No other components! Well, okay, a 0.1uF decoupling capacitor to provide mild debouncing for the TTL input.

Let’s talk about the code. Briefly, I use an infinite loop which continuously displays the value of the volatile long integer “numba”. In the display function, I set all of my segments to (+) then momentarily provide a current sink (-) on the appropriate digit anode for 1ms. I do this for each of the four characters, then repeat. How is the time (the value of “numba”) incremented? Using a hardware timer and its overflow interrupt! It’s all in the ATMega48 datasheet, but virtually every microcontroller has some type of timer you can use to an equivalent effect. See my earlier article “Using Timers and Counters to Clock Seconds” for details. I guess that’s pretty much it! I document my code well enough below that anyone should be able to figure it out. The microcontroller is an ATMega48 (clocked 8MHz with an internal RC clock, close enough for my purposes).

#define F_CPU 8000000UL // 8mhz
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

// for simplicity, define pins as segments
#define A (1<<PD0)
#define B (1<<PD1)
#define C (1<<PD2)
#define D (1<<PD3)
#define E (1<<PD4)
#define F (1<<PD5)
#define G (1<<PD6)
#define H (1<<PD7)

void setDigit(char dig){ // set the digit starting at 0
	PORTC=(1<<dig)|(1<<PC4); // always keep the PC4 pin high
}

void setChar(char c){
	// given a number, set the appropraite segments
	switch(c){
		case 0:	DDRD=A|B|C|D|E|F;	break;
		case 1:	DDRD=B|C;			break;
		case 2:	DDRD=A|B|G|E|D;		break;
		case 3: DDRD=A|B|G|C|D;		break;
		case 4: DDRD=F|G|B|C;		break;
		case 5: DDRD=A|F|G|C|D;		break;
		case 6: DDRD=A|F|G|E|C|D;	break;
		case 7: DDRD=A|B|C;			break;
		case 8: DDRD=A|B|C|D|E|F|G;	break;
		case 9: DDRD=A|F|G|B|C;		break;
		case 31: DDRD=H;			break;
		default: DDRD=0; 			break;
	}
}

void flashNumber(long num){
	char i;

	for (i=0;i<4;i++){
		setChar(num%10);
		if (i==2){DDRD|=H;} // H is the decimal point
		setDigit(3-i);
		num=num/10;
		_delay_ms(1); // time to leave the letter illuminated
	}
}

volatile long numba = 0;
volatile long addBy = 1;

ISR(PCINT1_vect){ // state change on PC4
	if ((PINC&(1<<PC4))==0) {addBy=0;} // pause
	else {numba=0;addBy=1;} // reset to 0 and resume
}

ISR(TIMER1_OVF_vect){
	TCNT1=65536-1250; // the next overflow in 1/100th of a second
	numba+=addBy;	  // add 1 to the secound counter
}

int main(void)
{
	DDRC=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3); // set all characters as outputs
	DDRD=255;PORTD=0; 	// set all segments as outputs, but keep them low

	TCCR1B|=(1<<CS11)|(1<<CS10); // prescaler 64
	TIMSK1|=(1<<TOIE1); //Enable Overflow Interrupt Enable
	TCNT1=65536-1250;   // the next overflow in 1/100th of a second

	// note that PC4 (PCINT12) is an input, held high, and interrupts when grounded
	PCICR |= (1<<PCIE1); // enable interrupts on PCING13..8 -> PCI1 vector
	PCMSK1 |= (1<<PCINT12); // enable PCINT12 state change to be an interrupt
	sei(); // enable global interrupts

	for(;;){flashNumber(numba);} // just show the current number repeatedly forever
}

I edit my code in Notepad++ by the way. To program the chip, I use a bash script…

avr-gcc -mmcu=atmega48 -Wall -Os -o main.elf main.c -w
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c usbtiny -p m48 -F -U flash:w:"main.hex":a -U lfuse:w:0xe2:m -U hfuse:w:0xdf:m

Nothing here is groundbreaking. It’s simple, and convenient as heck. Hopefully someone will be inspired enough by this write-up that, even if they don’t recreate this project, they’ll jump at the opportunity to make something quick and dirty in the future. It’s another example that goes to show that you don’t have to draw schematics, run simulations, do calculations and etch boards to make quick projects. Just hack it together and use it.

Update a two days later… I found a similarly quick and dirty way to package this project in an enclosure. I had on hand some 85x50x21mm project boxes (eBay, 10 for $14.85, free shipping, about $1.50 each) so I used a nibbler to hack out a square to accomodate the display. After a little super glue, ribbon cable, and solder, we’re good to go!

Related reading for the technically inclined:

 





Additional Resources

How long does a particular bit of Morse code take to transmit at a certain speed? This is a simple question, but when sitting down trying to design schemes for 10-minute-window QRSS, it doesn’t always have a quick and simple answer. Yeah, you could sit down and draw the pattern on paper and add-up the dots and dashes, but why do on paper what you can do in code? The following speaks for itself. I made the top line say my call sign in Morse code (AJ4VD), and the program does the rest. I now see that it takes 570 seconds to transmit AJ4VD at QRSS 10 speed (ten second dots), giving me 30 seconds of free time to kill.

program output
Output of the following script, displaying info about “AJ4VD” (my call sign).

Here’s the Python code I whipped-up to generate the results:

xmit=" .- .--- ....- ...- -..  " #callsign
dot,dash,space,seq="_-","_---","_",""
for c in xmit:
    if c==" ": seq+=space
    elif c==".": seq+=dot
    elif c=="-": seq+=dash
print "QRSS sequence:n",seq,"n"
for sec in [1,3,5,10,20,30,60]:
    tot=len(seq)*sec
    print "QRSS %02d: %d sec (%.01f min)"%(sec,tot,tot/60.0)

How ready am I to implement this in the microchip? Pretty darn close. I’ve got a surprisingly stable software-based time keeping solution running continuously executing a “tick()” function thanks to hardware interrupts. It was made easy thanks to Frank Zhao’s AVR Timer Calculator. I could get it more exact by using a /1 prescaler instead of a /64, but this well within the range of acceptability so I’m calling it quits!

Output frequency is 1.0000210 Hz. That'll drift 2.59 sec/day. I'm cool with that.
Output frequency is 1.0000210 Hz. That’ll drift 2.59 sec/day. I’m cool with that.




Additional Resources

Today I rigineered my frequency counter to output frequency to a computer via a USB interface. You might remember that I did this exact same thing two years ago, but unfortunately I fell victim to accidental closed source. When I rigged it the first time, I stupidly tried to get fancy and add USB interface with V-USB requiring special drivers and special software code to retrieve the data. The advantage was that the microcontroller spoke directly to the PC USB port via 2 pins requiring no extra hardware. The stinky part is that I’ve since lost the software I wrote necessary to decode the data. Reading my old post, I see I wrote “Although it’s hard for me, I really don’t think I can release this [microchip code] right now. I’m working on an idiot’s guide to USB connectivity with ATMEL microcontrollers, and it would cause quite a stir to post that code too early.”  Obviously I never got around to finishing it, and I’ve since lost the code. Crap! I have this fancy USB “enabled” frequency counter, but no ability to use it. NOTE TO SELF: NEVER POST PROJECTS ONLINE WITHOUT INCLUDING THE CODE! I guess I have to crack this open again and see if I can reprogram it…

IMG_0285

My original intention was just to reprogram the IC and add serial USART support, then use a little FTDI adapter module to serve as a USB serial port. That will be supported by every OS on the planet out of the box.  Upon closer inspection, I realized I previously used an ATMega48 which has trouble being programmed by AVRDUDE, so I whipped up a new perf-board based around an ATMega8. I copied the wires exactly (which was stupid, because I didn’t have it written down which did what, and they were in random order), and started attacking the problem in software.

IMG_0283 IMG_0284

The way the microcontroller reads frequency is via the display itself. There are multiplexed digits, so some close watching should reveal the frequency. I noticed that there were fewer connections to the microcontroller than expected – a total of 12. How could that be possible? 8 seven-segment displays should be at least 7+8=15 wires. What the heck? I had to take apart the display to remind myself how it worked. It used a pair of ULN2006A darlington transistor arrays to do the multiplexing (as expected), but I also noticed it was using a CD4511BE BCD-to-7-segment driver to drive the digits. I guess that makes sense. That way 4 wires can drive 7 segments. 8+4=12 wires, which matches up. Now I feel stupid for not realizing it in the first place. Time to screw things back together.

IMG_0288

 

Here’s the board I made. 3 wires go to the FTDI USB module (GND, VCC 5V drawn from USB, and RX data), black wires go to the display, and the headers are to aid programming. I added an 11.0592MHz crystal to allow maximum serial transfer speed (230,400 baud), but stupidly forgot to enable it in code. It’s all boxed up now, running at 8MHz and 38,400 baud with the internal RC clock. Oh well, no loss I guess.

I wasted literally all day on this. It was so stupid. The whole time I was kicking myself for not posting the code online. I couldn’t figure out which wires were for the digit selection, and which were for the BCD control. I had to tease it apart by putting random numbers on the screen (by sticking my finger in the frequency input hole) and looking at the data flowing out on the oscilloscope to figure out what was what. I wish I still had my DIY logic analyzer. I guess this project was what I built it for in the first place! A few hours of frustrating brute force programming and adult beverages later, I had all the lines figured out and was sending data to the computer.

With everything back together, I put the frequency counter back in my workstation and I’m ready to begin my frequency measurement experiments. Now it’s 9PM and I don’t have the energy to start a whole line of experiments. Gotta save it for another day. At least I got the counter working again!

IMG_0296

 

Here’s the code that goes on the microcontroller (it sends the value on the screen as well as a crude checksum, which is just the sum of all the digits)

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

void USART_Init(void){
	UBRRL = BAUD_PRESCALE;
	UBRRH = (BAUD_PRESCALE >> 8);
	UCSRB = (1<<TXEN);
	UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
	while ( !( UCSRA & (1<<UDRE)) );
	UDR = data;
}

void sendNum(int byte){
	if (byte==0){
		USART_Transmit(48);
	}
	while (byte){
		USART_Transmit(byte%10+48);
		byte-=byte%10;
		byte/=10;
	}
}

void sendBin(int byte){
	char i;
	for (i=0;i<8;i++){
		USART_Transmit(48+((byte>>i)&1));
	}
}

volatile char digits[]={0,0,0,0,0,0,0,0};
volatile char freq=123;

char getDigit(){
	char digit=0;
	if (PINC&0b00000100) {digit+=1;}
	if (PINC&0b00001000) {digit+=8;}
	if (PINC&0b00010000) {digit+=4;}
	if (PINC&0b00100000) {digit+=2;}
	if (digit==15) {digit=0;} // blank
	return digit;
}

void updateNumbers(){
	while ((PINB&0b00000001)==0){} digits[7]=getDigit();
	while ((PINB&0b00001000)==0){} digits[6]=getDigit();
	while ((PINB&0b00010000)==0){} digits[5]=getDigit();
	while ((PINB&0b00000010)==0){} digits[4]=getDigit();
	while ((PINB&0b00000100)==0){} digits[3]=getDigit();
	while ((PINB&0b00100000)==0){} digits[2]=getDigit();
	while ((PINC&0b00000001)==0){} digits[1]=getDigit();
	while ((PINC&0b00000010)==0){} digits[0]=getDigit();
}

int main(void){
	USART_Init();
	char checksum;
	char i=0;
	char digit=0;

	for(;;){
		updateNumbers();
		checksum=0;
		for (i=0;i<8;i++){
			checksum+=digits[i];
			sendNum(digits[i]);
		}
		USART_Transmit(',');
		sendNum(checksum);
		USART_Transmit('n');
		_delay_ms(100);
	}
}

Here’s the Python code to listen to the serial port, though you could use any program (note that the checksum is just shown and not verified):

import serial, time
import numpy
ser = serial.Serial("COM15", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

while True:
    line=ser.readline()[:-1]
    print line

This is super preliminary, but I’ve gone ahead and tested heating/cooling an oscillator (a microcontroller clocked with an external crystal and outputting its signal with CKOUT). By measuring temperature and frequency at the same time, I can start to plot their relationship…

photo 1 (1)

tf





Additional Resources

To maintain high frequency stability, RF oscillator circuits are sometimes “ovenized” where their temperature is raised slightly above ambient room temperature and held precisely at one temperature. Sometimes just the crystal is heated (with a “crystal oven”), and other times the entire oscillator circuit is heated. The advantage of heating the circuit is that other components (especially metal core instructors) are temperature sensitive. Googling for the phrase “crystal oven”, you’ll find no shortage of recommended circuits. Although a more complicated PID (proportional-integral-derivative) controller may seem enticing for these situations, the fact that the enclosure is so well insulated and drifts so little over vast periods of time suggests that it might not be the best application of a PID controller. One of my favorite write-ups is from M0AYF’s site which describes how to build a crystal oven for QRSS purposes. He demonstrates the MK1 and then the next design the MK2 crystal oven controller.  Here are his circuits:

Briefly, desired temperature is set with a potentiometer. An operational amplifier (op-amp) compares the target temperature with measured temperature (using a thermistor – a resistor which varies resistance by tempearture). If the measured temperature is below the target, the op-amp output goes high, and current flows through heating resistors. There are a few differences between the two circuits, but one of the things that struck me as different was the use of negative feedback with the operational amplifier. This means that rather than being on or off (like the air conditioning in your house), it can be on a little bit. I wondered if this would greatly affect frequency stability. In the original circuit, he mentions

The oven then cycles on and off roughly every thirty or forty seconds and hovers around 40 degrees-C thereafter to within better than one degree-C.

I wondered how much this on/off heater cycle affected temperature. Is it negligible, or could it affect frequency of an oscillator circuit? Indeed his application heats an entire enclosure so small variations get averaged-out by the large thermal mass. However in crystal oven designs where only the crystal is heated, such as described by Bill (W4HBK), I’ll bet the effect is much greater. Compare the thermal mass of these two concepts.

How does the amount of thermal mass relate to how well it can be controlled? How important is negative feedback for partial-on heater operation? Can simple ON/OFF heater regulation adequately stabalize a crystal or enclosure? I’d like to design my own heater, pulling the best elements from the rest I see on the internet. My goals are:

  1. use inexpensive thermistors instead of linear temperature sensors (like LM335)
  2. use inexpensive quarter-watt resistors as heaters instead of power resistors
  3. be able to set temperature with a knob
  4. be able to monitor temperature of the heater
  5. be able to monitor power delivered to the heater
  6. maximum long-term temperature stability

Right off the bat, I realized that this requires a PC interface. Even if it’s not used to adjust temperature (an ultimate goal), it will be used to log temperature and power for analysis. I won’t go into the details about how I did it, other than to say that I’m using an ATMEL ATMega8 AVR microcontroller and ten times I second I sample voltage on each of it’s six 10-bit ADC pins (PC0-PC5), and send that data to the computer with USART using an eBay special serial/USB adapter based on FTDI. They’re <$7 (shipped) and come with the USB cable. Obviously in a consumer application I’d etch boards and use the SMT-only FTDI chips, but for messing around at home I a few a few of these little adapters. They’re convenient as heck because I can just add a heater to my prototype boards and it even supplies power and ground. Convenient, right? Power is messier than it could be because it’s being supplied by the PC, but for now it gets the job done. On the software side, Python with PySerial listens to the serial port and copies data to a large numpy array, saving it every once and a while. Occasionally a bit is sent wrong and a number is received incorrectly (maybe one an hour), but the error is recognized and eliminated by the checksum (just the sum of all transmitted numbers). Plotting is done with numpy and matpltolib. Code for all of that is at the bottom of this post.

That’s the data logger circuit I came up with. Reading six channels ten times a second, it’s more than sufficient for voltage measurement. I went ahead and added an op-amp to the board too, since I knew I’d be using one. I dedicated one of the channels to serve as ambient temperature measurement. See the little red thermistor by the blue resistor? I also dedicated another channel to the output of the op-amp. This way I can measure drive to whatever temperature controller circuity I choose to use down the road. For my first test, I’m using a small thermal mass like one would in a crystal oven. Here’s how I made that:

I then build the temperature controller part of the circuit. It’s pretty similar to that previously published. it uses a thermistor in a voltage divider configuration to sense temperature. It uses a trimmer potentiometer to set temperature. An LED indicator light gives some indication of on/off, but keep in mind that a fraction of a volt will turn the Darlington transistor (TIP122) on slightly although it doesn’t reach a level high enough to drive the LED. The amplifier by default is set to high gain (55x), but can be greatly lowered (negative gain actually) with a jumper. This lets me test how important gain is for the circuitry.

controller

When using a crystal oven configuration, I concluded high high gain (cycling the heater on/off) is a BAD idea. While average temperature is held around the same, the crystal oscillates. This is what is occurring above when M0AYF indicates his MK1 heater turns on and off every 40 seconds. While you might be able to get away with it while heating a chassis or something, I think it’s easy to see it’s not a good option for crystal heaters. Instead, look at the low gain (negative gain) configuration. It reaches temperature surprisingly quickly and locks to it steadily. Excellent.

high gain
high gain configuration tends to oscillate every 30 seconds
low gain / negative gain configuration is extremely stable
low gain / negative gain configuration is extremely stable (fairly high temperature)
Here's a similar experiment with a lower target temperature. Noise is due to unregulated USB power supply / voltage reference. Undeniably, this circuit does not oscillate much if any.
Here’s a similar experiment with a lower target temperature. Noise is due to unregulated USB power supply / voltage reference. Undeniably, this circuit does not oscillate much if any.

Clearly low (or negative) gain is best for crystal heaters. What about chassis / enclosure heaters? Let’s give that a shot. I made an enclosure heater with the same 2 resistors. Again, I’m staying away from expensive components, and that includes power resistors. I used epoxy (gorilla glue) to cement them to the wall of one side of the enclosure.

I put a “heater sensor” thermistor near the resistors on the case so I could get an idea of the heat of the resistors, and a “case sensor” on the opposite side of the case. This will let me know how long it takes the case to reach temperature, and let me compare differences between using near vs. far sensors (with respect to the heating element) to control temperature. I ran the same experiments and this is what I came up with!

heater temperature (blue) and enclosure temperature (green) with low gain (first 20 minutes), then high gain (after) operation. High gain sensor/feedback loop is sufficient to induce oscillation, even with the large thermal mass of the enclosure
CLOSE SENSOR CONTROL, LOW/HIGH GAIN: TOP: heater temperature (blue) and enclosure temperature (green) with low gain (first 20 minutes), then high gain (after) operation. High gain sensor/feedback loop is sufficient to induce oscillation, even with the large thermal mass of the enclosure. BOTTOM: power to the heater (voltage off the op-amp output going into the base of the Darlington transistor). Although I didn’t give the low-gain configuration time to equilibrate, I doubt it would have oscillated on a time scale I am patient enough to see. Future, days-long experimentation will be required to determine if it oscillates significantly.
Even with the far sensor (opposite side of the enclosure as the heater) driving the operational amplifier in high gain mode, oscillations occur. Due to the larger thermal mass and increased distance the heat must travel to be sensed they take much longer to occur, leading them to be slower and larger than oscillations seen earlier when the heater was very close to the sensor.
FAR SENSOR CONTROL, HIGH GAIN: Even with the far sensor (opposite side of the enclosure as the heater) driving the operational amplifier in high gain mode, oscillations occur. Blue is the far sensor temperature. Green is the sensor near the heater temperature. Due to the larger thermal mass and increased distance the heat must travel to be sensed they take much longer to occur, leading them to be slower and larger than oscillations seen earlier when the heater was very close to the sensor.

Right off the bat, we observe that even with the increased thermal mass of the entire enclosure (being heated with two dinky 100 ohm 1/4 watt resistors) the system is prone to temperature oscillation if gain is set too high. For me, this is the final nail in the coffin – I will never use a comparator-type high gain sensor/regulation loop to control heater current. With that out, the only thing to compare is which is better: placing the sensor near the heating element, or far from it. In reality, with a well-insulated device like I seem to have, it seems like it doesn’t make much of a difference! The idea is that by placing it near the heater, it can stabilize quickly. However, placing it far from the heater will give it maximum sensation of “load” temperature. Anywhere in-between should be fine. As long as it’s somewhat thermally coupled to the enclosure, enclosure temperature will pull it slightly away from heater temperature regardless of location. Therefore, I conclude it’s not that critical where the sensor is placed, as long as it has good contact with the enclosure. Perhaps with long-term study (on the order of hours to days) slow oscillations may emerge, but I’ll have to build it in a more permanent configuration to test it out. Lucky, that’s exactly what I plan to do, so check back a few days from now!

Since the data speaks for itself, I’ll be concise with my conclusions:

  • two 1/4 watt 100 Ohm resistors in parallel (50 ohms) are suitable to heat an insulated enclosure with 12V
  • two 1/4 watt 100 Ohm resistors in parallel (50 ohms) are suitable to heat a crystal with 5V
  • low gain or negative gain is preferred to prevent oscillating tempeartures
  • Sensor location on an enclosure is not critical as long as it’s well-coupled to the enclosure and the entire enclosure is well-insulated.

I feel satisfied with today’s work. Next step is to build this device on a larger scale and fix it in a more permanent configuration, then leave it to run for a few weeks and see how it does. On to making the oscillator! If you have any questions or comments, feel free to email me. If you recreate this project, email me! I’d love to hear about it.

Here’s the code that went on the ATMega8 AVR (it continuously transmits voltage measurements on 6 channels).

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

/*
8MHZ: 300,600,1200,2400,4800,9600,14400,19200,38400
1MHZ: 300,600,1200,2400,4800
*/
#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

/*
ISR(ADC_vect)
{
    PORTD^=255;
}
*/

void USART_Init(void){
	UBRRL = BAUD_PRESCALE;
	UBRRH = (BAUD_PRESCALE >> 8);
	UCSRB = (1<<TXEN);
	UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
	while ( !( UCSRA & (1<<UDRE)) );
	UDR = data;
}

void sendNum(long unsigned int byte){
	if (byte==0){
		USART_Transmit(48);
	}
	while (byte){
		USART_Transmit(byte%10+48);
		byte-=byte%10;
		byte/=10;
	}
}

int readADC(char adcn){
	ADMUX = 0b0100000+adcn;
	ADCSRA |= (1<<ADSC); // reset value
	while (ADCSRA & (1<<ADSC)) {}; // wait for measurement
	return ADC>>6;
}

int sendADC(char adcn){
	int val;
	val=readADC(adcn);
	sendNum(val);
	USART_Transmit(',');
	return val;
}

int main(void){
	ADCSRA = (1<<ADEN)  | 0b111;
	DDRB=255;
	USART_Init();
	int checksum;

	for(;;){
		PORTB=255;
		checksum=0;
		checksum+=sendADC(0);
		checksum+=sendADC(1);
		checksum+=sendADC(2);
		checksum+=sendADC(3);
		checksum+=sendADC(4);
		checksum+=sendADC(5);
		sendNum(checksum);
		USART_Transmit('n');
		PORTB=0;
		_delay_ms(200);
	}
}

Here’s the command I used to compile the code, set the AVR fuse bits, and load it to the AVR.

del *.elf
del *.hex
avr-gcc -mmcu=atmega8 -Wall -Os -o main.elf main.c -w
pause
cls
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c usbtiny -p m8 -F -U flash:w:"main.hex":a -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m

Here’s the code that runs on the PC to listen to the microchip, match the data to the checksum, and log it occasionally. 

import serial, time
import numpy
ser = serial.Serial("COM16", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

while True:
    line=ser.readline()[:-1]
    lines+=1
    if "," in line:
        line=line.split(",")
        for i in range(len(line)):
            line[i]=int(line[i][::-1])

    if line[-1]==sum(line[:-1]):
        line=[time.time()]+line[:-1]
        print lines, line
        data.append(line)
    else:
        print  lines, line, "<-- FAIL"

    if lines%50==49:
        numpy.save("data.npy",data)
        print "nSAVINGn%d lines in %.02f sec (%.02f vals/sec)n"%(lines,
            time.time()-t1,lines/(time.time()-t1))

Here’s the code that runs on the PC to graph data.

import matplotlib
matplotlib.use('TkAgg') # <-- THIS MAKES IT FAST!
import numpy
import pylab
import datetime
import time

def adc2F(adc):
    Vo=adc*5.0/1024.0
    K=Vo*100
    C=K-273
    F=C*(9.0/5)+32
    return F

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

def adc2V(adc):
    Vo=adc*5.0/1024.0
    return Vo

if True:
    print "LOADING DATA"
    data=numpy.load("data.npy")
    data=data
    print "LOADED"

    fig=pylab.figure()
    xs=data[:,0]
    tempAmbient=data[:,1]
    tempPower=data[:,2]
    tempHeater=data[:,3]
    tempCase=data[:,4]
    dates=(xs-xs[0])/60.0
    #dates=[]
    #for dt in xs: dates.append(datetime.datetime.fromtimestamp(dt))

    ax1=pylab.subplot(211)
    pylab.title("Temperature Controller - Low Gain")
    pylab.ylabel('Heater (ADC)')
    pylab.plot(dates,tempHeater,'b-')
    pylab.plot(dates,tempCase,'g-')
    #pylab.axhline(115.5,color="k",ls=":")

    #ax2=pylab.subplot(312,sharex=ax1)
    #pylab.ylabel('Case (ADC)')
    #pylab.plot(dates,tempCase,'r-')
    #pylab.plot(dates,tempAmbient,'g-')
    #pylab.axhline(0,color="k",ls=":")

    ax2=pylab.subplot(212,sharex=ax1)
    pylab.ylabel('Heater Power')
    pylab.plot(dates,tempPower)

    #fig.autofmt_xdate()
    pylab.xlabel('Elapsed Time (min)')

    pylab.show()

print "DONE"




Additional Resources

In an effort to resume previous work [A, B, C, D] on developing a crystal oven for radio frequency transmitter / receiver stabilization purposes, the first step for me was to create a device to accurately measure and log temperature. I did this with common, cheap components, and the output is saved to the computer (over 1,000 readings a second). Briefly, I use a LM335 precision temperature sensor ($0.70 on mouser) which outputs voltage with respect to temperature. It acts like a Zener diode where the breakdown voltage relates to temperature. 2.95V is 295K (Kelvin), which is 22ºC / 71ºF. Note that Kelvin is just ºC + 273.15 (the difference between freezing and absolute zero). My goal was to use the ADC of a microcontroller to measure the output. The problem is that my ADC (one of 6 built into the ATMEL ATMega8 microcontroller) has 10-bit resolution, reporting steps from 0-5V as values from 0-1024. Thus, each step represents 0.0049V (0.49ºC / 0.882ºF). While ~1ºF resolution might be acceptable for some temperature measurement or control applications, I want to see fractions of a degree because radio frequency crystal temperature stabilization is critical. Here’s a video overview.

This is the circuit came up with. My goal was to make it cheaply and what I had on hand. It could certainly be better (more stable, more precise, etc.) but this seems to be working nicely. The idea is that you set the gain (the ratio of R2/R1) to increase your desired resolution (so your 5V of ADC recording spans over just several ºF you’re interested in), then set your “base offset” temperature that will produce 0V. In my design, I adjusted so 0V was room temperature, and 5V (maximum) was body temperature. This way when I touched the sensor, I’d watch temperature rise and fall when I let go.  Component values are very non-critical. LM324 is powered 0V GND and +5V Vcc. I chose to keep things simple and use a single rail power supply. It is worth noting that I ended-up using a 3.5V Zener diode for the positive end of the potentiometer rather than 5V.  If your power supply is well regulated 5V will be no problem, but as I was powering this with USB I decided to go for some extra stability by using a Zener reference.

precision thermometer LM335 LM324 microcontroller

 

On the microcontroller side, analog-to-digital measurement is summed-up pretty well in the datasheet. There is a lot of good documentation on the internet about how to get reliable, stable measurements. Decoupling capacitors, reference voltages, etc etc. That’s outside the scope of today’s topic. In my case, the output of the ADC went into the ATMega8 ADC5 (PC5, pin 28). Decoupling capacitors were placed at ARef and AVcc, according to the datasheet. Microcontroller code is at the bottom of this post.

To get the values to the computer, I used the USART capability of my microcontroller and sent ADC readings (at a rate over 1,000 a second) over a USB adapter based on an FTDI FT232 chip. I got e-bay knock-off FTDI evaluation boards which come with a USB cable too (they’re about $6, free shipping). Yeah, I could have done it cheaper, but this works effortlessly. I don’t use a crystal. I set fuse settings so the MCU runs at 8MHz, and thanks to the nifty online baud rate calculator determined I can use a variety of transfer speeds (up to 38400). At 1MHz (if DIV8 fuse bit is enabled) I’m limited to 4800 baud. Here’s the result, it’s me touching the sensor with my finger (heating it), then letting go.

finger touch
Touching the temperature sensor with my finger, voltage rose exponentially. When removed, it decayed exponentially – a temperature RC circuit, with capacitance being the specific heat capacity of the sensor itself. Small amounts of jitter are expected because I’m powering the MCU from unregulated USB +5V.

I spent a while considering fancy ways to send the data (checksums, frame headers, error correction, etc.) but ended-up just sending it old fashioned ASCII characters. I used to care more about speed, but even sending ASCII it can send over a thousand ADC readings a second, which is plenty for me. I ended-up throttling down the output to 10/second because it was just too much to log comfortable for long recordings (like 24 hours). In retrospect, it would have made sense to catch all those numbers and do averaging on the on the PC side.

I keep my house around 70F at night when I'm there, and you can see the air conditioning kick on and off. In the morning the AC was turned off for the day, temperature rose, and when I got back home I turned the AC on and it started to drop again.
I keep my house around 70F at night when I’m there, and you can see the air conditioning kick on and off. In the morning the AC was turned off for the day, temperature rose, and when I got back home I turned the AC on and it started to drop again.

On the receive side, I have nifty Python with PySerial ready to catch data coming from the microcontroller. It’s decoded, turned to values, and every 1000 receives saves a numpy array as a NPY binary file. I run the project out of my google drive folder, so while I’m at work I can run the plotting program and it loads the NPY file and shows it – today it allowed me to realize that my roomate turned off the air conditioning after I left, because I saw the temperature rising mid-day. The above graph is temperature in my house for the last ~24 hours. That’s about it! Here’s some of the technical stuff.

AVR ATMega8 microcontroller code:

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

/*
8MHZ: 300,600,1200,2400,4800,9600,14400,19200,38400
1MHZ: 300,600,1200,2400,4800
*/
#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

/*
ISR(ADC_vect)
{
    PORTD^=255;
}
*/

void USART_Init(void){
	UBRRL = BAUD_PRESCALE;
	UBRRH = (BAUD_PRESCALE >> 8);
	UCSRB = (1<<TXEN);
	UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
	while ( !( UCSRA & (1<<UDRE)) );
	UDR = data;
}

void sendNum(long unsigned int byte){
	if (byte==0){
		USART_Transmit(48);
	}
	while (byte){
		USART_Transmit(byte%10+48);
		byte-=byte%10;
		byte/=10;
	}

}

unsigned int readADC(char adcn){
	ADMUX = 0b0100000+adcn;
	ADCSRA |= (1<<ADSC); // reset value
	while (ADCSRA & (1<<ADSC)) {}; // wait for measurement
	return ADC>>6;
}

void ADC_Init(){
	// ADC Enable, Prescaler 128
	ADCSRA = (1<<ADEN)  | 0b111;
}

int main(void){
	//DDRD=255;
	USART_Init();
	ADC_Init();
	for(;;){
		sendNum(readADC(5));
		USART_Transmit('n');
		_delay_ms(100);
	}
}

Here is the Python code to receive the data and log it to disk:

import serial, time
import numpy
ser = serial.Serial("COM15", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

while True:
    line=ser.readline()[:-1]

    if "," in line:
        line=line.split(",")
        for i in range(len(line)):
            line[i]=line[i][::-1]
    else:
        line=[line[::-1]]
    temp=int(line[0])
    lines+=1
    data.append(temp)
    print "#",
    if lines%1000==999:
        numpy.save("DATA.npy",data)
        print
        print line
        print "%d lines in %.02f sec (%.02f vals/sec)"%(lines,
				time.time()-t1,lines/(time.time()-t1))

Here is the Python code to plot the data that has been saved:

import numpy
import pylab

data=numpy.load("DATA.npy")
print data
data=data*.008 #convert to F
xs=numpy.arange(len(data))/9.95  #vals/sec
xs=xs/60.0# minutes
xs=xs/60.0# hours

pylab.plot(xs,data)
pylab.grid(alpha=.5)
pylab.axis([None,None,0*.008,1024*.008])
pylab.ylabel(r'$Delta$ Fahrenheit')
pylab.xlabel("hours")
pylab.show()

If you recreate this project, or have any questions, feel free to email me!





Additional Resources

Here I demonstrate a dirt-cheap method of transmitting data from any microchip to any PC using $3.21 in parts.  I’ve had this idea for a while, but finally got it working tonight. On the transmit side, I’m having a an ATMEL AVR microcontroller (ATMega48) transmit data (every number from 0 to 200 over and over) wirelessly using 433mhz wireless modules. The PC receives the data through the microphone port of a sound card, and a cross-platform Python script I wrote decodes the data from the audio and graphs it on the screen. I did something similar back in 2011, but it wasn’t wireless, and the software wasn’t nearly as robust as it is now.

This is a proof-of-concept demonstration, and part of a larger project. I think there’s a need for this type of thing though! It’s unnecessarily hard to transfer data from a MCU to a PC as it is. There’s USB (For AVR V-USB is a nightmare and requires a precise, specific clock speed, DIP chips don’t have native USB, and some PIC DIP chips do but then you have to go through driver hell), USART RS-232 over serial port works (but who has serial ports these days?), or USART over USB RS-232 interface chips (like FTDI FT-232, but surface mount only), but both also require precise, specific clock speeds. Pretend I want to just measure temperature once a minute. Do I really want to etch circuit boards and solder SMT components? Well, kinda, but I don’t like feeling forced to. Some times you just want a no-nonsense way to get some numbers from your microchip to your computer. This project is a funky out-of-the-box alternative to traditional methods, and one that I hope will raise a few eyebrows.

 

Ultimately, I designed this project to eventually allow multiple “bursting” data transmitters to transmit on the same frequency routinely, thanks to syncing and forced-sync-loss (read on). It’s part of what I’m tongue-in-cheek calling the Scott Harden RF Protocol (SH-RFP). In my goal application, I wish to have about 5 wireless temperature sensors all transmitting data to my PC.  The receive side has some error checking in that it makes sure pulse sizes are intelligent and symmetrical (unlike random noise), and since each number is sent twice (with the second time being in reverse), there’s another layer of error-detection.  This is *NOT* a robust and accurate method to send critical data. It’s a cheap way to send data. It is very range limited, and only is intended to work over a distance of ten or twenty feet. First, let’s see it in action!

The RF modules are pretty simple. At 1.56 on ebay (with free shipping), they’re cheap too! I won’t go into detail documenting the ins and out of these things (that’s done well elsewhere). Briefly, you give them +5V (VCC), 0V (GND), and flip their data pin (ATAD) on and off on the transmitter module, and the receiver module’s DATA pin reflects the same state. The receiver uses a gain circuit which continuously increases gain until signal is detected, so if you’re not transmitting it WILL decode noise and start flipping its output pin. Note that persistent high or low states are prone to noise too, so any protocol you use these things for should have rapid state transitions. It’s also suggested that you maintain an average 50% duty cycle. These modules utilize amplitude shift keying (ASK) to transmit data wirelessly. The graphic below shows what that looks like at the RF level. Transmit and receive is improved by adding a quarter-wavelength vertical antenna to the “ANT” solder pad. At 433MHz, that is about 17cm, so I’m using a 17cm copper wire as an antenna.

Transmitting from the microcontroller is easy as pie! It’s just a matter of copying-in a few lines of C.  It doesn’t rely on USART, SPI, I2C, or any other protocol. Part of why I developed this method is because I often use ATTiny44A which doesn’t have USART for serial interfacing. The “SH-RFP” is easy to implement just by adding a few lines of code. I can handle that.  How does it work? I can define it simply by a few rules:

 

SHRFP (Scott Harden RF Protocol)

Pulses can be one of 3 lengths: A (0), B (1), or C (break).

Each pulse represents high, then low of that length.

Step 1: prime synchronization by sending ten ABCs

Step 2: indicate we’re starting data by sending C.

Step 3: for each number you want to send:

A: send your number bit by bit (A=0, B=1)

B: send your number bit by bit (A=1, B=0)

C: indicate number end by sending C.

 Step 4: tell PC to release the signal by sending ten Cs.

Decoding is the same thing in reverse. I use an eBay sound card at $1.29 (with free shipping) to get the signal into the PC. Syncronization is required to allow the PC to know that real data (not noise) is starting. Sending the same number twice (once with reversed bit polarity) is a proofchecking mechanisms that lets us throw-out data that isn’t accurate.

From a software side, I’m using PyAudio to collect data from the sound card, and the PythonXY distribution to handle analysis with numpy, scipy, and plotting with QwtPlot, and general GUI functionality with PyQt. I think that’s about everything.

 The demonstration interface is pretty self-explanatory. The top-right shows a sample piece of data. The top left is a histogram of the number of samples of each pulse width. A clean signal should have 3 pulses (A=0, B=1, C=break). Note that you’re supposed to look at the peaks to determine the best lengths to tell the software to use to distinguish A, B, and C. This was intentionally not hard-coded because I want to rapidly switch from one microcontroller platform to another which may be operating at a different clock speed, and if all the sudden it’s running 3 times slower it will be no problem to decide on the PC side. Slick, huh? The bottom-left shows data values coming in. The bottom-right graphs those values. Rate reporting lets us know that I’m receiving over 700 good data points a second. That’s pretty cool, especially considering I’m recording at 44,100 Hz. 

Here’s the MCU code I used. It’s an ATMega48 ATMEL AVR microcontroller. Easy code!

#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>

void tick(char ticks){
	while (ticks>0){
		_delay_us(100);
		ticks--;
	}
}

void pulse(char ticks){
	PORTB=255;
	tick(ticks);
	PORTB=0;
	tick(ticks);
}

void send_sync(){
	char i;
	for (i=0;i<10;i++){
		pulse(1);
		pulse(2);
		pulse(3);
	}
	pulse(3);
}

void send_lose(){
	char i;
	for (i=0;i<5;i++){
		pulse(3);
	}
}

void sendByte(int val){
	// TODO - make faster by only sending needed bytes
	char i;
	for (i=0;i<8;i++){
		if ((val>>i)&1){pulse(2);}
		else{pulse(1);}
	}
}

void send(int val){
	sendByte(val);  // regular
	sendByte(~val); // inverted
	pulse(3);
}

int main (void)
{
    DDRB = 255;
	int i;

    while(1) {
		send_sync();
		for (i=0;i<200;i++){
			send(i);
		}
		send_lose();
	}
}

Here’re some relevant snippits of the PC code. Download the full project below if you’re interested.

import matplotlib
matplotlib.use('TkAgg') # -- THIS MAKES IT FAST!
import numpy
import pyaudio
import threading
import pylab
import scipy
import time
import sys

class SwhRecorder:
    """Simple, cross-platform class to record from the microphone.
    This version is optimized for SH-RFP (Scott Harden RF Protocol)
    Pulse data extraction. It's dirty, but it's easy and it works.

    BIG PICTURE:
    continuously record sound in buffers.
    if buffer is detected:

        ### POPULATE DELAYS[] ###
        downsample data
        find Is where data>0
        use ediff1d to get differences between Is
        append >1 values to delays[]
        --if the old audio[] ended high, figure out how many
        --on next run, add that number to the first I

        ### PLUCK DELAYS, POPULATE VALUES ###
        only analyze delays through the last 'break'
        values[] is populated with decoded delays.

    ."""

    def __init__(self):
        """minimal garb is executed when class is loaded."""
        self.RATE=44100
        self.BUFFERSIZE=2**10
        print "BUFFER:",self.BUFFERSIZE
        self.threadsDieNow=False
        self.newAudio=[]
        self.lastAudio=[]
        self.SHRFP=True
        self.dataString=""
        self.LEFTOVER=[]

        self.pulses=[]
        self.pulsesToKeep=1000

        self.data=[]
        self.dataToKeep=1000

        self.SIZE0=5
        self.SIZE1=10
        self.SIZE2=15
        self.SIZEF=3

        self.totalBits=0
        self.totalNumbers=0
        self.totalSamples=0
        self.totalTime=0

        self.nothingNewToShow=True

    def setup(self):
        """initialize sound card."""
        #TODO - windows detection vs. alsa or something for linux
        #TODO - try/except for sound card selection/initiation
        self.p = pyaudio.PyAudio()
        self.inStream = self.p.open(input_device_index=None,
                                    format=pyaudio.paInt16,channels=1,
                                    rate=self.RATE,input=True,
                                    frames_per_buffer=self.BUFFERSIZE)

    def close(self):
        """cleanly back out and release sound card."""
        self.p.close(self.inStream)

    def decodeBit(self,s):
        "given a good string 1001101001 etc, return number or None"
        if len(s)<2:return -2
        s=s[::-1]
        A=s[:len(s)/2] #INVERTED
        A=A.replace("0","z").replace("1","0").replace("z","1")
        B=s[len(s)/2:] #NORMAL

        if A<>B:
            return -1
        else:
            return int(A,2)

    def analyzeDataString(self):
        i=0
        bit=""
        lastB=0
        while i<len(self.dataString):
            if self.dataString[i]=="B":
                self.data.append(self.decodeBit(bit))
                self.totalNumbers+=1
                lastB=i
            if self.dataString[i] in ['B','?']:
                bit=""
            else:
                bit+=self.dataString[i]
            i+=1
        self.dataString=self.dataString[lastB+1:]
        if len(self.data)>self.dataToKeep:
            self.data=self.data[-self.dataToKeep:]

    def continuousAnalysis(self):
        """keep watching newAudio, and process it."""
        while True:
            while len(self.newAudio)< self.BUFFERSIZE:
                time.sleep(.1)

            analysisStart=time.time()

            audio=self.newAudio

            # TODO - insert previous audio sequence here

            # GET Is where data is positive
            Ipositive=numpy.nonzero(audio>0)[0]
            diffs=numpy.ediff1d(Ipositive)
            Idiffs=numpy.where(diffs>1)[0]
            Icross=Ipositive[Idiffs]
            pulses=diffs[Idiffs]

            # remove some of the audio buffer, leaving the overhang

            if len(Icross)>0:
                processedThrough=Icross[-1]+diffs[Idiffs[-1]]
            else:
                processedThrough=len(audio)

            self.lastAudio=self.newAudio[:processedThrough]
            self.newAudio=self.newAudio[processedThrough:]

            if False:
                # chart audio data (use it to check algorythm)
                pylab.plot(audio,'b')
                pylab.axhline(0,color='k',ls=':')

                for i in range(len(Icross)):
                    # plot each below-zero pulse whose length is measured
                    pylab.axvspan(Icross[i],Icross[i]+diffs[Idiffs[i]],
                                  color='b',alpha=.2,lw=0)

                # plot the hangover that will be carried to next chunk
                pylab.axvspan(Icross[i]+diffs[Idiffs[i]],len(audio),
                              color='r',alpha=.2)
                pylab.show()
                return

            # TODO - histogram of this point to assess quality
            s=''
            for pulse in pulses:
                if (self.SIZE0-self.SIZEF)<pulse<(self.SIZE0+self.SIZEF):
                    s+="0"
                elif (self.SIZE1-self.SIZEF)<pulse<(self.SIZE1+self.SIZEF):
                    s+="1"
                elif (self.SIZE2-self.SIZEF)<pulse<(self.SIZE2+self.SIZEF):
                    s+="B"
                else:
                    s+="?"

            self.pulses=pulses
            self.totalBits+=len(pulses)

            print "[%.02f ms took %.02f ms] T: 0=%d 1=%d B=%d ?=%d"%(
                          len(audio)*1000.0/self.RATE,
                          time.time()-analysisStart,
                          s.count('0'),s.count('1'),s.count('B'),s.count('?'))

            self.dataString+=s
            self.analyzeDataString()

            self.totalSamples+=self.BUFFERSIZE
            self.totalTime=self.totalSamples/float(self.RATE)
            self.totalBitRate=self.totalBits/self.totalTime
            self.totalDataRate=self.totalNumbers/self.totalTime

            self.nothingNewToShow=False

    def continuousRecord(self):
        """record forever, adding to self.newAudio[]. Thread this out."""
        while self.threadsDieNow==False:
            maxSecBack=5
            while len(self.newAudio)>(maxSecBack*self.RATE):
                print "DELETING NEW AUDIO!"
                self.newAudio=self.newAudio[self.BUFFERSIZE:]
            audioString=self.inStream.read(self.BUFFERSIZE)
            audio=numpy.fromstring(audioString,dtype=numpy.int16)
            self.newAudio=numpy.hstack((self.newAudio,audio))

    def continuousDataGo(self):
        self.t = threading.Thread(target=self.continuousRecord)
        self.t.start()
        self.t2 = threading.Thread(target=self.continuousAnalysis)
        self.t2.start()

    def continuousEnd(self):
        """shut down continuous recording."""
        self.threadsDieNow=True

if __name__ == "__main__":
    SHR=SwhRecorder()
    SHR.SHRFP_decode=True
    SHR.setup()
    SHR.continuousDataGo()

    #SHR.DataStart()

    print "---DONE---"

Finally, if you’re interested, here’s the full code (and demo audio WAV files):

DOWNLOAD: SCOTT HARDEN RF PROTOCOL DEMO.zip

If you use these concepts, hardware, or ideas in your project, let me know about it! Send me an email showing me your project – I’d love to see it. Good luck!





Additional Resources

A majority of the microcontroller programming I do these days involves writing C for the ATMEL AVR series of microcontrollers. I respect PIC, but I find the open/free atmosphere around AVR to be a little more supportive to individual, non-commercial cross-platform programmers like myself. With that being said, I’ve had a few bumps along the way getting unofficial AVR programmers to work in Windows 7. Previously, I had great success with a $11 (shipped) clone AVRISP-mkII programmer from fun4diy.com. It was the heart of a little AVR development board I made and grew to love (which had a drop-in chip slot and also a little breadboard all in one) seen in a few random blog posts over the years. Recently it began giving me trouble because, despite downloading and installing various drivers and packages, I couldn’t get it to work with Windows Vista or windows 7. I needed to find another option. I decided against the official programmer/software because the programmer is expensive (for a college student) and the software (AVR studio 6) is terribly bloated for LED-blink type applications. “AStudio61.exe” is 582.17 Mb. Are you kidding me? Half a gig to program a microchip with 2kb of memory? Rediculous.  I don’t use arduino because I’m comfortable working in C and happy reading datasheets. Furthermore, I like programming chips hot off the press, without requiring a special boot loader.

I got everything running on Windows 7 x64 with the following:

Here’s the “hello world” of microchip programs (it simply blinks an LED). I’ll assume the audience of this page knows the basics of microcontroller programming, so I won’t go into the details. Just note that I’m using an ATMega48 and the LED is on pin 9 (PB6). This file is named “blink.c”.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>

int main (void)
{
    DDRB = 255;
    while(1)
    {
        PORTB ^= 255;
        _delay_ms(500);
    }
}

Here’s how I compiled the code:

avr-gcc -mmcu=atmega48 -Wall -Os -o blink.elf blink.c
avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex

In reality, it is useful to put these commands in a text file and call them “compile.bat”

Here’s how I program the AVR. I used AVRDudess! I’ve been using raw AVRDude for years. It’s a little rough around the edges, but this GUI interface is pretty convenient. I don’t even feel the need to include the command to program it from the command line! If I encourage nothing else by this post, I encourage (a) people to use and support AVRDudess, and (b) AVRDudess to continue developing itself as a product nearly all hobby AVR programmers will use. Thank you 21-year-old Zak Kemble.

And finally, the result. A blinking LED. Up and running programming AVR microcontrollers in 64-bit Windows 7 with an unofficial programmer, and never needing to install bloated AVR Studio software.





Additional Resources

It is not difficult to program ATMEL AVR microcontrollers with linux, and I almost exclusively do this because various unofficial (inexpensive) USB AVR programmers are incompatible with modern versions of windows (namely Windows Vista and Windows 7). I am just now setting-up a new computer station in my electronics room (running Ubuntu Linux 12.04), and to make it easy for myself in the future I will document everything I do when I set-up a Linux computer to program microcontrollers.

Install necessary software

sudo apt-get install gcc-avr avr-libc uisp avrdude

Connect the AVR programmer
This should be intuitive for anyone who has programmed AVRs before. Visit the datasheet of your MCU, identify pins for VCC (+), GND (-), MOSI, MISO, SCK, and RESET, then connect them to the appropriate pins of your programmer.

Write a simple program in C
I made a file “main.c” and put the following inside. It’s the simplest-case code necessary to make every pin on PORTD (PD0, PD1, …, PD7) turn on and off repeatedly, sufficient to blink an LED.

#include <avr/io.h>
#include <util/delay.h>

int main (void)
{
 DDRD = 255; // MAKE ALL PORT D PINS OUTPUTS

 while(1) {
  PORTD = 255;_delay_ms(100); // LED ON
  PORTD = 0;  _delay_ms(100); // LED OFF
 }

 return 0;
}

Compile the code (generate a HEX file)

avr-gcc -w -Os -DF_CPU=2000000UL -mmcu=atmega8 -c -o main.o main.c
avr-gcc -w -mmcu=atmega8 main.o -o main
avr-objcopy -O ihex -R .eeprom main main.hex

note that the arguments define CPU speed and chip model – this will need to be customized for your application

Program the HEX firmware onto the AVR

sudo avrdude -F -V -c avrispmkII -p ATmega8 -P usb -U flash:w:main.hex

note that this line us customized based on my connection (-P flag, USB in my case) and programmer type (-c flag, AVR ISP mkII in my case)

When this is run, you will see something like this:


avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e9307
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: input file main.hex auto detected as Intel Hex
avrdude: writing flash (94 bytes):

Writing | ################################################## | 100% 0.04s

avrdude: 94 bytes of flash written

Your Program should now be loaded! Sit back and watch your LED blink.

TIP 1: I’m using a clone AVRISP mkII from Fun4DIY.com which is only $11 (shipped!). I glued it to a perf-board with a socket so I can jumper its control pins to any DIP AVR (80 pins or less). I also glued a breadboard for my convenience, and added LEDs (with ground on one of their pins) for easy jumpering to test programs. You can also build your own USB AVR ISP (free schematics and source code) from the USBtinyISP project website.

TIP 2: Make a shell script to run your compiling / flashing commands with a single command. Name them according to architecture. i.e., “build-m8” or “build-t2313”. Make the first line delete all .hex files in the directory, so it stalls before it loads old .hex files if the compile is unsuccessful. Make similar shell scripts to program fuses. i.e., “fuse-m8-IntClock-8mhz”, “fuse-m8-IntClock-1mhz”, “fuse-m8-ExtCrystal”.

TIP 3: Use a nice text editor well suited for programming. I love Geany.





Additional Resources

Some days you feel like working on projects to benefit humanity. The day I made this clearly wasn’t one of those days. A little over a year ago, I got into a troll war with my friend Mike Seese. The joke, similar to that of rick rolling, was to get each other to unexpectedly click a link to the Hatsune Miku version of the leekspin song. After several weeks of becoming beyond annoying, I decided to make an actual Hatsune Miku which would spin her leek and bobble her head to the techno version of the Levan Polka for his birthday.

The goal was to create a minature Miku which would perform perfectly in sync with audio coming from a portable music player (iPod or something) and NOT require a computer connection. I accomplished it by sending some creative control beeps out of the left channel of the stereo signal. Although I didn’t finish the project, I got pretty far with the prototype, so I decided to dig it out of the archives and share it with the world because it’s pretty entertaining!


(look how close I came to replicating the original!)

How did I do it? First off, I used servos. If you’re not familiar with them, I suggest you look up how servos work. Perhaps check out how to control servos with AVR microcontrollers. Basically, their position along a rotational axis is determined by the width of a pulse on a 20ms time window. Anyhow, if I only had 1 servo to control (i.e., leek only), I’d have controlled the servo directly with PWM signals in the left channel – no microcontroller needed, easy as pie, problem solved. However, since I needed to control two servos, I had to come up with something a bit more creative. Although I could have probably done this ten different ways, the way I chose to do it was using a series of pre-encoded leek spin and head bobble motions, triggered by control beeps in the left channel of the audio cable. (The right channel was patched through to the speakers.) Below is a diagram of what I believe I did, although I didn’t thoroughly document it at the time, so you might have to use your imagination if you decide to re-create this project.

The idea is that by sending bursts of sine waves, the circuit can rectify them and smooth them out to have them look to a microcontroller like a brief “high” signal. Each signal would tell the microcontroller to proceed to the next pre-programmed (and carefully timed) routine. With enough practice listening, watching, and tweaking the code, I was able to make a final version which worked pretty darn well!

LISTEN TO:MUSIC WITH CONTROL BEEPS (it’s a surprisingly fun listen)

A few technical details are that I used an ATTiny44a microcontroller (it may have been an ATTiny2313, I can’t remember for sure, but they’re so similar it’s virtually negligable). The servos I used were cheap (maybe $4?) from eBay. They looked like the one pictured below. The servo position was controlled by PWM, but I manually sent the pulses and didn’t actually use the integrated PWM in the microcontroller. I can’t remember why I did it this way – perhaps because it was so simple to use the _delay_us() and _delay_ms() functions? I also used an operational amplifier (if I remember, it was a LM741) to boost the left channel control signals rather than rectifying/assessing the left channel directly.

This is the video which I mimiced to create my prototype (note how the leek in her arm and her head move exactly the same as the prototype I made – score!)

For good measure, here’s the original song:

And how did I find out about this song? I actually saw it on the video below which was hosted on wimp.com. I thought the song was catchy, looked it up, and the rest was history. It’s worth noting that (perhaps to avoid copyright issues?) the key was shifted two half-steps up. I get a kick out of the way the girl waves her arm in the beginning, mimicking the leek 🙂

Here are some of the images I made which I printed, glued to foam board, and cut out with a razor blade. I’m not sure how useful they are, but they’re provided just in case.

… but sometimes Japan takes it a bit too far and things get awkward …

Below is the code I used. Note that PWM that controls the servos isn’t the integrated PWM, but rather a couple pins I manually pulse on and off to control the arm and head positions. Also notice how, in the main routine, I wait for the control beeps before continuing the next sequences.

// leek spin code - designed for ATTiny
// by Scott Harden, www.SWHarden.com

#include <avr/io.h>
#include <avr/delay.h>

void go_high(){
	// sets the arm to the highest position
	for (char i=0;i<5;i++){
		PORTA|=(1<<PA0);
		_delay_us(1400);
		PORTA&=~(1<<PA0);
		_delay_us(20000-1200);
		}
	}

void go_low(){
	// sets the leek to the middle position
	for (char i=0;i<5;i++){
		PORTA|=(1<<PA0);
		_delay_us(1900);
		PORTA&=~(1<<PA0);
		_delay_us(20000-1900);
		}
	}

void go_lowest(){
	// sets the leek to the lowest position
	for (char i=0;i<5;i++){ // takes 100ms total
		PORTA|=(1<<PA0);
		_delay_us(2300);
		PORTA&=~(1<<PA0);
		_delay_us(20000-2500);
		}
	}

void go_slow(char times){
	// does one slow leek down/up
	// beat is 500ms
	for (char i=0;i<times;i++){
		go_low();
		_delay_ms(10);
		go_high();
		_delay_ms(290);
		PORTA^=(1<<PA2);
		PORTA^=(1<<PA3);
	}
}

void go_fast(char times){
	// does one fast leek down/up
	// beat is 250ms
	for (char i=0;i<times;i++){
		go_low();
		_delay_ms(10);
		go_high();
		_delay_ms(15);
		PORTA^=(1<<PA2);
		PORTA^=(1<<PA3);
	}
}
void head_left(){
	// tilts the head to the left
	for (char i=0;i<5;i++){
		PORTA|=(1<<PA1);
		_delay_us(1330);
		PORTA&=~(1<<PA1);
		_delay_us(20000-1200);
		}
	}

void head_right(){
	// tilts the head to the right
	for (char i=0;i<5;i++){
		PORTA|=(1<<PA1);
		_delay_us(1500);
		PORTA&=~(1<<PA1);
		_delay_us(20000-1200);
		}
	}

void head_center(){
	// centers the head
	for (char i=0;i<5;i++){
		PORTA|=(1<<PA1);
		_delay_us(1400);
		PORTA&=~(1<<PA1);
		_delay_us(20000-1200);
		}
	}

void head_go(char times){
	// rocks the head back and forth once
	for (char i=0;i<(times-1);i++){
		head_left();
		_delay_ms(400);
		PORTA^=(1<<PA2);
		PORTA^=(1<<PA3);
		head_right();
		_delay_ms(400);
		PORTA^=(1<<PA2);
		PORTA^=(1<<PA3);
	}
	head_center(); // returns head to center when done
	_delay_ms(400);
	PORTA^=(1<<PA2);
	PORTA^=(1<<PA3);
}

int main(void) {
	while (1){
		DDRA=255; // set port A (servos) as outputs
		DDRB=0; // set port B (listening pins) as inputs

		go_lowest();head_center();// set starting positions

		while ((PINB & _BV(PB0))){} // wait for beep que
		PORTA=(1<<PA3);
		go_high();_delay_ms(1000);
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_slow(31); // tilt leek slowly 31 times
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_slow(31); // tilt leek slowly 31 times

		while ((PINB & _BV(PB0))){} // wait for beep que
		_delay_ms(200);
		head_go(16); // rock head 16 times
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_fast(68); // tilt leek rapidly 68 times
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_slow(24); // tilt leek slowly 24 times
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_fast(17); // tilt leek rapidly 17 times
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_slow(31); // tilt leek slowly 31 times
		while ((PINB & _BV(PB0))){} // wait for beep que
		go_slow(31); // tilt leek slowly 31 times

		while ((PINB & _BV(PB0))){} // wait for beep que
		_delay_ms(200);
		head_go(16); // rock head 16 times
		go_lowest(); // reset position
		PORTA=0;
	}
  return 0;
}

Finally, I’d like to take a moment to indicate one of the reasons this project is special to me. My wife, Angelina Harden, died one year ago today. This project was the last one she worked on with me. She died a few days after the video was taken, and in the process of moving out of our apartment I threw away almost everything (including this project). Although I never finished it, I remember working on it with Angelina – we went to wal-mart together to buy the foam board I used to make it, and she told me that I should make her head rock back and forth rather than just move her arm. I remember that, once it was all done, I let her sit in the chair in front of it and played it through, and she laughed nearly the whole time 🙂 I’ll always miss her.