First Blinky Assembly Program...

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is my first real attempt at writing code in assembly language for the AVR8 micro-controller. With the AVR, it has always been C.

 

I wanted to create some functions that I use frequently. In the past, in the MC68HC11 days, I did all coding in assembly language and, I have had a longing to return to it with my use of the AVR – though, many people claim it to be obsolete and primitive. So be it!

 

So, I began studying the listing of some of the C programs Ive written and digging into the instruction set. It seems that the MC68HC11 was quite a bit easier to program with assembly, than is the AVR. But learning is fun.

 

I figured I’d begin with a “BLINKY” program. But… I would accomplish that task using an interrupt handler for timing.

 

In addition, I wanted two forms of delay: (1. I wanted a PAUSE function, where normal CPU program execution is held in check, until the programmable PAUSE time terminated. (2. I wanted several delays that would run concurrently to each other and in the background to the main program loop.

 

I am posting the listing of my first successful attempt at assembly programming with the AVR in the form of a “BLINKY” program for your critique. I also have some question regarding assembly structure and format. I just need to figure out how to present them in a logical and orderly fashion.

 

I am using the AVRA flavor of the AVR assembler and the Atmel AVR000 “.inc” controller definitions with the PRAGMA’s commented our so that the program would assemble without warnings or errors.

 

The target controller is the AVRMega328P.

 

;*************************** Controller specifit definitions ******************************

.NOLIST
  .include "/usr/share/avra/m328Pdef.inc"
.LIST
;------------------------------------------------------------------------------------------

.equ LED              = PB5

;************************************** EEPROM Stuff **************************************

.ESEG
  .ORG 0x0000
;------------------------------------------------------------------------------------------

;************************************* Data Variables *************************************

.DSEG
  .ORG 0x0100 ;SRAM_START
        MCU_Status:     .byte 1
;------------------------------------------------------------------------------------------

;*************************************** FLASH Stuff **************************************

.CSEG
  .ORG 0x0000
;------------------------------------------------------------------------------------------

;*************************************** IRQ_Vectors **************************************

; IRQ vectors
        jmp RESET                           ; RESET
        jmp RESET                           ; INT0
        jmp RESET                           ; INT1
        jmp RESET                           ; PCINT0
        jmp RESET                           ; PCINT1
        jmp RESET                           ; PCINT2
        jmp RESET                           ; WDT
        jmp RESET                           ; TIMER2 COMPA
        jmp RESET                           ; TIMER2 COMPB
        jmp RESET                           ; TIMER2 OVF
        jmp RESET                           ; TIMER0 CAPT
        jmp TIMER1_COMPA                    ; Vecttor 11
        jmp RESET                           ; TIMER1 COMPB
        jmp RESET                           ; TIMER1 OVF
        jmp RESET                           ; TIMER0 COMPA
        jmp RESET                           ; TIMER0 COMPB
        jmp RESET                           ; TIMER0 OVF
        jmp RESET                           ; SPI, STC
        jmp RESET                           ; USART, RX
        jmp RESET                           ; USART, UDRE
        jmp RESET                           ; USART, TX
        jmp RESET                           ; ADC
        jmp RESET                           ; EE READY
        jmp RESET                           ; ANALOG COMP
        jmp RESET                           ; TWI
        jmp RESET                           ; SPM READY
;------------------------------------------------------------------------------------------

;********************************** Start-Up sequence *************************************

RESET:                                      ; Main program start
        in      r16, MCUSR                  ; Get the MCU status register
        sts     MCU_Status, r16             ; Save the MCU status after power-up
        clr     r16                         ; Clear r16
        out     MCUSR, r16                  ; Clear the MCUSR register for next RESET

        ldi     r16, high(RAMEND)           ; Set Stack Pointer to top of RAM
        out     SPH, r16
        ldi     r16, low(RAMEND)
        out     SPL, r16
        sei

        rcall   INIT_IO
        rcall   INIT_Timer1
;------------------------------------------------------------------------------------------

;************************************ Main Program loop ***********************************

; Pause resolution = 0.001 Second
; Pause Time = 5000 mS = 0x1388
; Blinky cycle time = 500 mS ON, 500,S OFF
; 500 mS = 0x01F4

MAIN:

; Setup PauseCounter
        ldi     r16, 0x13                       ; Initialize DelayCounter
        sts     high(PauseCounter),r16          ; 5 seconds upon bootup
        ldi     r16, 0x88
        sts     low(PauseCounter), r16

; Turn on the LED
        clr     r24
        ori     r24, (1<<LED)
        out     PORTB, r24                      ; Turn on LED

; Wait for a while
        rcall   Pause                           ; Wait here until Pause done

; Turn off the LED
        clr     r24                             ; Pause comlete!
        andi    r24, ~(1<<LED)
        out     PORTB, r24                      ; Turn off LED

BLINKY:
        ldi     r24, 0x01
        sts     high(TimerCounter_1), r24
        ldi     r24, 0xF4
        sts     low(TimerCounter_1), r24

        rcall   Timer1

BLINKY_LOOP:
        lds     r24, TimerStatus                ; Check for active TimerFlag_1
        andi    r24, (1<<TimerFlag_1)
        breq    BLINKY_LOOP                     ; If TimerFlag_1 NOT set, BLINKY

        in      r24, PORTB
        sbrs    r24, LED
        sbi     PORTB, LED
        sbrc    r24, LED
        cbi     PORTB, LED

        rjmp    BLINKY
;------------------------------------- Main loop end --------------------------------------

;************************************ Initialize PORTB ************************************

Init_IO:
        ldi     r16, (1<<LED)
        out     DDRB, r16                       ; Make PB5 an output
        ldi     r16, 0x00
        out     PORTB, r16                      ; Turn LED at PB5 --- OFF ---
        ret
;------------------------------------------------------------------------------------------

;************************************ Project includes ************************************

.include "/home/carl/Desktop/Projects/Assembly_Projects/Timer_IRQ.asm"
;------------------------------------------------------------------------------------------


;******************************************************************************************
;************************************ Timer1 Interrupt ************************************
;******************************************************************************************

;***************************************** Equates ****************************************
.equ PauseFlag        = 0
.equ TimerFlag_1      = 1
.equ TimerFlag_2      = 2
.equ TimerFlag_3      = 3
.equ TimerFlag_4      = 4
.equ TimerFlag_5      = 5
.equ TimerFlag_6      = 6
.equ TimerFlag_7      = 7
;------------------------------------------------------------------------------------------

;*************************************** Data segment *************************************
.DSEG

TimerStatus:     .byte 1
PauseCounter:    .byte 2
TimerCounter_1:  .byte 2
;------------------------------------------------------------------------------------------

;*************************************** Code segment *************************************
.CSEG

;****************************************** Pause *****************************************

Pause:
;   Pause is a holding delay ---> CPU control is not released, until PauseFlag comes TRUE
;   The variable --> PuseCounter <-- is loaded pryor to entering this routine
;   Maximum delay @ 0.001 second resolution = 65.535 seconds

        push    r24                         ; Preserve r24

;   Clear the PauseFlag to start thePauseTimer
        lds     r24, TimerStatus
        andi    r24, ~(1<<PauseFlag)        ; TimerStatus &= ~(1<<PauseFlag)
        sts     TimerStatus, r24

;   Hold program control here until PaueFlag becomes true
_Pause:
        lds     r24, TimerStatus            ; Loop while PauseFlag is FALSE
        andi    r24, (1<<PauseFlag)
        breq    _Pause                      ; PauseFlag = FALSE?
                
        pop     r24                         ;recover r24
        ret
;------------------------------------------------------------------------------------------

;***************************************** Delay_1 ****************************************

Timer1:
;   Timer1 is an asychronus delay. CPU control is imediately released back to the caller;
;   That is, timing is performed concurrent to norman program operation - in the background.
;   The variable --> TimerCounter_1 <-- is loaded pryor to entering this routine

;   Maximum delay @ 0.001 second resolution = 65.535 seconds
        push    r24                         ; Preserve r24

;   Clear the TimerFlag_1 to start Timer1
        lds     r24, TimerStatus
        andi    r24, ~(1<<TimerFlag_1)      ; TimerStatus &= ~(1<<TimerFlag_1)
        sts     TimerStatus, r24

        pop     r24                         ;recover r24
        ret
;------------------------------------------------------------------------------------------

;*************************************** Timer_Init ***************************************

INIT_Timer1:
;   Desired value: 1.000mS
;   Actual value:  1.000mS (0.0% error)

        push r24                            ; Preserve r24
        
;   TCCR1B = 0x00
        ldi     r24, 0x00
        sts     TCCR1B, r24                 ; Stop Timer Counter 1

;   TCNT1 = 0x0000
        sts     TCNT1H, r24                 ; OCR1AH -MUST- be written first
        sts     TCNT1L, r24                 ; Clear Timer Counter 1

;   TCCR1A = 0x00
        sts     TCCR1A, r24                 ; Disable OC1A  --> NO output at OC1A I/O pin

;   Set Output Compare Register 1A for 1mS time interval
;   Target resolution = 1.00 millisecond
;   0.001 sec / (1 / (18432000 / 8)) = 2304
;   2304 decimal = 0x0900 HEX

;   OCR1A = 0x0900
        ldi     r24, 0x09
        sts     OCR1AH, r24                 ; OCR1AH -MUST- be written first
        ldi     r24, 0x00
        sts     OCR1AL, r24

;   TCCR1B = (1<<WGM12) | (1<<CS11)
        ldi     r24, (1<<WGM12) | (1<<CS11) ; Start Timer1 OCR1A in CTC mode, CLK / 8
        sts     TCCR1B, r24

;   TIMSK1 |= (1<<OCIE1A)
        lds     r24, TIMSK1                 ; Enable OCR1A interrupt
        ori     r24, (1<<OCIE1A)
        sts     TIMSK1, r24

        pop     r24                         ; Recover r24

        ret
;------------------------------------------------------------------------------------------

;********************************** TIMER1_COMPA_vect_11 **********************************

;   TIMER1_COMPA: IRQ_vector_11:
;   WGM: 4) CTC mode, TOP = OCR1A, no timer/counter output generated
;   Resolution:
;       Desired value: 1.0 mS
;       Actual value:  1.000 mS (0.0% error)

TIMER1_COMPA:                               ; IRQ vector 11
        push    r24                         ; Preserve r24
        in      r24, SREG                   ; Get SREG
        push    r24                         ; Preserve SREG content
        push    r25                         ; Preserve r25

Pause_IRQ_Service:
;   if( (DelayStatus & (1<<PauseFlag)) )
        lds     r24, TimerStatus            ; Check for active PauseFlag
        andi    r24, (1<<PauseFlag)
        brne    Delay1_IRQ_Service          ; If PauseFlag set, Delay1_IRQ_Service

;   PauseCounter = --PauseCounter
        lds     r24, high(PauseCounter)
        lds     r25, low(PauseCounter)
        subi    r25, 1                      ; Decrement PauseCounter by 1
        sbci    r24, 0
        sts     high(PauseCounter), r24
        sts     low(PauseCounter), r25

;   if( PauseCounter-- != 0 )               ; PauseCounter zero check
        or      r24, r25                    ; Logically OR PauseCounter high/low bytes
        brne    Delay_IRQ_Exit              ; If PauseCounter not zero, Delay_IRQ_Exit

;   DelayStatus |= (1<<PauseFlag)
        lds     r24, TimerStatus            ; Pause done
        ori     r24, (1<<PauseFlag)         ; Set PauseFlag
        sts     TimerStatus, r24
        rjmp    Delay_IRQ_Exit              ; PauseFlag is set, Delay_IRQ_Exit

Delay1_IRQ_Service:
;   if( (DelayStatus & (1<<TimerFlag_1)) )
        lds     r24, TimerStatus            ; Check for active TimerFlag_1
        andi    r24, (1<<TimerFlag_1)
        brne    Delay2_IRQ_Service          ; If TimerFlag_1 set, Delay2_IRQ_Service

;   TimerCounter_1 = --TimerCounter_1
        lds     r24, high(TimerCounter_1)
        lds     r25, low(TimerCounter_1)
        subi    r25, 1                      ; Decrement TimerCounter_1 by 1
        sbci    r24, 0
        sts     high(TimerCounter_1), r24
        sts     low(TimerCounter_1), r25

;   if( TimerCounter_1-- != 0 )             ; TimerCounter_1 Zero check
        or      r24, r25                    ; Logically OR TimerCounter_1 high/low bytes
        brne    Delay_IRQ_Exit              ; If TimerCounter_1 not zero, Delay_IRQ_Exit

;   DelayStatus |= (1<<TimerFlag_1)
        lds     r24, TimerStatus            ; Timer_1 done
        ori     r24, (1<<TimerFlag_1)       ; Set TimerFlag_1
        sts     TimerStatus, r24
        rjmp    Delay_IRQ_Exit              ; TimerFlag_1 is set, Delay_IRQ_Exit

; For future use
Delay2_IRQ_Service:
;   .
;   .
;   .
Delay7_IRQ_Service:

Delay_IRQ_Exit:

        pop     r25
        pop     r24
        out     SREG, r24                   ; Recover SREG content
        pop     r24

        reti
;------------------------------------------------------------------------------------------

 

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The listing looks great.  It has all the proper formatting for assembler.  The AVR isn't all that much different from the 68HC11 except that it has more registers than X and Y.   There are a few opcodes that only work on the upper 16 registers. 

 

  I would have used a label "temp" for the register R24, but that is simply stylistic.

 

Assembly gets used with AVR when a peripheral has to have logic edges at quick predictable times.  For example, the programmable three-color LED called NeoPixel.  These are often on long strings where the digital output of one LED goes into the next of the sequence.  With 100+ NeoPixels in a strip, the data has to be sent quickly.  So a 1 microsecond period is pulse-wave modulated.  With an AVR, this gets only done with asm. 

 

The big trick is integrating asm with C++.  The Adafruit library for NeoPixels has an example of how to do this.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

With an AVR, this gets only done with asm. 

Or not. (Arguable as whether or not an Xmega is an "AVR", I guess)

 

This Thread shows my breadboarded WS2812B LEDs, (What AdaFruit has trademarked as NeoPixels), being driven by an AVR Xmega, programmed in Basic.

That Thread has a link to a You Tube Video that demo's the LEDs.

 

Carl, congrats on conquering Asm.

I've not don't any Asm in years.

 

JC

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Suggestion:  Use a .ORG for each vector with the "name" from the chip-include file.

 

It takes a few minutes more than your vector table, but someday you will save hours.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

Suggestion:  Use a .ORG for each vector with the "name" from the chip-include file.

 

It takes a few minutes more than your vector table, but someday you will save hours.

 

 

Theusch,

 

I'm not sure what you are suggesting with .ORG and "name.  Can you elaborate with an example?

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Simonetta wrote:

 

  I would have used a label "temp" for the register R24, but that is simply stylistic.

 

 

I had actually considered naming some registers "ACCA" and "ACCB".  The issue is, do you do that with r0 & r1 or r16 & r17?

 

Maybe ACCA = r0, ACCB = r1 and TEMP1 = r16 and TEMP2 = r17, or something

 

It seems that r0 & r1 get inherent priority with some arithmetic instructions whereas, r16 & r17 may, or do not.  And then, there are instructions that only operate in the address range between 0x0000 and 0x0100.  And the lower I/O PORTS and configuration registers has their peculiarities, as well

 

I programmed the MC6800 and MC68HC11E2 for many years, until they EOL'd (End Of Life) the HC11E2.  Personally, the HC11 was a lot more straight forward and intuitive, I think.  But I guess familiarity is everything.

 

Without a doubt, I'll master assembly for the AVR - you can be sure of that!

You can avoid reality, for a while.  But you can't avoid the consequences of reality! - C.W. Livingston

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

microcarl wrote:
The issue is, do you do that with r0 & r1 or r16 & r17?

Indeed, designate register scratchpads and use the heck out of them.

 

But I'd suggest high registers--the low will be too limiting.  >>Especially<< R0 and R1.

 

microcarl wrote:
Can you elaborate with an example?

https://www.avrfreaks.net/comment...

and other search results on "org vector table int0addr"

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The hc11 didn't give much choice regarding the registers. The AVR (and other risc cpus) have a number of registers so you really need a calling convention so you don't tie yourself up in knots. Since i tended to use the imagecraft compiler, i followed their convention. If you want to interface with avr-gcc, follow their convention. Even if you do not expect to interface with C, just having a convention makes your life easier.

The suggestion to ORG each of your vectors is to force the vector address. Depending on the actual AVR, the address spacing of the vectors may vary, so relying on the instructions to do the correct spacing may cause you problems.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

microcarl wrote:
The issue is, do you do that with r0 & r1

Suggestion.  Don't use R0 or R1 for ANYTHING.

 

If memory serves me well I was having a problem with a program running amok and the problem was I was using R0 and R1.  Apparently the AVR uses these two for certain math instructions I think the AVR uses other registers in the R0 - R15 as well so I always stayed  between R16 and R25.

 

Then I started learning C and all these problems went away..wink  And new problems filled the void.

 

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You might want to have a look ad Fischl / Obdev / usbasp

http://www.fischl.de/usbasp/

https://www.obdev.at/products/vu...

 

USBasp is a well used programming "dongle" which does software USB on a M8.

I've been looking a bit at their code and it seems very well witten. All those arduino wannabe's could learn something from that :)

The critical USB timing is done in assembly. A C compiler simply can not get the timing rewliably accurate enough.

 

Another very wel known cross contamination area between C <--> Assembly if of course RTOS task switching.

FreeRTOS also has a port to AVR.

http://www.freertos.org/a00098.html

 

How can you make a C compiler without working with asm?

Asm won't be obsolete for a long time to come.

Asm is primitive though. It can get you in contact with you inner caveman pretty easily :)

 

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com