Digital_Thermometer - johnelwart/Projects GitHub Wiki
Breadboard Picture

Circuit Schematic
I used NI MultiSim to create the circuit schematic. The input and output probes represent the inputs and output going to and from the arduino board and does not contain the circuitry for the board itself. The shift registers are not a default component in MultiSim and therefore had create my own components using the component wizard. These do not have any circuitry or SPICE code behind them and therefore the circuit would not simulate. The Rotary Pulse Generator and the DHT11 Temperature Sensor also do not have anything behind them and are just there for visual purposes.
Description
*** The lab report was written by my lab partner ***
Contents
Introduction
The goal of this lab was to create a microcontroller-based thermostat capable of sensing the current temperature, adjusting a user-input ‘desired’ temperature, and switching a light emitting diode on and off, based on the relationship between the actual and desired temperatures. This should all be done using two seven-segment LED displays to display the current and desired temperatures, two eight-bit shift registers, a rotary pulse generator to adjust the desired temperature, a pushbutton to switch between the two modes, and a DHT11 temperature/humidity sensor to read the current temperature. When reset, the circuit starts in mode 1, in which the two seven-segment displays show the current temperature. In this mode, the temperature is read, and the displays are updated correspondingly once every second. When the push button is pushed, the circuit toggles to mode 2, where the user can adjust the desired temperature using the rotary pulse generator. When the pushbutton is pushed again, the circuit returns to mode 1, and the LED is either activated or deactivated, depending on whether the desired temperature is greater than or less than the current temperature, respectively. To implement the delays necessary for the two modes, we used several looping routines in conjunction with the timer/counters built into the microcontroller.
How It Works
Hardware
The hardware phase consisted mainly of four parts. First, we had to ensure that we had I/O ports on the microcontroller connected to the correct pins (SRCLK, RCLK, and SER) on each shift register, and that all pins on the shift register were properly connected to voltage and ground, as shown in the Lab 2 lecture slides. Next, we had to ensure that the output pins on the shift registers were properly wired to the input pins on the digital displays. We included 1k resistors between each shift register output and digital display input so to limit the amount of current flowing into the LEDs. Then, we had to wire the pushbutton switch, as well as pins A and B of the rotary pulse generator, to input ports on the microcontroller. Finally, we had to wire the DHT11 sensor to an I/O port on the microcontroller, and to voltage and ground as well.
Software
Our software implementation begins by configuring the various input and output lines to and from the microcontroller. Outputs include SER, SRCLK, RCLK, and the control signal that turns the ‘L’ LED on and off. Once the I/O lines are configured, we ‘set’ the mode to mode one by loading the value zero into register R19. R19 is the register which controls the mode of the circuit. We also load registers R20 and R21 each with the value zero, effectively saving the values for each of the seven-segment displays, so that they can be redisplayed when toggling between modes. At this point, we enter the start of our main loop, denoted by a ‘start’ tag.
The Main Loop
The main (start) loop begins by checking for a cleared bit on line four of port D (which connects to terminal A of the RPG). If the bit is cleared, the desired temperature may need to be incremented, so we perform a relative jump to our ‘debounce2’ tag, which handles this function. If the relative jump isn’t taken, the start loop then checks for a cleared bit on line 5 of port D (which connects to terminal B of the RPG). If the bit is cleared, the desired temp may need to be decremented, so we perform a relative jump to our ‘debounce3’ tag, which handles this function. If neither of the two previous relative jumps are taken, the start loop then checks for a set bit on line 3 of port B (which is connected to the pushbutton switch). If the bit is set, the pushbutton might be pressed, so we perform a relative jump to our ‘debounce1’ tag, which processes the button push and performs all the necessary actions to toggle between modes. More detail will be provided on the implementation of debounce1, debounce2, and debounce3 later. Finally, if none of the three previous jumps are taken, the start loop compares the value in register R19 to the immediate value zero. If the two are equal, that means that we’re currently in mode one, which means that the current temperature should be read and displayed once every second. To handle this, we branch to a tag called ‘useSensor’.
Reading and Displaying the Temperature
At the end of the main (start) loop, if the circuit is currently in mode one, a branch is taken to the ‘useSensor’ tag. This block of code performs four functions:
- It makes a call to our tempSensor subroutine, which reads the current temperature and adjusts the ‘L’ LED accordingly.
- It makes a call to our display subroutine, which displays the current temperature on our seven-segment displays.
- It calls our delay_second subroutine, which creates a one-second delay in the program.
- It performs a relative jump back to the start tag.
These four lines of code alone are entirely responsible for reading the temperature, displaying it, and adjusting the LED accordingly. I will now go into greater detail about the implementation of the tempSensor subroutine.
The primary responsibilities of the tempSensor subroutine are to receive data from the DHT11 sensor, process that data so that it can be displayed on the seven-segment displays and update the LED accordingly. To kick off the process of receiving data from the temperature sensor, we first configure line four on port B as an output from the microcontroller and pull the line low. We then make a call to our timerLoop subroutine, which uses the built-in timer/counter 0 to create a 30-millisecond delay. Once the delay has expired, we configure the line as an input to the microcontroller, putting it in a high-impedance state and signaling to the temperature sensor that we’re ready to begin receiving information. The sensor will then complete the handshake by sending back an 80-microsecond low, followed by and 80- microsecond high. To guide the program through the various peaks and valleys of the signal transmission, we use a series of while-loop-like structures, which only proceed when the signal is either high or low. These two tags in conjunction will ensure that the program follows the input signal through one low-to-high-to-low transition (one bit of transmission). This basic structure is the basis of how we read in data from the DHT11 sensor.
After sending the initial 80-microsecond low and high to cap off the handshake, the sensor will then begin transmitting 16 bits of humidity data. Because we aren’t concerned with the humidity reading for this lab, we can essentially just read in these bits and discard them. We accomplish this using a loop which iterates sixteen times, waiting for the current bit to pass before continuing to the next iteration using a looping structure like the one shown in the figure below.

After sending the humidity data, the sensor then begins sending temperature data. The first eight bits of the temperature data constitute the ‘integer’ part of the temperature reading. We read in these eight bits using a looping structure which iterates eight times. In each iteration, we first wait until the line is high, and then create a 40-microsecond delay once the line goes high. To transmit a binary 0, the temperature sensor will send a 26-28 microsecond pulse, and to transmit a 1, it will send a 70-microsecond pulse. Therefore, we can use the 40- microsecond delay to differentiate between a 0 and a 1. After the 40-microsecond delay, we read the input line connected to the sensor. If the line is low, we shift register R22 left and branch back to the start of the loop. If the line is high, we shift register R22 left, set the least significant bit, wait until the line goes low again, and then branch back to the start of the loop. After iterating eight times, register R22 contains the binary value representing the current temperature as read by the sensor.
Next, we call our divide subroutine, which computes the integer result and remainder of the quotient of the value in R22 and 10, and places them in R18 and R16, respectively. Effectively, this gives us the “tens” digit (R18) and the “ones” digit (R16) of the temperature. These are the two values that will be shown on the digital displays when the display subroutine is called from the useSensor tag, after returning from tempSensor. In the meantime, however, we need to compare the stored desired temperature values in R20 and R21, to R16 and R18. To do this, we first need to convert the values of R20 and R21 from the binary value used for the seven-segment display, to their corresponding binary values representing the actual temperature. This is accomplished within our deconvert subroutine. Once we have binary values representing the actual temperature in R20 and R21, we can compare them with R16 and R18, respectively. We begin by comparing R21 to R18, because these each represent the “tens” digit of their corresponding temperatures. If the value of R18 is less than the value of R21 (the actual temperature is less than the desired temperature), we turn the LED on by activating the control signal at line 3 of port D. If the value of R18 is greater than the value of R21 (the actual temperature is greater than the desired temperature), we turn the LED off by deactivating the control signal at line 3 of port D. If the two are equal, we compare R16 and R20 (the “ones” digits) and configure the LED control signal accordingly. After configuring the LED, we convert R16, R18, R20, and R21 each from the binary value representing the temperature digits, to the binary value which makes that digit appear on the seven-segment display. We then return from the subroutine back to the useSensor tag.
The signal starts low because we are manually pulling it low. Once we release it, the signal spikes and then quickly falls again. Then, the sensor completes the handshake by sending an 80-microsecond pulse. After this pulse, it starts transmitting humidity data. This data is represented in the figure as the sixteen pulses which fall under the blue arrow. After the sensor finishes sending humidity data, it begins sending temperature data. The eight bits of temperature data that we are interested in fall under the green arrow on the figure. The rest of the data represents the decimal portion of the temperature, and then a check of the sums of each of the readings to ensure that there were no errors in transmission. We are not interested in this data, so we simply disregard it.
Interacting with the RPG and Pushbutton
I will now go into detail about the implementation of debounce1, debounce2, and debounce3. These three code segments control the flow of the program when an interaction with either the pushbutton or RPG takes place.
As described above, whenever the user presses the pushbutton, a relative jump is taken from the start loop to the debounce1 segment. The first thing that takes place within debounce1 is the I/O line that connects the pushbutton to the microcontroller (line 3 of port B) is sampled ten times and the number of samples that record zeros and ones are stored in registers R26 and R27, respectively. These ten samples will be used to effectively ‘debounce’ the signal, and allow us to determine more conclusively whether or not the button was actually pressed, or if the jump was a fluke. After recording ten samples of the I/O line, we ensure that the program doesn’t continue execution until the line is cleared (the user releases the button) using a sbic instruction in combination with a rjmp instruction. We then signal a change in mode by ‘flipping’ the value of R19 (a value of one goes to zero and a value of zero goes to one). If the updated value of R19 is one, we move the saved desired temperature values from R20 and R21 into R16 and R18 so that the desired temperature can be displayed. Otherwise, if the updated value of R19 is zero, we move the values currently in R16 and R18 to R20 and R21, effectively ‘saving’ the two values. We then set or clear the decimal point bit in R16 and R18, depending on the current mode, and jump back to start.
The other form of user interaction within this circuit comes in the form of the rotary pulse generator. As described above, if the I/O line on the A terminal of the RPG (port D, pin 4) pules before the I/O line on the B terminal of the RPG (port D, pin 5), the RPG is probably being turned clockwise. Thus, when this situation occurs, we perform a rjmp from the start loop to our debounce2 code segment, which debounces the input and processes it. It does this by first taking ten samples of the two I/O lines connected to terminals A and B of the RPG. Out of those ten samples, if the I/O line connected to terminal A is high more than the I/O line connected to terminal B, then we make a call to our holdInc subroutine, which guides the program through the remaining four states of the clockwise rotation before returning. The idea behind this approach is that if the RPG is truly being turned clockwise, the majority (likely all) of the samples taken will show that the A line is high (and thus the B line is low), since the first BA state encountered when turning the RPG clockwise is 01. We then jump to our increment tag, which ‘increments’ the value in R16 (and possibly R18 in the case of a carry), before jumping to display to display the new value.
On the other hand, if the I/O line on the B terminal of the RPG pulses before the I/O line on terminal A of the RPG, the RPG is probably being turned counterclockwise. Thus, when this situation occurs, we perform a rjmp from the start loop to our debounce3 code segment, which debounces the input and processes it. The debounce3 segment works very similarly to debounce2 in that it takes ten samples of the I/O lines connected to terminals A and B of the RPG, but unlike debounce2, debounce3 makes sure that the I/O line connected to terminal B is higher more than the I/O line connected to terminal A. The reason for that is that if the RPG is truly being turned counterclockwise, the first BA state encountered will be 10. If the B line is in fact high more than the A line, we make a call to our holdDec subroutine, which guides the program through the remaining four states of the counterclockwise rotation before returning. Then, if the circuit is in the correct mode, we jump to our decrement code segment, which ‘decrements’ the value of R16 (and possibly R18 in the case of a borrow), before jumping to display to show the new values on the displays.
Generating Delays
To generate the delays required for this lab, we used looping subroutines, in conjunction with one of the timer/counters built into the microcontroller. One example of a delay using a looping structure is the delay_10_millisec subroutine, which uses a doubly nested loop to generate a 10-millisecond delay.
Alternatively, one example of a delay using a timer/counter would be the timer subroutine, which, in conjunction with the timerLoop subroutine, can generate delays of variable length by looping a variable number of times. When called, the timerLoop subroutine first sets the pre-scaler of TCNT0 to 64 by loading TCCR0B with the value 0x03. It then calls the timer subroutine, decrements the contents of the counter register, R27, and branches back to timerLoop if R27 is greater than zero. Since the number of times that the timer subroutine is called is directly proportional to the initial value of R27, we can generate delays of variable length by simply loading R27 with different values before calling timerLoop.
Inside of the timer subroutine, we first stop TCNT0 by loading TCCR0B with the value 0x00. We then reset the overflow flag, TOV0, load TCNT0 with the value in R28 (specifying the number to start counting from), start the counter once again, and then wait until the overflow flag is set. In the case of the timerLoop subroutine, R28 contains the value zero, so TCNT0 will start counting from zero each time it is reset. The justification for this starting value, as well as the pre-scaler value chosen is shown in the figure below.

As stated above, we wanted to create a timer subroutine which causes a delay of 1 millisecond. We chose one millisecond because it is a value that can be easily extended to meet various other delay requirements within our program. For example, to initiate the handshake with the temperature sensor, we create a 30-millisecond delay by essentially calling the timer subroutine 30 times. To create a one-second delay, we just need to call the timer subroutine one thousand times. Using a 16 MHz clock source, the pre-scaler 64 produces a delay closest to the desired 1 millisecond. Therefore, we chose to use the pre-scaler 64 in our timerLoop subroutine, which we activated by loading TCCR0B with 0x03. For our purposes, the full delay of 1.024 milliseconds was close enough to the desired 1 millisecond that we decided to just start TCNT0 at zero, effectively configuring it to produce a delay of 1 millisecond.
Conclusion
From this lab, we’ve gained experience using rotary pulse generators. Specifically, we learned how to decode the signals received from the output terminals of the RPG to determine whether it was being rotated clockwise or counterclockwise. We learned how to use that information to cause a change on two seven-segment LED displays. We also gained experience using a DHT11 humidity/temperature sensor. We learned how to initiate a handshake with a sensor by pulling the I/O line low for a specified amount of time, and then putting the line in a high impedance state. We learned how to receive information from the sensor in response to the handshake, decode it to determine the temperature reading, and then display that temperature reading on the seven-segment displays. Additionally, we gained experience using the timer/counter registers built into the microcontroller to generate delays within our program. We learned that for an eight-bit timer counter register, the counter will be incremented until it reaches a value of 256, at which point it overflows, triggering an overflow flag. We learned how to use the overflow flag to create delays of variable length, and we learned how a pre-scaler value can be used to manipulate the clock frequency, thereby changing the time elapsed until timer/counter overflows.