AVR
Atmel's RISC microcontrollers with flash memory in it for program codes. AVR at Wikipedia
You can buy e.g. Atmel STK200/STK500/STK600 development board, you do NOT need to build programming hardware, but here is my story.
Hardware
When I would like to built an USB/I2C adapter, I found i2c-usb-tiny. To built it I had to program Atmel ATTiny45 microcontroller, but I hadn't got any programming adapter device for it. I found that DAPA cable could work on parallel port, so I built it.
It looked like that it works, but unfortunately the i2c-usb-tiny hadn't worked for me and magical way the microcontroller looked bad, so I tried another hardware. I'd built PonyProg's STK200 in solderable breadboards:
This hardware works well with PonyProg and AVRDUDE too, but the i2c-usb-tiny adapter hadn't worked for me. My knowledge about AVR chips were near nothing at that time, so I stopped this project. (later I bought a ch341a usb/i2c adapter)
Then I read more about ATTiny45 and I learnt that programming fuse bits can cause the magical thing that the microcontroller looked wrong.
I found this project and then I can bring back the "wrong" microcontrollers to "live".
Reading about programming AVRs looked interesting and I liked that I can build "almost anything" with this controllers, not just blinking leds as lot of people thinks.
My newer computers hasn't got parallel port, just USB ports, - I searched the internet, and found and investigated Simplest USB AVR programmer, - and I built my usbtiny programmer with ISP port, so the previously built 8pin+20pin AVR adapter works with it.
This usbtiny hardware + AVR adapter is not compatible with PonyProg, but works well with AVRDUDE.
AVRDUDE - AVR Downloader/UploaDEr
AVRDUDE is a free utility to manipulate/read/write Atmel chips.
To test that you can access ATTiny45 chip with your hardware you can run for stk200:
avrdude -p t45 -c stk200
or for usbtiny:
avrdude -p t45 -c usbtiny
If all the connections are correct you will get similar result:
Programming
You can create program codes in Assembly or in C programming languages. If you choose C you can compile and flash to ATTiny45 with usbtiny with this code:
avr-gcc -Wall -g -Os -mmcu=attiny45 -o mycode.bin mycode.c
avr-objcopy -j .text -j .data -O ihex mycode.bin mycode.hex
avrdude -p t45 -c usbtiny -U flash:w:mycode.hex:i
And by popular request: blinking leds
My example C code to switch connected leds to ON or OFF and waits a little beetween changes. It will stop when only one led is ON which is connected to PB2 Pin.
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0x1f; // set PortB to output
PORTB = 0b00011111;
_delay_ms(100);
PORTB &= 0b11100011;
_delay_ms(100);
PORTB |= 0b00011100;
_delay_ms(100);
PORTB &= 0b11111000;
_delay_ms(100);
PORTB |= 0b00000001;
PORTB &= 0b11100001;
_delay_ms(100);
PORTB |= 0b00010000;
_delay_ms(100);
PORTB |= 0b11101110;
_delay_ms(100);
PORTB &= 0b11101110;
_delay_ms(100);
PORTB &= 0b11100100;
return 0;
}
I2C on ATTiny45
Here is a simplified, incomplete/"faulty" sample code, how I2C can work on an AVR MCU. If you install it on a circuit shown above (led blinking), you can see how communication works. If you change speed (hc) and connect an EEPROM like 24c02 it will really write a byte to it.
This is incomplete/"faulty" because some functions are not implemented e.g. reading and in other environment it can cause faults. So please read the full spec. of I2C and rewrite it. This can help:
Understanding the I2C Bus by Texas Instruments
//
// Test program for accessing 24c02 through i2c from ATTiny45 in C
//
// Copyright (c)2017-2020 Tóthpál István, istvan@tothpal.eu
//
// =========================================================================================
// ATtiny 24c02
// 25/45/85
// +--------+ +--------+
// RESET --+ o Vcc +------------ A0 --+ o Vcc +------------
// Red LED1 - PB3 --+ +-- SCK (PB2) A1 --+ +-- WP
// Green LED2 - PB4 --+ +-- NC (PB1) A2 --+ +-- SCK
// ------------+ GND +-- SDA (PB0) --------+ GND +-- SDA
// +--------+ +--------+
// =========================================================================================
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#define LED_PORT1 PB3
#define LED_PORT2 PB4
#define hc 100 // 0.4 was good for 24c02 maybe it can be faster
#define SDA_LINE (1 << PB0)
#define SCK_LINE (1 << PB2)
#define memdevaddr 0x50
#define memaddr 0x22
#define data 0x22
// set SDA,SCK high, then SDA to low while SCK remain high
int starti2c(void) {
DDRB |= SDA_LINE | SCK_LINE; // enable output
PORTB |= SDA_LINE | SCK_LINE;
_delay_ms( hc );
PORTB &= ( 0b00011111 ^ SDA_LINE );
_delay_ms( hc );
return 0;
}
// SDA to low from high while SCK high
int stopi2c(void) {
DDRB |= SDA_LINE | SCK_LINE; // enable output
if (PORTB & SCK_LINE) { // SCK high -> to low, set SDA, then SCK to high
PORTB &= ( 0b00011111 ^ SCK_LINE );
_delay_ms( hc );
PORTB &= ( 0b00011111 ^ SDA_LINE );
_delay_ms( hc );
PORTB |= SCK_LINE;
_delay_ms( hc );
PORTB |= SDA_LINE;
}
else { // SCK low set SDA
PORTB &= ( 0b00011111 ^ SDA_LINE );
_delay_ms( hc );
PORTB |= SCK_LINE;
_delay_ms( hc );
PORTB |= SDA_LINE;
}
_delay_ms( hc );
return 0;
}
int clearsck(void) {
PORTB &= ( 0b00011111 ^ SCK_LINE );
_delay_ms( hc );
return 0;
}
int senddevaddress(void) {
int i;
for (i=6; i>=0;i--) {
int bit = (memdevaddr & (1 << i)) >> i;
if (bit) {
PORTB |= SDA_LINE;
}
else {
PORTB &= ( 0b00011111 ^ SDA_LINE );
}
_delay_ms( hc );
PORTB |= SCK_LINE;
_delay_ms( hc );
_delay_ms( hc );
clearsck();
}
return 0;
}
int sendbyte( int b ) {
int i;
for (i=7; i>=0;i--) {
int bit = (b & (1 << i)) >> i;
if (bit) {
PORTB |= SDA_LINE;
}
else {
PORTB &= ( 0b00011111 ^ SDA_LINE );
}
_delay_ms( hc );
PORTB |= SCK_LINE;
_delay_ms( hc );
_delay_ms( hc );
clearsck();
}
return 0;
}
int setwrite(void) {
PORTB &= ( 0b00011111 ^ SDA_LINE );
_delay_ms( hc );
PORTB |= SCK_LINE;
_delay_ms( hc );
_delay_ms( hc );
clearsck();
return 0;
}
// Incomplete - This code don't check result
int getack(void) {
PORTB &= ( 0b00011111 ^ SDA_LINE );
DDRB &= ( 0b00011111 ^ SDA_LINE );
_delay_ms( hc );
PORTB |= SCK_LINE;
_delay_ms( hc );
int ack = PORTB & SDA_LINE;
_delay_ms( hc );
clearsck();
DDRB |= SDA_LINE;
return ack;
}
int main(void) {
DDRB &= 0b11100000;
DDRB |= (1 << LED_PORT1); // set PortB to output
DDRB |= (1 << LED_PORT2);
PORTB &= 0b11100000;
PORTB |= 1 << LED_PORT1; // set RED Led ON
_delay_ms(100);
PORTB |= 1 << LED_PORT2; // set GREEN led ON
// do i2c communication
starti2c();
clearsck();
senddevaddress();
setwrite();
getack();
sendbyte( memaddr );
getack();
sendbyte( data );
getack();
_delay_ms( hc );
stopi2c();
clearsck();
_delay_ms( hc );
_delay_ms(100);
PORTB &= (0b00011111 ^ ( 1 << LED_PORT1) ); // set RED Led OFF - Process finished, GREEN led stay ON
_delay_ms(100);
return 0;
}
How can you use this info? If you complete this code, you can store initial settings on an EEPROM and install it e.g. to LM75a so you can easily make a thermostat like things or you can build a contactless thermometer (you may need MLX90614 and a different MCU). Also you can connect anything works through I2C.
If you buy an e.g. Arduino instead of MCU you can get a complete library so you don't need to program the communication, you can make easier codes.
My Simple I2C Thermostat
Using knowledge and resources I have previously used, I have built my simple I2C thermostat.
I used my I2C test module but now not the computer controlls it. It works from battery, it has only one program, settings stored in 24c02. I can change settings without rebuild program code, only have to modify the eeprom.
There is no buttons and display in this demo system, but it can be added with some modification.
This config can be used in a device protect it from overheating (LM75a can only measure air/surface in contact with it) with a driven cooler, or it can drive a heater if room temperature is too cold. It depends on LM75a settings ( driver electronics may needed ).
I share my test firmware If you would like to try it. (thermostat_test_v10.tar.gz)
You have to program .hex file to ATTiny45 with AVRDUDE and you need to setup configuration in 24c02 (maybe 24c01 also good).
This test version of program can only acces one sensor you must save its address to 0x20 position at eeprom. From this address you MUST write the next data: xx 00 02 00 02 yy 03 zz
where xx is the address of LM75a, yy = THYST in °C, zz = TOS in °C. If you don't do exactly the same it will not work, see sample test configuration in PonyProg in next picture. (all the data in hexadecimal)
If you would like different thermal settings just change yy/zz at eeprom, then reset MCU or power off/on the system.
LCD Display
ATTiny45 is a small chip, so it has only some pins. Normally you can use only 5 pins, but LCD-s mostly has more pins to connect. (If you program FUSE bits you can get RESET pin too.) If you would like to use I2C/SMBus connections only 3 pins will remain. My advice to choose an I2C display so you do not need to tie more. Other way to use serial to parallel connection with e.g. shift register, or you may have to change MCU.
I have ordered a better display, - I will connect it to I2C, - but until it arrives I've connected a 2digit LCD to my ATTiny45. I've found a simple LCD designed to display time and 2 different shift registers. In my free time I've soldered it to display 2digit + a sign + ':5' and connected to my simple I2C thermostat to display current temperature.
Easy to create a serial->parallel conversion (1->8bit) with HEF4094B shift register I've found. Mostly you need 2 wire, clock and data. Every 8 cycles of clock sets different 1 bit on output from data. So you need to set clock low and set data, wait a little, set clock high, wait. In this cycle you send 1bit to an output pin and jump to next pin. Now you have to repeat with the next pin data.
An example function that displays the temperature:
//
// Main functions to communicate to mySimpleLCD with shiftreg
//
// Copyright (c)2020 Tóthpál István, istvan@tothpal.eu
//
/* HEF4794B HEF4094B neg. output
----------------- ----------------- -----------------
| x80 pin7 | | x80 pin7 | | x01 pin0 |
----------------- ----------------- -----------------
--- --- --- --- ---
| | | | | | | | | |
|x| |x| |x| |x| |x|
|0| |4| |0| |4| ----- |0|
|8| |0| |8| |0| |x01| |1|
| | | | | | | | | 0 | | |
|3| |6| |3| |6| ----- |0|
| | | | | | | | | |
--- --- --- --- ---
----------------- ----------------- ----------------- -----------------
| x01 pin0 | | x20 pin5 | | x20 pin5 | | x01 pin0 |
----------------- ----------------- ----------------- -----------------
--- --- --- --- ---
| | | | | | | | | |
|x| |x| |x| |x| |x|
|0| |1| |0| |1| ----- |0|
|4| |0| |4| |0| |x01| |1|
| | | | | | | | | 0 | | |
|2| |4| |2| |4| ----- |0|
| | | | | | | | | |
--- --- --- --- ---
----------------- ----------------- -----------------
| x02 pin1 | | x02 pin1 | | x01 pin0 |
----------------- ----------------- -----------------
*/
#define CLK_PORT (1 << PB1)
#define DIGIT1_PORT (1 << PB3)
#define DIGIT2_PORT (1 << PB0)
uint8_t numbers[16] = { 0xDE, 0x50, 0xe6, 0xf2, 0x78, 0xba, 0xbe, 0xd0, 0xfe, 0xfa,
0xfc, 0x3e, 0x8e, 0x76, 0xae, 0xac };
// print temperature in °C (-99.5,+99.5) 0.5°C scale (bit0 on digit2)
int mylcd_printtemperature( float temperature ){
DDRB |= CLK_PORT | DIGIT1_PORT | DIGIT2_PORT; // enable output
int i;
uint8_t digit1;
uint8_t digit2;
int index1;
int index2;
int sign = 1;
int half;
if (temperature < 0) {
sign = -1;
}
index1 = sign*temperature/10;
digit1 = numbers[index1];
if (sign<0) {
digit1 |= 1;
}
index2 = sign*temperature - index1*10;
digit2 = 0xff - numbers[index2];// have to set inverse 4094
half = 10*(sign*temperature - index1*10 - index2*1) / 5;
if (half!=0) {
digit2 &= 0b11111110; // digit2 inverse
}
for (i=0; i<8; i++) {
if ( (digit1 & (1 << i)) > 0 ) {
PORTB |= DIGIT1_PORT;
}
else {
PORTB &= (0b00011111 ^ DIGIT1_PORT);
}
if ( (digit2 & (1 << i)) > 0 ) {
PORTB |= DIGIT2_PORT;
}
else {
PORTB &= (0b00011111 ^ DIGIT2_PORT);
}
PORTB &= (0b00011111 ^ CLK_PORT);
mylcd_wait();
PORTB |= CLK_PORT;
mylcd_wait();
}
return 0;
};
Meanwhile the 1602LCD has arrived. The I2C adapter is arriving, but I've tried to connect to my ATTiny45. The problem is, that it has 16 pins to connect, but the good news is it can work with 4bit data. So I need to handle RS,RW,E,D4,D5,D6,D7. This small MCU has only 5 pins normally, but if RESET disabled I can use 6, so I have to give up reading from LCD to fit. Other solutions are connecting through shift register or I2C.
A very simple example code writing "TIS" to 1602 LCD using 6pin connection:
// Test program for accessing 1602LCD from ATTiny45 in C
//
// Copyright (c)2020 Tóthpál István, istvan@tothpal.eu
//
// =================================================================================================
// ATtiny 1602 LCD 4bit interface
// 25/45/85
// +--------+
// RS - PB5 --+ o Vcc +------------ G + G P G P P + G
// D6 - PB3 --+ +-- PB2 - D5 PIN N 5 N B N B B 5 N
// D7 - PB4 --+ +-- PB1 - D4 PIN D V D 5 D 0 1--4 V D
// ------------+ GND +-- PB0 - EN PIN | | | | | | |||| | |
// +--------+ VSS VDD VO RS RW E D0-D7 A K
// =================================================================================================
//
// FUSE: HFUSE: 0x5F - RESET DISABLED; LFUSE: 0xD2 - 8Mhz no_div
//
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#define D4_PIN (1 << PB1)
#define D5_PIN (1 << PB2)
#define D6_PIN (1 << PB3)
#define D7_PIN (1 << PB4)
#define EN_PIN (1 << PB0)
#define RS_PIN (1 << PB5)
#define wait _delay_us(100) // ~40us
int my1602_send( int rs, uint8_t data ) {
if (rs) {
PORTB |= RS_PIN;
}
else {
PORTB &= ~RS_PIN;
}
uint8_t p = PORTB & 0b11100001;
PORTB = p | (( data & 0x0f ) << 1);
//pulse EN
PORTB &= ~EN_PIN;
_delay_us(1);
PORTB |= EN_PIN;
_delay_us(2);
PORTB &= ~EN_PIN;
return 0;
}
// as 1602 LCD datasheet described, with longer timing
int my1602_init( void ) {
DDRB |= EN_PIN | RS_PIN | D4_PIN | D5_PIN | D6_PIN | D7_PIN; // enable output
_delay_ms(150);
PORTB &= ~RS_PIN;
PORTB &= ~EN_PIN;
my1602_send( 0, 0b0011 ); // 8bit
_delay_ms(5);
my1602_send( 0, 0b0011 ); // 8bit
_delay_us(150);
my1602_send( 0, 0b0011 ); // 8bit
wait;
my1602_send( 0, 0b0010 ); // set 4bit
wait;
my1602_send( 0, 0b0010 ); // set 4bit
my1602_send( 0, 0b1000 ); // 2row, 5x8 font
wait;
my1602_send( 0, 0b0000 ); // set display off / cr off
my1602_send( 0, 0b1000 );
wait;
my1602_send( 0, 0b0000 );
my1602_send( 0, 0b0001 ); // clear display
_delay_ms(5); // ~1.6ms
my1602_send( 0, 0b0000 );
my1602_send( 0, 0b0110 ); // increment - no shift
wait;
return 0;
}
int main(void) {
my1602_init();
_delay_ms(300); // init finished, I would like to wait a while before starting
my1602_send( 0, 0b0000 ); // set display on / cr on / cr blink
my1602_send( 0, 0b1111 );
wait;
my1602_send( 0, 0b1000 ); // set DDRAM address 0x00
my1602_send( 0, 0b0000 );
wait;
my1602_send(1, 0b0101); // write T
my1602_send(1, 0b0100);
wait;
my1602_send(1, 0b0100); // write I
my1602_send(1, 0b1001);
wait;
my1602_send(1, 0b0101); // write S
my1602_send(1, 0b0011);
wait;
DDRB &= ~ (EN_PIN | RS_PIN | D4_PIN | D5_PIN | D6_PIN | D7_PIN); // disable output
return 0;
}
Finally the I2C adapter has arrived. It has a PCF8574T chip on it, which can convert I2C to 8 bit parallel out/in. This adapter designed to handle a 1602 LCD with 4bit data communication, you can use simple i2c_smbus_read_byte and i2c_smbus_write_byte functions on 0x27 address by default.
Debugging AVR
You can use DebugWIRE,- on chip debugging serial connection in 1 wire,- to connect your MCU to a serial port.
I have tried DWire-Debug and it worked for me. It uses a very simple connection, I connected to my CH341A serial RX to PB5/RESET/dW pin and a diode to TX as shown in the manual. It can dump eeprom, flash, registers, add break point, trace and more...
[@localhost avr_dw_test]$ ./dwire-debug-master/dwdebug /dev/ttyUSB0
Unconnected. > reset,r
Connected to ATtiny45 on /dev/ttyUSB0 at 64575 baud.
r0 01 r4 08 r8 00 r12 80 r16 33 r20 40 r24 00 r28 5f
r1 00 r5 0a r9 03 r13 86 r17 12 r21 00 r25 00 r29 01
r2 01 r6 00 r10 0c r14 02 r18 00 r22 10 r26 90 r30 57
r3 81 r7 40 r11 00 r15 48 r19 e1 r23 00 r27 00 r31 00
SREG i t h s v n z c PC 0000 SP 015f X 0090 Y 015f Z 0057
0000: 303a cpi r19, $a > t
0002: 3030 cpi r19, {main} > u
0004: 3030 cpi r19, {main}
0006: 3030 cpi r19, {main}
0008: 4631 sbci r19,
000a: 0d46 add r20, r6
000c: c00a rjmp 0022 (+11)
000e: c00f rjmp 002e (+16)
0010: c00e rjmp 002e (+15)
0012: c00d rjmp 002e (+14) > e
0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................ >
0002: 3030 cpi r19, {main} > f
0000: 3a 30 30 30 30 30 30 30 31 46 46 0d 0a c0 0f c0 :00000001FF.....
0010: 0e c0 0d c0 0c c0 0b c0 0a c0 09 c0 08 c0 11 24 ...............$
0020: 1f be cf e5 d1 e0 de bf cd bf 02 d0 66 c0 e8 cf ............f...
0030: 8f e1 87 bb 88 bb 2f ef 80 e7 92 e0 21 50 80 40 ....../.....!P.@
0040: 90 40 e1 f7 00 c0 00 00 88 b3 83 7e 88 bb 2f ef .@.........~../.
0050: 80 e7 92 e0 21 50 80 40 90 40 e1 f7 00 c0 00 00 ....!P.@.@......
0060: 88 b3 8c 61 88 bb 2f ef 80 e7 92 e0 21 50 80 40 ...a../.....!P.@
0070: 90 40 e1 f7 00 c0 00 00 88 b3 88 7f 88 bb 2f ef .@............/. >
0002: 3030 cpi r19, {main} >
To enable DebugWire you need to modify fuse bits, have to enable DWEN. To calculate fuses it should be useful: Engbedded Atmel AVR® Fuse Calculator
You could try another debugger: avarice, avr-gdb, ...
Portable power
You can add power to your AVR board from several solution, it could work from e.g. USB or batteries, and also you could buy powerboards. They have different size, and they can supply different Voltage and Current, choose the best you need.
My favorite is actually a PowerBank, I can charge from microUSB and from a ~5.5V solar panel and it can supply 5V (adjustable up to 28V) 2A from 18650 battery cells. Usually I can use it to charge from USB or give 5V to my test AVR panel.
It is a charger board with TC4056 IC and a step up module. (You can find similar panel which contains these panels in one.) This charger developed to charge one 18650 cell with 4.2V 1A. The voltage of this step up module is adjustable up to 28V and works from near 2V. Maybe this is not the best to charging this way, but it works well with my solar panel designed to ~5.5V 700mA, so it can fully charge the cells in two not cloudy winter days (1 in summer).