SWHarden.com

The personal website of Scott W Harden

Festivus Pole Video Game

December 23 is Festivus! To commemorate the occasion, I have built a traditional Festivus pole with a couple added features. To my knowledge, this is the first electronic Festivus pole on the internet.

Festivus is a holiday comically celebrated as an alternative to the pressures of commercialism commonly associated with other winter holidays. Originating from the 1997 Seinfeld episode “The Strike”, the traditions of Festivus include demonstrating feats of strength, declaring common occurrences as Festivus miracles, airing of grievances, and of course the fabrication of a Festivus pole.

Over the years various Festivus poles (often made of beer cans) have been erected in government buildings alongside the nativity scene and menorah, including this year in my home state Florida (the video is a good laugh). Here, I show a Festivus pole I made made from individually illuminated diet coke cans which performs as a simple video game, controlled by a single button. The illuminated can scrolls up and down, and the goal is to push the button when the top can is lit. If successful, the speed increases, and the game continues! It’s hours of jolly good fun.

After playing at my workbench for a while, I figured out a way I could light-up individual coke cans. I drilled a dozen holes in each can (with a big one in the back), stuck 3 blue LEDs (wired in parallel with a 220-ohm current limiting resistor in series) in the can, and hooked it up to 12V. This was the motivation I needed to continue…

Now for the design. I found a junk box 12V DC wall-wart power supply which I decided to commandeer for this project. Obviously a microcontroller would be the simplest way to implement this “game”, and I chose to keep things as minimal as possible. I used a single 8-pin ATMEL ATTiny85 microcontroller ($1.67) which takes input from 1 push-button and sends data through two daisy-chained 74hc595 shift-registers ($0.57) to control base current of 2n3904 transistors ($.019) to illuminate LEDs which I had on hand (ebay, 1000 3mm blue LEDs, $7.50 free shipping). A LM7805 linear voltage regulator ($0.68) was used to bring the 12V to 5V, palatable for the microcontroller. Note that all prices are for individual units, and that I often buy in bulk from cheap (shady) vendors, so actual cost of construction was less.

To build the circuit, I used perf-board and all through-hole components. It’s a little messy, but it gets the job done! Admire the creative resistor hops connecting shift registers and microcontroller pins. A purist would shriek at such construction, but I argue its acceptability is demonstrated in its functionality.

The installation had to be classy. To stabilize the fixture, I used epoxy resin to cement a single coke can to an upside-down Pyrex dish (previously used for etching circuit boards in ferric chloride). I then used clear packaging tape to hold each successive illuminated can in place. All wires were kept on the back side of the installment with electrical tape. Once complete, the circuit board was placed beneath the Pyrex container, and the controller (a single button in a plastic enclosure connected with a telephone cord) was placed beside it.

It’s ready to play! Sit back, relax, and challenge your friends to see who can be the Festivus pole video game master!

A few notes about the code… The microcontroller ran the following C code (AVR-GCC) and is extremely simple. I manually clocked the shift registers (without using the chip’s serial settings) and also manually polled for the button press (didn’t even use interrupts). It’s about as minimal as it gets! What improvements could be made to this Festivus hacking tradition? We will have to wait and see what the Internet comes up with next year…

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

// PB2 data
// PB1 latch
// PB0 clock
// PB4 LED

// PB3 input button

volatile int speed=400;
volatile char canlit=0;
volatile char levelsWon=0;

char buttonPressed(){
    char state;
    state = (PINB>>PB3)&1;
    if (state==0) {
        PORTB|=(1<<PB4);
        return 1;
    }
    else {
        PORTB&=~(1<<PB4);
        return 0;
    }
}

void shiftBit(char newval){
    // set data value
    if (newval==0){PORTB&=~(1<<PB2);}
    else {PORTB|=(1<<PB2);}
    // flip clock
    PORTB|=(1<<PB0);
    PORTB&=~(1<<PB0);
}

void allOff(){
    char i=0;
    for(i=0;i<16;i++){
        shiftBit(0);
    }
    updateDisplay();
}

void allOn(){
    char i=0;
    for(i=0;i<14;i++){
        shiftBit(1);
    }
    updateDisplay();
}

void onlyOne(char pos){
    if (pos>=8) {pos++;}
    char i;
    allOff();
    shiftBit(1);
    for (i=0;i<pos;i++){shiftBit(0);}
    //if (pos>8) {shiftBit(0);} // because we skip a shift pin
    updateDisplay();
    }

void updateDisplay(){PORTB|=(1<<PB1);PORTB&=~(1<<PB1);}

void ledON(){PORTB|=(1<<PB4);}
void ledOFF(){PORTB&=~(1<<PB4);}

char giveChance(){
    int count=0;
    for(count=0;count<speed;count++){
        _delay_ms(1);
        if (buttonPressed()){return 1;}
    }
    return 0;
}

void strobe(){
    char i;
    for(i=0;i<50;i++){
        allOn();_delay_ms(50);
        allOff();_delay_ms(50);
    }
}

char game(){
    for(;;){
        for(canlit=1;canlit<15;canlit++){
            onlyOne(canlit);
            if (giveChance()) {return;}
        }
        for(canlit=13;canlit>1;canlit--){
            onlyOne(canlit);
            if (giveChance()) {return;}
        }
    }
}

void levelWin(){
    char i;
    for(i=0;i<levelsWon;i++){
        allOn();
        _delay_ms(200);
        allOff();
        _delay_ms(200);
    }
}
void levelLose(){
    char i;
    for(i=0;i<20;i++){
        for(canlit=13;canlit>1;canlit--){
            onlyOne(canlit);
            _delay_ms(10);
        }
    }
}

void showSelected(){
    char i;
    for(i=0;i<20;i++){
        onlyOne(canlit);
        _delay_ms(50);
        allOff();
        _delay_ms(50);
    }
}

void nextLevel(){
    // we just pushed the button.
    showSelected();
    levelsWon++;
    if (canlit==14) {
        levelWin();
        speed-=speed/5;
        }
    else {
        levelLose();
        speed=400;
        levelsWon=0;
    }
}

int main(void){
    DDRB=(1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB4);
    char i;
    for(;;){
        game();
        nextLevel();
    }
}

Programming: note that the code was compiled and programmed onto the AVR from a linux terminal using AvrDude. The shell script I used for that is here:

rm main
rm *.hex
rm *.o
echo "MAKING O"
avr-gcc -w -Os -DF_CPU=1000000UL -mmcu=attiny85 -c -o main.o main.c
echo "MAKING BINARY"
avr-gcc -w -mmcu=attiny85 main.o -o main
echo "COPYING"
avr-objcopy -O ihex -R .eeprom main main.hex
echo "PROGRAMMING"
avrdude -c usbtiny -p t85 -F -U flash:w:"main.hex":a -U lfuse:w:0x62:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
echo "DONE"

Epoch Timestamp Hashing

I was recently presented with the need to rename a folder of images based on a timestamp. This way, I can keep saving new files in that folder with overlapping filenames (i.e., 01.jpg, 02.jpg, 03.jpg, etc.), and every time I run this script all images are prepended with a timestamp. I still want the files to be sorted alphabetically, which is why an alphabetical timestamp (rather than a random hash) is preferred.

The result I came up with uses base conversion and a string table of numbers and letters (in alphabetical order) to create a second-respecting timestamp hash using an arbitrary number of characters. For simplicity, I used 36 characters: 0-9, and a-z. I then wrote two functions to perform arbitrary base conversion, pulling characters from the hash. Although I could have nearly doubled my available characters by including the full ASCII table, respecting capitalization, I decided to keep it simple. The scheme goes like this:

I can now represent any modern time, down to the second, with 6 characters. Here’s some example output:

19-Apr-2014 13:08:55 <-> 1397912935 <-> n4a4iv
19-Apr-2014 13:08:56 <-> 1397912936 <-> n4a4iw
19-Apr-2014 13:08:57 <-> 1397912937 <-> n4a4ix
19-Apr-2014 13:08:58 <-> 1397912938 <-> n4a4iy
19-Apr-2014 13:08:59 <-> 1397912939 <-> n4a4iz
19-Apr-2014 13:09:00 <-> 1397912940 <-> n4a4j0
19-Apr-2014 13:09:01 <-> 1397912941 <-> n4a4j1
19-Apr-2014 13:09:02 <-> 1397912942 <-> n4a4j2
19-Apr-2014 13:09:03 <-> 1397912943 <-> n4a4j3
19-Apr-2014 13:09:04 <-> 1397912944 <-> n4a4j4

Interestingly, if I change my hash characters away from the list of 36 alphanumerics and replace it with just 0 and 1, I can encode/decode the date in binary:

19-Apr-2014 13:27:28 <-> 1397914048 <-> 1010011010100100111100111000000
19-Apr-2014 13:27:29 <-> 1397914049 <-> 1010011010100100111100111000001
19-Apr-2014 13:27:30 <-> 1397914050 <-> 1010011010100100111100111000010
19-Apr-2014 13:27:31 <-> 1397914051 <-> 1010011010100100111100111000011
19-Apr-2014 13:27:32 <-> 1397914052 <-> 1010011010100100111100111000100
19-Apr-2014 13:27:33 <-> 1397914053 <-> 1010011010100100111100111000101
19-Apr-2014 13:27:34 <-> 1397914054 <-> 1010011010100100111100111000110
19-Apr-2014 13:27:35 <-> 1397914055 <-> 1010011010100100111100111000111
19-Apr-2014 13:27:36 <-> 1397914056 <-> 1010011010100100111100111001000
19-Apr-2014 13:27:37 <-> 1397914057 <-> 1010011010100100111100111001001

Here’s the code to generate / decode Unix epoch timestamps in Python:

hashchars='0123456789abcdefghijklmnopqrstuvwxyz'
#hashchars='01' #for binary

def epochToHash(n):
  hash=''
  while n>0:
    hash = hashchars[int(n % len(hashchars))] + hash
    n = int(n / len(hashchars))
  return hash

def epochFromHash(s):
  s=s[::-1]
  epoch=0
  for pos in range(len(s)):
    epoch+=hashchars.find(s[pos])*(len(hashchars)**pos)
  return epoch

import time
t=int(time.time())
for i in range(10):
  t=t+1
  print(time.strftime("%d-%b-%Y %H:%M:%S", time.gmtime(t)),
              "<->", t,"<->",epochToHash(t))

Directly Driving 7-Segment Display with AVR IO Pins

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.

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.[/caption]

SCHEMATIC? If you want it, read this. 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:

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.

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:


Calculate QRSS Transmission Time with Python

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.

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.


Adding USB to a Cheap Frequency Counter (Again)

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…

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.

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.

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!

__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…