From 9a6eac73b344730c90b2ad955c0cd6bc046bcf0a Mon Sep 17 00:00:00 2001
From: Markus Hitter <mah@jump-ing.de>
Date: Fri, 15 Nov 2013 15:44:57 +0100
Subject: Add yet another example, simple_serial.

This example is extremely simple, it does nothing fancy, breath taking
or showing advanced coding skills. It also depends on neither TCL nor
Python. It's purpose is to exercise the UART serial port and to show
how to make this port available in a simulation _without_ altering the
binary.

The code is tested on real hardware already and works just as expected.
---
 configure.ac                       |   1 +
 examples/Makefile.am               |   2 +-
 examples/simple_serial/.gitignore  |   3 +
 examples/simple_serial/Makefile.am |  42 ++++++++
 examples/simple_serial/README      |  48 +++++++++
 examples/simple_serial/main.c      |  56 +++++++++++
 examples/simple_serial/serial.c    | 193 +++++++++++++++++++++++++++++++++++++
 examples/simple_serial/serial.h    |  47 +++++++++
 8 files changed, 391 insertions(+), 1 deletion(-)
 create mode 100644 examples/simple_serial/.gitignore
 create mode 100644 examples/simple_serial/Makefile.am
 create mode 100644 examples/simple_serial/README
 create mode 100644 examples/simple_serial/main.c
 create mode 100644 examples/simple_serial/serial.c
 create mode 100644 examples/simple_serial/serial.h

diff --git a/configure.ac b/configure.ac
index 4f880af..03950c6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -215,6 +215,7 @@ AC_CONFIG_FILES([
   examples/atmega48/Makefile examples/atmega128_timer/Makefile
   examples/atmel_key/Makefile examples/feedback/Makefile examples/simple_ex1/Makefile
   examples/spi/Makefile examples/stdiodemo/Makefile examples/python/Makefile
+  examples/simple_serial/Makefile
 ])
 
 ## Certain files should only be generated if Tcl is available and enabled
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 556cb18..f98a40c 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -5,7 +5,7 @@
 
 examplesdir = $(docdir)/examples
 
-SUBDIRS = simple_ex1
+SUBDIRS = simple_ex1 simple_serial
 if USE_TCL
 SUBDIRS += anacomp atmega48 spi stdiodemo feedback atmega128_timer atmel_key
 endif
diff --git a/examples/simple_serial/.gitignore b/examples/simple_serial/.gitignore
new file mode 100644
index 0000000..fde070e
--- /dev/null
+++ b/examples/simple_serial/.gitignore
@@ -0,0 +1,3 @@
+*.elf
+*.hex
+
diff --git a/examples/simple_serial/Makefile.am b/examples/simple_serial/Makefile.am
new file mode 100644
index 0000000..bebf7a7
--- /dev/null
+++ b/examples/simple_serial/Makefile.am
@@ -0,0 +1,42 @@
+#
+#  $Id$
+#
+
+examplesdir = $(docdir)/examples/simple_serial
+
+EXAMPLE = simple_serial
+
+AVR_GCC = @AVR_GCC@
+AVR_CPU = atmega644
+AVR_FREQUENCY = 20000000
+AVR_CFLAGS = -g -O2 -mmcu=$(AVR_CPU) -DF_CPU=$(AVR_FREQUENCY)
+AVR_OBJS = main.o serial.o
+SIMULAVR_ARGS = -d $(AVR_CPU) -f $(EXAMPLE).elf
+
+EXTRA_DIST = main.c serial.c serial.h
+
+examples_DATA = $(EXAMPLE).hex $(EXAMPLE).elf $(EXTRA_DIST) README
+
+CLEANFILES = $(EXAMPLE).hex $(EXAMPLE).elf $(AVR_OBJS)
+
+
+do: $(EXAMPLE).elf
+	../../src/simulavr $(SIMULAVR_ARGS)
+
+program: $(EXAMPLE).hex
+	avrdude -c stk500v2 -b 115200 -p $(AVR_CPU) -P $(SERIAL_AVR) \
+	-U flash:w:$<
+
+
+$(EXAMPLE).hex: $(EXAMPLE).elf
+	avr-objcopy -j .text -j .data -O ihex -R .eeprom -R .fuse -R .lock $< $@
+
+$(EXAMPLE).elf: $(AVR_OBJS)
+	$(AVR_GCC) $(AVR_CFLAGS) -o $@ $^
+
+main.o: $(srcdir)/main.c
+	$(AVR_GCC) $(AVR_CFLAGS) -c -o $@ $<
+
+serial.o: $(srcdir)/serial.c  $(srcdir)/serial.h
+	$(AVR_GCC) $(AVR_CFLAGS) -c -o $@ $<
+
diff --git a/examples/simple_serial/README b/examples/simple_serial/README
new file mode 100644
index 0000000..9456914
--- /dev/null
+++ b/examples/simple_serial/README
@@ -0,0 +1,48 @@
+#
+#  $Id$
+#
+#
+# Copyright (C) 2013 Markus Hitter <mah@jump-ing.de>
+
+
+This example illustrates how one can communicate with the simulated AVR
+using its USART. The other end of the communications line is stdin/stdout,
+so you can simply interact with the simulated AVR by typing into the
+terminal you used to start it with.
+
+The firmware provided can be uploaded to real hardware as-is and should do
+just the same when accessed through a serial terminal. This is an important
+point, because in main.c you can see how few tweaks to the original code
+are required to allow interacting with a simulated run. All of these tweaks
+don't alter the binary actually executed at all.
+
+Building and running the example:
+
+ - Have SimulAVR built, default configuration options are fine.
+
+ - Go to examples/simple_serial (here).
+
+ - Build the example:
+
+      make
+
+ - Run the example:
+
+      make do
+
+ - Upload the example to and run it on real hardware (this isn't exactly
+   flexible, yet, it might require some tweaks to the Makefile to match
+   /your/ hardware):
+
+      export SERIAL_AVR=/dev/<your device here>
+      make program
+      gtkterm -p $SERIAL_AVR -s 19200
+
+Interaction:
+
+Well, extremely simple. It's a code example, after all. Type characters and
+the characters will be sent back with some extra text. That's all, I hope
+this fuels your imagination.
+
+
+
diff --git a/examples/simple_serial/main.c b/examples/simple_serial/main.c
new file mode 100644
index 0000000..4b3c092
--- /dev/null
+++ b/examples/simple_serial/main.c
@@ -0,0 +1,56 @@
+ /*
+ ****************************************************************************
+ *
+ * simple_serial - A demo for the SimulAVR simulator.
+ * Copyright (C) 2013 Markus Hitter <mah@jump-ing.de>
+ * 
+ * This program is free software; 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 program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ ****************************************************************************
+ *
+ *  $Id$
+ *
+ * Super trivial example exercising the UART serial communications line.
+ *
+ * It's purpose is to show how SimulAVR can redirect serial communications
+ * in a way useful for running in a simulator, while requiring NO code
+ * modifications which would change it's behaviour compared to running
+ * on real hardware or disallowing to run the very same compiled binary
+ * on that hardware. The compiled binary should work in the simulator just
+ * as fine as on hardware.
+ */
+
+#include "serial.h"
+
+#include <avr/interrupt.h>
+
+
+// This is all we need:
+int main (void) {
+
+  serial_init();
+  sei();
+
+  serial_writestr_P(PSTR("Hello, world!\n\nNow, please type:\n"));
+
+  for (;;) {
+    if (serial_rxchars() != 0) {
+      uint8_t c = serial_popchar();
+      serial_writestr_P(PSTR("received: <"));
+      serial_writechar(c);
+      serial_writestr_P(PSTR(">\n"));
+    }
+  }
+}
diff --git a/examples/simple_serial/serial.c b/examples/simple_serial/serial.c
new file mode 100644
index 0000000..0c9724c
--- /dev/null
+++ b/examples/simple_serial/serial.c
@@ -0,0 +1,193 @@
+/*
+ *  $Id$
+ *
+ * Serial subsystem
+ *
+ * Copyright (C) 2009 Michael Moon <triffid.hunter@gmail.com>
+ *               2010 Markus Hitter <mah@jump-ing.de>
+ *
+ * This is a pretty lean, fast and powerful serial subsystem, taken from
+ * RepRaps' Teacup firmware: https://github.com/Traumflug/Teacup_Firmware .
+ * For even more simplification, support for XON/XOFF flow control was stripped.
+ *
+ * The system uses ringbuffers for both transmit and receive, and
+ * intelligently decides whether to wait or drop transmitted characters if
+ * the buffer is full. It's tested on many versions of the ATmega.
+ */
+#include "serial.h"
+
+#include <avr/interrupt.h>
+
+// Baud rate.
+#define BAUD 19200
+
+// Size of TX and RX buffers. MUST be a power of 2 and 256 or less.
+#define BUFSIZE 64
+
+// rx buffer head pointer. Points to next available space.
+volatile uint8_t rxhead = 0;
+// rx buffer tail pointer. Points to last character in buffer.
+volatile uint8_t rxtail = 0;
+// rx buffer
+volatile uint8_t rxbuf[BUFSIZE];
+
+// tx buffer head pointer. Points to next available space.
+volatile uint8_t txhead = 0;
+// tx buffer tail pointer. Points to last character in buffer.
+volatile uint8_t txtail = 0;
+// tx buffer
+volatile uint8_t txbuf[BUFSIZE];
+
+// check if we can read from this buffer
+#define buf_canread(buffer) ((buffer ## head - buffer ## tail) & (BUFSIZE - 1))
+// read from buffer
+#define buf_pop(buffer, data) do { data = buffer ## buf[buffer ## tail]; buffer ## tail = (buffer ## tail + 1) & (BUFSIZE - 1); } while (0)
+
+// check if we can write to this buffer
+#define buf_canwrite(buffer) ((buffer ## tail - buffer ## head - 1) & (BUFSIZE - 1))
+// write to buffer
+#define buf_push(buffer, data) do { buffer ## buf[buffer ## head] = data; buffer ## head = (buffer ## head + 1) & (BUFSIZE - 1); } while (0)
+
+/*
+ * ringbuffer logic:
+ * head = written data pointer
+ * tail = read data pointer
+ *
+ * when head == tail, buffer is empty
+ * when head + 1 == tail, buffer is full
+ * thus, number of available spaces in buffer is (tail - head) & bufsize
+ *
+ * can write:
+ * (tail - head - 1) & (BUFSIZE - 1)
+ *
+ * write to buffer:
+ * buf[head++] = data; head &= (BUFSIZE - 1);
+ *
+ * can read:
+ * (head - tail) & (BUFSIZE - 1)
+ *
+ * read from buffer:
+ * data = buf[tail++]; tail &= (BUFSIZE - 1);
+ */
+
+// initialise serial subsystem
+void serial_init() {
+  // set up baud generator and interrupts, clear buffers
+
+  #if BAUD > 38401
+    UCSR0A = MASK(U2X0);
+    UBRR0 = (((F_CPU / 8) / BAUD) - 0.5);
+  #else
+    UCSR0A = 0;
+    UBRR0 = (((F_CPU / 16) / BAUD) - 0.5);
+  #endif
+
+  UCSR0B = MASK(RXEN0) | MASK(TXEN0);
+  UCSR0C = MASK(UCSZ01) | MASK(UCSZ00);
+
+  UCSR0B |= MASK(RXCIE0) | MASK(UDRIE0);
+}
+
+// receive interrupt
+// Using the pragma inside the function is incompatible with Arduinos' gcc.
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#ifdef  USART_RX_vect
+ISR(USART_RX_vect) {
+#else
+ISR(USART0_RX_vect) {
+#endif
+  // we have received a character, stuff it in the rx buffer
+  // if we can, or drop it if we can't
+  if (buf_canwrite(rx))
+    buf_push(rx, UDR0);
+  else {
+    // Not reading the character makes the interrupt logic to swamp us with
+    // retries, so better read it and throw it away.
+    // #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+    uint8_t trash;
+    // #pragma GCC diagnostic pop
+
+    trash = UDR0;
+  }
+}
+#pragma GCC diagnostic pop
+
+// transmit buffer ready interrupt
+#ifdef  USART_UDRE_vect
+ISR(USART_UDRE_vect) {
+#else
+ISR(USART0_UDRE_vect) {
+#endif
+  // provide the next character to transmit if we can,
+  // otherwise disable this interrupt
+  if (buf_canread(tx))
+    buf_pop(tx, UDR0);
+  else
+    UCSR0B &= ~MASK(UDRIE0);
+}
+
+// check how many characters can be read
+uint8_t serial_rxchars() {
+  return buf_canread(rx);
+}
+
+// read one character
+uint8_t serial_popchar() {
+  uint8_t c = 0;
+
+  // it's imperative that we check, because if the buffer is empty
+  // and we pop, we'll go through the whole buffer again
+  if (buf_canread(rx))
+    buf_pop(rx, c);
+
+  return c;
+}
+
+// send one character
+void serial_writechar(uint8_t data) {
+  // check if interrupts are enabled
+  if (SREG & MASK(SREG_I)) {
+    // if they are, we should be ok to block since the
+    // tx buffer is emptied from an interrupt
+    for ( ; buf_canwrite(tx) == 0; );
+    buf_push(tx, data);
+  }
+  else {
+    // interrupts are disabled- maybe we're in one?
+    // anyway, instead of blocking, only write if we have room
+    if (buf_canwrite(tx))
+      buf_push(tx, data);
+  }
+  // enable TX interrupt so we can send this character
+  UCSR0B |= MASK(UDRIE0);
+}
+
+// send a string
+void serial_writestr(uint8_t *data) {
+  uint8_t i = 0, r;
+
+  // Yes, this is *supposed* to be assignment rather than comparison, so we
+  // break when r is assigned zero.
+  while ((r = data[i++]))
+    serial_writechar(r);
+}
+
+/*
+ * Write from FLASH
+ *
+ * Extensions to output flash memory pointers. This prevents the data to
+ * become part of the .data segment instead of the .code segment. That means
+ * less memory is consumed for multi-character writes.
+ *
+ * For single character writes (i.e. '\n' instead of "\n"), using
+ * serial_writechar() directly is the better choice.
+*/
+void serial_writestr_P(PGM_P data) {
+  uint8_t r, i = 0;
+
+  // Yes, this is *supposed* to be assignment rather than comparison, so we
+  // break when r is assigned zero.
+  while ((r = pgm_read_byte(&data[i++])))
+    serial_writechar(r);
+}
+
diff --git a/examples/simple_serial/serial.h b/examples/simple_serial/serial.h
new file mode 100644
index 0000000..0b980e5
--- /dev/null
+++ b/examples/simple_serial/serial.h
@@ -0,0 +1,47 @@
+/*
+ *  $Id$
+ *
+ * Serial subsystem
+ *
+ * Copyright (C) 2009 Michael Moon <triffid.hunter@gmail.com>
+ *               2010 Markus Hitter <mah@jump-ing.de>
+ *
+ * This is a pretty lean, fast and powerful serial subsystem, taken from
+ * RepRaps' Teacup firmware: https://github.com/Traumflug/Teacup_Firmware .
+ * For even more simplification, support for XON/XOFF flow control was stripped.
+ *
+ * The system uses ringbuffers for both transmit and receive, and
+ * intelligently decides whether to wait or drop transmitted characters if
+ * the buffer is full. It's tested on many versions of the ATmega.
+ */
+
+#ifndef _SERIAL_H
+#define _SERIAL_H
+
+#include <stdint.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+
+#define MASK(PIN) (1 << PIN)
+
+// initialise serial subsystem
+void serial_init(void);
+
+// return number of characters in the receive buffer,
+// and number of spaces in the send buffer
+uint8_t serial_rxchars(void);
+
+// read one character
+uint8_t serial_popchar(void);
+
+// send one character
+void serial_writechar(uint8_t data);
+
+// read/write many characters
+void serial_writestr(uint8_t *data);
+
+// write from flash
+void serial_writestr_P(PGM_P data);
+
+#endif  /* _SERIAL_H */
+
-- 
1.8.3.2

