I was brainstorming some RF circuits today and I had the desire to create a rapid transmitter/receiver pair that anyone would have around their house. I decided that AM or FM radio would be good since everyone can receive that, and pondered how best to generate the necessary radio signal and modulate it appropriately. After a few LC oscillator designs, I thought about the RC oscillators built into most micro-controllers. I grabbed an ATMEL AVR I had on hand (an ATTiny44A) and checked the datasheet. It had an 8MHz RC oscillator, which could be divided-down to 1MHz, and output on a CKOUT pin – all configurable with a few hardware fuses! Note that commercial AM radio stations are between 0.52 and 1.61 MHz, so a 1MHz signal would be smack-dab in the middle of our radio dial! I had to build a prototype to see how well it would work. Once concern was that the RC oscillator wouldn’t be stable enough to produce reliable audio – boy was I wrong!
schem

The circuitry is textbook simple. Appropriately configured, the AVR generates 5V square waves from its CKOUT pin. Although a pretty shape, they’re not powerful enough on their own to be heard across a room, so I needed an amplifier stage. A class C amplifier provided by a 2n7000 is commonly done in the low power amateur radio (QRP) community, so I went with it. A 2n7000 N-channel MOSFET with a 220-ohm resistor on the drain and the CKOUT directly into the gate did a fine job (I’ve used this design for 10MHz QRSS transmitters before), and I was able to modulate its amplitude by feeding the voltage from a MCU pin (turned on/off rapidly) through a decoupling capacitor into the drain of the MOSFET. I couldn’t have asked for a simpler result!

This code sends a message in Morse code. It seems too easy! Applications are endless, as this is one heck of an easy way to send audio from a micro-controller to a radio, and possibly to a computer. Morse code is easy, and since we have the ability to dynamically generate different audio frequencies and tones, data exchange is easy too! Nothing’s stopping you from adding the code to turn this into a RTTY (or Hellschreiber?) transmitter.
DSCN1670

Again, this transmitter can be heard on a standard AM radio tuned to about 1000 kHz. This is the setup I used with great success:
schem2

Here’s the code on the chip! Nothing complicated:

// designed for and tested with ATTiny44A
#include <avr/io.h>
#define F_CPU 1000000UL
#include <avr/delay.h>
#include <avr/interrupt.h>

void beep(){
	for(char i=50;i;i--){
		DDRA|=_BV(PA7);_delay_ms(1);
		DDRA&=~_BV(PA7);_delay_ms(1);
	}
}

void rest(){_delay_ms(100);}

void dot(){beep();rest();}
void dash(){beep();beep();beep();rest();}
void space(){rest();rest();}
void space2(){space();space();}

int main(){
	DDRA|=_BV(PA7);
	for(;;){
		dot();dot();dot();space(); 			// S
		dash();dot();dash();dot();space(); 	// C
		dash();dash();dash();space(); 		// O
		dash();space(); 					// T
		dash();space(); 					// T
		space2();
		dot();dash();dot();space();			// R
		dash();dash();dash();space(); 		// O
		dash();dot();dash();dot();space(); 	// C
		dash();dot();dash();space();		// K
		dot();dot();dot();space(); 			// S
		_delay_ms(1000); // silence
	}
	return 0;
}

THIS IS ILLEGAL to do if you exceed a certain amount of power because you’re stepping on legitimate commercial broadcasters and will have to deal with the FCC. Additionally, you are transmitting on more frequencies than the primary frequency because the signal is heavy in odd harmonics. This means a 1 MHz transmitter, producing square waves, will generate tones on 1, 3, 5, 7 MHz, etc. Don’t do this with much power! Heck, you probably shouldn’t do it at all 😉





This post describes a project I designed which transmits strings of data from a microcontroller to a PC’s screen using audio beeping in a special mode called Hellschreiber. Although these days it’s almost exclusively used by amateur radio operators, I thought it would make a cool microcontroller project! The result can be accomplished with a microcontroller and a speaker as a transmitter and a PC with a microphone as a receiver and decoder, or with actual radio equipment (even toy walkie talkies) by transmitting the tones over modulated radio frequencies for long distance communication! Ideas anyone?

SPECIAL THANKS: I’d like to think Mike Seese for his brainstorming help in making this project a reality. Mike and I are working on a high altitude balloon project together, and a creative inexpensive radio link is one of our goals. Thanks Mike!

As a professional dental student by day and amateur electrical/RF engineer by night, I’m having a very strange summer. I’m developing rapidly in my experience and skills in both arenas. I finally feel like I have a working knowledge of most fundamental electrical and radio frequency concepts, and I’m starting to see patients and do procedures on humans (no more mannequins) in the student dental clinic. For legal and ethical reasons I do not write specifics about what I do with my patients, but I certainly make up for it by documenting the electronic projects I work on! My goals of doing this are to (a) inspire potential electronics tinkerers to come up with new ideas and attack new projects, and (b) receive feedback and insight from those more experienced than me to help me grow in my knowledge. My eye caught a comment a few posts ago that made me smile: You have been blessed with talent and the drive to attempt things not been tried before, keep it up, great job. –David S While I can’t claim that everything I do is truly novel or never tried before, I appreciate the encouraging words. Thank you David S!

Today’s project is a fun one involving vintage wartime radio equipment, amateur radio computer software, and a healthy dose of microcontrollers! My goal is to design a single chip Hellschreiber (technically Feldhellschreiber) transmitter. “Hellschreiber” translates into English as “Light Writer” and is a pun on the name of its inventor, Rudolf Hell, who built the first device in 1920. It was intended to allow messages to be transferred over poor radio links too noisy for intelligible voice or radioteletype (RTTY) communication. Its cool factor is upped by the fact that it was sometimes used by the German military in conjunction with the Enigma encryption system during World War 2! [As an aside, RTTY is still pretty sweet and dates back to the mid 1800s! Check out hardware receivers in video 1 and video 2]

Seeing a battlefield-ready Hellschreiber receiver gives you a good idea of how it works. (The video isn’t mine, I found it on youtube.) The concept is relatively simple (shown above), and the receiver has only 2 moving parts. A spinning corkscrew presses a ticker tape into ink when it receives a radio signal. As the radio signal beeps on and off, the corkscrew contacts at different positions at different times, and letters are written on the ticker tape! anaglyph-hell-GL-11The designers of these things were extraordinarily creative! The picture on the right shows a Hellschreiber transmitter – basically a typewriter with mechanical wizardry that turns key presses into a series of radio tones corresponding to the pixelated shape of a character.

Almost a century later, people are still sending messages around the world using Hellschreiber! With an amateur radio license and an amateur radio transceiver you can tune around special Hellschreiber calling frequencies and engage in conversations with other people who enjoy using this unique mode. Computers have modernized the process, allowing you to send Hellschreiber text by typing on your keyboard and receive it by just looking at your screen. My favorite program (free) to do this is Digital Master 780, part of Ham Radio Deluxe.

This is the project I just completed. It takes strings of text stored (or dynamically generated) in an array on a microcontroller (I’m using an ATMega48, but the code is almost identical for any ATMEL AVR microcontroller, and easy adapted for other architectures) and turns it into an audio tone using PWM. This audio tone could be fed into a speaker and a microphone across the room could receive it and use the software to show the received data, or the audio could be fed into a radio transmitter and a PC hooked to the receiver could decode the audio. Either way, the text in the microcontroller is converted to Hellschreiber audio tones ready to be used however you see fit! Although I designed it as a resilient way to transmit GPS/altitude data from a high altitude balloon using a small, cheap, low-power radio transmitter, this project is just the foundation of a plethora of potential projects!
DSCN1663

Here’s the circuit I’m using. It’s actually less complicated than shown – all those yellow wires are going to my AVR programmer! The chip just receives +5V and GND, and the audio is generated automatically and output on the OC0A pin, which happens to be pin 12 on my ATMega48. The output (audio level square waves) is fed to a crystal oscillator like this one, which generates square waves with an amplitude equal that to the input. Thus, by audio-frequency AC from the microchip, decoupled through a series capacitor, added to the power supply of the oscillator (provided by the 5V rail through a 1.8k resistor), we effectively produce an amplitude modulated (AM) radio signal!
DSCN1667

This is the receiver I’m using. I’m lucky enough to have an all-mode, general-coverage, 100W amateur radio transceiver! It’s a Yaesu 857-D and I’m completely in love with it. It’s quite pricey though! You can find wide coverage receive-only radios called radio scanners (or police scanners), often for $20 or so on eBay which would do just as good a job of receiving all sorts of radio signals! Whatever you use, after tuning into the audio with the ham radio delux software, you’ll be able to decode Hellschreiber like this:
hell

A few notes about the code: Each letter is sent twice vertically and I don’t think I should have done that. It’s easy enough to correct by eliminating the second FOR loop in the sendChar() function, and doubling the height of the pixels transmitted by changing on(1) and off(1) to on(2) and off(2). Then again, I could be mistaken – I don’t use this mode much. Also, horizontal width of characters (increase this and horizontally compress the received image to reduce the effects of noise) is controlled by a single variable, dynamically adjustable in software. Characters are created from a 3×5 grid (15 bits) and stored as an integer (16 bits, 2 bytes in AVR-GCC). Custom characters are certainly possible! This program takes 16.1% of program space (658 bytes) and 25.4% of data space (130 bytes) and certainly leaves room for optimization.

// designed for and tested with ATMega48
#include <avr/io.h>
#define F_CPU 8000000UL
#include <avr/delay.h>
#include <avr/interrupt.h>

/*
character format (3x5):
	KFA
	LGB
	MHC
	NID
	OJE

variable format:
	2-byte, 16-bit int 0b0ABCDEFGHIJKLMNO
	(note that the most significant bit is not used)
*/
#define A 	0b0111111010011111
#define B 	0b0010101010111111
#define C	0b0100011000101110
#define D	0b0011101000111111
#define E	0b0100011010111111
#define F	0b0100001010011111
#define G 	0b0100111000101110
#define H	0b0111110010011111
#define I	0b0100011111110001
#define J	0b0111110000100011
#define K	0b0110110010011111
#define L	0b0000010000111111
#define M	0b0111110110011111
#define N	0b0011111000001111
#define O	0b0011101000101110
#define P	0b0010001010011111
#define Q	0b0111011001011110
#define R	0b0010111010011111
#define S	0b0100101010101001
#define T	0b0100001111110000
#define U	0b0111110000111111
#define V	0b0111100000111110
#define W	0b0111110001111111
#define X	0b0110110010011011
#define Y	0b0110000011111000
#define Z	0b0110011010110011
#define n0	0b0111111000111111
#define n1	0b0000011111101001
#define n2	0b0111011010110111
#define n3	0b0111111010110001
#define n4	0b0111110010011100
#define n5	0b0101111010111101
#define n6	0b0101111010111111
#define n7	0b0110001011110000
#define n8	0b0111111010111111
#define n9	0b0111111010111101
#define SP	0b0000000000000000
#define BK	0b0111111111111111
#define SQ	0b0001000111000100
#define PR	0b0000110001100011
#define AR	0b0001000111011111

volatile char width=1; // width of characters, widen to slow speed

#define spd 8300 // synchronization, incr to make it slant upward

void rest(char times){while (times){times--;_delay_us(spd);}}

void on(char restfor){OCR0A=110;rest(restfor);}
void off(char restfor){OCR0A=0;rest(restfor);}

void sendChar(int tosend){
	char w;
	char bit;
	for(w=0;w<width*2;w++){ // left column
		off(1);
		for (bit=0;bit<5;bit++){
				if ((tosend>>bit)&1) {on(1);}
				else {off(1);}
			}
		off(1);
		}
	for(w=0;w<width*2;w++){ // middle column
		off(1);
		for (bit=5;bit<10;bit++){
				if ((tosend>>bit)&1) {on(1);}
				else {off(1);}
			}
		off(1);
		}
	for(w=0;w<width*2;w++){ // right column
		off(1);
		for (bit=10;bit<15;bit++){
				if ((tosend>>bit)&1) {on(1);}
				else {off(1);}
			}
		off(1);
		}
	off(14); // letter space (1 column)
}

// CUSTOMIZE THE MESSAGE, OR GENERATE IT DYNAMICALLY!
int message[]={AR,AR,AR,S,W,H,A,R,D,E,N,PR,C,O,M,SP,R,O,C,K,S,
	SP,AR,AR,AR,SP,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,
	V,W,X,Y,Z,n0,n1,n2,n3,n4,n5,n6,n7,n8,n9,BK,SP};

void sendMessage(){
	char i;
	for(i=0;i<sizeof(message)/2;i++){
		sendChar(message[i]);
	}
}

int main(){ // ### PROGRAM STARTS HERE ###

	// this sets up CPWM in CTC mode,
	// it may be slightly different for other chips
	DDRD|=255; // OC0A is now an output
	TCCR0A=0b01000010; // toggle on match, CTC mode
	TCCR0B=0B00000011; // set prescalar

	for(;;){
		width=1; // fast mode
		sendMessage();
		width=3; // slow mode
		sendMessage();
	}

	return 0;
}




[warning]This is one part of a multi-post project

view PART 1 for circuity and theory[/warning]

Last week I had the crazy idea of sending data from a PC to a microchip through the monitor, using javascript and a web interface as a ridiculously simple data transfer platform that would work on virtually any computer! While I quickly hacked together the hardware, I struggled with the web interface (I’m a little slow with javascript) and I got a lot of help from people around the internet, especially after my project (and need for assistance) was mentioned on Hack-A-Day!
DSCN1657

This is part two of a multi-page project. To fully understand what I’m trying to accomplish and why I want to accomplish it, read the first part of the project.

Finally, I have a working javascript! I’d like to thank Tom, Riskable, Ben, and Mike for their input on this script. We got it to a point where we think it’s friendly to the majority of browsers and platforms. The idea is simple – enter two bytes to send the chip, it generates it’s own checksum (an XOR of the two bytes), and it flashes it out. Here’s a photo of the interface, click it for a live demo:

flasher_interfaceHere’s the code that goes on the microchip:

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

volatile int times=1000;

char readADC(char pin){
  ADMUX = 0b1100000+pin; // AVCC ref on ADC5
  ADCSRA = 0b10000111; //ADC Enable, Manual Trigger, Prescaler 128
  ADCSRA |= (1<<ADSC); // reset value
  while (ADCSRA & ( 1<<ADSC)) {}; // wait for measurement
  return ADCH;
}

int main(void)
{
  lcd_init(LCD_DISP_ON);
  char lastClock=0;
  char thisClock=0;
  char thisClock2=0;
  char thisData=0;
  char buffer[8];

  char lastNum=0;
  char bitsGotten=0;

  int msInactive=0;

  /*for(;;){
    itoa(readADC(5), buffer, 10);
    lcd_gotoxy(0,15);
    lcd_puts(buffer);

    itoa(readADC(4), buffer, 10);
    lcd_gotoxy(8,0);
    lcd_puts(buffer);
  }*/

  for(;;){
    thisClock = readADC(5);
    if (thisClock<250){
      _delay_ms(1);
      if (readADC(5)>250) {break;}

      _delay_ms(1);
      if (readADC(4)<250) {thisData=1;}
      else {thisData=0;}
      lastNum=lastNum*2+thisData; // left shift, add data
      itoa(thisData, buffer, 10);
      lcd_puts(buffer);
      msInactive=0;

      bitsGotten++;
      if (bitsGotten==8){
        lcd_gotoxy(1,1);
        lcd_puts("=   ");
        lcd_gotoxy(2,1);
        itoa(lastNum, buffer, 10);
        lcd_puts(buffer);
        bitsGotten=0;
        lastNum=0;
        lcd_gotoxy(0,0);
      }

      while (1) {
        if (readADC(5)>250){
          _delay_ms(10);
          if (readADC(5)>250){break;}
        }
      }
    }
    else{
      msInactive++;
      if (msInactive==400){
        bitsGotten=0;
        lastNum=0;
        lcd_clrscr();
        lcd_puts(" TIMEOUT");
        _delay_ms(1000);
        lcd_clrscr();
        lcd_gotoxy(0,0);
        lcd_puts("________ =");
        lcd_gotoxy(0,0);
      }
    }
    _delay_ms(1);
  }
}

Here’s the javascript in a web page:

<html>
<head>
<style>
.flasher {
  font-weight: bold;
  text-align: center;
  color: #888888;
  width: 200px;
  height: 200px;
  background-color: black;
  float: left;
  -webkit-transform: translateZ(0);
  border-right-style:dotted;
  border-color:#888888;
  border-width:1px;
}
</style>
<script type="text/javascript">

/* Copyright 2011, Tom Hayward <tom@tomh.us>, MIT License */

var ms = 50,
  bytes = 0,
  leftblock = null,
  rightblock = null,
  statustext = null;

function sendBit(bit) {
  if (bit) {rightblock.style.backgroundColor = 'white';}
  else {rightblock.style.backgroundColor = 'black';}
  leftblock.style.backgroundColor = 'white';
  setTimeout(function() {
  leftblock.style.backgroundColor = 'black';
  rightblock.style.backgroundColor = 'black';
  }, ms);
}

function sendByte(byte) {
  var bits = 8;
  setTimeout(function() {
  var timer = setInterval(function() {
    bits--;
    sendBit(byte >> bits & 1);
    if (bits == 0) {clearInterval(timer);return;}
  }, ms * 2);
  }, ms * 2 * bits * bytes++);
}

function Pause() {
timer = setTimeout("endpause()",5000); // 3 secs
return false;
}

function endpause() {
sendData();
return false;
}

function sendData() {

  var button = document.getElementById('sendnow'),
    byte1 = parseInt(document.getElementById('b1').value),
    byte2 = parseInt(document.getElementById('b2').value),
    checksum = byte1 ^ byte2;
  leftblock = document.getElementById('leftblock');
  rightblock = document.getElementById('rightblock');
  statustext = document.getElementById('status');
  bytes = 0; // reset byte counter

  document.getElementById('b3').value = checksum;
  button.disabled = true;
  statustext.innerHTML = "Writing data...";

  sendByte(byte1);
  sendByte(byte2);
  sendByte(checksum);

  setTimeout(function() {
  statustext.innerHTML = "done";
  button.disabled = false;
  }, ms * 2 * 8 * bytes);

}

</script>
</head>
<body bgcolor="#666">

<h1>PC/MCU Flasher Interface</h1>
<code>
Byte 1: <input id="b1" type="text" name="b1" size="3" value="255" /> <br>
Byte 2: <input id="b2" type="text" name="b2" size="3" value="0" />  <br>
CHKsum: <input id="b3" type="text" name="b3" size="3" value="" disabled="disabled" />  <br>
<br>
<input id="sendnow" type="button" value="SEND NOW" onClick="javascript:Pause();" />
<br><br><br>
<p>Status: <span id="status"></span></p>
</code>
<div id="leftblock" class="flasher"> CLOCK</div>
<div id="rightblock" class="flasher"> DATA</div>

</body>
</html>




UPDATE! >>> [FINISHED PROJECT HERE]

Several days ago I had a crazy idea. I was driving to Orlando to pick my wife up from the airport and it was dark and stormy on the highway and I was thinking about the backlash I got from my Sound Card Microcontroller/PC Communication project, where I used an embarrassingly simple hardware to accomplish the simple task of exchanging a few bytes of data between a PC and microcontroller (in the face of many people who adamantly prefer more complicated “traditional standard” methods). The car in front of me drove with his emergency flashers on, and at times all I could see were his lights. At that moment the crazy idea popped in my head – I wonder if I could use a PC monitor and phototransistors to send data to a microchip? I can’t think of any immediate uses for this capability, but perhaps if I make a working prototype I’ll stumble upon some. Either way, it sounds like a fun project!
DSCN1652

The circuit is as simple as it gets. PHOTOTRANSISTOR MICROCONTROLLER CIRCUITA phototransistor is exactly what it says, a photo (light-triggered) transistor (uses small current to trigger a large current). It’s a photodiode with a small transistor circuit built in. Make sure you give it right polarity when you plug it in! For some reason (likely known to electrical engineers, not dental students) the larger metal piece in the plastic part, which I normally associate as negative for LEDs, should be plugged in the +5V for my photodiode. Again, make sure you hook yours up right. I purchased mine from eBay quite cheaply, but I’ll bet you can find some in RadioShack. Note that the value of the 22k resistor is important, and that your needed value may differ from mine. The resistor relates to sensitivity, the larger the value the more sensitive the device is to light. If it’s too sensitive, it will sense light even when aimed at a black portion of the screen.
hardcode

Initial tests were done using the pins as digital inputs. This was difficult to achieve because, even as transistorized photo-diodes, it took a large difference in light to go from 5V to 0V (even past the 2.5V threshold). After a few minutes of frustration, I decided to use ADC to measure the light intensity. I use only the most significant 8 bits (ADCH). I found that in ambient light the readings are 255, and that white monitor light is around 200. Therefore my threshold is 250 (4.88V?) and I use this for logic decisions. Here’s my setup showing the ADC value of each phototransistor translated into a 1 and 0 for clock (C) and data (D). Both are aimed toward the lamp, so both show a logical 1:
DSCN1651

My first test involved reading the data from the image above. The clock is on the bottom line, data is on the top. Every time the clock transitions from black to white, the value of the data at that point is read (white=1, black=0) and the number is placed on a screen. Here’s what it looks like in action:

Hopefully soon we can get a JavaScript interface going! Rather than swiping I’d like to just point this at the screen and let JS flash some squares for my device to read. This will allow virtually unlimited amounts of data to be transferred, albeit slowly, to the micro-controller. Here’s a preliminary sketch of how to send strings.
string

Remember now we’re using a time domain, not a 2d barcode. I really stink at writing JavaScript, I’m going to have to pull in some help on this one!





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)
DSCN1630

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!
DSCN1634

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!
DSCN1637

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;
	}
}




 

[warning]

UPDATE: Check out what happened when I revisited this project and made it wireless two years later!:

http://www.swharden.com/blog/2013-05-19-wireless-microcontroller-pc-interface-for-3-21/

[/warning]

 

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!

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

This is the finished product ready to send data to a PC:DSCN1532

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:
DSCN1466 DSCN1470

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

~ 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.

### VIDEO ###

"""
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!





My current secret project involves cramming a bunch of features into a single microcontroller. The chip I chose to use is an ATMega48. The ATMega 48 is $1.40 each in small quantities and comes crammed packed with features. The chip will be quite busy performing many functions, but its main loop will be executed at least every 50ms (required for USB, did I mention I’m bit-banging USB?!). I desire to have a bit of RTC (real time clock) functionality in that I need to precisely measure seconds, although I don’t need to actually know the time or date. I desire to execute a function once per second, consuming a minimum of resources. The solution was quite simple, but I’m choosing to document it because it’s somewhat convoluted in its explanation elsewhere on the net.

In summary, the way I accomplished this is using the built-in 16-bit timer (most AVRs have such a timer, including the ATTiny series). If I’m clocking the microcontroller at a known rate (determined by my selection of crystal, 12 MHz in my case), I can set the chip to continuously increment a register (timer1) and execute a function every time it overflows. Timer1 overflows at 2^16 (65,536). I enabled a prescaler value of 256 so that it takes 256 clock pulses to increment the timer. 12MHz/256 = 46,875 Timer1 increments each second. Since Timer1 overflows at 65,536, if I initiate Timer1 at 18,661 (65,536-46,875), it will take 1 second exactly to overflow. Upon overflowing, I do something (maybe flip a LED on or off), and reset the Timer1 back to its starting value 18,661. Done! Without using an external RTC module or even an external crystal or asynchronous timer, we managed to execute a function every second on the second with minimal overhead, allowing the chip to do everything it wants in the rest of the time!

The following example is a little more specific, executing a function exactly 15 times a second, and executing another function (to flash an LED) exactly every 1 second. It should be self explanatory:

// This function is called every second on the second
volatile int count; // this should be global
ISR(TIMER1_OVF_vect){
	TCNT1=62411;//Initialize our varriable (set for 1/15th second)
	count++; //increment 1/15th second counter
	if(count==15){
		statusTOGGLE(); // do your event (flash a LED in my case)
		count=0;//reset global variable
		}
	}
// This is for ATMega48, consult datasheet for variations for different chips
// place this just inside main(), before your primary loop
TCCR1B|=(1<<CS12);// prescaler 256
TIMSK1|=(1<<TOIE1); //Enable Overflow Interrupt Enable
TCNT1=62411;//Initialize our varriable (set for 1/15th second)
count=0; //Initialize a global variable
sei(); // enable interrupts

I’m having a lot of fun spending time going through the datasheet of this chip. It has a lot of features, and some I didn’t really dig deeply into. Without giving away too much of my project, I’ll show some photos I’m excited to share. My project interfaces the PC through USB directly attached to 2 pins using no intermediate chips (wow!). The photos demonstrate various steps in the temperature measurement and calibration tests…
DSCN1367
DSCN1372





I did this purely for the fun of it, and am aware there are many ways to accomplish the same thing. I was playing Counter Strike Source (you should buy it and play with me, name “swharden”) and my fingers are really cold from the winter weather, and wondered if I could have a button help with the rapid firing of pistols. I mentioned it on the microphone, and one of the players (“{Ẋpli¢it} shadow”) said I should go for it. Because it was a fun little project, I documented it so I could share it. Check out the cool photos and video!

There’s a summary of the project in video form. Some details of the project are below…rapidfire_mouse_mod (1)

Here you can see the original circuit board in the mouse. The microchip on the bottom right of the image seems to do the data processing, so I investigated it a bit and found the pin that the left-click button goes to.rapidfire_mouse_mod (2)

Here’s the underside. It helped me identify good locations to grab +5V and GND solder points.rapidfire_mouse_mod (3)

This is the microcontroller I decided to use for the project. It’s an ATTiny25, $1.33 USD (10+ quantity from Mouser), and has a built in 8MHz oscillator (which can run at 1MHz thanks to the DIV/8 clock prescaler.rapidfire_mouse_mod (4)

I slap the chip in the homebrew development board (a glorified AVR-ISP mkII) and it’s ready for programming. Code and schematics are at the bottom.rapidfire_mouse_mod (5)

After programming, I glued the microchip upside-down in the mouse case and soldered wires directly to the pins. I used small (about 28AWG) magnet wire because it’s a lot easier than stripping wires. Just heat the tip with a soldering iron, the coating melts away, and you can stick it wherever you need to with a dab of solder. Not too many people use this method, but I recommend you try it at least once! It can be very useful at times, and is about as cheap as you can get. (eBay has good prices)rapidfire_mouse_mod (6)

BIG PROBLEM! It didn’t work *AT ALL*. Why? Didn’t know… I checked the o-scope and saw everything seemed to be working fine. It turns out that 50 clicks per second was too fast to register, and when I reduced the speed to 25 clicks per second it worked fine. Unfortunately I had to add extra wires to allow myself to program the chip while it was in the mouse – a major pain that complicated the project more than I wished!

rapidfire_mouse_mod (7)

Here’s a good view of the transistor. Simply put, when the microcontroller sends power to the “base” pin of the 2n2222 transistor, the “collector” is drained through the “emitter”, and the transistor acts like a switch. It’s shorting the pin, just like would happen if you physically pressed the left click mouse button. When the mouse microchip is positive (+5V), it’s “no click”, but when it goes to ground (shorted by the click button), a click is detected. I biased the “base” pin toward ground by connecting it to GND through a high value resistor. This makes sure it doesn’t accidentally fire when it’s not supposed to.

rapidfire_mouse_mod (8)

Here you can clearly see the programmer pins I added. This lets me quickly access the chip and reprogram it if I decide to add/modify functionality.

rapidfire_mouse_mod (9)

When it’s all said and done, it’s surprisingly slick and functional. I’m using it right now to write my blog, and the button isn’t really in the way. I think it’s one of those el-cheapo buttons you get in a pack of 10 from RadioShack, but I would highly recommend eBay as RadioShack is ridiculously overpriced on components.

rapidfire_mouse_mod (10)

There’s the schematic. Grabbing 5v and GND from a usb mouse is trivial. Heck, most of the circuity/code is trivial! Now that I think about it, this represents are really great starter project for anyone interested in microcontrollers.

ATtiny25-45-85V

Use this diagram of the pin functions for reference.

Remember there’s more than one way to skin a cat! For example, if you don’t want to program a microcontroller, a 555 timer is a simple method and there are tutorials out there demonstrating how to do this. I chose a microcontroller because I can precisely control the rate of firing and the duration. If you decide to do something similar, send me photos and I’d be happy to share them on the site! I love doing tangible projects, however silly they are.

And finally, the code!

#define F_CPU 1000000UL	// frequency (20MHz)
#include <avr/io.h>
#include <util/delay.h>



void on(){
	PORTB |= 1 << PB3; //led
	PORTB |= 1 << PB2; //heater
	}
void off(){
	PORTB &= ~( 1 << PB3 );//led
	PORTB &= ~( 1 << PB2 );//heater
	}


void main() {
	DDRB |= (1<<PB3)|(1<<PB2);
	int ticks;

	for (;;) { //FOREVER
		while ((PINB & _BV(PB4))==0) {} // NOT PRESSED, DO NOTHING
		for(ticks=0;ticks<125;ticks++) // CLICK FOR 5 seconds
			{
			on();_delay_ms(20);off();_delay_ms(20);
			} // CLICK TAKES 1/50'th second
	}
}




While working to perfect my temperature-controlled manned experimental propagation transmitter (MEPT), I developed the need to accurately measure temperature inside my Styrofoam enclosure (to assess drift) and compare it to external temperature (to assess insulation effects). I accomplished this utilizing the 8 ADC channels of the ATMega48 and used its in-chip USART capabilities to send this data to a PC for logging. I chose the ATMega48 over the ATTiny2313 (which has USART but no ADCs) and the ATTiny44a (which has ADCs but no USART). From when I see, no ATTiny series ATMEL AVR has both! Lucky for me, the ATMega48 is cheap at $2.84 USD. Here’s my basic circuit idea: IMG_4559

EDIT: the SCHEMATIC1 : PAGE1voltage reference diagram is wrong at the bottom involving the zener diode. Reference the picture to the right for the CORRECT way to use such a diode as a voltage reference. (stupid me!)

MULTIPLE SENSORS – Although in this demonstration post I only show a single sensor, it’s possible to easily have 8 sensors in use simultaneously since the ATMega48 has 8 ADC pins, and even more (infinitely) if you want to design a clever way to switch between them.

LM335 Temperature Sensor – selected because it’s pretty cheap (< $1) and quantitative. In other words, every 10mV drop in voltage corresponds to a change of 1ºC. If I wanted to be even cheaper, I would use thermistors (<$0.10) which are more qualitative, but can be calibrated I guess. Notes on power stability - The output of the sensor is measured with the ADC (analog to digital converter) of the microcontroller. The ADC has a 10-bit resolution, so readings are from 0 to 2^10 (1024). AREF and AVCC can be selected as a voltage reference to set what the maximum value (1024) should be. If the ADC value is 1V (for example) and AREF is 1V, the reading will be 1024. If AREF becomes 5V, the reading will be 1024/5. Make sense? If AREF is fluctuating like crazy, the same ADC voltage will be read as differing vales which is not what we want, therefore care should be taken to ensure AREF is ripple-free and constant. Although I did it by adding a few capacitors to the lines of the power supply (not very precise), a better way would be to use a zener diode (perhaps 4.1V?) as a voltage reference.IMG_4575

Here is my circuit. I’m clocking the chip at 9.21MHz which works well for 19200 baud for serial communication. Refer to my other MAX232 posts for a more detailed explanation of how I chose this value. The temperature sensor (blurry) is toward the camera, and the max232 is near the back. Is that an eyelash on the right? Gross!logger

The data is read by a Python script which watches the serial port for data and averages 10 ADC values together to produce a value with one more significant digit. This was my way of overcoming continuously-fluctuating values.
IMG_4564

Here you can see me testing the device by placing an ice cube on the temperature sensor. I had to be careful to try to avoid getting water in the electrical connections. I noticed that when I pressed the ice against the sensor firmly, it cooled at a rate different than if I simply left the ice near it.

NOTICE THE PROGRAMMER in the background (slightly blurry). The orange wires connect the AVR programmer to my circuit, and after the final code is completed and loaded onto the microcontroller these orange wires will be cut away.lm335 microcontroller graph annotated

Here is some actual data from the device. The LM335 readout is in Kelvin, such that 3.00V implies 300K = 80ºF = 27ºC (room temperature). The data is smooth until I touch it with the soldering iron (spike), then it gets cool again and I touch it with a cold piece of metal (wimpy dip), then later I put an ice cube on it (bigger dip). Pretty good huh? Remember, 0.01V change = 1ºC change. The bottom of the dip is about 2.8V = 280K = 44ºF = 7ºC. If I left the cube on longer, I imagine it would reach 0ºC (273K, or 2.73V).

For everyone’s reference, here’s the pinout diagram of the ATMega48:atmega48pinout

Finally, the code to record live data:

import socket
import sys
import serial

ser = serial.Serial('COM1', 19200, timeout=1)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

chunk=""
i=0
data = ser.readline()
while True:
    i+=1
    data = ser.readline()
    data=data.replace("n","")
    data=data.replace("r","")
    data="["+data[:-1]+"]"
    data=eval(data)
    val=sum(data)/float(len(data))
    print i,data,val
    chunk=chunk+"%.01f,"%val
    if i==100:
        print "nSAVING"
        i=0
        f=open("data.txt","a")
        f.write(chunk)
        f.close()
        chunk=""

and the code to PLOT the data file:

import matplotlib.pyplot as plt
import numpy

def smoothTriangle(data,degree,dropVals=False):
        """performs moving triangle smoothing with a variable degree."""
        """note that if dropVals is False, output length will be identical
        to input length, but with copies of data at the flanking regions"""
        triangle=numpy.array(range(degree)+[degree]+range(degree)[::-1])+1
        smoothed=[]
        for i in range(degree,len(data)-degree*2):
                point=data[i:i+len(triangle)]*triangle
                smoothed.append(sum(point)/sum(triangle))
        if dropVals: return smoothed
        smoothed=[smoothed[0]]*(degree+degree/2)+smoothed
        while len(smoothed)<len(data):smoothed.append(smoothed[-1])
        return smooth


print "loading..."
f=open("data.txt")
raw="["+f.read()+"]"
f.close()
data=eval(raw)

print "converting..."
data=numpy.array(data)
data=data/1024.0*5 #10-bit resolution, 5V max

print "graphing"
plt.plot(data)

plt.grid(alpha=.5)
plt.title("ATMega48 LM335 Temperature Sensor")
plt.ylabel("Voltage (V)")
plt.xlabel("Time (5/sec)")
plt.show()

Also, the AVR-GCC code loaded on the ATMega48:





#define F_CPU 9210000UL

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

void init_usart(unsigned long);

unsigned int readADC(char times){
	unsigned long avg=0;
	for (char i=0; i<times; i++){
        ADCSRA |= (1<<ADSC); // reset value
        while (ADCSRA & ( 1<<ADSC)) {}; // wait for measurement
		avg=avg+ADC;
	}
	avg=avg/times;
	return avg;
}

int main (void){

    ADMUX = 0b0100101; // AVCC ref on ADC5
    ADCSRA = 0b10000111; //ADC Enable, Manual Trigger, Prescaler 128
	ADCSRB = 0;

	DDRD=255;

	init_usart(19200);
	for(;;){
		for(char j=0;j<10;j++){
			sendNum(readADC(10)>>6); // shift to offset 10bit 16bit
			send(44); // COMMA
			PORTD=255;_delay_ms(10);
			PORTD=0;_delay_ms(10);
			}
		send(10);send(13); // LINE BREAK
		}
	}


void sendNum(unsigned int num){
		char theIntAsString[7];
		int i;
		sprintf( theIntAsString, "%u", num );
		for (i=0; i < strlen(theIntAsString); i++)
		{send(theIntAsString[i]);}
}

void send (unsigned char c){
		while((UCSR0A & (1<<UDRE0)) == 0) {}
		UDR0 = c;
}

void init_usart (unsigned long baud)
{
	/////////////////////////
	//		Baud Generation
	unsigned int UBRR_2x_off;
	unsigned int UBRR_2x_on;
	unsigned long closest_match_2x_off;
	unsigned long closest_match_2x_on;
	unsigned char off_2x_error;
	unsigned char on_2x_error;

	UBRR_2x_off = F_CPU/(16*baud) - 1;
	UBRR_2x_on = F_CPU/(8*baud) - 1;

	closest_match_2x_off = F_CPU/(16*(UBRR_2x_off + 1));
	closest_match_2x_on = F_CPU/(8*(UBRR_2x_on + 1));

	off_2x_error = 255*(closest_match_2x_off/baud - 1);
	if (off_2x_error <0) {off_2x_error *= (-1);}
	on_2x_error = 255*(closest_match_2x_on/baud -1);
	if (on_2x_error <0) {on_2x_error *= (-1);}

	if(baud > F_CPU / 16)
	{
		UBRR0L = 0xff & UBRR_2x_on;
		UBRR0H = 0xff & (UBRR_2x_on>>8);
		UCSR0A |= (1<<U2X0);
	} else {

		if (off_2x_error > on_2x_error)
		{
			UBRR0L = 0xff & UBRR_2x_on;
			UBRR0H = 0xff & (UBRR_2x_on>>8);
			UCSR0A |= (1<<U2X0);
		} else {
			UBRR0L = 0xff & UBRR_2x_off;
			UBRR0H = 0xff & (UBRR_2x_off>>8);
			UCSR0A &= ~(1<<U2X0);
		}
	}
	/////////////////////////
	//	Configuration Registers
	UCSR0B = (0<<RXCIE0) |//We don't want this interrupt
	(0<<TXCIE0) |//We don't want this interrupt
	(0<<UDRIE0) |//We don't want this interrupt
	(1<<RXEN0) |//Enable RX, we wont use it here but it can't hurt
	(1<<TXEN0) |//Enable TX, for Talkin'
	(0<<UCSZ02);//We want 8 data bits so set this low

	UCSR0A |= (0<<U2X0) |//already set up, so don't mess with it
	(0<<MPCM0) ;//We wont need this

	UCSR0C = (0<<UMSEL01) | (0<<UMSEL00) |//We want UART mode
	(0<<UPM01) | (0<<UPM00) |//We want no parity bit
	(0<<USBS0) |//We want only one stop bit
	(1<<UCSZ01) | (1<<UCSZ00) |//We want 8 data bits
	(0<<UCPOL0) ;//This doesn't effect UART mode
}

UPDATE: A day later I added multiple sensors to the device. I calibrated one of them by putting it in a plastic bag and letting it set in ice water, then I calibrated the rest to that one. You can see as my room temperature slowly falls for the night, the open air sensor (red) drops faster than the insulated one in a Styrofoam box. Also, I did a touch of math to convert voltage to kelvin to Fahrenheit. You can also see spikes where it quickly approached 90+ degrees from the heat of my fingers as I handled the sensor. Cool!3traces

UPDATE: a day and a half later, here’s what the fluctuations look like. Notice the cooling of night, the heating of day, and now (near the end of the graph) the scattered rain causes more rapid fluctuations. Also, although one sensor is in an insulated styrofoam box, it still fluctuates considerably. This measurement system is prepped and ready to go for crystal oven tests!insulated3





UPDATE: I found a method of PC/microcontroller communication which I feel is simpler, easier, and definitely cheaper than this! It’s not good for everything, but worth looking at. It’s a way to communicate with a PC using your sound card and zero components!

This weekend I had a need, and I met it with parts I had on hand. Simply put, I wanted to assess whether or not my temperature-controlled crystal heater is doing its job in keeping temperature rock-stable. I wanted to measure temperature by measuring the ADC (analog-to-digital) value at the middle of a voltage divider with a resistor and a thermistor. Using a computer to plot this data, I can see if temperature fluctuates as my apartment AC turns on/off, or if it’s perfectly stable (my goal). The problem is that my only MCU (micro-controller unit) with USART (universal asynchronous receiver/transmitter) built-in is an ATTiny2313, which has no ADC capabilities. I had a lot of ATTiny44A chips on hand, so I had to figure out a way to get the data from my an ATTiny44A to an ATTiny2313 then to a MAX232 chip (voltage driver) so it can be sent to a PC’s serial port.

IMG_3919This is my bare-bones solution to easily sending data from ANY microcontroller to a PC’s serial port using 3 pins to send data to an ATTiny2313 which is interpreted, converted to decimal, then sent to my PC’s serial port. I will keep this little board and use it often to peek at variables inside my microcontroller projects in real time, with minimal coding!

Let’s take a look! schematic_fixed2

Above is the bare-bones schematic required to send data from an ATTiny2313 to a PC via a serial port. This schematic is improved and documented better on this page than on my previous post Simple Case AVR/PC Serial Communication via MAX232. Note that I’m designing this to be functional, perhaps not well enough to be used in mission-critical systems. Although some schematics suggest extra capacitors, I found that the only one required is between pins 4 and 5 of the MAX232. The role of the MAX232 chip is to act as a voltage pump and relay the incoming signal at increased voltage which the PC’s serial port can read. It doesn’t actually change the data.

UPDATE: in a later project working with an ATMega48 I found that a capacitor was needed between pin 6 and ground - don't know why! If it's not working for you (you're getting garbage) start adding capacitors as shown in this MAX232 circuit

Power supply: Since the thing runs on 5V, we’re golden! Just grab a USB cable, hook up the black (ground) and red (+5V) wires, and you’re good to go! If you want you can add a few capacitors of different values in parallel to the power supply to stabilize fluctuations in voltage, but I’ve been doing just fine without this extra precaution.

Display: The two LEDs are totally optional, but they let me see what’s going on. One of them flashes when the device is waiting for data (to let me know it’s on), and the other one turns on every time a [CLOCK] signal is detected (which is any time data is being sent)

Notes on frequency and crystals. The UBRRL value in the code must be set depending on your micro-controller speed and desired baud rate. I set-up an Excel spreadsheet and did some math determining UBRRL for a combination of different frequencies/rates. The UBRRL values closest to whole numbers are those which should be used to minimize errors. External crystals are often used to increase and stabalize the clock speed of micro-controllers, but I was able to send serial data without a crystal. I set the fuse for “internal 8mhz” clocking, and enabled the “div8” fuse so it actually ran at 1mhz. With these settings at 4800 baud, UBRR [according to the equation UBRR=(freq/(16*baud))-1] is 12.02 (pretty close to 12), so I set UBRRL=12 in the code and it sent data to a PC beautifully without a crystal. However, I had the desire to run the MCU faster to watch for incoming signals. I therefore used a 9.21MHz crystal (I had to set the fuses to enable the external crystal), which can send serial data to a PC reliably at 19200 baud.

Sending data to the ATTiny2313 to be relayed to the PC: Not every MCU has SPI, USI, I2C, TWI, USART, or other “standard” communication methods. If I want to have a Texas Instruments or PIC or PICaxe chip send data to a PC, I have to decipher the datasheet and spend half a day to figure out how (yuk!). Therefore, I developed an ULTRA-SIMPLE protocol of data transfer which can be used by ANY microcontroller. Here’s an example of a sensor microcontroller. Although it’s not shown, there’s a thermistor (or some analog voltage being measured) somewhere. It reads the sensor, then sends its data over the 3 wires [CLOCK], [A], and [B].

Pulling-down the clock: Note that the 100k resistor shown pulling the [CLOCK] line to ground is critical. It doesn’t have to be 100k, it can be virtually any value, it just makes sure that if current is not being sent on the clock line, it quickly goes to 0V. Without this resistor, the clock line might flicker on and off even though no data is being sent.

serial_example

Sending data this way is embarrassingly easy! The [clock] line is almost always low. When [clock] goes high, data is read. When data is read, IMG_3907the ATTiny2313 determines the state of [A] and [B]. If A=0 and B=0, a ZERO is sent. If A=1 and B=0, a ONE is sent. If A=0 and B=1, a SPACE is sent (between values). If A=1 and B=1, a LINE BREAK is sent. Values are sent in binary, and when a space or line break is detected, the binary value is converted to decimal and sent to the PC’s serial port. It’s not dependent on speed, so send data as fast (within reason) or slowly as you want from any microcontroller and it will end-up on your computer screen! It’s that easy!

FLAME ALERT: A lot of people will be mad at me for suggesting this method. There are fancier, faster, and more stable ways to data transfer between micro-controllers, but this works well at moderate speeds (maybe 10 measurements a second?) and I can implement this on any microcontroller in seconds, without puzzling over the datasheet.

BREADBOARDED PROTOTYPE

Remember that I’m powering this entirely from USB power. The layout is simple: ATTiny44A measuring ADC of a thermistor on the left (see the little red thing?) sending data with 3 wires (top 3) to an ATTiny2313 intermediate chip (center), which converts this binary data to decimal and sends it to a MAX232 chip (right) where it gets converted to levels suitable for transmission to a serial port.IMG_3905

IMG_3891

IMG_3899

IMG_3904

IMG_3907

FINAL DEVICE

IMG_3922

IMG_3919

CODE (AVR-GCC)

This is what runs on the ATTiny2313 intermediate chip:

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

#define B PB2
#define A PB1
#define clk PB0
unsigned int times=0;

// THIS RUNS ON THE ATTINY2313
// FUSES SET FOR INTERNAL 8MHZ, DIV/8=TRUE
// LISTENS ON PINS 12 (CLOCK), 13 (DATA), AND 14 (TRANSMIT)
// OUTPUTS TO A MAX232 THEN TO SERIAL PORT
// PC TERMINAL SET TO LISTEN AT 4800 BAUD

int main (void) {
	UBRRL = 29; // value determined for 9.21MHz crystal at 19200 baud
	UCSRB = (1 << RXEN) | (1 << TXEN); // fire-up USART
	UCSRC = (1 << UCSZ1) | (1 << UCSZ0); // fire-up USART
    DDRB=0; // set all of port b to input
	DDRD=255; // set all of port d to output
	char last=255;
	int var=0;
	char pos=0;
	char i=0;
	for(;;){
		while bit_is_clear(PINB,clk){blink();}
		PORTD|=(1<<PD5);PORTD&=~(1<<PD4);
		if (bit_is_set(PINB,A) && bit_is_clear(PINB,B)) {var=(var<<1)+1;}//ONE
		if (bit_is_clear(PINB,A) && bit_is_clear(PINB,B)) {var=(var<<1);}//ZERO
		if (bit_is_clear(PINB,A) && bit_is_set(PINB,B)) {show(var);var=0;send(32);}//SPACE
		if (bit_is_set(PINB,A) && bit_is_set(PINB,B)) {show(var);var=0;send(10);send(13);}//BREAK
		while bit_is_set(PINB,clk){blink();}
		PORTD&=~(1<<PD5);PORTD|=(1<<PD4);
		}
	}
void blink(){
	// just hanging out
	times++;
	if (times==10000){times==0;PORTD|=(1<<PD3);}
	if (times==20000){times==0;PORTD&=~(1<<PD3);times=0;}
	}

unsigned int rev(unsigned int b) {
  unsigned char result = 0;
  while (b) {
    result <<= 1;
    result  |= b % 2;
    b>>= 1;
    }
  return result;
  }

void show(unsigned int val){
	/* SHOW INDIVIDUAL 1s and 0s?
	for (char i=0;i<16;i++){
		if (val&(1<<i)){send(49);}
		else {send(48);}
		}
	send(61);
	*/
	val=rev(val);
	if (val==0) {send(48);}
	else {
		char started=0;
		int div=10000;
		for (char i=0;i<5;i++){
			if (val>div){started=1;}
			if (started){
				send(val/div+48);
				val=val-(val/div)*div;
				}
			div=div/10;
			}
		}
	return;
	}

void send(unsigned char data){
	  while (!(UCSRA & (1 << UDRE))); // wait for buffer to be empty
	  UDR = data; // send that sucker
	}

This is what runs on my ATTiny44a sensor chip. This is what you can replace with ANYTHING, as long as it twiddles the [clock], [a], and [b] pins similarly.

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

#define dtaprt PORTA
#define clk PA1
#define A PA2
#define B PA0
#define delayms 100 // increase to slow it down

void wait(){_delay_ms(delayms);}
void clockNow(){dtaprt|=(1<<clk);wait();dtaprt&=~(1<<clk);wait();}
void sendSpace(){dtaprt=(1<<B);clockNow();}
void sendLine(){dtaprt=(1<<B)|(1<<A);clockNow();}
void sendOne(){dtaprt=(1<<A);clockNow();}
void sendZero(){dtaprt=0;clockNow();}

// TAKE A READING FROM ADC7 AND SEND IT TO SERIAL CHIP

int main (void) {
	DDRA|=(1<<clk);
	DDRA|=(1<<A);
	DDRA|=(1<<B);
	ADMUX = (1<<REFS1)|(1<<MUX2)|(1<<MUX1)|(1<<MUX0); // ADC on ADC7 to 1.1v ref
	ADCSRA = (1<<ADEN)|(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2); // enable, prescale
	for(;;){
		int data=ReadADC();
		sendData(data);
		sendSpace();
		sendData(data);
		sendLine();
		_delay_ms(1000);
	}
}

void sendData(int data){
	char datalen=16;
	for (char pos=0;pos<datalen;pos++){
		if ((data>>pos)&1){sendOne();}
		else {sendZero();}
		}
	}

int ReadADC()
{
	ADCSRA |= (1<<ADSC); // reset value
	while (ADCSRA & ( 1<<ADSC)); // wait for measurement
	return ADC;
}

UPDATE!!! I found a simpler way to convert binary numbers into strings ready to be sent in ASCII via USART serial port:

void sendNum(unsigned int num){
		char theIntAsString[7];
		int i;
		sprintf( theIntAsString, "%u", num );
		for (i=0; i < strlen(theIntAsString); i++)
		{send(theIntAsString[i]);}
}