# Measuring Distance With An Ultrasonic Sensor | Embedded C Programming - Part 14

Published

Hi again! In this tutorial we will make the last device in this series that uses the 7-segment LED indicator. This time, we will create the ultrasonic distance meter based on the HC-SR04 sensor. I already talked about it in the “Obstacle Avoidance Robot” tutorial but that time we used it for rough estimation of the distance, and now we will use its full capabilities.

So the task for today is to create a distance meter that will display the distance in centimeters in the 3-digit 7-segment LED indicator. If the distance is less than 1 meter, display it in format “xx.x”, otherwise it will be shown as “xxx”.

But let’s first recall what the HC-SR04 sensor is. Again, I will just copy-paste information from this tutorial, so if you are already familiar with it, just skip to the next section.

## HC-SR04 Sensor Description

The HC-SR04 sensor is very widespread and is often included in Arduino sets. If you don’t have a set, you can buy it separately on Adafruit, Sparkfun, eBay or Aliexpress (the price is about $0.70-$1.00 from the cheaper locations).

Let’s briefly consider the principle of operation of this sensor module. As follows from its name, it is based on ultrasonic waves. These “eyes” in Figure 1 are in fact the ultrasonic transmitter and the ultrasonic receiver. The transmitter sends a short ultrasonic packet. If some obstacle appears in the way of the ultrasonic wave, it reflects and returns back to the sensor, where the receiver detects it. Knowing the speed of sound in air (which is about 340 m/s), and the time between sending and receiving the packet (time of flight or ToF), you can calculate the distance to the object (d):

We have to divide the result by two as the wave has to cover the distance twice - from the transmitter to the obstacle, and from the obstacle to the receiver.

Usually the ToF is very short, so it’s more convenient to use it in microseconds (us), and the distance is better to measure in cm, then we’ll have:

So, to calculate the distance to the obstacle we simply need to know the ToF and divide it by 58.

To understand how it’s implemented with the module, let’s consider Figure 2 which I copied from the data sheet of the HC-SR04 module.

As you can see from Figure 1, the module has four pins - VCC (5V), GND, Trig, and Echo.

VCC and GND are clear. The “Trig” is the trigger input of the module. You need to send a 10us pulse to this pin to start the ranging (see Figure 2). After that the module will send out an 8 cycle burst of ultrasound at 40 kHz and set its Echo output as high. When the receiver detects the reflected wave, the Echo goes low. Thus the Echo pulse width represents the ToF time which we need for calculating the distance. As the module simplifies the process, the only thing we need to focus on is measuring this pulse width, and converting it into the distance.

In the PIC10F200 MCU we checked the timer value at every loop iteration to get know when the pulse ends. In more advanced MCUs (to which PIC18F14K50 belongs) there is a special module which helps to precisely measure the time, which is called the ECCP (Enhanced Capture/Compare/PWM) module. This module works with either Timer1 or Timer3. We will use Timer1 this time, so let’s consider it in more detail.

## Timer1 Module

We are already familiar with the Timer0 module from tutorial 8. I mentioned in it that there are four timers in the PIC18F14K50 MCU, and they all are different. So let’s now familiarize ourselves with Timer1.

It has something in common with Timer0 but also has some unique features which we will consider now:

• Software selectable operation as 16-bit timer or counter.
• Readable and writable 8-bit counting registers (TMR1H and TMR1L).
• Selectable internal or external clock source and Timer1 oscillator option.
• Interrupt-on-overflow.
• Reset on CCP Special Event Trigger.

So unlike Timer0, Timer1 works only in 16-bit mode. But like Timer0, it has readable and writable timer counter registers, can work both from internal and external pulses, has the prescaler, and can cause the interrupt on overflow.

The unique feature of this timer is that it has a separate oscillator with two external pins to which a 32768 Hz quartz can be connected. This oscillator can be used both for clocking Timer1 and for clocking the CPU. In this tutorial we will not consider external clocking in detail and focus on the internal clock source. If you want to know more about this feature please refer to the datasheet of the PIC18F14K50 MCU.

Timer1 is configured with the T1CON (Timer1 CONtrol) register, which we need to consider in detail:

• bit #7 - RD16 (16-bit Read/Write Mode enabled). Setting this bit to 1 allows reading/writing of the Timer1 register in one 16-bit operation, resetting it to 0 enables reading/writing of the Timer1 register in two 8-bit operations.
• bit #6 - T1RUN (Timer1 System Clock Status). This bit is controlled by the MCU and indicates if the CPU is clocked by the Timer1 oscillator (T1RUN = 1) or by another source (T1RUN = 0).
• bits #5-4 - T1CKPS1 and T1CKPS0 (Timer1 input Clock PreScaler)
 T1CKPS1 T1CKPS0 Prescaler Value 0 0 1:1 0 1 1:2 1 0 1:4 1 1 1:8
• bit #3 - T1OSCEN (Timer1 Oscillator Enable). Enables the Timer1 oscillator when set to 1 or disables when reset to 0. If the oscillator is not used, it’s better to disable it to reduce the power consumption.
• bit #2 - T1SYNC (Timer1 External Clock Input Synchronization Select). This bit makes sense only when bit #1 (TMR1CS) is set to 1. It allows one to synchronize the external clock with the main CPU clock if reset to 0, or not to synchronize it when it is set to 1. When the TMR1CS bit is reset to 0, this bit is enabled.
• bit #1 - TMR1CS (Timer1 Clock Source Select). When this bit is 1, the external clock applied to pin T13CKI is used for clocking the Timer1. When this bit is 0, the internal source Fosc/4 is used as a Timer1 clock source.
• bit #0 - TMR1ON (Timer1 On). When set to 1, this bit enables Timer1, when reset to 0, it disables the Timer1.

I think the RD16 bit requires a more detailed description. 16-bit read/write mode allows you to read the whole Timer1 register at once. In this case the TMR1L register is accessed directly, and the TMR1H register is read through the buffer into which the value of TMR1H is loaded once we read the TMR1L register. The same with writing: the upper byte is written to the buffer first, and is copied to the TMR1H register when we write to the TMH1L register. This guarantees that the timer register is updated (or read) at once, not in two steps. If you don’t need high time precision, you can set the RD16 bit to 0, in this case both TMR1L and TMR1H registers will be available independently, and you can reload any of them at any time.

As for the Timer1 overflow interrupt, I’ll talk about it a bit later, after we consider the ECCP module.

If you want to know more about the Timer1, please feel free to read chapter 11 of the PIC18F14K50 datasheet.

## Enhanced Capture/Compare/PWM (ECCP) Module

Well, this module is really enhanced, and its description can take several pages, but in fact the enhancements are only in PWM mode, capture and compare are quite ordinary. Today we will consider only the capture mode in detail.

It’s not a very common thing in 8-bit MCUs that the capture/compare module is separated from the timer module, so usually each timer has its own capture/compare module. But for some reason the PIC18F14K50 MCU designers decided to stand out and be unique.

So what this module is used for? It has a 16-bit register which operates as a capture or compare or PWM register. Let’s briefly consider every of these modes.

In capture mode if there is a certain change of a dedicated pin, the value from the timer register is automatically copied into the capture register, and an interrupt may be generated. After that the capture register can be read by software. This mode is useful for precise measurement of the time between events. And we will use exactly the capture mode in our program.

In compare mode you can write some 16-bit value into the compare register, and when the timer register reaches this value some events may happen, like: timer reset, pin change, interrupt generation, A/D conversion start. We will consider it in later tutorials.

In PWM mode, the value of the 16-bit register sets the PWM width that is generated on dedicated pins. This mode is very advanced, it can generate PWM pulses on up-to four pins with the configured polarity and dead-band for motor control. And again, we will discuss this mode in one of the next tutorials.

As for me, the disadvantage is that the PIC18F14K50 has only one such module. So for instance if you use the ECCP in capture mode, you can’t use it for PWM generation. But what can you do? We’ll deal with what we have.

To configure the ECCP module, the register CCP1CON is used. We will not consider the whole register as half of the bits are used to configure the PWM mode. There are bits CCP1M0-CCP1M3 that configure the mode of the module (table 1).

### Table 1 - ECCP Module Operation Modes Configuration

 CCP1M3 CCP1M2 CCP1M1 CCP1M0 Mode 0 0 0 0 Capture/Compare/PWM off (module reset) 0 0 0 1 Reserved 0 0 1 0 Compare mode, toggle output on match 0 0 1 1 Reserved 0 1 0 0 Capture mode, every falling edge 0 1 0 1 Capture mode, every rising edge 0 1 1 0 Capture mode, every 4th rising edge 0 1 1 1 Capture mode, every 16th rising edge 1 0 0 0 Compare mode, initialize CCP1 pin low, set output high on compare match (set CC1IPF bit) 1 0 0 1 Compare mode, initialize CCP1 pin high, set output low on compare match (set CC1IPF bit) 1 0 1 0 Compare mode, generate software interrupt only, CCP1 pin reverts the state 1 0 1 1 Compare mode, trigger special event (ECCP resets Timer1 or Timer3, start A/D conversion, set CC1IPF bit) 1 1 0 0 PWM mode: P1A, P1C active high; P1B, P1D active high 1 1 0 1 PWM mode: P1A, P1C active high; P1B, P1D active low 1 1 1 0 PWM mode: P1A, P1C active low; P1B, P1D active high 1 1 1 1 PWM mode: P1A, P1C active low; P1B, P1D active high

As you can see there are a variety of modes, but we don’t need the majority of them so far, so we’ll consider them later. For now, we are interested only in the capture modes which I highlighted with the green color in table 1.

As you see, the capture event may occur on every rising or falling edge, on every 4th or every 16th rising edge. In our application we will need the first two options: rising edge to detect the time of starting the pulse, and falling edge to detect the end of the pulse. Thus we will need to reconfigure the module after each detection.

All these changes are detected only on a dedicated pin called CCP1 which is merged with the RC5 pin (Figure 3). This pins should be configured manually as an input by setting bit #5 of the TRISC register to 1. If it’s configured as an output, changing the CCP1 state by writing to bit#5 of the LATC register will cause the capture event, and this can be used as a trick if you want the capture to happen via firmware.

The ECCP module 16-bit register is called CCPR1 and contains two 8-bit registers: CCPR1H and CCPR1L. In these registers, the value of the timer register will be copied when the capture event happens.

As I mentioned before, the ECCP module is separated from the timers, and each mode works with the dedicated timer:

• Capture or Compare mode - Timer1 or Timer3
• PWM mode - Timer2

To select which timer the ECCP module will work with in capture/compare mode, there is the T3CCP1 bit in the T3CON register (actually this register configures Timer3, and we will consider it in more detail in some of the next tutorials, but for now we need only one bit of it). If this bit is 0, then the ECCP module works with Timer1, if it is 1 then the ECCP module works with Timer3.

The ECCP module is described in more detail in the chapter 14 of the PIC18F14K50 datasheet.

## Timer1 and ECCP Module Interrupts Configuration

As I mentioned before, Timer1 can cause an interrupt on overflow and the ECCP module can cause an interrupt on capture/compare event.

In tutorial 8 we learned how to enable and process the interrupt caused by Timer0 overflow. I mentioned there that the peripheral interrupts (to which Timer1 and ECCP interrupts belong) are enabled separately by setting the PEIE bit of the INTCON register to 1.

Also I mentioned that the peripheral interrupts are enabled individually in the PIE1 and PIE2 registers. Both Timer1 and ECCP interrupts are enabled using the same PIE1 register:

• bit #2 - CCP1IE (CCP1 module Interrupt Enable). When this bit is 1, the ECCP module interrupt is enabled, otherwise it’s masked.
• bit #0 - TMR1IE (Timer1 overflow Interrupt Enable). When this bit is 1, the Timer1 overflow interrupt is enabled, otherwise it’s masked.

The interrupt flags of these module are located in the PIR1 register:

• bit #2 - CCP1IF (CCP1 module Interrupt Flag). This bit has several meanings depending on the mode:
• In Capture mode if this bit becomes 1, this means that the Timer1 register capture occurred, otherwise no capture happened. This bit should be reset manually in firmware.
• In Compare mode if this bit becomes 1, this means that the Timer1 register compare match occurred, otherwise no match happened. This bit should be reset manually in firmware as well.
• In PWM mode this bit is unused.
• bit #0 - TMR1IF (Timer1 overflow Interrupt Flag). if this bit becomes 1, this means that the Timer1 register overflowed, otherwise it’s still not overflowed. This bit should be reset manually by the firmware.

I think that’s all you need to know about the Timer1 and the ECCP modules, and thus we can move forward and consider the schematics diagram of the device.

## Schematics Diagram

The schematics diagram is shown in Figure 3.

This schematics diagram is very similar to the one presented in tutorial 12: it also consists of the PIC18F14K50 MCU (DD1) and 7-segment 3-digits LED indicator (7Seg1). There are some differences in their connection though. As I mentioned earlier, the RC5 pin is merged with the CCP1 pin which we will use to connect the ultrasonic sensor. So we need to free it. Previously the segment E was connected to the RC5 pin, and if we move it to another port, this will break the simplicity of the program, so it’s better to move the DP segment somewhere else from the RC0 pins and connect the E segment instead. This is what we do here. And the DP pin is now connected to the RA4 pin of the MCU.

The ultrasonic sensor HC-SR04 (U1) has four pins as I mentioned before. The VCC and GND pins are connected to the VDD and the VSS pins of the MCU, respectively. The TRIG pin to which the start pulse will be provided is connected to the RA5 pin, and the ECHO pin, where the echo pulse will be present, should be connected to the CCP1 (RC5) pin to measure the pulse width precisely.

That’s actually everything about the device schematics diagram so we can proceed to the program code consideration.

## Program Code Description

#define _XTAL_FREQ 16000000 //CPU clock frequency

#include <xc.h>         	//Include general header file

#define A   0b00001000  	//Segment A is connected to RC3
#define B   0b10000000  	//Segment B is connected to RC7
#define C   0b00000010  	//Segment C is connected to RC1
#define D   0b00010000  	//Segment D is connected to RC4
#define E   0b00000001  	//Segment E is connected to RC0
#define F   0b01000000  	//Segment F is connected to RC6
#define G   0b00000100  	//Segment G is connected to RC2

const uint8_t digit_gen[10] = //Digits generator
{
~(A+B+C+D+E+F),     	//0
~(B+C),             	//1
~(A+B+G+E+D),       	//2
~(A+B+C+D+G),       	//3
~(F+G+B+C),         	//4
~(A+F+G+C+D),       	//5
~(A+F+E+D+C+G),     	//6
~(A+B+C),           	//7
~(A+B+C+D+E+F+G),   	//8
~(D+C+B+A+F+G)      	//9
};

uint8_t digit;          	//Digit number that is currently displayed
uint32_t distance;   		 //Distance calculated from HC-SR04 sensor
uint16_t start;         	//Start of the pulse
uint32_t tick;          	//5 ms counter

void __interrupt() InterruptManager (void)	//Interrupt vector
{
if(INTCONbits.PEIE == 1) //If peripheral interrupts are enabled
{
if(PIE1bits.CCP1IE == 1 && PIR1bits.CCP1IF == 1) //It ECCP1 interrupt is enabled and ECCP1 interrupt flag is set
{
PIR1bits.CCP1IF = 0; // Clear the ECCP1 interrupt flag

if (CCP1CONbits.CCP1M0 == 1) //If interrupt has occurred on rising edge
start = CCPR1H * 256 + CCPR1L; //Save the time of pulse starting
else //If interrupt has occurred on falling edge
distance = ((uint32_t)(CCPR1H * 256 + CCPR1L - start) * 10) / 58; //Calculate the distance
CCP1CONbits.CCP1M0 ^= 1; //And toggle the interrupt detection edge
}
}
}

//=========Main function of the program========================
void main(void)         	//Main function of the program
{
//GPIO configure
TRISC = 0b00100000;//Configure RC port as output, but RC5 is input
TRISB = 0;       	//Configure RB port as output
LATC = 0xFF;        	//Set all RC pins high
TRISAbits.RA4 = 0;  	//Configure RA4 as output
TRISAbits.RA5 = 0;  	//Configure RA5 as output

//Oscillator module configuration
OSCCONbits.IRCF = 7;	//Set CPU frequency as 16 MHz

//Timer1 configuration
T1CONbits.RD16 = 1; 	//Enables read/write in one 16-bit operation
T1CONbits.T1CKPS = 2;   //1:4 prescaler
T1CONbits.T1OSCEN = 0;  //Disable Timer1 oscillator
T1CONbits.TMR1CS = 0;   //Timer1 clock source is Fosc/4
T1CONbits.TMR1ON = 1;   //Enable Timer1

//ECCP module configuration
CCP1CONbits.CCP1M = 5;  //Capture mode, every rising edge
PIR1bits.CCP1IF = 0;	//Clear the ECCP interrupt flag
PIE1bits.CCP1IE = 1;	//Enable the ECCP interrupts
T3CONbits.T3CCP1 = 0;   //ECCP module works with Timer1

//Interrupts configuration
INTCONbits.GIE = 1; 	//Enable global interrupts
INTCONbits.PEIE = 1;	//Enable peripheral interrupts

while (1)           	//Main loop of the program
{
if (tick % 50 == 0) //If module of division tick by 50 is 0 (every 250ms)
{
LATAbits.LA5 = 1;//Set RA5 pin high to which TRIG pin is connected
__delay_us(10); //Perform 10us delay
LATAbits.LA5 = 0;//Set RA5 pin low to finish the pulse
TMR1H = 0;  	//Reset the upper Timer1 register
TMR1L = 0;  	//Reset the lower Timer1 register
}
LATB = 0;       	//Set all RB pins low to shutdown all the segments
LATAbits.LA4 = 1;   //Turn DP off
switch (digit)  	//Which digit to display
{
case 0:     	//If digit 1
if (distance > 999) //If distance is bigger than 1m
LATC = digit_gen[distance / 1000]; //Display hundreds of cm
else    	//If distance is less than 1m
LATC = digit_gen[distance / 100]; //Display tens of cm
LATBbits.LB4 = 1;   //Turn on digit 1
break;
case 1:     	//If digit 2
if (distance > 999) //If distance is bigger than 1m
LATC = digit_gen[(distance % 1000) / 100]; //Display tens of cm
else    	//If distance is less than 1m
{
LATC = digit_gen[(distance % 100) / 10]; //Display ones of cm
LATAbits.LA4 = 0;   //Turn DP on
}
LATBbits.LB5 = 1;   //Turn on digit2
break;
case 2:     	//If digit 3
if (distance > 999) //If distance is bigger than 1m
LATC = digit_gen[(distance % 100) / 10]; //Display ones of cm
else    	//If distance is less than 1m
LATC = digit_gen[distance % 10]; //Display the fractional part of cm
LATBbits.LB6 = 1;   //Turn on digit 3
break;
}
__delay_ms(5); //Perform the 5 ms delay
digit ++;       	//Select next digit
if (digit > 2)  	//If digit number is higher than 2
digit = 0;  	//Then set the digit as 0
tick ++;        	//Increment the tick value
}
}

In line 1, we, as usual, define the _XTAL_FREQ macro, this time as 16000000 because we will overclock the CPU up to 16 MHz. This value is quite convenient, and I’ll explain why later.

In lines 5-11, we define the bits of the LED indicator segments. Please note that it’s different from the ones in the previous tutorials. The E segment is now connected to the RC0 pin (line 9), and the DP segment is absent because it’s now connected to another port.

In lines 13-25, we define the digits generator array digit_gen. It has no changes in comparison to the previous tutorial, because the names of macros didn’t change, only their values.

In lines 27-30, we define the required variables. In line 27 there is the digit variable which represents the number of the LED indicator digit that is currently on. In line 28 we define the distance variable, which represents the distance in 0.1 cm measured by the HC-SR04 sensor.

The start variable defined in line 29 represents the Timer1 register value at the start of the echo pulse.

In line 30, we define the tick variable which is the counter of 5ms intervals which are formed by the delay function in line 118.

In lines 32-47 there is the interrupt subroutine. Its name is the same as in tutorial 8 but the content is different because this time we need to proceed to another interrupt. In line 34, we check if the peripheral interrupts are enabled because the ECCP interrupt, as I mentioned before, belongs to the peripheral ones. In line 36, we check if the ECCP interrupt is enabled (PIE1bits.CCP1IE == 1) and if the ECCP interrupt flag is set (PIR1bits.CCP1IF == 1). In this case we consider that the interrupt has occurred. After that, we clear the interrupt flag (line 38), because, as I said before, we must clear it with the firmware.

In line 40, we check if the CCP1M0 bit of the register CCP1CON is 1. Let’s consider this condition in detail. In line 70 of the configuration part we write CCP1CONbits.CCP1M = 5. If we look at table 1 we can see that this value (5 in decimal system is 0101 in binary system) corresponds to the “Capture mode, every rising edge”. As I mentioned before, to capture both the start and end of a pulse we need to toggle the sensing edge. In the same table 1, we find that the “Capture mode, every falling edge” corresponds to the CCP1M value of 0100 which differs from the previous value only with the last bit: to sense the rising edge it should be 1, and to sense the falling edge, it should be 0. Now line 40 should be clear. If the CCP1M0 bit is 1 then the capture happens at the rising edge which is the start of the pulse.

When the capture event happens, the value of the Timer1 register is automatically copied to the CCPR1 register, and can be now saved into some variable. So in line 41 we copy the CCPR1 value into the start variable. To get the whole 16-bit value of the CCPR1 register we need to multiply the CCPR1H value by 256 (which shifts the value 8 bits to the left) and add the CCPR1L value to the result.

If bit CCP1M0 is 0, it means that the capture has happened on the falling edge, which is the end of the pulse (line 42). In this case we take the value of the CCPR1 register and subtract the start value from it (CCPR1H * 256 + CCPR1L - start). Then we multiply the result by 10 and divide by 58 (line 43). The last two actions require additional description, and I will return to them very soon. Just believe me for now, that after this the distance variable will correspond to the measured distance in 0.1 cm.

In line 44 we toggle the CCP1M0 bit of the CCP1CON register to change the capture edge for the next time.

And this is all we do in the interrupt subroutine. As I said in one of the previous tutorials, it should be as short as possible not to block the program for a long time.

Now let’s consider the main function of the program (lines 50-124). In line 53 we configure all the bits of the port C except for the RC5 as outputs. Then in line 54 we configure all pins of port B as outputs as well. These are the pins to which the LED indicator is connected. Next we set all pins of port C high to turn off all the segments (line 55). Finally, we configure pins RA4 and RA5 as outputs (lines 56 and 57). I already mentioned that the DP segment is connected to RA4, and the TRIG pin of the HC-SR04 is connected to RA5.

In line 60 we set the CPU frequency as 16 MHz.

In lines 63-67 we configure Timer1: enable 16-bit read/write operation (line 63), set the timer prescaler as 1:4 (line 64), disable Timer1 oscillator (line 65), set the Timer1 clock source as Fosc/4 (line 66), and finally enable the Timer1 (line 67).

And now I owe you the clarification of line 43 which I promised earlier. When we set the Timer1 clock source as Fosc/4, the frequency of this source is 16 / 4 = 4 MHz. After the prescaler the input timer frequency is 4 x 1:4 = 1 MHz. So each tick of the timer corresponds to 1 / 1MHz = 1us. According to the distance formula for the HC-SR04 sensor

As every tick of the timer is 1us, if we divide the timer value by 58, we will have the distance in cm. But according to the task we need to display the fractional part of the distance for the values less than 1m. So we need to multiply this value by 10 to get rid of the floating point. Now line 43 is almost clear. Let’s look at it again.

There is something that we didn’t see before - the uint32_t type in the brackets. Before I wrote this short text I spent a couple hours trying to understand why the distance becomes wrong after some value. So, in C if you write the type in the brackets before some expression, you implicitly set the type of it. The thing is that the value (CCPR1H * 256 + CCPR1L - start) * 10 overflows the 16-bit max value of 65535, and for some reason the compiler doesn’t expand the integer type to 32-bit, which it should. So we need to set the 32-bit type implicitly, and this solves the problem. Thus the distance variable now has the measured distance in 0.1cm. OK, I guess everything is clear now, and we can return to the initialization part.

In lines 70-73, we configure the ECCP module: set the capture mode, every rising edge (line 70), clear the interrupt flag (line 71), enable the ECCP interrupt (line 72), and assign the ECCP module to the Timer1 (line 73).

In line 76 we enable all unmasked interrupts, and in line 77 we enable the peripheral interrupts. That’s all about the initialization part. Now we can move to the main loop of the program (lines 79-123).

In line 81 we check if the modulo of division of the tick value by 50 is 0. The tick is incremented every 5 ms so this condition becomes true every 50 x 5 = 250 ms. So we start the distance measurement 4 times per second. To initialize the measurement we set the RA5 pin high (line 83), wait for 10 us (line 84), and set the RA5 pins low (line 85). Also we reset the Timer1 register by setting the TMR1H and TMR1L register to 0. Please pay attention that as we use the one 16-bit read/write operations, we need to set the upper register first.

Then we have the standard dynamic indication algorithm that we established in tutorial 6. First we turn off all the segments by setting all pins of port B low (line 89). Then we turn off the decimal point by setting the RA4 pin high (line 90). Then we check which digit is needed to be turned on in the switch construction (lines 91-117). So, according to the task we need to display the distance in different formats for values less than 1m (xx.x) and for values bigger than 1m (xxx). Thus, in each case part we check if the distance value is bigger than 999, which corresponds to 99.9 cm (lines 94, 101, 111). If it is, we display the thousands of the distance (which correspond to hundreds of cm) in the first position (line 95), hundreds of the distance (which correspond to tens of cm) in the second position (line 102), and tens of the distance (which correspond to ones of cm) in the third position (line 112).

If the distance is less than 1m, the displayed values are different. We display the hundreds of the distance in the first position (line 97), tens of the distance in the second position (line 105), and ones of the distance in the third position (line 114). Also, we turn on the decimal point at the second position in line 106.

After setting the correct digits shapes at the required positions, we turn on the corresponding digits in lines 98, 108, 115.

After turning on the segment we perform the 5ms delay (line 118), and select the next digit to be displayed (lines 119-121).

In line 122, as I mentioned before, we increment the tick value every loop iteration.

And that’s finally all about the program code. As you can see it’s quite simple and similar to ones that we had in some previous tutorials.

Now you can assemble your device, compile the code, and download it to the MCU. If you made everything correctly, the device will start working without additional adjustment. If something is wrong, check the assembly of your circuit. If nothing is displayed at all, make sure that you didn’t forget to add the configuration bits.

As homework, try to change the measurement units: mm, inches, feet, etc.

## Friends of CircuitBread

Get the latest tools and tutorials, fresh from the toaster.