Firmware for the b-parasite board, but in Rust!
1use embassy_nrf::{
2 Peri,
3 gpio::Output,
4 peripherals,
5 pwm::{self, SimplePwm},
6 saadc::{self, ChannelConfig, Config, Resolution, Saadc},
7};
8use embassy_time::Timer;
9use sachy_battery::BatteryDischargeProfile;
10use sachy_fmt::{info, unwrap};
11use static_cell::ConstStaticCell;
12
13use crate::{
14 Irqs,
15 constants::{DISCARGE_PROFILES, DRY_COEFFS, WET_COEFFS},
16 state::{ADC_MEASUREMENT, AdcMeasurements, START_MEASUREMENTS},
17};
18
19const VREF: f32 = 3.6;
20
21#[inline]
22fn calculate_polynomial(coeffs: &[f32; 3], val: f32) -> f32 {
23 coeffs[0] + (coeffs[1] * val) + (coeffs[2] * (val * val))
24}
25
26#[inline]
27fn calculate_soil_moisture(bat: f32, soil: i16) -> f32 {
28 let dry = calculate_polynomial(&DRY_COEFFS, bat);
29 let wet = calculate_polynomial(&WET_COEFFS, bat);
30
31 info!("WUH: dry {}, wet {}, soil {}", dry, wet, soil);
32
33 (((soil as f32) - dry) / (wet - dry)).clamp(0.0, 1.0)
34}
35
36#[inline]
37fn calculate_lux(voltage: f32) -> f32 {
38 const LUX_SUN: f32 = 10000.0;
39 const CURRENT_SUN: f32 = 3.59e-3;
40 const PHOTO_RESISTOR: f32 = 470.0;
41
42 let current = voltage / PHOTO_RESISTOR;
43
44 LUX_SUN * current / CURRENT_SUN
45}
46
47#[inline]
48fn to_volts(sample: i16, reference: f32) -> f32 {
49 ((sample.max(0) as f32) * reference) / 1024.0
50}
51
52fn init_pwm<'scope>(
53 pwm: Peri<'scope, peripherals::PWM0>,
54 ch0: Peri<'scope, peripherals::P0_05>,
55) -> SimplePwm<'scope> {
56 let pwm_ctrl = SimplePwm::new_1ch(pwm, ch0);
57 pwm_ctrl.set_prescaler(pwm::Prescaler::Div1);
58 pwm_ctrl.set_period(2_000_000);
59
60 pwm_ctrl
61}
62
63fn init_saadc<'scope>(
64 saadc: Peri<'scope, peripherals::SAADC>,
65 light_pin: Peri<'scope, peripherals::P0_02>,
66 soil_pin: Peri<'scope, peripherals::P0_03>,
67) -> Saadc<'scope, 3> {
68 let light_config = ChannelConfig::single_ended(light_pin);
69
70 let mut soil_config = ChannelConfig::single_ended(soil_pin);
71 soil_config.reference = saadc::Reference::VDD1_4;
72
73 let bat_config = ChannelConfig::single_ended(saadc::VddInput);
74
75 let mut saadc_config = Config::default();
76 saadc_config.resolution = Resolution::_10BIT;
77
78 Saadc::new(
79 saadc,
80 Irqs,
81 saadc_config,
82 [soil_config, light_config, bat_config],
83 )
84}
85
86#[embassy_executor::task]
87pub async fn task(
88 mut saadc: Peri<'static, peripherals::SAADC>,
89 mut light_pin: Peri<'static, peripherals::P0_02>,
90 mut soil_pin: Peri<'static, peripherals::P0_03>,
91 mut photo_ctrl: Output<'static>,
92 mut pwm: Peri<'static, peripherals::PWM0>,
93 mut pin5: Peri<'static, peripherals::P0_05>,
94) {
95 static ADC_BUFFER: ConstStaticCell<[i16; 3]> = ConstStaticCell::new([0; 3]);
96 let adc_buf = ADC_BUFFER.take();
97
98 let mut measure = unwrap!(START_MEASUREMENTS.receiver());
99
100 loop {
101 measure.changed().await;
102
103 let mut pwm_ctrl = init_pwm(pwm.reborrow(), pin5.reborrow());
104
105 let mut saadc = init_saadc(saadc.reborrow(), light_pin.reborrow(), soil_pin.reborrow());
106
107 photo_ctrl.set_high();
108 pwm_ctrl.enable();
109 pwm_ctrl.set_duty(0, 4);
110
111 Timer::after_millis(30).await;
112
113 let mut acc_buf = [0; 3];
114 let divisor = 4;
115
116 for _ in 0..divisor {
117 saadc.sample(adc_buf).await;
118 acc_buf
119 .iter_mut()
120 .zip(adc_buf.iter())
121 .for_each(|(slot, &value)| *slot += value);
122 Timer::after_millis(5).await;
123 }
124
125 photo_ctrl.set_low();
126 pwm_ctrl.set_duty(0, 0);
127
128 acc_buf.iter_mut().for_each(|acc| *acc /= divisor);
129
130 let [soil, light, bat] = acc_buf;
131
132 let bat_volt = to_volts(bat, VREF);
133
134 let (soil, light, bat) = (
135 calculate_soil_moisture(bat_volt, soil),
136 calculate_lux(to_volts(light, VREF)).max(0.0),
137 BatteryDischargeProfile::calc_pct_from_profile_range(
138 bat_volt,
139 DISCARGE_PROFILES.iter(),
140 ),
141 );
142
143 let measurements = AdcMeasurements::new(bat, bat_volt, soil, light);
144
145 info!("Soil {}, Light {}, Bat {}", soil, light, bat);
146
147 ADC_MEASUREMENT.signal(measurements);
148 pwm_ctrl.disable();
149 drop(pwm_ctrl);
150 drop(saadc);
151 }
152}