Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

simplistic ATmega8-based UART to SPI conversion

39 views
Skip to first unread message

Ivan Shmakov

unread,
Dec 6, 2012, 10:27:13 AM12/6/12
to
Archive-name: avr-uart-to-spi-is
Submitted-by: onei...@gmail.com
Last-modified: 2012-12-02 17:13:53.621238550+00:00
Copyright-Notice: Both the README and the code are under the
CC0 Public Domain Dedication.


Description

The code below makes a simplistic UART-driven SPI master
interface out of an ATmega8 MCU. Such an interface could then
be used together with avrdude(1) (with a patch [1]) to
communicate (and program) other AVR 8-bit MCU's, like:

$ avrdude -c serspi -p atmega48 -P /dev/ttyS0 -t

Obviously, one'd need a MAX3232 or similar IC for a proper
RS-232 to SPI interface. Also, this interface is expected to
work together with USB to UART TTL interfaces (e. g., based on
CP2103, PL2303, etc.), but it wasn't tested so far.

Note that after restarting, the interface will report one of the
following 7-octet sequences to the host:

* READY CR LF -- after initial power-up;

* RESET CR LF -- after reset;

* WATCH CR LF -- after watchdog timer reset (i. e., indicates a
likely bug in the code.)

After that, all the data sent to the device will be sent via SPI
to whatever SPI slave currently attached, and the responses of
the latter will be sent back to the host.

[1] news:86lidgx...@gray.siamics.net
http://permalink.gmane.org/gmane.comp.hardware.avr.avrdude.devel/3453


Pinout

The interface assumes that UART's RxD, TxD (PD0, PD1) are wired
to the host's UART (possibly to an RS-232 port via MAX3232, or
otherwise.)

The SPI slave is connected to MOSI, MISO, SCK (PB3, PB4, PB5.)

PB0, PB1, PB2 are set up as "debugging outputs." For instance,
PB0 is toggled after every SPI exchange; PB1, PB2 are toggled
after receiving a character from, and sending one to, the host.

The external oscillator (if used) is connected to XTAL1, XTAL2
(PB6, PB7), along with the necessary capacitors (check the
datasheet for details.)

The reset circuit (unless disabled) is at /RESET (PC6.)


Building and installing

The following command will build the code, producing the .raw
file suitable for later ATmega8 flash programming. (Note that
no Makefile is required, as the command relies on GNU Make's
"implicit" rules.)

$ make \
CC=avr-gcc \
CFLAGS='-g -O2 -Wall -pedantic -std=gnu99 -mmcu=atmega8' \
CPPFLAGS= \
7mfypphkobt4bjn3nwxernmyun \
&& avr-objcopy -O binary -R .fuse \
-- 7mfypphkobt4bjn3nwxernmyun{,.raw}

To program the MCU flash with avrdude(1):

$ avrdude [... -c PROGRAMMER -P PORT ...] \
-p atmega8 -U flash:w:7mfypphkobt4bjn3nwxernmyun.raw:r


Bugs

Arguably, the only easy solution to the deficiencies below is to
tailor the code to suit one's own needs.

The program is configured to use the 8E1 mode (8-bit characters,
even parity, 1 stop bit) unconditionally, instead of much more
common, and the /only/ supported by avrdude(1) at the moment,
8N1 (8-bit, no parity, 1 stop bit.)

The program lacks automatic baud rate detection and uses a fixed
rate of 1200 baud.

Similarly, the code assumes a 14.7456 MHz crystal, unless a
-DEXT_XTAL=0 option is passed to the C preprocessor (e. g., via
CPPFLAGS in the command above) at build time, which makes the
code assume an internal 8 MHz oscillator circuit instead.

Check the code for more FIXME:'s.


Code

/*** 7mfypphkobt4bjn3nwxernmyun.c --- UART to SPI converter -*- C -*- */

/*** Ivan Shmakov, 2012 */

/** To the extent possible under law, the author(s) have dedicated all
** copyright and related and neighboring rights to this software to the
** public domain worldwide. This software is distributed without any
** warranty.
**
** You should have received a copy of the CC0 Public Domain Dedication
** along with this software. If not, see
** <http://creativecommons.org/publicdomain/zero/1.0/>.
*/

/*** Code: */
#ifndef EXT_XTAL
#define EXT_XTAL 1
#endif

#if EXT_XTAL
#define F_CPU 14745600 /* 14.7456 MHz crystal */
#else
#define F_CPU 8000000 /* 8 MHz internal RC oscillator */
#endif
#include <avr/interrupt.h> /* for EMPTY_INTERRUPT (), etc. */
#include <avr/io.h>
#include <avr/pgmspace.h>
/* #include <avr/power.h> */ /* for power_adc_disable () */
#include <avr/sleep.h>
#include <avr/wdt.h>

#ifndef DO_ECHO
#define DO_ECHO 0
#endif

#ifndef SPI_POLAR
#define SPI_POLAR 0
#endif

#ifndef SPI_PHASE
#define SPI_PHASE 0
#endif

#if EXT_XTAL
FUSES = {
/* external crystal oscillator */
.low = (LFUSE_DEFAULT
& ~FUSE_CKOPT),
.high = (HFUSE_DEFAULT
| FUSE_SUT1
| FUSE_SUT0
| FUSE_CKSEL3
| FUSE_CKSEL2
| FUSE_CKSEL1
| FUSE_CKSEL0),
#ifdef EFUSE_DEFAULT
.extended
= EFUSE_DEFAULT
#endif
};
#endif

#if defined (__AVR_ATmega8__)
/* SPI port & bits */
#define DDR_SPI DDRB
#define DD_MOSI DDB3
#define DD_MISO DDB4
#define DD_SCK DDB5
#else
#error "Unknown MCU?"
#endif

volatile uint8_t spi_complete_p
= 0;
ISR (SPI_STC_vect)
{
spi_complete_p
= 1;

/* flash the LED at PB0, if any */
PORTB ^= ((1 << PB0));

/* . */
}

EMPTY_INTERRUPT (TIMER2_OVF_vect);

volatile uint8_t usart_rxb[16] __attribute__ ((section (".noinit")));
volatile uint8_t *usart_rxb_rd = usart_rxb;
volatile uint8_t *usart_rxb_wr = usart_rxb;
volatile uint8_t usart_rxb_ovf = 0;

ISR (USART_RXC_vect)
{
uint8_t status
= UCSRA;
uint8_t c
= UDR;

if ((status & ((1 << FE) | (1 << DOR) | (1 << PE))) != 0) {
/* FIXME: record error condition */
/* . */
return;
}

*(usart_rxb_wr++)
= c;
if (usart_rxb_wr >= usart_rxb + sizeof (usart_rxb)) {
usart_rxb_wr
-= sizeof (usart_rxb);
}
if (usart_rxb_wr == usart_rxb_rd) {
/* raise overflow flag and disable further interrupts */
usart_rxb_ovf
= 1;
UCSRB &= ~(1 << RXCIE);
}

/* flash the LED at PB1, if any */
PORTB ^= ((1 << PB1));

/* . */
}

/* EMPTY_INTERRUPT (USART_TXC_vect); */

ISR (USART_UDRE_vect)
{
/* disable further interrupts */
UCSRB &= ~(1 << UDRIE);

/* flash the LED at PB2, if any */
PORTB ^= ((1 << PB2));

/* . */
}

/* NB: catch MCUSR early, so it could be examined later */
/* (as per the AVR Libc documentation) */

uint8_t mcusr_mirror __attribute__ ((section (".noinit")));

void get_mcusr (void)
__attribute__ ((naked))
__attribute__ ((section (".init3")));

void
get_mcusr (void)
{
mcusr_mirror = MCUSR;
MCUSR = 0;
/* wdt_disable (); */
wdt_enable (WDTO_2S);
}

static char
b36 (uint8_t v)
{
/* . */
return (v < 10 ? '0' + v : 'a' + v - 10);
}

static int
spi_transfer (char c)
{
cli ();
spi_complete_p
= 0;
SPDR
= c;
sei ();
while (! spi_complete_p) {
#if 1
sleep_cpu ();
#else
/* do nothing */
#endif
}

uint8_t status
= SPSR;
uint8_t d
= SPDR;

/* . */
return ((status & (1 << WCOL)) == 0
? d
: -1);
}

static void
uart_putc (char c)
{
/* allow USART_UDRE_vect () to fire */
cli ();
UCSRB |= ((1 << UDRIE));

/* wait for the UART data register to become empty */
while ((UCSRA & (1 << UDRE)) == 0) {
/* NB: only allowing interrupt to fire in sleep */
sei ();
sleep_cpu ();
cli ();
}
sei ();

UDR = c;
/* . */
}

static int
uart_getc_nb (void)
{
cli ();

/* check for more data */
if (! usart_rxb_ovf && usart_rxb_rd == usart_rxb_wr) {
/* no data */
sei ();
/* . */
return -1;
}

uint8_t c
= *(usart_rxb_rd++);
if (usart_rxb_rd >= usart_rxb + sizeof (usart_rxb)) {
usart_rxb_rd
-= sizeof (usart_rxb);
}
if (usart_rxb_ovf) {
/* lower the flag and enable further interrupts */
usart_rxb_ovf
= 0;
UCSRB |= (1 << RXCIE);
}

sei ();
/* . */
return c;
}

static int
uart_getc (void)
{
/* wait for data */
while (! usart_rxb_ovf && usart_rxb_rd == usart_rxb_wr) {
sleep_cpu ();
}
uint8_t c
= *(usart_rxb_rd++);
if (usart_rxb_rd >= usart_rxb + sizeof (usart_rxb)) {
usart_rxb_rd
-= sizeof (usart_rxb);
}
if (usart_rxb_ovf) {
/* lower the flag and enable further interrupts */
usart_rxb_ovf
= 0;
UCSRB |= (1 << RXCIE);
}

/* . */
return c;
}

int
main ()
{
/* enable watchdog timer */
/* NB: done in get_mcusr () */
/* wdt_enable (WDTO_2S); */

/* set up power and sleep modes */
/* power_adc_disable (); */
set_sleep_mode (SLEEP_MODE_IDLE);
#if 1
sleep_enable ();
#else
/* FIXME: debugging */
sleep_disable ();
#endif
#if 1
/* FIXME: debugging only at this time */
/* allow timer interrupt to wake the MCU up */
TCCR2 = ((1 << CS22)
| (1 << CS21)
| (1 << CS20));
TIMSK |= (1 << TOIE2);
#endif

/* set up UART */
{
#define BAUD 1200
#include <util/setbaud.h> /* for UBRRH_VALUE, etc. */
UBRRH = UBRRH_VALUE;
UBRRL = UBRRL_VALUE;
/* FIXME: set all at once */
UCSRA &= ~(1 << MPCM);
#if USE_2X
UCSRA |= (1 << U2X);
#else
UCSRA &= ~(1 << U2X);
#endif
/* enable interrupts, Tx & Rx */
UCSRB = ((1 << RXEN)
| (1 << TXEN));
UCSRB |= (1 << RXCIE);
/* set 8E1 mode */
UCSRC = ((1 << URSEL)
| (1 << UPM1)
| (1 << UCSZ1)
| (1 << UCSZ0));
}

/* set up SPI */
{
/* NB: use SPI clock = 1/128 times CPU clock */
SPCR = ((1 << SPIE)
| (1 << SPE)
| (1 << MSTR)
| (SPI_POLAR ? (1 << CPOL) : 0)
| (SPI_PHASE ? (1 << CPHA) : 0)
| (1 << SPR1)
| (1 << SPR0));
}

/* set up PORTB */
{
/* set up PB2 for output, so that it won't be used as /SS */
/* set up PB1, PB0 for debugging output */
DDRB |= ((1 << DDB2)
| (1 << DDB1)
| (1 << DDB0));
}

/* set up whatever port used for SPI */
{
DDR_SPI |= ((1 << DD_MOSI)
| (1 << DD_SCK));
}

/* enable interrupts */
sei ();

/* transmit READY via UART */
{
static const char ready_s[] PROGMEM
= "READY\r";
static const char reset_s[] PROGMEM
= "RESET\r";
static const char watch_s[] PROGMEM
= "WATCH\r";
const char *x
= ((mcusr_mirror & (1 << WDRF)) != 0 ? &(watch_s[0])
: (mcusr_mirror & (1 << EXTRF)) != 0 ? &(reset_s[0])
/* FIXME: also try BORF and PORF? */
: &(ready_s[0]));
/* FIXME: all the strings assumed to be of the same length */
const size_t len
= sizeof (ready_s);

uint8_t i;
for (i = 0; i < len; i++) {
char a
= (pgm_read_byte (x + (i % len)) ? : '\n');
uart_putc (a);
}
}

/* enter the main loop */
while (1) {
/* enter sleep mode, unless there're more data */
cli ();
int8_t more_data_p;
#if USART_RX_BUFFER
more_data_p
= (usart_rxb_ovf || usart_rxb_rd != usart_rxb_wr);
#else
more_data_p
= ((UCSRA & (1 << UDRE)) != 0);
#endif
if (more_data_p) {
sei ();
} else {
sei ();
sleep_cpu ();
}

/* reset the watchdog timer */
wdt_reset ();

int
c = uart_getc_nb ();
if (c < 0) {
/* FIXME: indicate error condition? */
continue;
}

#if ! DO_ECHO
int
d = spi_transfer (c);
if (d < 0) {
/* FIXME: indicate error condition? */
uart_putc (~0);
continue;
}
#else
/* alternative, test function */
int d
= ((c & 0300) == 0100
? (c ^ 0040) /* invert ASCII case */
: c);
#endif

uart_putc (d);
#if DO_ECHO
if (c == '\r') {
uart_putc ('\n');
}
#endif
}

/* . */
return 0;
}

/*** 7mfypphkobt4bjn3nwxernmyun.c ends here */

--
FSF associate member #7257 np. face_another_day_ii_edit.mod
0 new messages