Using a rain sensor with Arduino Uno in C

Posted on 2014/12/23

10


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.

100Y YL-83 rain sensor

100Y YL-83 rain sensor

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.

Arduino Uno connected to YL-83 rain sensor

Arduino Uno connected to YL-83 rain sensor

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.

minicom terminal with rain gauge

minicom terminal with rain gauge

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.

Arduino Uno rain sensor application

Arduino Uno rain sensor application

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.

 

Posted in: Hardware