What it does
What you can expect
Who this is written for
Warning
PID
Features and requirements
Project features
- Secure hardware, communication and Cloud application
- Capacitive non-corroding soil moisture sensor (I2C Soil moisture sensor from Catnip electronics)
- Autonomous watering using pumps
- Data visualization
- Rules
- Rapid prototyping
Key technologies explored
- Azure Sphere
- I2C serial protocol for writing device libraries
- Oscilloscope with serial decode capability for I2C debugging
- Click boards
- Controlling high and low voltage components and appliances using relays (Relay Click)
Requirements
Software
- Visual Studio
- Azure account
- Azure IoT Central
- Azure Sphere SDK
- iotc-explorer
- Fusion 360
- (Optional) Simplify3D
Hardware
- Azure Sphere MT3620 starter kit
- Relay Click
- Proto Click
- Moisture sensors
- 5v DC water pumps
- Silicon tubing
- 5v power supply
- Plant growth lamp
- Water container
- Schottky barrier diode
Tools
- Multimeter
- Soldering equipment
- (Optional) Power supply
- (Optional) Oscilloscope
- 3D Printer
Edge or Cloud
Azure Sphere MT3620 Starter Kit
- Install Visual Studio for C++
- Azure Sphere SDK
- Create an Azure account
- Configure and claim devices
- Prep device for debug
- Try out a Blink example
- Create an Azure IoT Central Application
- Register devices in IoT Central
- Generate and validate certificate
Now clone the https://github.com/Azure/azure-sphere-samples repo. I usually start by making a new personal project repo out of https://github.com/Azure/azure-sphere-samples/tree/master/Samples/AzureIoT. Spend some time following the tutorial on how to make a Sphere application that connects with IoT Central as described here https://github.com/Azure/azure-sphere-samples/blob/master/Samples/AzureIoT/IoTCentral.md .
Sensors
After having tested quite a few soil moisture sensor I have found Chirp! I2C Soil moisture sensor from Catnip Electronics a durable choice. It is actually not the Chirp! version, rather the silent rugged sensor version. But I like the name. You will not have to worry about this sensor getting wet, dirty or corrode as it measures capacitance, not conductivity. It is open hardware and software, find the repository here. I have used this sensor in Arduino environment previously, find the original library here. Coming from a Arduino world I was used to finding libraries for communicating with digital sensors ready for use. The Linux based MT3620 is new and libraries have to be written. Included in my source code is the I2C-library I wrote to talk with the Chirp. This was a highly educational exercise. My takeaways were getting used to c-programming(having spent the passed 15 years in .Net), investigating different c-implementations and bridging missing features, converting Arduino-libraries, understanding the I2C protocol and using an oscilloscope to debug I2C.
I was looking for some clever way to determine if the water tank was empty until I realized I could just use the same capacitive sensor I use for the soil. Now I can use a rule in IoT Central to give me a reminder to fill up the tank when it is about to run dry. In edge-mode the device will not try to water plants if there is no water left.
Oscilloscope
After moving from pure software to electronics a few years back the best investment I have made to enhance my understanding was the oscilloscope. I started with the most basic soldering kit my dad gave me (thanks Dad for steering my on the right course!) and 3D printed an enclosure. Getting in early on Azure Sphere you should not be surprised if you have to write or convert libraries for digital sensors. Having a scope handy will make a huge difference. Working with the sensors for this project I would not be able to make it in time without a scope due to my lacking understanding of the API, timing issues, weirdness in the sensor firmware, noise, unclarities in pullup values on the I2C bus, timing in the Sphere application, to name some challenges. Since the first basic scope, I have upgraded to one with much more handy features, my favorite being serial decoding and triggering.
I also make heavy use of Bus Pirate to interact live with sensors, but this requires that you have figured out all settings and levels.
MT3620 Starter Kit I2C pullup resistors
I was reading conflicting information whether the starter kit had built-in pullup resistors on the I2C bus. The bus expects the signal to be pulled up to HIGH when idle. When working with different Arduino-compatible hardware you usually have to supply this yourself, usually in the form of a connection to logic level HIGH via a certain value of resistor. Calculating this value is very complex, and an experimental approach is my preference. Too high a resistor value (too much resistance) will not pull up to level HIGH, or not fast enough.
Too low resistance will not allow the clock or data signals of the sensors or the bus to pull down to LOW. For this new hardware I could not rule out that the Click modules provided the pullup, although this would be a very bad design. The reason simply being that two sensors would provide this twice and would not work. Regardless, being able to use a scope to look at the signals at the speed you are intending to use is a great advantage. We are simply looking for "square enough" signals. If you have to provide pullup yourself in a design, this amounts to setting up the scope, preferably in serial decode+trigger mode, and testing different resistor values. I spent quite a lot of time getting comfortable in configuring serial decoding on my scope, but it is all worth it. Refer to your manual, YouTube and brew a large pot of coffee.
In conclusion you do not need to provide pullup when using a fair amount of sensors with short wires on the I2C bus.
No extra pullup added:
No extra pullup added, zoomed:
10kOhm pullup added, no significant difference:
Connecting the sensors
While the two Click interfaces are great for rapid prototyping, you quickly run out of slots. I wanted to connect at least two soil moisture sensors and searched for a Click module with e.g. Grove sockets. This would have provided easy connect/disconnect and hopefully many sockets. No luck. I could have connected a Grove hub to the on-board Grove socket, but I was trying to limit the amount of floating PCBs, to reduce complexity in designing the housing.
I tested driving the small pumps at 3.3 and 5 volts from the power rail of the devkit, more on that later, and needed access to ground and power. Proto Click seemed the way to go. Had I done this over I would have still used the Proto Clicks, but found Grove connectors with the correct spacing insted of terminal blocks.
The design is really simple: One terminal block provides a number of ground connections, another is split between 5v and 3.3v. The middle block connects each other terminal to I2C SCL and SDA. This is done by making two rails underneath the Proto Click and alternating which terminal connects to which. I also added two short wires to these SCL and SDA rails as tabs for easy connection to the oscilloscope probes. I use yellow wire for SCL and green for SDA.
Only thing left was to strip and add a bit of solder to the sensor wires before they were screwed in place. I have a bit of bad experience with these kinds of narrow wire and screw terminals losing connection, that being the reason I recommend finding friction based connectors.
Writing library for I2C communication
This was a first for me and in a foreign programming language. I read James Flynn's great blog posts on the topic, connected my scope and just went function by function through the Arduino library. A few times I had to look at the firmware source code and I think I have spotted an error in the way the address is reported. Not important, I have to define the address explicitly anyway. In the end this was an easy library to port, not much logic to handle just writing and reading registers.
Explore SoilMoistureI2cSensor and i2cAccess source code for details.
Change address
One important aspect was that the sensors all come programmed with the same I2C address. To be able to address them individually new addresses need to be assigned. Find the commented out function ChangeSoilMoistureI2cAddress to perform this step. Just remember not to have more than one sensor connected at a time!
The assigned addresses are hardcoded as the following:
static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress1 = 0x20;
static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress2 = 0x21;
static const I2C_DeviceAddress WaterTankI2cDefaultAddress = 0x22;
I do report the addresses and version numbers as read-only Properties in IoT Central, this could be further improved by making them Settings that could be changed remotely. I am not sure this is something you would need, or even a good idea.
Accessing the I2C bus
To actually be able to communicate with the bus a few things have to be in place. Good articles have recently surfaced on this topic and I will just provide references and key settings. The app_manifest.json was extended in the Capabilities collection with the following key and value:
"I2cMaster": [ "$MT3620_ISU2_I2C" ]
The bus is initialized like this:
i2cFd = I2CMaster_Open(MT3620_ISU2_I2C);
I2CMaster_SetBusSpeed(i2cFd, I2C_BUS_SPEED_STANDARD);
I2CMaster_SetTimeout(i2cFd, 100);
Actuators
Relay
In the planning phase I chose to use the Relay Click-modules for this project mainly because I did not have full overview of the actuators I would eventually use. Plus James Flynn had provided libraries that would reduce risk of having too many unknowns. See the relay library. For the light this is a safe bet, for the pumps other options would be less overkill.
Pump
At the point of planning and ordering parts I hoped I could solve the watering problem without using a current hungry DC motor. I made attempts in using magnetic solenoids and the principle of siphon and even servos to make water flow, but in the end found it more involved than just using a pump. Some DC motors come with flyback diodes installed, some with capacitors. As I did not know what my pumps looked like inside, I cut one open.
Nothing. So I added a diode as close to the motor as possible.
Alas, I had not taken a separate power supply into consideration and wanted to see what would happen if I ran it from the board's 5 volt power rail. I connected it to the exposed ground and 5 volt of the proto click. When running the motor the oscilloscope in serial decode mode displayed noise.
The MCU would read the sensors for most of the time, but intermittently fail and not recover. This seemed to me to be a real problem, but exaggerated on the scope. I tried to keep the probes as far away from the wires and motor as possible, to no avail. I concluded this was the reason one should not share power supply between a motor and sensitive circuits. To my surprise changing to using my non-switching lab power supply made no visible difference!
I even swapped the Relay Click with an identical one to see if it was faulty. This has left me puzzled and I hope someone can share some light. Regardless I decided to use a 12 volt power adapter from mains and an adjustable regulator to 5 volts. I also added guards in code to try to minimize the chances of I2C access during motor operation.
Plant light
I used an AC plant light from my local hardware store. It is plugged into mains, I only opened the chord an split one of the leads in two. I used another piece of two-lead wire to guide the split lead into Relay port 2. This way the relay breaks the circuit when off and leaves the two circuits isolated in any other regard. This part of the project can really be used for controlling a lot of external systems, even though appliances are moving toward more complex operations. Please don't attempt experimenting with this alone if you are inexperienced. I got several shocks of 230 volts and it was unpleasant.
Relay 2 is controlled using two settings I call Relay2OnTimeSetting and Relay2OffTimeSetting. These are Date/Time values that simply tells the application when to switch the relay on and off. In code I compare the Time part of these settings, adding up number of minutes since 0:00 to local time. This is a simplification that might have some timezone issues on edge cases, but it seems to work. Please explore the function SwitchOnLampAtDayTime for more insight and how telemetry is sent.
JSON DateTime
I had to relearn string manipulation in c and found it quite a bit of a chore compared to it's high-level equivalents. In particular I had some struggles deserializing the digital twin from JSON-format. Not only does c not have a concept of strings, only arrays of chars, you don't have classes, only structs and pretty low-level libraries for manipulation. On top of that I found that for example the implementation of time.h did not include methods that my Stack Overflow results pointed to. It made me aware of how spoiled us .Net developers are. So to deserialize a DateTime-field I was left hanging. I am sure the time-library will be extended so I ended up making a simple hack for now. In my code you will see how I parse the field indexing the hour and minute part I am interested in and hope the number placements don't change...
Azure IoT Central
So many great guides have been written on setting up IoT Central, I will only supply some screenshots and key properties used in my project.
Telemetry
Look in the function SendTelemetryMoisture how these sensor values are read and reported. Notice how the names are enumerated so it is possible to make a loop that can support any number of sensors.
"Soil Moisture #1", Capacitance1
"Soil Moisture #2", Capacitance2
"Water Tank Capacitance", CapacitanceWaterTank
"Temperature #1", Temperature1
"Temperature #2", Temperature2
"Water Tank Temperature", TemperatureWaterTank
static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress1 = 0x20;
static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress2 = 0x21;
static const I2C_DeviceAddress WaterTankI2cDefaultAddress = 0x22;
static const I2C_DeviceAddress moistureSensorsAddresses[3] =
{
SoilMoistureI2cDefaultAddress1,
SoilMoistureI2cDefaultAddress2,
WaterTankI2cDefaultAddress
};
static char temperatureSensorNames[3][21] =
{
"Temperature1",
"Temperature2",
"TemperatureWaterTank"
};
static char capacitanceSensorNames[3][21] =
{
"Capacitance1",
"Capacitance2",
"CapacitanceWaterTank"
};
Settings
These settings are handled in the function TwinCallback.
"Relay #1", Relay1Setting, ON/OFF, Will be overridden by local logic, for debugging
"Relay #2", Relay2Setting, ON/OFF, Will be overridden by local logic, for debugging
"Plant lamp on time", Relay2OnTimeSetting, Show Time On, "Toggles time of day to turn on plant light. The date-part is ignored. Relative to device local time. Must be before Off time."
"Plant lamp off time", Relay2OffTimeSetting, Show Time On, "Toggles time of day to turn off plant light. The date-part is ignored. Relative to device local time. Must be after On time."
"Water pulse seconds", Relay1PulseSecondsSetting, Seconds, 0, 0, 10, "Dictates for how long water pumps should run at a time."
"Pause between watering", Relay1PulseGraceSecondsSetting, Seconds, 0, 0, 36000 (10 hours), "Minimum number of seconds after watering the next attempt may be made."
Properties
These read-only properties are reported during HubConnectionStatusCallback.
"Soil Sensor #1 Version", SoilSensorVersionProperty1, Text
"Soil Sensor #1 Address", SoilSensorAddressProperty1, Text
"Soil Sensor #2 Version", SoilSensorVersionProperty2, Text
"Soil Sensor #2 Address", SoilSensorAddressProperty2, Text
"Water Tank Sensor Version", SoilSensorVersionProperty3, Text
"Water Tank Sensor Address", SoilSensorAddressProperty3, Text
Commands
I did not go deep into commands in this project as I have done so in the past.
The names of the commands are hardcoded as such:
static const char Relay1PulseCommandName[] = "Relay1PulseCommand";
static const char Relay2PulseCommandName[] = "Relay2PulseCommand";
Look in the function DirectMethodCall for deserialization and how the code responds.
Rules
If you wish to control the devices via rules, there are many options. One of them is Flow.
iotc-explorer
Follow this guide to install and monitor messages. I use the authentication method of "iotc-explorer login "SharedAccessSignature sr=5c31d6e..." providing the whole generated string from Access Tokens in IoT Central. More info and source code.
Enclosure
What really moves an electronics project from an experiment to a prototype is a custom enclosure. I spent a good portion of this project measuring, modelling and 3D printing a house that would protect the electronics and make it stand out. I use calipers a lot!
I started out taking measurements of the starter kit and Click boards. I then proceeded modelling them in Fusion 360. It does take some extra time if you can not find existing models of your hardware, but it is almost always time saved in the end.
From experience stacking electronics add errors in placement. Therefore I did not model final holes for fastening the board to the base. Instead I drilled holes after printing and added nylon hex nuts and screws.
Let me also point out I started experimenting with low-poly aesthetics be fore a certain truck was unveiled
I also made some custom fixtures for holding the water tubes in place. They were fixed with epoxy.
Tutorials on low-poly modeling in Fusion 360:
Water tank
I was not prepared to test this system with a continous pressurized water supply. I therefore bought a simple plastic container I could use during development. I fixed the pumps to the bottom using epoxy. The surfaces were smooth, so I roughened them up using sand paper and a knife.
Notes
- Only supporting positive temperatures made life easier with limited project time. For plants this is fine, but if you attempt to repurpose the project it's worth mentioning.
- Liquid pumps usually need to be primed, meaning you might have to suck some water into it to get it running.
Source code and models
https://github.com/eivholt/UrbanFarming
Top Comments