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.