// This is the source code for the PIC16F872 microcontroller used in the SWR/wattmeter I described at http://www.jasonhsu.com/swrwatt.html . // Programmer: PICSTART Plus // OS: antiX Linux M8.2 // IDE: MPLAB v8.43 (through WINE 1.0.1-1) // C compiler: HI-TECH PICC 9.70 (included with MPLAB v8.43) // Editor: MPLAB's default editor // Memory Summary: // Program space used 227h ( 551) of 800h words ( 26.9%) // Data space used 13h ( 19) of 80h bytes ( 14.8%) // EEPROM space used 0h ( 0) of 40h bytes ( 0.0%) // Configuration bits used 1h ( 1) of 1h word (100.0%) // ID Location space used 0h ( 0) of 4h bytes ( 0.0%) // The wattmeter display parameters: // LED A/D range VF2/VR2 range VF3/VR3 range Approx. Power Power Output Range LED color // D11 33-45 .32-.45V .16-.22V .28W .20-.40W blue/green // D12 46-64 .45-.63V .22-.32V .56W .40-.79W blue/green // D13 65-91 .63-.89V .32-.45V 1.1W .79-1.6W blue/green // D14 92-128 .89-1.3V .45-.63V 2.2W 1.6-3.2W blue/green // D15 129-181 1.3-1.8V .63-.89V 4.5W 3.2-6.3W blue/green // D16 182-256 1.8-2.5V .89-1.3V 8.9W 6.3-13W blue/green // D17 257-362 2.5-3.5V 1.3-1.8V 18W 13-25W blue/green // D18 363-512 3.5-5.0V 1.8-2.5V 35W 25-50W blue/green // D19 513-723 5.0-7.1V 2.5-3.5V 71W 50-100W yellow // D20 724-1023 7.1-10V 3.5-5.0V >100W 100-200W red // The SWR meter display parameters: // LED Reflection ratio range Approx. SWR SWR range LED color // D1 0 to 1/9 1.1 1.00 to 1.25 blue/green // D2 1/9 to 2/9 1.4 1.25 to 1.57 blue/green // D3 2/9 to 1/3 1.8 1.57 to 2.0 blue/green // D4 1/3 to 4/9 2.3 2.0 to 2.6 yellow // D5 4/9 to 5/9 3 2.6 to 3.5 yellow // D6 5/9 to 2/3 4 3.5 to 5 yellow // D7 2/3 to 7/9 6 5 to 8 red // D8 7/9 to 8/9 11 8 to 17 red // D9 8/9 to 1 35 >17 red // D10 >1 infinity infinity red // All of the LEDs in the SWR meter and wattmeter displays remain dark when transmitted power is under 200mW. // DESIGN HISTORY OF THE LED OUTPUT DISPLAY: // FIRST VERSION: This LED SWR/wattmeter design originally used an LM3915 IC for the wattmeter display and an LM3914 IC for the SWR display. // However, various LEDs in the SWR display would light up at random between transmissions, as the LM3914 compared the near zero forward and reflected voltages. // SECOND VERSION: This was based on the design by Bert Kelley (AA4FB) in the December 1999 issue of _QST_ magazine. I used a PIC16F72 microcontroller, because it was // similar to the PIC16C71 of his design. The major changes I made to his design were the addition of a wattmeter function, the use of discrete LEDs instead of an LCD, and // a microcontroller with FLASH memory and more I/O pins. // THIRD VERSION: I replaced the PIC16F72 with a PIC16F872 microcontroller. The PIC16F72 microcontroller has 8-bit A/D converters. The PIC16F872 has 10-bit A/D converters. // This means more precise voltage measurements and better capability at very low transitter power levels. // Because the PIC16F872 is pin compatible with the PIC16F72, it didn't require any hardware changes. // ODDBALL I/O PINS: // Port A Pin 4 (RA4) is the open drain output and is configured differently from the other I/O pins. It is connected in series with LED D5, a 330 ohm resistor, and the +5V supply. // To keep this LED unlit, set TRISA4 = 1. To light this LED, set TRISA4 = 0 and RA4 = 0. // Setting TRISA4 as an input port gives it a high impedance. This effectively means that no current flows through LED D5, which remains unlit. // Setting TRISA4 as an output port gives it a low impedance. However, it is also necessary to set RA4 = 0 so that this pin is connected to ground. This allows current to flow // from the +5V supply through LED D5, the 330 ohm resistor, and into ground. This lights up LED D5. // If RA4 is not specifically set to 0, then this pin floats. Current will not flow through LED D5 if RA4 is connected to the +5V supply instead of ground. // The low voltage programming feature MUST be disabled to allow normal I/O operation of Port B Pin 3. // This firmware was created and uploaded to the PIC16F872 microcontroller with antiX Linux M8.2, WINE 1.0.1-1, MPLAB 8.43, HI-TECH PICC 9.70. To get started: // 1. Install antiX Linux // 2. Use Synaptic to install the package wine // 3. Download MPLAB for Windows from the Microchip web site and save this *.zip file to its own folder. // 4. Open a shell window, cd your way into the folder containing the MPLAB file, and unzip the file with the command "unzip (filename)" to uncompress the contents. // 5. Move the folder containing the MPLAB file to somewhere within /home/(username)/.wine/drive_c for easier access in WINE. // 6. In the shell window, enter "wine explorer". This opens the WINE file manager and works just like Windows Explorer. // 7. In WINE file manager, go to the MPLAB folder containing the extracted contents of the MPLAB zip file and run the setup.exe file. This installs MPLAB in WINE. // 8. After you have finished installing MPLAB in WINE, go to the directory c:\Program Files\Microchip\MPLAB IDE\Core and open MPLAB.exe. This opens the MPLAB IDE. // 9. To start a new project, go to Project -> Project Wizard. In step one, select the appropriate device (PIC16F872 for this project). // In step two, select the toolsuite HI-TECH Universal ToolSuite. In step three, select "Create New Project File" and select a name for your project. // In step four, add files to your project. The files to add are this source code file (saved as a *.c file) and the files pic.h and stdlib.h in the // /home/(username)/.wine/drive_c/Program Files/HI-TECH Software/PICC/9.70/include directory. // The WINE file manager does not show hidden files, so you'll have to go to the /home/(username) directory and enter ".wine" in order to get into the drive_c directory. // (In the interest of convenience, I recommend keeping all files used by your project in the same directory containing the project file.) // In order to compile your source code, you must: // 1. Make sure your *.c file is included under "Source Files" in the Project window. If you don't see the Project window, go to View -> Project until a check mark appears next to "Project". // If your *.c file is not included under "Source Files" in the Project window, right-click on "Source Files" and select "Add Files". // 2. Make sure the *.h files (stdlib.h and pic.h) used in the source code are included under "Header Files" in the Project window. // The files to add are in the /home/(username)/.wine/drive_c/Program Files/HI-TECH Software/PICC/9.70/include directory. // The WINE file manager does not show hidden files, so you'll have to go to the /home/(username) directory and enter ".wine" in order to get into the drive_c directory. // 3. Go to Configure -> Select Device and make sure the appropriate microcontroller is selected. // 4. Go to Project -> Select Language Toolsuite and make sure the appropriate choice is selected (HI-TECH Universal ToolSuite). // 5. To compile, go to Project -> Build. If you still have files resulting from earlier compiling, go to Project -> Rebuild. // NOTE: If you get error messages about code that seems to be OK, check the *.pre preprocessor file generated during the compiling process. This shows you what the compiler sees. // I once copied a working function declaration from one program to another and had an inexplicable error that I couldn't figure out. It turned out that a key line in the source // code I saw did NOT show up in the *.pre file. For some reason, the preprocessor associated it with the previous line that was commented out. // In order to simulate in MPLAB SIM, go to Debugger -> Tool -> MPLAB SIM. Now you can run, halt, step, etc. // To connect your computer to the PICSTART Plus: // 1. Turn on and connect the PICSTART Plus to your computer. // 2. Go to Programmer -> Select Programmer -> PICSTART Plus. // 3. Go to Programmer -> Settings -> Communications and select the appropriate COM port. // 4. Go to Programmer -> Enable Programmer to establish the connection between your computer and the PICSTART Plus. If the connection cannot be made, try another COM port. // NOTE: If your computer has no serial port, you will need a USB/serial port converter. You will also need a symlink to make your computer treat the USB port as a COM port. // When you have a connection between your computer and the PICSTART Plus, you are ready to program your microcontroller: // 1. Insert the microcontroller into the PICSTART Plus. Be sure that pin 1 faces the correct direction. The handle settings for the ZIF switch are up = locked and down = unlocked. // 2. With the microcontroller properly installed in the PICSTART Plus, go to Programmer -> Program to upload your code into the microcontroller. // 3. Remove the microcontroller from the PICSTART Plus and install it in your circuit. // The program's steps are: // Step 1. Set the inputs and outputs. // Step 2. Obtain the forward voltage sample. // Step 3. Clear the LED displays. // Step 4. Given the value of the forward voltage sample, light up the appropriate wattmeter LED. // Step 5. If the forward voltage sample is below the minimum threshold, pause for 10 msec (do not light any more LEDs) and go back to step 2. Otherwise, go to the next step. // Step 6. Obtain the reflected voltage sample. // Step 7. Calculate the reflection ratio. // Step 8. Light up the appropriate SWR LED (D1-D9). Keep the wattmeter and SWR meter LEDs lit for 10 msec and go back to step 2. #define __16F872 #define _XTAL_FREQ 3579545 // Provide the frequency of the crystal, necessary for using the delay functions #include // Needed to access the delay functions. #include #include __CONFIG (XT&WDTDIS&PWRTDIS&BORDIS&UNPROTECT&LVPDIS); // Set the configuration bits: // XT=crystal oscillator, WDTDIS=Watchdog Timer disabled, PWRTDIS=Power Up Timer disabled, BORDIS=Brown Out Reset disabled, UNPROTECT=code is unprotected // LVPDIS=low voltage programming disabled setting is necessary for normal I/O operation of Port B Pin 3. // Go to Configure->Configuration Bits to confirm configuration bits. // The crystal is a 3.579545 MHz crystal resonator. unsigned char fwd_1, fwd_256, refl_1, refl_256, ninths, prod_1, prod_256, count; // Step 1. Set the inputs and outputs. // Step 1-1. Set pins 2 through 7 of Port A as outputs. Set pins 0 and 1 of Port A to be analog inputs. // Step 1-2. Set all Port B and Port C pins as outputs. // Note that the Register Bank is set to 01 only for toggling the analog/digital and input/output settings of the I/O pins. // At all other times, the Register Bank is set to 00, because that's where the values of most variables used are kept. void start(void) { RP1 = 0; RP0 = 1; // Select Register Bank 01. TRISA = 0b00000011; // Set pins 0 and 1 as inputs. Set all other Port A pins as outputs. ADCON1 = 0b10000100; // Set A/D Control Register 1 // ADCON1 bit 7: A/D result format select bit; set to 1 = right justified, six most significant bits of ADRESH are 0. // ADCON1 bits 6-4: not used // ADCON1 bits 3-0: A/D port configuration bits; set to 0100 means pins 0, 1, and 3 of Port A are analog, VREF+ = VDD = 5V, VREF- = VSS = 0V // Port A pins 0 and 1 are now analog inputs. // Port A pins 2, 4, 5, 6, and 7 are now digital outputs (5V or 0V). // Port A pin 3 is now an analog output - the output of a D/A converter. The digital output is converted into 5V or 0V, so the analog/digital distinction is irrelevant. TRISB = 0b00000000; // Set RB7-RB0 pins as outputs. TRISC = 0b00000000; // Set RC7-RC0 pins as outputs. // Because RA3 is an output, the digital output will be converted to an analog value. RP0 = 0; // Select Register Bank 00. GIE = 0; // Disable Global Interrupt } // Step 2. Obtain the forward voltage sample. // Step 2-1. Acquire a voltage sample from input channel 0. // Step 2-2. Convert the voltage sample into a digital quantity. void get_fwd(void) { ADCON0 = 0b10000001; // Set A/D Control Register 0 // Bits 7-6: A/D clock conversion set bits; set to 10 for an A/D conversion clock rate of FOSC/32 (input capacitor limits rate of change of voltage -> faster conversion rate not needed), // Bits 5-3: select channel, set to 000 for channel 0 (RA0) // Bit 2: A/D conversion status bit, ADGO, set to 0 (A/D conversion not in progress) // Bit 0: A/D on bit, set to 1 (A/D converter module operating) // This rate is one sample every 8.9 usec. So the A/D converter module will be able to obtain a sample given 20 usec. __delay_us (20); // Let the A/D converter module continue operating for 20 usec. ADGO = 1; // ADGO is bit 2 of ADCON0. Setting this bit to 1 starts the A/D conversion process. while (ADGO == 1) // Continue to keep the A/D converter active until the A/D conversion process completes. {} fwd_256 = ADRESH; // ADRESH contains the 2 most significant bits of the 10-bit result of the A/D conversion. RP0 = 1; // Select Register Bank 01 to access ADRESL. fwd_1 = ADRESL; // ADRESL contains the 8 least significant bits of the 10-bit result of the A/D conversion. RP0 = 0; // Select Register Bank 00. // 256*fwd_256 + fwd_1 = V_F_2*1023/10V } // Step 3. Clear the LED displays. // Each LED is connected to one of the pins of the ports. // Wattmeter LEDs and ports/pins: D11 = A5, D12 = C0, D13 = C1, D14 = C2, D15 = C3, D16 = B0, D17 = C7, D18 = C6, D19 = C5, D20 = C4 // SWR LEDs and ports/pins: D1 = B7, D2 = B6, D3 = A2, D4 = A3, D5 = A4, D6 = B5, D7 = B4, D8 = B3, D9 = B2, D10 = B1 // For most LEDs/pins, a value of 0 means unlit and a value of 1 means lit. // This does not work for Port A pin 4 (which controls D5), because it behaves as an open drain when in output mode. // For this pin, we must use bit 4 of TRISA (TRISA4) to control LED D5. Setting TRISA to 1 means low current and an unlit LED. Setting TRISA to 0 means high current and a lit LED. // Note that we switch to Register Bank 01 to change the value of TRISA and switch back to Register Bank 00 after we finish. void clear_displays (void) { RA5=0; RC0=0; RC1=0; RC2=0; RC3=0; RB0=0; RC7=0; RC6=0; RC5=0; RC4=0; // Turn off LEDs D11-D20, respectively. RB7=0; RB6=0; RA2=0; RA3=0; // Turn off LEDs D1-D4, respectively. RB5=0; RB4=0; RB3=0; RB2=0; RB1=0; // Turn off LEDs D6-D10, respectively. RP1=0; RP0=1; // Select Register Bank 1. TRISA4=1; // Port A pin 4 is now an input pin. This means high impedance, low current, and an unlit LED D5. RP1=0; RP0=0; // Select Register Bank 0. } void display1 (void) // Light up LED D1. {RB7=1;} void display2 (void) // Light up LED D2. {RB6=1;} void display3 (void) // Light up LED D3. {RA2=1;} void display4 (void) // Light up LED D4. {RA3=1;} // Port A Pin 4 (RA4) is the open drain output and is configured differently from the other I/O pins. It is connected in series with LED D5, a 330 ohm resistor, and the +5V supply. // To keep this LED unlit, set TRISA4 = 1. To light this LED, set TRISA4 = 0 and RA4 = 0. // Setting TRISA4 as an input port gives it a high impedance. This effectively means that no current flows through LED D5, which remains unlit. // Setting TRISA4 as an output port gives it a low impedance. However, it is also necessary to set RA4 = 0 so that this pin is connected to ground. This allows current to flow // from the +5V supply through LED D5, the 330 ohm resistor, and into ground. This lights up LED D5. // If RA4 is not specifically set to 0, then this pin floats. Current will not flow through LED D5 if RA4 is connected to the +5V supply instead of ground. void display5 (void) // Light up LED D5. { RA4 = 0; // This is necessary to connect the pin to ground. RP1=0; RP0=1; // Select Register Bank 1. TRISA4=0; // Port A pin 4 is now an output pin. This means low impedance, high current, and a lit LED D5. RP1=0; RP0=0; // Select Register Bank 0. } void display6 (void) // Light up LED D6. {RB5=1;} void display7 (void) // Light up LED D7. {RB4=1;} void display8 (void) // Light up LED D8. {RB3=1;} void display9 (void) // Light up LED D9. {RB2=1;} void display10 (void) // Light up LED D10. {RB1=1;} void display11 (void) // Light LED D11. {RA5=1;} void display12 (void) // Light LED D12. {RC0=1;} void display13 (void) // Light LED D13. {RC1=1;} void display14 (void) // Light LED D14. {RC2=1;} void display15 (void) // Light LED D15. {RC3=1;;} void display16 (void) // Light LED D16. {RB0=1;} void display17 (void) // Light LED D17. {RC7=1;} void display18 (void) // Light LED D18. {RC6=1;} void display19 (void) // Light LED D19. {RC5=1;} void display20 (void) // Light LED D20. {RC4=1;} void displaywatt (void) // Step 4. Given the value of the forward voltage sample, light up the appropriate wattmeter LED. { if (fwd_256 >= 3) // Step 4-1. If the value of the forward voltage sample is 768 to 1023, light up D20. {display20();} else if (fwd_256 == 2) // Step 4-2. If the value of the forward voltage sample is 512 to 767, light up D18, D19, or D20. { if (fwd_1 >= 212) // Step 4-2-1. If the value of the forward voltage sample is 724 to 767, light up D20. {display20 ();} else if (fwd_1 >= 1) // Step 4-2-2. If the value of the forward voltage sample is 513 to 723, light up D19. {display19 ();} else // Step 4-2-3. If the value of the forward voltage sample is 512, light up D18. {display18 ();} } else if (fwd_256 == 1) // Step 4-3. If the value of the forward voltage sample is 256 to 511, light up D16, D17, or D18. { if (fwd_1 >=107) // Step 4-3-1. If the value of the forward voltage sample is 363 to 511, light up D18. {display18 ();} else if (fwd_1 >= 1) // Step 4-3-2. If the value of the forward voltage sample is 257 to 362, light up D17. {display17 ();} else // Step 4-3-3. If the value of the forward voltage sample is 256, light up D16. {display16 ();} } else // Step 4-4. If the value of the forward voltage sample is 0 to 255, light up D11, D12, D13, D14, D15, D16, or no LED at all. { if (fwd_1 >= 182) // Step 4-4-1. If the value of the forward voltage sample is 182 to 255, light up D16. {display16 ();} else if (fwd_1 >= 129) // Step 4-4-2. If the value of the forward voltage sample is 129 to 181, light up D15. {display15 ();} else if (fwd_1 >= 92) // Step 4-4-3. If the value of the forward voltage sample is 92 to 128, light up D14. {display14 ();} else if (fwd_1 >= 65) // Step 4-4-4. If the value of the forward voltage sample is 65 to 91, light up D13. {display13 ();} else if (fwd_1 >= 64) // Step 4-4-5. If the value of the forward voltage sample is 46 to 64, light up D12. {display12 ();} else if (fwd_1 >=33) // Step 4-4-6. If the value of the forward voltage sample is 33 to 45, light up D11. {display11 ();} // NOTE: If the value of the forward voltage sample is 0 to 32, do not light up any of the wattmeter LEDs. } } // Step 6. Obtain the reflected voltage sample. // Step 6-1. Acquire a voltage sample from input channel 1. // Step 6-2. Convert the voltage sample into a digital quantity. void get_refl(void) { ADCON0 = 0b10001001; // Set A/D Control Register 0 // Use the same settings as those used in the get_fwd function, but select channel 1 instead (bits 5-3 = 001). // This means we set the channel 1 A/D converter module to operate. __delay_us (20); // Let the A/D converter module continue operating for 20 usec. ADGO = 1; // ADGO is bit 2 of ADCON0. Setting this bit to 1 starts the A/D conversion process. while (ADGO == 1) // Continue to keep the A/D converter active until the A/D conversion process completes. {} refl_256 = ADRESH; // ADRESH contains the 2 most significant bits of the 10-bit result of the A/D conversion. RP0 = 1; // Select Register Bank 01 to access ADRESL. refl_1 = ADRESL; // ADRESL contains the 8 least significant bits of the 10-bit result of the A/D conversion. RP0 = 0; // Select Register Bank 00. // 256*refl_256 + refl_1 = V_R_2*1023/10V } // Step 7. Calculate the reflection ratio. // We need to determine how many full units of 1/9 there are in the refll/fwd voltage ratio. // refl/fwd = ninths/9, 9*refl = ninths * fwd, 9*refl - ninths*fwd = 0 // ninths is the maximum unsigned char for which 9*refl - ninths*fwd >=0 // Step 7-1. Multiply refl by 9. // Step 7-2. Keep subtracting fwd from 9*refl. The number of times this is necessary to produce a result <0 = ninths. void get_refl_ratio (void) { // prod=256*prod_256+prod_1 stores the result of the addition and subtraction operations in the process. prod_1=0; prod_256=0; count=0; ninths=0; while (count<9) { if (prod_1 > 255-refl_1) // If prod_l+refl_1>255 (prod_1 > 255 - refl_1), then increment prod_256 by 1. This is a carry operation. {prod_256++;} prod_1 = prod_1+refl_1; // Increment prod by ref (*1). prod_256 = prod_256 + refl_256; // Increment prod by refl (*256). count++; } // Now prod=9*refl. Time to keep subtracting fwd from prod until prod fwd_256. If this is true, then prod>fwd MUST be true. while (ninths<9 && prod_256 > fwd_256) { ninths++; if (prod_1 < fwd_1) // If prod_1 - fwd_1 <0 (prod_1 < fwd_1), then decrement prod_256 by 1. This is a borrow operation. {prod_256--;} prod_1 = prod_1 - fwd_1; // Decrement prod by fwd (*1). prod_256 = prod_256 - fwd_256; // Decrement prod by fwd (*256). } // This while loop applies as long as prod_256 = fwd_256. This means that we're only comparing prod_1 and fwd_1. // If prod_1 < fwd_1, then we are finished with this while loop. while (ninths<9 && prod_256 == fwd_256 && prod_1 >= fwd_1) { ninths++; prod_1 = prod_1 - fwd_1; // Decrement prod by fwd (*1). prod_256 = prod_256 - fwd_256; // Decrement prod by fwd (*256). } } void display_swr (void) { if (ninths==0) {display1();} else if (ninths==1) {display2();} else if (ninths==2) {display3();} else if (ninths==3) {display4();} else if (ninths==4) {display5();} else if (ninths==5) {display6();} else if (ninths==6) {display7();} else if (ninths==7) {display8();} else if (ninths==8) {display9();} else // ninths = 9 {display10();} } void main() { start(); // Step 1. Set the inputs and outputs. while (1) { get_fwd(); // Step 2. Obtain the forward voltage sample. // BREAK POINT below - Provide values of fwd_256 and fwd_1 if debugging. clear_displays(); // Step 3. Clear the LED displays. displaywatt (); // Step 4. Given the value of the forward voltage sample, light up the appropriate wattmeter LED. // Step 5. If the forward voltage sample is below the minimum threshold, pause for 10 msec (do not light any more LEDs) and go back to step 2. Otherwise, go to the next step. // The forward voltage sample must have a value of at least 13 (30 mW) for an SWR value to be displayed. if (fwd_256 >= 1 || fwd_1 >= 13) // If neither condition is met, then the forward voltage sample is 0-12. { get_refl (); // Step 6. Obtain the reflected voltage sample. // BREAK POINT below - Provide values of refl_256 and refl_1 if debugging. get_refl_ratio (); // Step 7. Calculate the reflection ratio. // BREAK POINT below - Check ninths. display_swr(); // Step 8. Light up the appropriate SWR LED (D1-D9). Keep the wattmeter and SWR meter LEDs lit for 1 msec and go back to step 2. } __delay_ms (10); // BREAK POINT here - Check Ports A, B, and C. } }