aboutsummaryrefslogtreecommitdiff
path: root/lib/sensor_dht11.c
blob: 3433724c2ccf509007370306e58ffaac61ca7104 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
/** library to query measurements from Aosong DHT11 temperature and relative humidity sensor (code)
 *  @file sensor_dht11.c
 *  @author King Kévin <kingkevin@cuvoodoo.info>
 *  @date 2017
 *  @note peripherals used: timer channel @ref sensor_dht11_timer
 */

/* standard libraries */
#include <stdint.h> // standard integer types

/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities

/* own libraries */
#include "sensor_dht11.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods

/** @defgroup sensor_dht11_timer timer peripheral used to measure signal timing for bit decoding
 *  @{
 */
#define SENSOR_DHT11_TIMER 3 /**< timer peripheral */
#define SENSOR_DHT11_CHANNEL 1 /**< channel used as input capture */
#define SENSOR_DHT11_JITTER 0.1 /**< signal timing jitter tolerated in timing */
/** @} */

volatile bool sensor_dht11_measurement_received = false;

/** communication states */
volatile enum sensor_dht11_state_t {
	SENSOR_DHT11_OFF, // no request has started
	SENSOR_DHT11_HOST_START, // host starts request (and waits >18ms)
	SENSOR_DHT11_HOST_STARTED, // host started request and waits for slave answer
	SENSOR_DHT11_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
	SENSOR_DHT11_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
	SENSOR_DHT11_MAX
} sensor_dht11_state = SENSOR_DHT11_OFF; /**< current communication state */

/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht11_bit = 0;

/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht11_bits[5] = {0};

/** reset all states */
static void sensor_dht11_reset(void)
{
	// reset states
	sensor_dht11_state = SENSOR_DHT11_OFF;
	sensor_dht11_bit = 0;
	sensor_dht11_measurement_received = false;
	gpio_set(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
	gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
	timer_ic_disable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL));  // enable capture interrupt only when receiving data
	timer_disable_counter(TIM(SENSOR_DHT11_TIMER)); // disable timer
}

void sensor_dht11_setup(void)
{
	// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
	rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // enable clock for GPIO peripheral
	rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT11_TIMER)); // enable clock for timer peripheral
	timer_reset(TIM(SENSOR_DHT11_TIMER)); // reset timer state
	timer_set_mode(TIM(SENSOR_DHT11_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
	timer_set_prescaler(TIM(SENSOR_DHT11_TIMER), 20-1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/20/(2**16))=18.20ms )
	timer_ic_set_input(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT11_CHANNEL)); // configure ICx to use TIn
	timer_ic_set_filter(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
	timer_ic_set_polarity(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_FALLING); // capture on rising edge
	timer_ic_set_prescaler(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse

	timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
	timer_update_on_overflow(TIM(SENSOR_DHT11_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
	timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_UIE); // enable update interrupt for timer

	timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL)); // clear input compare flag
	timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_CCIE(SENSOR_DHT11_CHANNEL)); // enable capture interrupt

	nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT11_TIMER)); // catch interrupt in service routine

	sensor_dht11_reset(); // reset state
}

bool sensor_dht11_measurement_request(void)
{
	if (sensor_dht11_state!=SENSOR_DHT11_OFF) { // not the right state to start (wait up until timeout to reset state)
		return false;
	}
	if (gpio_get(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL))==0) { // signal should be high per default
		return false;
	}
	if (TIM_CR1(TIM(SENSOR_DHT11_TIMER))&(TIM_CR1_CEN)) { // timer should be off
		return false;
	}
	sensor_dht11_reset(); // reset states

	// send start signal (pull low for > 18 ms)
	gpio_clear(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // set signal to low
	timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
	timer_enable_counter(TIM(SENSOR_DHT11_TIMER)); // enable timer to wait for 18 ms until overflow
	sensor_dht11_state = SENSOR_DHT11_HOST_START; // remember we started sending signal

	return true;
}

struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void)
{
	struct sensor_dht11_measurement_t measurement = { 0xff, 0xff }; // measurement to return
	if (sensor_dht11_bit<40) { // not enough bits received
		return measurement;
	}
	if ((uint8_t)(sensor_dht11_bits[0]+sensor_dht11_bits[1]+sensor_dht11_bits[2]+sensor_dht11_bits[3])!=sensor_dht11_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
		return measurement;
	}
	// calculate measured values (byte 1 and 3 should be the factional value but they are always 0)
	measurement.humidity = sensor_dht11_bits[0];
	measurement.temperature = sensor_dht11_bits[2];

	return measurement;
}

/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT11_TIMER)(void)
{
	if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF)) { // overflow update event happened
		timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
		if (sensor_dht11_state==SENSOR_DHT11_HOST_START) { // start signal sent
			gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
			sensor_dht11_state = SENSOR_DHT11_HOST_STARTED; // switch to next state
			timer_ic_enable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL));  // enable capture interrupt only when receiving data
		} else { // timeout occurred
			sensor_dht11_reset(); // reset states
		}
	} else if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL))) { // edge detected on input capture
		uint16_t time = TIM_CCR(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL); // save captured bit timing (this clear also the flag)
		timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
		time = (time*1E6)/(rcc_ahb_frequency/(TIM_PSC(TIM(SENSOR_DHT11_TIMER))+1)); // calculate time in us
		switch (sensor_dht11_state) {
			case (SENSOR_DHT11_HOST_STARTED): // the host query data and the slave is responding
				sensor_dht11_state = SENSOR_DHT11_SLAVE_START; // set new state
				break;
			case (SENSOR_DHT11_SLAVE_START): // the slave sent the start signal
				if (time >= ((80+80)*(1-SENSOR_DHT11_JITTER)) && time <= ((80+80)*(1+SENSOR_DHT11_JITTER))) { // response time should be 80 us low and 80 us high
					sensor_dht11_state = SENSOR_DHT11_SLAVE_BIT; // set new state
				} else {
					goto error;
				}
				break;
			case (SENSOR_DHT11_SLAVE_BIT): // the slave sent a bit
				if (sensor_dht11_bit>=40) { // no bits should be received after 40 bits
					goto error;
				}
				if (time >= ((50+26)*(1-SENSOR_DHT11_JITTER)) && time <= ((50+28)*(1+SENSOR_DHT11_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
					sensor_dht11_bits[sensor_dht11_bit/8] &= ~(1<<(7-(sensor_dht11_bit%8))); // clear bit
				} else if (time >= ((50+70)*(1-SENSOR_DHT11_JITTER)) && time <= ((50+70)*(1+SENSOR_DHT11_JITTER))) { // bit 1 time should be 50 us low and 70 us high
					sensor_dht11_bits[sensor_dht11_bit/8] |= (1<<(7-(sensor_dht11_bit%8))); // set bit
				} else {
					goto error;
				}
				sensor_dht11_bit++;
				if (sensor_dht11_bit>=40) { // all bits received
					sensor_dht11_reset(); // reset states
					sensor_dht11_bit = 40; // signal decoder all bits have been received
					sensor_dht11_measurement_received = true; // signal user all bits have been received
				}
				break;
			default: // unexpected state
error:
				sensor_dht11_reset(); // reset states
		}
	} else { // no other interrupt should occur
		while (true); // unhandled exception: wait for the watchdog to bite
	}
}