Arduino: Simple LCD Thermometer with USB interface

From Luky-Wiki
Jump to: navigation, search

Attention: this page is work in progress. TODO: ...

My motivation for "Simple LCD Thermometer with USB interface" is my server room. It is important to keep temperature and humidity in correct range. I am using automatic monitoring to be sure that everything is in "green" state. Previously I was using Conrad TFD 128 logger. It is good product but it have several design problems. To fulfill my needs I created my own device.

Technical specification

  • Temperature and humidity collected in 10 second interval
  • LCD interface with warnings (Temperature: hot / cold, Humidity: wet / dry, Dew point: Too high / Too low)
  • LCD interface with 5 tactile keys (+1 reset)
  • History and diagnostic information on LCD
  • USB interface with simple text oriented protocol
  • Historical data stored in RAM memory readable via LCD and USB interface
  • Historical data with 60 minutes interval (display) and 30 minutes interval (USB)
  • Historical data up to 24 hours
  • Minimum and Maximum (cleared during boot or on user request)
  • Absolute detection range (indoor use):
    • Temperature: +1C - +50C, Resolution: 0.1C
    • Humidity: 1% - 99%, Resolution: 1% (display), 0.1% (USB)
    • Dew point: -65C - +50C, Resolution: 0.1C
  • Internal monitoring of voltage for mCPU and sensor (Vcc between 4.8V and 5.2V reported as good)
  • Range indicating "normal" condition:
    • temperature: 20 - 25C
    • humidity: 40 - 70%
    • dew point: 10 - 16C
  • Device is pooling sensor for new data each 10 seconds. Displayed data may be up to 20 seconds old due to nature of selected sensor chip.
  • Device can adjust brightness of backlight on supported LCD "shields".

Note 1: Selected temperature range is based on expected usage pattern (indoor only). Temperature sensor can measure wider range of temperature but this may complicate code a bit (especially negative temperature).

Note 2: Selected temperature and humidity range is not proof that Arduino UNO board will survive it (e.g. high temperature and/or humidity).

Note 3: Values outside of range are internally used to indicate problem with sensor reading or reading outside of bounds.

LCD Interface

Most of the details are accessible via LCD interface. "Startup screen" is displayed during boot-up. After successful initialization device move to "Normal screen".

Startup screen

During boot-up device show source code and build date.

|sTemp_LCD_USB   |
|Jan 17 2016     |

Device show also reboot source. If there is more that one reboot source set then device cycle through all of them. It is common to see BORF and PORF after device power up.

+----------------+ +----------------+ +----------------+ +----------------+
|sTemp_LCD_USB   | |sTemp_LCD_USB   | |sTemp_LCD_USB   | |sTemp_LCD_USB   |
|(Watchdog)  WDRF| |(Brown-out) BORF| |(External) EXTRF| |(Power-on)  PORF|
+----------------+ +----------------+ +----------------+ +----------------+

Presence of boot loader (if detected) is indicated by separate screen.

|sTemp_LCD_USB   |
|(Loader)  LOADER|

Note: For details about boot loader see Note 1 at end of page.

Normal screen

Displayed during regular operation. Left part of second line show device status:

  • First position:
    • ♡ altering with ♥
      • everything looks good (each successful read will change "fill" of hearth symbol)
    • exclamation mark
      • minor problem reported, "warning level" (example: device was recently rebooted, problem with sensor in past, ...)
    • exclamation mark (negative font)
      • serious problem reported, "error level" (example: problem with power supply, problem with sensor, invalid data received, ...)
  • Second position:
    • Up and Down arrows are on display when there is activity on serial (USB) interface.
+----------------+ +----------------+ +----------------+
|T: 21.3C H: 50% | |T: 28.8C H: 90% | |T: 10.2C H: 10% |
|♡↑↓  NORM   NORM| |♥↑↓  HOT    WET | |♡↑↓  COLD   DRY |
+----------------+ +----------------+ +----------------+

Note 1: If keyboard is not used for 10 minutes and display is not on "Normal screen" or "Big Numbers" then device automatically return to "Normal screen".

Note 2: All text indicators are blinking when displaying other that normal condition (Hot, Cold, etc.)

Dew point screen

Show current dew point value.

+----------------+ +----------------+ +----------------+
|Dew point:  14.1| |Dew point:  20.2| |Dew point: - 9.1|
|     Comfortable| |        Too high| |         Too low|
+----------------+ +----------------+ +----------------+

Note: Negative values are indicated by minus character. Location of this character is fixed. Therefore it is not aligned with number for values between "-0.0" and "-9.9". This apply for all screens and serial (USB) commands working with Dew point value. You may take extra precaution while working with this value (for example when it is automatically imported using calculation tools and/or programming language)

History + min/max screen

Set of screens to show minimum, maximum and historical values. Left down corner of screen show selected mode:

Temperature and humidity:

+----------------+ +----------------+ +----------------+ +----------------+
|T: 21.3C H: 50% | |T: 21.3C H: 50% | |T: 21.3C H: 50% | |T: 21.3C H: 50% |
|-01h NORM   NORM| |-02h NORM   NORM| |-23h NORM   NORM| |-24h NORM   NORM|
+----------------+ +----------------+ +----------------+ +----------------+

+----------------+ +----------------+
|T: 20.0C H: 30% | |T: 25.0C H: 30% |
+----------------+ +----------------+

Dew point:

+----------------+ +----------------+ +----------------+ +----------------+
|Dew point: 14.1C| |Dew point: 14.1C| |Dew point: 14.1C| |Dew point: 14.1C|
|-01h Comfortable| |-02h Comfortable| |-23h Comfortable| |-24h Comfortable|
+----------------+ +----------------+ +----------------+ +----------------+

+----------------+ +----------------+
|Dew point: 12.1C| |Dew point: 16.1C|
|MIN  Comfortable| |MAX  Comfortable|
+----------------+ +----------------+ 

Note: See details about negative values at Dew point screen

Diagnostic screen

System support following diagnostic screens:

Diag: uptime

Time since last reboot (uptime):

|Diag: uptime    |
|   1234d 21h 45m|

Note: Values are Days, Hours, Minutes

Diag: sensor

Current status and status in past:

|Diag: sensor    |
|OK        Mising|
  • Text on left show current status (Last reading).
  • Test on right show if there was problem in past (Reason of last unsuccessful read. Only device reboot will reset this to "OK").

Possible values:

  • OK -> sensor is properly wired and responding
  • Missing -> no response from sensor at all or wrong start sequence received
  • CRC Err -> all data received but CRC or Parity don't match
  • Timeout -> timeout while receiving data

Diag: history

Numbers of consecutive history banks populated with valid data:

|Diag: history   |
|      03        |

All history banks contain valid data:

|Diag: history   |
|      ok        |

Note: There are two banks for each hour. "03" mean that device have data for "1.5 hours" of history (3x 30 minutes).

Diag: reboot by

Show reboot source type. Each source ma be "set" (indicated by hardware) of "not set" (not indicated by hardware).

  • W-- reboot source is not indicated by hardware.
  • WDR reboot source is indicated by hardware.
|Diag: reboot by |
|W-- B-- E-- P-- |
|Diag: reboot by |

Note 1: this is example. There is no possibility that hardware indicate all reboot sources at once.

Note 2: "<" is shown when device detect bootloader.

Note 3: check Startup screen for more details.

Diag: Power

Show voltage for mCPU and sensor measured by Armel itself.

  • Normal condition:
|Diag: Power     |
|Vcc: 4.9 V  OK  |
  • Error condition (voltage too low):
|Diag: Power     |
|Vcc: 4.0 V Err  |

Diag: version

Show build date.

|Diag: version   |
|Jan 17 2016     |

Big Numbers screen

This screen show temperature only while utilizing both lines. Displayed number is "bigger" than regular characters displayed by internal character generator.

Left side show "Temp" on first line. Second line show global status (if not OK).

This screen use ability to define up to 8 custom characters to get number on both lines. Specially selected bitmaps are stored in 8 custom banks. Those bitmaps are used to generate "big" numbers appearing on both lines of display.

Note : see example distributed with code for more details.

Screen position

Screen can be selected by tactile keyboard and position of screens are displayed bellow. Line between displays represent possible transition from one display to another. Graph is aligned in natural order. (e.g. If you are on Normal display then "Up" key switch to Temp/Hum minimum display).

Normal screen is selected after device boot-up.

                       return back to       return back to 
                      temp/hum display     temp/hum display
                             | |                  | |
                     +----------------+   +----------------+
                     |Min/Max cleared |   |Min/Max cleared |
                     | any key to ret.|   | any key to ret.|
                     +----------------+   +----------------+
                             | |                  | |       
                     +----------------+   +----------------+
                     |T: 25.0C H: 50% | - |Dew point:  16.1|
                     |MAX  NORM   NORM| - |MAX  Comfortable|
                     +----------------+   +----------------+
                             | |                  | |       
                     +----------------+   +----------------+
                     |T: 20.0C H: 30% | - |Dew point:  12.1|
                     |MIN  NORM   NORM| - |MIN  Comfortable|
                     +----------------+   +----------------+
                             | |                  | |       
+----------------+   +----------------+   +----------------+   +----------------+
|(Big numbers)   | - |T: 21.3C H: 50% | - |Dew point:  14.1| - |Diag: uptime    | -                          - ("Normal screen")
|                | - |♡↑↓  NORM   NORM| - |     Comfortable| - |    123d 21h 45m| - (all other diag screens) -
+----------------+   +----------------+   +----------------+   +----------------+
                             | |                  | |       
                     +----------------+   +----------------+
                     |T: 21.3C H: 50% | - |Dew point:  14.1|
                     |-01h NORM   NORM| - |-01h Comfortable|
                     +----------------+   +----------------+
                             | |                  | |       
                       history up to        history up to 
                          24 hours      -      24 hours
                       for temperature  -  for dew point
                        and humidity

If there is no action for 10 minutes and display is not showing "Normal screen" or "Big numbers" then screen move back to "Normal screen".

Serial (USB) interface

  • Device use virtual serial port over USB.
  • Protocol is text oriented.
  • System is case sensitive and silently ignore unknown requests.
  • Additional requests are ignored while device is processing / sending response.
  • Configuration of serial port is 9600, 8-N-1, flow control: no
  • Device send blank (space) character before responding to query.
  • Maximum size of response is: 64 characters.

Format of response

Example: Temp: 10.1 C, HUM: 36.1 %, Dew: - 4.2 C, 1 *E62D

  • all values are send in format: "type: value unit"
  • more values in one message are separated by coma (,)
  • last value in message is sequential id. Number from 0 to 3.
  • CRC is separated by asterisk (*) and is calculated as CRC-16.

Message id start at 0. Increment sequentially to 3 and then wrap around to 0. This ensure that each message is unique even when temperature / humidity reading result in same number.

Commands / data query

Communication rules:

  • Wait for "silence on line" before requesting data.
  • Request data by issuing one upper case character.
  • Device schedule response using internal scheduler. Response is send once all higher priority tasks are done.
  • Additional response lines are can be requested by issuing same request again.
  • Version command can be used as "ping" to check if device is alive (it use smallest amount of CPU cycles to provide response). It can be also used to reset internal counter on multi line responses.


List of commands:

? -> Help 1/2: Ver., Reboot, Temp., Alarms, History, 0 *7F6D
? -> Help 2/2: MinMax, Diag, Lcd, Flash 1 *E2D8


Build date / time.

V -> Version: Jan 17 2016, 20:48:03, 2 *6018


This command force device reboot by disabling interrupts and initiating endless loop. In such a condition device is automatically restarted by watchdog.

R -> (no response by device)

Note: Display show "Reboot ..."


This command force device reboot using connection to external reset pin. After reset boot loader is invoked and accept new firmware

F -> (no response by device)

Note 1: Display show "Flash FW ..."

Note 2: If reboot to boot loader fail or no FW is send to device then device reboot and return to normal operation.

Temperature / Humidity / Dew point

Last temperature, humidity and dew point reading.

T -> Temp: 21.1 C, HUM: 47.1 %, Dew:   9.4 C, 3 *542B

Note: See details about negative values at Dew point screen


Current alarm condition.

A -> Temp: NORM, Hum: NORM, Dew: Too low    , 0 *66F0


Historical data (up to 24 hours in 30 minutes increments).

H -> -00.5h, Temp: 14.6 C, Hum: 40.6 %, Dew:   1.4 C, 1 *E823
H -> -01.0h, Temp: 14.1 C, Hum: 40.1 %, Dew:   0.8 C, 2 *1B46
H -> -23.5h, Temp: 15.6 C, Hum: 41.6 %, Dew:   2.6 C, 3 *863E
H -> -24.0h, Temp: 15.1 C, Hum: 41.1 %, Dew:   2.0 C, 0 *83E4

Minimum / Maximum + clear

Minimum and maximum values + command to clear data.

M -> Temp/MIN: 10.1 C, Hum/MIN: 36.1 %, Dew/MIN: - 4.2C, 1 *13B0                         
M -> Temp/MAX: 22.2 C, Hum/MAX: 42.1 %, Dew/MAX:   8.7C, 2 *0B01
M -> Clear MIN/MAX ?, 3 *3AB0
M -> MIN/MAX cleared, 0 *118E

Note: See details about negative values at Dew point screen


Show diagnostic data in similar format as on screen.

D -> UpD: 0002, UpH: 02, UpM: 56, UpS: 38, 1 *F4B3
D -> Sensor: OK     , Missing, 3 *7810
D -> History: ok, 2 *B419
D -> Reboot by: WDR B-- E-- P--<, 0 *7D1B
D -> Power: 4.9 V, OK , 3 *956E
D -> Status: Warning, 0 *4F74

Note 1: See Diagnostic screen for more details.

Note 2: Lines in example was captures in different time to show specific details. This caused jumps in sequential id. I keep it as is to preserve line checksum.


Show content displayed on Display. There is no line break between lines on display.

L -> T: 22.5C H: 22% !<>  NORM   DRY  0 *D7BD

Note: This command is for debugging only. Don't use it for other purposes than debugging code. It is not working with "big numbers" screen due to nature of this screen.

Internal details

I decided to use fixed connection layout to simplify code. System is internally "task" oriented.

Physical connections

Serial (USB) interface (connection)

Serial converter is connected to PD0 / PD1 on micro-controller. Internal serial logic handle serial interface.


Character display (16x2) in 4bit mode is connected in following way:

PB0 -> RS
PB1 -> E

PB2 -> backlight

PD4 -> DB4
PD5 -> DB5
PD6 -> DB6
PD7 -> DB7

Note: All work is based on "DF Robot" style LCD Shield. See Assembly instructions how to modify it. Code require small modification of it and may kill Arduino UNO board if connected without modifications.


DF Robot style LCD Shields have backlight of LCD wired to Pin 10. This allow Atmel micro-controller on Arduino UNO board to control "light" on "display".

Unfortunately this "control" is not well made on almost all shields. Backlight is controlled by bipolar transistor connected between backlight pins on LCD module itself and ground. Base of this transistor is connected directly to Pin 10 on Arduino UNO board. There is also pull-up resistor between base of transistor and 5V power supply.

This configuration result in following "functionality":

  • Pin 10 is tri-state (input) -> LCD backlight is ON as pull-up resistor "open" transistor.
  • Pin 10 is LOW (output) -> LCD backlight is OFF.
  • Pin 10 is HIGH (output) -> Pin 10 output driver is shorted to ground via backlight transistor.

LCD backlight will be ON when HIGH output is selected on Pin 10. This will last until backlight transistor fail and / or Pin 10 driver too. Excessive amount of power is consumed during this state.

It is possible to add workaround in code only for simple On / Off operation. It is not possible to add workaround for PWM controlled brightness adjustment. PWM work by rapidly switching output to High and Low state. This meant that output is shorted to ground each time there is High part of PWM pulse.

This design problem was not found by QA at DF Robot and wrong design was copied by many electronics suppliers. It can be fixed just by replacing bipolar transistor by unipolar (mosfet). It is not easy to replace it at "home" and also not easy to get information from suppliers (to get fixed shield). My Assembly instructions contain workaround for this.


Keyboard is connected to analog input on pin AD0. Internally it acts as voltage divider.

Function Resistor H Resistor L Voltage (expected) Voltage (measured) ADC (expected)
Right 3K 0R 0.00 V 0.00 V 000
Up 3K 330R 0.50 V 0.49 V 025
Down 3K 680R 1.26 V 1.24 V 064
Left 3K 1K 2.01 V 2.00 V 102
Select 3K 3K 3.13 V 3.12 V 159
(none) 3K inf 5.00 V 4.98 V 255
(vcc) - - 5.00 V 4.98 V 255

Note: It looks like resistor matrix is not standardized along different vendors. Current configuration is for DF-Robot style LCD shield. Edit source code keyboard.c if values are not matching yours LCD shield.

Temperature and humidity sensor

Code expect AM2301 or AM2302 connected to "Analog 5" / PC5 pin on Arduino UNO board.

PC5 / A5 -> bidirectional data


Software way of "jump" to boot loader may fail under certain condition. Additionally "optiboot" is ignoring all reset sources except external reset. It is not easy to simulate this using software.

Arduino UNO board have connection from DTR signal of serial line to reset pin. This add possibility to reset mCPU remotely and enter boot loader. Unfortunately this may cause unwanted reset (for example if OS is scanning ports or app wrongly use modem signals). I recommend to cut "reset-en" on board to fix this problem.

Unattended FW update is not possible with "reset-en" disconnected. To fix this connect "Analog 1" / PC1 pin to reset pin on board. This add possibility to reset mCPU if flash of firmware is required. Reset is controlled using SW and requested by "F" via serial line.

PC1 / A1 -> connected to reset on mCPU

Timer (wall clock)




Serial interface




Sensor reading


Notes / Errata

Note 1 - Boot loader


Assembly instructions

Ardino UNO board

Step 1

Power-up Arduino UNO board (only) and connect to AVR Dragon using ISP. Flash boot loader using:

cd loader

Note: New boards normally contain optiboot (installed at factory). This step is optional for those boards.

Step 2

Cut "reset-en" on Arduino UNO board.

LCD Shield

Step 3

Cut / De-solder "Digital 10" pin from LCD shield. See Back light for more details.

Step 4

Cut / De-solder ISP header on LCD shield. (it may not fit to case with ISP header in place)

Step 5

Locate transistor operating backlight and pull-up resistor on base (R7 / Q1 on my boards). Remove resistor. If possible then remove also transistor. Alternatively you can remove connection between LCD backlight and transistor. Vendors tend to put those components between boards. Pick one which is easiest to reach for you.

Verify that LCD backlight is not lit after this modification. Verify that pin 15 on LCD module (LED+ / A) is connected to VCC (5V) and pin 16 (LED- / K) is not connected (normally connected to ground via transistor).

Verify that LCD backlight is lit when connecting pin 16 on LCD (LED- / K) to ground. Current should be less than 18 - 20 mA.

Step 6

Round left top corner using file tool or Dremel. Arudino LCD cases tend to be round at corners. DF Robot style shield is sharp at this edge and don't fit those cases. There are no PCB tracks along this edge so it is safe to round it.

Step 7

Solder connection from "Analog 1" to "Reset" on LCD shield.

Step 8

Connect / solder AM2301 or AM2302 to "Analog 5" and Power/GND pin.

Step 9

Stack LCD shield on top of Arduino UNO.

Step 10

Solder connection form Arudino UNO board pin 10 to LCD board (not shield) pin 16 (LED- / K).


Step 11

Install thermometer in case. ( I like this one -> Cade Case )

Step 12

Connect Arduino UNO board to PC using USB. Hold reset button. Issue following command immediately after releasing it:


Note: Verify than device file is correctly selected in script.

Step 13

Verify functionality using diagnostic screens.

Step 14

Leave thermometer running for at least 30 minutes and review diagnostic screens once more.

Download / Change log