//
//
#include <htc.h>
typedef unsigned char Byte;
typedef unsigned int Word;
// pins
#define SwServo1 RC5 // Servo output
#define LED1 RB4 // LED output
#define Button1 RB5 // Button input
#define LED2 RB6 // LED output
#define Button2 RB7 // Button input
#define ExtSwitch RA2 // Ext Switch input
// Configuration bits (set in MPLAB/Cofigure/Configuration Bits)
// OSC Internal RC No Clock
// WDT Off
// PUT Off
// MCLRE Internal
// CP Off
// CPD off
// BODEN BOD and SBOREN disabled
// IESO Enabled
// FCMEN Enabled
// Constants
//
#define VersionNo 0x28
// servo
#define ServoCenter 1500 // = 1.500 ms
#define ServoMax 900 // = .9 ms
#define ServoMin 2100 // = 2.1 ms
#define Pressed 0
#define NotPressed 1
#define Armed 1
#define NotArmed 0
#define Closed 1
#define Thrown 0
#define SwitchOn 1
#define SwitchOff 0
#define SpeedMax 100
#define SpeedMin 1
bit isr_flag;
bit doRestore;
bit servosOn;
bit but1Armed;
bit but2Armed;
bit train1;
bit train2;
bit trainSpeed;
bit s1State;
bit lastSwitch;
Byte version;
Word servoThrown;
Word servoClosed;
Word servo1Pulse = ServoCenter;
Byte servoSpeed = 10;
Word temp;
Word sPos1 = ServoCenter;
Byte but1Timer;
Byte but2Timer;
Byte moveTimer;
Byte flashLED1;
Byte flashLED2;
Byte LEDTimer = 1;
Byte switchTimer = 1;
// Subroutine declarations
void move_servo(void);
void set_servo(void);
void save_sys(void);
void restore_sys(void);
void delay( Word t );
void check_buttons(void);
void set_servos(void);
void check_short(void);
void LED_handler(void);
void DCC_receive(void);
void interrupt isr_routine(void)
{
if (T0IF){ // is this is a timer0 interrupt?
isr_flag = 1; // set flag to let main routine know intterrupt occured
temp = 0xFFFF - sPos1; // calculate timer1 value
TMR1ON = 0; // turn off timer1 while changing it
TMR1L = temp&0x00ff; // set timer1 low bits
TMR1H = temp>>8; // set timer1 high bits
TMR1ON = 1;
SwServo1 = 1; // turn on servo
TMR0 = 256-155; // reset timer0
T0IF = 0; // clear intterrupt flag
} else { // otherwise it must be a timer 1 intterrupt
TMR1IF = 0; // clear the interrupt flag
SwServo1 = 0; // turn off servo
move_servo(); // ramp the servo
}
}
main()
{
// Setup starts here
OSCCON = 0b01110000; // 8Mhz Internal Clock
PORTA = 0; // clear PortA
PORTB = 0; // clear PortB
PORTC = 0; // clear PortC
ANSEL = 0; // turn off analog channels
ANSELH = 0; // turn off analog channels
TRISA = 0b11111111; // set RA0 to an output
WPUA = 0b0000100; // enable weak pullups on PORTA
TRISB = 0b10101111; // set port B I/O directions
WPUB = 0b10100000; // enable weak pullups on PORTB
TRISC = 0b11011111; // set port C I/O directions
OPTION = 0b01000111; // TMR0 prescaler set to 256
temp = 0xFFFF - sPos1; // calculate timer1 for servo pulse
TMR1H = temp>>8;
TMR1L = temp&0x00FF;
T1CON = 0b00010001; // TMR1 prescaler = 2, TMR1 on
TMR0 = 256-155; // calculate timer0 for 20 ms delay
TMR1IE = 1; // enable timer 1 interrupt
T0IE = 1; // enable timer 0 interrupt
PEIE = 1; // peripheral interrupts are enabled
GIE = 1; // global interrupt enabled
T0IF = 1; // force timer0 interrupt right away to start servo
flashLED1 = 2; // flash LEDs
flashLED2 = 2;
// reset memory to defaults if either button pressed on power up
if( Button1 == Pressed ) {
delay(5);
if (Button1 == Pressed ){ // Check twice to avoid noise spikes
doRestore = 1;
but1Timer = 51;
}
} else if(Button2 == Pressed ) { // Check the other button
delay(5);
if (Button2 == Pressed ){ // Check twice to avoid noise spikes
doRestore = 1;
but2Timer = 51;
}
}
restore_sys(); // restore the values from memory
// init the servos to stored values
if( s1State == Closed ) servo1Pulse = servoClosed;
else servo1Pulse = servoThrown;
sPos1 = servo1Pulse;
lastSwitch = ExtSwitch; // external switch setup
// end of setup
// Wait for an interrupt then poll inputs - forever...
while(1){ // this loop repeats infinitely
if (isr_flag) { // wait for ISR flag
isr_flag = 0;
check_buttons(); // check for button changes
LED_handler(); // handle the leds
//move_servo(); // ramp the servo a bit
}
}
}
// This subroutine ramps the servos up or down to make sPos1 = servoPulse
void move_servo(void){
if (sPos1 < servo1Pulse ){
sPos1+= servoSpeed;
if (sPos1 > servo1Pulse) sPos1 = servo1Pulse;
} else if (sPos1 > servo1Pulse ){
sPos1-=servoSpeed;
if (sPos1 < servo1Pulse) sPos1 = servo1Pulse;
}
}
// This subroutine sets the servos according to s1State
void set_servo(void)
{
if ( s1State == Closed) {
servo1Pulse = servoClosed;
flashLED1 = 2;
} else {
servo1Pulse = servoThrown;
flashLED2 = 2;
}
save_sys(); // remember the servo states
}
//this subroutine handles the LED according to the values in flashLED1/2
// also checks to see if we are in training or program mode and flahes leds accordingly
void LED_handler(void)
{
LEDTimer--;
if ( LEDTimer == 0){
if( trainSpeed ) { // if training speed flash both LEDs
if( flashLED1 == 0 ) flashLED1 = 2;
if( flashLED2 == 0 ) flashLED2 = 2;
} else if ( train1 ) { // if training endpoint1 flash LED1
if( flashLED1 == 0 ) flashLED1 = 2;
LED2 =0;
} else if ( train2 ) { // if training endpoint2 flash LED2
if( flashLED2 == 0 ) flashLED2 = 2;
LED1 = 0;
} else { // otherwise set the LED to sState if not flashing an LED
if ( flashLED1 == 0 ) LED1 = s1State;
if ( flashLED2 == 0 ) LED2 = ~s1State;
}
if ( flashLED1 > 0 ) {
// if flashLED is even light the LED
if ((flashLED1 & 0x01) == 0) {
LED1 = 1;
} else { // if it is odd turn it off
LED1 = 0;
}
flashLED1--; // subtract 1
}
if ( flashLED2 > 0 ) {
if ((flashLED2 & 0x01) == 0) {
LED2 = 1;
} else {
LED2 = 0;
}
flashLED2--;
}
LEDTimer = 10; // wait 10 more interrupt cycles = 0.2 seconds
}
}
// this subroutine handles button presses
// if not in training mode then a brief push of the buttons changes to that servo state
// long presses (> ~0.5 sec) puts us in training mode where the buttons now
// are used to adjust the servo endpoints
// finally, the sub checks to see if the remote switch input has changed
// the remote switch allos for using more than one switch to change the turnout
// useful on a module where a switch is needed on both sides
void check_buttons(void)
{
if( but1Timer == 50 ) {
if ( trainSpeed ) { // already in program mode so end
trainSpeed = train1 = train2 = 0;
} else if ( but2Timer > 0 ) {
// both buttons - DCC programming mode
trainSpeed = 1;
train1 = train2 = 0;
but2Timer = 51; // inhibit short press
} else {
// train servo endpoit 1 mode (train1)
train1 = ~train1; // enter opposite mode
train2 = trainSpeed = 0;
s1State = Closed;
set_servo();
}
but1Timer = 51; // prevents interpreting as short press
}
if( but1Armed == Armed ) { // short press
if( train1 ==1) { // increase servoClosed
servoClosed = servoClosed + 5;
servo1Pulse = servoClosed;
save_sys();
} else if (train2 == 1 ){ // increase servoThrown
servoThrown = servoThrown + 5;
servo1Pulse = servoThrown;
save_sys();
} else if (trainSpeed == 1){ // increase speed
servoSpeed += 5;
if( servoSpeed >= SpeedMax ) servoSpeed = SpeedMax;
s1State = ~ s1State; // change servo so user can see the change in speed
set_servo();
} else {
s1State = Closed;
set_servo();
}
but1Armed = NotArmed;
}
// button2
if( but2Timer == 50 ) {
if ( trainSpeed ) { // already in program mode so end
trainSpeed = train1 = train2 = 0;
} else if ( but1Timer > 0 ) {
// both buttons - DCC programming mode
trainSpeed = 1;
train1 = train2 = 0;
but1Timer = 51; // inhibit short press
} else {
// train servo endpoit 2 mode (train2)
train2 = ~train2; // enter opposite mode
train1 = trainSpeed = 0;
s1State = Thrown;
set_servo();
}
but2Timer = 51;
}
if( but2Armed == Armed ) {
if( train1 ==1) { // decrease servoClosed
servoClosed = servoClosed -5;
servo1Pulse = servoClosed;
save_sys();
} else if (train2 == 1 ){ // decrease servoThrown
servoThrown = servoThrown -5;
servo1Pulse = servoThrown;
save_sys();
} else if (trainSpeed == 1){ // decrease speed
if (servoSpeed >= SpeedMin + 5 )servoSpeed -= 5;
else servoSpeed = SpeedMin;
s1State = ~ s1State; // change servo so user can see the change in speed
set_servo();
} else {
s1State = Thrown;
set_servo();
}
but2Armed = NotArmed;
}
// re-arm buttons
if (Button1 == NotPressed){
if(but1Timer > 0 ) { // found falling edge since timer > 0
if ( but1Timer < 50 ) {
// if timer> 0 but less than 50 then this is a short press
but1Armed = Armed;
}
}
but1Timer = 0; // reset timer
} else {
if ( but1Timer < 0xff ) but1Timer++;
}
if (Button2 == NotPressed){
if(but2Timer > 0 ) { // found falling edge since timer > 0
if ( but2Timer < 50 ) {
// if timer> 0 but less than 50 then this is a short press
but2Armed = Armed;
}
}
but2Timer = 0; // reset timer
} else {
if ( but2Timer < 0xff ) but2Timer++;
}
// check the remote switch
// change state if edge detected outside of 25*.20 second limit
if ( switchTimer > 0 ) {
switchTimer--;
}
if ( switchTimer == 0 ) {
if ( ExtSwitch != lastSwitch ) {
s1State = ~s1State;
set_servo();
switchTimer = 25;
}
}
lastSwitch = ExtSwitch;
}
// delay x milliseconds (approximate!)
// especially if an interrupt occurs in the middle
Byte delcntr;
void delay(Word t)
{
Word i;
for(i=0; i<t*2; i++){
#asm
clrf _delcntr
delay1 nop
;nop
incfsz _delcntr
goto delay1
#endasm
}
}
// Reads saved values from EEPROM memeory for
// servo throw endpoints, servo speed and servo state
// If doRestore is set uon entry, or if the memory
// has never been written to, then it sets these values to
// the factory defaults.
void restore_sys (void)
{
if (doRestore) {
restore:
// restore to factory defaults
doRestore = 0;
version = VersionNo;
s1State = Closed;
servoThrown = ServoCenter - 30; // small movement so we can tell its alive
servoClosed = ServoCenter + 30;
servo1Pulse = servoClosed;
servoSpeed = 10;
save_sys(); // save the new values
flashLED1 = 4; // lots of flashses so we know it happened
flashLED2 = 4;
return;
} else {
// version is an arbitrary number to mark th memory as previously written to
version = eeprom_read( 0x00 );
if( version != VersionNo ) goto restore; // blank EEPROM - restore from
defaults
servoThrown = eeprom_read( 0x03 );
servoThrown += (Word)(eeprom_read( 0x04 )<<8);
servoClosed = eeprom_read( 0x05 );
servoClosed += (Word)(eeprom_read( 0x06 )<<8);
servo1Pulse = eeprom_read( 0x07 );
servo1Pulse += (Word)(eeprom_read( 0x08 )<<8);
s1State = (bit)eeprom_read( 0x09 );
servoSpeed = eeprom_read( 0x0A );
}
}
// Writes the servo values to EEPROM
// Make sure that the memory laocatsion are in synce between
// restore_sy() and save_sys()
// Plenty of room left in the 256-byte EEPROM (PIC16F690)
// for saving other variables
void save_sys(void){
eeprom_write(0x00, version);
eeprom_write(0x03, (Byte)(servoThrown&0x00FF));
eeprom_write(0x04, (Byte)(servoThrown>>8));
eeprom_write(0x05, (Byte)(servoClosed&0x00FF));
eeprom_write(0x06, (Byte)(servoClosed>>8));
eeprom_write(0x07, (Byte)(servo1Pulse&0x00FF));
eeprom_write(0x08, (Byte)(servo1Pulse>>8));
eeprom_write(0x09, (Byte)s1State);
eeprom_write(0x0A, servoSpeed);