From 5c49f7c2e46d6fced0763094ec05bba41298cbed Mon Sep 17 00:00:00 2001 From: Julian Blake Kongslie Date: Thu, 25 Jul 2024 13:28:55 -0700 Subject: Initial working version. --- .gitignore | 1 + CMakeLists.txt | 15 ++++++++ Makefile | 19 ++++++++++ read.rb | 56 +++++++++++++++++++++++++++ rivulet.cpp | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ watering.run | 1 + 6 files changed, 210 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100755 read.rb create mode 100644 rivulet.cpp create mode 120000 watering.run diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f624cc4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.29) + +if(NOT DEFINED PICO_SDK_PATH) + set(PICO_SDK_PATH /usr/src/pico-sdk) +endif() + +include(/usr/src/pico-sdk/external/pico_sdk_import.cmake) + +project(rivulet) + +pico_sdk_init() + +add_executable(rivulet rivulet.cpp) +target_link_libraries(rivulet hardware_gpio pico_stdio_usb pico_stdlib) +pico_add_extra_outputs(rivulet) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..22d8da7 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +connect: + picocom --baud 115200 --flow none --parity none --databits 8 --stopbits 1 --nolock --echo --raise-rts --raise-dtr /dev/ttyACM1 +.PHONY: connect + +build: + mkdir -p build + cmake -B build -G Ninja + cmake --build build +.PHONY: build + +install: + $(MAKE) build + picotool load -f -x build/rivulet.uf2 + @until [ -e /dev/ttyACM1 ]; do sleep 0.1; done; sleep 1 +.PHONY: install + +clean: + rm -rf build +.PHONY: clean diff --git a/read.rb b/read.rb new file mode 100755 index 0000000..5f8182e --- /dev/null +++ b/read.rb @@ -0,0 +1,56 @@ +#!/usr/bin/ruby -w + +require "enumerable/statistics" +require "serialport" + +SENSOR_BITS = 12 +SENSOR_MAX = (1 << SENSOR_BITS) - 1 + +$stdout.sync = true +def log(msg) + $stdout.write("#{Time.now.strftime("%H:%M:%S")} #{msg}\n") + $stdout.flush +end + +sensors = [] + +SerialPort.open("/dev/ttyACM1", 115200, 8, 1, SerialPort::NONE) do | port | + + log("Allowing sensors to settle...") + + sleep(3) + begin + port.read_nonblock(16 << 20) + rescue IO::WaitReadable + end + port.readline + + log("Reading soil moisture sensors...") + + 10.times do + raw = port.readline + raise "Cannot parse sensor results #{raw.inspect}" unless raw =~ /\[([^\]]+)\]/ + + sensors << $1.split(/\s+/).filter { | x | not x.empty? }.map { | x | x.to_i } + + log("Raw sensor results: \t#{sensors[-1].join("\t")}") + end + +end + +avgsensors = [] +stdsensors = [] + +sensors.transpose.each do | sensor | + avgsensors << sensor.mean.round + stdsensors << sensor.stdev.round +end + +log("Avg sensor results: \t#{avgsensors.join("\t")} \t(per sensor)") +log("SDv sensor results: \t#{stdsensors.join("\t")} \t(per sensor)") + +avg = sensors.flatten.mean.round +std = sensors.flatten.stdev.round + +log("Avg sensor results: \t#{avg} \t(#{(100.0 * avg.to_f / SENSOR_MAX.to_f).round}%) \t(overall)") +log("SDv sensor results: \t#{std} \t(overall)") diff --git a/rivulet.cpp b/rivulet.cpp new file mode 100644 index 0000000..5b18bb0 --- /dev/null +++ b/rivulet.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include + +constexpr const unsigned int PERIOD_MS = 3000; + +enum { + PIN_CLOCK, + + PIN_DATA, + + PIN_CS0, + PIN_CS1, + PIN_CS2, + PIN_CS3, + PIN_CS4, + PIN_CS5, + PIN_CS6, + PIN_CS7, + + PIN_LED = 25, +}; + +constexpr unsigned int MASK(unsigned int pin) { return 1 << pin; } + +enum { + MASK_CLOCK = MASK(PIN_CLOCK), + + MASK_DATA = MASK(PIN_DATA), + + MASK_CS0 = MASK(PIN_CS0), + MASK_CS1 = MASK(PIN_CS1), + MASK_CS2 = MASK(PIN_CS2), + MASK_CS3 = MASK(PIN_CS3), + MASK_CS4 = MASK(PIN_CS4), + MASK_CS5 = MASK(PIN_CS5), + MASK_CS6 = MASK(PIN_CS6), + MASK_CS7 = MASK(PIN_CS7), + + MASK_LED = MASK(PIN_LED), + + MASK_CSALL = MASK_CS0 | MASK_CS1 | MASK_CS2 | MASK_CS3 | MASK_CS4 | MASK_CS5 | MASK_CS6 | MASK_CS7, + MASK_IN = MASK_DATA, + MASK_OUT = MASK_CLOCK | MASK_CSALL | MASK_LED, + MASK_ALL = MASK_IN | MASK_OUT, +}; + +unsigned int read_bit() { + gpio_set_mask(MASK_CLOCK); + sleep_us(1); + gpio_clr_mask(MASK_CLOCK); + auto bit = gpio_get(PIN_DATA); + sleep_us(1); + return bit; +} + +unsigned int read_sensor(unsigned int i) { + unsigned int cs; + switch (i) { + case 0: cs = PIN_CS0; break; + case 1: cs = PIN_CS1; break; + case 2: cs = PIN_CS2; break; + case 3: cs = PIN_CS3; break; + case 4: cs = PIN_CS4; break; + case 5: cs = PIN_CS5; break; + case 6: cs = PIN_CS6; break; + case 7: cs = PIN_CS7; break; + default: return 0; + } + + gpio_clr_mask(MASK(cs)); + sleep_us(1); + + // 3 leading zero bits + for (unsigned int i = 0; i < 3; ++i) + read_bit(); + + // 12 data bits; big endian + unsigned int result = 0; + for (unsigned int i = 0; i < 12; ++i) + result = (result << 1) | read_bit(); + + gpio_set_mask(MASK(cs)); + sleep_us(1); + + return result; +} + +int main() { + stdio_usb_init(); + gpio_init_mask(MASK_ALL); + gpio_set_dir_in_masked(MASK_IN); + gpio_set_dir_out_masked(MASK_OUT); + gpio_set_mask(MASK_CSALL); + gpio_clr_mask(MASK_CLOCK | MASK_LED); + + while (true) { + if (!stdio_usb_connected()) { + sleep_ms(1000); + continue; + } + + gpio_set_mask(MASK_LED); + + while (stdio_usb_connected()) { + printf("["); + for (unsigned int i = 0; i < 8; ++i) + printf(" %u", read_sensor(i)); + printf(" ]\n"); + sleep_ms(PERIOD_MS); + } + + gpio_clr_mask(MASK_LED); + } + + return 0; +} diff --git a/watering.run b/watering.run new file mode 120000 index 0000000..ef7c810 --- /dev/null +++ b/watering.run @@ -0,0 +1 @@ +/var/lib/laminar/cfg/jobs/watering.run \ No newline at end of file -- cgit v1.2.3