This post is part of a series where I develop C programs for my Arduino Uno. This time I wanted to interface with a rain sensor.
This post will show an application that uses the following functionalities:
- Read a digital signal from a pin (this is an easy one),
- Read an analog signal with the ADC,
- Dim a LED with PWM, and visualize the analog signal level,
- Transmit to a PC serial port terminal with the USART, and visualize the analog signal level.
Interfacing the rain sensor
The first step was actually to identify the rain sensor, because I don’t know its origins except for the fact that it has some letters and Chinese characters etched on it.
What I did was photograph the letters, run them through an online OCR program to retrieve these characters: 雨滴模块, that Google translate with “raindrop module”. By searching for them and the YL-83 code, I found the official page of the 100Y YL-83 rain sensor with its data brief. The documentation contains the following information, that I place here translated for convenience:
Features:
Voltage: 5V
Power indicator light, the output signal LED indicating lamp.
TTL level output, TTL output signal for low level drive capacity of around 100MA, can directly drive the relay, a buzzer, a small fan, etc..
Sensitivity adjustment via potentiometer
No rain when the LED light output is high, the output level, go up, LED bright.
The board and the control board is separate, convenient wire.
A large area of the board, more conducive to detect the rain.
The board is equipped with a positioning hole to facilitate installation
Control panel board size: 3*1.6 MM
A large area of raindrop detection board 5.4*4.0 MM
By studying the schematics I understand the following things:
- The sensor is a resistive dipole that shows less resistance when wet and more resistance when dry.
- There is a resistive divider that outputs a lower voltage when the sensor is wet and a higher voltage when the sensor is dry. This voltage (AC) represents the humidity level.
- The humidity level is compared with a reference voltage (IN) with a saturating op-amp that outputs a “digital” signal (OUT) that is high (VCC) when the sensor is dry and low (GND) when the sensor is wet.
- The reference voltage (IN) can be trimmed with a potentiometer.
- The 4-pin connector is used to power the module with VCC (5V) and GND signals, and to retrieve “analog” humidity level (AC) and “digital” rain indicator (OUT).
- There is a blue LED (D1) that indicates that the module is powered, and a red LED (D2) that is lit when it rains (when OUT is low).
I connect the module to the Arduino Uno in the following way:
- VCC to 5V,
- GND to GND (duh…),
- OUT to pin 7, which is PD7 of the ATMega chip,
- AC to pin A0, which is PC0(ADC0) of the ATMega chip.
I also placed a LED on pin 6 which is ATMega PD6(OC0A) to be able to visualize AC level with PWM.
Reading the analog humidity signal and digital rain signal
The ATMega has an ADC that can be configured to read one of the chip pins, in our case ADC0, and convert it into a 10-bit value. The value is presented in two 8-bit registers containing the low part (ADCL) and the high part (ADCH) of the 10-bit value. In my case I decided that 8 bits should be more than enough, so I configure the ADC so that 8 bits are in the high register ADCH and 2 bits are in the low register ADCL, and I read only ADCH. Since timing is not a problem, I decide to simplify the management of the conversion and do a “blocking” read where I start the conversion and wait the result with a loop. The reference voltage for the ADC is AVCC, that in the Arduino Uno board is connected to 5V. In this way I can read the AC signal from the rain module from the ADCH register, as a value from 0 to 255 (0xFF).
The OUT rain signal instead can be read from PIND7 bit of the PIND register directly as a 1 or 0.
This is a snippet of the code that reads the sensor data:
#define BVV(bit, val) ((val)?_BV(bit):0) static void rain_init(void) { DDRD &= ~_BV(DDD7); /* OUT pin connected to PORTD7 */ DDRC &= ~_BV(DDC0); /* AC pin connected to PORTC0(ADC0) */ ADMUX = BVV(MUX0, 0) | BVV(MUX1, 0) | BVV(MUX2, 0) | BVV(MUX3, 0) /* Read ADC0 */ | BVV(ADLAR, 1) /* Left justify */ | BVV(REFS0, 1) | BVV(REFS1, 0); /* AVCC as reference voltage */ ADCSRA = BVV(ADPS0, 1) | BVV(ADPS1, 1) | BVV(ADPS2, 1) /* clk/128 */ | BVV(ADIE, 0) | BVV(ADIF, 0) /* No interrupt */ | BVV(ADATE, 0) /* No auto-update */ | BVV(ADSC, 0) /* Don't start conversion */ | BVV(ADEN, 1); /* Enable */ } static bool rain_is_raining(void) { return bit_is_clear(PIND, PIND7); /* OUT = 0 means rain */ } static uint8_t rain_get_humidity(void) { ADCSRA |= _BV(ADSC); /* Start conversion */ loop_until_bit_is_clear(ADCSRA, ADSC); /* Wait for end of conversion */ return 255 - ADCH; /* lower means more humidity */ }
Note: I created a macro called BVV
to simplify writing whole registers with a chain of bitwise OR operators “|
“.
Dim a LED with PWM
I attached a green LED to pin 6 (PD6/OC0A) with a resistance in series; I chose that pin because it offers functionalities related to ATMega Timer/Counter0, in particular it can do Pulse Width Modulation (PWM). In brief, it can generate on pin OC0A a square wave with a chosen duty cycle, and the more the duty cycle is close to 100% the more the LED is lit. The duty cycle is configured using the OCR0A (Output Compare Register of timer 0, unit A) 8-bit register, from 0 (0% duty cycle) to 255 (100% duty cycle). In my application I can transfer directly the value read from ADC0 to OCR0A. The frequency of the square wave is not very important, but I chose a lower frequency (by configuring a high prescaler) so that the voltage has time to rise and fall, in case there are relevant capacitances in the circuit.
Here is a snippet of the code that manages the LED light with PWM:
static void pwm_set_duty_cycle(uint8_t oc) { OCR0A = oc; } static void pwm_init(uint8_t oc) { DDRD |= _BV(DDD6); /* set pin 6 of PORTD for output*/ TCCR0A = BVV(WGM00, 1) | BVV(WGM01, 1) /* Fast PWM update on OCRA */ | BVV(COM0A1, 1) | BVV(COM0A0, 0) /* non-inverting OC0A */ | BVV(COM0B1, 0) | BVV(COM0B0, 0); /* OC0B not connected */ pwm_set_duty_cycle(oc); TCCR0B = BVV(CS00, 1) | BVV(CS01, 0) | BVV(CS02, 1) /* F_CPU/1024 */ | BVV(WGM02, 0) /* Fast PWM update on OCRA */ | BVV(FOC0A, 0) | BVV(FOC0B, 0); /* ignored */ }
Configuring USART
The AVR Libc toolchain has an utility header to configure the USART baud rate. I choose the quite standard 57600 baud rate, and since the Arduino Uno has a 16MHz clock it’s not a problem to reach that frequency. The default configuration is otherwise OK, I just need to enable the TX side of the USART:
#define BAUD 57600 #include <util/setbaud.h> static void usart_init(void) { UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; #if USE_2X UCSR0A |= _BV(U2X0); #else UCSR0A &= ~_BV(U2X0); #endif UCSR0B = BVV(TXEN0, 1) | BVV(RXEN0, 0); /* Only TX */ } static void usart_tx(char c) { while(!(UCSR0A & _BV(UDRE0))); UDR0 = c; } static void usart_puts(const char *s) { while(*s != '\0') { usart_tx(*s++); } }
Writing the application
Now I have all the functions to interface with the hardware, I need to glue them together.
I want to visualize the rain sensor data both on the LED and on the serial port.
On the LED I simply want it to be brighter when the sensor is more wet, so it’s simply a matter to transfer the humidity level to the duty cycle of the PWM.
On the serial port I decided to visualize the level with a “gauge” that shows the rain indicator (0: no rain, 1: rain), a bar that is more full with raising humidity, and an hexadecimal (because it’s easier to program) value of the humidity level. The string containing the gauge has a carriage return character ‘\r
‘ that goes back to the beginning of line without moving down to the next line, so that the line updates itself. The effect is shown in the screenshot below.
The main function is simply a loop that reads the values from the sensor and outputs them on the LED and serial port, with a small delay.
static void gauge(char *dst, uint8_t size, uint8_t val, uint8_t max_val) { uint8_t i; uint8_t levels; uint8_t gauge_level; levels = size - 2 - 1; /* start/end markers and null char */ gauge_level = (val * (uint16_t)levels) / max_val; *dst++ = '['; for(i = 0; i < levels; i++) { char c; c = (i < gauge_level)?'=':' '; *dst++ = c; } *dst++ = ']'; *dst = '\0'; } static void byte2hex(char *dst, uint8_t src) { const char hexdigits[16] = "0123456789ABCDEF"; *dst++ = hexdigits[(src >> 4) & 0xF]; *dst++ = hexdigits[src & 0xF]; *dst = '\0'; } static void update_gauge(bool state, uint8_t lvl) { const uint8_t gauge_strlen = 50; char line[1+1+gauge_strlen+2+1]; char *pline = &line[0]; *pline++ = '\r'; *pline++ = state?'1':'0'; gauge(pline, gauge_strlen+1, lvl, 255); pline += gauge_strlen; byte2hex(pline, lvl); pline += 2; *pline++ = '\0'; usart_puts(line); } int main (void) { rain_init(); usart_init(); pwm_init(0); while (true) { bool raining; uint8_t humidity; raining = rain_is_raining(); humidity = rain_get_humidity(); pwm_set_duty_cycle(humidity); update_gauge(raining, humidity); _delay_ms(100); } }
Building, uploading and running
All the code that I have shown above is collected in “rain.c
” source file, that is also available on Github.
In order to compile and upload the program I run the following commands on my Debian machine:
avr-gcc -g -Os -Wall -Wextra -DF_CPU=16000000UL -mmcu=atmega328p -c -o rain.o rain.c avr-gcc -Wall -Wextra -mmcu=atmega328p rain.o -o rain avr-objcopy -O ihex -R .eeprom rain rain.hex avrdude -F -V -c arduino -p ATMEGA328P -P /dev/ttyACM0 -b 115200 -U flash:w:rain.hex
I can try the sensor by sprinkling some water drops on the grill, or by placing my fingers on it.
In order to connect with the PC serial port to the ATMega USART, on my Debian machine I use minicom with the following command:
minicom -D /dev/ttyACM0 -b 57600
Be aware that the serial is also used to upload, so you need to exit from minicom before flashing a new program.
Conclusions
In this post I have shown how to use together some functionalities of the ATMega chip in C; the occasion was to play with a rain sensor and its outputs. The program size is less than 600 Bytes, which shows an advantage of using C, especially when program size and speed become a blocking problem.
fabien
2015/01/20
Great job.
What is your advice on the yl-83 ?
It is ok for rain sensor ?
It is ok to get % humidity with it ? (with some computing on arduino side)
Balau
2015/01/21
I think it’s made to answer the question “is it raining?” with a yes or no. My impression is that it would be difficult to use it as a fine grain humidity sensor. One would have to characterize the sensor by measuring the resistance of the plate with various humidity levels, and I suspect that the resulting plot won’t be a nice line. Maybe it is, I don’t know, but it’s not quick or easy to find out if it is.
Hadiza Tafida
2015/11/03
Hi please can you help me cos am also doing a project on rain automated car wiper using Arduino
Balau
2015/11/04
I don’t have much free time, so if you have a specific question about something similar to what I did, then I might be able to answer you.
Priscus
2016/04/07
hello, where can i get a Proteus library for this sensor ?
Balau
2016/04/07
I never heard of Proteus before, I see now that it’s a software for PCBs but I have no idea where to get libraries for components. It’s possible that you have to build one yourself.
Hugo
2016/05/16
Hi Balau:
I am a little bit confuse with the project, does your rain sensor project requried the adruino connects to a computer (for the serial port), then reading results from computer serial port to output reading?
I am trying to do one with battery powered (so it is not connecting to the computer usb port and be portable)
Thanks!
Balau
2016/05/16
Yes, you understood correctly that this demonstration assumes that the Arduino is connected to a PC.
If you want to do a battery-powered sensor, it still has to communicate the reading to some other device/computer, or show it on a display.
Hugo Ng
2016/05/16
Thanks for your reply, do you know what other devices (beside computer) can be portable, will lcd display able to replace the serial port from computer and output the reading?
Balau
2016/05/16
An LCD shield (I would probably seek one with I2C interface) would be OK to show something like that. But you have to imagine how will you use the finished product. You can put it just outside a window, but it will be probably more useful if it tells you remotely if it rains, with something like Bluetooth or WiFi or by SMS. Or if it performs directly some actions like watering plants if it doesn’t rain. Anyway I can’t tell you what you want to do with it, you are the only one who can know.