;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Name: usart-cli.asm ; Description: Implements a command line interface on the Atmel serial port ; (c) by Thomas Hoehn, October 2007 ; ; N.B.: Source code lines: "egrep -v "^\;|^[ \t]*$" usart-cli.asm |wc -l" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; ; include files ; ;;;;;;;;;;;;;;;;; ; Register and bit definitions for ; ATtiny2313 from tn2313def.inc .ifndef _TN2313DEF_INC_ .include "../inc/tn2313def.avra.inc" .endif ; bcd-convert.inc is included later in the ; helper functions section because code must ; be placed in the right cseg memory location ; ;.ifndef _KONVERT_ASM_ ;.include "../inc/bcd-convert.inc" ;.endif ;;;;;;;;;;;;;;;;;;;; ; Define constants ; ;;;;;;;;;;;;;;;;;;;; ; CPU oscillator frequency .set F_OSC = 8000000 ; Baud rate for USART .set BAUD = 9600 ; Command line buffer len (16 chars) .set INPUT_BUF_LEN = 40 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Internal variables/constants ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; UBRR value for Baud rate .set UBRR_VAL = (F_OSC/(BAUD*16)-1) ; Real Baud rate .set BAUD_REAL = (F_OSC/(16*(UBRR_VAL+1))) ; Error rate = 0.156% (p * 10) for 9600 Baud, fosc=8Mhz, U2X=0 .equ BAUD_ERROR_x10 = (((BAUD_REAL-BAUD)*1000)/BAUD) ; error > 1% ? .if ((BAUD_ERROR_x10>1) || (BAUD_ERROR_x10<-1)) .error "Baud rate error to high!" .endif ; define some ASCII control chars .set ASCII_CR = 0x0d ; Carriage Return .set ASCII_LF = 0x0a ; Line Feed .set ASCII_FF = 0x0c ; Form Feed .set ASCII_BS = 0x08 ; Back space .set ASCII_DEL = 0x7f ; Delete .set ASCII_SPACE = 0x20 ; Space ; escape sequence and control sequence characters ; (see http://www.tfh-berlin.de/~kempfer/skript_c/Kap07.html) ; ; escape sequence introducer (27) .set ESC_SEQ_INTRODUCER = 0x1b ; escape sequence intermediate characters (32 - 47) .set ESC_SEQ_INTERMED_CHARS_LOW = 0x20 .set ESC_SEQ_INTERMED_CHARS_HIGH = 0x2F ; escape sequence final character (48 - 126) .set ESC_SEQ_FINAL_CHAR_LOW = 0x30 .set ESC_SEQ_FINAL_CHAR_HIGH = 0x7e ; control sequence introducer (91) .set CTRL_SEQ_INTRODUCER = 0x5b ; control sequence parameter characters (48 - 63) .set CTRL_SEQ_PARAM_CHARS_LOW = 0x30 .set CTRL_SEQ_PARAM_CHARS_HIGH = 0x3f ; control sequence intermediate characters (32 - 47) .set CTRL_SEQ_INTERMED_CHARS_LOW = 0x20 .set CTRL_SEQ_INTERMED_CHARS_HIGH = 0x2f ; control sequence final characters (64 - 126) .set CTRL_SEQ_FINAL_CHAR_LOW = 0x40 .set CTRL_SEQ_FINAL_CHAR_HIGH = 0x7e ; escape/control sequence state machine .set ESC_STATE_NONE = 0 .set ESC_STATE_DONE = 1 .set ESC_SEQ_ESC_STATE = 2 .set ESC_SEQ_II_STATE = 3 ;.set ESC_SEQ_F_STATE = 4 .set CTRL_SEQ_CSI_STATE = 5 .set CTRL_SEQ_PP_STATE = 6 .set CTRL_SEQ_II_STATE = 7 ;.set CTRL_SEQ_F_STATE = 8 ; escape/control sequence state machine register .def escape_sequence_state = r20 ; Current input/buffer position .def cursor_pos = r21 ; number of commands .set CMD_COUNT = 3 ; commands enumerated .set SYSINFO_CMD_IDX = 0 .set HELP_CMD_IDX = 1 .set LED_CMD_IDX = 2 ;;;;;;;;;;;;;;;;;;;;; ; Macro definitions ; ;;;;;;;;;;;;;;;;;;;;; ; switch LED1 on .MACRO LED1_ON sbi PORTD,PORTD5 .ENDMACRO ; switch LED1 off .MACRO LED1_OFF cbi PORTD,PORTD5 .ENDMACRO ; toggle LED1 status .MACRO LED1_TOGGLE sbi PIND,PORTD5 .ENDMACRO ; switch LED2 on .MACRO LED2_ON sbi PORTD,PORTD6 .ENDMACRO ; switch LED2 off .MACRO LED2_OFF cbi PORTD,PORTD6 .ENDMACRO ; toggle LED2 status .MACRO LED2_TOGGLE sbi PIND,PORTD6 .ENDMACRO ;;;;;;;;;;;;;;;; ; Data segment ; ;;;;;;;;;;;;;;;; .dseg .org SRAM_START ; 0x60 ; command line input buffer CMD_LINE_BUF: .BYTE INPUT_BUF_LEN ; (address) table in RAM to hold pointers to command strings CMD_STR_TABLE: .BYTE (CMD_COUNT*2) ; buffer to hold decimal value for double word hex value ; (max. 65535) and string termination byte 0x0 DW_HEX_DECIMAL_VAL_BUF: .BYTE 6 ; ;;;;;;;;;;;;;;;; ; Code segment ; ;;;;;;;;;;;;;;;; .cseg ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Interrupt vectors table ; ;;;;;;;;;;;;;;;;;;;;;;;;;;; .org 0x0 rjmp MAIN ; External interrupt request 0, INT0 .org INT0addr reti ; External interrupt request 1, INT1 .org INT1addr reti ; Timer/Counter1 capture event, TIMER1 CAPT .org ICP1addr reti ; Timer/Counter1 compare match A, TIMER1 COMPA .org OC1Aaddr reti ; Timer/Counter1 overflow, TIMER1 OVF .org OVF1addr reti ; Timer/Counter0 overflow, TIMER0 OVF .org OVF0addr reti ; USART0 Rx complete, USART0 RX .org URXCaddr rjmp USART_Receive ; USART0 Data register empty, USART0 UDRE .org UDREaddr reti ; USART0 Tx complete, USART0 TX .org UTXCaddr reti ; Analog Comparator, ANALOG COMP .org ACIaddr reti ; Pin change interrupt, PCINT .org PCIaddr reti ; Timer/Counter1 Compare Match B, TIMER1 COMPB .org OC1Baddr reti ; Timer/Counter0 Compare Match A, TIMER0 COMPA .org OC0Aaddr reti ; Timer/Counter0 Compare Match B, TIMER0 COMPB .org OC0Baddr reti ; USI Start condition, USI START .org USI_STARTaddr reti ; USI Overflow .org USI_OVFaddr reti ; EEPROM Ready .org ERDYaddr reti ; Watchdog timer overflow, WDT OVERFLOW .org WDTaddr reti ;;;;;;;;;;;;;;;;;;;;; ; Main rogramm code ; ;;;;;;;;;;;;;;;;;;;;; ; follows directly interrupt vector table .org INT_VECTORS_SIZE MAIN: ; disable all interrupts cli ; set Stack Pointer to RAMEND .if (RAMEND>255) ldi r16,high(RAMEND) out SPH,r16 .endif ldi r16,low(RAMEND) out SPL,r16 ; init leds rcall INIT_LEDS ; init serial port rcall INIT_USART ; register commands rcall REGISTER_CMDS ; print cli info after reboot rcall PRINT_CLI_INFO ; print cli prompt rcall PRINT_PROMPT ; reset cursor position clr cursor_pos,0 ; reset escape/control sequence state machine ldi escape_sequence_state,ESC_STATE_NONE ; enable all interrupts sei ; wait for input on USART LOOP: rjmp LOOP ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Initialization routines ; ;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; init LED1 and LED2 ; INIT_LEDS: ; configure PORTD:5 (LED1) as output sbi DDRD,PORTD5 cbi PORTD,PORTD5 ; configure PORTD:6 (LED2) as output sbi DDRD,PORTD6 cbi PORTD,PORTD6 ret ; init USART ; INIT_USART: push r16 ; set Baud rate in UBRR ldi r16,high(UBRR_VAL) out UBRRH,r16 ldi r16,low(UBRR_VAL) out UBRRL,r16 ; (UCSR - USART control and status register) ; normal divider of baud rate divisor, U2X=0 ; disable multi-processor communication mode clr r16 out UCSRA,r16 ; enable receiver and transmitter and Rx interrupt ldi r16,(1< z clz ret Y_LE_Z: ; x <= y <= z! sez ret ; Function: IS_DIGIT ; Args: character in r16 ; Description: ; Tests if character in is a digit, ; i.e. in character class [0-9] ; Result: sets Z=1 if true and Z=0 if not ; IS_DIGIT: push r17 push r18 ldi r17,0x30 ; '0' ldi r18,0x39 ; '9' rcall TEST_INTERVALL pop r18 pop r17 ret ; Function: IS_CAPITAL_LETTER ; Args: character in r16 ; Description: ; Tests if character is captial letter ; Result: sets Z=1 if true and Z=0 if not ; IS_CAPITAL_LETTER: push r17 push r18 ldi r17,0x41 ; 'A' ldi r18,0x5a ; 'Z' rcall TEST_INTERVALL pop r18 pop r17 ret ; Function: IS_SMALL_LETTER ; Args: character in r16 ; Description: ; Tests if character in r16 is captial letter ; Result: sets Z=1 if true and Z=0 if not ; IS_SMALL_LETTER: push r17 push r18 ldi r17,0x61 ; 'a' ldi r18,0x7a ; 'z' rcall TEST_INTERVALL pop r18 pop r17 ret ; Function: VALIDATE_INPUT_CHAR ; Args: character in r16 ; Description: ; Validate an input character and allow ; character class [a-zA-Z0-9] and space ; Result: sets Z=1 if valid and Z=0 if invalid ; VALIDATE_INPUT_CHAR: ; space? cpi r16,ASCII_SPACE breq RETURN0 ; digit [0-9]? rcall IS_DIGIT breq RETURN0 ; big letter? rcall IS_CAPITAL_LETTER breq RETURN0 ; small letter? rcall IS_SMALL_LETTER breq RETURN0 ; invalid char! Z=0 clz RETURN0: ret ; Function PARSE_ESC_SEQUENCE ; Description: ; Parse the escape and control sequences with a state machine ; in order to filter these multi byte sequences out of the ; character stream on the serial port. We just parse them but ; dont store the byte sequence in a buffer (its not difficult) ; so we cant evaluate it to say which key was pressed for the ; sequence. For an introduction to escape and control sequences ; see: http://www.tfh-berlin.de/~kempfer/skript_c/Kap07.html ; PARSE_ESC_SEQUENCE: cpi escape_sequence_state,ESC_STATE_NONE breq CHECK_ESC_SEQ_INTRODUCER cpi escape_sequence_state,ESC_STATE_DONE breq CHECK_ESC_SEQ_INTRODUCER rjmp CHECK_ESC_SEQ_ESC_STATE CHECK_ESC_SEQ_INTRODUCER: cpi r16,ESC_SEQ_INTRODUCER brne RETURN1 ldi escape_sequence_state,ESC_SEQ_ESC_STATE ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_ESC_SEQ_ESC_STATE: cpi escape_sequence_state,ESC_SEQ_ESC_STATE brne CHECK_ESC_SEQ_II_STATE ldi r17,ESC_SEQ_INTERMED_CHARS_LOW ldi r18,ESC_SEQ_INTERMED_CHARS_HIGH rcall TEST_INTERVALL brne CHECK_CTRL_SEQ_INTRODUCER ldi escape_sequence_state,ESC_SEQ_II_STATE ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_CTRL_SEQ_INTRODUCER: cpi r16,CTRL_SEQ_INTRODUCER ; invalid escape sequence, reset state machine brne CHECK_ESC_SEQ_FINAL_CHAR ldi escape_sequence_state,CTRL_SEQ_CSI_STATE ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_ESC_SEQ_II_STATE: cpi escape_sequence_state,ESC_SEQ_II_STATE brne CHECK_CTRL_SEQ_CSI_STATE ldi r17,ESC_SEQ_INTERMED_CHARS_LOW ldi r18,ESC_SEQ_INTERMED_CHARS_HIGH rcall TEST_INTERVALL brne CHECK_ESC_SEQ_FINAL_CHAR ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_ESC_SEQ_FINAL_CHAR: ldi r17,ESC_SEQ_FINAL_CHAR_LOW ldi r18,ESC_SEQ_FINAL_CHAR_HIGH rcall TEST_INTERVALL brne RESET_ESC_SEQ_STATE ldi escape_sequence_state,ESC_STATE_DONE ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_CTRL_SEQ_CSI_STATE: cpi escape_sequence_state,CTRL_SEQ_CSI_STATE brne CHECK_CTRL_SEQ_PP_STATE ldi r17,CTRL_SEQ_PARAM_CHARS_LOW ldi r18,CTRL_SEQ_PARAM_CHARS_HIGH rcall TEST_INTERVALL brne CHECK_CTRL_SEQ_INTERMED_CHARS ldi escape_sequence_state,CTRL_SEQ_PP_STATE ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_CTRL_SEQ_PP_STATE: cpi escape_sequence_state,CTRL_SEQ_PP_STATE brne CHECK_CTRL_SEQ_II_STATE ldi r17,CTRL_SEQ_PARAM_CHARS_LOW ldi r18,CTRL_SEQ_PARAM_CHARS_HIGH rcall TEST_INTERVALL brne CHECK_CTRL_SEQ_INTERMED_CHARS ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_CTRL_SEQ_INTERMED_CHARS: ldi r17,CTRL_SEQ_INTERMED_CHARS_LOW ldi r18,CTRL_SEQ_INTERMED_CHARS_HIGH rcall TEST_INTERVALL brne CHECK_CTRL_SEQ_FINAL_CHAR ldi escape_sequence_state,CTRL_SEQ_II_STATE ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_CTRL_SEQ_II_STATE: cpi escape_sequence_state,CTRL_SEQ_II_STATE brne RETURN1 ldi r17,CTRL_SEQ_INTERMED_CHARS_LOW ldi r18,CTRL_SEQ_INTERMED_CHARS_HIGH rcall TEST_INTERVALL brne CHECK_CTRL_SEQ_FINAL_CHAR ;st Z+,r16 ; save escape sequence char in buffer ret CHECK_CTRL_SEQ_FINAL_CHAR: ldi r17,CTRL_SEQ_FINAL_CHAR_LOW ldi r18,CTRL_SEQ_FINAL_CHAR_HIGH rcall TEST_INTERVALL brne RESET_ESC_SEQ_STATE ldi escape_sequence_state,ESC_STATE_DONE ;st Z+,r16 ; save escape sequence char in buffer ret RESET_ESC_SEQ_STATE: ldi escape_sequence_state,ESC_STATE_NONE RETURN1: ret ; Function: HANDLE_CHAR ; Args: character in r16 ; Description: ; process an input character on the command line which ; allows minimal line editing capabilities (input, BS, CR). ; If input is finished with CR evaluate and execute the cmd. ; Changes cursor position and input buffer ; HANDLE_CHAR: rcall PARSE_ESC_SEQUENCE cpi escape_sequence_state,ESC_STATE_NONE breq CONT_CR cpi escape_sequence_state,ESC_STATE_DONE brne RETURN2 ; do some action for the escape/control sequence ;rcall PRINT_BYTE_0x_HEX ;rcall EVAL_ESC_SEQUENCE ldi escape_sequence_state,ESC_STATE_NONE ret CONT_CR: ; carriage return? cpi r16,ASCII_CR brne SKIP_CR ; then process input line rcall EVAL_CMD ret SKIP_CR: ; Back space? cpi r16,ASCII_BS brne SKIP_BS ; beginning of command line? cpi cursor_pos,0 breq RETURN2 ; output Backspace dec cursor_pos rcall USART_Transmit ldi r16,ASCII_SPACE rcall USART_Transmit ldi r16,ASCII_BS rcall USART_Transmit ret SKIP_BS: ; input buffer full? cpi cursor_pos,(INPUT_BUF_LEN-1) breq RETURN2 ; validate character rcall VALIDATE_INPUT_CHAR ; invalid? brne RETURN2 ; if not beginning of line save char cpi cursor_pos,0 brne SAVE_CHAR ; space at the beginning of a line? cpi r16,ASCII_SPACE breq RETURN2 ; digit at the beginning of a line? rcall IS_DIGIT breq RETURN2 SAVE_CHAR: ; save input char to buffer push ZH push ZL ldi ZH,high(CMD_LINE_BUF) ldi ZL,low(CMD_LINE_BUF) add ZL,cursor_pos st Z,r16 pop ZL pop ZH inc cursor_pos rcall USART_Transmit RETURN2: ret ; Function: COMPARE_BUFFER_CMD ; Args: command string address in Z ; (and command line buffer address CMD_LINE_BUF) ; Description: compare the (current command) string ; from the command string table with the input buffer ; Result: Set Z=1 if equal, Z=0 if not equal ; ; current char position in input buffer and command string .def current_pos = r17 ; current char from command string (CSEG) .def cmd_char = r18 ; current char from input buffer (DSEG) .def input_char = r19 ; COMPARE_BUFFER_CMD: push r17 push r18 push r19 push ZH push ZL push YH push YL clr current_pos ldi YL,low(CMD_LINE_BUF) ldi YH,high(CMD_LINE_BUF) COMPARE_CHAR: ; load char from command string lpm cmd_char,Z+ ; load char from input buffer ld input_char,Y+ ; command string done? cpi cmd_char,0x0 ; string termination 0x0 brne CONT_CMP ; command string done, end of input ; buffer or space at current position? cp current_pos,cursor_pos breq IS_EQUAL ; end of input buffer not reached, ; space (end of buffer command)? cpi input_char,ASCII_SPACE breq IS_EQUAL ; command string matches with input, ; but input continues with other chars rjmp NOT_EQUAL ; command string not yet compared completely CONT_CMP: ; space in input buffer string? cpi input_char,ASCII_SPACE ; space finishes input buffer command string breq NOT_EQUAL ; command char = buffer char? cp cmd_char,input_char ; command string char and input buffer char differ brne NOT_EQUAL ; increment current char position inc current_pos ; command string char and input buffer char equal cpi current_pos,INPUT_BUF_LEN ; if input buffer not yet done continue comparison brne COMPARE_CHAR IS_EQUAL: sez rjmp RETURN3 NOT_EQUAL: clz RETURN3: pop YL pop YH pop ZL pop ZH pop r19 pop r18 pop r17 ret ; Function: GET_CMD_IDX ; Args: none ; Description: ; Analyse input buffer (up to cursor_pos). If the input line contains a ; valid command return the index of this command to call handling routine. ; A valid command ends at the cursor position or at the 1st space (or at ; the buffer end). ; Result: command index in r16 and Z=0 or Z=1 if not found ; GET_CMD_IDX: push ZH push ZL push XH push XL ; reset command index clr r16 ; load address of command string table in X ldi XH,high(CMD_STR_TABLE) ldi XL,low(CMD_STR_TABLE) NEXT_CMD_STR: ; load address of command string in Z and ; correct programm memory address (see ; PRINT_STRING description for details) ld ZL,X+ lsl ZL ; ADDR<<1 ld ZH,X+ rol ZH ; ADDR<<1 ; compare the current command string with the input buffer rcall COMPARE_BUFFER_CMD ; if command found return breq RETURN4 ; increment command index inc r16 cpi r16,CMD_COUNT brne NEXT_CMD_STR ; command in input not found, set Z=0 (not equal) clz RETURN4: pop XL pop XH pop ZL pop ZH ret ; Function: EVAL_CMD ; Args: none ; Description: ; Process (evaluate) a command, i.e. run appropriate subroutine ; or print an error if the command in the input buffer is unknown. ; EVAL_CMD: ; empty input? cpi cursor_pos,0 breq FINISH_CMD ; print newline to begin output on next line rcall PRINT_NEWLINE ; get command index for input rcall GET_CMD_IDX ; unknown command in input buffer? brne UNKNOWN_CMD ; Z=0 ; valid command, run designated handling routine ; ; sysinfo command ; cpi r16,SYSINFO_CMD_IDX brne HELP_CMD rcall PRINT_SYSINFO rjmp FINISH_CMD ; ; help command ; HELP_CMD: cpi r16,HELP_CMD_IDX brne LED_CMD rcall PRINT_HELP rjmp FINISH_CMD ; ; led command ; LED_CMD: cpi r16,LED_CMD_IDX ; Ups, GET_CMD_IDX returned command index not yet implemented! brne UNKNOWN_CMD rcall EXEC_LED_CMD rjmp FINISH_NO_NEWLINE ; ; unknown command (invalid input) ; UNKNOWN_CMD: ; print error message and new prompt rcall PRINT_UNKNOWN_CMD_ERR ; finish command line FINISH_CMD: rcall PRINT_NEWLINE FINISH_NO_NEWLINE: ; then show prompt again rcall PRINT_PROMPT ; reset cursor position clr cursor_pos ret ; Function: EXEC_LED_CMD ; Description: Execute LED command and toggle LED1. ; TODO: Improve to switch on/off LED1/LED2, dim LED1/LED2 ; EXEC_LED_CMD: LED1_TOGGLE ret ; Function: USART_Transmit ; Registers: none ; Args: char in r16 ; Description: Output character byte on USART ; USART_Transmit: ; Wait for empty transmit buffer sbis UCSRA,UDRE rjmp USART_Transmit ; Put data into buffer, sends the data out UDR,r16 ret ;;;;;;;;;;;;;;;;;;;;;; ; Interrupt routines ; ;;;;;;;;;;;;;;;;;;;;;; ; Function: USART_Receive ; Registers: Z, r16, r17, r18, cursor_pos (r19) ; Args: none ; Description: Interrupt handling routine to process USART input ; USART_Receive: push r16 ; Wait for data to be received sbis UCSRA,RXC rjmp USART_Receive ; Get received data from buffer in r16,UDR ; process character input rcall HANDLE_CHAR pop r16 reti ;;;;;;;;;;;;;;;;;;;; ; string constants ; ;;;;;;;;;;;;;;;;;;;; ; system identification string CLI_INFO_STRING: .DB ASCII_FF, "Atmel Evaluations-Board by Pollin, Version 2.0", ASCII_CR, ASCII_LF, "Atmel AVR ATtiny2313 console, version 0.1", ASCII_CR, ASCII_LF, "written by Thomas Hoehn, Erding, 10/2007", ASCII_CR, ASCII_LF, ASCII_CR, ASCII_LF, 0x0 ; system info SYSINFO_HEADER_STRING: .DB "System info:", ASCII_CR, ASCII_LF, 0x0 SYSINFO_DEV_STRING: .DB "Device: ATtiny2313", ASCII_CR, ASCII_LF, 0x0 SYSINFO_FLASH_STRING: .DB "Flash: ", 0x0 ; PROG_FLASH SYSINFO_BYTE_STRING: .DB " Byte", ASCII_CR, ASCII_LF, 0x0 SYSINFO_SRAM_STRING: .DB "SRAM: ", 0x0 ; SRAM_SIZE SYSINFO_EEPROM_STRING: .DB "EEPROM: ", 0x0 ; EEPROM_SIZE SYSINFO_FOSC_STRING: .DB "f_osc: ", 0x0 ; F_OSC SYSINFO_MHZ_STRING: .DB "Mhz", 0x0 ; help message HELP_MESSAGE: .DB "Available commands: sysinfo, help, led1", 0x0 ; unknown command error UNKNOWN_CMD_ERR_STRING: .DB "Error: unknown command. Type 'help' for available commands.", 0x0 ; prompt string PROMPT_STRING: .DB "cli > ", 0x0 ; command strings INFO_CMD_STR: .DB "sysinfo", 0x0 HELP_CMD_STR: .DB "help", 0x0 LED_CMD_STR: .DB "led1", 0x0 .exit