/* The following program is a pic-based PSK31 sender. The 0.05 base version has PWM analog output. This is accomplished by sampling the output at 32 times the bitrate (i.e. 1 KHz) with a PWM signal. The PWM generator in the ISR has a total of 100 "counts." The input variable "PWM" varies from 1 to 99 - one portion of routine outputs a 1 for "PWM" counts while the other portion outputs a zero for "100-PWM" counts. For the rest of the time this output is in hi-Z state. The R/C filter on the output smooths the output and allows for a linear 0-5 volt output for a 1-99 input (0 and 100 are not used.) The sine table (SINTBL) goes from 28 to 78 to keep away from the supply rails and within the working range of the output buffer/amplifier. The "zero" output (for the balanced modulator) occurs at approximately mid-supply. The 0.09 base version has an input select (PB4) that, when low (grounded) selects the mode whereby PA3 outputs a PWM version as described above. When PB4 is open (high) PA3 outputs a modulation envelope for an outboard PA. In this case, when a 0 is to be transmitted (i.e. a phase shift) the PA3 output goes low, PB3 changes state (i.e. phase) and then PA3 goes high again. This allows the phase shift to occur when the output power is at (or very near) zero. Copyright 1999, 2000 by Clint Turner, KA7OEI Revision History: 0.01 - 19990905 - First working version 0.05 - 19990906 - Version with PWM analog output for sine-shaped output 0.07 - 19990910 - Version with another PWM output mode for modulation of an output power amp (see above) 0.09 - 19991025 - PB4 (mode select) inverted (leaving PB4 open for power-saving AM mode). Also transmits EEPROM contents 1.00 - 19991103 - First "released" version - still using R/C LPF. 1.01 - 20001104 - Fixed minor bug in EEPROM sending routine ("warble" wasn't sent between repeats if the message was exactly 64 characters) and revised the "waveform" tables to add "predistortion" to significantly improve the IMD numbers */ #OPT 9 #include <16c84.h> // define use of 16c84a #include "varicode.h" // include varicode table #define PORT_A_ADDR 0x05 // port A address #define PORT_A_TRIS 0x00 // port A I/O mask #define PORT_A_TRIS_A3I 0x08 // port A I/O mask with A3 as input (for PWM output) #define PORT_B_ADDR 0x06 // port B address #define PORT_B_TRIS 0x10 // port B I/O mask #define CALL_LEN 16 // length of "callsign" in bytes #define RTCC_CNT 39 // timer setting of RTCC loop for 1 KHz interrupt (32x bitrate) // - do not adjust this value! (error: approx +0.3 Hz) #byte PORT_A = 5 #byte PORT_B = 6 #fuses XT, WDT, NOPROTECT, PUT // standard xtal, watchdog, no code protect, Power Up Timer, no brownout protect #use delay(clock=3579545, RESTART_WDT) // calibrate delays for apparent clock speed // //#ROM 0x2100 = { 10, 13,'Y','o','u','r',' ','m','e','s','s','a','g','e',' ','g','o','e','s', //' ','h','e','r','e','.',' ','I','t',' ','m','a','y',' ','c','o','n','t','a','i','n',' ','u','p', //' ','t','o',' ','6','4',' ','c','h','a','r','a','c','t','e','r','s',' ',0xff } // #ROM 0x2100 = { 10, 13, 'v', 'v','v',' ','v','v','v',' ','v','v','v',' ','D','E',' ','c','t',' ','c','t', ' ','c','t',' ','c','t',' ','c','t',' ','c','t', ' ','c','t',' ','D','N','4','0','a','o', ' ','t','u','r','n','e','r','@','u','s','s','c','.','c','o','m',' ',0xff } // byte const sintbl[32] = { // for cosine modulation of balanced modulator 24, 24, 25, 27, 29, 30, 33, 35, 37, 40, 42, 44, 47, 49, 52, 54, 56, 59, 61, 64, 66, 68, 70, 72, 73, 75, 76, 77, 78, 79, 79, 79 }; // original waveform 28, 28, 29, 30, 31, 32, 34, 35, 37, 39, 41, 43, 46, 48, 51, 53, 55, 58, 60, 63, //65, 67, 69, 71, 72, 74, 75, 76, 77, 78, 78, 78 }; byte const pw_sine[32] = { // for power modulation cosinewave (when RB4 is left open (high)) 92, 92, 89, 88, 86, 82, 78, 73, 67, 61, 53, 46, 38, 32, 22, 13, 5, 23, 32, 40, 49, 57, 62, 68, 73, 77, 82, 84, 88, 90, 91, 92}; // original waveform 98, 96, 91, 86, 79, 71, 63, 54, 45, 37, 29, 22, 17, 12, 10, 9, 10, 12, 17, 22, //29, 37, 45, 54, 63, 71, 79, 86, 91, 96, 98, 99 }; #use fast_io(a) // set port A for fixed-mode of I/O direction #use fast_io(b) // set port B for fixed-mode of I/O direction #byte PORT_A = PORT_A_ADDR // define Port A as a memory location #byte PORT_B = PORT_B_ADDR // define Port B as a memory location // #bit TOGGLE = PORT_A_ADDR.1 // debug/test bit #bit PWM_OUT = PORT_A_ADDR.3 // PWM output // #bit OUT_PHASE = PORT_B_ADDR.2 // phase shift output - used when P.A. is modulated #bit OUT_BIT = PORT_B_ADDR.3 // data bit output #bit OUT_MODE = PORT_B_ADDR.4 // mode-select input. When high, it is in the // amplitude modulator mode byte v_byte; // varicode word bit holder byte idx; // index of varicode byte b_count; // bit counter byte v_len; // varicode word length byte pwm; // pwm value byte pwm_n; // pwm work variable byte pwm_i; // inverse of pwm work variable byte sinptr; // pointer within sine table byte baud_cnt; // baud rate counter // short send_ok; // set to TRUE by the ISR if it is time to send another bit short doing_char; // flag to indicate that a character is being sent short bit_wait; // flag to indicate that a bit is waiting to be sent short bit_send; // bit that contains bit that is currently being sent (1 or 0) short mbit_send; // bit that contains a copy of bit_send for the PA modulation routine short mbit_flag; // bit that flags when mbit_send is used short last_bit; // bit that contains the previous bit that was sent // Code begins #int_rtcc // interrupt function for the timeout of the RTCC timer_isr() { //TOGGLE=1; // for debug - to time ISR dwell time #asm nop // just a leeetle bit more delay to make it as close to 1KHz as possible nop #endasm set_rtcc(RTCC_CNT); // refresh RTCC counter pwm_n = pwm; // load current PWM value pwm_i = 100 - pwm_n; // calculate inverse of pwm variable set_tris_a(PORT_A_TRIS); // take out of HI-Z mode to output mode PWM_OUT = 1; // set PWM output to high state charge cap #asm // do the PWM part in assembly norloop: decfsz pwm_n,f // spin wheels here until pwm count is zero and charge cap goto norloop #endasm PWM_OUT = 0; // set PWM output low to discharge cap #asm invloop: decfsz pwm_i,f // spin wheels here, too - to discharge cap goto invloop #endasm set_tris_a(PORT_A_TRIS_A3I); // go to hi-z state between ISR cycles baud_cnt++; // increment baud counter if(baud_cnt > 31) { // every 32 interrupts, a new data bit is ready send_ok = 1; // it is now ok to send a character baud_cnt = 0; // reset bit counter } if(!OUT_MODE) { // if in the "feed the balanced modulator" mode, do this... if((OUT_BIT) && (sinptr < 31)) { // is output bit 1 AND pointer in table NOT at top? sinptr++; // increment position in sine table pwm = sintbl[sinptr]; // look up sine value (only if index was changed...) } else if(sinptr > 0) { // if output bit is 0 AND pointer in table is NOT at bottom sinptr--; // decrement position in sine table pwm = sintbl[sinptr]; // look up sine value } } else { // We are in the "AM" mode if(!mbit_send) { // is there supposed to be a phase shift? mbit_send = 1; // yes - clear it... sinptr = 0; // reset sine pointer } pwm = pw_sine[sinptr]; // get current data point in sine table if(sinptr < 31) { sinptr++; if(sinptr == 19) { // are we in the middle of the phase shift? OUT_PHASE = !OUT_PHASE; // yes - flip the phase when the sine is at zero } } } //TOGGLE=0; // for debug } // the following function interfaces with the ISR and the send_varichar() function and // actually diddles the bit sending data void bitsend(void) { if(send_ok) { // is there a bit ready to be sent? if(!bit_send) { // yes - is it a zero? OUT_BIT = !OUT_BIT; // toggle send bit (This represents a zero) } send_ok = 0; // clear the send-bit flag bit_wait = 1; // let it be known that the current bit has been sent... } } // the following function converts the input character (c) into the varicode character to be void lookup_varichar(byte c) { if(c > 127) { // is it out of the valid character range? doing_char = 0; // clear flag to allow next character to be processed return; // yes - ignore the character } else { // in the valid character range - look it up idx = c; idx += c; // double the index v_byte = varicode[idx]; // get bits of varicode doing_char = 1; // indicate that a character is ready to be sent b_count = 0; // init bit counter bit_send = 1; // init holder for current bit last_bit = 1; // init holder for the last bit sent } } // the following function shifts out the bits and returns them in 'bit_send' void sendbit(void) { bit_wait = 0; // reset bit indicator last_bit = bit_send; // get previous bit bit_send = bit_test(v_byte, 7); // get the current bit to send if(!mbit_flag) { // copy current bit if it hasn't been done before... mbit_flag = 1; mbit_send = bit_send; // actually copy the bit... } b_count++; // bump bit count v_byte <<= 1; // shift the current varicode byte left 1 bit if((!last_bit) && (!bit_send)) { // are the past two recent bits both zero? doing_char = 0; // signal that this is the last bit of this character } if(b_count > 7) { // if this is the last bit of this word, get the next one... idx++; // look at the next bit of the v_byte = varicode[idx]; // get the remaining bits of the varicode b_count = 0; // reset the bit counter } mbit_flag = 0; // clear flag in preparation for next bit } void main(void) { char c; byte cnt; set_rtcc(RTCC_CNT); // initialize the rtcc setup_counters(RTCC_INTERNAL, RTCC_DIV_4); // set up to use internal clock enable_interrupts(RTCC_ZERO); // setup for interrupt on an RTCC count hitting zero enable_interrupts(GLOBAL); // actually turn on interrupts PORT_B_PULLUPS(TRUE); PORT_A = 0; // init output bits PORT_B = 0; SET_TRIS_A(PORT_A_TRIS); // set I/O direction for ports SET_TRIS_B(PORT_B_TRIS); // send_ok = 0; // initialize various flags and counters doing_char = 0; bit_wait = 0; bit_send = 0; b_count = 0; sinptr = 15; // place the sine table pointer in the middle last_bit = 0; pwm = 50; // init the PWM algorithm // while(TRUE) { restart_wdt(); for(cnt = 0; cnt < 80; cnt++) { // send some zeroes before each EEPROM playback cycle bit_send = 0; mbit_send = 0; bit_wait = 0; while(!bit_wait) { restart_wdt(); bitsend(); } } cnt = 0; // initialize EEPROM send count c = 32; // put *something* (like a space...) in the send register while((c != 0xff) && (cnt < 64)) { // was this the last character or at end of EEPROM? c = read_eeprom(cnt); cnt++; lookup_varichar(c); // get the first varicode byte while(doing_char) { // hang around here until the character is done restart_wdt(); sendbit(); while(!bit_wait) { restart_wdt(); bitsend(); // hang around until the current bit is sent } } } } } hang around here until the character is done restart_wdt(); sendbit(); while(!bit_wait) { restart_wdt(); bitsend(); // hang around until the current bit is sent } } } } }