Experiment 9 - ATmega328 Selection and Branching

In this experiment, we continue to explore the ATmega328 using the basic knowledge we acquired working with the Intel 8080.  Follow the procedure in Experiment 8 (see here) for setting up an ATMEL Studio project called "Experiment_9".  Use the "main.asm file here.

Experiment 9-0 - Input/Output Capability with the ATmega328

The Intel 8080 has no internal digital I/O ports; all are external, usually on separate "parallel I/O" boards that communicate with the processor board via a buss like the S-100.  The ATmega328, on the other hand, has three internal digital I/O ports designated "PORTB", "PORTC", and "PORTD".  PORTB and PORTD are 8-bit; PORTC is 7-bit.  Individual bits of these ports can function as simple digital I/O or, with appropriate configuration, perform functions associated with ATmega328's internal peripherals (counter/timers, serial I/O, analog to digital converters, etc.). 

Intel 8080 S-100 peripheral boards are generally hardware configured using wire jumpers.  The ATmega328's I/O peripherals are software configured using I/O registers.  For instance, to use ATmega328 ports for digital I/O, we output configuration data to DDRn, the data direction I/O register where n is A, B, or C.  Recall there are 64 I/O registers numbered 0 to 63.  DDRB is I/O register 4, DDRC is I/O register 7, and DDRD is I/O register 10.  For convenience, the ATmega328 assembler keeps track of I/O register numbers so we can reference them with mnemonics like "DDRB".

One nice feature of the ATmega328 when using it for Digital I/O is that any bit on any port can be configured as input or output.  Setting a bit in a DDRn register makes it an output; clearing it makes it an input.  For our code testing, we make all bits of ports B and C input by clearing all bits of the DDRB and DDRC I/O registers.  See the lines highlighted in yellow in the I/O configuration code below.  The ATmega328 has configurable pull-up resistors that can be activated by setting bits on PORTB and PORTC I/O registers.  See the lines highlighted in blue below.  Lastly, to use PORTD to drive the Data LEDs, we set all bits of DDRD.  See the lines highlighted in green below.   

    ;  
   ;   I/O Configuration Code
   ; 
      ldi         r16,0b00000000        ;set all bits of port b to input
     out       ddrb,r16
     ldi         r16,0b11111111        ;turn on pull-up resistors on all bits of PORTB
     out       PORTB,r16
     ldi        r16,0b00000000         ;set all bits of port c to input
     out       ddrc,r16
     ldi        r16,0b11111111        ;turn on pull-up resistors on all bits of port c
     out       PORTC,r16
     ldi        r16,0b11111111       ;set all bits of port d to output       
    out       ddrd,r16

Once configured, we use the IN and OUT instructions to perform input and output on the ATmega328's external pins.  While the ATmega328's IN and OUT instructions are similar to the Intel 8080, there are a couple of differences: (1) The Intel 8080 transfers data to and from the A register only while the ATmega328 can utilize any register R0 to R31 and (2) The Intel 8080 can address up to 256 ports (0 to 255) while the ATmega328 IN and OUT instructions up to 64 ( 0 to 63).  Here are the specifics:

Basic Operation Example Intel 8080 ATmega328
Input from input port to a register IN p where p = 0 to 225
Data value on port "p" transferred to register A
IN Rn, PINx where n = 0 to 31 & x = a, b, or c
Data value on port "x" transferred to register "n"
Output from a register to output port OUT p where p = 0 to 225
Data value in register A transferred to port "p"
OUT PORTx, Rn where n = 0 to 31 & x = a, b, or c
Data value in register "n" transferred to port "x"

Try it!

Experiment 9-4 Example 1.  Code a basic I/O routine that inputs from the Sense Switches and outputs to the Data LEDs.

The ATmega328 code in this example is essentially the same as the Intel 8080 except that we must specify a destination register, in this case r16.  Also note that the Redboard sense switches connect to PORT B bits 0 to 5, so this direct I/O test only affects these bits. See the code comments for more specifics.

;
; Experiment 4 Example 1 - Basic I/O
;
; Using IN and OUT instructions
;
; Set Sense Switches to 0b00xxxxxx (any one or more x's set and all others closed)
;
; Data LEDs display complement pattern
;
; Note: (1) Bits 0 to 5 of PORT B connect directly to senses switches 1 to 6. Sense switches 7 and 8 are connected
; to bits 1 and 2 of PORTC. (2) When inputting sense switch PORTB directly, "up" produces cleared (0) bit while "down" a
; set (1) bit. (3) While programming flash memory, all sense switches must be "down"; i.e., open.
;
exmp4_1:
        in               r16,pinb               ;get lower six bits of Sense Switches on port b
        out             portd,r16             ;update LEDs on port d
        rjmp           exmp4_1             ;repeat
;

Change the Target Jump line to "exmp4_1".  Build the solution and program the ATmega328 flash memory to test the program.

Note:  As mentioned previously, the sense switches are not the same as on the Altair simulation: up is closed and produces a zero, not a one.  The decision to arrange the DIP switch this way was made to avoid a flash programming issues when the DIP switches were in their "normal" down position. 

Change the "in" statement to reference "pinc".  Redo the example and see that sense switches 7 and 8 affect the lsft most LEDs as they are connected to bits 0 and 1 of PORTC.  Examine the "ssinp" subroutine and see that these two bits are rotated into bits 6 and 7 then ORed with the lower six bits from PORTB to form the final sense switch value in register r16. 

It should be clear now why we had to use a subroutines to make our the Redboard sense switches function like the Altair.

Experiment 9-4 Selection and Branching

 For the Intel 8080, a program can "jump" conditionally or unconditionally to any address in the Intel 8080's 64K address space.  Intel 8080 "jump" instructions require three bytes of 8-bit program memory: one for the instruction and two for the 16 bit address. 

Likewise, the ATmega328 has an unconditional jump instruction "JMP" that can reach anywhere in its 32K program address space.  "JMP" requires two 16-bit words of program memory: one for the instruction and one for the 16 bit address.  The designers of the ATmega328 recognized that conditional jumps were frequently to "near by" addresses, not requiring a full 16-bit address.  With this in mind, they developed a collection of "branch" instructions that perform a relative jump to the address PC + 1 + k where -64 < k < 63.  Using the assembler, we simply designate the label and the assembler calculates k.  If we try to branch beyond the limits of k, an assembly error is generated. 

The first two tables below compare the Intel 8080 and ATmega328 jump instructions instructions.  The remaining tables describe additional branching instructions available only with the ATmega328.

Unconditional Branching
8080 JUMP
Instruction
ATmega328
Instruction
ATmega328
Status Bit
Comment
       
JMP JMP K   Unconditional Jump to address K where 0 ≤ K< 4M* -> PC (Requires 2 16-bit words)
N.A. RJMP k   Unconditional Relative jump to an address within PC - 2K +1 and PC + 2K (Requires 1 16-bit word)
PCHL IJMP   Indirect Unconditional Jump to address in 16-bit Z register (R31:R30) -> PC **  (Requires 1 16-bit word)
 

Conditional Branching - Unsigned Numbers
8080 JUMP
Instruction
ATmega328
Instruction
ATmega328
Status Bit
Comment
       
JNZ BRNE k Z = 0 Branch if not equal (signed or unsigned)  If the instruction is executed immediately after any of the instructions CP, CPI, SUB, or SUBI, the branch will occur if and only if, the unsigned or signed binary number represented in Rd was not equal to the unsigned or signed binary number represented in Rr.
JZ BREQ k Z = 1 Branch if equal (signed or unsigned)   If the instruction is executed immediately after any of the instructions CP, CPI, SUB, or SUBI, the branch will occur if and only if the unsigned or signed binary number represented in Rd was equal to the unsigned or signed binary number represented in Rr.
JNC BRSH k C = 0 Branch if same or higher  (unsigned)  If the instruction is executed immediately after execution of any of the instructions CP, CPI, SUB, or SUBI, the branch will occur if and only if, the unsigned binary number represented in Rd was greater than or equal to the unsigned binary number represented in Rr.
JC BRLO k C = 1 Branch if lower (unsigned)   If the instruction is executed immediately after any of the instructions CP, CPI, SUB, or SUBI, the branch will occur if and only if, the unsigned binary number represented in Rd was smaller than the unsigned binary number represented in Rr
JPO N.A.    
JPE N.A.    
JP BRPL k N = 0 Branch on plus
JM BRMI k N = 1 Branch on minus

* For the ATmega328, K is limited to the flash memory space 32,767.
** For the ATmega328, Z is limited to the flash memory space 32,767.

Additional ATmega328 Conditional Branching - Signed Numbers
ATmega328
Instruction
ATmega328
Status Bit
Comment
     
BRGE k N eor V = 0 Branch if greater or equal (signed)   If the instruction is executed immediately after any of the instructions CP, CPI, SUB, or SUBI, the branch will occur if and only if the signed binary number represented in Rd was greater than or equal to the signed binary number represented in Rr. ("eor" = Exclusive OR)
BRLT k N eor V = 1) Branch if less than (signed)    If the instruction is executed immediately after any of the instructions CP, CPI, SUB, or SUBI, the branch will occur if and only if, the signed binary number represented in Rd was less than the signed binary number represented in Rr. ("eor" = Exclusive OR)

Note: BRSH and BRLO above work with unsigned binary numbers.  BRGE and BRLT work with signed binary numbers.

Additional ATmega328 Conditional Branching - Based on Status Register Values
ATmega328
Instruction
ATmega328
Status Bit
Comment
     
BRBC b,k   Branch if bit b in status register is cleared b = 0 C, 1 Z, 2 N, 3 V, 4 S, 5 H, 6 T, 7 I
BRBS b,k   Branch if bit b in status register is set b = 0 C, 1 Z, 2 N, 3 V, 4 S, 5 H, 6 T, 7 I
BRCC k C = 0 Branch on carry cleared
BRCS k C = 1 Branch on carry set
BRHC k H = 0 Branch on half-carry cleared
BRHS k H = 1 Branch on half-carry set
BRID k I = 0 Branch on global interrupt disabled
BRIE k I = 1 Branch on global interrupt enabled
BRTC k T = 0 Branch on T cleared
BRTS k T = 1 Branch on T set
BRVC k V = 0 Branch on V cleared
BRVS k V = 1 Branch on V set

Finally, the ATmega328 has four test instructions that skip the next instruction if a certain condition is met.

ATmega328 Conditional Branching - Skip Next Instruction
ATmega328
Instruction
Comment
   
SBIC A,b Skip next instruction if bit b in I/O register A is cleared
SBIS A,b Skip next instruction if bit b in I/O register A is set
SBRC Rr,b Skip next instruction if bit b in working register Rr is cleared
SBRS Rr,b Skip next instruction if bit b in working register Rr is set
CPSE Rd,Rr Skip the next instruction if registers Rd and Rr are equal

Note: When using "Skip Next Instructions" in loops, create the loop with the "next instruction" so that the skip only occurs when the condition is met.  This provides the fastest execution.

Two Way and One Way Selection

Recall the basic flow diagrams for selection. 

TWO-WAY SELECTION
SELECTION-DUAL
ONE-WAY SELECTION
ONE-WAY SELECTION

The ATmega328 selection scheme is basically the same as the Intel 8080 with the former's conditional branching instructions replacing the latter's conditional jump instructions.

Try it!

Experiment 9-4 Example 2. Display a one in the Data LEDs if bit 7 in the sense switches is set or a zero if it is cleared.

The code as implemented below explores two way selection using a logical operation (AND).  As required, the branch and relative jump instructions are "nearby" and within the specified ranges of k.

;
; Experiment 4 Example 2 - Two Way Selection (mask condition)
;
; Setting sense switch bit 7 = 1 displays a one (true) in Data LEDs.  Otherwise, zero (false) is displayed.
;
exmp4_2:
    call          ssinp                         ;get Sense Switches into R16
    andi         r16,0b10000000      ;mask r16 to test bit 7
    brne        exmp4_2_true          ;if r16 bit 7 = 1, branch to true sequence
;
; False sequence
;
    ldi          r16,0                          ;load zero (false) in r16
    rjmp      exmp4_2_exit            ;branch to exit
;
; True sequence
;
    exmp4_2_true:
    ldi          r16,1                          ;load one (true) in r16
;
; Exit
;
exmp4_2_exit:
    call         LEDout                     ;display r16 in Data LEDs
    rjmp       exmp4_2 ;repeat

Change the Target Jump line to "exmp4_2".  Build the solution and program the ATmega328 flash memory to test the program.

Experiment 9-4 Example 3. Like Example 2 above, the code below tests bit 7 of the sense switch only it uses a one way selection approach.  The first code sequence (3a) duplicates the Intel 8080 code closely.  The second (3b) simplifies the code by using the skip instruction (SBIS) that looks directly at the I/O bit (PORTC pin 1) and, if set, skips the next instruction.

;
; Experiment 4 Example 3a - One Way Selection (mask condition)
;
; Sense switch bit 7 = 1 will produce a one (true) in Data LEDs
;
exmp4_3a:
    call            ssinp                            ;get Sense Switches into R16
    andi           r16,0b10000000         ;mask r16 to test bit 7
    ldi              r16,0                           ;preload r16 with zero (false)
    breq          exmp4_3a_exit            ;if r16 bit 7 = 0, branch to exit
;
; True sequence
;
    ldi             r16,1                            ;load one (true) in r16
;
; Exit
;
exmp4_3a_exit:
    call            LEDout                       ;display r16 in Data LEDs
    rjmp          exmp4_3a                    ;repeat

;
; Experiment 4 Example 3b - One Way Selection (mask condition using skip instruction)
;
; Sense switch bit 7 = 1 will produce a one (true) in Data LEDs
;
; Reads I/O port c pin (bit) 1 directly. Recall that Sense Switch 7 up produces a zero on PORTC bit 1
;.
exmp4_3b:
    ldi                 r16,0            ;preload r16 with zero (false)
    sbis pinc,1                         ;if PORTC pin (bit) 1 = 1 (sense switch down), skip next instruction and go to exit
;
; True sequence
;
    ldi                 r16,1             ;load one (true) in r16
;
; Exit
;
exmp4_3b_exit:
    call               LEDout         ;display r16 in Data LEDs
    rjmp             exmp4_3b     ;repeat

Change the Target Jump line to "exmp4_3a".  Build the solution and program the ATmega328 flash memory to test the program. Repeat for "exmp4_3b".

 Arithmetic Branching with the ATmega328

The ATmega328 provides for both unsigned and signed arithmetic selection; the Intel 8080 has only unsigned arithmetic selection.  This is a welcomed addition and provides more convenient handling of arithmetic conditioning with signed numbers.

Try it!

Experiment 4 Example 4.  With the code below, we explore unsigned and signed arithmetic comparison.  In example 4a, input from the Sense Switches is compared to the number 80 using the unsigned branch instruction BRSH (branch on Same or Higher).  We display in the Data LEDs either a one for True or zero for False.  Example 4b, demonstrates that signed branching with BRGE (branch on Greater of Equal) works equally well with signed numbers like -80.  Finally, in Example 4c, we achieve modest simplification by using a one-way selection.

;
; Experiment 4 Example 4a - Two Way Selection (unsigned comparison)
;
; Any sense switch input equal or greater than 120Q will produce a one (true)
;
exmp4_4a:
    call           ssinp                     ;get Sense Switches into R16
    cpi           r16,80                   ;compare r16 to 80 (r16 - 80)
    brsh         exmp4_4a_true     ;if r16 >= 80 branch to true sequence
;
; False sequence
;
    ldi           r16,0                         ;zero (false) -> r16
    rjmp       exmp4_4a_exit         ;branch to exit
;
; True sequence
;
exmp4_4a_true:
    ldi          r16,1                         ;one (true) -> r16
;
; Exit
;
exmp4_4a_exit:
   call         LEDout                       ;display r16 in Data LEDs
   rjmp       exmp4_4a                   ;repeat
;
; Experiment 4 Example 4b - Two Way Selection (signed comparison)
;
; Try these sense switch settings: (1) 000Q = 0 >= -80 LEDs = 1 (True)
; (2) 261Q = -79 >= -80 LEDs = 1 (True)
; (3) 260Q = -80 >= -80 LEDs = 1 (True)
; (4) 257Q = -81 < -80 LEDs = 0 (False)
;
exmp4_4b:
    call           ssinp                     ;get Sense Switches into R16
    cpi            r16,-80                 ;compare r16 to -80 (r16 - (-80))
    brge         exmp4_4b_true     ;if r16 >= -80 branch to true sequence
;
; False sequence
;
    ldi             r16,0                     ;zero (false) -> r16
    rjmp         exmp4_4b_exit      ;branch to exit
;
; True sequence
;
exmp4_4b_true:
    ldi            r16,1                     ;one (true) -> r16
;
; Exit
;
exmp4_4b_exit:
    call          LEDout                 ;display r16 in Data LEDs
    rjmp        exmp4_4b             ;repeat
;
; Experiment 4 Example 4c - One Way Selection (unsigned comparison)
;
; Any sense switch input less than 80 will produce a zero (false)
;
exmp4_4c:
    call          ssinp                     ;get Sense Switches into r16
    cpi           r16,80                  ;compare r16 to 80 (r16 - 80)
    ldi            r16,0                    ; (preload) zero (false) -> r16
    brlo         exmp4_4c_exit     ;if r16 < 80 branch to exit
;
; True sequence
;
    ldi           r16,1                     ;one (true) -> r16
;
; Exit
;
exmp4_4c_exit:
    call         LEDout                   ;display r16 in Data LEDs
    rjmp       exmp4_4c               ;repeat
;

Change the Target Jump line to "exmp4_4a".  Build the solution and program the ATmega328 flash memory to test the program.  Repeat for "exmp4_4b" and "exmp4_4c".

Experiment 9-4 Example 5.  In Example 5 we use arithmetic comparison in a one-way selection to switch values in two registers and display the larger of two.  One value is entered in the Sense Switches (via r16) and the other a constant value 120 (via r17).  Rather than compare immediate, we use the register compare instruction CP.  Register r1 is used as a temporary register for switching r16 and r17.

;
; Experiment 4 Example 5 - One Way Selection (arithmetic comparison - unsigned)
;
; Display the larger of 120 (170Q) and the value in the Sense Switches
;
exmp4_5:
    ldi            r17,120             ;load r17 with 120
    call          ssinp                  ;load r16 with sense switch value
    cp            r17,r16             ;compare r17 to r16 (120 - r16)
    brlo         exmp4_5_exit    ;if r16 < 80 branch to exit
;
; True sequence - Switch registers
;
    mov          r1,r16              ;r16 -> r1 (temporary register)
    mov          r16,r17            ;r17 -> r16
    mov          r17,r1              ;r1 -> r17
;
; Exit
;
exmp4_5_exit:
    call           LEDout            ;display r16 in Data LEDs
    rjmp         exmp4_5         ;repeat

Change the Target Jump line to "exmp4_5".  Build the solution and program the ATmega328 flash memory to test the program.

Experiment 9-4 Example 6.  In Example 6 we convert the lower hex nibble in  the Sense Switches to ASCII and display the latter in the Data LEDs. 

;
; Experiment 4 Example 6 - One Way Selection
;
; Convert the low nibble of the Sense Switches from hex to ASCII
;
exmp4_6:
    call          ssinp                        ;load r16 with sense switch value
    andi         r16, 0x0f                 ;mask off upper nibble
    addi         r16,48                    ;assume value 0 to 9 and add 48 ASCII bias
    cpi           r16,57                    ;if r16 < 57, branch to exit
    brlo         exmp4_6_exit
;
; True sequence
;
    addi         r16,7                     ;add 7 more ASCII bias for values 10 to 15
;
; Exit
;
exmp4_6_exit:
    call         LEDout                    ;display r16 in Data LEDs
                                                  ;0000 - ASCII 0 or 060Q to 1001 - ASCII 9 or 101Q
                                                  ;1010 - ASCII A or 101Q to 1111 - ASCII F or 106Q
    rjmp       exmp4_6                 ;repeat

Change the Target Jump line to "exmp4_6".  Build the solution and program the ATmega328 flash memory to test the program.

Continue to next Experiment - Click Here