;********************************************************************** ; at328-3s.S - Demonstrate simple I/O functions of ATmega328 ; and the use of an internal timer and interrupt ; ; This program will cause a 7-segment display to either count up in ; hexadecimal (0,1,2,...,E,F,0,1,...) or count down in decimal ; (9,8,...,1,0,9,8,..) depending on whether or not a switch is pressed. ; ; Port C, bit 1 - input from switch (0 = pressed, 1 = not pressed) ; When the switch is not pressed, the 7-segment display counts ; up in hexadecimal. When the switch is pressed, the 7-segment ; display counts down in decimal. ; Port B, bits 0-1 and Port D, bits 2-6 - Outputs to data inputs of ; the 74LS374 register. ; Bit 6 -> segment A, 5->B, ... , 1->F, 0->G ; A low output bit will cause the LED segment to light up. ; Port C, bit 2 - Output to positive edge-triggered clock input ; of 74LS374 register. ; ; Revision History ; Date Author Description ; 08/18/07 A. Weber Updated for JL16 ; 02/26/08 A. Weber Modified for CodeWarrior assembler ; 06/11/13 A. Weber Translated to AVR assembler (avr-gcc) ; 11/18/13 A. Weber Renamed for ATmega328P ;********************************************************************** #include #define SWITCH PC1 #define SEG_DATA_B 0x03 // Bits in Port B for LED display #define SEG_DATA_D 0x7c // Bits in Port D for LED display #define SEG_CLOCK PC2 count = 19 ; Current count value temp = 20 zero = 1 ; R1 is zero .section .text .global main main: sbi _SFR_IO_ADDR(PORTC), PC1 ; Enable pull-up resistor on switch sbi _SFR_IO_ADDR(DDRC), SEG_CLOCK ; Set register clock for output in r16, _SFR_IO_ADDR(DDRB) ; Segment bits 0-1 are output on ori r16, SEG_DATA_B ; PORTB bits 0-1 out _SFR_IO_ADDR(DDRB), r16 in r16, _SFR_IO_ADDR(DDRD) ; Segment bits 2-6 are output on ori r16, SEG_DATA_D ; PORTD bits 2-6 out _SFR_IO_ADDR(DDRD), r16 clr count ; Set initial count value to zero /* The demo board has a 9.8304 MHz clock. We want the timer to interrupt every half second (2 Hz) so we need to count clocks to 9.8304MHz/2Hz = 4,915,200. This is too big for the 16 bit counter register so use the prescaler to divide the clock by 256 and then count that clock to 19,200. */ // Reset clears register bits to zero so don't have to clear individual bits // Set for CTC mode using OCR1A for the modulus ldi r26,lo8(TCCR1B) ; Put address of TCCR1B register in X ldi r27,hi8(TCCR1B) ld temp, X ; Get TCCR1B to temp ori temp, (1 << WGM12) ; Set WGM12 bit st X, temp ; Put temp back to TCCR1B // Enable CTC interrupt lds temp, TIMSK1 ; Get TIMSK1 to temp ori temp, (1 << OCIE1A) ; Set OCIE1A bit sts TIMSK1, temp ; Put temp back to TIMSK1 sei ; Enable global interrupts // Set the counter modulus ldi temp, hi8(19200) ; High byte must be written first (Sec. 15.3) sts OCR1AH, temp ldi temp, lo8(19200) sts OCR1AL, temp // Set prescaler for divide by 256, starts timer ld temp, X ; Get TCCR1B to temp ori temp, (1 << CS12) ; Set CS12 bit st X, temp ; Put temp back to TTCR1B, starts timer rcall display_digit ; Output count digit to display loop: rjmp loop ; Loop forever while interrupts occur .global TIMER1_COMPA_vect TIMER1_COMPA_vect: sbic _SFR_IO_ADDR(PINC), PC1 ; Check the button rjmp up ; if PC1 = 0, count down down: dec count ; Decrement count cpi count,10 ; Is it over 9? If number was zero, ; decrementing it will make it 255 brlo d1 ; If lower or same, it's OK ldi count,9 ; Otherwise, set it to nine d1: rjmp done up: inc count ; Increment it cpi count,16 ; Is it over 15? brlo done ; If lower or same, it's OK clr count ; Otherwise, reset it to zero done: rcall display_digit reti display_digit: ldi r30,lo8(segtbl) ; Put segment table address in Z ldi r31,hi8(segtbl) add r30,count ; Add the offset adc r31,zero lpm r18,Z ; Get segment bit from program memory com r18 ; Invert bits since low output lights LED in r17, _SFR_IO_ADDR(PORTB) ; Get PORTB to R17 andi r17, ~SEG_DATA_B ; Mask off the segment bits mov r16, r18 ; Copy segment bits to R16 andi r16, SEG_DATA_B ; Mask off the other bits or r17, r16 ; OR them together out _SFR_IO_ADDR(PORTB), r17 ; Put R17 back to PORTB in r17, _SFR_IO_ADDR(PORTD) ; Get PORTD to R17 andi r17, ~SEG_DATA_D ; Mask of the segment bits mov r16, r18 ; Copy segment bits to R16 andi r16, SEG_DATA_D ; Mask off the other bits or r17, r16 ; OR them together out _SFR_IO_ADDR(PORTD), r17 ; Put R17 back to PORTB sbi _SFR_IO_ADDR(PORTC), SEG_CLOCK ; Toggle the clock bit to 1 cbi _SFR_IO_ADDR(PORTC), SEG_CLOCK ; Toggle the clock bit to 0 ret ; Table of seven-segment bits, from Wakerly, Table 5-21 ; ; 6 ; 1 5 ; 0 ; 2 4 ; 3 ; segtbl: .byte 0x7e ; 0 .byte 0x30 ; 1 .byte 0x6d ; 2 .byte 0x79 ; 3 .byte 0x33 ; 4 .byte 0x5b ; 5 .byte 0x5f ; 6 .byte 0x70 ; 7 .byte 0x7f ; 8 .byte 0x73 ; 9 .byte 0x77 ; A .byte 0x1f ; b .byte 0x4e ; C .byte 0x3d ; d .byte 0x4f ; E .byte 0x47 ; F