ATtiny Examples For Programing
ATtiny Examples For Programing
Code
We are going to read an 8-bit value from ADC3 (most significant bits) and then pass the
corresponding output value to our shift register.
To be more specific I've made a table of values for I/O states:
ADC3 value
Output value
Segments on
0..40
0xFF
41..80
0xFE
81..120
0xFC
121..160
0xF8
161..200
0xF0
201..255
0xE0
NOTE: Because it is a common anode LED indicator we need to set the appropriate pin
LOW whenever we want it to light up.
Now, let's write some code.
In short, we need to set up a Timer Overflow interrupt, which will read the current state of
ADC3 and send the corresponding value to our shift register approximately every ~27ms.
SEND_BYTE subroutine sends an 8-bit stream of data and latches it in the shift register.
This is a bit simplified function, but with some minor improvements can be used
universally.
I wrote most numbers in binary form, so it will be easier (at least for me) to see which LEDs
are enabled and which flags are set.
*
*
*
*
*
*/
.include
.def
.def
.def
.def
.equ
.equ
.equ
PB1
PB2
PB3
PB4
PB5
"tn13Adef.inc"
A = R16 ; g.p. variable and/or function argument
B = R17 ; Used in SEND_BYTE and ADC_START as temporary storage
LED = R18
; stores current LED output
BCT = R19
; Bit counter for SEND_BYTE
SRCK = 0
; PB0 = Clock
SRDA = 1
; PB1 = Serial Data
SRLC = 2
; PB2 = Latch
/*
.org
rjmp
.org
rjmp
INTERRUPT VECTORS
*/
0x0000
RESET
; Reset interrupt
0x0003
TC0_OV
; Timer1 interrupt
/*
RESET:
/*
START!!!
/*
MAIN:
Main loop
*/
SETUP STACK
*/
ldi
A, low(RAMEND) ; Set stack pointer
out
SPL, A
/*
SETUP PINS
*/
ldi
A,0b0000_0111 ; Set output pins PB0..PB2
out
DDRB,A
/*
SETUP TIMER1
*/
ldi
A,0b0000_0101 ; Set Timer Prescaler (1024)
out
TCCR0B,A
; This will cause Timer Interrupt every ~27ms
ldi
A,0b00000010
; Enable Timer0 Overflow Interrupt
out
TIMSK0,A
/*
SETUP ADC3
*/
ldi
A,0
out
ADCSRB,A
; Disable autotrigger(Free running)
ldi
A,0b00001000
; Disable Digital Input on PB3(ADC3)
out
DIDR0,A
ldi
A,0b00000011
out
ADMUX,A
; Source:ADC3, Align:RIGHT, Reference:VCC.
ldi
A,0b10000110
out
ADCSRA,A
; Enable ADC with prescale 1/64
/*
RESET REGISTERS*/
ldi
A,0x00
; clear A
ldi
LED,0xFF
; Set all LED's to OFF(1-off, 0-on)
rcall
SEND_BYTE
; Clear display
sei
; Enable interrupts
rjmp
*/
MAIN
/*
*
Sends 8-bit data from LED register to Shift Register
*/
SEND_BYTE:
ldi
BCT,0b1000_0000; Set Bit counter
next_bit:
mov
B,LED
; Move data byte to temp
and
B,BCT
; Check bit
breq
zero
; Set Data to 0
sbi
PortB,SRDA
; Set Data to 1
rjmp
shift
; shift
zero:
cbi
PortB,SRDA
shift:
sbi
PortB,SRCK
; CLK up
nop
cbi
PortB,SRCK
; CLK down
clc
; Clear Carry flag
ror
BCT
; Shift bit counter
brne
next_bit ; Next iteration
sbi
PortB,SRLC
; When done, Latch
nop
cbi
PortB,SRLC
ret
; Done
/*
Start ADC conversion. Saves result to A
*/
ADC_START:
sbi ADCSRA,ADSC
; Start ADC conversion
adc_wait:
sbic
ADCSRA,ADSC
; Check conversion status
rjmp
adc_wait ; Skip jump if completed
in
A,ADCL
; Get low bits
in
B,ADCH
; Get high bits
lsr
B
; Shift 2 bits to the right
ror A
; through Carry
lsr B
ror A
ret
/*
Timer 0 overflow interrupt */
TC0_OV:
rcall
ADC_START
; start ADC0 Conversion
/* Compare Input, Set output */
cpi
A,0xC8
; A>=200?
brlo
gt_160
ldi
LED,0b11100000
rjmp
sr_write
gt_160:
; A>=160?
cpi
A,0xA0
brlo
gt_120
ldi
LED,0b11110000
rjmp
sr_write
gt_120:
; A>=120?
cpi
A,0x78
brlo
gt_80
ldi
LED,0b11111000
rjmp
sr_write
gt_80:
; A>=80?
cpi
A,0x50
brlo
gt_40
ldi
LED,0b11111100
rjmp
sr_write
gt_40:
; A>=40?
cpi
A,0x28
brlo
lt_40
ldi
LED,0b11111110
rjmp
sr_write
lt_40:
; A<40
ldi
LED,0b11111111
sr_write:
rcall
SEND_BYTE
; Send byte to shift reg.
reti
; return
As strange as it sounds, handling multiple digital inputs with a shift register is almost the same as handling multiple
outputs. Let's look at the circuit first, so I can explain how it works.
Diodes are added to protect the outputs of the shift register, since multiple HIGH inputs may cause a short circuit. PB3
is connected to the ground through a 10K resistor (logical 0 when no match found).
The general idea is to send a certain set of data bits to the shift register and if there is a bitwise match with the input we will get HIGH signal on PB3. For example, we have an 8-bit input 0x91, which is 10010001 in binary.
We start with sending 0x01 to the shift register (0b00000001) and see if the first bit is 1. If we have a match (PB3 is
HIGH), we perform OR operation of the input to the result. Next, we shift the test data 1 bit to the left, so we get 0x02
(0b00000010) and repeat the procedure to acquire the second bit, which gives no match and results in logical 0... and
so on until we test all 8 bits.
Shift Register
PB3
Result
00000001
00000001
00000010
00000001
00000100
00000001
00001000
00000001
00010000
00010001
00100000
00010001
01000000
00010001
10000000
10010001
This technique allows to read reasonably large array of inputs at the cost of acquisition speed only. It does not require
any additional pins, so it is a perfect solution for low-speed applications, like keypads, switchboards, or even lowspeed digital sensors. The number of used pins can be further reduced, if we alternate Serial Data pin of the
microcontroller between digital output and digital input(instead of PB3).
For our next example we will use almost identical circuit, but instead of digital inputs and transistors we will use simple
tactile switches.
This code is much smaller and simpler than the previous example, because this time we are not using ADC. As you
can see, SEND_BYTE subroutine is unchanged and does pretty much the same thing.
/*
*
PB4 - LED
PB5 RST
PIN ASSIGNMENT:
*/
.include "tn13Adef.inc"
.def
A = R16
.def
B = R17
.def
LED = R18
.def
BCT = R19
.def
TIM = R20
.def
TMP = R21
.equ
SRCK = 0
; PB0 = Clock
.equ
SRDA = 1
.equ
SRLC = 2
; PB2 = Latch
/*
INTERRUPT VECTORS
.org
0x0000
rjmp
RESET
.org
0x0003
rjmp
TC0_OV
/*
START!!!
*/
; Reset interrupt
*/
RESET:
/*
SETUP STACK
*/
ldi
A, low(RAMEND)
out
SPL, A
/*
SETUP PINS
*/
ldi
A,0b0001_0111
out
DDRB,A
/*
SETUP TIMER0
*/
ldi
out
TCCR0B,A
ldi
A,0b00000010
out
TIMSK0,A
/*
RESET REGISTERS*/
ldi
A,0x00
; clear A
ldi
LED,0x10
sei
/*
; Enable interrupts
Main loop
*/
MAIN:
ldi
A,1
ldi
TMP,0
rcall
SEND_BYTE
sbic
PINB,3
or
TMP,A
; Add it to result
next:
clc
; Clear carry
rol
; Rotate A
breq
check
rjmp
next
check:
/*
*
TMP
; TMP==0?
breq
MAIN
; skip
mov
LED,TMP
rjmp
MAIN
*/
SEND_BYTE:
ldi
mov
B,A
and
B,BCT
; Check bit
breq
zero
; Skip if 0
sbi
PortB,SRDA
; Send Data
rjmp
shift
; shift right
next_bit:
zero:
cbi
PortB,SRDA
sbi
PortB,SRCK
; CLK up
PortB,SRCK
; CLK down
shift:
nop
cbi
clc
ror
BCT
brne
sbi
PortB,SRLC
nop
cbi
PortB,SRLC
ret
/*
; Done
TC0_OV:
inc
cp
TIM
TIM,LED
; TIM++
; TIM>LED?
brlo
early
; too early
push
TMP
in
TIM, PINB
ldi
TMP, 0x10
eor
TIM,TMP
; Toggle PB4
out
PORTB, TIM
ldi
TIM,0
; Reset counter
pop
TMP
; Restore TMP
early:
reti
; return
Additional resources
If you are new to AVR Assembly and you feel intimidated by cryptic opcodes and registers,
but you think you are ready to conquer the world of microcontrollers, these are good places
to start:
AVRbeginners.net
My intro to ATTiny13