A Place Where No Dreams Come True...

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