2 * Microcontroller message bus
3 * uc-side slave implementation for Atmel AVR8
5 * The gcc compiler always treats multi-byte variables as litte-endian.
6 * So no explicit endianness conversions are done on the message header,
7 * footer and status data structures.
9 * This hotpath-assembly implementation is about twice as fast
10 * as the C implementation.
12 * Copyright (C) 2009 Michael Buesch <mb@bu3sch.de>
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
31 #include <avr/interrupt.h>
32 #include <avr/pgmspace.h>
37 # error "Need the GNU C compiler"
41 #define __naked __attribute__((__naked__))
43 #define __used __attribute__((__used__))
46 struct ucmb_message_hdr
{
47 uint16_t magic
; /* UCMB_MAGIC */
48 uint16_t len
; /* Payload length (excluding header and footer) */
49 } __attribute__((packed
));
51 struct ucmb_message_footer
{
52 uint16_t crc
; /* CRC of the header + payload. */
53 } __attribute__((packed
));
56 uint16_t magic
; /* UCMB_MAGIC */
57 uint16_t code
; /* enum ucmb_status_code */
58 } __attribute__((packed
));
60 #define UCMB_MAGIC 0x1337
62 enum ucmb_status_code
{
64 UCMB_STAT_EPROTO
, /* Protocol format error */
65 UCMB_STAT_ENOMEM
, /* Out of memory */
66 UCMB_STAT_E2BIG
, /* Message too big */
67 UCMB_STAT_ECRC
, /* CRC error */
71 static uint8_t ucmb_buf
[sizeof(struct ucmb_message_hdr
) +
73 sizeof(struct ucmb_message_footer
)] __used
;
74 static uint16_t ucmb_buf_ptr __used
;
75 static struct ucmb_status status_buf __used
;
76 static uint16_t ucmb_send_message_len __used
;
78 /* The current IRQ handler */
79 static void (*ucmb_interrupt_handler
)(void) __used
;
82 /* Polynomial: x^16 + x^15 + x^2 + 1 */
83 static const prog_uint16_t crc16_table
[256] = {
84 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
85 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
86 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
87 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
88 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
89 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
90 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
91 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
92 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
93 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
94 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
95 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
96 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
97 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
98 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
99 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
100 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
101 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
102 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
103 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
104 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
105 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
106 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
107 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
108 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
109 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
110 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
111 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
112 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
113 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
114 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
115 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
118 static inline uint16_t crc16_block_update(uint16_t crc
, const void *_data
, uint16_t size
)
120 const uint8_t *data
= _data
;
125 offset
= crc
^ *data
;
126 crc
= (crc
>> 8) ^ pgm_read_word(&crc16_table
[offset
]);
133 static inline uint16_t ucmb_calc_msg_buffer_crc(void)
135 const struct ucmb_message_hdr
*hdr
;
136 uint16_t crc
= 0xFFFF;
138 hdr
= (const struct ucmb_message_hdr
*)ucmb_buf
;
139 crc
= crc16_block_update(crc
, ucmb_buf
,
140 sizeof(struct ucmb_message_hdr
) + hdr
->len
);
146 /* The generic interrupt handler.
147 * This just branches to the state specific handler.
149 ISR(SPI_STC_vect
) __naked
;
152 __asm__
__volatile__(
153 " ; UCMB generic interrupt handler \n"
154 " push __tmp_reg__ \n"
155 " in __tmp_reg__, __SREG__ \n"
158 " lds r30, ucmb_interrupt_handler + 0 \n"
159 " lds r31, ucmb_interrupt_handler + 1 \n"
160 " ijmp ; Jump to the real handler \n"
163 #define UCMB_IRQ_EPILOGUE \
164 " ; UCMB interrupt epilogue (start) \n"\
167 " out __SREG__, __tmp_reg__ \n"\
168 " pop __tmp_reg__ \n"\
170 " ; UCMB interrupt epilogue (end) \n"
172 static void __naked __used
ucmb_handler_LISTEN(void)
174 __asm__
__volatile__(
178 " lds r16, ucmb_buf_ptr + 0 \n"
179 " lds r17, ucmb_buf_ptr + 1 \n"
180 " ldi r18, hi8(%[sizeof_buf]) \n"
181 " cpi r16, lo8(%[sizeof_buf]) \n"
183 " in r18, %[_SPDR] \n"
184 " brsh 1f ; overflow \n"
185 " ; Store SPDR in the buffer \n"
187 " subi r30, lo8(-(ucmb_buf)) \n"
188 " sbci r31, hi8(-(ucmb_buf)) \n"
191 " ; Increment the buffer pointer \n"
192 " subi r16, lo8(-1) \n"
193 " sbci r17, hi8(-1) \n"
194 " sts ucmb_buf_ptr + 0, r16 \n"
195 " sts ucmb_buf_ptr + 1, r17 \n"
198 " cpi r16, %[sizeof_msg_hdr] \n"
199 " breq hdr_sanity_check ; buf_ptr == hdrlen \n"
200 " brlo st_listen_out ; buf_ptr < hdrlen \n"
202 " ; Get payload length from header \n"
203 " lds r30, (ucmb_buf + %[offsetof_hdr_len] + 0) \n"
204 " lds r31, (ucmb_buf + %[offsetof_hdr_len] + 1) \n"
205 " ; Add header and footer length to get full length \n"
206 " subi r30, lo8(-(%[sizeof_msg_hdr] + %[sizeof_msg_footer])) \n"
207 " sbci r31, hi8(-(%[sizeof_msg_hdr] + %[sizeof_msg_footer])) \n"
208 " ; Check if we have the full packet \n"
211 " breq st_listen_have_full_packet \n"
216 UCMB_IRQ_EPILOGUE
/* reti */
218 "hdr_sanity_check: \n"
219 " lds r30, (ucmb_buf + %[offsetof_hdr_magic] + 0) \n"
220 " lds r31, (ucmb_buf + %[offsetof_hdr_magic] + 1) \n"
221 " ldi r18, hi8(%[_UCMB_MAGIC]) \n"
222 " cpi r30, lo8(%[_UCMB_MAGIC]) \n"
224 " brne invalid_hdr_magic \n"
225 " lds r30, (ucmb_buf + %[offsetof_hdr_len] + 0) \n"
226 " lds r31, (ucmb_buf + %[offsetof_hdr_len] + 1) \n"
227 " ldi r18, hi8(0x8001) \n"
228 " cpi r30, lo8(0x8001) \n"
230 " brsh bogus_payload_len \n"
231 " rjmp st_listen_out \n"
233 "invalid_hdr_magic: \n"
234 " ; Invalid magic number in the packet header. Reset. \n"
235 "bogus_payload_len: \n"
236 " ; Bogus payload length in packet header. Reset. \n"
238 " sts ucmb_buf_ptr + 0, r18 \n"
239 " sts ucmb_buf_ptr + 1, r18 \n"
240 " rjmp st_listen_out \n"
242 "st_listen_have_full_packet: \n"
243 " ; We have the full packet. Any SPI transfer is stopped \n"
244 " ; while we are processing the packet, so this \n"
245 " ; is a slowpath. Branch to a C function. \n"
246 " clr __zero_reg__ \n"
259 " push __tmp_reg__ \n"
260 " rcall ucmb_received_packet \n"
261 " pop __tmp_reg__ \n"
274 " rjmp st_listen_out \n"
276 : [sizeof_buf
] "i" (sizeof(ucmb_buf
))
277 , [sizeof_msg_hdr
] "M" (sizeof(struct ucmb_message_hdr
))
278 , [sizeof_msg_footer
] "M" (sizeof(struct ucmb_message_footer
))
279 , [offsetof_hdr_magic
] "M" (offsetof(struct ucmb_message_hdr
, magic
))
280 , [offsetof_hdr_len
] "M" (offsetof(struct ucmb_message_hdr
, len
))
281 , [_SPDR
] "M" (_SFR_IO_ADDR(SPDR
))
282 , [_UCMB_MAGIC
] "i" (UCMB_MAGIC
)
287 static void __naked __used
ucmb_handler_SENDSTATUS(void)
289 __asm__
__volatile__(
293 " lds r16, ucmb_buf_ptr + 0 \n"
294 " cpi r16, %[sizeof_ucmb_status] \n"
295 " brsh 1f ; This is the trailing byte \n"
296 " ; Write the next byte from status_buf to SPDR \n"
299 " subi r30, lo8(-(status_buf)) \n"
300 " sbci r31, hi8(-(status_buf)) \n"
302 " out %[_SPDR], r18 \n"
304 " subi r16, lo8(-1) \n"
305 " sts ucmb_buf_ptr + 0, r16 \n"
306 " cpi r16, (%[sizeof_ucmb_status] + 1) \n"
307 " brne st_sendstatus_out \n"
308 " ; Finished. Sent all status_buf bytes + trailing byte. \n"
310 " sts ucmb_buf_ptr + 0, r18 \n"
311 " ; Switch back to listening state... \n"
312 " ldi r18, lo8(gs(ucmb_handler_LISTEN)) \n"
313 " sts ucmb_interrupt_handler + 0, r18 \n"
314 " ldi r18, hi8(gs(ucmb_handler_LISTEN)) \n"
315 " sts ucmb_interrupt_handler + 1, r18 \n"
316 " ; ...if we have no pending transmission \n"
317 " lds r30, ucmb_send_message_len + 0 \n"
318 " lds r31, ucmb_send_message_len + 1 \n"
322 " breq st_sendstatus_out \n"
323 " ; Switch status to SENDMESSAGE and send the first byte. \n"
324 " ldi r18, lo8(gs(ucmb_handler_SENDMESSAGE)) \n"
325 " sts ucmb_interrupt_handler + 0, r18 \n"
326 " ldi r18, hi8(gs(ucmb_handler_SENDMESSAGE)) \n"
327 " sts ucmb_interrupt_handler + 1, r18 \n"
328 " ; Send the first byte \n"
329 " lds r18, ucmb_buf + 0 \n"
330 " out %[_SPDR], r18 \n"
332 " sts ucmb_buf_ptr + 0, r18 \n"
333 "st_sendstatus_out: \n"
337 UCMB_IRQ_EPILOGUE
/* reti */
339 : [sizeof_ucmb_status
] "M" (sizeof(struct ucmb_status
))
340 , [_SPDR
] "M" (_SFR_IO_ADDR(SPDR
))
345 static void __naked __used
ucmb_handler_SENDMESSAGE(void)
347 __asm__
__volatile__(
351 " lds r16, ucmb_buf_ptr + 0 \n"
352 " lds r17, ucmb_buf_ptr + 1 \n"
353 " lds r30, ucmb_send_message_len + 0 \n"
354 " lds r31, ucmb_send_message_len + 1 \n"
357 " brsh 1f ; This is the trailing byte \n"
359 " subi r30, lo8(-(ucmb_buf)) \n"
360 " sbci r31, hi8(-(ucmb_buf)) \n"
362 " out %[_SPDR], r18 \n"
364 " subi r16, lo8(-1) \n"
365 " sbci r17, hi8(-1) \n"
366 " sts ucmb_buf_ptr + 0, r16 \n"
367 " sts ucmb_buf_ptr + 1, r17 \n"
368 " lds r30, ucmb_send_message_len + 0 \n"
369 " lds r31, ucmb_send_message_len + 1 \n"
370 " subi r30, lo8(-1) \n"
371 " sbci r31, hi8(-1) \n"
374 " brne st_sendmessage_out \n"
375 " ; Message + trailing byte processed. Retrieve status. \n"
377 " sts ucmb_buf_ptr + 0, r18 \n"
378 " sts ucmb_buf_ptr + 1, r18 \n"
379 " ldi r18, lo8(gs(ucmb_handler_RETRSTATUS)) \n"
380 " sts ucmb_interrupt_handler + 0, r18 \n"
381 " ldi r18, hi8(gs(ucmb_handler_RETRSTATUS)) \n"
382 " sts ucmb_interrupt_handler + 1, r18 \n"
383 "st_sendmessage_out: \n"
387 UCMB_IRQ_EPILOGUE
/* reti */
389 : [_SPDR
] "M" (_SFR_IO_ADDR(SPDR
))
394 static void __naked __used
ucmb_handler_RETRSTATUS(void)
396 __asm__
__volatile__(
400 " in r18, %[_SPDR] \n"
401 " lds r16, ucmb_buf_ptr + 0 \n"
404 " subi r30, lo8(-(status_buf)) \n"
405 " sbci r31, hi8(-(status_buf)) \n"
408 " sts ucmb_buf_ptr + 0, r16 \n"
409 " cpi r16, %[sizeof_ucmb_status] \n"
410 " brne st_retrstatus_out \n"
411 " ; Completely received the status \n"
413 " sts ucmb_buf_ptr + 0, r16 \n"
414 " ; Switch back to listening state... \n"
415 " ldi r18, lo8(gs(ucmb_handler_LISTEN)) \n"
416 " sts ucmb_interrupt_handler + 0, r18 \n"
417 " ldi r18, hi8(gs(ucmb_handler_LISTEN)) \n"
418 " sts ucmb_interrupt_handler + 1, r18 \n"
419 " ; Check status-report magic value \n"
420 " lds r30, (status_buf + %[offsetof_status_magic] + 0) \n"
421 " lds r31, (status_buf + %[offsetof_status_magic] + 1) \n"
422 " ldi r18, hi8(%[_UCMB_MAGIC]) \n"
423 " cpi r30, lo8(%[_UCMB_MAGIC]) \n"
425 " brne invalid_status_magic \n"
426 " ; Check status-report error code \n"
427 " lds r30, (status_buf + %[offsetof_status_code] + 0) \n"
428 " lds r31, (status_buf + %[offsetof_status_code] + 1) \n"
429 " ldi r18, hi8(%[_UCMB_STAT_OK]) \n"
430 " cpi r30, lo8(%[_UCMB_STAT_OK]) \n"
432 " brne faulty_status_code \n"
433 "st_retrstatus_out: \n"
437 UCMB_IRQ_EPILOGUE
/* reti */
439 "invalid_status_magic: \n"
440 "faulty_status_code: \n"
441 " ; Branch to the C error handler \n"
442 " clr __zero_reg__ \n"
455 " push __tmp_reg__ \n"
456 " rcall ucmb_received_faulty_status \n"
457 " pop __tmp_reg__ \n"
470 " rjmp st_retrstatus_out \n"
472 : [sizeof_ucmb_status
] "M" (sizeof(struct ucmb_status
))
473 , [offsetof_status_magic
] "M" (offsetof(struct ucmb_status
, magic
))
474 , [offsetof_status_code
] "M" (offsetof(struct ucmb_status
, code
))
475 , [_SPDR
] "M" (_SFR_IO_ADDR(SPDR
))
476 , [_UCMB_MAGIC
] "i" (UCMB_MAGIC
)
477 , [_UCMB_STAT_OK
] "i" (UCMB_STAT_OK
)
482 /* We received a full packet. This is called from assembly code. */
483 static void __used
ucmb_received_packet(void)
485 struct ucmb_message_hdr
*hdr
;
486 struct ucmb_message_footer
*footer
;
487 uint16_t payload_len
;
489 hdr
= (struct ucmb_message_hdr
*)ucmb_buf
;
490 payload_len
= hdr
->len
;
492 status_buf
.magic
= UCMB_MAGIC
;
493 status_buf
.code
= UCMB_STAT_OK
;
494 if (unlikely(ucmb_buf_ptr
> sizeof(ucmb_buf
))) {
495 /* Message is way too big and was truncated. */
496 status_buf
.code
= UCMB_STAT_E2BIG
;
498 footer
= (struct ucmb_message_footer
*)(
499 ucmb_buf
+ sizeof(struct ucmb_message_hdr
) +
501 if (ucmb_calc_msg_buffer_crc() != footer
->crc
)
502 status_buf
.code
= UCMB_STAT_ECRC
;
504 ucmb_interrupt_handler
= ucmb_handler_SENDSTATUS
;
506 /* Send the first byte */
507 SPDR
= ((uint8_t *)&status_buf
)[ucmb_buf_ptr
];
510 if (unlikely(status_buf
.code
!= UCMB_STAT_OK
))
511 return; /* Corrupt message. Don't pass it to user code. */
513 ucmb_send_message_len
= ucmb_rx_message(
514 ucmb_buf
+ sizeof(struct ucmb_message_hdr
),
516 if (ucmb_send_message_len
) {
517 footer
= (struct ucmb_message_footer
*)(
518 ucmb_buf
+ sizeof(struct ucmb_message_hdr
) +
519 ucmb_send_message_len
);
521 hdr
->magic
= UCMB_MAGIC
;
522 hdr
->len
= ucmb_send_message_len
;
523 footer
->crc
= ucmb_calc_msg_buffer_crc();
525 ucmb_send_message_len
+= sizeof(struct ucmb_message_hdr
) +
526 sizeof(struct ucmb_message_footer
);
530 /* We received a status report with an error condition.
531 * This is called from assembly code. */
532 static void __used
ucmb_received_faulty_status(void)
534 /* The master sent us a status report with an error code.
535 * Something's wrong with us. Print a status message and
536 * get caught by the watchdog, yummy.
541 uart_logmsg("UCMB: Received faulty status report. Triggering reset.");
542 wdt_enable(WDTO_15MS
);
544 /* "It's Coming Right For Us!" */
550 ucmb_interrupt_handler
= ucmb_handler_LISTEN
;
552 /* SPI slave mode 0 with IRQ enabled. */
553 DDRB
|= (1 << 6/*MISO*/);
554 DDRB
&= ~((1 << 7/*SCK*/) | (1 << 5/*MOSI*/) | (1 << 4/*SS*/));
555 SPCR
= (1 << SPE
) | (1 << SPIE
) /*| (1 << CPOL) | (1 << CPHA)*/;
556 (void)SPSR
; /* clear state */
557 (void)SPDR
; /* clear state */