234 lines
5.9 KiB
C
Raw Normal View History

/*
* Microcontroller message bus
* uc-side slave implementation for Atmel AVR8
*
* The gcc compiler always treats multi-byte variables as litte-endian.
* So no explicit endianness conversions are done on the message header,
* footer and status data structures.
*
* Copyright (C) 2009 Michael Buesch <mb@bu3sch.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.
*/
#include "ucmb.h"
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/crc16.h>
struct ucmb_message_hdr {
uint16_t magic; /* UCMB_MAGIC */
uint16_t len; /* Payload length (excluding header and footer) */
} __attribute__((packed));
struct ucmb_message_footer {
uint16_t crc; /* CRC of the header + payload. */
} __attribute__((packed));
struct ucmb_status {
uint16_t magic; /* UCMB_MAGIC */
uint16_t code; /* enum ucmb_status_code */
} __attribute__((packed));
#define UCMB_MAGIC 0x1337
enum ucmb_status_code {
UCMB_STAT_OK = 0,
UCMB_STAT_EPROTO, /* Protocol format error */
UCMB_STAT_ENOMEM, /* Out of memory */
UCMB_STAT_E2BIG, /* Message too big */
UCMB_STAT_ECRC, /* CRC error */
};
static uint8_t ucmb_buf[sizeof(struct ucmb_message_hdr) +
UCMB_MAX_MSG_LEN +
sizeof(struct ucmb_message_footer)];
static uint16_t ucmb_buf_ptr;
static struct ucmb_status status_buf;
static uint16_t ucmb_send_message_len;
/* Statemachine */
static uint8_t ucmb_state;
enum {
UCMB_ST_LISTEN, /* Listen for incoming messages. */
UCMB_ST_SENDSTATUS, /* Send the status report. */
UCMB_ST_SENDMESSAGE, /* Send the message. */
UCMB_ST_RETRSTATUS, /* Retrieve the status report. */
};
#define TRAILING 1
static void ucmb_send_next_byte(void)
{
switch (ucmb_state) {
case UCMB_ST_SENDSTATUS: {
const uint8_t *st = (const uint8_t *)&status_buf;
if (ucmb_buf_ptr < sizeof(struct ucmb_status))
SPDR = st[ucmb_buf_ptr];
ucmb_buf_ptr++;
if (ucmb_buf_ptr == sizeof(struct ucmb_status) + TRAILING) {
ucmb_buf_ptr = 0;
if (ucmb_send_message_len) {
ucmb_state = UCMB_ST_SENDMESSAGE;
goto st_sendmessage;
} else
ucmb_state = UCMB_ST_LISTEN;
}
break;
}
case UCMB_ST_SENDMESSAGE: {
st_sendmessage:;
uint16_t full_length = sizeof(struct ucmb_message_hdr) +
ucmb_send_message_len +
sizeof(struct ucmb_message_footer);
if (ucmb_buf_ptr < full_length)
SPDR = ucmb_buf[ucmb_buf_ptr];
ucmb_buf_ptr++;
if (ucmb_buf_ptr == full_length + TRAILING) {
ucmb_send_message_len = 0;
ucmb_buf_ptr = 0;
ucmb_state = UCMB_ST_RETRSTATUS;
}
break;
} }
}
static uint16_t crc16_block_update(uint16_t crc, const void *_data, uint16_t size)
{
const uint8_t *data = _data;
while (size) {
crc = _crc16_update(crc, *data);
data++;
size--;
}
return crc;
}
static uint16_t ucmb_calc_msg_buffer_crc(void)
{
const struct ucmb_message_hdr *hdr;
uint16_t crc = 0xFFFF;
hdr = (const struct ucmb_message_hdr *)ucmb_buf;
crc = crc16_block_update(crc, ucmb_buf,
sizeof(struct ucmb_message_hdr) + hdr->len);
crc ^= 0xFFFF;
return crc;
}
/* SPI data transfer interrupt. */
ISR(SPI_STC_vect)
{
uint8_t data;
data = SPDR;
SPDR = 0;
switch (ucmb_state) {
case UCMB_ST_LISTEN: {
struct ucmb_message_hdr *hdr;
struct ucmb_message_footer *footer;
if (ucmb_buf_ptr < sizeof(ucmb_buf))
ucmb_buf[ucmb_buf_ptr] = data;
ucmb_buf_ptr++;
if (ucmb_buf_ptr < sizeof(struct ucmb_message_hdr))
return; /* Header RX not complete. */
hdr = (struct ucmb_message_hdr *)ucmb_buf;
if (ucmb_buf_ptr == sizeof(struct ucmb_message_hdr)) {
if (hdr->magic != UCMB_MAGIC) {
/* Invalid magic! Reset. */
ucmb_buf_ptr = 0;
return;
}
if (hdr->len > 0x8000) {
/* Completely bogus length! Reset. */
ucmb_buf_ptr = 0;
return;
}
return;
}
if (ucmb_buf_ptr == sizeof(struct ucmb_message_hdr) +
sizeof(struct ucmb_message_footer) +
hdr->len) {
status_buf.magic = UCMB_MAGIC;
status_buf.code = UCMB_STAT_OK;
if (ucmb_buf_ptr > sizeof(ucmb_buf)) {
/* Message is way too big and was truncated. */
status_buf.code = UCMB_STAT_E2BIG;
} else {
footer = (struct ucmb_message_footer *)(
ucmb_buf + sizeof(struct ucmb_message_hdr) +
hdr->len);
if (ucmb_calc_msg_buffer_crc() != footer->crc)
status_buf.code = UCMB_STAT_ECRC;
}
ucmb_state = UCMB_ST_SENDSTATUS;
ucmb_buf_ptr = 0;
ucmb_send_next_byte();
if (status_buf.code != UCMB_STAT_OK)
return; /* Corrupt message. Don't pass it to user code. */
ucmb_send_message_len = ucmb_rx_message(
ucmb_buf + sizeof(struct ucmb_message_hdr),
hdr->len);
if (ucmb_send_message_len) {
footer = (struct ucmb_message_footer *)(
ucmb_buf + sizeof(struct ucmb_message_hdr) +
ucmb_send_message_len);
hdr->magic = UCMB_MAGIC;
hdr->len = ucmb_send_message_len;
footer->crc = ucmb_calc_msg_buffer_crc();
}
}
break;
}
case UCMB_ST_SENDSTATUS:
case UCMB_ST_SENDMESSAGE:
ucmb_send_next_byte();
break;
case UCMB_ST_RETRSTATUS: {
uint8_t *st = (uint8_t *)&status_buf;
st[ucmb_buf_ptr++] = data;
if (ucmb_buf_ptr == sizeof(struct ucmb_status)) {
/* We could possibly handle the status report here... */
ucmb_buf_ptr = 0;
ucmb_state = UCMB_ST_LISTEN;
}
break;
} }
}
void ucmb_init(void)
{
ucmb_state = UCMB_ST_LISTEN;
/* SPI slave mode 0 with IRQ enabled. */
DDRB |= (1 << 4/*MISO*/);
DDRB &= ~((1 << 5/*SCK*/) | (1 << 3/*MOSI*/) | (1 << 2/*SS*/));
SPCR = (1 << SPE) | (1 << SPIE) /*| (1 << CPOL) | (1 << CPHA)*/;
(void)SPSR; /* clear state */
(void)SPDR; /* clear state */
}