This PIC keyer was a fun project. It has simple features so far, just mode A or mode B, an option to swap the dit and dah inputs via software, and a sidetone output. I made a circuit board for the keyer which took a lot of time. The board was designed using EXPRESS PCB software. I put the entire design on side 1 making what is now refered to as a Pittsburg style board. Rather than send for proto boards, I printed out the board layout on paper, but found I could not print without the grid showing.
I cut the paper printout with a hobby knife to make a stencil, which did not work very well as the ink ran under the stencil when I attempted to draw the etches. I cleaned up the bad areas with alchohol and drew the board mostly freehand under a magnifing glass.
The program was written in C and compiled using my own compiler. I was quite pleased the way this program came together.
The highlights of the program design are:
if( swap == 0 ) elcount = ( cel == DAH ) ? dahcount: ditcount; else elcount = (cel == DAH ) ? ditcount: dahcount;
Program Listing:
/* pic keyer (C) 2003 Ron Carr B port all inputs with pullups enabled dah bit 7 dit bit 6 swap bit 1 mode bit 0 A port all outputs tx enable bit 1 sidetone bit 0 */ /* clock speed and timer count determin sidetone freq and keying speed. Aiming for 800 hz sidetone. Think want 400 khz clock and timer value of 64. may need to reduce timer value by amount of instructions in interupt routine. R/C values R= C= */ /* simple commands speed change - 10 or more dits in a row - increase speed 10 or more dahs in a row - decrease speed */ #include "keyer.h" #define DAH 128 /* bit 7 on B port */ #define DIT 64 /* bit 6 on B port */ #define MODEA 0 #define MODEB 1 /* bit 0 on B port */ /* intcon values */ #define CHANGE_EN 0x08 #define TIMER_EN 0xa0 /* timer value to load 256 - 64 */ #define TIMEVAL 160 /* eeprom values */ extern char EEDITCOUNT = 72; /* about 13 wpm */ extern char EEDAHCOUNT = 216; /* globals */ char cel; /* current element */ char nel; /* next element */ char mask; /* mask for A port, enables tx and sidetone */ char toggle; /* interupt half count, for generating a tone */ char counter; char mode; char swap; /* swap DIT and DAH port definitions */ char fmask; /* 1st half el time mask */ char lmask; /* 2nd half el time mask */ char ditcount; /* terminal values for counter */ char dahcount; char halfcount; /* sample time */ char elcount; char ttlel; /* for speed change routine */ /* for interupt temp storage */ char wtemp; char stemp; /* set up ports, init key variables */ init(){ cel= nel= mask= toggle= 0; ditcount= EEDITCOUNT; dahcount= EEDAHCOUNT; PORTA= 0; PORTB= 0; TRISA= 0; TRISB= 0xff; OPTION_REG= 0x08; /* ?may need prescaler 0, maybe not 8 ? */ /* pullups are enabled */ } /* gen side tone and increment a counter */ _interupt(){ /* !!!! check generated code does not use _temp, indirect register, shift, or EEPROM reads. If so then will need to save other data */ /* save W register, clear interupt status */ #asm movwf wtemp swapf STATUS,W movwf stemp; #endasm INTCON= 0x20; /* timer enable only, clear int status */ /* generate the side tone, A port bit 0 */ toggle ^= 1; PORTA= ( toggle + 2 ) & mask; /* count at half interupt rate, avoid overflow */ counter= ( counter == 254 ) ? 254 : counter + toggle; TMR0= TIMEVAL; /* value to count from */ /* restore W register */ #asm swapf stemp,W movwf STATUS swapf wtemp,F swapf wtemp,W #endasm } /* return enables interupts */ /* loops via the .h code */ main(){ /* code needs to handle bounces on the contacts nothing to do ( on powerup probably no paddle is closed ) */ /* start up counter interupts */ INTCON= TIMER_EN; cel= readpdl(); /* we wokeup, get which paddle */ mode= PORTB & 1; /* 0 mode A, 1 mode B */ /* swap- xor of zero gives normal operation, xor 192 swaps. ie 128 ^ 192 == 64. 64 ^ 192 == 128 ( dit+dah = 192 ) */ swap= ( PORTB & 2 ) ? 0 : DIT + DAH; /* 0 swap dit and dah, hi normal */ /* speed change reversed if swap is used */ while( cel ){ if( cel == (DIT + DAH) ) cel= DIT; /* dit wins if ever a tie */ elcount = ((cel ^ swap) == DAH) ? dahcount: ditcount; mask= 3; /* enable tx and sidetone */ fmask= counter= 0; lmask= (DIT + DAH) - cel; /* sample opposite only 2nd half */ if( mode == MODEA && readpdl() == ( DIT + DAH ) ) lmask= 0; wait4it(); counter= mask= 0; /* send space */ elcount= ditcount; fmask= lmask; /* continue with selected sample type for */ wait4it(); /* the complete element space time */ /* last sample */ if( nel == 0 ) nel= readpdl(); /* toggle may be needed for mode A iambic */ if( nel == ( DIT + DAH ) ) nel-= cel; /* speed change routine */ if( cel == nel ) ++ttlel; else ttlel= 0; if( ttlel >= 10 ) speedchange(cel); cel= nel; nel= 0; /* send whatever in queue */ } /* end something to send */ /* wait short time for more contacts on switches, also if switch bounces, we will catch the contact here */ /* could provide other functions on Bport with switches and read them here instead of calling readpdl */ counter= 0; while( counter < EEDAHCOUNT ){ if( readpdl() ) return; /* re-enters main(), skips sleep */ } /* nothing happening so power down sleep with just port b change enabled, global interupt disabled just wakes up, no branch to interupt code */ INTCON= CHANGE_EN; #asm sleep nop nop #endasm /* INTCON= 0; */ /*(redundant) disable ints,clear change bit */ } /* loops via .h code back to main() */ char readpdl(){ return ( PORTB ^ 0xff ) & ( DIT + DAH ); } /* wait for element to be sent sample paddles - mode B sample other paddle anytime after half element sent sample same paddle after space sent mode A both closed at start --> only sample both at end of space and toggle if still have both. else same as mode B */ wait4it(){ halfcount= elcount >> 1; while( counter < elcount ){ if( nel == 0 ){ nel= readpdl(); if( counter >= halfcount ) nel &= lmask; else nel &= fmask; } } /* end while */ } speedchange( char updown ){ if( updown == DIT ){ --ditcount; dahcount-= 3; } else{ ++ditcount; dahcount+= 3; } /* overflow ? */ if( dahcount < 30 || dahcount >= 254 ){ ditcount= EEDITCOUNT; dahcount= EEDAHCOUNT; } }