sd-card: Only add header to the results file if it’s empty
[brewing-logger:firmware.git] / humidity.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * Brewing Logger.
4  * Copyright (C) Philip Withnall 2012 <philip@tecnocode.co.uk>
5  *
6  * Brewing Logger is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Brewing Logger is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Brewing Logger.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21
22 #include <avr/io.h>
23
24 #include <util/delay_basic.h>
25 #include <util/delay.h>
26
27 #include "common.h"
28 #include "humidity.h"
29
30 /**
31  * \file
32  * \brief Humidity sensor input handling.
33  *
34  * Handling of the humidity sensor, including checksum handling and data sanity checking.
35  */
36
37 /* Reference: RHT03 data sheet. */
38
39 static void _rht03_port_input_mode (void)
40 {
41         /* Set the DDR to input mode, and disable the internal pull-up resistor. */
42         HUMIDITY_SENSOR_DDR &= ~(1 << HUMIDITY_SENSOR_DATA);
43         HUMIDITY_SENSOR_PORT &= ~(1 << HUMIDITY_SENSOR_DATA);
44 }
45
46 static void _rht03_port_output_mode (void)
47 {
48         /* Set the DDR to output mode. */
49         HUMIDITY_SENSOR_DDR |= (1 << HUMIDITY_SENSOR_DATA);
50 }
51
52 static void _rht03_port_low (void)
53 {
54         HUMIDITY_SENSOR_PORT &= ~(1 << HUMIDITY_SENSOR_DATA);
55 }
56
57 static void _rht03_port_high (void)
58 {
59         HUMIDITY_SENSOR_PORT |= (1 << HUMIDITY_SENSOR_DATA);
60 }
61
62 /* NOTE: This returns 0 or an unspecified non-zero value. Do *not* compare it directly to 1. */
63 static uint8_t _rht03_port_read (void)
64 {
65         return (HUMIDITY_SENSOR_PIN & (1 << HUMIDITY_SENSOR_DATA));
66 }
67
68 /* The bit is the LSB of the return value. */
69 static uint8_t _rht03_read_bit (void)
70 {
71         unsigned int low_counter = 0, high_counter = 0;
72
73         /* The RHT03 transmits each bit as a low level on the data line, followed by a variable-length high level. The duration of the high level
74          * indicates whether the bit is a 1 or a 0.
75          * A 1 is transmitted as 50us low followed by 70us high.
76          * A 0 is transmitted as 50us low followed by 26--28us high.
77          *
78          * We count the length of the low period and compare this to the length of the high period to work out the value of the bit. */
79         while (_rht03_port_read () == 0) {
80                 low_counter++;
81         }
82
83         while (_rht03_port_read () != 0) {
84                 high_counter++;
85         }
86
87         return (high_counter > low_counter) ? 1 : 0;
88 }
89
90 static uint8_t _rht03_read_byte (void)
91 {
92         unsigned int i;
93         uint8_t output = 0;
94
95         /* The protocol doesn't frame bytes specially (i.e. there's no start bit or stop bit. */
96         for (i = 0; i < 8; i++) {
97                 output = (output << 1) | _rht03_read_bit ();
98         }
99
100         return output;
101 }
102
103 void humidity_init (void)
104 {
105         /* Initialise the port for the humidity sensor to be an output port. We'll change to being an input port when receiving data. */
106         _rht03_port_output_mode ();
107
108         /* Serial line for the humidity sensor is normally high. */
109         _rht03_port_high ();
110 }
111
112 MeasurementResponse humidity_take_measurement (HumidityTemperatureReading *measurement)
113 {
114         uint8_t humidity_int;
115         uint8_t humidity_frac;
116         uint8_t temperature_int;
117         uint8_t temperature_frac;
118         uint8_t checksum;
119         uint16_t humidity;
120         uint16_t temperature;
121
122         /* Disable interrupts for the duration of this critical section. */
123         SREG &= ~(1 << 7);
124
125         do {
126                 /* Send a start signal to the RHT03. This means taking the data line low for 1--10ms, then taking it high and waiting for 20--40us for
127                  * a response. */
128                 _rht03_port_output_mode ();
129                 _rht03_port_low ();
130                 _delay_us (1100);
131                 _rht03_port_high ();
132
133                 /* Change the data line to be an input and wait until the RHT03 pulls it low, which should happen 20--40us after we take it high. If
134                  * 45us has passed and the sensor hasn't pulled the line low, loop and try again. */
135                 _rht03_port_input_mode ();
136
137                 _delay_us (45);
138         } while (_rht03_port_read () != 0);
139
140         /* Wait for the RHT03 to pull the line low for 80us. The RHT03 will then take it high again for 80us, then transmission will start. */
141         while (_rht03_port_read () == 0);
142         while (_rht03_port_read () != 0);
143
144         /* Receive the data. */
145         humidity_int = _rht03_read_byte ();
146         humidity_frac = _rht03_read_byte ();
147         temperature_int = _rht03_read_byte ();
148         temperature_frac = _rht03_read_byte ();
149         checksum = _rht03_read_byte ();
150
151         /* Once the data has been received, the RHT03 pulls the data line low. We then change it to be an output and take it high again. */
152         while (_rht03_port_read () != 0);
153         _rht03_port_output_mode ();
154         _rht03_port_high ();
155
156         /* Re-enable interrupts. */
157         SREG |= (1 << 7);
158
159         /* Validate the data against the checksum. The checksum is literally just the sum of the other four fields (truncated to 8 bits). */
160         if (checksum != (uint8_t) (humidity_int + humidity_frac + temperature_int + temperature_frac)) {
161                 return MEASUREMENT_CHECKSUM_FAILURE;
162         }
163
164         /* Return the measurement. The RH value is given as a percentage (and the value from the wire needs to be divided by 10 to get this), and the
165          * temperature is given in celsius. The value on the wire is given as a sign and magnitude, and the magnitude needs to be divided by 10 to
166          * give celsius. We ignore the sign and don't divide by 10 so the result remains in tenths of a degree (as required by TemperatureReading). */
167         humidity = (((uint16_t) humidity_int) << 8) | humidity_frac;
168         temperature = (((uint16_t) temperature_int) << 8) | temperature_frac;
169
170         measurement->relative_humidity = humidity;
171         measurement->temperature = (temperature & 0x7fff);
172
173         /* Sanity check the measurements. Relative humidities must not exceed 100.0%, and temperatures must not be negative. Temperatures must not
174          * exceed 150 degrees Celsius or fall below 5 degrees Celsius. */
175         if (measurement->relative_humidity > HUMIDITY_READING_MAX || (temperature & 0x1000) != 0 ||
176             temperature > TEMPERATURE_READING_MAX || temperature < TEMPERATURE_READING_MIN) {
177                 return MEASUREMENT_INVALID;
178         }
179
180         return MEASUREMENT_SUCCESS;
181 }