/* Mini Link (c) 1998-2000 Creative Control Concepts The CCC Mini-Link is an extremely small HCS network node with 3 I/O ports that can be individually configured as 8-bit Analog inputs or Digital I/O ports. There is also a 4th digital input which cannot be configured. Release History Version 1.1 - August 2000 - Added support for EEPROM 12CE67x so Net Addr & # of ADCs can be stored internally instead of permanently burned in - Added code to buffer short pulses on input pins - Added support for watchdog feature for stability Version 1.0 - Original Release The Mini-Link appears to the HCS as an AnswerMan Jr. The PIC IO lines are assigned as follows: GP0 AIN0 GP1 AIN1 GP2 AIN2 GP3 DIN0 GP4 Serial IO GP5 Serial Enable */ #INCLUDE <12ce673.h> #USE DELAY (CLOCK=4000000) #INCLUDE "eeprom.c" // EEPROM access routines //#INCLUDE "ccs_e~a5.c" /* FUSE Configuration INTRC - Use internal RC oscillator MACLR - Disable MCLR input (make std input pin) PUT - Enable Power Up Timer NOWDT - Disable Watchdog Timer NOPROTECT - No Code Protect */ #FUSES INTRC, PUT, WDT, PROTECT, NOMCLR #USE FAST_IO(A) #ZERO_RAM // Saves us any trouble! // Buffer only needs to be 16 - we'll give it 20 #define BUFFER_SIZE 20 struct porta_map { boolean AIN0; boolean AIN1; boolean AIN2; boolean DIN0; boolean serial_io; boolean serial_enable; int not_used : 2; } porta; #byte porta_reg = 0x05 #byte OSCCAL = 0x0F #byte ADCON1 = 0x9F #byte TRIS = 0x85 // Port A Direction Register byte CONST adc_conf[4] = {7,6,4,2}; // Globals // Network packet buffer char buffer1[BUFFER_SIZE]; int serial_idx, check_val, idx, scratch, process_idx, byte_change, last_query; char thisnode[4]; boolean s_idle; // When TRUE, we are NOT receiving a packet boolean go_data; // When TRUE, we have a packet to munch boolean checksum; // Serial routine void get_packet() { 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 // Tuned for 12C671 7/20/98 // We'll receive the entire packet in here and then return to have it processed go_data = FALSE; check_val = 0; while (!go_data) { // Lets receive the packet! // Wait for serial line to go high if it is low from previous bit while (!porta.serial_io) { restart_wdt(); } // Wait for start bit while (porta.serial_io) { restart_wdt(); // Grab any short input pulses (we'll wipe out bits 4-8 later) byte_change |= porta_reg ^ last_query; } delay_us(69); // Need to delay 1.5B (156us) We wait 69 here since we wait // another 76 in the loop plus 11 inst before middle sample // 8/00 Lowered to 66 since start_bit loop has 5 inst and // the start bit can arrive anytime during that 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 // Grab any short input pulses (we'll wipe out bits 4-8 later) byte_change |= porta_reg ^ last_query; restart_wdt(); delay_us(72); // 104us - 32us of instructions (32 inst) for loop = 72us // Lets sample over 15us samp_cnt = porta.serial_io; delay_us(3); if (porta.serial_io) { samp_cnt++; } delay_us(3); if (porta.serial_io) { samp_cnt++; } delay_us(3); shift_right(&data_in, 1, bit_test(samp_cnt,1)); // If samp_cnt = 0|1 -> 0 2|3 -> 1 } // 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; // Lets get the correct offset for the address/node name if (buffer1[0] == '#') { process_idx = 4; checksum = TRUE; } else { process_idx = 2; checksum = FALSE; } } } else { if (++serial_idx == BUFFER_SIZE) { // Buffer Overflow - lets dump it since the checksum won't validate anyway s_idle = TRUE; } else { // 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; // Lets check character against node address if necessary if ((serial_idx >= process_idx) && (serial_idx < (process_idx + 4))) { // Its an address character - lets check it if (thisnode[(serial_idx-process_idx)] != data_in) { // It isn't our packet - break out and wait for next one s_idle = TRUE; } } // Lets add this into the checksum value if (checksum) { // Lets add the char to the checksum value if needed if (serial_idx >= 9) { check_val += 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 // Send RS485 enable bit high // Send start bit (LOW) // Delay 104us // Shift data bits (8) onto serial out - wait 104us // Send stop bit (HIGH) // Delay 104us for stop bit porta.serial_enable = 1; // Put receiver output in TRISTATE bit_clear(tris, 4); // Enable the serial output // delay_cycles(1); restart_wdt(); // For safety and 1 cyc delay porta.serial_io = 0; // Delay 104us minus 9 inst cycles to get first bit out - 9us = 94 delay_us(95); for(bitc = 0; bitc < 8; ++bitc) { // Lets send the bits porta.serial_io = shift_right(&byte_in,1,0); // grab any port change byte_change |= porta_reg ^ last_query; // Loop takes 16 instructions. @4MHz - 16us (104 - 16 = 88us) delay_us(88); } // Now we send the stop bit and re-enable the interrupts delay_us(4); // Allow last bit to finish 104us porta.serial_io = 1; // Delay for the rest of the stop bit time delay_us(99); // Next 2 inst plus return = 5us bit_set(tris, 4); // Disable the serial output porta.serial_enable = 0; // Enable 75176 output 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, num_adcs; boolean byte_query; char data_char[8]; // Need to setup internal oscillator calibration!!!! // We call 3FF, get the value in W and write it to OSCCAL (85) #ASM call 0x3FF bsf 03,5 movwf OSCCAL bcf 03,5 #ENDASM set_tris_a(0b11011111); // All inputs except for serial enable porta.serial_enable = 0; last_hcs_data = 0x00; byte_change = 0x00; last_query = porta_reg; // Grab stored config - function returns 1 on valid read // Store address array thisnode[0] = 'M'; thisnode[1] = 'A'; thisnode[2] = 'N'; eeaddr = 0x00; // Setup EEPROM address while(!access_eeprom(READ_RANDOM)) {} if (eedata > 7) { eedata = 0; eeaddr = 0; access_eeprom(WRITE_BYTE); // Save default value } // Set to default 0 if out of bounds thisnode[3] = eedata; // EEPROM puts requested data into eedata // Now we grab the ADC info access_eeprom(READ_CURRENT); if (eedata > 3) { eedata = 3; eeaddr = 1; access_eeprom(WRITE_BYTE); // Save default value } // Set to 3 ADCs if out of bounds num_adcs = eedata; // Lets setup the ADC // num_adcs will dictate how many Analog inputs and digital I/O ports we have ADCON1 = adc_conf[num_adcs]; SETUP_ADC(ADC_CLOCK_DIV_8); // Gives us a 2us per bit conversion time // 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' + ' ' + 'M' + 'A' + 'N' + ' '; node_total += thisnode[3]; // Clear out the data_char array with ASCII 0's for (idx=0;idx<8;idx++) { data_char[idx] = '0'; } // Initial values for serial and buffer handling s_idle = TRUE; go_data = FALSE; Buffer_Scan: // Lets call the serial input routine and wait for a valid packet get_packet(); // Okay, lets process the packet endbuff = serial_idx; // So we know where the end of the buffer is! // If necessary, lets check the checksum value using the value we have // from the get_packet routine plus some preset values for stuff that won't change if (checksum) { check_val += ((gethexbyte(buffer1[1]) << 4) | gethexbyte(buffer1[2])); check_val += node_total; // Add in the chars that don't change // Need the lower 8 bits to compare if (check_val != 0) { // Bad Checksum goto Buffer_Scan; } } // Lets set process_idx to the start of the command process_idx += 5; // It'll be 2 for no checksum and 4 for checksum // Command handling // The Mini-Link understands the QE and QC commands from the HCS // It also handles the bit set and direction commands from the HCS // Set bit directions MAN# SB XX // Set output byte: MAN# SO XX // Query Everything: MAN# QE // Query Config: MAN# QC (We return 0022 for Analog IO) // Set Config: MAN# CXY where X is Net Addrs (0-7) and Y is Num of ADCs (0-3) switch(buffer1[process_idx++]) { case('S'): // Looks like a set command. If we have Digital IO, lets handle it // If we have 3 analog ports, we can skip this! if (num_adcs == 3) { // Later! break; } // Lets grab the hex value and put it into scratch scratch = ((gethexbyte(buffer1[process_idx+2]) << 4) | gethexbyte(buffer1[process_idx+3])); switch(buffer1[process_idx]) { case('O'): // Lets set the output bits as necessary (0-2 ONLY) // Lets wipe out any set bits in Analog input positions // This way we leave them as 0's and only set those bits in digital IO spots // This will also reassert the serial enable to 0 which is okay // We'll end up setting the latches on all ports but it won't matter since reads // come straight from the pin itself. scratch &= adc_conf[num_adcs]; // Now lets write to the port. // Analog Ins unaffected due to direction register porta_reg = scratch; last_hcs_data = scratch; // Save for Query Everything last_query = scratch; byte_change = 0; break; case('B'): // Lets set the bit direction for those digital IO pins we have // Take any configured analog inputs into account and leave them ALONE! // 0 is an output, 1 is an input. // Serial lines must not be touched and analog ports must stay inputs (1's) // Thus we have 11011111 where the functs are xxSSDAAA // OR scratch with 1's complement of adc_conf to ensure Analog inputs stay input // AND it with 0b11011111 to keep serial enable an output scratch |= ~adc_conf[num_adcs]; scratch &= 0b11011111; set_tris_a(scratch); break; default: break; } break; case('Q'): // Now lets see if it is a config query or everything if (buffer1[process_idx] == 'C') { // Easy - we can't change our stripes // Should we send back a checksum? if (checksum) { // Lets build the checksum. We'll cheat and use some predetermined values here scratch = '$'+'0'+'0'+' '+'M'+'A'+'N'+' '+'C'+':'+'0'+'0'+'2'+'2' ; // In 8 bits only! scratch += thisnode[3]; // Now lets take the 2's complement of the checksum subtotal result scratch ^= 0xFF; scratch++; } byte_query = FALSE; } else { // Its a query everything. Lets read the ports we need to read and build the // response. byte_query = TRUE; // Lets read the ADCs and store the ASCII chars in the data_char array // We read only the ADCs we have configured scratch = 0; for(idx = 0; idx < num_adcs; idx++) { set_adc_channel(idx); delay_us(20); // Allow the analog cap to charge properly (docs give us 11.64us for 10k impedance) data = read_adc(); // The conversions below give us our 2.0Tad delay for the next analog read // Convert the value to ASCII data_char[scratch++] = byte_to_ascii(data >> 4); data_char[scratch++] = byte_to_ascii(data & 0x0F); } // Lets read the digital port and return it if need be. // We read the ports (Analogs read as 0's anyway) // For now lets read the digital port // We include any short pulses // Lets check the port one last time byte_change |= porta_reg ^ last_query; last_query = (last_query ^ byte_change) & 0x0F; data_char[6] = byte_to_ascii(last_query); data_char[7] = byte_to_ascii(last_hcs_data & 0x07); byte_change = 0; // Should we send back a checksum? if (checksum) { // Lets build the checksum. We'll cheat and use some predetermined values here scratch = '$'+'0'+'0'+' '+'M'+'A'+'N'+' '+'I'+':'+'0'+' '+'O'+':'+'0'+' '+'A'+'0'+':'+' '+'A'+'1'+':'+' '+'A'+'2'+':'+' '+'A'+'3'+':'+'0'+'0'+' '+'A'+'4'+':'+'0'+'0'+'0'+' '+'A'+'5'+':'+'0'+'0'+'0'+' '+'D'+'0'+':'+'0'+'0'+'0'+' '+'D'+'1'+':'+'0'+'0'+'0'; // In 8 bits only! scratch += thisnode[3]; for(idx = 0; idx < 8; idx++) { scratch += data_char[idx]; } // Now lets take the 2's complement of the checksum subtotal result scratch ^= 0xFF; scratch++; } } // Lets send the response delay_ms(49); // The above code can take almost 1ms send_serial_byte('$'); if (checksum) { printf(send_serial_byte, "%2X", scratch); } printf(send_serial_byte, " MAN%c ", thisnode[3]); // Now we either send the default config response or the long query everything if (!byte_query) { // Lets send back the standard config line printf(send_serial_byte, "C:0022\n"); } else { // Lets send back the data stored in data_char // 0-1=A0, 2-3=A1, 4-5=A2, 6=DI, 7=DO // Here we send any digital data if it exists printf(send_serial_byte, "I:0%c O:0%c", data_char[6], data_char[7]); // Lets send each analog value stored in ASCII form // 00 is output for any Analog Input not configured! scratch = 0; for(idx = 0; idx < 3; idx++) { // Output A#:XX printf(send_serial_byte, " A%1X:%c%c", idx, data_char[scratch++], data_char[scratch++]); } printf(send_serial_byte, " A3:00 A4:000 A5:000 D0:000 D1:000\n"); } break; case('C'): // New configuration! // The next two bytes are the info we need to save // Note we force the bytes into the ranges when we convert frmo ASCII eedata = buffer1[process_idx++] & 0x07; // Net Addr idx = buffer1[process_idx] & 0x03; // # of ADCs to configure eeaddr = 0; access_eeprom(WRITE_BYTE); eedata = idx; access_eeprom(WRITE_BYTE); // Once that is done, lets reset by timing out watchdog while(TRUE) {} break; default: // Do nothing break; } // switch(buffer1[process_idx++]) goto Buffer_Scan; } // End main