Tiny AVR

A TWI project using the AVR tiny85

In progress.

June 2010,


I have been using AVRs since the 20 pin AT-90S1200 was released last century. I used a number of other devices as they became available including the AT-90S2313 (not called a tiny then) and mega32. From around 2003 till 2009 or so almost all my work was on the mega128 and a little on the mega8. When arduinos hit the market mega168 and mega328 joined the family.

In recent times my professional work has been mega128 and my personal projects mainly mega328. These chips serve me well but often I only need a few I/O pins and but using an 8 pin (or less) device I could make my circuits smaller. I don't really have to have them that small but it is fun to try new things.

To read a single analog value over a two wire interface TWI a 6-pin tiny10 would be enough. I have couple on back order but don't have the means to program them as their "in system programming" (ISP) interface is different to the other AVR chips.

Image a tiny13 being programmed via eze-hooks

I've had a tiny13 laying around for a while but using a tiny85 gives me the luxury of 8K of program space so I can some of my exist GCC code in it. This project is a smaller version of the pressure module seen on my TWI page. The existing code for this is written in WINAVR/GCC.


The ATtiny85 is high end of the ATtinyX5 series (ATtiny25/45/85). Grab the current data sheet from atmel.com if you are interested - I won't be covering the full specs for it.
The main points are 8-pin PDIP, 8-pin SOIC or 20-pad QFN/MLF, 8K of program flash, 1/2 a K of RAM and EEPROM. It also has the usual goodies like, ADC, timers, interrupts and ISP. It does not have a separate boot block or full hardware TWI support. Having only six I/O lines the pins are multifunctional.
Image The tiny85 soic attached to a DIN socket, voltage regulator and LED. There is little reason to use the surface mounted version (SOIC) instead of the DIP. It shrinks the project a small amount but makes it much harder to construct. I had the SOIC version "on hand" and didn't want to wait for the DIP version so I took the challenge.

Programming the flash.

My ezi-hooks were not small enough to grab all the soic pins needed for programming so initially I soldered the soic to a breakout board to check it out. I found my USB pocket programmer could program it but I'd get verify errors. I'm having trouble with some mega328s as well so it may be faulty. It USB programmer is handy because it can supply power to the micro. After powering the tiny85 with 5V from a USB adapter I was able to reliably program it with my kanda programmer.
I then un-soldered it and made the little circuit you see here. This could be programmed via hooks and as a bonus I could even leave it connected to my TWI while programming.
On the megas I use a bootloader so I can upload new applications via the TWI. I don't use a bootloader on the tiny85. It might be possible but it would be a squeeze and a fair bit of work to write a new loader that can work without dedicated TWI support.

The circuit.


Pin 1 is the reset pin - making this an I/O pin stops the ISP from working so I've left it as is.
Pin 2 is setup as an analog input but remains unused in the project.
pin 3 is also used as analog input and is used to measure the output of a pressure transducer. To bring the voltage into the 2.56 volt range of the ADC two 33K resisters are used as a divider. There is also a 100nF cap soldered across the transducer output and ground.
Pin 4 is ground.
Pin 5 is the TWI SDA (data) line.
Pin 6 driver the LED.
Pin 7 is the TWI SCK (clock)
Pin 8 is 5V power.
My TWI network uses a nominal 12 volt supply with regulators on each module. A 78L05 with caps provides the power for the micro.
Normally I have two unused pins on my 8 pin DIN plug but here I used them so I could bring out all the signals need for ISP. In theory I can still reprogram the micro after the circuit is blobbed in polymorph.
Image The SOIC micro is dwarfed by the freescale MPX4250AP pressure sensor.


The majority of the software was already written. The main thing needed was TWI support. The tiny85 does not have a TWI peripheral but it does have a Universal Serial Interface (USI). The USI can work in a TWI mode where it can detect the "start" condition and generate an interrupt. It can also detect a "stop" but doesn't have an interrupt for this. The USI shift register can assemble the data from the synchronous serial stream while a USI counter can interrupt after 1 to 16 clock-edges are counted. The shifter can also transmit data out of the SDA pin. The building blocks are there but it still need software to tie it together and do stuff like generate "acks".
I choose to also use a "pin-change" interrupt for doing TWI. One could get by without it but I think the extra interrupt make things neater.

My TWI runs with a 20KHZ clock. I thought I'd get away with writing the code totally in 'C' but decided against it. I found my 'C' had a 6uS interrupt response time - almost 1/4 of a clock half cycle has passed before my interrupt code starts to execute. I think 'C' may have worked but the timing is marginal at 20KBS and 100KBS isn't going to happen.
I've never mixed 'C' and assembler before and this took me longer than it should have to work out.

Not all the interrupt processing needs to be super quick. The "start" and "timer overflow" interrupt were done in 'C', only the "pin-change" interrupt was done in assembler.

Make file.

If I'd read the comment in the make file I'd have saved myself a lot of time. I solved them the hard way then saw the comments afterward. If you added the assembler source to the "SRC" line instead of the "ASCR" weird things happen. Not only does it fail to assemble but also "make clean" destroys your file.
Here is part of the "make" file.
# List C source files here. (C dependencies are automatically generated.)
SRC = $(TARGET).c custcmd.c ../include/timer.c ../include/comms.c ../include/lowlevel.c ../include/crc.c ../include/command.c 

# List Assembler source files here.
#     Make them always end in a capital .S.  Files ending in a lowercase .s
#     will not be considered source files but generated files (assembler
#     output from the compiler), and will be deleted upon "make clean"!
#     Even though the DOS/Win* filesystem matches both .s and .S the same,
#     it will preserve the spelling of the filenames, and gcc itself does
#     care about how the name is spelled on its command-line.
ASRC = ../include/tx5TWI.S 

You may notice I have a lot of files in my build and I won't be covering them here. The thing to note is "tx5TWI.S" - that is my assembler file and note the .S is uppercase.

'C' interrupt code.

The start interrupt does very little.

It clears the start and counter overflow flags. The former should be obvious, if this interrupt is execute the start flag is set and must be cleared for the next time. Normally the counter interrupt flag would not be set but we may as well clear it just in case it is. We are also clearing the counter to zero.
Both interrupt are enabled and we expect the next interrupt will be the counter overflow after eight clock pulses. The counter counts both edges of the clock so 16 counts equals 8 clocks. At this point in the program we don't know if the TWI is for our micro or whether it is a read or write. If this turn out the be a read by the master (we write) we will need the length of the previous packet. This is why inptr is copied to DataIndex before being set to -1. The index inptr is set to -1 to indicate the next byte received is an address byte. That's a lot of words to describe four lines of code, I think I'll need to gloss over the rest or this page will take the rest of the year to write.

	outp((BV(USIOIF)+BV(USISIF)),USISR); // reset start and counter overflow flags.
	outp((BV(USIOIE)+BV(USISIE)+BV(USIWM1)+BV(USICS1)),USICR); // Set up control reg with interrupts,mode and clock source
	DataIndex=inptr; // save inptr in case this is a reply packet and we need the process the command first.

The counter overflow interrupt does a lot more.

The counter overflow interrupt is set up to occur when a byte is ready to be transferred from the USC buffer register (USIBR) or when data in the data register USIDR has be sent.
There are five possible cases the interrupt needs to deal with.

Part 1.

The first three are in the section I've labeled "part 1". This is when the index is "-1" which mean we expect the data which has been received from the serial bus to be a TWI address byte. The address byte is transmitted by the master and received by the slave regardless of whether the rest of the packet is a read or a write. This tiny85 is always a slave the code does not support the tiny85 being a master. The address byte contains a seven bit address shifted left with a direction bit in bit zero. If the direction bit is zero the rest of the packet is transmitted by the master and received by the slave.
If the master wanted to write data to a slave with an address of hex 01 the address byte would be hex 02.
The byte we expect to be in the address field when out micro is addressed is pre-computed to speed up the interrupt code.
The address field value for a data-write is stored in UIDMASK and a the data-read value in UIDRDMASK. If the slave a address was fixed these could be constants but I use EEPROM to set my slave address.
The three possibilities are.
  1. The address byte equals UIDMASK and the packet is a master-write/slave-read.
  2. The address byte equals UIDRDMASK and the packet is a master-read/slave-write.
  3. The address matches neither and the packet is not intended for this node.
If the address byte matches our ID the code for read and write setup are fairly similar. In both cases we set up the pin change interrupt, set the interrupt, buffer index and state. In the case of a master-write we also begin a "ack".
If the address did not match we simply turn off the interrupts except for the "start" interrupt.

Part 2.

This is where data is being moved.
The two possibilities are.
  1. There is data in the buffer register (USIBR) which needs to be moved to RAM.
  2. Data has been sent from the data register.
In both cases the pin-change interrupt is set up.
In the first case the data is moved from USIBR to the RAM buffer.
In the second case the data has been transmitted from the data register (USIDR) but we have to delay writing the new data into the register till after the "ack" field. Instead we write the value to a temporary variable which the pin change interrupt will write to USIDR. Using the temporary storage simplifies the pin change code which is written in assembler.
	outp((BV(USIOIF)),USISR); // clear overflow flag
	if (inptr==-1) {
// part 1
		if (USIBR==UIDMASK) // that's a write from the master - a read from this end.
			outp(BV(PCIF),GIFR);	// clear pin change flag
			outp(BV(PCIE),GIMSK);  // enable pin change interrupt
			TWI_SDA_LOW				// force SDA low to begin ack
			PCcnt=3;				// set pin change count to 3 (3 ints = two clock 1/2 cycles)
			inptr=0;				// set buffer point to start of buffer (recieve mode)
			TWIstate=TWI_ST_SL_RD;	// set state to slave read
		else if (USIBR==UIDRDMASK) // that's a read from the master - a write from this end.
			outp(BV(PCIF),GIFR);	// clear pin change flag
			outp(BV(PCIE),GIMSK);	// enable pin change interrupt
			PCcnt=3;				// set pin change count to 3 (3 ints = two clock 1/2 cycles)
			inptr=0;				// set buffer point to start of buffer (TX mode)
			TWIstate=TWI_ST_SL_WRST;// set state to slave write
		else // not for this address kill overflow interupt.
// part 2
	else 	// we are in the data part of the packet now - need to move data one way or another.
		outp(BV(PCIF),GIFR);	// clear pin change flag
		outp(BV(PCIE),GIMSK);	// enable pin change interrupt	
		if(TWIstate!=TWI_ST_SL_WRTR) // if we're not in "write tranfer" mode we are reading.
			PCcnt=3;			// set pin change count to 3 (3 ints = two clock 1/2 cycles)
			serialbuff[inptr++]=USIBR; // move byte from TWI shifter into RAM buffer.
		else // we're in the byte transfer phase of a write packet (read at the master end).
			PCcnt=2;		// set pin change count to 2 - we don't ack in write mode.
			bytebuffer=serialbuff[inptr++]; // move byte from into TWI shifter .

Also see Tiny tiny85 PCB
Note AVRdude fuse settings are DF E2 FF

Created by eddie. Last Modification: Tuesday 20 of July, 2010 12:46:02 AEST by eddie.

Main Index

Switch Theme


eddie, 11:29 AEST, Sat 22 of Jun, 2024: Offline wiki repair is mostly done. It is running on tiki-27-alpha
eddie, 18:14 AEST, Sun 12 of May, 2024: Phase one of wiki repair is to upgrade to V22 (the last version with image galleries). All 150 images in the wiki-up directory are broken and need manual wiki page repairs.
admin2, 19:45 AEST, Sun 28 of Apr, 2024: Tested tiki 26.2 on a raspberry pi. Tiki 26 does not support image galleries so I have to try again after migrating images to file galleries.
eddie, 13:04 AEST, Thu 10 of Aug, 2023: Offline tiki 26 upgrade went badly. Waiting for 26.1. Will limp on.
System Administrator, 18:45 AEST, Wed 26 of Jul, 2023: Recovered from lockout but unable to upgrade to V24

Last-Visited Pages

Online Users

8 online users