/* * 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 * * 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 #include #include #include 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 */ }