diff --git a/config.h b/config.h new file mode 100644 index 0000000..6448663 --- /dev/null +++ b/config.h @@ -0,0 +1,3 @@ +#include +typedef uint16_t uint_16; +typedef uint8_t uint_8; \ No newline at end of file diff --git a/crc16.c b/crc16.c new file mode 100644 index 0000000..429f112 --- /dev/null +++ b/crc16.c @@ -0,0 +1,83 @@ +/* ======================================================================== */ +/* CRC-16 routines J. Zbiciak, 2001 */ +/* ------------------------------------------------------------------------ */ +/* The contents of this file are hereby released into the public domain. */ +/* This does not affect the rest of the program code in jzIntv, which */ +/* remains under the GPL except where specific files state differently, */ +/* such as this one. */ +/* */ +/* Programs are free to use the CRC-16 functions contained in this file */ +/* for whatever purpose they desire, with no strings attached. */ +/* ======================================================================== */ + + +#include "config.h" +#include "misc/crc16.h" + +/* ======================================================================== */ +/* CRC16_TBL -- Lookup table used for the CRC-16 code. */ +/* ======================================================================== */ +const uint_16 crc16_tbl[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +/* ======================================================================== */ +/* CRC16_UPDATE -- Updates a 16-bit CRC using the lookup table above. */ +/* Note: The 16-bit CRC is set up as a left-shifting */ +/* CRC with no inversions. */ +/* ======================================================================== */ +uint_16 crc16_update(uint_16 crc, uint_8 data) +{ + return (crc << 8) ^ crc16_tbl[(crc >> 8) ^ data]; +} + +/* ======================================================================== */ +/* CRC16_BLOCK -- Updates a 16-bit CRC on a block of 8-bit data. */ +/* Note: The 16-bit CRC is set up as a left-shifting */ +/* CRC with no inversions. */ +/* ======================================================================== */ +uint_16 crc16_block(uint_16 crc, uint_8 *data, int len) +{ + int i; + + for (i = 0; i < len; i++) + crc = (crc << 8) ^ crc16_tbl[(crc >> 8) ^ data[i]]; + + return crc; +} +/* ======================================================================== */ +/* This specific file is placed in the public domain by its author, */ +/* Joseph Zbiciak. */ +/* ======================================================================== */ diff --git a/design.txt b/design.txt new file mode 100644 index 0000000..2e6edd3 --- /dev/null +++ b/design.txt @@ -0,0 +1,38 @@ +things that exist on the device: + adcs + dacs? + gpios + pwms + +messages to send: + request data from gpio + request data from adc + request continual transfer from adc + +desiderata: + limited resending so we don't need to do it in app code + + + +HDLC: + tiny is secondary station + som is primary station + +using sequence and ack numbers, we can get reliability +(seq, ack) + +HOST: send (0,0).h, CLIENT: send (0,0).c +HOST: send (1,1).h, CLIENT: send (1,1).c +continue like so + +now suppose we have an error +HOST: send (0,0).h, CLIENT: send (0,0).c, client message fails chksum +HOST: send (1,0).h, CLIENT: send (1,1).c +now client sees it has a problem and sends +HOST: send (2,0).h, CLIENT: send (0,2).c, this time it passes chksum +HOST: send (3,1).h, CLIENT: send (1,3).c, back to sync + +these ack/send numbers do not need to be large, can use 4 bits each and put them in a byte. This also means we can easily bound the number of elements in a holding array + + + diff --git a/linux_test_end.c b/linux_test_end.c new file mode 100644 index 0000000..01f3163 --- /dev/null +++ b/linux_test_end.c @@ -0,0 +1,277 @@ +/* + * SPI testing utility (using spidev driver) + * + * Copyright (c) 2007 MontaVista Software, Inc. + * Copyright (c) 2007 Anton Vorontsov + * + * 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. + * + * Cross-compile with cross-gcc -I/path/to/cross-kernel/include + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "spi_proto.h" + +#define SPI_TRANSFER_LEN sizeof(struct spi_packet) + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +static void pabort(const char *s) +{ + perror(s); + abort(); +} + +static const char *device = "/dev/spidev0.0"; +static uint8_t mode; +static uint8_t bits = 8; +static uint32_t speed = 8388608U; // TODO +static uint16_t delay; + +#define SPI_TRANSFER_SIZE SPI_PACKET_LEN + +//TODO make this less brittle +uint8_t spi_in_buf[SPI_TRANSFER_SIZE], spi_out_buf[SPI_TRANSFER_SIZE]; +static void transfer(int fd) +{ + int ret; + struct spi_ioc_transfer tr = { + .tx_buf = (unsigned long)spi_out_buf, + .rx_buf = (unsigned long)spi_in_buf, + .len = ARRAY_SIZE(spi_out_buf), + .delay_usecs = delay, + .speed_hz = speed, + .bits_per_word = bits, + }; + + ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); + if (ret < 1) + pabort("can't send spi message"); + + for (ret = 0; ret < ARRAY_SIZE(spi_out_buf); ret++) { + if (!(ret % 12)) + puts(""); + printf("%.2X ", spi_in_buf[ret]); + } + puts(""); +} + +static void print_usage(const char *prog) +{ + printf("Usage: %s [-DsbdlHOLC3]\n", prog); + puts(" -D --device device to use (default /dev/spidev1.1)\n" + " -s --speed max speed (Hz)\n" + " -d --delay delay (usec)\n" + " -b --bpw bits per word \n" + " -l --loop loopback\n" + " -H --cpha clock phase\n" + " -O --cpol clock polarity\n" + " -L --lsb least significant bit first\n" + " -C --cs-high chip select active high\n" + " -3 --3wire SI/SO signals shared\n"); + exit(1); +} + +static void parse_opts(int argc, char *argv[]) +{ + while (1) { + static const struct option lopts[] = { + { "device", 1, 0, 'D' }, + { "speed", 1, 0, 's' }, + { "delay", 1, 0, 'd' }, + { "bpw", 1, 0, 'b' }, + { "loop", 0, 0, 'l' }, + { "cpha", 0, 0, 'H' }, + { "cpol", 0, 0, 'O' }, + { "lsb", 0, 0, 'L' }, + { "cs-high", 0, 0, 'C' }, + { "3wire", 0, 0, '3' }, + { "no-cs", 0, 0, 'N' }, + { "ready", 0, 0, 'R' }, + { NULL, 0, 0, 0 }, + }; + int c; + + c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL); + + if (c == -1) + break; + + switch (c) { + case 'D': + device = optarg; + break; + case 's': + speed = atoi(optarg); + break; + case 'd': + delay = atoi(optarg); + break; + case 'b': + bits = atoi(optarg); + break; + case 'l': + mode |= SPI_LOOP; + break; + case 'H': + mode |= SPI_CPHA; + break; + case 'O': + mode |= SPI_CPOL; + break; + case 'L': + mode |= SPI_LSB_FIRST; + break; + case 'C': + mode |= SPI_CS_HIGH; + break; + case '3': + mode |= SPI_3WIRE; + break; + case 'N': + mode |= SPI_NO_CS; + break; + case 'R': + mode |= SPI_READY; + break; + default: + print_usage(argv[0]); + break; + } + } +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + int fd; + + parse_opts(argc, argv); + + fd = open(device, O_RDWR); + if (fd < 0) + pabort("can't open device"); + + /* + * spi mode + */ + ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); + if (ret == -1) + pabort("can't set spi mode"); + + ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); + if (ret == -1) + pabort("can't get spi mode"); + + /* + * bits per word + */ + ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); + if (ret == -1) + pabort("can't set bits per word"); + + ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); + if (ret == -1) + pabort("can't get bits per word"); + + /* + * max speed hz + */ + ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); + if (ret == -1) + pabort("can't set max speed hz"); + + ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); + if (ret == -1) + pabort("can't get max speed hz"); + + printf("spi mode: %d\n", mode); + printf("bits per word: %d\n", bits); + printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000); + + struct spi_state s; + spi_proto_initialize(&s); + first_loop(); + for (int i = 0; i < 3; i++) { + loop(&s, fd); + sleep(1); + } + //transfer(fd); + + close(fd); + + return ret; +} + +void +linux_test_callback(struct spi_packet *p) +{ + //TODO print it out or something + uint8_t *s = (uint8_t *) p; + for (int i = 0; i < sizeof(struct spi_packet);i++) + printf("%02x ", s[i]); + printf("\n"); +} + + +void +print_bytes(unsigned char *b, int n) +{ + for (int i = 0; i < n;i++) + printf("%02x ", b[i]); + printf("\n"); +} +void +first_loop(void) +{ + //TODO set up communications and initialize SPI +} +void +loop(struct spi_state *s, int spi_fd) +{ + /* TODO loop: + do whatever testing processing (message sending, for example) + marshal messages into buffers + do the transaction + process the received message + */ + + + //load messages into queue if any + int N = 10; + unsigned char m2s[10] = {2,3,5,7,11,13,17,19,23, 29}; + // TODO send other messages + while(!spi_proto_send_msg(s, m2s, N)); + + //message sending + spi_proto_prep_msg(s, spi_out_buf, SPI_TRANSFER_LEN); + + //edbug output + puts("sending"); + print_bytes(spi_out_buf, SPI_TRANSFER_LEN); + + //do transaction + transfer(spi_fd); + + //process buffer into struct + struct spi_packet pack; + memcpy(&pack, spi_in_buf, SPI_TRANSFER_LEN); + //TODO maybe fixup the CRC byte order? + + //process received message + spi_proto_rcv_msg(s, &pack, linux_test_callback); + + print_spi_state(s); +} \ No newline at end of file diff --git a/misc/crc16.h b/misc/crc16.h new file mode 100644 index 0000000..aeb93a9 --- /dev/null +++ b/misc/crc16.h @@ -0,0 +1,43 @@ +/* ======================================================================== */ +/* CRC-16 routines J. Zbiciak, 2001 */ +/* ------------------------------------------------------------------------ */ +/* The contents of this file are hereby released into the public domain. */ +/* This does not affect the rest of the program code in jzIntv, which */ +/* remains under the GPL except where specific files state differently, */ +/* such as this one. */ +/* */ +/* Programs are free to use the CRC-16 functions contained in this file */ +/* for whatever purpose they desire, with no strings attached. */ +/* ======================================================================== */ + + +#ifndef CRC_16_H_ +#define CRC_16_H_ 1 + +/* ======================================================================== */ +/* CRC16_TBL -- Lookup table used for the CRC-16 code. */ +/* ======================================================================== */ +extern const uint_16 crc16_tbl[256]; + +/* ======================================================================== */ +/* CRC16_UPDATE -- Updates a 16-bit CRC using the lookup table above. */ +/* Note: The 16-bit CRC is set up as a left-shifting */ +/* CRC with no inversions. */ +/* */ +/* All-caps version is a macro for stuff that can use it. */ +/* ======================================================================== */ +uint_16 crc16_update(uint_16 crc, uint_8 data); +#define CRC16_UPDATE(crc, d) (((crc) << 8) ^ crc16_tbl[((crc) >> 8) ^ (d)]) + +/* ======================================================================== */ +/* CRC16_BLOCK -- Updates a 16-bit CRC on a block of 8-bit data. */ +/* Note: The 16-bit CRC is set up as a left-shifting */ +/* CRC with no inversions. */ +/* ======================================================================== */ +uint_16 crc16_block(uint_16 crc, uint_8 *data, int len); + +#endif +/* ======================================================================== */ +/* This specific file is placed in the public domain by its author, */ +/* Joseph Zbiciak. */ +/* ======================================================================== */ diff --git a/spi_proto.c b/spi_proto.c new file mode 100644 index 0000000..a902601 --- /dev/null +++ b/spi_proto.c @@ -0,0 +1,237 @@ +#include +#include +#include + +#include "config.h" +#include "misc/crc16.h" + +#include "spi_proto.h" + +//TODO replace all 16s with 15s so that 0b1111 can be used as a dummy SEQ number + +//update the protocol state with a message. Doesn't do any processing of the message itselves +void +spi_proto_rcv_msg(struct spi_state *s, struct spi_packet *p) +{ + uint16_t rcvd_crc = p->crc; + uint16_t calc_crc = spi_msg_crc(p); + printf("rcvd_crc: 0x%04x\n", rcvd_crc); + printf("calc_crc: 0x%04x\n", calc_crc); + //validate checksum. If it's wrong, take no action. The lack of change in our ACK will be the signal the other side needs. + //if the checksum is correct, verify that p->seq == s->their_next_ack. If so, increase their_next_ack. If not, take no action, they will resend. + if (rcvd_crc == calc_crc) { + //TODO put the compose_unfailing checks here + if (p->seq == s->we_sent_preack) { // the seq they sent, vs the preack we sent + s->our_next_preack++; + s->our_next_preack %= 16; // this isn't really a magic number because bytes aren't changing size anytime soon + } else { + //TO CONFIRM probably nothing, but how to handle a desync? is such a desync even possible? + //take no action on this side. Either our message was sent correctly (so the other side knows to change) or it wasn't, so they don't have any information about what we expect but they can learn later. XXX possible optimization would be to revert to last-known-expected if an incoming message is garbled, look at that later + } + + // if this round we got N preack and last round we got N-1, mark N-1 as confirmed + if (((s->last_round_rcvd_preack + 1)%16) == p->preack) { + // mark s->last_round_rcvd_preack as confirmed and those before it in the sent_but_unconfirmed section + // a position P is in a range of the queue if the start S of the queue slice and the length L of the queue slice include it, so if S + k = P mod 16 and 0 <= k <= L + //TODO improve this bad algorithm (written while tired) + for (int k = 0; k < s->num_sent_but_unconfirmed; k++) { + if (((s->first_unconfirmed_seq + k) % 16)==s->last_round_rcvd_preack) { + s->num_sent_but_unconfirmed -= k; + s->num_avail += k; + s->first_unconfirmed_seq += k; + s->first_unconfirmed_seq %= 16; + + s->num_sent_successfully +=k; + break; + } + } + } + if (p->preack == s->we_sent_seq) { + //the anticipatory ack was the same as our send, so assume send was successful and increment. If it wasn't we'll find out next completed round + //send the next packet (by incrementing seq which changes the index) + s->our_seq++; + s->our_seq %= 16; + } else { + //reset our send counter to the most recent received "expected ack" + s->our_seq = s->oldest_unconfirmed_seq; + //this will either not move the line of messages that must be kept forward, or move it forward by one + } + //handle releasing messages that were being held for resending. Release those with confirmed receipt. This is done by freeing that space in the message queue. + //this means release held messages before this p->preack. Write a loop, but it will only ever clear one at a time I think. + + //no queue modification necessary, just modify the variables holding the information about the queue. + //TODO is this manipulation valid always? + s->oldest_unconfirmed_seq = p->preack; // not + 1 because it's PREack + + + //set up last_round values for future use + s->last_round_rcvd_seq = p->seq; + s->last_round_rcvd_preack = p->preack; + } else { + //no action. XXX possibly increment send counter but seems likely we will have to go back anyway so don't + puts("bad crc!"); + } +} + +int +spi_proto_prep_msg(struct spi_state *s, void *buf, int n) +{ + //give it the buffer to write the message into + //TODO make #defs for return values + //TODO implement. return -1 if n is too short + if (n < SPI_PACKET_LEN) return -1; + //if no message just write all zeros but still write the SEQ/ACK/CRC + //do the seq/ack/crc insertion in this function + struct spi_packet *pack; + struct spi_packet p; // possibly not used + if (s->num_unsent) { + //responsibility of receive_msg to control these + s->queue[s->first_unsent_seq].seq = s->our_seq; + s->we_sent_seq = s->our_seq; + s->queue[s->first_unsent_seq].preack = s->our_next_preack; + s->we_sent_preack = s->our_next_preack; + + pack = &s->queue[s->first_unsent_seq]; + + //TODO maybe bump seq? + s->queue[s->first_unsent_seq].crc = spi_msg_crc(&s->queue[s->first_unsent_seq]); + s->first_unsent_seq++; + s->first_unsent_seq %= 16; + s->num_unsent--; + s->num_sent_but_unconfirmed++; + } else { + //safe to send seq here because of s->first_unsent_seq + memset(&p,0,sizeof(struct spi_packet)); + p.seq = s->our_seq; + p.preack = s->our_next_preack; + p.crc = spi_msg_crc(&p); + pack = &p; + } + memset(buf, 0, SPI_PACKET_LEN); + memcpy(buf, pack, SPI_PACKET_LEN); + return 0; +} + +int +spi_proto_send_msg(struct spi_state *s, void *buf, int n) +{ + //this puts the message in the queue, or returns -1 + //TODO add #defs for return values + if (n > SPI_MSG_PAYLOAD_LEN) return -1; + + //no need to modify other fields of message, that is handled when it's about to be sent + if (s->num_avail) { + //copy to buffer, maintain invariants + memset(s->queue[s->first_avail_seq].msg, 0, n); + memcpy(s->queue[s->first_avail_seq].msg, buf, n); + s->first_avail_seq++; + s->first_avail_seq %= 16; + s->num_avail--; + s->num_unsent++; + return 0; + } else { + return -2; + } +} + +void +spi_proto_initialize(struct spi_state *s) +{ + //most of these aren't necessary, but perhaps they should be present for a "reinitialize" + + memset(s, 0, sizeof(struct spi_state)); + s->num_avail = SPI_MSG_QUEUE_SIZE; + /* + s->num_unsent = 0; + s->num_sent_but_unconfirmed = 0; + + s->oldest_unconfirmed_seq = 0; + s->first_unconfirmed_seq = 0; + s->first_unsent_seq = 0; + s->first_avail_seq = 0; + + + s->our_seq = 0; + s->our_next_preack = 0; + s->we_sent_seq = 0; + s->we_sent_preack = 0; + //*/ +} + +void +print_spi_state(struct spi_state *s) +{ +#define PRINTIT(x) printf( #x ": %d\n", s-> x) + if (s) { + printf("our_seq: %d\n", s->our_seq); + printf("our_next_preack: %d\n", s->our_next_preack); + + PRINTIT(we_sent_seq); + PRINTIT(we_sent_preack); + + PRINTIT(last_round_rcvd_seq); + PRINTIT(last_round_rcvd_preack); + PRINTIT(num_sent_successfully); + + PRINTIT(oldest_unconfirmed_seq); + + PRINTIT(first_unconfirmed_seq); + PRINTIT(first_unsent_seq); + PRINTIT(first_avail_seq); + PRINTIT(num_unsent); + PRINTIT(num_sent_but_unconfirmed); + PRINTIT(num_avail); + + } else { + puts("spi_state NULL!"); + } +#undef PRINTIT +} + +void +print_spi_state_full(struct spi_state *s) +{ + print_spi_state(s); + if (s) { + //TODO print out spi queue + for (unsigned int j = 0; j < SPI_MSG_QUEUE_SIZE; j++) { + print_spi_packet(&s->queue[j]); + } + } +} + +void +print_spi_packet(struct spi_packet *p) +{ + unsigned char *pp = (void *) p; + for (unsigned int i = 0; i < SPI_PACKET_LEN;i++) { + printf("0x%02x ",pp[i]); + } + printf("\n"); +} + +int +spi_proto_check_invariants(struct spi_state *s) +{ + //TODO give seperate return codes, ensure this is all invariants + if (!s) return -1; + + if (s->we_sent_seq > 16) return -2; + if (s->we_sent_preack > 16) return -2; + if (s->our_seq > 16) return -2; + if (s->our_next_preack > 16) return -2; + + if ((s->num_avail + s->num_unsent + s->num_sent_but_unconfirmed) != 16) + return -3; + + return 0; +} + +uint16_t +spi_msg_crc(struct spi_packet *p) +{ + //run crc16 over all of message except for last two bytes (which are crc) + uint16_t crc_res = crc16_block(0, (void *) p, sizeof (struct spi_packet) - 2); + //p->crc = crc_res; + return crc_res; +} \ No newline at end of file diff --git a/spi_proto.h b/spi_proto.h new file mode 100644 index 0000000..89eacd5 --- /dev/null +++ b/spi_proto.h @@ -0,0 +1,72 @@ +#define SPI_MSG_PAYLOAD_LEN 32 +//Queue is 16 so that all seq and ack values are valid indexes +#define SPI_MSG_QUEUE_SIZE 16 + +struct __attribute__((packed)) spi_packet { + uint8_t magic; // includes at least a version number + uint8_t seq : 4; + uint8_t preack : 4; //or "anticipatory ack", or "expected ack" + uint8_t msg[SPI_MSG_PAYLOAD_LEN]; + uint16_t crc; // TODO confirm byte order on wire here +}; + +#define SPI_PACKET_LEN sizeof(struct spi_packet) + +//basic rule is send the message seq # 1 after your last received ack, which is really "expected". If you send n and the message you receive simultaneously is "I expect n this round" everything is good move on +//corrected: if the sent seq # and the received "expected ack" # for a round are the same, sent the next seq number. If they are not, send the "expected ack" number + +//there can't ever be more than 16 messages waiting in the queue + + +struct spi_state { + //each side has its own + uint8_t our_seq, our_next_preack; + + uint8_t we_sent_seq, we_sent_preack; // this side's last round sendings + + //for received values + uint8_t last_round_rcvd_seq, last_round_rcvd_preack; + + //note that this one doesn't directly control slots, it's a loop boundary + uint8_t oldest_unconfirmed_seq; + + uint8_t first_unconfirmed_seq; + + uint8_t first_unsent_seq; + + uint8_t first_avail_seq; + + //logging of sorts + int num_sent_successfully; + + //occupancy controls + uint8_t num_unsent; + uint8_t num_sent_but_unconfirmed; + uint8_t num_avail; + + + struct spi_packet queue[SPI_MSG_QUEUE_SIZE]; +}; + +typedef void (*spi_msg_callback_t)(struct spi_packet *); + +void +spi_proto_initialize(struct spi_state *s); +int +spi_proto_check_invariants(struct spi_state *s); +void +print_spi_state(struct spi_state *s); +void +print_spi_state_full(struct spi_state *s); +void +print_spi_packet(struct spi_packet *p); + +void +spi_proto_rcv_msg(struct spi_state *s, struct spi_packet *p, , spi_msg_callback_t cb); +int +spi_proto_prep_msg(struct spi_state *s, void *buf, int n); +int +spi_proto_send_msg(struct spi_state *s, void *buf, int n); + +uint16_t +spi_msg_crc(struct spi_packet *p); \ No newline at end of file diff --git a/spi_proto_tests.c b/spi_proto_tests.c new file mode 100644 index 0000000..3462208 --- /dev/null +++ b/spi_proto_tests.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include + +#include "spi_proto.h" + +//SPI protocol testing + +void +test_spi_proto_initialize(void) +{ + struct spi_state master, slave; + + //need function because of some invariants + spi_proto_initialize(&master); + spi_proto_initialize(&slave); + + //create buffers + struct spi_packet master_to_slave, slave_to_master; + + //prepare message + int master_write_ret = spi_proto_prep_msg(&master, &master_to_slave, SPI_PACKET_LEN); + int slave_write_ret = spi_proto_prep_msg(&slave, &slave_to_master, SPI_PACKET_LEN); + + //receive messages + spi_proto_rcv_msg(&slave, &master_to_slave); + spi_proto_rcv_msg(&master, &slave_to_master); + + //TODO print out data structures + print_spi_state(&master); + print_spi_state(&slave); +} + +void +test_spi_proto_one_round(unsigned char *m2s, unsigned char *s2m, int len) +{ + struct spi_state master, slave; + + //need function because of some invariants + spi_proto_initialize(&master); + spi_proto_initialize(&slave); + + + puts("MASTER after init"); + print_spi_state_full(&master); + + //send m2s from master to slave, and vice versa. + //TODO crash if len > SPI_PACKET_LEN + assert(len <= SPI_PACKET_LEN); + + //create buffers + struct spi_packet master_to_slave, slave_to_master; + + puts("after creation"); + print_spi_packet(&master_to_slave); + + puts("after clear"); + memset(&master_to_slave, 0, sizeof(master_to_slave)); + print_spi_packet(&master_to_slave); + + int msend = spi_proto_send_msg(&master, m2s, len); + int ssend = spi_proto_send_msg(&slave, s2m, len); + + puts("MASTER after send"); + print_spi_state_full(&master); + + assert(msend != -1); + assert(ssend != -1); + + //prepare message + int master_write_ret = spi_proto_prep_msg(&master, &master_to_slave, SPI_PACKET_LEN); + int slave_write_ret = spi_proto_prep_msg(&slave, &slave_to_master, SPI_PACKET_LEN); + + puts("after write"); + print_spi_packet(&master_to_slave); + + //receive messages + spi_proto_rcv_msg(&slave, &master_to_slave); + spi_proto_rcv_msg(&master, &slave_to_master); + + //TODO extract messages + //TODO compare with sent messages + + puts("after sending"); + print_spi_packet(&master_to_slave); + print_spi_packet(&slave_to_master); + + //print out data structures + print_spi_state(&master); + //print_spi_state(&slave); + +} + +int +test_spi_longer(unsigned int rounds) +{ + /*TODO + initialize + begin loop + feed messages into queue until queue full message received + do a step + handle results and do output + */ + + struct spi_state master, slave; + + //need function because of some invariants + spi_proto_initialize(&master); + spi_proto_initialize(&slave); + + int N = 10; + unsigned char m2s[10] = {1,2,3,4,5,6,7,8,9,10}; + unsigned char s2m[10] = {2,3,5,7,11,13,17,19,23, 29}; + while (rounds--) { + //TODO feed messages into each queue + while(!spi_proto_send_msg(&master, m2s, N)); + while(!spi_proto_send_msg(&slave, s2m, N)); + + //CHECK step transmission + + //create buffers + struct spi_packet master_to_slave, slave_to_master; + + //prepare message + int master_write_ret = spi_proto_prep_msg(&master, &master_to_slave, SPI_PACKET_LEN); + int slave_write_ret = spi_proto_prep_msg(&slave, &slave_to_master, SPI_PACKET_LEN); + + //receive messages + spi_proto_rcv_msg(&slave, &master_to_slave); + spi_proto_rcv_msg(&master, &slave_to_master); + + //TODO do output + print_spi_state(&master); + puts("------------------"); + } +} + +int +test_spi_longer_some_noise(unsigned int rounds, float noise_chance) +{ + /*TODO + initialize + begin loop + feed messages into queue until queue full message received + do a step + handle results and do output + */ + srand(0x14411441); + + struct spi_state master, slave; + + //need function because of some invariants + spi_proto_initialize(&master); + spi_proto_initialize(&slave); + + int N = 10; + unsigned char m2s[10] = {1,2,3,4,5,6,7,8,9,10}; + unsigned char s2m[10] = {2,3,5,7,11,13,17,19,23, 29}; + while (rounds--) { + //TODO feed messages into each queue + while(!spi_proto_send_msg(&master, m2s, N)); + while(!spi_proto_send_msg(&slave, s2m, N)); + + //CHECK step transmission + + //create buffers + struct spi_packet master_to_slave, slave_to_master; + + //prepare message + int master_write_ret = spi_proto_prep_msg(&master, &master_to_slave, SPI_PACKET_LEN); + int slave_write_ret = spi_proto_prep_msg(&slave, &slave_to_master, SPI_PACKET_LEN); + + int rand_cap = ((float)RAND_MAX) * noise_chance; + if (rand() < rand_cap) { + master_to_slave.msg[5] ^= 0xff; + } + if (rand() < rand_cap) { + slave_to_master.msg[5] ^= 0xff; + } + //receive messages + spi_proto_rcv_msg(&slave, &master_to_slave); + spi_proto_rcv_msg(&master, &slave_to_master); + + //TODO do output + print_spi_state(&master); + puts("------------------"); + } +} + +int +main(int argc, char *argv[]) +{ + //TODO run the tests + + //test_spi_proto_initialize(); + //puts("ayy"); + unsigned char m2s[10] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}; + unsigned char s2m[10] = {0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}; + int n = 10; + + /* + puts("one round start:"); + test_spi_proto_one_round(m2s, s2m, n); + puts("one round end."); + */ + + //test_spi_longer(9); + + test_spi_longer_some_noise(90, 0.1); +} diff --git a/spi_slave.c b/spi_slave.c new file mode 100644 index 0000000..27634e8 --- /dev/null +++ b/spi_slave.c @@ -0,0 +1,167 @@ +/* +The communication protocol for letting the master use the peripherals of the slave is as follows +data direction is always known and so never marked in the protocol. +typeCode | id | ... + +typeCodes are +syntax error 0 TODO use this for info commands instead +GPIO 1 +ADC 2 + +TODO there should be a way to get maximum id of type, as well as a short description of a type-id pair. + +*/ + +struct spi_protocol_state { + bool waiting_on_deadman; + struct spi_prosthetic_msg deadman_pending_msg; + long deadman_deadline; +} + +struct spi_prosthetic_msg { + byte type_code; + byte id; //an id, in the case of deadman a non-sequential code + byte payload[0]; +} + +#define CONTROL_BAD_ID 0 +#define CONTROL_BAD_OPCODE 1 + +#define MTYPE_CONTROL 0 +#define MTYPE_GPIO 1 +#define MTYPE_ADC 2 +#define MTYPE_DEADMAN 3 +#define NUM_M_TYPE 4 + +typedef void (type_handler_function_t)(struct spi_prosthetic_msg *m); + +type_handler_function_t *type_handlers[NUM_M_TYPE] = { + handle_control, handle_gpio, handle_adc, handle_deadman +}; + +void +process(struct spi_protocol_state *s, struct spi_prosthetic_msg *m) +{ + if (waiting_on_deadman) { + //TODO store the message in the current deadman handler for later processing + s->deadman_pending_msg = *m; // TODO copy it + //TODO note that the waiting period begins now + } + if (m->type_code < NUM_M_TYPE) { + if (&& m->type_code != MTYPE_DEADMAN) { + type_handlers[m->type_code](m); + } else { + //TODO create a deadman handler for this message, or update an existing one + } + } +} + +struct gpio_spi_box { + GPIO_Type *base; + uint8_t pin_ix; // the mask is 1 << pin_ix + //int clock; //TODO other stuff that's needed + char *desc; // has a max length, should be something simple like "D6" +}; + +int +read_gpio(struct gpio_spi_box *g) +{ + return GPIO_ReadPinInput(g->base, g->pin_ix); +} +void +write_gpio(struct gpio_spi_box *g, int level) +{ + GPIO_WritePinOutput(g->base, g->pin_ix, level); +} +void +handle_gpio(struct spi_prosthetic_msg *m) +{ + /* + if valid id + case opcode of + read + read that gpio, compose a packet reporting it + write + write that gpio + settings change + apply the settings + description query + compose message with short description (e.g. D7) + other + compose unreckognized opcode message + else + compose invalid id opcode + */ + if (m->id < gpios.max_id) { + switch (m->payload[0]) { + case OP_GPIO_READ: + //read GPIO + unsigned char gpio_read_val = read_gpio(&gpios[m->id]); + //TODO compose response message + break; + case OP_GPIO_WRITE: + //DONE write the gpio, no response + write_gpio(&gpios[m->id], m->payload[1]); + break; + case OP_GPIO_SETTINGS: + //TODO apply the settings change, no response + break; + case OP_GPIO_DESCRIPTION: + //TODO compose response with description for this GPIO + break; + default: + //TODO compose BAD OPCODE response, with gpio id and opcode? + break; + } + } else { + //TODO compose BAD ID (GPIO) message + } +} + +void +handle_adc(struct spi_prosthetic_msg *m) +{ + /* + if valid id + case opcode of + read + read that adc, compose a packet reporting it + settings change + apply the settings + description query + compose message with short description (e.g. D7) + other + compose unrecognized opcode message + else + compose invalid id opcode + */ + if (m->id < adc.adc_num) { + switch (m->payload[0]) { + case OP_ADC_READ: + //TODO read the adc, compose a response + break; + case OP_ADC_SETTINGS: + //TODO apply the settings change, no response + break; + case OP_ADC_DESCRIPTION: + //TODO get the description, compose response + break; + default: + //TODO compose BAD OPCODE message with adc id and opcode + } + } else { + //TODO compose BAD ID (ADC) message + } +} + +void +compose_unfailing(struct spi_protocol_state *s, struct spi_prosthetic_msg *m /*TODO ARGS*/) +{ + /* + the key thing here is that this function is unfailing. This invariant is maintained by disabling receipt of messages if the queue fills. So when the program starts the queue is empty, and if messages are transmitted correctly the queue will never have more than one message in it. If messages are repeatedly dropped in both directions, the queue state won't change. If messages are dropped in one direction, the queue on one edge might fill up. If the side with the full queue stops accepting messages, things will balance unless the other side also suddenly has a full queue. + + TODO can this problem can be avoided by taking note of the SEQ/ACK data in each message rather than simply discarding it? + */ + + //TODO enqueue the message or whatever +} \ No newline at end of file