Using the HW-390 with the Raspberry Pi Pico

And with Rust

A picture of the HW-390 Capacitive Soil Moisture Sensor

HW-390 Capacitive Soil Moisture Sensor

The Sensor

I found these on Amazon for ~$1 each. The durability is not so great, but I don’t have any code in my greenhouse library for handling moisture sensor values. Comically these sensors that are made for measuring moisture have no kind of IP rating so I’d say even spray watering a plant that has this sensor implemented is a bad idea because the circuitry is too vulnerable.

That said, it was about ~$1, so I can deal with some babysitting for that price.

Here is a data sheet (word doc weirdly) for the sensor. It’s good stuff, tells you what values to expect, which is going to save me some work setting up my test rig just to figure that out. I’ll verify after I’ve got my code and test cases set up.

The three things I was most interested in finding out were:

  1. The board does not have any waterproofing built in, being exposed to moisture will ruin it.

  2. According to various blogs, the preferred mode of operation tends to be 5VDC.

  3. It’s output ranges look something in this neighborhood when operating correctly

    1. Dry: (520 430]

    2. Wet: (430 350]

    3. Water: (350 260]

Heh. Water is in the doc as a moisture level. I’ll be curious to see how the measurement curves look over time, the resolution of dryness, and if there is a sort of even drop over time or if it’s sudden jumps in between measured values. long term I’d love to record this data, and log it. it’d be beautiful to see that value create a sort of ‘sawtooth’ cycle over weeks and months:

ADC Interface

This sensor works via ADC, so here is a post that covers setting up the Pico with this sensor and leveraging rp-hal to utilize the ADC functionality of the GPIO pin 26.

I’m going to work from here assuming that I’ll have set up the Pico so that it will use its ADC ports to interpret the signal coming from the sensor and create values similar to what is outlined in the datasheet.


Creating Moisture Level Abstractions

I want the output of the sensor to be quickly distinguishable in the rest of the program; meaning I want the context of the values of the sensor to be transferable to other contexts without needing to move ‘interpretive logic’ into other contexts of the program. To be more clear, I want to make a logical decision about the moisture level only one time per sample, and then move the result of that sample measurement decision around within the program. I’m doing this with an Enum, and giving 5 levels in that Enum.

Here is the Enum:

With PV meaning Process Variable - I’ll be able to know based on the parent element of context which thing is emitting the PV, and the PV status will just be a plain understandable level that I can make a decision on.

A Catch

There is a chance for a logical mismatch between how I’ve programmed the variable handler against what it will be outputting. The higher the number goes from the sensor, the more DRY the soil is. This means when I give this thing a container variable, the variable will have to be called DRYNESS level.

This is because I want to be sure to add water when the variable is higher, not lower. If I added water until the variable went to a lower level, I would indefinitely add water because the value decreases as the soil moisture level increases. This logical mismatch is common in control schemes.

Interface Code

I won’t cover that in detail here. This is typically always handled the same and you can check out some of my previous posts on the PI regarding Digital inputs and Digital Outputs for how this stuff works.

This file from the rp-hal repo shows how to handle ADC inputs if you’re interested to compose your own. I’ll eventually have a repo specifically for this sensor so if you’re reading this and I’ve not already linked that, sorry, but it’s planned. Also, there is a chance I forgot to update this so using the search tool for this blog and searching HW-390 may yield a good result.


IO Val -> Abstraction Function

The input value direct from the sensor gets mapped into my abstraction function. The output of this function will eventually go into a container class that gives a bit of context to the ‘level’ value.

So the concept still remains that a cyclical read of the input will occur, that input value will be passed into the above function, that result will be assigned to a state variable called “dryness” and from there the system can make further control decisions.

Conclusion

Implementing ADC kit is nicely done when a bit of babysitting of the value meaning is done on the controller side. The heavy lifting of creating meaningful values from the source signal is done by the team behind rp-hal, so the rate of input read is the responsibility of you and me, the application developers. I think in the next posts I’ll cover my process for creating state.

Previous
Previous

Updating Application State from IO Devices

Next
Next

Understanding ADC with the Raspberry Pi Pico and Rust