Current Topic: An Assembly Language interrupt driven USART serial communication interface. This is a primitive library using proper return codes with non-blocking functions allowing it to work well even in a multi-tasking environment.
Every Project Needs A Background Command Line Interpreter.
Although this code is suitable for any low-level serial (comm) interface I wrote it primarily to support a light-weight command-line-interpreter background processor. A kind of resident debugger. Being interrupt driven means that it essentially uses 0 (zero) processing time when idle. Which is normally the case. It also uses very little processor resources. One USART channel and two small FIFO buffers (RAM).
What This Example Demonstrates.
There are essentially three parts to this example. An initialization function to set up the USART channel for normal ASYNC communications, 'send' and 'receive' functions which push or pull data from FIFO (ring) buffers and transmit and receive interrupt service routines.
All written in Assembly language and optimized for the AVR processor family.
Since this code shares the FIFO buffers directly between linear and interrupt (non-linear) code it is essential to differentiate which context owns which FIFO pointer. For each FIFO there is an IN pointer (index) and an OUT pointer. Because of the nature of the FIFOs (transmit - receive) the roles are reversed for the two counterparts.
;----------------------------------------------------------------------------- ; file: console.S ; ; Smegduino USART serial interface driver example. ; ; Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com ; ; All Smegware software is free; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2 of the License, or ; (at your option) any later version. ; ; This software is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ;----------------------------------------------------------------------------- ; ; History... ; ; $Source$ ; $Author$ ; $Revision$ ; ; $Log$ ;------------------------------------------------------------------------------ #define __SFR_OFFSET 0 #include <avr/io.h> #include "avrbaud.h" ;----------------------------------------------------------------------------- ; Convenience macros. #define RZERO R1 #define RIN R18 #define ROUT R19 #define RPARAM2L R22 #define RPARAM2H R23 #define RPARAM1L R24 #define RPARAM1H R25 #define RPARAM1 RPARAM1L #define RPARAM2 RPARAM2L ;----------------------------------------------------------------------------- ; USART Control and Status Register-A bit-field definitions. .equ BIT_MPCM, 0 .equ BIT_U2X, 1 .equ BIT_UPE, 2 .equ BIT_DOR, 3 .equ BIT_FE, 4 .equ BIT_UDRE, 5 .equ BIT_TXC, 6 .equ BIT_RXC, 7 .equ MASK_MPCM, 0B00000001 .equ MASK_U2X, 0B00000010 .equ MASK_UPE, 0B00000100 .equ MASK_DOR, 0B00001000 .equ MASK_FE, 0B00010000 .equ MASK_UDRE, 0B00100000 .equ MASK_TXC, 0B01000000 .equ MASK_RXC, 0B10000000 ; USART Control and Status Register-B bit-field definitions. .equ BIT_TXB8, 0 .equ BIT_RCB8, 1 .equ BIT_UCSZ2, 2 .equ BIT_TXEN, 3 .equ BIT_RXEN, 4 .equ BIT_UDRIE, 5 .equ BIT_TXCIE, 6 .equ BIT_RXCIE, 7 .equ MASK_TXB8, 0B00000001 .equ MASK_RCB8, 0B00000010 .equ MASK_UCSZ2, 0B00000100 .equ MASK_TXEN, 0B00001000 .equ MASK_RXEN, 0B00010000 .equ MASK_UDRIE, 0B00100000 .equ MASK_TXCIE, 0B01000000 .equ MASK_RXCIE, 0B10000000 ; USART Control and Status Register-C bit-field definitions. .equ BIT_UCPOL, 0 .equ BIT_UCSZ0, 1 .equ BIT_UCSZ1, 2 .equ BIT_USBS, 3 .equ BIT_UPM0, 4 .equ BIT_UPM1, 5 .equ BIT_UMSEL0, 6 .equ BIT_UMSEL1, 7 .equ MASK_UCPOL, 0B00000001 .equ MASK_UCSZ0, 0B00000010 .equ MASK_UCSZ1, 0B00000100 .equ MASK_USBS, 0B00001000 .equ MASK_UPM0, 0B00010000 .equ MASK_UPM1, 0B00100000 .equ MASK_UMSEL0, 0B01000000 .equ MASK_UMSEL1, 0B10000000 ; USART0 register definitions. .equ REG_UCSR0A, 0XC0 .equ REG_UCSR0B, 0XC1 .equ REG_UCSR0C, 0XC2 .equ REG_UBRR0L, 0XC4 .equ REG_UBRR0H, 0XC5 .equ REG_UDR0, 0XC6 ;------------------------------------------------------------------------------ ; Driver FIFO buffer size - Currently 8-bit so must be between 1 - 255. #define FIFO_SIZE 96 ;------------------------------------------------------------------------------ ; Console receive status. .lcomm rx_in_pnt,1 ; Receive FIFO input pointer - uint16_t. .lcomm rx_out_pnt,1 ; Receive FIFO output pointer - uint16_t. ;------------------------------------------------------------------------------ ; Console receive data - shared by interrupt service routine. .lcomm rx_fifo,FIFO_SIZE ; Receive input buffer - uint8_t[n]. ;------------------------------------------------------------------------------ ; Console transmit status. .lcomm tx_in_pnt,1 ; Transmit FIFO input pointer - uint16_t. .lcomm tx_out_pnt,1 ; Transmit FIFO output pointer - uint16_t. ;------------------------------------------------------------------------------ ; Console transimt data - shared by interrupt service routine. .lcomm tx_fifo,FIFO_SIZE ; Transmit output buffer - uint8_t[n]. ;------------------------------------------------------------------------------ ; BAUD rate lookup table - Translate to BAUD generator divider table in PROM. ; Note: Must be synchronized with avrbaud.h else Uh oh! .text baudrate: .word 832,416,207,138,103,68,51,34,25,16,8 ;------------------------------------------------------------------------------ ; Console initialize. ; From C - void console_init(uint8_t) ; where uint8_t = BAUD rate constant defined in avrbaud.h .global console_init console_init: ; Reset hardware. STS REG_UCSR0B,RZERO ; Disable USART RX, TX and interrupts. LDI ROUT,(MASK_UCSZ1 | MASK_UCSZ0) ; 8 bits. STS REG_UCSR0C,ROUT ; Set - Asynchroneous, No parity, 1 Stop LDI ROUT,MASK_U2X ; BAUD generator divisor. STS REG_UCSR0A,ROUT ; ; Initialize variables (RAM). STS rx_in_pnt,RZERO ; Clear all FIFO index variables. STS rx_out_pnt,RZERO ; STS tx_in_pnt,RZERO ; STS tx_out_pnt,RZERO ; ; Initialize hardware. LDI ZL,lo8(baudrate) ; Baudrate lookup table LDI ZH,hi8(baudrate) ; address. ADD ZL,RPARAM1L ; Point to baud init value. ADC ZH,RZERO ; vector. LPM ROUT,Z+ ; STS REG_UBRR0L,ROUT ; Baudrate low-byte. LPM ROUT,Z ; STS REG_UBRR0H,ROUT ; Baudrate high-byte. ; Enable RX and TX and Interrupts. LDS ROUT,REG_UCSR0B ; ORI ROUT,(MASK_RXEN | MASK_TXEN | MASK_RXCIE) STS REG_UCSR0B,ROUT ; Enable receiver and transmitter. RET ;------------------------------------------------------------------------------ ; Pull uint8_t data from receive FIFO - Returns int16_t -1 on failure. ; From C - int16_t console_read(void) ; This is a non-blocking routine - If return != -1 the return value is uint8_t. .global console_read console_read: SER RPARAM1L ; Default return -1. SER RPARAM1H ; 16-bit. LDS RIN,rx_in_pnt ; Get top of FIFO point. LDS ROUT,rx_out_pnt ; Get output FIFO point. CP RIN,ROUT ; Check available. BREQ .rx_empty ; FIFO empty. LDI ZL,lo8(rx_fifo) ; Get FIFO pointer LDI ZH,hi8(rx_fifo) ; address. ADD ZL,ROUT ; Point to current FIFO slot. ADC ZH,RZERO ; 16-bit. LD RPARAM1L,Z ; Get FIFO data. INC ROUT ; Consumed. CPI ROUT,FIFO_SIZE ; Check for overflow. BRNE .rx_no_ov ; Still linear. CLR ROUT ; Wrap circular buffer. .rx_no_ov: STS rx_out_pnt,ROUT ; Update output index pointer. CLR RPARAM1H ; Return true. .rx_empty: RET ;------------------------------------------------------------------------------ ; USART Receive Interrupt Service Routine. .global USART0_RX_vect USART0_RX_vect: PUSH ZL ; Preserve linear state. IN ZL,SREG ; PUSH ZL ; Preserve Status register. PUSH ZH ; Preserve linear state. PUSH RIN ; PUSH ROUT ; PUSH RZERO ; CLR RZERO ; Force. ; Check FIFO availability. LDS RIN,rx_in_pnt ; Input pointer index (local). LDS ROUT,rx_out_pnt ; Output pointer index (interrupt). LDI ZL,lo8(rx_fifo) ; Get FIFO pointer LDI ZH,hi8(rx_fifo) ; address. ADD ZL,RIN ; Point to next available slot ADC ZH,RZERO ; 16-bit. LDS ROUT,REG_UDR0 ; Pull data from hardware (USART). ST Z,ROUT ; Push data to FIFO. INC RIN ; Point to next slot. CPI RIN,FIFO_SIZE ; Check buffer overflow. BRNE .irx_in_ov ; Still linear. CLR RIN ; Overflow wrap circular buffer. .irx_in_ov: CP RIN,ROUT ; Full if equal. BREQ .irx_full ; FIFO full exit - ERROR - Ignore. STS rx_in_pnt,RIN ; Update input pointer index. .irx_full: ; Restore and return. POP RZERO ; Restore pre-interrupt state. POP ROUT ; POP RIN ; POP ZH ; POP ZL ; OUT SREG,ZL ; Restore status Register. POP ZL ; RETI ; Re-Enter linear processing state. ;------------------------------------------------------------------------------ ; Push uint8_t data to transmit FIFO - Returns int16_t -1 on failure. ; From C - int8_t console_write(uint8_t) ; This is a non-blocking routine - Returns -1 on error otherwise 0. .global console_write console_write: SER RPARAM2L ; Default return -1. LDS RIN,tx_in_pnt ; Input pointer index (local). LDS ROUT,tx_out_pnt ; Output pointer index (interrupt). LDI ZL,lo8(tx_fifo) ; Get FIFO pointer LDI ZH,hi8(tx_fifo) ; address. ADD ZL,RIN ; Point to next available slot ADC ZH,RZERO ; 16-bit. ST Z,RPARAM1L ; Push data to FIFO. INC RIN ; Point to next slot. CPI RIN,FIFO_SIZE ; Check buffer overflow. BRNE .tx_in_ov ; Still linear. CLR RIN ; Overflow wrap circular buffer. .tx_in_ov: CP RIN,ROUT ; Difference determines slot available. BREQ .tx_full ; FIFO full return default (error). STS tx_in_pnt,RIN ; Update input pointer index. ; Enable TX Interrupts. LDS ROUT,REG_UCSR0B ; Get current state. ORI ROUT,(MASK_UDRIE) ; Enable transmit interrupt. STS REG_UCSR0B,ROUT ; Update. CLR RPARAM2L ; Return success. .tx_full: MOV RPARAM1L,RPARAM2L ; Return flag. RET ;------------------------------------------------------------------------------ ; USART Transmitter Data-Register-Empty Interrupt Service Routine. .global USART0_UDRE_vect USART0_UDRE_vect: PUSH ZL ; Preserve linear state. IN ZL,SREG ; PUSH ZL ; Preserve Status register. PUSH ZH ; Preserve linear state. PUSH RIN ; PUSH ROUT ; PUSH RZERO ; CLR RZERO ; Force. ; Check FIFO status. LDS RIN,tx_in_pnt ; Get top of FIFO point. LDS ROUT,tx_out_pnt ; Get output FIFO point. CP RIN,ROUT ; Check available. BREQ .itx_empty ; FIFO empty. LDI ZL,lo8(tx_fifo) ; Get FIFO pointer LDI ZH,hi8(tx_fifo) ; address. ADD ZL,ROUT ; Point to current FIFO slot. ADC ZH,RZERO ; 16-bit. LD RIN,Z ; Get FIFO data. STS REG_UDR0,RIN ; Push data to hardware (USART). INC ROUT ; Consumed. CPI ROUT,FIFO_SIZE ; Check for overflow. BRNE .itx_no_ov ; Still linear. CLR ROUT ; Wrap circular buffer. .itx_no_ov: STS tx_out_pnt,ROUT ; Update output index pointer. JMP .itx_rtn ; Restore and return. ; FIFO empty - Disable transmit interrupts. .itx_empty: LDS RIN,REG_UCSR0B ; Control register. CBR RIN,MASK_UDRIE | MASK_TXCIE ; Clear UDRIE interrupt. STS REG_UCSR0B,RIN ; Disable TX interrupts. ; Restore and return. .itx_rtn: POP RZERO ; Restore pre-interrupt state. POP ROUT ; POP RIN ; POP ZH ; POP ZL ; OUT SREG,ZL ; Restore status Register. POP ZL ; RETI ; Re-Enter linear processing state. ;------------------------------------------------------------------------------ ; USART Transmit-Complete Interrupt Service Register. .global USART0_TX_vect USART0_TX_vect: PUSH ROUT ; Preserve linear state. IN ROUT,SREG ; PUSH ROUT ; Preserve Status register. ; Spurious Interrupt - Disable TX interrupts. LDS ROUT,REG_UCSR0B ; Control register. CBR ROUT,MASK_UDRIE | MASK_TXCIE ; Clear UDRIE interrupt. STS REG_UCSR0B,ROUT ; Disable TX interrupts. ; Restore and return. POP ROUT ; OUT SREG,ROUT ; Restore status Register. POP ROUT ; RETI ; Re-Enter linear processing state. ;------------------------------------------------------------------------------ ; end: console.S
There Are Also Two Interface Header Files.
There is a header file that prototypes the interface so it is understood by the Compiler.
//---------------------------------------------------------------------------- // file:console.h // //---------------------------------------------------------------------------- extern "C" { #include <stdint.h> #include "avrbaud.h" extern void console_init(uint8_t); extern int16_t console_read(void); extern int8_t console_write(uint8_t); } //---------------------------------------------------------------------------- // end:console.h
And an additional header file to define the BAUD rate parameter necessary to initialize the USART interface speed.
//---------------------------------------------------------------------------- // file:avrbaud.h // //---------------------------------------------------------------------------- #define BAUD_2400 0 #define BAUD_4800 2 #define BAUD_9600 4 #define BAUD_14400 6 #define BAUD_19200 8 #define BAUD_28800 10 #define BAUD_38400 12 #define BAUD_57600 14 #define BAUD_76800 16 #define BAUD_115200 18 #define BAUD_230400 20 //---------------------------------------------------------------------------- // end:avrbaud.h
One Possible Use For This Code Would Be...
A Simple command line interface. Using the primitive FIFO interface a command line interpreter would need to acquire a line from a terminal (\n terminated) and pass it to an interpreter. The interpreter would in turn need to send the result back to the terminal.
//----------------------------------------------------------------------------- // file:monitor.ino // // Smegduino Simple Debug Monitor example. // // Copyright (c) 2016 - No Fun Farms A.K.A. www.smegware.com // // All Smegware software is free; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This software is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // //----------------------------------------------------------------------------- // // History... // // $Source$ // $Author$ // $Revision$ // // $Log$ //----------------------------------------------------------------------------- #include <console.h> //----------------------------------------------------------------------------- #define CONSOLE_SIZE 80 static char console[CONSOLE_SIZE + 1]; //----------------------------------------------------------------------------- static void monitor_get_line(char *buffer) { int16_t c; uint8_t i; for(i = 0; i < CONSOLE_SIZE; i++) { while((c = console_read()) == -1) { pause(); } if(c == '\n') { buffer[i] = 0; break; } buffer[i] = c; } buffer[CONSOLE_SIZE - 1] = '\0'; } //----------------------------------------------------------------------------- static void monitor_send(const char *data) { uint16_t i; uint16_t len = strlen(data); for(i = 0; i < len; i++) { while(console_write(data[i]) == -1) { pause(); } } } //----------------------------------------------------------------------------- static void monitor_send_line(const char *data) { monitor_send(data); while(console_write('\n') == -1) { pause(); } } //----------------------------------------------------------------------------- static void monitor_prompt(void) { char prompt[] = ">"; monitor_send(prompt); } //----------------------------------------------------------------------------- void setup(void) { console_init(BAUD_9600); init_tick(); } //----------------------------------------------------------------------------- void loop(void) { pause(); monitor_prompt(); monitor_get_line(console); monitor_send_line(console); interpret((uint8_t*)console); } //----------------------------------------------------------------------------- // end: monitor.ino
13109