You are on page 1of 10

Example #1 [OUTPUT]

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.

Example #1: Code


The code is written in AVR Assembly. If you work with controllers like ATTiny - it is a must!
It looks scary, but in reality it's much simpler and easier than C.
Another reason for using assembly language is that this code only needs 148 bytes of
space after compilation, while Arduino IDE produces 644 bytes of binary output for the
same code (haven't tested in AVR GCC), so if you want to expand functionality of your Tiny
project, you have less than 400 bytes left to work with...
/*
*
*
*
*
*
*
*

Shift register demo #1


ATTiny13A Running @9.6MHz
ADC running @150kHz
PIN ASSIGNMENT:
PB0 - Shift Register Clock

*
*
*
*
*
*/
.include
.def
.def
.def
.def
.equ
.equ
.equ

PB1
PB2
PB3
PB4
PB5

Shift Register Serial Data


Shift Register Latch(Store)
ADC3 (Potentiometer input)
[NOT USED]
RESET

"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

Example #2: [INPUT]

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.

Example #2: CODE

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.
/*
*

Shift register demo #2


*
PB0 - Shift Register Clock

ATTiny13A Running @9.6MHz *

PB1 - Shift Register Serial Data

PB2 - Shift Register Latch(Store)

PB3 - Digital input(bit match)

PB4 - LED

PB5 RST

PIN ASSIGNMENT:

*/
.include "tn13Adef.inc"

.def

A = R16

; g.p. variable and/or function argument

.def

B = R17

; Used in SEND_BYTE and ADC_START as temporary storage

.def

LED = R18

; stores current LED output

.def

BCT = R19

; Bit counter for SEND_BYTE

.def

TIM = R20

; Stores how many iterations of TOV0 have passed

.def

TMP = R21

.equ

SRCK = 0

; PB0 = Clock

.equ

SRDA = 1

; PB1 = Serial Data

.equ

SRLC = 2

; PB2 = Latch

/*

INTERRUPT VECTORS

.org

0x0000

rjmp

RESET

.org

0x0003

rjmp

TC0_OV

/*

START!!!

*/

; Reset interrupt

; Timer0 Overflow interrupt

*/

RESET:
/*

SETUP STACK

*/

ldi

A, low(RAMEND)

; Set stack pointer

out

SPL, A

/*

SETUP PINS

*/

ldi

A,0b0001_0111

; Set output pins PB0..PB2(CLK,DATA,LATCH)

out

DDRB,A

; PB4 - LED output

/*

SETUP TIMER0

*/

ldi

A,0b0000_0101 ; Set Timer Prescaler 1/1024

out

TCCR0B,A

; Interrupt every ~27ms

ldi

A,0b00000010

; Enable Timer0 Overflow Interrupt

out

TIMSK0,A

/*

RESET REGISTERS*/

ldi

A,0x00

; clear A

ldi

LED,0x10

; Default blink speed(~1Hz)

sei

/*

; Enable interrupts

Main loop

*/

MAIN:
ldi

A,1

; Set the first bit

ldi

TMP,0

; Temporary storage for new LED delay

rcall

SEND_BYTE

; Send A to Shift Reg.

sbic

PINB,3

; Check for match

or

TMP,A

; Add it to result

next:

clc

; Clear carry

rol

; Rotate A

breq

check

; If A==0, check and start over

rjmp

next

; else get next bit

check:

/*
*

; Only assign non-zero values


tst

TMP

; TMP==0?

breq

MAIN

; skip

mov

LED,TMP

; else assign new LED delay

rjmp

MAIN

Sends 8-bit data from A register to Shift Register


(Same as in the prev. example)

*/

SEND_BYTE:
ldi

BCT,0b1000_0000; Set Bit counter

mov

B,A

; Move data byte to temp

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

; 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

Timer 0 overflow interrupt */

TC0_OV:
inc
cp

TIM
TIM,LED

; TIM++
; TIM>LED?

brlo

early

; too early

push

TMP

; Save old TMP value

in

TIM, PINB

; Read current port state

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:

AVR Assembly Reference

AVRbeginners.net

My intro to ATTiny13

You might also like