Despite terrarium, greenhouse and plant growing automation projects are overhyped, this little development is meant for the exploration of Programmable System on Chip capabilities. As long as my portfolio consists of Embedded projects that inherit classical principles of emb-dev, this work is focused on showing the flexibility of PSoC microcontrollers, which will be described well enough in the documentation below.
Terrarium is designed to be scalable. On top of existing platform and codebase, many additional sensors and actuators can be added without altering the core API.
- General description
- List of components
- Hardware architecture
- Software architecture
- User guide
- Future desing consideration
- Credit
PSoC Terrarium provides a multisensing device solution to track the state of the environment, where the Terrarium system is placed.
The general system description with the interfaces is presented in Figure 1.
Figure 1. General system description
The system is equipped with four sensor for different purposes, soil moisture sensor, UART interface for user interactions as well as LED indicator and 9g servo m otor, that represents hatch. The details about the interfaces, components, hardware and software architectures are discussed in later sections.
The prototype of Terrarium does not have a case, all the components are laid out on the breadboard. Thus, case description is not present in this documentation.
Component | Name | Datasheet |
---|---|---|
MCU | PSoC 5LP | Link |
Soil temp sensor | DS18B20 | Link |
Air temp sensor | TC74 | Link |
Soil moist sensor | DFRobot | Link |
LED | Any | N/A |
9g Servo | Any | N/A |
Programmable System of Chip microcontrollers provide extensive functionality for quick prototyping of embedded devices. The ability to quickly map the internal hardware components in PSoC Creator allows to generate and utilize SDK through API prepared by the IDE. Widely used ARM Cortex M3 architecture makes this choice even more solid.
DS18B20 sensors are interfaced using OneWire bus. OneWire devices have unique IDs assigned and stored in ROM during manufacturing stage, thus solving the problem of having similar devices on one bus without a need for additional request to a manufacturer. The simplicity of hardware connections make OneWire based sensors an obvious choice when the application reqiures up to N devices to be placed. One of such applications is used in the project - measuring soil temperature in different areas of terrarium. It is worth noting, that DS18B20 offers a high and configurable precision.
TC74 sensor is a simple I2C device. It cannot provide as accurate measurement as DS18B20, however, its triviality is a big bonus when measuring temperature of the air. It is not a requirement to be aware of exact air temperature and, therefore, resolution up to one centigrade is reasonable.
DFRobot's soil temperature sensor is an analog plug-and-play solution for measuring soil temperature. Originally designed DFRobot's extension shields, it is still a decent device to use in a prototype project including PSoC Terrarium.
In the current prototype, simple red LED and 9g servo motor are used for demonstration purposes.
Some details on 9g related software are provided in respective section.
"Internal hardware" refers to the PSoC Creator hardware modules that are used to generate an SDK for the project.
Overall design is presented in Figure 3.
Figure 3. PSoC hardware modules design
Interfaces section includes all main interfaces that are used for communication in the environmant: UART, I2C, ADC, OneWire.
Memory section includes EEPROM component.
Actuators related HW includes means of interacting with the actuators: PWM, GPIO.
Timers section includes all the timers that are used for controlling the software flow. It is described in details in Overall Software Architecture chapter.
Figure 2. External hardware architecture.
Follow the link to view schematics in full resolution: https://i.imgur.com/N4sUtry.png
Overall software architecture is based on modularity. All the logic is performed inside modules, that are called based on the internal hardware timers. Figure 4 showcases module names and their periodicity.
Figure 4. Software architecture modules
You can view this modules controlled by the flags with similar names from the main function body.
Responsible timer: Timer_Save
Ready so save module obtains filtered samples from boxcar average filters.
It then creates a new samples structure and saves it to EEPROM.
Responsible timer: Timer_DeviceClock
Minute passed module adjusts device's time by one minute.
It then saves adjusted time into EEPROM.
Responsible timer: Timer_Measure
Ready to measure modules gets raw samples from the samples.
It then appends those samples to boxcar average filters.
Raw samples are used to adjust the actuators.
Responsible timer: Timer_OneWire
DS18B20_Ready_To_Convert starts in the beginning of the execution.
It issues convert command for all OneWire enabled sensors and then starts OneShot Timer that will interrupt after 800 ms to start DS18B20_Sample_Ready module.
The following module will then update OneWire samples that will be accessed in Ready to Measure module.
Responsible timer: ADC conversion ready interrupt
This module samples voltage when ADC conversion is ready and appends it to simple average filter.
Its main responsibility is to average and filter raw ADC samples to further get accurate moisture reading in Ready to Measure module.
Responsible timer: None
This module constantly polls UART for presence of input.
It then echoes back characters to UART and verifies if entered command are valid.
Basic device information (timestamps) as well as measurements are stored in EEPROM with periodicity described above.
This allows for quick and persistent access to main information available on the platform.
Address | Name | Description |
---|---|---|
0x0000 | EEPROM_WRITE_ADDR_MSB | Next write address where the measurement will be stored |
0x0001 | EEPROM_WRITE_ADDR_LSB | |
0x0002 | EEPROM_INFO_ADDR_MSB | Stores UNIX timestamp that was saved to the device |
0x0003 | EEPROM_INFO_ADDR | |
0x0004 | EEPROM_INFO_ADDR | |
0x0005 | EEPROM_INFO_ADDR_LSB | |
0x0006 | EEPROM_DATA_START_ADDR | Measurements are stored starting from this address |
Rest of the data is reserved for measurements.
Size of one measurement pack stored in EEPROM is the size of packed_samples
structure.
More information about EEPROM handling is provided in custom interfaces section.
Files: moisture_sensor
This abstraction provides interface for handling moisture sensor. It uses analog input on the microcontroller to provide moisture reading.
The analog input (ADC_DelSig component) emulates Arduino's ADC converter because the sensor was originally developed for Arduino UNO and DFRobot's shield.
There is no transfer function for the sensor, thus, the moisture is obtained by performing linear mapping from ADC range to percentage.
Configuration | Description |
---|---|
MOIST_VALUE_MV | ADC reading when the sensor is exposed to water |
DRY_VALUE_MV | ADC reading when the sensor is exposed to air |
Moisture sensor requires manual calibration, thus these values are unique to every set up.
Function | Parameters | Description |
---|---|---|
void initialize_soil_moisture_sensor | Initialize hardware related to the abstraction (ADC) | |
int get_soil_moisture | Get soil moisture in percent |
Files: i2c_driver
Simple interface for interacting with I2C bus. It's not the most generic interface but it can be simply expanded using PSoC I2C API for further development.
This interface only allows to read one byte from specified slave->register.
The communication template:
S | SLAVEADDR | W | REGADDR | ACK | RS | R | DATA | NAK | ST
Configuration | Description |
---|---|
I2C_ERROR | I2C error code returned by API when error occurs |
Function | Parameters | Description |
---|---|---|
void initialize_i2c | Initialize I2C hardware components | |
int16 read_i2c_data | uint8 slave_address, uint8 register_address | Read data from slave using comm. template described above |
Files: temperature_soil
This abstraction is built on top of OneWire interface.
It is meant for DS18B20 temperature sensor devices, however, it can smoothly operate with any OneWire enabled devices added to the bus.
During initialization, all the sensor found on the bus are stored in devices_on_bus structure that is later accessed by public interface functions. To alter number of sensor present on OneWire bus, change NUMBER_OF_SOIL_TEMP_SENSORS parameter.
Configuration | Description |
---|---|
NUMBER_OF_SOIL_TEMP_SENSORS | Number of soil temperature sensors on the bus |
After configuration has been changed, the Terrarium will be reorganized to print, measure and operate with different number of OneWire sensors. In order for the system to work correctly, samples saved to EEPROM must be cleared (refer to User Guide).
Function | Parameters | Description |
---|---|---|
void initialize_soil_temp_sensors | Find all devices present on the bus and save their addressed | |
float get_soil_temperature | uint8 index | Issue read command for the sensor with index index on the bus |
float start_conversion_soil_temp_sensor | uint8 index | Issue conversion command for the sensor with index index on the bus |
Technically, number of sensor on OneWire bus is unlimited. In the software, the limit is 255 (uint8 limitation). Consider also interference when having long physical bus.
This interface uses binary search algorithm to identify devices present on the bus. If requirements are time critical, consider delays during initialization when adding large amount of sensors to the bus.
Index of the sensor is determined during initialization and hidden behind the static interface. Refer to main.c to see how the API can be used in iterative manner to access all sensors on the bus.
Files: heater
This interface provides an adjustment functions for heater simulator. In this demo project, simple neon-red LED is used as a simulator.
Configuration | Description |
---|---|
HEATER_ON_TEMP_C | Heater is turned on when this value is reached |
HEATER_OFF_TEMP_C | Heater is turned back off once temperature is above this value |
Function | Parameters | Description |
---|---|---|
void adjust_heater | int16 temperature | Adjust heater actuator according to temperature |
Files: hatch
This interface provides an adjustment functions for hatch simulator.
In this demo project, 9g servo is used as a simulator.
Configuration | Description |
---|---|
HATCH_OPEN_TEMP_C | When this value is reached, hatch will be opened |
Hatch opens for 20% after each centigrade above HATCH_OPEN_TEMP_C.
Function | Parameters | Description |
---|---|---|
void inititialize_hatch | Initialize related hardware components (PWM) | |
void adjust_hatch | int16 temperature | Adjust hatch actuator according to temperature |
Files: average_filter
This file provides basic interface for the simplest filter: average filter.
The samples are saved to the running sum and the filtered result is an average of saved samples.
To start using the filter, AverageFilter structure must be created.
The filter clears running sum once .filter_length is reached.
Function | Parameters | Description |
---|---|---|
void add_sample_to_filter | AverageFilter* filter, const int new_sample | Update running sum with a new sample |
int get_filtered_result | AverageFilter* filter | Get filtered result (average) of the current samples collected |
Files: moving_average_filter
Moving Average Filter is a simple interface for quick calculations and saving of samples to sliding window.
This interface relies on a structure with a sliding window. Details can be viewed from the source code.
Configuration | Description |
---|---|
FILTER_LENGTH | Length of the sliding window |
To start using the filter, AverageFilter structure must be created.
Consider the size of PSoC heap when adjusting the size of the sliding window. Sliding window of a bigger size may lead to overflow.
Function | Parameters | Description |
---|---|---|
void add_sample_to_MA_filter | MovingAverageFilter* filter, const int new_sample | Update sliding window with a new sample |
int get_MA_filtered_result | MovingAverageFilter* filter | Get filtered result (average) of the current samples collected |
Files: main
EEPROM interface provides API to communicate with EEPROM on the device. It is created according to EEPROM layout described in the respective section.
When clearing memory, it is enough to reset next writing address. The layout therefore allows to extend EEPROM lifetime by saving amount of operations and boost performance by simplifying clearing operation to a single internal API call. Refer to the source code to see details.
When saving samples to the memory, samples should be packed into packed_samples structure. This way, EEPROM is utilized byte-to-byte without missing any space.
Function | Parameters | Description |
---|---|---|
void save_samples_to_eeprom | packed_samples samples | Save samples to EEPROM next writing address |
uint16 print_samples_from_eeprom | Print samples stored in EEPROM | |
void init_eeprom_layout | Perform validity of EEPROM layout | |
void erase_samples_from_eeprom | Erase samples by resetting next write address | |
uint8 set_date | uint day, uint month, uint year | Set date, store new timestamp to EEPROM |
uint8 set_time | uint hour, uint minute | Set time, store new timestamp to EEPROM |
struct tm get_time_from_eeprom | Get timestamp from EEPROM in a form of time.tm structure | |
void save_time_to_eeprom | uint32 timestamp | Save new UNIX timestamp to EEPROM |
uint32 get_time_from_eeprom_unix | Get timestamp from EEPROM in a form of UNIX timestamp |
When EEPROM is filler, the writing address is reset and the samples are abandoned. Thus, consider saving valuable information regularly with a client-side script.
Time is tracked using the hardware timer and standard C libraries (time). It adjusts the timestamp every minute and stores in UNIX form in EEPROM.
Files: main
These functions are used to print text to UART.
Configuration | Description |
---|---|
DEVICE_INFO_PROMPT | Information prompt that will be printed when ? command is issued |
If you are altering this project, DEVICE_INFO_PROMPT must contain original developer's name: Pavel Arefyev.
Function | Parameters | Description |
---|---|---|
void print_sample | packed_samples* sample | Print measurement stored in sample structure |
void print_current_time | Print current time on the device | |
void print_help | Print user help information |
Files: onewire
Private interfaces are considered lower-level API in the project. No information about configuration will be present in documentation. Therefore, refer to source files mentioned above.
As mentioned, the codebase allows flexibility in design choices.
If you would like to replace any of the existing components with the alternative, some configuration tweaking is required (I2C address, for instance) but it is simple and effective.
Follow these steps in order to set up your PSoC Terrarium:
- Clone repository and open psoc_project.cydsn or download source from Drive.
- Connect all the components required according to hardware schematics. If you would like to expand OneWire bus by adding more temperature sensors (unlimited), change NUMBER_OF_SOIL_TEMP_SENSORS configuration in temperature_soil.h to match your set up.
- Tweak any other configuration if required (review Custom interfaces section).
- Connect PSoC and program your device in the IDE.
- Open terminal for serial connection with 57600 baudrate.
- If you have changed NUMBER_OF_SOIL_TEMP_SENSORS configuration, clear device memory first by issuing "C" command to the terminal.
Now the device is fully operational. Figure 5 showcases menu help interface.
Figure 5. User terminal
After each successful or unsuccessful opeartion all the options will be displayed on the screen again.
Tsoil[index] represents temperature of the soil measured by OneWire based sensor number index. It will automatically adjust the printing according to your OneWire bus setup.
Figure 6. "A" command output example.
This section briefly describes issues that could be addressed in future development.
Samples should be additionally stored on external SD card. The alternative and much better solution is to save samples to cloud, where they could be analyzed well later.
To make time tracking more accurate, device could be synced with the real-time servers or use external RTC.
However, it poses redesign issues regarding date and time user configuration. Perhaps, if those options should be kept, usage of standard libraries and hardware timers is sufficient.
Calibration function could be implemented to speed up the process of environment set up. It may happen, that re-calibration is required after a certain time of using the device. Calibration function could be implemented as a part of user interface (CALIB <SENSOR_NAME>).
Prepared for Metropolia University of Applied Science's "Programmable System on Chip Design" course conducted by Antti Piironen, Principal Lecturer in Smart Systems Engineering.