The microcontroller performs several instrumentation measurements
Members can download this article in PDF format.
What you will learn:
- How to use the venerable PIC16F886 MCU to create a multifunction measuring device.
- What instrument measurements can this 6-in-1 device reproduce?
When you need a versatile, multi-functional instrument to measure six different types of parameters in the lab, the venerable PIC16F886 microcontroller is a suitable solution to perform such a task. The microcontroller in this design idea can perform the following instrument functions: voltmeter, thermometer, pulse counter, frequency counter, period counter, and tachometer.
This design only requires a three-digit digital LCD display; its total current consumption is less than 1 mA. To drive the LCD, the microcontroller’s Pulse Width Modulator (PWM) generates a signal to drive the common input on the LCD. The PIC drives the LCD by inverting the data on its ports with respect to the common input. Figure 1 shows the electronic diagram of this design and Figure 2 shows the diagram of the 24-pin digital LCD display.
The assembled prototype board is shown in picture 3. The voltmeter function is called Lb1. Figure 4 shows the thermometer function, called Lb2.
To select measurement functions (Lb1 to Lb6), press the PTC push button assigned to input RE3. Pickup PIC starts with Lb1 by default (Fig.5). You will see the message “Lb1” on the display, which is the DC voltmeter. To switch to another instrument, press the push button again. You will each see the “LbX” message in sequence.
To disable the decimal points on the display (pins 5 and 9), connect them to common (pin 1) on the LCD. To enable a decimal point, connect it to 5V with a 100k resistor.
Voltmeter (Lb1)
The micro PIC starts by default with Lb1, which is a dc voltmeter (Fig.6). The voltage under measurement must be between 0 and 5 V, and it is applied to RA0 configured as an analog input (pin 2). Its resolution is 10 mV. You can increase the input voltage range up to 49.9V by applying a voltage divider network using two precision resistors, such as 180k and 20k.
Thermometer (Lb2)
In this configuration you need a temperature sensor such as the LM34DZ or LM35DZ for Fahrenheit or Celsius, respectively (Fig.7). By pressing PB2 on RC0 you can select the sensor you are using.
The PIC measures the temperature applied to the analog channel RA0/AN0. The temperature reading will be in the range of 0 to 250˚F with a resolution of 1˚F degree. For the Celsius scale, the temperature is between 0 and 120˚C.
3 digit counter (Lb3)
In this setup (Fig.8), the microcontroller picks up the pulses applied to TIMER1 on the clock input (T1CKI) to make it a three-digit counter. The pulses must be applied to the Timer1 clock input (T1CKI). When the counter reaches 1000 counts, it is cleared to start a new counting cycle. The 0.01 µF capacitor in series with resistor R2 is used to bounce the push button and get clean pulses.
Frequency meter (Lb4)
In this configuration, an input frequency sample is read by TIMER1 on RC0/T1CKI every 1.00 seconds. This configuration can measure frequencies in the range of 0 to 999 Hz with a resolution of 1 Hz (Fig. 9).
If the frequency is greater than 999 Hz, it stops TIMER1 and clears the counter to start a new frequency measurement cycle. You can modify the code to measure up to 9.99 kHz by reducing its respective pause from 1.00 s to 100 ms (this instruction is in bold in Listing 2). In this case, its resolution would be equal to 10 Hz.
Period measurement (Lb5)
To measure an input signal period applied to RA0, a time base frequency of 1000 Hz must be applied to TIMER1 by the internal PWM (CCP1). Thus, a jumper must be placed from the CCP1 output (pin 13) to the T1CKI 16-bit clock input (pin 11). Picture 10 shows its configuration.
When a rising-edge input signal arrives at RA0, it begins to measure its period. When the signal goes logic low, it stops counting, and the period is displayed in milliseconds. With this code, the PIC can measure periods between 1 and 999 ms, with a resolution of 1 ms.
The measurement code of a period consists of reading the logic state of RA0. The variable counter continues to update its contents while RA0 remains high. Once it goes logic low, the TIMER is disabled with the T1CON.0 instruction. This is described in List 1.
Tachometer (Lb6)
In this case, the microcontroller functions as a tachometer to display measurements in the range of 0 to 999 RPM, with a resolution of 1 RPM (Fig. 10, again). When the reading is above 999 RPM, the counters are cleared to start a new measurement cycle.
The PWM module provides a 1000 Hz time base which is used to measure the period of the input signal T and then calculate its equivalent in RPM using the formula RPM=6000/T. The method used here determines how many times the period reading in the counter goes in a 60,000 count loop, as follows:
FOR X = 0 TO 60000 STEP COUNTER; determine RPM from period stored in variable counter. RPM = RPM + 1; NEXT X;
This method is more accurate than performing a division because it does not generate fractions.
List 2 below shows the complete code of the six instruments contained in the PIC16F883. The code is based on the PBP3 compiler from melabs.com.
Ricardo Jimenez holds a master’s degree in electronics from the TecNM/ITM campus in Mexicali. Gabriel Lee Alvarez pursuing his Masters in Computer Science at TecNM, Mexicali campus. Angel Dominguez holds a Technical Diploma in Low Voltage Systems from Imperial Valley College, Imperial, California.
Listing 2: Code for Six-Labs-in-One PIC Microcontroller
'* Name : 72segments-JANUARY26-22 version3.BAS '* Author : R. Jimenez, M. Sc., and Gabriel Lee Alvarez '* Notice : PB on RE3 requires 5.1K and 0.01 uF Cap for debouncing PB '* Date : 5/18/2021, 8/01/21, 1/25/22 '* Version : LAB5 requires external Time Base Freq and FF 74HC74 '* Notes : pic16f883 with 3-Digit Numerical LCD '* : 6 Labs in a single PIC 16F883 ;Code consumes 1391 words out of 4091 available on 16F883 #CONFIG ; configuration word __config _CONFIG1, _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF & _LVP_OFF & _CP_OFF #ENDCONFIG OSCCON = %01100111; $67 4-MHz internal oscillator ;----------------- TRISA= %00000001: ANSEL= %00000001; RA0/AN0 is analog input TRISB= %00000000: ANSELH= 0; PORT B digital Output TRISC= %00000001; PORTC digital Output. RC0 input TRISE = %00001000; RE3/MCLr input ;----------------- ;DEFINE ADC_BITS 8; A-to-D converter set to 8 bits resolution ;DEFINE ADC_CLOCK 3; internal clock source 3 for the A/D converter ;DEFINE SAMPLEUS 50; wait 50 uS to settle down after 20 uS reading ADCON0 = %00000001; ADCON1 = %10000000; ;----------------- ;calculated base frequency for practices 5 and 6, frequency = 1/((pr2+1)*4*(1/fosc)*(tmr2 prescale)) ; the value of frequenci = 1/((249+1)*4*(1/4Mhz)*(4)) = 1000 = 1 khz CCP1CON = %00000001; PWM off T2CON = %00000001; TMR2 ON, PRESCALER 4 PR2 = 249; CCP VAR WORD; CCP = 500; CCP1CON.4 = CCP.0; CCP1CON.5 = CCP.1; CCPR1L = CCP>>2 ;///--- OPTION_REG = %10000111; ADFM=1, A/D right justified ;----------------- A var byte; decimals segments to display in XOR with RC2 B var byte; Units segments to display in XOR with RC2 C var byte; Hundredths segments in XOR with RC2 L var byte; Stores value of Common phase signal for LCD PRACTIC VAR BYTE; volt var WORD; variable volt to store for voltage readings v1 VAR WORD: v2 VAR WORD; variables to store each digit digit3 VAR BYTE: digit2 VAR BYTE digit VAR BYTE ; digi var byte; pattern3 VAR BYTE: pattern2 VAR BYTE; variables for 7-segment conversions pattern VAR BYTE REMANENTE VAR WORD I VAR BYTE; CO VAR BYTE; COUNTER VAR WORD; PRACTIC = 0; RPM VAR WORD; T VAR BYTE X VAR WORD; ref var word; PB1 var PORTE.1 ; push button PB1 assigned to RE1 ;----------------- MAIN: ; --------------- GOTO SEL_P; Select Lab Practice to perform GOTO MAIN; D: ; BCD to 7 segments decoding lookup digit3,[$3F,$06,$5B,$4F,$66,$6D,$7D,$07,$7F,$67],pattern3; B= (pattern3); XOR of RC2 with segments to display in the volts digit if PORTC.2 = 1 THEN B= ~B; invert bit for phase purposes PORTB.1= B.0; a1 segment assigned to pin RB1 PORTB.2= B.1; b1 segment assigned to RB2 PORTB.3= B.2; c1 PORTB.4= B.3; d1 PORTB.5= B.4; e1 PORTB.6= B.5; f1 PORTB.7= B.6; g1 lookup digit2,[$3F,$06,$5B,$4F,$66,$6D,$7D,$07,$7F,$67],pattern2; A= (pattern2) ; XOR with RC2 for driving the decimals volt digit if PORTC.2 = 1 THEN A = ~a PORTA.1= A.0; a2 PORTA.2= A.1; b2 PORTA.3= A.2; c2 PORTA.4= A.3; d2 PORTA.5= A.4; e2 PORTA.6= A.5; f2 PORTA.7= A.6; g2 lookup digit,[$3F,$06,$5B,$4F,$66,$6D,$7D,$07,$7F,$67,$71],pattern; C= (pattern); XOR RC2 with segments to display the hundredths volt if PORTC.2 = 1 THEN C = ~C PORTB.0= C.0; a3 PORTC.1= C.1; b3 PORTC.3= C.2; c3 PORTC.4= C.3; d3 PORTC.5= C.4; e3 PORTC.6= C.5; f3 PORTC.7= C.6; g3 RETURN END; ADC_READ: ; reading A/D conversion ADCON0.1 = 1; START CONVERSION HERE: IF ADCON0.1 = 1 THEN HERE; VOLT.BYTE0 = ADRESL; VOLT.BYTE1 = ADRESH; REMANENTE = VOLT * 4887; VOLT = DIV32 1000; RETURN DEC_D: digit3 = VOLT DIG 3; getting units value of v1 and store it in digit3 digit2 = VOLT DIG 2; getting decimals value of v1 and store it in digit2 digit = VOLT DIG 1; getting 1/100 value of v1 and store it in digit1 RETURN SHOW_D: FOR I = 1 to 20; 20 loops to take a Reading every 400 mS,(20 Ms) = 0.4s PORTC.2 = 1; driving LCD Common pin (phase signal) to logic H GOSUB D; invoking method D to drive the LCD display PAUSE 10; 10 mS delay required by the LCD's Common pin PORTC.2= 0; common pin phase signal goes Low for 10 mS GOSUB D; invoking subroutine D to drive the LCD display PAUSE 10; 10 mS to drive Common pin phase to logic L NEXT I; I< 200? Continue loop, otherwise perform next instructio RETURN SEL_P: ; user selects Lab Practice PRACTIC= PRACTIC + 1; IF PRACTIC > 6 THEN PRACTIC = 1; MAX PRACTICES IS 6 on THIS chip PAUSE 200 SELECT CASE PRACTIC CASE 1 GOTO LB_1 CASE 2 GOTO LB_2 CASE 3 GOTO LB_3 CASE 4 GOTO LB_4 CASE 5 GOTO LB_5 CASE 6 GOTO LB_6 END SELECT GOTO MAIN; ;*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- LB_1: ; LAB1 VOLTMETER TRISA= %00000001: ANSEL= %00000001; RA0/AN0 configured as analog input co = 1; FOR i = 1 to 60 GOSUB MESSAGE pause 20; next i LB_1_HERE: GOSUB ADC_READ; GOSUB DEC_D; GOSUB SHOW_D; IF PORTE.3 = 0 THEN goto SEL_P; GOTO LB_1_HERE; RETURN ;*-*-*-*-*-*-*-*--*-*-*-*-*-*-*-*-*-*--*-*-*-*-*- LB_2: ; Lab 2 Thermometer in Fahrenheit degrees with LM34Z sensor co = 2; FOR i = 1 to 60 GOSUB MESSAGE pause 20; next i LB_2_HERE: GOSUB ADC_READ; ;GOSUB DEC_D; if VOLT < 1000 THEN digit3 = VOLT DIG 2; getting units value of v1 and store it in digit3 digit2 = VOLT DIG 1; getting decimals value of v1 and store it in digit2 digit = 10; getting 1/100 value of v1 and store it in digit1 else gosub DEC_D endif GOSUB SHOW_D; IF PORTE.3 = 0 THEN goto SEL_P; GOTO LB_2_HERE; GOTO MAIN; ;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*--*-*-*-*- LB_3: ;LAB 3 Decimal Counter with TIMER1 clocked by external pulses co = 3; FOR i = 1 to 60 GOSUB MESSAGE pause 20; next i T1CON = %00000111;;TIMER ENABLED LB_3_HERE: ;---------------- ;TMR1H = 0; ;TMR1L = 0; ;PAUSE 1000; COUNTER.BYTE0 = TMR1L; COUNTER.BYTE1 = TMR1H IF COUNTER> 999 THEN COUNTER = 0; TMR1H = 0; TMR1L = 0; ENDIF VOLT = COUNTER*10; GOSUB DEC_D; GOSUB SHOW_D; IF PORTE.3 = 0 THEN goto SEL_P; GOTO LB_3_HERE; GOTO MAIN; ;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*--*-*-*-*- ;*-*-*-*-*-*-*-*--*-*-*-*-*-*-*-*-*-*--*-*-*-*-*- LB_4: ; Frequency counter co = 4; FOR i = 1 to 60 GOSUB MESSAGE pause 20; next i T1CON = %00000111;;TIMER ENABLED LB_4_HERE: ; Frequency Counter 0-999 Hz ;---------------- TMR1H = 0; TMR1L = 0; PAUSE 1000; take a sample reading for 1.00 Sec COUNTER.BYTE0 = TMR1L; COUNTER.BYTE1 = TMR1H IF COUNTER> 999 THEN ; overflow? clear TMR1 COUNTER = 0; TMR1H = 0; TMR1L = 0; ENDIF VOLT = COUNTER*10; GOSUB DEC_D; GOSUB SHOW_D; IF PORTE.3 = 0 THEN goto SEL_P; GOTO LB_4_HERE; GOTO MAIN; ;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*--*-*-*-*- ;HPWM 1,127,1000 LB_5: ;PERIOD METER co = 5; Practice 5 FOR i = 1 to 60 GOSUB MESSAGE pause 20; next i T1CON = %00000110; TIMER1 disabled ANSEL = 0; T2CON.2 = 1; TIMER2 enabled? LB_5_HERE: ; PERIOD METER, BASE FREQUENCY taken internally at 1k Hz ;--------- from PWM, change it to an external time base applied ; to TMR1L and TMR1H to ge 10 bits for the display ; the signal to be measured is connected to ra0 TMR1H = 0; TMR1L = 0; PAUSE 1000; GOSUB SHOW_D; HERE_51: ;this section is used to refresh the lcd in idle state if TMR0 => 240 THEN T = T+1; TMR0 = 0; ENDIF IF T > 100 THEN GOSUB SHOW_D; T = 0; ENDIF ;PORTC.2 = ~PORTC.2 IF PORTE.3 = 0 THEN goto SEL_P; if PORTA.0 = 0 THEN HERE_51 ;************************************************************************* ; measurement process ****************************************************** T1CON.0 = 1; timer 1 on CCP1CON = %00001111;***************PWM is turned on, based of time, 1khz HERE_52: COUNTER.BYTE0 = TMR1L; COUNTER.BYTE1 = TMR1H IF PORTE.3 = 0 THEN goto SEL_P; if (PORTA.0 = 1) and COUNTER=<999 THEN HERE_52 T1CON.0 = 0; ; End measurement process ******************** ;******************************************* COUNTER.BYTE0 = TMR1L; COUNTER.BYTE1 = TMR1H CCP1CON = %00000001; PWM OFF IF COUNTER > 999 THEN VOLT = 9990; GOSUB DEC_D; GOSUB SHOW_D; VOLT = 0; GOSUB DEC_D; GOSUB SHOW_D; VOLT = 9990; GOSUB DEC_D; GOSUB SHOW_D; VOLT = 0; GOSUB DEC_D; GOSUB SHOW_D; ELSE VOLT = COUNTER*10; GOSUB DEC_D; GOSUB SHOW_D; ENDIF IF PORTE.3 = 0 THEN goto SEL_P; GOTO LB_5_HERE; GOTO MAIN; ;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*--*-*-*-*- LB_6: ; TACHOMETER co = 6; FOR i = 1 to 60 GOSUB MESSAGE pause 20; next i T1CON = %00000110;;TIMER ENABLED ANSEL = 0; T2CON.2 = 1; LB_6_HERE: ; TACHOMETER 1-999 RPM ;---------------- TMR1H = 0; TMR1L = 0; PAUSE 1000; GOSUB SHOW_D; HERE_61: ;this section is used to refresh the lcd in idle state if TMR0 => 240 THEN T = T+1; TMR0 = 0; ENDIF IF T > 100 THEN GOSUB SHOW_D; T = 0; ENDIF IF PORTE.3 = 0 THEN goto SEL_P; if PORTA.0 = 0 THEN HERE_61 ;********************************* ; measurement process ************** T1CON.0 = 1; tmr1 on CCP1CON = %00001111; pwm on, 1 khz RPM = 0; clear rpm HERE_62: COUNTER.BYTE0 = TMR1L; COUNTER.BYTE1 = TMR1H IF PORTE.3 = 0 THEN goto SEL_P; if (PORTA.0 = 1) THEN HERE_62; T1CON.0 = 0; ; End measurement process ******* ;****************************** COUNTER.BYTE0 = TMR1L; COUNTER.BYTE1 = TMR1H CCP1CON = %00000001; pwm off if COUNTER = 0 THEN LB_6_HERE FOR X = 0 TO 60000 STEP COUNTER; obtain RPM from period RPM = RPM + 1; NEXT X; IF RPM > 999 then VOLT = 9990; GOSUB DEC_D; GOSUB SHOW_D; VOLT = 0; GOSUB DEC_D; GOSUB SHOW_D; VOLT = 9990; GOSUB DEC_D; GOSUB SHOW_D; VOLT = 0; GOSUB DEC_D; GOSUB SHOW_D; ELSE VOLT = RPM*10; GOSUB DEC_D; GOSUB SHOW_D; ENDIF IF PORTE.3 = 0 THEN goto SEL_P; GOTO LB_6_HERE; GOTO MAIN; ;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*--*-*-*-*- MESSAGE: B= ($38); XOR of RC2 with segments to display in the volts digit if PORTC.2 = 1 THEN B = ~B PORTB.1= B.0; a1 segment assigned to pin RB1 PORTB.2= B.1; b1 segment assigned to RB2 PORTB.3= B.2; c1 PORTB.4= B.3; d1 PORTB.5= B.4; e1 PORTB.6= B.5; f1 PORTB.7= B.6; g1 A= ($7C); XOR with RC2 for driving the decimals volt digit if PORTC.2 = 1 THEN A = ~A PORTA.1= A.0; a2 PORTA.2= A.1; b2 PORTA.3= A.2; c2 PORTA.4= A.3; d2 PORTA.5= A.4; e2 PORTA.6= A.5; f2 PORTA.7= A.6; g2 lookup CO,[$3F,$06,$5B,$4F,$66,$6D,$7D,$07,$7F,$67],pattern; C= (pattern); XOR RC2 with segments to display the hundredths volt if PORTC.2 = 1 THEN C = ~C PORTB.0= C.0; a3 PORTC.1= C.1; b3 PORTC.3= C.2; c3 PORTC.4= C.3; d3 PORTC.5= C.4; e3 PORTC.6= C.5; f3 PORTC.7= c.6; Decimal Point ON, using a bitwise NOT function RETURN end
Comments are closed.