Tuto. 10 - Humidity sensor
PIC tutorial 10 - Humidity sensor
Fig. 1 Board 4 with humidity sensor and graphic LCD
Description:
This tutorial covers the humidity sensor and some graphic display use: drawing a graph, plotted as lines or dots.
The tutorial will read and store a number of records per given time, and display the graph on the display. The program is setup to record 80 readings over 24 hours. It displays the current humidity, and updates the display every second, while displaying the minimum and maximum reading in the last 24 hours. The graph will autoscale to display the number of records on the given width and the range of values between top (max) and bottom (mini).
You will need a processor board, a power supply, the LCD display and the humidity sensor.
We will build the humidity sensor library, which will count the pulses coming from the sensor with timer 1 over a given time to determine its frequency. The frequency is dependant of the humidity.
The boards can be powered by one of the PSU boards (powered from USB port), or between 2.7 to 5V.
Requirements:
Processor boardAny board (board 4 in example)
Extension(s)Humidity sensor
Graphic LCD display
Power supplyPSU 1, 2 or 3. Depends on board used.
LanguageCCS C
ProgrammerPicKIT 2
Program:
#define board_id 4
#define Nb_records 80 // numbers of records to store in 24 hours
#define samples 5 // numbers of measures to average per record
#define inc_period 10 // Delay in ms between each interrupt
#define Timer0_Reload (256-(inc_period*(CRYSTAL_FREQ/(4*256*1000)))) // Timer 0 reload = 256 - Tint*(Crystal/(4*Prescaler)) where Tint is the delay between interrupts
#define int_count 1000/inc_period // Number of interrupt to get 1 sec
#define int_store 24*3600/Nb_records // Calculate numbers of second to count before updating records in table (24 hours of 3600 seconds / Nb records to store per day)
#include "lib\mainboard.c"
#include "lib\nok3310_lib.c"
#include "lib\RH_lib.c"
#priority RTCC
unsigned char count, index, minRec, maxRec;
unsigned char reading[Nb_records];
long countSec;
unsigned char readData (void) { // Read data to store, with samples records averaged. Reads humidity in this example
unsigned char i;
long average=0;
for(i=0; i<samples; i++){
average += hum_read();
}
average /= samples;
return average;
}
#int_rtcc void Timer0Interrupt ( void ) { // Timer 0 will generate regular interrupt
unsigned char rec, i;
set_timer0(Timer0_Reload); // reload timer 0 with predetermined value
count ++; // increment interrupt counter
if (count == int_count) { // if desired number of interrupt counted for 1 sec
count = 0; // reset interrupt counter
countSec ++; // Updated second counter
rec = readData();
if (countSec >= int_store) { // if number of seconds counted between records
countSec = 0;
if (index >= Nb_records) { // All records are filled.
for(i=0; i<8; i++){
shift_right(reading, Nb_records, 0); // Shift record table: free last byte and discard first one
}
reading[Nb_records-1] = rec; // Store reading at last record
minRec = 255;
maxRec = 0;
for ( i = 0; i < Nb_records; i++ ) { // Go through all records to get mini and maxi
rec=reading[i];
if (rec >= maxRec) maxRec = rec; // get maxi
if (rec <= minRec) minRec = rec; // get mini
}
} else { // record table not full
reading[index] = rec; // Store data at current index
if (rec >= maxRec) maxRec = rec; // update maxi
if (rec <= minRec) minRec = rec; // update mini
index ++; // Increment index
}
}
}
}
void main() {
boot_up();
count = 0;
countSec = 65530;
index = 0;
minRec = 255;
maxRec = 0;
setup_timer_0 ( RTCC_DIV_256 | RTCC_8_BIT ); // SETUP TIMER 0
Init_display();
enable_interrupts ( INT_RTCC ); // enable Timer0 interrupt
enable_interrupts ( GLOBAL ); // enable all interrupts
while (true) {
if (count == 0) { // count = 0 every second, update display
printf(display_putc, "\f%02u%% mM:%02u-%02u%%", readData(), minRec, maxRec);
if (index >= Nb_records) { // if record table full, plot all records
Nok3310_graph(reading, Nb_records, 28, 80, 0);
} else { // else, only display the records stored
Nok3310_graph(reading, index, 28, 80, 0);
}
}
}
}
The program first defines various variables and macros. They determine how measurement are done and how often they are stored:
- board_id: processor board used (board 4 with PIC 18F2320 in the exemple)
- Nb_records: numbers of records to store in set time
- samples: number of successive readings to average when reading the sensor
- inc_period: delay in ms between each interrupt
- Timer0_Reload: macro that calculates timer 0 reload to get interrupts every inc_period
- int_count: number of interrupts to count to get 1 sec
- int_store: macro to calculate the number of seconds to count between each table update
We then loads the libraries for the processor, graphic lcd display and humidity sensor. A few variables are then defined to store various data.
The function readData is called when measuring. It reads the humidity sensor samples times and average the result. It is fairly easy to modify this function to get data from other sensors.
The main part of the program is in the interrupt from timer 0: it basically counts until it needs to read the sensor, and update the array. Timer 0 is loaded to get an interrupt every 10ms (inc_period). If one second has elapsed since the last reading, when read the sensor. If it is time to update the table, we check if we need to update the next record (table not full) or shift the whole table, delete the oldest record and store the new reading. The min and max values are also updated.
The main program initialize the board, some variables, timer 0, the display and then enables the interrupts. The main loop is then started. If the second flag is set (count = 0), we update the screen with the current reading, min and max value and update the graph, before starting the loop again. If the table is not full, we only display the readings stored.
long rh_corr;
unsigned char hum_read() {
long pulses;
unsigned char hum;
disable_interrupts ( INT_RTCC ); // disable timer 0 interrupts during measurement
rh_corr = 0; // correction for counter (used when calibrating the sensor
set_timer1(0); // set timer 1 counter to 0
setup_timer_1(T1_EXTERNAL|T1_DIV_BY_1); // set Timer 1 to external clock input and no prescaler. The output of the humidity sensor is connected to timer 1 input
delay_ms(50); // count pulses from sensor for 50ms
pulses = get_timer1(); // Read timer 1, which contains the number of pulses that came from the sensor
pulses += rh_corr; // Correct counter
if (pulses > 1700) { // calculate humidity
hum = (410-0.1955*pulses);
} else {
hum = (320-0.1429*pulses);
}
if (hum > 98) hum = 98;
if (hum < 6) hum = 6;
enable_interrupts ( INT_RTCC ); // re-enable timer 0 interrupts
return(hum);
}
RH_lib.c has just one function, to read the humidity. The frequency coming out of the humidity sensor is proportional to the relative humidity of the air. So by counting the pulses during a set time, we can determine the frequency, and therefore the humidity.
First we disable the interrupts from timer 0. rh_corr is used to correct the count of pulses when calibrating the sensor. We will come back to it later. Timer 0 is reset to 0 and set to count the pulses coming from the external input. Then it waits for 50 ms, time to count the pulses from the sensor. We can then read timer 0 to get the number of pulses. The count is updated with the correction and then the humidity is calculated. Here are some explanation:
The frequency from the sensor is determined by the following formula: F = 1.38/(300 000 x Cx), where Cx is the capacity from the humidity H1 sensor. We count during 50ms, so the counter is determined with: =1.38/(300 000 x Cx / 50ms). The following table shows the counters for different humidity (taken from datasheet of H1 sensor). It will allow to determine a formula to get the humidity out of the counter read. Fig. 2 shows the graph of humidity vs counter.
Fig. 2 Humidity/counter graph
| Cx (pF) | Humidity | Counter |
|---|---|---|
| 112 | 10 % | 2054 |
| 115 | 20 % | 2000 |
| 118 | 30 % | 1949 |
| 121 | 40 % | 1901 |
| 124.5 | 50 % | 1847 |
| 128 | 60 % | 1797 |
| 132 | 70 % | 1742 |
| 137 | 80 % | 1679 |
| 143.5 | 90 % | 1603 |
To keep the formula simple, we will split the graph in 2 linear zones, with a counter of 1700 (75% rh) as the limit of one and begining of the second.. Because the graph is fairly linear, this trick keeps the accuraty of the sensor pretty good. So we get the folowing formulas;
- up to 75% rh: rh = (410-0.1955*pulses).
- above 75% rh: rh = = (320-0.1429*pulses).
In the libray, if the counter (pulses) is above 1700, it uses one formula, else it uses the other one. The value is then checked to make sure it is within measurable values and timer 0 interrupt is re-enabled.
The graphic library is updated with a new function: Nok3310_graph. It will plot as bargraph or dots a set of data on the screen, while auto ranging to better display the data. It will display the lowest value -1 at the bottom, and the highest + 1 as the ceiling. The function works with 8 bits value, as the resolution is limited by the screen (48 pixels max with the nokia screen)
void Nok3310_graph(unsigned char *Ptr, unsigned char nbRec, unsigned char height, unsigned char width, unsigned char type) {
unsigned char i, j, k, maxi, mini;
long rec;
maxi = 0; // Get mini and maxi to scale graph
mini = 255;
for ( i = 0; i < nbRec; i++ ) { // Go through all records to get mini and maxi
rec=Ptr[i];
if (rec >= maxi) maxi = rec; // get maxi
if (rec <= mini) mini = rec; // get mini
}
if (maxi < 255) maxi += 1; // update maxi and mini to display maxi-mini +-1
if (mini > 0) mini -=1;
k = width/nbRec; // k will give the number of time to repeat the same reading to spread the number of records across the width of the graph
for ( i = 0; i < nbrec; i++ ) { // plot each record k times to scale the graph
if (maxi-mini == 0) {
rec = 3;
} else {
rec = (Ptr[i]-mini)*height/(maxi-mini)+3; // Get record value and scale it: (Reading-mini)*height/((maxi-mini))
}
for ( j = 0; j<k; j++ ) {
Nok3310_plot((i*k)+j+1, rec, type);
}
}
}
The function takes the folowing parameters:
- Ptr: pointer to the array of variables to display.
- nbRec: number of records from the array to display.
- height: height in pixels of the graph
- width: width in pixels of the graph
- type: display a bargraph (0) or dot graph (1)
First, the mini and maxi values are initialised, then each record is read to get the proper values. It will determine the range of data to display, and the higher and lower limits. The limits are updated if they are not at the min or max value (0 or 255).
With the width and number of records to display, we can determine the width in pixel that each record will take on the graph (held in k).
Then for each record, we calculate the value in pixels to plot, based on the value read, min and max limits and height of the graph. height = record value * height / (maxi - mini). An offset of 3 is added because my screen has 36 pixels high. (offset from the bottom).
The record is displayed with k columns of determined pixels.
Compile the project and program the target with a PicKIT 2 programmer for example. Connect the processor board to the graphic LCD display board on port B and the humidity sensor on port C, making sure the jumpers on the board match the processor board used to map the timer1 clock in pin.
Power up the boards.
It will take 24 hours before the board fills the graph with 80 readings. It reads the humidity every 18 mins to update the graph, but updates the current humidity every second.
Files and links:
Tutorial 10 source files.Processor board 4.
Graphic LCD Display board.
Humidity sensor board.

