2
2
3
3
#include " async_sensor.hpp"
4
4
#include " config/pins.hpp"
5
+ #include " hardware/gpio.h"
5
6
#include " sdk/pwm.hpp"
7
+ #include < algorithm>
8
+ #include < bitset>
6
9
#include < chrono>
7
10
#include < cstdint>
8
11
9
12
namespace nevermore ::sensors {
10
13
11
14
using namespace std ::literals::chrono_literals;
12
15
16
+ // 'Low' speed tachometer, intended for < 1000 pulses/sec.
17
+ // DELTA BFB0712H spec sheet says an RPM of 2900 -> ~49 rev/s
18
+ // PWM counter wraps at 2^16-1 -> if a fan is spinning that fast you've a problem.
13
19
struct Tachometer final : SensorPeriodic {
14
- // DELTA BFB0712H spec sheet says an RPM of 2900 -> ~49 rev/s
15
- // Honestly at this low a rate it might be worth just using
16
- // an interrupt instead of screwing with a PWM slice...
17
- constexpr static auto TACHOMETER_READ_PERIOD = SENSOR_UPDATE_PERIOD;
18
- static_assert (100ms <= TACHOMETER_READ_PERIOD && " need at least 100ms to get a good sampling " );
19
-
20
- Tachometer (GPIO pin = GPIO::none(), uint32_t pulses_per_revolution = 1 ) {
21
- setup (pin, pulses_per_revolution);
22
- }
20
+ // need at least 100ms for a reasonable read and no point sampling longer than 1s
21
+ constexpr static auto TACHOMETER_READ_PERIOD =
22
+ std::clamp<std::chrono::milliseconds>(SENSOR_UPDATE_PERIOD, 100ms, 1s);
23
+
24
+ // hz_max_pulse = hz_sample / 2
25
+ // sample at 10 kHz, that'll support up to 150'000 RPM w/ 2 pulses per rev
26
+ constexpr static auto PIN_SAMPLING_PERIOD = 0 .1ms;
27
+
28
+ Tachometer () = default ;
23
29
24
- void setup (GPIO pin, uint32_t pulses_per_revolution = 1 ) {
25
- assert ((!pin || pwm_gpio_to_channel (pin) == PWM_CHAN_B) && " tachometers must run on a B channel" );
30
+ void setup (Pins::GPIOs const & pins, uint32_t pulses_per_revolution = 1 ) {
26
31
assert (0 < pulses_per_revolution);
27
- this ->pin_ = pin;
28
- this ->pulses_per_revolution = pulses_per_revolution;
29
- }
30
32
31
- [[nodiscard]] GPIO pin () const {
32
- return pin_;
33
+ for (auto && pin : pins) {
34
+ if (!pin) continue ;
35
+
36
+ switch (gpio_get_function (pin)) {
37
+ default : assert (false ); break ;
38
+ case GPIO_FUNC_SIO: break ;
39
+ case GPIO_FUNC_PWM: {
40
+ auto cfg = pwm_get_default_config ();
41
+ pwm_config_set_clkdiv_mode (&cfg, PWM_DIV_B_FALLING);
42
+ pwm_init (pwm_gpio_to_slice_num_ (pin), &cfg, false );
43
+ } break ;
44
+ }
45
+ }
46
+
47
+ std::copy (std::begin (pins), std::end (pins), this ->pins );
48
+ this ->pulses_per_revolution = pulses_per_revolution;
33
49
}
34
50
35
51
[[nodiscard]] double revolutions_per_second () const {
@@ -42,29 +58,93 @@ struct Tachometer final : SensorPeriodic {
42
58
43
59
protected:
44
60
void read () override {
45
- if (!pin_) return ;
46
- auto slice_num = pwm_gpio_to_slice_num_ (pin_) ;
47
-
48
- pwm_set_counter (slice_num, 0 );
61
+ // Since we're targetting relatively low pulse hz don't bother about
62
+ // keeping code in RAM to avoid flash penalty or function overhead ;
63
+ // we're running at 125 MHz & reading <= 1 kHz pulse signals,
64
+ // we've plenty of room for sloppiness.
49
65
auto begin = std::chrono::steady_clock::now ();
50
- pwm_set_enabled (slice_num, true );
51
-
52
- task_delay (TACHOMETER_READ_PERIOD);
53
-
54
- pwm_set_enabled (slice_num, false );
66
+ uint32_t pulses = pulse_count (begin);
55
67
auto end = std::chrono::steady_clock::now ();
56
68
57
69
auto duration_sec =
58
70
std::chrono::duration_cast<std::chrono::duration<double , std::ratio<1 >>>(end - begin);
59
- auto count = pwm_get_counter (slice_num);
60
- revolutions_per_second_ = count / duration_sec.count () / pulses_per_revolution;
71
+ revolutions_per_second_ = pulses / duration_sec.count () / pulses_per_revolution;
61
72
62
- // printf("tachometer_measure dur=%f s cnt=%d rev-per-sec=%f rpm=%f\n",
63
- // duration_sec.count(), int(count ), revolutions_per_second_, revolutions_per_second_ * 60);
73
+ // printf("tachometer_measure dur=%f s cnt=%u rev-per-sec=%f rpm=%f\n", duration_sec.count() ,
74
+ // unsigned(pulses ), revolutions_per_second_, revolutions_per_second_ * 60);
64
75
}
65
76
66
77
private:
67
- GPIO pin_;
78
+ // we're nowhere near high precision stuff
79
+ uint32_t pulse_count (std::chrono::steady_clock::time_point const begin) {
80
+ uint32_t pulses = 0 ;
81
+ if (pulse_start ()) { // polling required, we've non-PWM tacho pins
82
+ for (auto now = begin; (now - begin) < TACHOMETER_READ_PERIOD;
83
+ now = std::chrono::steady_clock::now ()) {
84
+ pulses += pulse_poll ();
85
+ task_delay (PIN_SAMPLING_PERIOD);
86
+ }
87
+ } else // everything is handled by PWM slices, just nap for a bit
88
+ task_delay (TACHOMETER_READ_PERIOD);
89
+
90
+ pulses += pulse_end (); // add pulses from PWM counters
91
+ return pulses;
92
+ }
93
+
94
+ bool pulse_start () {
95
+ bool do_polling = false ;
96
+
97
+ for (auto && pin : pins) {
98
+ if (!pin) continue ;
99
+
100
+ switch (gpio_get_function (pin)) {
101
+ default : break ;
102
+ case GPIO_FUNC_SIO: {
103
+ do_polling = true ;
104
+ state.set (&pin - pins, gpio_get (pin));
105
+ } break ;
106
+ case GPIO_FUNC_PWM: {
107
+ auto slice = pwm_gpio_to_slice_num_ (pin);
108
+ pwm_set_counter (slice, 0 );
109
+ pwm_set_enabled (slice, true );
110
+ } break ;
111
+ }
112
+ }
113
+
114
+ return do_polling;
115
+ }
116
+
117
+ uint32_t pulse_poll () {
118
+ uint32_t pulses = 0 ;
119
+
120
+ for (auto && pin : pins) {
121
+ if (pin && gpio_get_function (pin) == GPIO_FUNC_SIO) {
122
+ auto curr = gpio_get (pin);
123
+ auto prev = state.test (&pin - pins);
124
+ pulses += (prev != curr) && curr; // count rising edges
125
+ state.set (&pin - pins, curr);
126
+ }
127
+ }
128
+
129
+ return pulses;
130
+ }
131
+
132
+ uint32_t pulse_end () {
133
+ uint32_t pulses = 0 ;
134
+
135
+ for (auto && pin : pins) {
136
+ if (pin && gpio_get_function (pin) == GPIO_FUNC_PWM) {
137
+ auto slice = pwm_gpio_to_slice_num_ (pin);
138
+ pwm_set_enabled (slice, false );
139
+ pulses += pwm_get_counter (slice);
140
+ }
141
+ }
142
+
143
+ return pulses;
144
+ }
145
+
146
+ Pins::GPIOs pins;
147
+ std::bitset<Pins::ALTERNATIVES_MAX> state;
68
148
uint pulses_per_revolution = 1 ;
69
149
double revolutions_per_second_ = 0 ;
70
150
};
0 commit comments