/* HCS Mini PICDIO Link (c) 1998 Creative Control Concepts The HCS MiniDIO is a cost effective alternative to the existing Comm-Link and AnswerMan Jr Digital IO nodes for the HCS Home Automation System. The MiniDIO is 100% backward compatible with the existing HCS DIO-Link IO commands. However, the T command is NOT implemented due to limited on board memory. Thus the MiniDIO is intended for simple IO functions off the RS-485 network. The strobe output IS implemented and is strobed for S commands The PIC IO lines are assigned as follows: RA0 Strobe RA1 Config Enable RA2 Serial Enable RA3 Serial Out RA4 Serial In (via RTCC for interrupt) RB0-7 Data Bits */ #INCLUDE <16f84.h> #FUSES XT, NOWDT, NOPROTECT, NOPUT // We actually want the Powerup Timer, but my programmer gets it backwards! #USE DELAY (CLOCK=4000000) #USE FAST_IO(A) #USE FAST_IO(B) #ZERO_RAM // Saves us any trouble! // Buffer only needs to be 16 - we'll give it 20 #define BUFFER_SIZE 20 // EEPROM memory map #define saved_node_addr 0 #Bit T0IF = 0x0B.2 // Map name to RTCC interrupt flag struct porta_map { boolean strobe; boolean config; boolean serial_enable; boolean serial_out; boolean serial_in; int not_used : 3; } porta; #byte porta = 0x05 #byte portb = 0x06 #byte OPTION = 0x81 // Globals // Buffer is split in two becuase compiler can't handle array's which cross // a memory page. char buffer1[BUFFER_SIZE]; int serial_idx, process_idx, idx, scratch; boolean p_idle; // When TRUE, the buffer is not being processed boolean s_idle; // When TRUE, we are NOT receiving a packet boolean go_data; // When TRUE, we have a packet to munch boolean a_check; // Serial routine // Since we want to use all of Port B for IO (need the pullups) // We'll use the RTCC input to generate an interrupt when a start bit arrives // To do this, we setup RTCC to increment on High to Low transitions and set it to 0xFF // We simply increment RTCC on the falling edge and generate a rollover interrupt #INT_RTCC void serial_in() { char data_in; int sidx, samp_cnt; // Lets receive the data at 9600 bps (104us per bit) // We have 1 us per inst at 4MHz // Int Handler takes ~21 instructions // Ensure it was not a glitch (this takes 3 us) if (porta.serial_in) { set_rtcc(0xFF); // Reset interrupt return; } delay_us(120); // Need to delay 1.5B (156us) We already have 21 int instructions = 21us + 3us above // There are 12 inst until the middle sample - remove 12us more so we sync properly // Thus we delay 156-(21 + 3 + 12) = 124us (Subtract 3 more for initial 3us delay) for(sidx=0; sidx<8; sidx++) { // To ensure the bit is read ok, lets sample the bit 3 times at 3us intervals // Majority rules for the value // Lets sample over 15us samp_cnt = porta.serial_in; delay_us(3); if (porta.serial_in) { samp_cnt++; } delay_us(3); if (porta.serial_in) { samp_cnt++; } delay_us(3); shift_right(&data_in, 1, bit_test(samp_cnt,1)); // If samp_cnt = 0|1 -> 0 2|3 -> 1 delay_us(76); // 104us - 28us of instructions (28 inst) for loop = 76us } // Lets setup RTCC for next start bit set_rtcc(0xFF); // Roll over on next drop // That's it! Lets act on the data... // We are looking for the start of a packet. // Otherwise we stay in this state and ignore the incoming char if (s_idle) { if ((data_in == '!') || (data_in == '#')) { // New Packet - Lets go to the front of the buffer serial_idx = 0; buffer1[serial_idx] = data_in; s_idle = FALSE; } } else { if (++serial_idx == BUFFER_SIZE) { // Buffer Overflow - lets dump it since the checksum won't validate anyway s_idle = TRUE; a_check = FALSE; // Reset the address check for the next packet } else { // If the buffer is being processed, make sure we don't catch up if (!p_idle) { if (serial_idx == process_idx) { // We caught up! Dump the buffer s_idle = TRUE; a_check = FALSE; // Reset the address check for the next packet return; } } // Check for the end of the packet if ((data_in == '\r') || (data_in == '\n')) { go_data = TRUE; // Tell main program to process buffer s_idle = TRUE; } else { // Save the data buffer1[serial_idx] = data_in; } } } } // This routine sends a serial byte // TUNED FOR 4MHz! void send_serial_byte(byte byte_in) { int bitc; // Sending a serial byte is fairly easy // Disable interrupt since data is looped in RS485 chip // Send RS485 enable bit high // Send start bit (LOW) // Delay 104us // Shift data bits (8) onto serial out - wait 104us // Send stop bit (HIGH) // Clear EXT interrupt flag // Re-enable EXT interrupt // Delay 104us for stop bit disable_interrupts(INT_RTCC); porta.serial_enable = 1; delay_cycles(1); porta.serial_out = 0; // Delay 104us minus 9 inst cycles to get first bit out - 9us = 95 delay_us(95); for(bitc = 0; bitc < 8; ++bitc) { // Lets send the bits porta.serial_out = shift_right(&byte_in,1,0); // Loop takes 13 instructions. @4MHz - 13us (104 - 13 = 91us) delay_us(91); } // Now we send the stop bit and re-enable the interrupts delay_us(4); // Allow last bit to finish 104us porta.serial_out = 1; delay_us(1); // Allow line to stabilize // Now we can re-enable our interrupts T0IF = FALSE; // Clear the INT bit which would be set above during send enable_interrupts(INT_RTCC); set_rtcc(0xFF); // Delay for the rest of the stop bit time // We have 6 inst above plus 3 to return -> 104 - 9 = 98us delay_us(95); porta.serial_enable = 0; // Disable 485 driver } // This routine is used to convert characters for checking the checksum // It simply converts an ASCII hex value into true decimal form // ASCII allows for a quick and dirty conversion routine! byte gethexbyte(char digit) { if(!BIT_TEST(digit,6)) // Its a number if bit 6 is 0 return(digit & 0x0F); // Simple way to convert 0-9 else return((digit & 0x0F) + 9); } // This routine converts an 4-bit hex value into an associated ASCII character byte byte_to_ascii(byte tdata) { if (tdata <= 9) { return (tdata | 0x30); } else { return ((tdata - 9) | 0x40); } } void main () { int endbuff, data, node_total, last_hcs_data; boolean checksum, byte_query; char thisnode[4], data_char[2]; PORT_B_PULLUPS(TRUE); // Since we tri state a High on port B set_tris_b(0b11111111); // User will determine the directions set_tris_a(0b11110010); // These shouldn't change porta.serial_enable = 0; delay_cycles(1); porta.strobe = 1; // Init printer port delay_cycles(1); porta.serial_out = 1; // Leave serial out high // We'll use the RTCC rollover to initiate an interrupt for the start bit // Compiler doesn't seem to be doing this right - we'll do it ourselves // Set bits 5, 4, 3 (External clock H to L, WDT prescale (RTCC = 1:1) OPTION |= 0b00111000; last_hcs_data = 0xFF; // Assume all inputs from HCS to start // Lets see if they are trying to configure the chip // If porta.config is low, they are! Read portb 0-2 and save value // then loop forever. if (!porta.config) { // Lets config the system and output a message so they know we are okay printf(send_serial_byte, "\nCreative Control Concepts Mini-DIO\nv1.0 July 1998\n"); // Read portb scratch = (portb & 0b00000111) + 48; // Only save 3 bits write_eeprom(saved_node_addr, scratch); // Save the address value printf(send_serial_byte, "\nAddress set to DIO%c\n", scratch); while(TRUE) {}; // Endless loop; } else { // Lets get our address thisnode[0] = 'D'; thisnode[1] = 'I'; thisnode[2] = 'O'; thisnode[3] = read_eeprom(saved_node_addr); // If they haven't set an addr and its bogus, set it to 0 for them if ((thisnode[3] > '7') || (thisnode[3] < '0')) { thisnode[3] = '0'; write_eeprom(saved_node_addr, '0'); } } // Lets total up the set parts of the checksum (#, spaces, dummy checksum, and nodename) // This saves us time during each packet node_total = '#' + '0' + '0' + ' ' + ' ' + 'D' + 'I' + 'O'; node_total += thisnode[3]; // Initial values for serial and buffer handling s_idle = TRUE; p_idle = TRUE; go_data = FALSE; serial_idx = 0; // Lets enable the interrupts! disable_interrupts(GLOBAL); enable_interrupts(INT_RTCC); T0IF = FALSE; set_rtcc(0xFF); // get ready for first start bit enable_interrupts(GLOBAL); Buffer_Scan: p_idle = TRUE; a_check = FALSE; // Make sure we run address check only once process_idx = 0; // Lets wait until the buffer has a valid packet for our node // We check here and quite using the buffer if it isn't our packet while((!a_check) || (!go_data)) { // Lets see if we get a packet with our address // We can verify the checksum later - this gets us a quicker start // Once we hit the 9th buffer slot, lets check the address for a match // a_check ensures we only run this once // !s_idle || go_data ensures we are either receiving or have received a new buffer // This is required since there is a chance we'll get an entire new buffer (s_idle TRUE) // before we even enter this loop (long previous packet) if (!a_check && (!s_idle || go_data) && (serial_idx > 6)) { // 8th character is now in the buffer a_check = TRUE; if (buffer1[0] == '#') { scratch = 4; checksum = TRUE; } else { scratch = 2; checksum = FALSE; } // Let's check the address. If it isn't ours, we skip for(idx = 0; idx < 4; idx++) { if (thisnode[idx] != buffer1[scratch++]) { // Not our packet! s_idle = TRUE; // Reset buffer - scan for packet start go_data = FALSE; // Until the next valid packet goto Buffer_Scan; } } if (checksum) { // convert ascii to hex byte scratch = ((gethexbyte(buffer1[1]) << 4) | gethexbyte(buffer1[2])); process_idx = 9; // Give the buffer some breathing room // Lets add the nodename and default chars (#, space, 00 for default checksum) scratch += node_total; } else { process_idx = 7; // Give the buffer some breathing room } } } // Okay, lets process the packet p_idle = FALSE; // Let the interrupt routine know we are processing a buffer endbuff = serial_idx; // So we know where the end of the buffer is! go_data = FALSE; // Lets finish adding and validate the checksum if (checksum) { idx = 9; // Skip first 10 chars in buffer while (idx < endbuff) { scratch += buffer1[idx++]; } // Need the lower 8 bits to compare if (scratch != 0) { // Bad Checksum goto Buffer_Scan; } } // Command handling // The PIC DIO watches for the following commands: // Note space after command is optional! // Byte Set: DIO# S DP=(hex byte) // Bit Set: DIO# S DP.(bit num 0-7)=0 or 1 // Byte Read: DIO# Q DP // Bit Read: DIO# Q DP.(bit num 0-7) scratch = buffer1[process_idx]; // Lets get the command // Lets allow a space between the command letter and DP if (buffer1[++process_idx] == ' ') { process_idx++; } switch(scratch) { case('S'): // Its a set command, bit or byte process_idx += 2; // skip to = or bit number if (buffer1[process_idx] == '=') { // Its a byte set - easy! // Convert the ASCII hex into a real hex value last_hcs_data = (gethexbyte(buffer1[++process_idx]) * 16) + gethexbyte(buffer1[++process_idx]); // Because we need open collector ports, we use the Tris command to set highs // and a normal port output for lows SET_TRIS_B(last_hcs_data); // Highs are high impedence portb = last_hcs_data; // Assert the lows - highs already there due to pullups } else { // Its a bit set - little more complicated // Use the last data sent by the HCS to maintain proper Tristate settings // We need this since a low input would reassert and flip the tri causing a possible short // if we read the port, flipped the bit, and sent it back to the port idx = 1; // Lets prevent bogus bit numbers if ((buffer1[++process_idx] > '7') || (buffer1[process_idx] < '0')) { break; } // Quick and dirty way to get 2^x for (scratch = (buffer1[process_idx] & 0x0F); scratch != 0; scratch--) { idx <<= 1; // Shift the bit once } process_idx += 2; // Shift to bit value if (buffer1[process_idx] == '0') { // Lets twiddle the bit using the last HCS data // Thus we won't grab inputs and re assert them last_hcs_data &= (idx ^ 0xFF); // Twiddle the bit from HCS data - maintain inputs portb = last_hcs_data; // Set latches BEFORE changing TRIS SET_TRIS_B(last_hcs_data); // Now assert the low already set in latches } else { last_hcs_data |= idx; // Set the specific bit SET_TRIS_B(last_hcs_data); // No need to set port. Pull-ups take care of that } } p_idle = TRUE; // Release the old buffer - we are done with it. // Pulse the strobe on a port change in case the user needs it delay_cycles(1); // Let data stabilize porta.strobe = 0; delay_us(250); // Pulse strobe low in case they need it porta.strobe = 1; break; case('Q'): // Its a query! Read the byte or bit and return it // Note we DON'T touch last_hcs_data because a low input would be reasserted during the // next bit twiddle. data = portb; // We need to figure out if they wanted a bit or byte returned if ((process_idx + 3) < endbuff) { // We still have valid data - it must be a bit query byte_query = FALSE; process_idx += 3; // Move to bit number // Lets prevent bogus bit numbers if ((buffer1[process_idx] > '7') || (buffer1[process_idx] < '0')) { break; } for (idx = buffer1[process_idx]; idx >= '0'; idx--) { // Lets shift out the bit into a byte variable scratch = shift_right(&data, 1, 0); } // The requested bit is now in position 1 - lets wipe out the rest scratch &= 0b00000001; data_char[0] = scratch + 48; // Convert to ASCII 0 or 1 idx = buffer1[process_idx]; // ASCII value to return for bit number // Neat trick - we won't use this to return data, but we can use it to add the .# // to the computed checksum. Slick! data_char[1] = '.' + idx; } else { // They want a byte returned // Lets convert data into a 2 char ASCII string byte_query = TRUE; data_char[0] = byte_to_ascii(data >> 4); data_char[1] = byte_to_ascii(data & 0x0F); } p_idle = TRUE; // We are done with the buffer // Should we send back a checksum? if (checksum) { // Lets build the checksum. We'll cheat and use some predetermined values here scratch = '$' + '0' + '0' + ' ' + 'D' + 'I' + 'O' + ' ' + 'D' + 'P' + '='; // In 8 bits only! scratch += thisnode[3] + data_char[0] + data_char[1]; // Now lets take the 2's complement of the checksum subtotal result scratch ^= 0xFF; scratch++; } // Lets send the response delay_ms(50); send_serial_byte('$'); if (checksum) { printf(send_serial_byte, "%2X", scratch); } printf(send_serial_byte, " DIO%c DP", thisnode[3]); if (!byte_query) { // Lets include the .# for the queried bit printf(send_serial_byte, ".%c=%c\n", idx, data_char[0]); } else { printf(send_serial_byte, "=%c%c\n", data_char[0], data_char[1]); } break; default: // Do nothing break; } // switch(buffer1[process_idx++]) goto Buffer_Scan; } // End main