From freemanw@westford.ccur.com Thu Sep 2 13:10:43 1993 Return-Path: Posted-Date: Thu, 2 Sep 93 16:07:13 EDT To: nmra@linus.mitre.org Subject: Better tested packet generators for synchronous transmitters. Date: Thu, 2 Sep 93 16:07:13 EDT From: freemanw@westford.ccur.com Sender: freemanw@westford.ccur.com Content-Length: 19823 X-Lines: 588 Status: RO Bill Brown has tested them on the Mac with Think C. I have tested them on the PC with MSC 7.0. I have tested them on a Concurrent (Masscomp) 68k based box with a non-ANSI compiler. When I say "tested", I don't mean that they have controlled trains, but rather that their output has been examined for a variety of inputs. The design leans a bit toward running fast, rather than being small and transparent to the reader (macros which make free references are used, rather than subroutines that get all the state variables or their addresses as arguments). There are two versions included: One is intended as a drop in replacement for Ken's MakePacket stuff At least the function has the same name and takes the same arguments, it just produces stuff suitable for sending via synchronous mode, rather than for sending via asynchronous mode. Its drawback is that it includes a few extra "1"s between successive packets (as compared to the minimum the spec allows). This version is comprised of vmakepkt.c, vmakepkt.h, and convert.h. Files that use this package will want to include vmakepkt.h. The other version has a different calling convention, allowing the state to be passed from one invocation of make_packet to the next. This state tells how full a byte of the buffer is, so that the new packet can begin immediately after the last bit of the previous packet. This means that after the last packet has been put in the buffer, the last byte will have to be filled out to capacity, and the function finish_tr_buf is provided for the purpose. As long as the arguments must be different anyway, make_packet uses an array and count to supply the bytes to put in the packet, rather than using Ken's vararg design. This is largely a matter of personal preference, but I believe that it simplifys functions which call make_packet and which have to deal with packets of variable (byte) length. Function make_packet also lets the first "sync" bit of the new packet serve as the packet end bit of the previous packet. So long as the buffer will be sent repeatedly, or is immediately followed by another valid buffer, that's fine, but if there may be an idle period after a buffer, the last packet will be without a packet end bit, and fail. To solve this problem, if the "force_pkt_end" parameter to finish_tr_buf is non-zero, it will guarantee that at least two "1"s are appended, and fills out to the end of the byte from there. The first "1" is the packet end bit, the second insures that there is a closing transition after the packet end bit (otherwise the second half of the packet end bit might look like a "0"). Since a fresh buffer has several pieces of state to initialize, a macro INIT_TR_BUF is provided to do so, and can be applied to a new buffer or one that is to be reused. The state is packaged with the buffer in a structure called a track_buf to minimize argument passing while still allowing multiple buffers. INIT_TR_BUF, make_packet, and finish_tr_buf all expect the address of a track_buf. A funciton alloc_tr_buf is supplied which malloc's a track_buf whose data portion is a user specified size. I have not provided a way for you to staticly allocate track_buf's without exposing implementation details. This version is comprised of makepkt.c, makepkt.h, and convert.h (yes, the same convert.h). Files that use this package will want to include makepkt.h. Both versions conditional compile according to the preprocessor symbol DO_NMRA_CHECKSUM. If it is defined, a check byte will be generated for and included in each packet. This is the default in makepkt.c, note the #define near the beginning of the file. If it is not defined, you must include a check byte as part of the data that you provide. This is the default in vmakepkt.c (the define is there, but it is commented out) since that is the behavior of Ken's code. There is another preprocessor symbol, SHIFT_REG_BYTES, that you might want to adjust. It lets the size of the variable used to assemble output be chosen according to your machine architecture. If 4 byte shifts and logical operations are slower on your machine than are 2 byte operations, you might want to set it to 2. (See the comment in conver.h.) Otherwise the default of 4 is fine. It is set up so that you can include a -DSHIRT_REG_BYTES=2 on the compile line, or you can change the define. 2 and 4 are the only values allowed. The code depends upon a byte being 8 bits, a short being at least 2 bytes, and a long being at least 4 bytes. If this is not true, you have porting work to do. I have test wrappers that let you exercise the code, display the result as a pseudo waveform, an which "decode" the result to confirm correct formatting. That code is large, so I'm not including it here. If you want it, send me mail. If there is enough demand, I'll post. Bill Freeman convert.h ------------------------------------------------------------ /* * A Bill Freeman nightmare. Given to the public domain, 8/20/1993 * * Stuff for converting data to NMRA sequences, and for inserting * them into a bit stream, represented by the bits, LSB first * (suitable for sending out a serial synchronous interface), of * a byte array. */ #ifndef SHIFT_REG_BYTES #define SHIFT_REG_BYTES 4 #endif /* * SHIFT_REG_BYTES must be 2 or 4, for short or long. * I can't use sizeof and let you sepcify short or long in a single * typedef instead bexause SHIFT_REG_BITS is used in #if clauses, and * sizeof isn't defined in the preprocessor. The second #elif below * is to call your attention to this spot if you do something else. * If short or long is a different number of bytes for you, adjust * the constants in the #if and the first #elif. Fix BITS_PER_BYTE * if necessary. * * I suggest that you use long (SHIFT_REG_BYTES defined as 4) unless * your architecture doesn't have 4 byte shiftable registers (8088 * through 80286, 8080, Z80, PDP-11, etc.). */ #define BITS_PER_BYTE 8 #if SHIFT_REG_BYTES == 2 typedef unsigned short shift_reg; #elif SHIFT_REG_BYTES == 4 typedef unsigned long shift_reg; #elif sizeof(int) < 4 /* * The above elif is intended to cause a compiler error if SHIFT_REG_BYTES * isn't one of the two recognized types, so if you get it, re-read the * comment above. On the other hand, if your compiler supports "sizeof" * in the preprocessor, you might consider changing things so that you * specify the type of shift_reg and let sizeof provide SHIFT_REG_BYTES. */ typedef unsigned short shift_reg; #else typedef unsigned long shift_reg; #endif #define SHIFT_REG_BITS (BITS_PER_BYTE * SHIFT_REG_BYTES) /* * When INSERT_BITS fills at least one whole byte, it will depend upon * the size of a shift_reg whether a loop must be used to see how many, * dictating an extra test and branch when the "while" exits, or whether * there cannot be more than two bytes filled to the end, allowing the * second to be handled with a single "if". */ #if SHIFT_REG_BYTES > 2 #define WHILE_OR_IF while #else #define WHILE_OR_IF if #endif /* * INSERT_BITS(dp, n, d) * * Appends the low "n" bits of "d" to the bit string at *dp, bit position * _IB_bit_. * * CAUTION: Makes free references to "_IB_dat_", "_IB_num_", "_IB_bit_", * and "_IB_sdp_". Use in declaring scope only. */ #define INSERT_BITS_DCL unsigned _IB_bit_; shift_reg _IB_dat_; \ int _IB_num_; unsigned char *_IB_dp_ #define INSERT_BITS_START(dp, n) _IB_bit_ = n; _IB_dp_ = dp #define INSERT_BITS(n, d) \ do { \ _IB_num_ = n; \ _IB_dat_ = d; \ if (_IB_num_ >= _IB_bit_) { /* At least fills the current byte */ \ *_IB_dp_++ |= _IB_dat_ << 8 - _IB_bit_; \ _IB_dat_ >>= _IB_bit_; \ _IB_num_ -= _IB_bit_; \ WHILE_OR_IF (_IB_num_ >= 8) { \ *_IB_dp_++ = _IB_dat_; \ _IB_dat_ >>= 8; \ _IB_num_ -= 8; \ } \ *_IB_dp_ = ((1 << _IB_num_) - 1) & _IB_dat_; \ _IB_bit_ = 8 - _IB_num_; \ } else { \ *_IB_dp_ |= (((1 << _IB_num_) - 1) & _IB_dat_) << 8 - _IB_bit_; \ _IB_bit_ -= _IB_num_; } \ } while (0) #define INSERT_BITS_BIT _IB_bit_ #define INSERT_BITS_BYTE _IB_dp_ /* * Look up table to convert data bit sequence (MSB first) to data that * will get NMRA track signals out of a (bi)synchronous transmitter or * shift register transmitter (LSB first) with constant 18kHz clock. * It is indexed by the next four data bits to send. Four bits is a * compromise between keeping down the size of the array vs. needing * two conversions to send a byte. */ struct convert { short d; /* data pattern in NMRA space */ char n; /* number of half-ones duration in NMRA space, * i.e.; number of meaningful bits in d, beginning * with LSB */ }; /* * If this ever winds up getting included in more than one module of a * program we will have to forgo the "static", declare it "extern" here, * and have a "convert.c" that does the initialization. Or, if only one * is including for the array, have a "DECLARE_CONVERT" ifdef. */ static struct convert convert[16] = { { 0x3333, 16 }, /* 0 0 0 0 */ { 0x1333, 14 }, /* 0 0 0 1 */ { 0x0D33, 14 }, /* 0 0 1 0 */ { 0x0533, 12 }, /* 0 0 1 1 */ { 0x0CD3, 14 }, /* 0 1 0 0 */ { 0x04D3, 12 }, /* 0 1 0 1 */ { 0x0353, 12 }, /* 0 1 1 0 */ { 0x0153, 10 }, /* 0 1 1 1 */ { 0x0CCD, 14 }, /* 1 0 0 0 */ { 0x04CD, 12 }, /* 1 0 0 1 */ { 0x034D, 12 }, /* 1 0 1 0 */ { 0x014D, 10 }, /* 1 0 1 1 */ { 0x0335, 12 }, /* 1 1 0 0 */ { 0x0135, 10 }, /* 1 1 0 1 */ { 0x00D5, 10 }, /* 1 1 1 0 */ { 0x0055, 8 } /* 1 1 1 1 */ }; /* * DO_BYTE(byte) * * Converts a byte into a start bit followed by its NMRA sequence, sending * them off to INSERT_BITS. * * CAUTION: Makes free references to "_DB_cp_". Use in declaring scope only. */ #define DO_BYTE_DCL struct convert *_DB_cp_ #if (SHIFT_REG_BITS < 20) #define DO_BYTE(byte) \ do { \ INSERT_BITS(4, 0x3); /* Byte start bit */ \ _DB_cp_ = &convert[(byte>>4) & 0xF]; /* high bits first */ \ INSERT_BITS(_DB_cp_->n, (shift_reg)_DB_cp_->d); \ _DB_cp_ = &convert[byte & 0xF]; \ INSERT_BITS(_DB_cp_->n, (shift_reg)_DB_cp_->d); \ } while(0) #else #define DO_BYTE(byte) \ do { \ _DB_cp_ = &convert[(byte>>4) & 0xF]; /* high bits first */ \ INSERT_BITS(_DB_cp_->n + 4, (((shift_reg)_DB_cp_->d) << 4) | 3); \ _DB_cp_ = &convert[byte & 0xF]; \ INSERT_BITS(_DB_cp_->n, (shift_reg)_DB_cp_->d); \ } while(0) #endif makepkt.h ------------------------------------------------------------ /* * A Bill Freeman nightmare. Given to the public domain, 8/20/1993 */ #define NMRA_SYNC_BITS 10 #define MIN_TR_BUF 1 /* makes minimum track_buf multiple of long */ /* * A structure for holding a bit string. Has storage for data, plus * remembers where in the data, bit and byte. */ typedef struct track_buf { short byte; /* Current buffer position is buf[byte] */ unsigned char bit; /* Number of bit positions free in buf[byte] */ unsigned char buf[MIN_TR_BUF]; } track_buf; #ifdef _STDC_ void make_packet(track_buf *, unsigned char *); void finish_tr_buf(track_buf *, int); track_buf *alloc_tr_buf(int); #else void make_packet(); void finish_tr_buf(); track_buf *alloc_tr_buf(); #endif #define INIT_TR_BUF(t) ((t)->byte = 0, (t)->bit = 8, (t)->buf[0] = 0) makepkt.c ------------------------------------------------------------ /* * A Bill Freeman nightmare. Given to the public domain, 8/20/1993 */ #define DO_NMRA_CHECKSUM #include "convert.h" #include "makepkt.h" /* * void make_packet(track_buf *bp, unsigned char *pp) * * Beginning at pp[1] are the data bytes of a desired packet. pp[0] * says how many data bytes. They are converted to a bit sequence in * which each bit represents the polarity of the NMRA DCC track signal * for a period of 55 microseconds. The entire sequence is the NMRA * DCC packet that corresponds to the data bytes, less the packet end * bit, normally covered by the first sync bit of the succeding packet, * but which can be forced with finish_tr_buf(bp, 1), see below. The * bit sequence is appended to the bit string represented by the struct * track_buf at "*bp". * * The bytes within the buf[] array of *bp are intended to be shifted * out to a booster at the NMRA half "1" rate (nominally 18kHz, * ~= 1/(55us)), low bit of each byte first, bytes in order. * * The track_buf should be allocated with alloc_tr_buf(size), see above. * Before it is used the first time, and when it is to be reused starting * from the beginning, it should be initialized with INIT_TR_BUF(). Call * finish_tr_buf() after the last call to make_packet() but before sending * the data. * * If "DO_NMRA_CHECKSUM" is defined, make_packet will add a check byte to * those in "pp", otherwise the caller should include one in "pp". */ void #ifdef _STDC_ make_packet(track_buf *bp, unsigned char *pp) #else make_packet(bp, pp) track_buf *bp; unsigned char *pp; #endif /* CAUTION: DO_BYTE accesses bp as a free reference. */ { int i; unsigned char byte; DO_BYTE_DCL; INSERT_BITS_DCL; #ifdef DO_NMRA_CHECKSUM unsigned char check; check = 0; #endif INSERT_BITS_BYTE = bp->buf+bp->byte; INSERT_BITS_BIT = bp->bit; #if SHIFT_REG_BITS < 2 * NMRA_SYNC_BITS for (i = 2 * NMRA_SYNC_BITS; i > SHIFT_REG_BITS; i -= SHIFT_REG_BITS) INSERT_BITS(SHIFT_REG_BITS, 0x55555555); INSERT_BITS(i, 0x55555555); #else INSERT_BITS(2 * NMRA_SYNC_BITS, 0x55555555); #endif for (i = *pp++; i; --i, ++pp) { byte = *pp; #ifdef DO_NMRA_CHECKSUM check ^= byte; #endif DO_BYTE(byte); } #ifdef DO_NMRA_CHECKSUM DO_BYTE(check); #endif bp->bit = INSERT_BITS_BIT; bp->byte = INSERT_BITS_BYTE - bp->buf; } /* * alloc_tr_buf(size) * * Allocate a struct track_buf capable of holding at least "size" * serial bytes. Each byte can hold 4 NMRA "1"s, 2 NMRA "0"s, or * something between (3, 2.5) for mixed bits. A maximum length N byte * packet using S sync bits and considering the possibility of * using the packet end bit as the first sync bit of the next packet * and assuming no bit stretching,requires (S + 18*N)/4 bytes. To find * the space for multiple worst case packets add the above formula * together for each, including fractional parts, and then round up. */ track_buf #ifdef _STDC_ *alloc_tr_buf(int size) #else *alloc_tr_buf(size) int size; #endif { track_buf *t; if (size <= MIN_TR_BUF) size = 0; else size -= MIN_TR_BUF; return (track_buf *)malloc(sizeof(*t) + size); } /* * finish_tr_buf(bp, force_pkt_end) * * Call after the last call to make_packet(). It fills the unused * part of the last byte with NMRA "1"s, so that the packet end bit * position of the last packet will have a "1". If sending of the * last byte of the track_buf will not be immediatly followed by * sending the first byte of this or another track_buf, every time, * then this should be called with force_pkt_end true (non-zero), * causing at least two NMRA "1" to be added to serve as the packet * end bit and a closing transition before the potential stall. * Otherwise, if the bits so far happen to end at a byte boundry, no * bits will be added, or if exactly 1 bit is added there may be no * transition after it which will prevent decoders that are timing * only the low half from recognizing it as a "1". */ void #ifdef _STDC_ finish_tr_buf(track_buf *bp, int force_pkt_end) #else finish_tr_buf(bp, force_pkt_end) track_buf *bp; int force_pkt_end; #endif { int i; INSERT_BITS_DCL; INSERT_BITS_BIT = i = bp->bit; if (force_pkt_end) { if (i < 4) i += 8; } else if (i == 8) return; INSERT_BITS_BYTE = bp->buf+bp->byte; INSERT_BITS(i, 0x5555); bp->bit = 8; /* Guaranteed == INSERT_BITS_BIT at this point */ bp->byte = INSERT_BITS_BYTE - bp->buf; } vmakepkt.h ----------------------------------------------------------- /* * A Bill Freeman nightmare. Given to the public domain, 8/20/1993 */ #define NMRA_SYNC_BITS 10 #ifndef CONCURRENTS_COMPILER int MakePacket(unsigned char *, long *, ...); #else int MakePacket(); #endif vmakepkt.c ----------------------------------------------------------- /* * A Bill Freeman nightmare. Given to the public domain, 8/20/1993 * * A drop-in replacement for Ken Rice's MakePacket when you switch * from using async mode to sync mode of your serial port (or whatever). * There are more "1"s than absolutely necessary between packets because * the interface is not extended to allow keeping bit postion across * calls to MakePacket (which would allow the end of one packet and the * beginning of the next to share a byte), and because it does not * require a function be called to finish off the buffer it can't assume * that there is another packet immediately following, so it forces * at least 2 "1"s at the end of the packet to serve as the packet end * "1" and to provide a closing transition in case a decoder is connected * in such polarity as to be using the second half of the bits. (If * there will always be an immediately following packet, comment out * the definition of "FORCE_PACKET_END", below.) * * Like Ken's version, it requires you to provide the NMRA checksum as * the last byte, but if "DO_NMRA_CHECKSUM" is defined, it will add a * NMRA checksum byte for you. */ /* #define DO_NMRA_CHECKSUM */ #define FORCE_PACKET_END #ifndef CONCURRENTS_COMPILER #include #else #include #endif #include "convert.h" #include "vmakepkt.h" /* * void make_packet(unsigned char *bufp, long *cntp, ...) * * The third argument up to but not including the first one equal to -1 * are the data bytes of a desired packet. They are converted to a bit * sequence in which each bit represents the polarity of the NMRA DCC * track signal for a period of (aproximately) 55 microseconds. The * entire sequence is the NMRA DCC packet that corresponds to the data * bytes, including the packet end bit and at least one more "1" bit * (but see "FORCE_PACKET_END"), and enough more "1" bits to make the * output data occupy an multiple of 8 bits. The resulting packet is * stored at "*bufp", the number of bytes used is stored at "*cntp". * * The bytes at "*bufp" are intended to be shifted out to a booster at * the NMRA half "1" rate (nominally 18kHz, ~= 1/(55us)), low bit of * each byte first, bytes in order. * * If "DO_NMRA_CHECKSUM" is defined, MakePacket will add a NMRA style * check byte after those provided as arguments, otherwise the caller * should any required check byte after the last data byte but before * the -1 (or, for non-standard protocols, wherever within the data * bytes that it belongs). */ #ifndef CONCURRENTS_COMPILER int MakePacket(unsigned char *bufp, long *cntp, ...) #else int MakePacket(bufp, cntp, va_alist) unsigned char *bufp; long *cntp; va_dcl #endif { va_list ap; int i; int byte; DO_BYTE_DCL; INSERT_BITS_DCL; #ifdef DO_NMRA_CHECKSUM unsigned char check; check = 0; #endif INSERT_BITS_BYTE = bufp; INSERT_BITS_BIT = 8; #if SHIFT_REG_BITS < 2 * NMRA_SYNC_BITS for (i = 2 * NMRA_SYNC_BITS; i > SHIFT_REG_BITS; i -= SHIFT_REG_BITS) INSERT_BITS(SHIFT_REG_BITS, 0x55555555); INSERT_BITS(i, 0x55555555); #else INSERT_BITS(2 * NMRA_SYNC_BITS, 0x55555555); #endif #ifndef CONCURRENTS_COMPILER va_start(ap, cntp); #else va_start(ap); #endif while ((byte = va_arg(ap, int)) != -1) { #ifdef DO_NMRA_CHECKSUM check ^= byte; #endif DO_BYTE(byte); } #ifdef DO_NMRA_CHECKSUM DO_BYTE(check); #endif i = INSERT_BITS_BIT; #ifdef FORCE_PACKET_END if (i < 4) i += 8; INSERT_BITS(i, 0x5555); #else if (i != 8) INSERT_BITS(i, 0x5555); #endif *cntp = INSERT_BITS_BYTE - bufp; return 0; }