Skip to content

Commit 5d2f994

Browse files
committed
add pulse counter driver
1 parent 77ccde7 commit 5d2f994

File tree

5 files changed

+423
-1
lines changed

5 files changed

+423
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ sdkconfig.old
66
sdkconfig
77
out
88
__*
9+
.cache
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
#pragma once
2+
3+
#include <jac/machine/class.h>
4+
#include <jac/machine/functionFactory.h>
5+
#include <jac/machine/machine.h>
6+
#include <jac/machine/values.h>
7+
8+
#include "driver/pulse_cnt.h"
9+
10+
11+
struct EdgeMode {
12+
pcnt_channel_edge_action_t pos;
13+
pcnt_channel_edge_action_t neg;
14+
};
15+
16+
struct LevelMode {
17+
pcnt_channel_level_action_t high;
18+
pcnt_channel_level_action_t low;
19+
};
20+
21+
22+
template<>
23+
struct jac::ConvTraits<EdgeMode> {
24+
static Value to(ContextRef ctx, EdgeMode val) {
25+
jac::Object obj = jac::Object::create(ctx);
26+
obj.set<int>("pos", val.pos);
27+
obj.set<int>("low", val.neg);
28+
29+
return obj;
30+
}
31+
32+
static EdgeMode from(ContextRef ctx, ValueWeak val) {
33+
try {
34+
auto obj = val.to<jac::Object>();
35+
int pos = obj.get("pos").to<int>();
36+
int neg = obj.get("neg").to<int>();
37+
if (neg < 0 || neg > 3 || pos < 0 || pos > 3) {
38+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Invalid EdgeMode");
39+
}
40+
return {
41+
.pos = static_cast<pcnt_channel_edge_action_t>(pos),
42+
.neg = static_cast<pcnt_channel_edge_action_t>(neg)
43+
};
44+
}
45+
catch (jac::Exception& e) {
46+
throw jac::Exception::create(jac::Exception::Type::TypeError, "EdgeMode must be an object");
47+
}
48+
}
49+
};
50+
51+
template<>
52+
struct jac::ConvTraits<LevelMode> {
53+
static Value to(ContextRef ctx, LevelMode val) {
54+
jac::Object obj = jac::Object::create(ctx);
55+
obj.set<int>("high", val.high);
56+
obj.set<int>("low", val.low);
57+
58+
return obj;
59+
}
60+
61+
static LevelMode from(ContextRef ctx, ValueWeak val) {
62+
try {
63+
auto obj = val.to<jac::Object>();
64+
int high = obj.get("high").to<int>();
65+
int low = obj.get("low").to<int>();
66+
if (high < 0 || high > 3 || low < 0 || low > 3) {
67+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Invalid LevelMode");
68+
}
69+
return {
70+
.high = static_cast<pcnt_channel_level_action_t>(high),
71+
.low = static_cast<pcnt_channel_level_action_t>(low)
72+
};
73+
}
74+
catch (jac::Exception& e) {
75+
throw jac::Exception::create(jac::Exception::Type::TypeError, "LevelMode must be an object");
76+
}
77+
}
78+
};
79+
80+
81+
template<typename Feature>
82+
class PulseCounter {
83+
public:
84+
Feature* _feature;
85+
private:
86+
pcnt_unit_handle_t _unit = nullptr;
87+
pcnt_channel_handle_t _channel = nullptr;
88+
public:
89+
PulseCounter(Feature* feature, int pinLevel, int pinEdge, EdgeMode edgeMode, LevelMode levelMode):
90+
_feature(feature)
91+
{
92+
pcnt_unit_config_t unitConfig = {
93+
.low_limit = std::numeric_limits<int16_t>::min(),
94+
.high_limit = std::numeric_limits<int16_t>::max(),
95+
.flags = { 0 }
96+
};
97+
unitConfig.flags.accum_count = 0;
98+
99+
auto res = pcnt_new_unit(&unitConfig, &_unit);
100+
if (res != ESP_OK) {
101+
throw jac::Exception::create(jac::Exception::Type::InternalError, std::string("Failed to configure pulse counter unit (") + esp_err_to_name(res) + ")");
102+
}
103+
104+
pcnt_chan_config_t channelConfig = {
105+
.edge_gpio_num = Feature::getDigitalPin(pinEdge), // TODO: rename
106+
.level_gpio_num = Feature::getDigitalPin(pinLevel),
107+
.flags = { }
108+
};
109+
channelConfig.flags.invert_edge_input = 0;
110+
channelConfig.flags.invert_level_input = 0;
111+
channelConfig.flags.virt_edge_io_level = 0;
112+
channelConfig.flags.virt_level_io_level = 0;
113+
channelConfig.flags.io_loop_back = 0;
114+
115+
res = pcnt_new_channel(_unit, &channelConfig, &_channel);
116+
if (res != ESP_OK) {
117+
throw jac::Exception::create(jac::Exception::Type::InternalError, std::string("Failed to configure pulse counter channel (") + esp_err_to_name(res) + ")");
118+
}
119+
120+
res = pcnt_channel_set_edge_action(_channel, edgeMode.pos, edgeMode.neg);
121+
if (res != ESP_OK) {
122+
throw jac::Exception::create(jac::Exception::Type::InternalError, std::string("Failed to configure pulse counter channel edge action (") + esp_err_to_name(res) + ")");
123+
}
124+
125+
res = pcnt_channel_set_level_action(_channel, levelMode.high, levelMode.low);
126+
if (res != ESP_OK) {
127+
throw jac::Exception::create(jac::Exception::Type::InternalError, std::string("Failed to configure pulse counter channel level action (") + esp_err_to_name(res) + ")");
128+
}
129+
130+
res = pcnt_unit_enable(_unit);
131+
if (res != ESP_OK) {
132+
throw jac::Exception::create(jac::Exception::Type::InternalError, std::string("Failed to enable pulse counter unit (") + esp_err_to_name(res) + ")");
133+
}
134+
135+
start();
136+
}
137+
138+
int read() {
139+
if (_unit == nullptr || _channel == nullptr) {
140+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter is closed");
141+
}
142+
int count;
143+
auto res = pcnt_unit_get_count(_unit, &count);
144+
if (res != ESP_OK) {
145+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Failed to read pulse counter unit");
146+
}
147+
return count;
148+
}
149+
150+
void clear() {
151+
if (_unit == nullptr || _channel == nullptr) {
152+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter is closed");
153+
}
154+
auto res = pcnt_unit_clear_count(_unit);
155+
if (res != ESP_OK) {
156+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Failed to clear pulse counter unit");
157+
}
158+
}
159+
160+
void start() {
161+
if (_unit == nullptr || _channel == nullptr) {
162+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter is closed");
163+
}
164+
auto res = pcnt_unit_start(_unit);
165+
if (res != ESP_OK) {
166+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Failed to start pulse counter unit");
167+
}
168+
}
169+
170+
void stop() {
171+
if (_unit == nullptr || _channel == nullptr) {
172+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter is closed");
173+
}
174+
auto res = pcnt_unit_stop(_unit);
175+
if (res != ESP_OK) {
176+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Failed to stop pulse counter unit");
177+
}
178+
}
179+
180+
void close() {
181+
if (_channel != nullptr) {
182+
pcnt_del_channel(_channel);
183+
_channel = nullptr;
184+
}
185+
if (_unit != nullptr) {
186+
pcnt_del_unit(_unit);
187+
_unit = nullptr;
188+
}
189+
}
190+
191+
~PulseCounter() {
192+
close();
193+
}
194+
};
195+
196+
197+
template<class Feature>
198+
class PulseCounterProtoBuilder : public jac::ProtoBuilder::Opaque<PulseCounter<Feature>>, jac::ProtoBuilder::Properties, jac::ProtoBuilder::LifetimeHandles {
199+
using PulseCounter_ = PulseCounter<Feature>;
200+
public:
201+
static PulseCounter_* constructOpaque(jac::ContextRef ctx, std::vector<jac::ValueWeak> args) {
202+
if (args.size() < 1) {
203+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Invalid number of arguments");
204+
}
205+
206+
jac::ObjectWeak options = args[0].to<jac::ObjectWeak>();
207+
208+
if (!options.hasProperty("pinLevel")) {
209+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Missing required property 'pinLevel'");
210+
}
211+
int pinLevel = options.get<int>("pinLevel");
212+
213+
if (!options.hasProperty("pinEdge")) {
214+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Missing required property 'pinEdge'");
215+
}
216+
int pinEdge = options.get<int>("pinEdge");
217+
218+
if (!options.hasProperty("levelMode")) {
219+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Missing required property 'levelMode'");
220+
}
221+
LevelMode levelMode = options.get<LevelMode>("levelMode");
222+
223+
if (!options.hasProperty("edgeMode")) {
224+
throw jac::Exception::create(jac::Exception::Type::TypeError, "Missing required property 'edgeMode'");
225+
}
226+
EdgeMode edgeMode = options.get<EdgeMode>("edgeMode");
227+
228+
return new PulseCounter_(static_cast<Feature*>(JS_GetContextOpaque(ctx)), pinLevel, pinEdge, edgeMode, levelMode);
229+
}
230+
231+
static void addProperties(jac::ContextRef ctx, jac::Object proto) {
232+
PulseCounterProtoBuilder::template addMethodMember<int(PulseCounter_::*)(), &PulseCounter_::read>(ctx, proto, "read");
233+
PulseCounterProtoBuilder::template addMethodMember<void(PulseCounter_::*)(), &PulseCounter_::clear>(ctx, proto, "clear");
234+
PulseCounterProtoBuilder::template addMethodMember<void(PulseCounter_::*)(), &PulseCounter_::start>(ctx, proto, "start");
235+
PulseCounterProtoBuilder::template addMethodMember<void(PulseCounter_::*)(), &PulseCounter_::stop>(ctx, proto, "stop");
236+
237+
jac::FunctionFactory ff(ctx);
238+
239+
proto.defineProperty("close", ff.newFunctionThis([](jac::ContextRef ctx, jac::ValueWeak thisVal) {
240+
PulseCounter_& self = *PulseCounterProtoBuilder::getOpaque(ctx, thisVal);
241+
self.close();
242+
self._feature->releaseLifetime(thisVal);
243+
}), jac::PropFlags::Enumerable);
244+
}
245+
246+
static void postConstruction(jac::ContextRef ctx, jac::Object thisVal, std::vector<jac::ValueWeak> args) {
247+
PulseCounter_& self = *PulseCounterProtoBuilder::getOpaque(ctx, thisVal);
248+
self._feature->extendLifetime(thisVal);
249+
}
250+
};
251+
252+
253+
struct NonexistentPulseCounterProtoBuilder : public jac::ProtoBuilder::Properties {
254+
static void addProperties(jac::ContextRef ctx, jac::Object proto) {
255+
jac::FunctionFactory ff(ctx);
256+
257+
proto.defineProperty("read", ff.newFunctionThis([](jac::ContextRef ctx, jac::ValueWeak thisVal) {
258+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter not available on this device");
259+
}), jac::PropFlags::Enumerable);
260+
261+
proto.defineProperty("clear", ff.newFunctionThis([](jac::ContextRef ctx, jac::ValueWeak thisVal) {
262+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter not available on this device");
263+
}), jac::PropFlags::Enumerable);
264+
265+
proto.defineProperty("start", ff.newFunctionThis([](jac::ContextRef ctx, jac::ValueWeak thisVal) {
266+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter not available on this device");
267+
}), jac::PropFlags::Enumerable);
268+
269+
proto.defineProperty("stop", ff.newFunctionThis([](jac::ContextRef ctx, jac::ValueWeak thisVal) {
270+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter not available on this device");
271+
}), jac::PropFlags::Enumerable);
272+
273+
proto.defineProperty("close", ff.newFunctionThis([](jac::ContextRef ctx, jac::ValueWeak thisVal) {
274+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter not available on this device");
275+
}), jac::PropFlags::Enumerable);
276+
}
277+
278+
static void postConstruction(jac::ContextRef ctx, jac::Object thisVal, std::vector<jac::ValueWeak> args) {
279+
throw jac::Exception::create(jac::Exception::Type::InternalError, "Pulse counter not available on this device");
280+
}
281+
};
282+
283+
284+
template<class Next>
285+
class PulseCounterFeature : public Next {
286+
public:
287+
#if defined(CONFIG_IDF_TARGET_ESP32C3)
288+
using PulseCounterClass = jac::Class<NonexistentPulseCounterProtoBuilder>;
289+
#else
290+
using PulseCounterClass = jac::Class<PulseCounterProtoBuilder<PulseCounterFeature>>;
291+
#endif
292+
293+
PulseCounterFeature() {
294+
PulseCounterClass::init("PulseCounter");
295+
}
296+
297+
void initialize() {
298+
Next::initialize();
299+
300+
auto& mod = this->newModule("pulseCounter");
301+
302+
jac::Object pcntCtor = PulseCounterClass::getConstructor(this->context());
303+
304+
jac::Object edgeAction = jac::Object::create(this->context());
305+
edgeAction.defineProperty("Hold", jac::Value::from(this->context(), static_cast<int>(pcnt_channel_edge_action_t::PCNT_CHANNEL_EDGE_ACTION_HOLD)), jac::PropFlags::Enumerable);
306+
edgeAction.defineProperty("Increase", jac::Value::from(this->context(), static_cast<int>(pcnt_channel_edge_action_t::PCNT_CHANNEL_EDGE_ACTION_INCREASE)), jac::PropFlags::Enumerable);
307+
edgeAction.defineProperty("Decrease", jac::Value::from(this->context(), static_cast<int>(pcnt_channel_edge_action_t::PCNT_CHANNEL_EDGE_ACTION_DECREASE)), jac::PropFlags::Enumerable);
308+
309+
jac::Object levelAction = jac::Object::create(this->context());
310+
levelAction.defineProperty("Keep", jac::Value::from(this->context(), static_cast<int>(pcnt_channel_level_action_t::PCNT_CHANNEL_LEVEL_ACTION_KEEP)), jac::PropFlags::Enumerable);
311+
levelAction.defineProperty("Inverse", jac::Value::from(this->context(), static_cast<int>(pcnt_channel_level_action_t::PCNT_CHANNEL_LEVEL_ACTION_INVERSE)), jac::PropFlags::Enumerable);
312+
levelAction.defineProperty("Hold", jac::Value::from(this->context(), static_cast<int>(pcnt_channel_level_action_t::PCNT_CHANNEL_LEVEL_ACTION_HOLD)), jac::PropFlags::Enumerable);
313+
314+
mod.addExport("EdgeAction", edgeAction);
315+
mod.addExport("LevelAction", levelAction);
316+
317+
mod.addExport("PulseCounter", pcntCtor);
318+
}
319+
};

main/main.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "espFeatures/i2cFeature.h"
2323
#include "espFeatures/simpleRadioFeature.h"
2424
#include "espFeatures/extendLifetimeFeature.h"
25+
#include "espFeatures/pulseCounterFeature.h"
2526

2627
#include "util/uartStream.h"
2728

@@ -61,13 +62,14 @@ using Machine = jac::ComposeMachine<
6162
jac::FilesystemFeature,
6263
jac::ModuleLoaderFeature,
6364
jac::EventLoopFeature,
65+
jac::TimersFeature,
6466
ExtendLifetimeFeature,
6567
GpioFeature,
6668
LedcFeature,
6769
AdcFeature,
6870
I2CFeature,
6971
SmartLedFeature,
70-
jac::TimersFeature,
72+
PulseCounterFeature,
7173
SimpleRadioFeature,
7274
jac::EventLoopTerminal
7375
>;

0 commit comments

Comments
 (0)