====== SUT AVR Assembler Laboratory Node Hardware Reference ======
===== Introduction =====
Each laboratory node is equipped with an Arduino Uno R3 development board, based on the ATmega328P MCU. It also has two extension boards:
* external, analogue and digital communication board,
* user interface board presented on the image {{ref>sutavrlabimage1}}.
There are 10 laboratory nodes. They can be used independently, but for collaboration, nodes are interconnected symmetrically, with GPIOs described in the hardware reference section below.
===== Hardware reference =====
The table {{ref>sutavrlabtable1}} lists all hardware components and details. Note that some elements are accessible, but their use is not supported via the remote lab, e.g., buttons and a buzzer.\\ The node is depicted in the figure {{ref>sutavrlabimage1}} and its interface visual schematic is presented in the figure {{ref>sutavrlabimage1_2}}. The schematic presents only components used in scenarios and accessible via the VREL NextGen environment (controllable and observable via video stream), omitting unused components such as buttons, a buzzer, and a potentiometer.
; Common Anode 7-segment masks (Active LOW)
; Segments: DP,g,f,e,d,c,b,a (Bit 7 -> Bit 0)
; Indices: 0 1 2 3 4 5 6 7 8 9
segment_masks:
.byte 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90
In a common-anode configuration, the active signal to turn on a segment is LOW (0), and to turn it off, it is HIGH (1). The state of a single digit is represented by an 8-bit mask: 7 segments to build the symbol and a DP (decimal point). For example, a digit 7 is represented by bits corresponding to segments "a", "b", and "c" set to 0 (to turn segments "a", "b", and "c" on) and the remaining bits set to 1 (to turn them off), so the corresponding binary value looks as follows: 11111000b, hence the hexadecimal value is 0xF8 (as in the code above). The MSB bit represents DP, and the LSB segment "a". This definition affects how one loads data into the shift register: starting from MSB towards LSB, because of the way the register is built and connected to the segments - refer to the function ''display_digit'' below.
; Pin definitions using direct I/O addresses for ATmega328P
.equ SER_PORT, 0x05 ; PORTB I/O address
.equ SER_PIN, 0
.equ CLK_PORT, 0x0B ; PORTD I/O address
.equ CLK_PIN, 7
.equ LAT_PORT, 0x0B ; PORTD I/O address
.equ LAT_PIN, 4
.global display_digit
; void display_digit(uint8_t pos, uint8_t number);
; r24 = position (0 to 3)
; r22 = number (0 to 9)
display_digit:
push r16
push r17
push r18
push zl
push zh
; 1. Load Segment Mask (for U3) from flash
ldi zl, lo8(segment_masks)
ldi zh, hi8(segment_masks)
add zl, r22 ; Add number index to Z pointer
adc zh, r1 ; r1 is assumed to be 0 (gcc standard)
lpm r16, Z ; r16 now holds segment data
; 2. Load Digit Select Mask (for U2) from flash
ldi zl, lo8(digit_masks)
ldi zh, hi8(digit_masks)
add zl, r24 ; Add position index to Z pointer
adc zh, r1
lpm r17, Z ; r17 now holds digit select data
; 3. Shift out Segment Data (r16) -> Ends up in U3
ldi r18, 8 ; Loop counter for 8 bits
shift_segments:
lsl r16 ; Shift MSB into Carry flag
brcs set_ser_seg ; If Carry is 1, branch to set SER high
cbi SER_PORT, SER_PIN ; Clear SER low
rjmp clock_seg
set_ser_seg:
sbi SER_PORT, SER_PIN ; Set SER high
clock_seg:
; Pulse SRCLK
sbi CLK_PORT, CLK_PIN
cbi CLK_PORT, CLK_PIN
dec r18
brne shift_segments
; 4. Shift out Digit Select Data (r17) -> Ends up in U2
ldi r18, 8 ; Loop counter for 8 bits
shift_digits:
lsl r17 ; Shift MSB into Carry flag
brcs set_ser_dig
cbi SER_PORT, SER_PIN
rjmp clock_dig
set_ser_dig:
sbi SER_PORT, SER_PIN
clock_dig:
; Pulse SRCLK
sbi CLK_PORT, CLK_PIN
cbi CLK_PORT, CLK_PIN
dec r18
brne shift_digits
; 5. Pulse Latch (RCLK) to update the output displays
sbi LAT_PORT, LAT_PIN
cbi LAT_PORT, LAT_PIN
pop zh
pop zl
pop r18
pop r17
pop r16
ret
; ---------------------------------------------------------
; Data stored in Program Memory (Flash)
; ---------------------------------------------------------
.section .progmem.data, "a", @progbits
; Common Anode 7-segment masks (Active LOW)
; Segments: DP,g,f,e,d,c,b,a (Bit 7 -> Bit 0)
; Indices: 0 1 2 3 4 5 6 7 8 9
segment_masks:
.byte 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90
; Digit select masks (Assuming active high on QA-QD for digits 1-4)
digit_masks:
.byte 0x01, 0x02, 0x04, 0x08
.equ SREG, 0x3F ; Status Register
.equ SPH, 0x3E ; Stack Pointer High
.equ SPL, 0x3D ; Stack Pointer Low
.equ SER_PORT, 0x05 ; PORTB I/O address
.equ PINB, 0x03 ; Input Pins Port B (Toggle Shortcut)
.equ SER_PIN, 0 ; GPIO8
.equ DDRD, 0x0A ; Data Direction Port D
.equ DDRB, 0x04 ; Data Direction Port B
.equ CLK_PORT, 0x0B ; PORTD I/O address
.equ CLK_PIN, 7 ; GPIO7
.equ LAT_PORT, 0x0B ; PORTD I/O address
.equ LAT_PIN, 4 ; GPIO4
.equ RAMEND, 0x08FF
.global display_digit
; ---------------------------------------------------------
; Data stored in Program Memory (Flash)
; ---------------------------------------------------------
.section .text
.org 0x0000
rjmp RESET
RESET:
; Prepare stack
ldi r16, hi8(RAMEND)
out SPH, r16
ldi r16, lo8(RAMEND)
out SPL, r16
; Initialise display control outputs
sbi DDRB, SER_PIN ; Set PB0 as output
sbi DDRD, CLK_PIN ; Set PD7 as output
sbi DDRD, LAT_PIN ; Set PD4 as output
clr r25
clr r23
; --- Main Loop, displays in sequence 1->9->7->5 ---
LOOP:
ldi r24,0
ldi r22,1
call display_digit ; Display 1
ldi r24,1
ldi r22,9
call display_digit ; Display 9
ldi r24,2
ldi r22,7
call display_digit ; Display 7
ldi r24,3
ldi r22,5
call display_digit ; Display 5
rjmp LOOP
; void display_digit(uint8_t pos, uint8_t number);
; r24 = position (0 to 3)
; r22 = number (0 to 9)
.... here comes the body of the display_digit function
In the function above, we used fixed (constant) digits to display. A common scenario, however, is when the number is stored in some register or in a memory variable.
** Convert number to digits: function definition **\\
To display a number on this kind of display, you need to convert it into an array of bytes, each representing a digit. A function below does the trick.
; void convert_to_digits(uint16_t value, uint8_t* array);
; Inputs:
; r25:r24 = Value to convert (up to 9999)
; r23:r22 = Pointer to SRAM array (4 bytes long)
convert_to_digits:
; Save registers we are about to use
push r26
push r27
push r18
push r19
push r20
; Move the SRAM pointer from r23:r22 into the X pointer (r27:r26)
movw r26, r22
; ---------------------------------------------------
; 1. Thousands Digit (Subtract 1000 = 0x03E8)
; ---------------------------------------------------
clr r18 ; Clear digit counter
ldi r19, 0x03 ; High byte of 1000
ldi r20, 0xE8 ; Low byte of 1000
loop_1000:
cp r24, r20 ; Compare value low byte with 1000 low byte
cpc r25, r19 ; Compare value high byte with 1000 high byte
brlo done_1000 ; If value < 1000, branch out
sub r24, r20 ; Subtract 1000 low byte
sbc r25, r19 ; Subtract 1000 high byte (with carry)
inc r18 ; Increment thousands digit
rjmp loop_1000
done_1000:
st X+, r18 ; Store thousands digit in array[0] and increment X
; ---------------------------------------------------
; 2. Hundreds Digit (Subtract 100 = 0x0064)
; ---------------------------------------------------
clr r18 ; Reset digit counter
ldi r19, 0x00 ; High byte of 100
ldi r20, 0x64 ; Low byte of 100
loop_100:
cp r24, r20
cpc r25, r19
brlo done_100
sub r24, r20
sbc r25, r19
inc r18
rjmp loop_100
done_100:
st X+, r18 ; Store hundreds digit in array[1] and increment X
; ---------------------------------------------------
; 3. Tens Digit (Subtract 10 = 0x000A)
; ---------------------------------------------------
clr r18 ; Reset digit counter
ldi r19, 0x00 ; High byte of 10
ldi r20, 0x0A ; Low byte of 10
loop_10:
cp r24, r20
cpc r25, r19
brlo done_10
sub r24, r20
sbc r25, r19
inc r18
rjmp loop_10
done_10:
st X+, r18 ; Store tens digit in array[2] and increment X
; ---------------------------------------------------
; 4. Ones Digit (The Remainder)
; ---------------------------------------------------
; Whatever is left in r24 is the ones digit (0-9)
st X, r24 ; Store ones digit in array[3] (no need to increment X)
; Restore registers and return
pop r20
pop r19
pop r18
pop r27
pop r26
ret
Note, this function operates on a buffer located in the memory, which can be declared, e.g. as follows:
.section .bss ; .bss is for uninitialized variables in SRAM
; Reserve 4 bytes in SRAM to hold the 4 converted digits
display_array:
.space 4
===== Communication =====
Devices (laboratory nodes) are interconnected in pairs, so it is possible to work in groups and implement scenarios involving more than one device:
* node 1 with node 2,
* node 3 with node 4,
* node 5 with node 6,
* node 7 with node 8,
* node 9 with node 10.
Interconnections are symmetrical, so that device 1 can send data to device 2 and vice versa (similar to serial communication). Note that analogue inputs are also involved in the interconnection interface.
See image {{ref>sutavrlabimage2}} for details.
...
sbi LAT_PORT, LAT_PIN
cbi LAT_PORT, LAT_PIN
...
The figures {{ref>arduinounodigitoscilloscope1}} and {{ref>arduinounodigitoscilloscope2}} present the ''LAT_PIN'' signal, called periodically during the display of the consecutive digits (they represent the same signal but differ by the oscilloscope time base for better observation).\\
''SBI'' causes the signal to rise, while ''CBI'' to fall. Thus, the HIGH time is the exact time during which the CBI instruction executes. It takes about 120-130ns.\\
The Arduino Uno operates at 16 MHz, so each cycle is 1/16000000 s, which is about 63 ns. According to the documentation, ''CBI'' takes 2 cycles, which is ~126ns.