summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Blake Kongslie2024-07-25 13:28:55 -0700
committerJulian Blake Kongslie2024-07-25 13:28:55 -0700
commit5c49f7c2e46d6fced0763094ec05bba41298cbed (patch)
tree951d9fd4744eec31557274e3af659aab01185436
downloadrivulet-main.tar.xz
Initial working version.HEADmain
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt15
-rw-r--r--Makefile19
-rwxr-xr-xread.rb56
-rw-r--r--rivulet.cpp118
l---------watering.run1
6 files changed, 210 insertions, 0 deletions
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 @@
1cmake_minimum_required(VERSION 3.29)
2
3if(NOT DEFINED PICO_SDK_PATH)
4 set(PICO_SDK_PATH /usr/src/pico-sdk)
5endif()
6
7include(/usr/src/pico-sdk/external/pico_sdk_import.cmake)
8
9project(rivulet)
10
11pico_sdk_init()
12
13add_executable(rivulet rivulet.cpp)
14target_link_libraries(rivulet hardware_gpio pico_stdio_usb pico_stdlib)
15pico_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 @@
1connect:
2 picocom --baud 115200 --flow none --parity none --databits 8 --stopbits 1 --nolock --echo --raise-rts --raise-dtr /dev/ttyACM1
3.PHONY: connect
4
5build:
6 mkdir -p build
7 cmake -B build -G Ninja
8 cmake --build build
9.PHONY: build
10
11install:
12 $(MAKE) build
13 picotool load -f -x build/rivulet.uf2
14 @until [ -e /dev/ttyACM1 ]; do sleep 0.1; done; sleep 1
15.PHONY: install
16
17clean:
18 rm -rf build
19.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 @@
1#!/usr/bin/ruby -w
2
3require "enumerable/statistics"
4require "serialport"
5
6SENSOR_BITS = 12
7SENSOR_MAX = (1 << SENSOR_BITS) - 1
8
9$stdout.sync = true
10def log(msg)
11 $stdout.write("#{Time.now.strftime("%H:%M:%S")} #{msg}\n")
12 $stdout.flush
13end
14
15sensors = []
16
17SerialPort.open("/dev/ttyACM1", 115200, 8, 1, SerialPort::NONE) do | port |
18
19 log("Allowing sensors to settle...")
20
21 sleep(3)
22 begin
23 port.read_nonblock(16 << 20)
24 rescue IO::WaitReadable
25 end
26 port.readline
27
28 log("Reading soil moisture sensors...")
29
30 10.times do
31 raw = port.readline
32 raise "Cannot parse sensor results #{raw.inspect}" unless raw =~ /\[([^\]]+)\]/
33
34 sensors << $1.split(/\s+/).filter { | x | not x.empty? }.map { | x | x.to_i }
35
36 log("Raw sensor results: \t#{sensors[-1].join("\t")}")
37 end
38
39end
40
41avgsensors = []
42stdsensors = []
43
44sensors.transpose.each do | sensor |
45 avgsensors << sensor.mean.round
46 stdsensors << sensor.stdev.round
47end
48
49log("Avg sensor results: \t#{avgsensors.join("\t")} \t(per sensor)")
50log("SDv sensor results: \t#{stdsensors.join("\t")} \t(per sensor)")
51
52avg = sensors.flatten.mean.round
53std = sensors.flatten.stdev.round
54
55log("Avg sensor results: \t#{avg} \t(#{(100.0 * avg.to_f / SENSOR_MAX.to_f).round}%) \t(overall)")
56log("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 @@
1#include <hardware/gpio.h>
2#include <pico/printf.h>
3#include <pico/stdio_usb.h>
4#include <pico/stdlib.h>
5
6constexpr const unsigned int PERIOD_MS = 3000;
7
8enum {
9 PIN_CLOCK,
10
11 PIN_DATA,
12
13 PIN_CS0,
14 PIN_CS1,
15 PIN_CS2,
16 PIN_CS3,
17 PIN_CS4,
18 PIN_CS5,
19 PIN_CS6,
20 PIN_CS7,
21
22 PIN_LED = 25,
23};
24
25constexpr unsigned int MASK(unsigned int pin) { return 1 << pin; }
26
27enum {
28 MASK_CLOCK = MASK(PIN_CLOCK),
29
30 MASK_DATA = MASK(PIN_DATA),
31
32 MASK_CS0 = MASK(PIN_CS0),
33 MASK_CS1 = MASK(PIN_CS1),
34 MASK_CS2 = MASK(PIN_CS2),
35 MASK_CS3 = MASK(PIN_CS3),
36 MASK_CS4 = MASK(PIN_CS4),
37 MASK_CS5 = MASK(PIN_CS5),
38 MASK_CS6 = MASK(PIN_CS6),
39 MASK_CS7 = MASK(PIN_CS7),
40
41 MASK_LED = MASK(PIN_LED),
42
43 MASK_CSALL = MASK_CS0 | MASK_CS1 | MASK_CS2 | MASK_CS3 | MASK_CS4 | MASK_CS5 | MASK_CS6 | MASK_CS7,
44 MASK_IN = MASK_DATA,
45 MASK_OUT = MASK_CLOCK | MASK_CSALL | MASK_LED,
46 MASK_ALL = MASK_IN | MASK_OUT,
47};
48
49unsigned int read_bit() {
50 gpio_set_mask(MASK_CLOCK);
51 sleep_us(1);
52 gpio_clr_mask(MASK_CLOCK);
53 auto bit = gpio_get(PIN_DATA);
54 sleep_us(1);
55 return bit;
56}
57
58unsigned int read_sensor(unsigned int i) {
59 unsigned int cs;
60 switch (i) {
61 case 0: cs = PIN_CS0; break;
62 case 1: cs = PIN_CS1; break;
63 case 2: cs = PIN_CS2; break;
64 case 3: cs = PIN_CS3; break;
65 case 4: cs = PIN_CS4; break;
66 case 5: cs = PIN_CS5; break;
67 case 6: cs = PIN_CS6; break;
68 case 7: cs = PIN_CS7; break;
69 default: return 0;
70 }
71
72 gpio_clr_mask(MASK(cs));
73 sleep_us(1);
74
75 // 3 leading zero bits
76 for (unsigned int i = 0; i < 3; ++i)
77 read_bit();
78
79 // 12 data bits; big endian
80 unsigned int result = 0;
81 for (unsigned int i = 0; i < 12; ++i)
82 result = (result << 1) | read_bit();
83
84 gpio_set_mask(MASK(cs));
85 sleep_us(1);
86
87 return result;
88}
89
90int main() {
91 stdio_usb_init();
92 gpio_init_mask(MASK_ALL);
93 gpio_set_dir_in_masked(MASK_IN);
94 gpio_set_dir_out_masked(MASK_OUT);
95 gpio_set_mask(MASK_CSALL);
96 gpio_clr_mask(MASK_CLOCK | MASK_LED);
97
98 while (true) {
99 if (!stdio_usb_connected()) {
100 sleep_ms(1000);
101 continue;
102 }
103
104 gpio_set_mask(MASK_LED);
105
106 while (stdio_usb_connected()) {
107 printf("[");
108 for (unsigned int i = 0; i < 8; ++i)
109 printf(" %u", read_sensor(i));
110 printf(" ]\n");
111 sleep_ms(PERIOD_MS);
112 }
113
114 gpio_clr_mask(MASK_LED);
115 }
116
117 return 0;
118}
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