SWHarden.com

The personal website of Scott W Harden

Frequency Counter Gen2

I’m working to further simplify my frequency counter design. This one is simpler than my previous design both in hardware and software! Here’s a video to demonstrate the device in its current state:

I utilize the ATMega48’s hardware counter which is synchronous with the system clock, so it can only measure frequency less than half of its clock speed. I solve this issue by dividing the input frequency by 8 and clocking the chip at 12mhz. This allows me to measure frequencies up to about 48MHz, but can be easily adapted to measure over 700MHz (really?) by dividing the input by 128. Division occurs by a 74HC590 8-bit counter (not a 74HC595 as I accidentally said in the video, which is actually a common shift register), allowing easy selection of input divided by 1, 2, 4, 8, 16, 32, 64, or 128. The following image shows the o-scope showing the original signal (bottom) and the divided-by-8 result (top)

The device outputs graphically to a LCD simply enough. That LCD is from eBay and is only $3.88 shipped! I’m considering buying a big box of them and implementing them in many more of my projects. They’re convenient and sure do look nice!

The signal I test with comes from an oscillator I built several months ago. It’s actually a SA612 style receiver whose oscillator is tapped, amplified, and output through a wire. It’s tunable over almost all of 40m with a varactor diode configuration. It was the start of a transceiver, but I got so much good use out of it as a function generator that I decided to leave it like it is!

THIS IS HOW THE PROGRAM WORKS: I don’t supply a schematic because it’s simple as could be. Divide the input frequency to something relatively slow, <1MHz at least. Configure the 16-bit counter to accept an external pin as the counter source (not a prescaled clock, as I often use in other applications). Then set the timer value to 0, _delay_ms() a certainly amount of time (1/10th second), and read the counter value. Multiply it by 10 to account for the 1/10th second, then multiply it by 8 to account for the divider, and it’s done! It will update 10 times a second, with a resolution down to 10*8 = 80 Hz. It’s well within the range of amateur radio uses! If you’re considering replicating this, read up on how to use hardware counters with ATMEL AVR microcontrollers. That should be enough to get you started! Here’s the code I used…

For the LCD, this code requires LCD library.

#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include "lcd.h"
#include "lcd.c"

int main(void)
{
    TCCR1B=0b00000111; // rising edge trigger
    char buffer[8];
    long toshow=0;
    char mhz=0;
    int khz=0;
    int hz=0;
    lcd_init(LCD_DISP_ON);
    for(;;){
        lcd_clrscr();

        lcd_gotoxy(0,0);
        itoa(mhz , buffer, 10);
        lcd_puts(buffer);
        lcd_puts(".");

        if (khz<100){lcd_puts("0");}
        itoa(khz , buffer, 10);
        lcd_puts(buffer);

        itoa(hz/100 , buffer, 10);
        lcd_puts(buffer);

        lcd_puts(" MHz");

        TCNT1=0;
        _delay_ms(99);
        _delay_us(312);
        toshow=TCNT1;
        toshow=(long)toshow*16*10; // tenth second period
        mhz=toshow/1000000;
        toshow=toshow-mhz*1000000;
        khz=toshow/1000;
        toshow=toshow-khz*1000;
        hz=toshow;
    }
}

DIY State Machine

While trying to attack the problem described in the previous entry, it became clear that a logic analyzer would be necessary. I thought I’d try to build one, and my first attempt was so close to being successful, but not quite there. It records 19 channels (the maximum pins available on the ATMega48 not being occupied by the status LED or USB connection pins) at a rate just under 1,000 samples per second. The USB connection to the PC is obvious, and it utilizes the V-USB project to bit-bang the USB protocol. I’m posting this in part because some of the comments to my entry two posts ago were disheartening, discouraging, and even down-right viscous! I made a simple way to send numbers to a PC through the sound card, so what? Don’t be nasty about it! Meh, internet people. Anyway, here’s a marginally more proper way to send data to a PC with USB and an AVR (logging and interface designed in python), but I’ll probably still get yelled at for it.

As you can see from the video, it’s good but not good enough. If I could get samples at 2,000 per second I’d probably be OK, but it’s just not quite fast enough with it’s current, ultra-simplistic method of sample recording. I’ll figure out a fancier way to build a spectrum analyzer - it’s obvious the platform is there, it just needs some refinement.

Images of the output:

UPDATE! The more I think about it, the more I think this might be just good enough to work! Look at the stagger in those peaks near the top - that’s probably the lines telling which character to display. Data between the peaks indicates the value to be provided, and I should have enough time to accurately measure that… Maybe this is good enough after all? I’ll have to run some more tests tomorrow…

Where’s the code? It kills me to do this, but I need to withhold the chip side code. I’m working on an idiot’s guide to USB connectivity with AVR microcontrollers, and I’d rather post the simplest-case code first, then share complicated stuff like this. I’ll post the python scripts:


# LOGIC.PY - this script will log (or print) raw data from the USB device
from optparse import OptionParser
import time
import usb.core
import usb.util
import os

while True:
        print "nTrying to communicate with the Gator Keyer ...",
        dev = usb.core.find(find_all=True, idVendor=0x16c0, idProduct=0x5dc)
        if len(dev)==0: print "FAIL"
        dev=dev[0]
        dev.set_configuration()
        print "there it is!"
        break

def readVals():
    x=dev.ctrl_transfer(0xC0, 2, 2, 3, 4).tolist()
    return x

def toBinary(desc):
    bits=[]
    for i in range(7,-1,-1):
        if (2**i>desc):
            bits.append('0')
        else:
            bits.append('1')
            desc=desc-2**i
    return bits

def toStr(lists):
    raw=[]
    for port in lists: raw+=toBinary(port)
    return ''.join(raw)

### PROGRAM START ##################
live=False
#live=True
start=time.time()
if live==True:
    while True:
        a,b,c,d=readVals()
        if not a==123: continue #bad data
        elapsed=time.time()-start
        print "%.010f,%s"%(elapsed,toStr([b,c,d]))
else:
    times=0
    data=''
    f=open("out.txt",'a')
    while True:
        a,b,c,d=readVals()
        if not a==123: continue #bad data
        elapsed=time.time()-start
        data+="%.010f,%sn"%(elapsed,toStr([b,c,d]))
        times+=1
        if times%1000==999:
            print "%d readings / %.02f = %.02f /sec"%(times,elapsed,times/elapsed)
            f.write(data)
            data=""

#logicGraph.py - this will show the data in a pretty way
import matplotlib.pyplot as plt
import numpy

c={
0:"",
1:"",
2:"blk sol",
3:"yel str",
4:"yel sol",
5:"pur sol",
6:"pur str",
7:"",
8:"",
9:"",
10:"blu sol",
11:"blu str",
12:"orn sol",
13:"orn str",
14:"pnk sol",
15:"pnk str",
16:"",
17:"",
18:"",
19:"",
20:"",
21:"",
22:"",
23:"",
24:"",
}

print "loading"
f=open("out.txt")
raw=f.readlines()
f.close()

print "crunching"
times=numpy.array([])
data=numpy.array([])
for line in raw:
    if len(line)<10: continue
    line=line.replace("n",'').split(',')
    times=numpy.append(times,float(line[0]))
    bits = []
    for bit in line[1]:
        if bit=="1":bits.append(1)
        else:bits.append(0)
    data=numpy.append(data,bits)

columns=24
rows=len(data)/columns
data=numpy.reshape(data,[rows,columns])
print "DONE processing",len(data),"linesnn"
print "plotting..."
plt.figure()
plt.grid()
for i in range(len(c.keys())):
    if c[i]=="": continue
    plt.plot(times,data[:,i]+i*1.1,'-',label=c[i])
plt.legend()
plt.show()

Frequency Counter Hacked to add USB Functionality

This is a multi-part blog entry added over 2 days of time documenting the progress of the addition of USB to a simple frequency counter. The final result lets me graph frequency over time on the computer, automatically, over days of time if desired. I’m quite pleased at the result, especially considering so little circuitry was required!

It looks like this will be a multi-part blog entry. I’m in the process of figuring out how to add USB functionality to this simple device, which will be a fun way for me to express my creativity and think a bit outside the box while gaining some simple electrical engineering experience! Here’s the jist of what I’m planning…

After a brief trip to Orlando to visit family, I decided to stop by the house of one of my neighbors who worked at the same small engineering company I did when I was first starting college (about the time I decided to peruse biology rather than engineering). I hadn’t seen him in a while and we talked about various electronics things (he’s working on an impressive project currently), and before I left he offered me a brown box. “Do you have any use for a function generator?” I got excited and said “sure!” On closer inspection, it was actually a frequency counter, and he said “oh well I don’t need one of those anyway” and gave it to me. I was ecstatic! Between this post, this post, this post, this post, and this final project post you can tell that building a frequency counter was really important to me, and that I was never truly satisfied with the result - it wasn’t stable to the Hz! I’m excited to finally have a real counter at my workstation. (It’s an instek GFC-8010H, 1-120 MHz range.) Now onto figuring out how to build a spectrum analyzer… X_x

Update (2 days later)

I never can leave things alone can I? While basking in happiness over this new acquisition I pondered how easy it would be to interface this to a PC. I would like to graph frequency drift over time directly (not using a radio receiver outputting audio which I graph, since the radio is sensitive to drift). Plus this counter allows sample sizes of 10 seconds! That’s some serious resolution (compared to what I’m used to at least). First step to PC interfacing is to see what I’ve got to work with. I unscrewed the box and snapped some photos of the surprisingly simple device… I can’t believe this costs over $175 (as listed on Amazon.com) - it’s so simple!

I guess it all makes sense. AC transformer and rectifier diodes with a smoothing capacitor on the bottom left, fed into a 7805 linear voltage regulator, no doubt powering the micro-controller (big IC), logic buffer (small IC), and whatever analog circuitry is under the panel.

I’m not going to lift the panel because it’s obvious what’s under there. Likely some high gain, high distortion amplifier stages to provide a lot of buffering, eventually the input shape is fed to the chip for counting.

After posting and thinking about it, the curiosity got to me! I lifted the panel and this is what I found…

There’s our buffer and wave shaper! The full datasheet shows it’s a (triple) line driver.

Come to think of it, I’m not entirely sure about that smaller IC. It’s a 74HC00N, quad NAND gate. Knee-jerk was to say it was used for dividing-down the frequency, but that’s silly since it takes 2 NAND gates to make a flip flop, and that chip would be only 2 flip flops (/4), and there are flip flip chips for all that. Perhaps this has something to do with the buttons on the front panel? Perhaps something to do with square-shaping the oscillator with the 10mhz crystal? The big GFC 9701-1 IC seems to be a custom counter chip used in many Instek products. Here’s a blurb from a page of their manual for a function generator they make:

The most important function of the internal counter is to show the main frequency on the display. So we take a square signal from the square shaper and change the level to TTL compatible level with a TTL shaper block (is this the role of that NAND gate?) then the signal will connect with the counter GFC-9701. Because the counter directly connects with the MPU system, the MPU can get correct frequency and show it on the display. So, it seems that chip is already outputting data ready to go into a CPU. I wonder if it’s outputting some type of data on an unused pin, ripe for the picking? I can’t find more ICs in this device, so it’s possible no other MCU is driving the display, and this counter IC is doing that all on its own. Bah, again curiosity is getting the best of me… [unscrews front panel]

More ICs! I couldn’t see them well before so I’m glad I opened up the display. The ULN2003A is a 7 channel darlington array, x2 = 14 darlingtons. The CD4511 is a common 7-segment driver - BINGO! If I’m going to interface this device by intercepting the display, this is the way to do it! The darlingtons tell me which character is selected, and the input of this chip tells me the value to be displayed. Pow!

Let’s take a closer look at that main chip again… X-RAY VISION TIME! I used Image-J to extract the red channel of the image and increased contrast, inverted, then used a 10 pixel wide unsharp mask with 0.8 weight to bring-out the leads. I guess I could have just unscrewed it and looked at the bottom, but where’s the fun in that? I imagine the top left pin is input of frequency. The bottom left pins go to buttons on the front, so they’re for front panel input. The headers on the right go to the front panel display. The pin going to the smaller IC must be the clock input, so that NAND gate DOES do something with shaping the input clock signal. On the top fight of the image you can see the crystal connecting to that gate. The trace going in the center of the chip on top is probably +5V supply for the chip. I’m not sure about much else, and I don’t feel like poking around with a continuity meter right now. UPDATE - I tested each pin with an analog o-scope. I found that pin 6 (unconnected) outputs a burst of data every time the display updates - this is my data line! If I had a logic analyzer I’d know how to read it… BAH!

What’s this?! The voltage regulator with its hefty heat sink (which obviously gets quite warm) is attached to a 10.000 MHz crystal! Is this the time base crystal? Doesn’t accuracy depend on thermostability of this crystal? It’s not just near it - it’s physically connected with it through metal! Does this imply that a loaded 7805 voltage regulator produces heat more steadily, and with a final temperature more stable than room air in a plastic enclosure??

update: The following was emailed to me in response to this puzzling issue. It’s from my good friend Bill!

It may be an SC-cut crystal which is the best type for precision oscillators because the turn around inflection occurs at a much broader temperature range than the regular AT-cut, el cheapo types we often use. SC types, if carefully selected, can remain within a fraction of a ppm over a temperature range to 10 to 20 C. The turn around point temperature is pretty high, about 90 C, compared to around 25C for the at-cut. So, my guess is that the 7805 provides this really high temperature to the xtal and can be trusted to not vary by more than a few degrees, particularly in a laboratory environment. –Bill (W4HBK) Afterthought: This would make one hell of a huff-and-puff oscillator!

PROJECT COMPLETED!

I’m quite excited, the end product works wonderfully! It looks pretty spiffy too!

Here’s some video showing the device at work!

Of course Python and MatPlotLib can graph it:

… but so can Excel!

UPDATE Oops, I forgot to remove the trailing zero. That’s 9.9 MHz, not 99 MHz. That’s easy enough to do later, so I’m not going to fix it and re-post updated images. Don’t email me about it, I know ^_^

UPDATE 2 Here’s some useful data! I hooked up a canned oscillator at 3.57 something MHz (very stable) and watched it as my frequency counter warmed up. The result showed that the counter takes about 2 hours to warm up!!! he shift is only about 15 Hz over 2 hours, but still it’s good to know.

Once it’s warm, it’s stable!

Schematic

This device is very simple and specialized for my use and I have not designed a custom schematic. USB functionality is as recommended by V-USB, similar to:

For more information on the USB circuitry, view the hardware considerations page relating to the V-USB project.

CODE

__Microcontroller code - __ Although it’s hard for me, I really don’t think I can release this 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. It’ll be shared soon! Here are the python scripts for the logging and for the graphing:


#This code polls the USB device and displays/logs frequency
from optparse import OptionParser
import time
import usb.core
import usb.util
import os

while True:
        print "nTrying to communicate with the Gator Keyer ...",
        dev = usb.core.find(find_all=True, idVendor=0x16c0, idProduct=0x5dc)
        if len(dev)==0: print "FAIL"
        dev=dev[0]
        dev.set_configuration()
        print "there it is!"
        break

def readVals(c):
    x=dev.ctrl_transfer(0xC0, 3, c,4,4).tolist()
    val=x[0]
    if val>9: val=0
    return val

def readDisp():
    c=[]
    for i in range(1,9):
        val=readVals(i)
        c.append(val)
        #print "char",i,"=",val
    disp="%d%d%d%d%d%d%d%d"%(c[0],c[1],c[2],c[5],c[6],c[3],c[4],c[7])
    return disp

def readFreq():
    i=0
    first=readDisp()
    while True:
        if first==readDisp():
            i+=1
            if i==5: break #we're good!
        else: #FAIL! start over...
            i=0
            first=readDisp()
    return first

### PROGRAM START ##################

start=time.time()
while True:
    line="%.02f,%s"%(time.time()-start,readFreq())
    print line
    f=open("freq.csv",'a')
    f.write(line+"n")
    f.close()
    time.sleep(1)

#This code reads the log file and graphs it with matplotlib
import matplotlib.pyplot as plt
import numpy

print "loading"
f=open("freq.csv")
raw=f.readlines()
f.close()

print raw

print "crunching"
times=numpy.array([])
data=numpy.array([])
for line in raw:
    if len(line)<10: continue
    line=line.replace("n",'').split(',')
    times=numpy.append(times,float(line[0]))
    data=numpy.append(data,float(line[1]))

#data=data/1000000.0
print times, data
print "DONE processing",len(data),"linesnn"
print "plotting..."
plt.figure()
plt.grid()
plt.plot(times,data,'-')
plt.plot(times,data,'.')
plt.show()

Sound Card Microcontroller/PC Communication

_This page describes a method of sending data from a microchip to a PC using pulses of data. It’s an alternative to more traditional serial or USB methods of connectivity. It’s not intended as a solution for consumer products, but rather an easy hack for hobbyists to employ if they don’t have the equipment for other methods. This method doesn’t require any circuitry, just a sound card. The one built in your computer is fine, but I’m using a $1.30 USB sound card for simplicity. It boils down to just a single microcontroller pin connected to a PC sound card microphone jack!

MY PROBLEM: I want to send data from a simple microcontroller to a PC. While USART and a serial port is the common solution like I’ve done before, it’s not convenient because it requires a level converter (like a MAX232, about $4), crystal (specific values based on bit and error rate, if you’re lucky you might have a right value in your junk box), and an archaic PC which actually has a serial port. A usb serial port adapter sounds clever, but many aren’t supported on Linux, Windows Vista, or Windows 7. Also, many small chips (most of the ATTiny series) don’t have built in serial capabilities, so it has to be bit-banged in software! Yuk! The second choice would be USB. This requires a crystal too, zener diodes, and bit-banging the USB protocol with something like V-USB since most of the AVR series don’t have built in USB (do they even make breadbordable DIP chips with USB?). Even so, it requires drivers, custom software, cross-platform frustrations, etc. I know PIC has some 18f series chips with USB, but I don’t feel like switching architectures just to send a few bytes of data to a PC. FDTI has a FT232R chip which is a USB serial port adapter, but it’s expensive (about $5) and doesn’t come in dip, so no breadboarding! Sure there are adapter boards, but that just adds the cost. I’m not excited about a $5 solution for a $1 microcontroller. I even did a bit of trolling on AVR Freaks to see if anyone could help me out - just more of the same!

MY SOLUTION: Send data through the sound card! USB sound cards are $1.30 (shipped) on eBay! It couldn’t be simpler. Send pulses, measure distance between pulses. Short pulses are a zero, longer ones are a 1, and very long pulses are number separators. A Python solution with PyAudio allows 1 script which will work on Mac, Linux, Windows, etc, and because it calibrates itself, this will work on any chip at any clock rate. Data is initiated with calibration pulses so timing is not critical - the PC figures out how fast the data is coming in. Check it out! (scroll way down for a bidirectional communication solution)

Here is a sound card I used for bidirectional communication:

Output graph (python and excel) of temperature when I put a soldering iron near the sensor:

UNIDIRECTIONAL SOLUTION

The following code is designed to have a chip send data to your PC automatically. This can be run on any micro-controller (PIC or AVR I guess, the concept is the same) at any clock rate. Just make sure the sound card is recording fast enough to differentiate pulses. (keep scrolling down for a bidirectional solution)

A NOTE ABOUT MY CODE: This is just the code I used for my demonstration. It might be more logical for you to write your own since the concept is so simple. I’m a dental student, not a programmer, so I’m sure it’s not coded very elegantly. I didn’t work hard to make this code easy to read or easy to share. With that being said, help yourself!

/*The following code is written in AVR-GCC for an ATTiny44a.
It reads ADC values on 3 pins and reports it each second along
 with a number which increments each time data is sent.
It's designed as a starting point, allowing anyone to
customize it from here!*/

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

// bytes we want to send to the PC
volatile int data1=0;
volatile int data2=0;
volatile int data3=0;
volatile int data4=0;

void solid(){  // dont touch
    _delay_ms(1);
    pulse(1);pulse(1);pulse(1);pulse(3);pulse(3);
    pulse(3);pulse(5);pulse(5);// CALIBRATION PULSES
}
void pulse(char size){ // dont touch
    PORTA|=_BV(PA3);
    _delay_us(100);
    PORTA&=~_BV(PA3);
    while (size){size--;_delay_us(100);}
}
void sendVal(unsigned long tosend){ // dont touch
    pulse(5); // send a space
    while (tosend){
        if (tosend&1){pulse(3);} // send ONE
        else {pulse(1);} // send ZERO
        tosend=tosend>>1;
    }
}

int readADC(char adcNum){
    _delay_ms(1);
    ADMUX=adcNum; // select which ADC to read, VCC as ref.
    ADCSRA=0b11000111; // enable, start, 128 prescale
    while (ADCSRA&( 1<<ADSC)) {}; // wait for measurement
    return ADC;
}

void takeReadings(){
        data1=readADC(0); // ADC0
        data2=readADC(1); // ADC1
        data3=readADC(2); // ADC2
        data4++; // incriment just because we want to
}

void sendStuff(){ // EDIT to send what you want
    solid(); //required
    sendVal(12345); //required
    sendVal(12345); //required
    sendVal(54321); //required

    sendVal(data1);
    sendVal(data2);
    sendVal(data3);
    sendVal(data4);

    pulse(1); //required
}

int main(){
    DDRA|=_BV(PA2)|_BV(PA3);
    for (;;){
        _delay_ms(1000);
        takeReadings();
        sendStuff();
    }
    return 0;
}
"""
file name: listenOnly.py

This is the PC code to listen to the microphone and display
and log the data. It probably does NOT need adjustment!
 Make sure the correct sound card is selected (in the code)
 and make sure microphone input is turned up in volume control.

This code is what was used on my PC for the demonstration
video. This is the listenOnly.py file which will turn any audio
 detected from a sound card into data, optionally logging it
(if the last few lines are uncommented). This also works to
capture data for the bidirectional communication method,
described below on this website.

If this is running but no data is coming through, make sure the
microphone is selected as a recording device, the correct sound
card is selected, and the microphone volume is turned to high.

REQUIRED: To run this, you need to have the following installed:
-- Python 2.6
-- numpy for python 2.6
-- matplotlib for python 2.6
-- pyaudio for python 2.6
(other versions may work, but this is what I'm using)
"""
import numpy
import pyaudio
import matplotlib.pyplot as plt
import wave
import time

def listCards(dontAsk=True):
    p=pyaudio.PyAudio()
    print "SOUND CARDS:"
    for i in range(p.get_default_host_api_info()["deviceCount"]):
        if p.get_device_info_by_index(i)["maxInputChannels"]>0:
                cardName = p.get_device_info_by_index(i)["name"]
                cardIndex = p.get_device_info_by_index(i)["index"]
                print "[%d] %s"%(cardIndex,cardName)
    if dontAsk: return
    return int(raw_input("CARD NUMBER TO USE:"))

cardID=1
listCards()
print "USING CARD:",cardID

rate=44100.0
sampleSize=1024

def data2vals(data):
    vals=numpy.array([])
    lastPeak=0
    for i in range(1,len(data)):
        if data[i]==True and data[i-1]==False:
            if lastPeak>0: vals=numpy.append(vals,i-lastPeak)
            lastPeak=i
    return vals

def binary2dec(binary):
    binary=binary[:-1]
    dec=0
    s=""
    for i in range(len(binary)):
        dec=dec*2
        dec+=binary[i]
        s="%d"%binary[i]+s
    #print s,"=",dec #11111100101100000 = 3391
    return dec

def readVals(vals):
    if len(vals)<7: return False
    vals2=[]
    aLow = min(vals[0:3])
    aMed = min(vals[3:6])
    aHigh = vals[6]
    thresh1=sum([aLow,aMed])/2+2
    thresh2=sum([aMed,aHigh])/2+2
    #print "tresholds:",thresh1,thresh2
    #print vals
    vals=vals[8:]
    binary=[]
    for i in range(len(vals)):
        if vals[i]>thresh2:
            vals2.append(binary2dec(binary))
            binary=[]
        if vals[i]>thresh1:binary=[1]+binary
        else:binary=[0]+binary
    vals2.append(binary2dec(binary))
    for i in range(len(vals2)):
        if vals2[i]==54321: return vals2[i+1:]
    return False

def playFile():
    chunk = 1024
    wf = wave.open("short_onenum.wav", 'rb')
    p = pyaudio.PyAudio()
    stream = p.open(format =
                    p.get_format_from_width(wf.getsampwidth()),
                    channels = wf.getnchannels(),
                    rate = wf.getframerate(),
                    output = True)
    data = wf.readframes(chunk)
    while data != '':
        stream.write(data)
        data = wf.readframes(chunk)
    stream.close()

def captureData():
    pyaud = pyaudio.PyAudio()
    stream = pyaud.open(format=pyaudio.paInt16,channels=1,
        rate = 44100,input_device_index=cardID,input=True,output=True)
    sample=numpy.array([])
    while True:
        sampleNew=numpy.fromstring(stream.read(sampleSize),dtype=numpy.int16)
        sampleNew=(sampleNew<-25000)*1
        if True in sampleNew: sample=numpy.append(sample,sampleNew)
        else:
            if len(sample):
                stream.close()
                return sample
    stream.close()

tone_quiet=0

def buildNumber(num=123):

    if num>255: print "NUMBER TOO HIGH!!!"
    #print num,'=',
    num+=1
    for i in [7,6,5,4,3,2,1,0]:
        if num>2**i:one();num=num-2**i;#print"1",
        else: zero();#print"0",
    #print
    space()

def pulse():
    global data
    data+=[-30000]*10

def space():
    global data
    data+=[tone_quiet]*900
    pulse()

def one():
    global data
    data+=[tone_quiet]*600
    pulse()

def zero():
    global data
    data+=[tone_quiet]*300
    pulse()

def silence(msec=1000):
    global data
    data+=[tone_quiet]*int(41.1*msec)

data=[]
def sendAudio(numbers=[11,66,77]):
    global data
    data=[]
    silence(100)
    buildNumber(250)
    print "SENDING",
    for numba in numbers:
        buildNumber(numba)
        print numba,
    buildNumber(250)
    silence(100)
    data=numpy.array(data)
    data=-data
    data=data.tostring()
    print

    p = pyaudio.PyAudio()
    stream = p.open(rate=44100, channels=1, format=pyaudio.paInt16,
                    input_device_index=cardID, output=True)
    stream.write(data)
    stream.close()
    p.terminate()

i=0
while True:
    i+=1
    val=readVals(data2vals(captureData()))
    if val == False: continue
    line=""
    for item in val: line+=str(item)+","
    print i,line
    #f=open('log.csv','a')
    #f.write("%sn"%line)
    #f.close()

BIDIRECTIONAL SOLUTION

What if we want to send data TO the microcontroller? The solution is a little more complex, but quite doable. Just add an extra wire to the sound card’s speaker output and attach it to PCINT0 (the highest level internal interrupt). This is intended for advanced users, and if you’re doing this you probably are better off with USB or serial anyway! … but heck, why not do it as a proof of concept!

Note that the USB sound card speaker output was not powerful enough to trigger the digital input pin of the AVR, so an inverting buffer was made from a single NPN transistor (2n3904). The hardware interrupt was attacked to the collector, and the collector was attached through +5V through a 220 ohm resistor. The emitter was grounded. The base was attached directly to the sound card output. I also tried running the sound card output through a small series capacitor (0.1uF) and biasing the base to ground through a 1Mohm resistor and it worked the same. Hardware, simple. Chip-side software… a little more complex.

"""
This code is what was used on my PC for the
 demonstration video. The listenonly.py file
 (above on site) was also used without modification.
"""
import pyaudio
from struct import pack
from math import sin, pi
import wave
import random
import numpy
import time

RATE=44100
maxVol=2**15-1.0 #maximum amplitude
p = pyaudio.PyAudio()
stream = p.open(rate=44100, channels=1, format=pyaudio.paInt16,
        input_device_index=1, output=True)

def pulseZero():
    global wvData
    wvData+=pack('h', 0)*30
    wvData+=pack('h', maxVol)

def pulseOne():
    global wvData
    wvData+=pack('h', 0)*40
    wvData+=pack('h', maxVol)

def pulseSpace():
    global wvData
    wvData+=pack('h', 0)*50
    wvData+=pack('h', maxVol)

def buildNumber(num=123):
    if num>255: print "NUMBER TOO HIGH!!!"
    num+=1
    for i in [7,6,5,4,3,2,1,0]:
        if num>2**i:
            pulseOne()
            num=num-2**i
        else:
            pulseZero()

wvData=""
wvData+=pack('h', 0)*2000
pulseOne() #required before sending data

buildNumber(55)
buildNumber(66)
buildNumber(77)
buildNumber(123)

wvData+=pack('h', 0)*2000

while True:
    print "SENDING",
    stream.write(wvData)
    raw_input()
/*
This code is what was used on my AVR
microcontroller for the demonstration video
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>

volatile long commandIncoming=0;
volatile char command1=0;
volatile char command2=0;
volatile char command3=0;
volatile char command4=0;
volatile char bitsGotten=0;

// timing thresholds are critical! Send pulses to the chip
// and have it report the time between them. Use this to
// determine the best threshold value for your application.
// The ones here must be changed if you run at a speed other
// than 1mhz or if you use different timings in PC software
#define thresh_low 100 // between this and the next
#define thresh_high 130 // is the range for a logical 'one'

// ######## OUTGOING AUDIO DATA #########
void solid(){
    _delay_ms(1); //LONG LOW
    pulse(1);pulse(1);pulse(1);pulse(3);pulse(3);
    pulse(3);pulse(5);pulse(5);// CALIBRATION PULSES
}
void pulse(char size){
    PORTA|=_BV(PA3);
    _delay_us(100);
    PORTA&=~_BV(PA3);
    while (size){size--;_delay_us(100);}
}
void sendVal(unsigned long tosend){
    pulse(5); // send a space
    while (tosend){
        if (tosend&1){pulse(3);} // send ONE
        else {pulse(1);} // send ZERO
        tosend=tosend>>1;
    }
}

// ######## INCOMING AUDIO DATA #########
// NOTE THAT INPUTS ARE NORMALLY *HIGH* AND DROP *LOW* FOR SIGNAL
SIGNAL (PCINT0_vect) { // audio input trigger
    TIMSK0|=(1<<TOIE1); //Overflow Interrupt Enable
    if (TCNT0<10){return;} // seem too fast? ignore it!
    // Enable the following line to test custom timings
    //command1=command2;command2=command3;
    //command3=command4;command4=TCNT0;
    bitsGotten++;
    commandIncoming=commandIncoming*2; // shift left
    if (TCNT0>thresh_low){commandIncoming++;} // make 1
    TCNT0=0;
}

ISR(TIM0_OVF_vect){ // TIMER OVERFLOW
    if (bitsGotten){sendStuff();}
}

void fillCommands(){
    command1=(char*)(commandIncoming>>24);
    command2=(char*)(commandIncoming>>16);
    command3=(char*)(commandIncoming>>8);
    command4=(char*)(commandIncoming);
}

void sendStuff(){
    TIMSK0=0; //Overflow Interrupt
    cli(); // disable interrupts!
    fillCommands();
    solid(); // start data transmissions with this
    sendVal(12345);
    sendVal(12345);
    sendVal(54321);
    sendVal(command1);
    sendVal(command2);
    sendVal(command3);
    sendVal(command4);
    sendVal(1234567890);
    pulse(1);
    bitsGotten=0;
    sei(); // enable interrupts again!
    TIMSK0|=(1<<TOIE1); //Overflow Interrupt
}

// ######## MAIN PROGRAM #########
int main(){

    DDRA|=_BV(PA2)|_BV(PA3);

    // SET UP FOR SOUND CARD INTERRUPT
    MCUCR = 0b00000010; // trigger interrupt on falling edge
    GIMSK = 0b00010000; // pin change interrupt enable 0
    GIFR =  0b00010000; // flag register, same as above
    PCMSK0 = (1<<PCINT0); // Set Pin to use (PCINT0)
    sei(); // enable global interrupts

    // SET UP 8-bit COUNTER
    TCCR0B|=0b00000010;
    //TCCR1B|=(1<<CS12)|(1<<CS10); // prescaler 1024
    TIMSK0|=(1<<TOIE1); //Enable Overflow Interrupt Enable
    TCNT0=0;//Initialize our varriable (set for 1/15th second?)

    // MAIN PROGRAM
    for (;;){}
    return 0;

}

In closing, I’m tickled this works so well. It’s funny to me that no one’s really done this before in the hobby field. I’m sure I’m not the only one who wished there were an easy way to do this. I’m sure the process could be greatly improved, but this is a fun start. Wow, it’s late, I should get to bed. I have to treat patients tomorrow morning!

PS: If you replicate this concept, let me know about it! I’d love to see your project!

UPDATE: This story was featured on this post of HackADay.com!


Create Mono and Stereo Wave Files with Python

My current project involves needing to create stereo audio in real time with Python. I’m using PyAudio to send the audio data to the sound card, but in this simple example I demonstrate how to create mono and stereo sounds with Python. I’m disappointed there aren’t good simple case examples on the internet, so I’m sharing my own. It doesn’t get much easier than this!

Python 2

from struct import pack
from math import sin, pi
import wave
import random

RATE=44100

## GENERATE MONO FILE ##
wv = wave.open('test_mono.wav', 'w')
wv.setparams((1, 2, RATE, 0, 'NONE', 'not compressed'))
maxVol=2**15-1.0 #maximum amplitude
wvData=""
for i in range(0, RATE*3):
    wvData+=pack('h', maxVol*sin(i*500.0/RATE)) #500Hz
wv.writeframes(wvData)
wv.close()

## GENERATE STERIO FILE ##
wv = wave.open('test_stereo.wav', 'w')
wv.setparams((2, 2, RATE, 0, 'NONE', 'not compressed'))
maxVol=2**15-1.0 #maximum amplitude
wvData=""
for i in range(0, RATE*3):
    wvData+=pack('h', maxVol*sin(i*500.0/RATE)) #500Hz left
    wvData+=pack('h', maxVol*sin(i*200.0/RATE)) #200Hz right
wv.writeframes(wvData)
wv.close()

The output is two sound files which look like this:

Python 3

from struct import pack
from math import sin, pi
import wave
import random
from os.path import abspath

# create a bytestring containing "short" (2-byte) sine values
SAMPLE_RATE = 44100
waveData = b''
maxVol = 2**15-1.0
frequencyHz = 500.0
fileLengthSeconds = 3
for i in range(0, SAMPLE_RATE * fileLengthSeconds):
    pcmValue = sin(i*frequencyHz/SAMPLE_RATE * pi * 2)
    pcmValue = int(maxVol*pcmValue)
    waveData += pack('h', pcmValue)

# save the bytestring as a wave file
outputFileName = 'output.wav'
wv = wave.open(outputFileName, 'w')
wv.setparams((1, 2, SAMPLE_RATE, 0, 'NONE', 'not compressed'))
wv.writeframes(waveData)
wv.close()
print(f"saved {abspath(outputFileName)}")