A personal repository of random information in compensation for a fatigued biological computer
My Basic PIC18F 40 pin PDIP breakout PCB as used previously. This one has a PIC16F4685 in it.
Purpose of this module is to interface commands received from Mach3 via the parallel port to our custom hardware
20120306 OK, have made a significant project change of direction and the controlling software is now KPlace per smtplace.com
KPlace directly controls the Head Z actuation & Vacuum through a Dynomotion KFLOP controller board, refer my KFLOP page in this project for details
This only allows for a single head, with automated feeders controlled by serial port.
This PIC18F PCB will receive a single BYTE command over a serial connection before activating the required feeder ie receive '0' activate feeder 0 etc etc
The PIC UART RX & TX pins will be connected to a FT232RL Serial to USB converter (A small PCB moduleI home 4 years ago that is lying around unused)
PIC18F4685 TX is on RC6 & RX is on RC7
#include <18F4685.h> #include <string.h> #include <math.h> #include <stdlib.h> #FUSES NOWDT //No Watch Dog Timer #FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale #FUSES H4 //High speed osc with HW enabled 4X PLL //#FUSES INTRC_IO //Internal RC Osc, no CLKOUT #FUSES NOPROTECT //Code not protected from reading #FUSES BROWNOUT //Reset when brownout detected //#FUSES BORV25 //Brownout reset at 2.5V #FUSES PUT //Power Up Timer #FUSES STVREN //Stack full/underflow will cause reset #FUSES NODEBUG //No Debug mode for ICD #FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O #FUSES NOWRT //Program memory not write protected #FUSES NOIESO //Internal External Switch Over mode disabled #FUSES NOFCMEN //Fail-safe clock monitor disabled #FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET #FUSES NOWRTC //configuration not registers write protected #FUSES NOWRTB //Boot block not write protected #FUSES NOEBTR //Memory not protected from table reads #FUSES NOEBTRB //Boot block not protected from table reads #FUSES NOCPB //No Boot Block code protection #FUSES NOLPT1OSC //Timer1 configured for higher power operation #FUSES MCLR //Master Clear pin enabled //#FUSES XINST //Extended set extension and Indexed Addressing mode enabled #use delay(clock=40000000) #use rs232(baud=115200, xmit=PIN_C6, rcv=PIN_C7) #define FEEDERACTIVEDELAYMILLISECS 2500 int32 ISRupTimeSecs; int16 ISRupTimeMilliSecs; int32 upTimeSecs; int16 upTimeMilliSecs; int1 feederActive; int16 feederActiveMilliSecs; int receiveByteCount; int receivedByte; int receivedDigit1; int receivedDigit2; int receivedFeeder; int1 commandReceived; int currentFeeder; int i; // 20120306 // Inputs // RC6 & RC7 Serial command: // 3 bytes: // - byte 1: 'f' or 'F' // - bytes 2-3: zero padded feeder no to activate //Outputs // D0-3/D4-7 feeder activation // - D0-6 select the target feeder // - D7 is a separate activate (active high) // E0 HeartBeat Led void feederActivate(int feeder, int activate) { if (activate) { output_D(feeder); output_D(feeder | 0b10000000); feederActiveMilliSecs = 0; feederActive = true; } else { output_D(feeder & 0b10000000); feederActive = false; } } #int_timer0 void timer0_isr(void) { set_timer0(100); //256-156? if (++ISRupTimeMilliSecs == 1000) { ISRupTimeMilliSecs = 0; ISRupTimeSecs ++; } if (feederActive) { feederActiveMilliSecs++; } } #int_rda void serial_isr() { receivedByte = getc(); if (receivedByte == 'f' || receivedByte == 'F') { receiveByteCount = 1; } else if (receiveByteCount == 1) { receivedDigit1 = receivedByte; receiveByteCount = 2; } else if (receiveByteCount == 2) { receivedDigit2 = receivedByte; receiveByteCount = 0; receivedFeeder = 0; if (receivedDigit1 >= '0' && receivedDigit1 <= '9') { receivedFeeder = 10 * (receivedDigit1 - '0'); if (receivedDigit2 >= '0' && receivedDigit2 <= '9') { receivedFeeder = receivedFeeder + receivedDigit2 - '0'; if (!feederActive) { commandReceived = true; } } } } } void main() { //------------------------------------- //Startup sequence setup_adc_ports(NO_ANALOGS); setup_adc(ADC_OFF); receiveByteCount = 0; feederActive= FALSE; commandReceived = false; currentFeeder = 0; upTimeMilliSecs = 0; upTimeSecs= 0; set_timer0(100); setup_timer_0(RTCC_INTERNAL|RTCC_8_BIT|RTCC_DIV_64); Output_D(0); Output_E(0); for (i = 0; i<32; i++) { output_bit(pin_e0,1); delay_ms(25); output_bit(pin_e0,0); delay_ms(25); putc('a'); Output_D(i); } // ext_int_edge(1,H_TO_L); enable_interrupts(GLOBAL); enable_interrupts(INT_TIMER0); enable_interrupts(int_rda); ///enable_interrupts(INT_EXT); currentFeeder = 0; // end startup sequence //------------------------------------- while(1) { uptimeSecs = ISRuptimeSecs; uptimeMilliSecs = ISRuptimeMilliSecs; if (uptimeSecs != ISRuptimeSecs) { // must've rolled over so get it again uptimeSecs = ISRuptimeSecs; uptimeMilliSecs = ISRuptimeMilliSecs; } output_bit(pin_e0,(upTimeMilliSecs < 500)); // testing cycle all feeders up to n /* if (!feederActive) { currentFeeder = (currentFeeder == 17) ? 0 : ++currentFeeder; delay_ms(250); feederActivate(currentFeeder, true); } */ if (feederActive && feederActiveMilliSecs >= FEEDERACTIVEDELAYMILLISECS) { feederActivate(currentFeeder, false); } if (commandReceived) { currentFeeder = receivedFeeder; feederActivate(currentFeeder, true); commandReceived = false; } } // end while(1) } //end main
The feeders on activating the index BTN pull back the current component cover. If the index btn is not released within about 2 seconds, the component cover then moves back over the current component with no additional feed. If the index btn is released within the 2 second window a component feed action is initiated.
Do I:
1. Need to be signal both these actions from Mach3?
Initial answer: no
2. Need the PIC to manage the timing of the release action to be within the 2 seconds?
Initial answer?: yes
In this initial MK2 version it needs to accept commands to:
- action 4 heads up/down (pneumatic solenoid valves via 4 way Mosfet PCB)
- action 4 head vacuums (vacuum solenoid valves via 4 way Mosfet PCB)
- action up to n Siemens automated feeders
Later MK2 iterations may need to:
- Rotate head or PCB holder
Analysis
From the parallel port (assuming only Enable, X dir, x step, y dir, y step outputs are already used) we have 7 free signal lines available for driving this PCB.
I considered creating a simple serial protocol to send commands, but with 7 data lines, I can instead send a 6 line parallel command and clock it in using the 7th line. This will keep the Mach3 VBA macros for sending the commands simpler and easier to monitor / debug etc
This allows 2^6 commands = 64
Commands from MACH3:
Action | First command | last command | |
Head n down | 0 | 3 | |
Head n up | 4 | 7 | |
Head n vacuum on | 8 | 11 | |
Head n vacuum off | 12 | 15 | |
Buzzer off | 16 | 16 | |
Buzzer on | 17 | 17 | |
Vacuum pump off | 18 | 18 | |
Vacuum pump on | 19 | 19 | |
unused | 20 | 31 | |
Feeder n activate | 32 | 63 |
By using the 7th output to activate the PIC, the remaining 6 lines could potentially still be used for other purposes (ie A axis) just not simultaneously
PIC pins
B0 = toggled to signal action current command (needs to be an interrupt)
B1 = unused as yet
B2-7 = current command:
- B4-7 = command bit 0-3
- B2-3 = command bit 4-5
RA0-3 unused as yet
RA4-5 unused as yet
RE0: Heartbeat/Indicator LED
RE1: Buzzer
RE2: Vacuum pump on/off (possibly)
C0-3 = head z movement solenoids x 4
C4-7 = head vacuum solenoids x 4
D0-3, D4-7 = automated feeder activation
When B0 interrupt is activated we will set flags for command activation in the main loop.
We will also use Timer 0 to as a timer count every millisecond for deactivating any timed commands in the main loop and also for the heartbeat LED
Reminder to self on PIC18F4685 timer0:
- Timer frequency = system clock = instruction cycles
- There is no Period Register, by default is 8/16 bit mode values of 255 or 65535 on overflow but we can preset the Timer0 register in the ISR to effectively use a PR
- Crystal is 10MHZ * HSPLL = 40MHZ = 10MHZ system clock, doesn't divide nicely by powers of 2 (prescaler values) so some inaccuracy.. so looking for best trade off of low overhead / accuracy
Did up a spreadsheet.. best result (highest prescaler, highest PR):
- In 8 bit mode, prescaler at 64, fudge PR to 156:
Interval = 1 sec / (10,000,000 / 64 * 156 ) = 0.0009984 seconds = 0.16% error, pretty good
For the feeders, we need to activate the feeder & hold it for just before the timeout to initiate a feed.
- will need to track the time the feeder was activated & automatically deactivate it when the timeout period is reached
- assume only every one feeder active at a time
Below is a WIP just here as a backup
#include <18F4685.h> #include #include #include #FUSES NOWDT //No Watch Dog Timer #FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale #FUSES H4 //High speed osc with HW enabled 4X PLL //#FUSES INTRC_IO //Internal RC Osc, no CLKOUT #FUSES NOPROTECT //Code not protected from reading #FUSES BROWNOUT //Reset when brownout detected //#FUSES BORV25 //Brownout reset at 2.5V #FUSES PUT //Power Up Timer #FUSES STVREN //Stack full/underflow will cause reset #FUSES NODEBUG //No Debug mode for ICD #FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O #FUSES NOWRT //Program memory not write protected #FUSES NOIESO //Internal External Switch Over mode disabled #FUSES NOFCMEN //Fail-safe clock monitor disabled #FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET #FUSES NOWRTC //configuration not registers write protected #FUSES NOWRTB //Boot block not write protected #FUSES NOEBTR //Memory not protected from table reads #FUSES NOEBTRB //Boot block not protected from table reads #FUSES NOCPB //No Boot Block code protection #FUSES NOLPT1OSC //Timer1 configured for higher power operation #FUSES MCLR //Master Clear pin enabled //#FUSES XINST //Extended set extension and Indexed Addressing mode enabled #use delay(clock=40000000) #define FEEDERACTIVEDELAYMILLISECS 2000 int32 ISRupTimeSecs; int16 ISRupTimeMilliSecs; int32 upTimeSecs; int16 upTimeMilliSecs; int1 feederActive; int16 feederActiveMilliSecs; int1 commandReceived; int command; int currentHead; int currentFeeder; int1 head0VacuumOn, head1VacuumOn, head2VacuumOn, head3VacuumOn; // 201201227 // Inputs // B0 is action current command // B2-7 is current command bits 0-5 //Outputs // C0-3 head pneumatic valves // C4-7 head vacuum valves // D0-3/D4-7 feeder activation // - D0-6 select the target feeder // - D7 is a separate activate (active high) // E0 HeartBeat Led // E1 Buzzer // E2 Mains Vacuum pump on / off void buzzerOn(int1 on) { output_bit(PIN_E1, on); } void mainVacuumOn(int1 on) { output_bit(PIN_E2, on); } void headDown(int head, int1 down) { switch (head) { case 0: output_bit(PIN_C0, down); case 1: output_bit(PIN_C1, down); case 2: output_bit(PIN_C2, down); case 3: output_bit(PIN_C3, down); } } void headVacuumOn(int head, int1 on) { switch (head) { case 0: output_bit(PIN_C4, on); case 1: output_bit(PIN_C5, on); case 2: output_bit(PIN_C6, on); case 3: output_bit(PIN_C7, on); } } void feederActivate(int feeder, int1 activate) { if (activate) { output_D(feeder); output_D(feeder & 0b100000000); feederActiveMilliSecs = 0; feederActive = true; } else output_D(0); feederActive = false; } void outputAllHeadVacuums(void) { headVacuumOn(0, Head0VacuumOn); headVacuumOn(1, Head1VacuumOn); headVacuumOn(2, Head2VacuumOn); headVacuumOn(3, Head3VacuumOn); } void setAllHeadVacuums(int1 setting) { Head0VacuumOn = Head1VacuumOn = Head2VacuumOn = Head3VacuumOn = setting; outputAllHeadVacuums(); } #int_timer0 void timer0_isr(void) { set_timer0(100); //256-156? if (++ISRupTimeMilliSecs == 1000) { ISRupTimeMilliSecs = 0; ISRupTimeSecs ++; } if (feederActive) { feederActiveMilliSecs++; } } #int_ext //B0 void B0_isr(void) { commandReceived = true; command = input_b() & 0b00111111; // only want 6 bits of command value } void main() { //------------------------------------- //Startup sequence setup_adc_ports(NO_ANALOGS); setup_adc(ADC_OFF); feederActive= false; commandReceived = false; command = 0; upTimeMilliSecs = 0; upTimeSecs= 0; set_timer0(100); setup_timer_0(RTCC_INTERNAL|RTCC_8_BIT|RTCC_DIV_64); Output_C(0); Output_D(0); Output_E(0); // Cycle through all the head selects so we can see startup on the // the Stepper controller PCB & cycle all vacuum solenoids for(CurrentHead = 0; CurrentHead<4; ++CurrentHead) { Output_C(1<= FEEDERACTIVEDELAYMILLISECS) { feederActivate(currentFeeder, false); } if (commandReceived) { if (command <= 3) { headDown(command, true); } else if (command <= 7) { headDown(command - 4, false); } else if (command <= 11) { headVacuumOn(command - 8, true); } else if (command <= 15) { headVacuumOn(command - 12, false); } else if (command <= 17) { buzzerOn(command - 16); } else if (command <= 19) { mainVacuumOn(command - 18); } else if (command <= 31) { // unused } else if (command <= 63) { currentFeeder = command - 32; feederActivate(currentFeeder, true); feederActive = true; feederActiveMilliSecs = upTimeMilliSecs; } } // end command response processing } // end while(1) } //end main
xxxx