21#include "driver/gpio.h"
22#include "driver/ledc.h"
23#include "esp_adc/adc_oneshot.h"
34constexpr const char*
TAG =
"plg_gpio";
37 void* owner =
nullptr;
40constexpr size_t MAX_GPIO_PINS = 49;
41std::array<PinLock, MAX_GPIO_PINS> s_pin_locks{};
47bool manifest_allows_gpio(uint8_t pin) {
50 const auto& cap = p->manifest().capabilities;
51 if (cap.grove && (pin == 2 || pin == 3))
return true;
52 if (cap.sao && (pin == 15 || pin == 16))
return true;
53 for (uint8_t allowed : cap.gpio_pins)
if (allowed == pin)
return true;
54 for (uint8_t allowed : cap.pwm_pins)
if (allowed == pin)
return true;
55 for (uint8_t allowed : cap.adc_pins)
if (allowed == pin)
return true;
59int acquire_lock(uint8_t pin) {
65 PinLock& lock = s_pin_locks[pin];
72void release_lock(uint8_t pin) {
73 if (pin >= MAX_GPIO_PINS)
return;
74 s_pin_locks[pin] = PinLock{};
83 void* owner =
nullptr;
85std::array<PwmChannel, LEDC_CHANNEL_MAX> s_pwm_channels{};
87int pwm_channel_for(uint8_t pin) {
88 for (
size_t i = 0; i < s_pwm_channels.size(); ++i)
89 if (s_pwm_channels[i].used && s_pwm_channels[i].pin == pin)
90 return static_cast<int>(i);
94int pwm_channel_alloc(uint8_t pin) {
95 for (
size_t i = 0; i < s_pwm_channels.size(); ++i) {
96 if (!s_pwm_channels[i].used) {
97 s_pwm_channels[i] = { pin,
true, active() };
98 return static_cast<int>(i);
107bool manifest_allows_i2c(uint8_t bus) {
109 if (!p)
return false;
110 for (uint8_t b : p->manifest().capabilities.i2c_bus)
if (b == bus)
return true;
117 return (bus && bus->init()) ? bus :
nullptr;
126 int rc = acquire_lock(pin);
130 cfg.pin_bit_mask = 1ULL << pin;
131 cfg.intr_type = GPIO_INTR_DISABLE;
132 cfg.pull_up_en = GPIO_PULLUP_DISABLE;
133 cfg.pull_down_en = GPIO_PULLDOWN_DISABLE;
135 case GPIO_DIR_IN: cfg.mode = GPIO_MODE_INPUT;
break;
145 int rc = acquire_lock(pin);
148 gpio_pull_mode_t mode = GPIO_FLOATING;
155 return gpio_set_pull_mode(
static_cast<gpio_num_t
>(pin), mode) == ESP_OK
161 int rc = acquire_lock(pin);
163 return gpio_set_level(
static_cast<gpio_num_t
>(pin), level ? 1 : 0) == ESP_OK
170 int rc = acquire_lock(pin);
172 *level = gpio_get_level(
static_cast<gpio_num_t
>(pin)) != 0;
180 if (s_pin_locks[pin].owner != active())
return HOST_OK;
181 gpio_reset_pin(
static_cast<gpio_num_t
>(pin));
189 for (uint8_t pin = 0; pin < MAX_GPIO_PINS; ++pin) {
190 if (!s_pin_locks[pin].in_use || s_pin_locks[pin].owner != plugin)
continue;
191 int ch = pwm_channel_for(pin);
193 ledc_stop(LEDC_LOW_SPEED_MODE,
static_cast<ledc_channel_t
>(ch), 0);
194 s_pwm_channels[ch] = PwmChannel{};
195 LOG_W(
TAG,
"force-releasing leaked PWM on GPIO %u", pin);
197 gpio_reset_pin(
static_cast<gpio_num_t
>(pin));
199 LOG_W(
TAG,
"force-releasing leaked GPIO %u", pin);
202 for (
size_t i = 0; i < s_pwm_channels.size(); ++i) {
203 if (!s_pwm_channels[i].used || s_pwm_channels[i].owner != plugin)
continue;
204 ledc_stop(LEDC_LOW_SPEED_MODE,
static_cast<ledc_channel_t
>(i), 0);
205 LOG_W(
TAG,
"force-releasing leaked PWM channel %u",
static_cast<unsigned>(i));
206 s_pwm_channels[i] = PwmChannel{};
212 int rc = acquire_lock(pin);
216 int ch = pwm_channel_for(pin);
217 const bool fresh = (ch < 0);
218 if (fresh) ch = pwm_channel_alloc(pin);
221 static bool s_timer_inited =
false;
222 if (!s_timer_inited) {
223 ledc_timer_config_t timer{};
224 timer.speed_mode = LEDC_LOW_SPEED_MODE;
225 timer.duty_resolution = LEDC_TIMER_10_BIT;
226 timer.timer_num = LEDC_TIMER_0;
227 timer.freq_hz = freq_hz ? freq_hz : 5000;
228 timer.clk_cfg = LEDC_AUTO_CLK;
229 if (ledc_timer_config(&timer) != ESP_OK) {
230 if (fresh) s_pwm_channels[ch] = PwmChannel{};
233 s_timer_inited =
true;
236 ledc_channel_config_t cfg{};
237 cfg.channel =
static_cast<ledc_channel_t
>(ch);
238 cfg.duty = (1023 * duty_per_mille) / 1000;
240 cfg.speed_mode = LEDC_LOW_SPEED_MODE;
242 cfg.timer_sel = LEDC_TIMER_0;
243 if (ledc_channel_config(&cfg) != ESP_OK) {
244 if (fresh) s_pwm_channels[ch] = PwmChannel{};
253 int ch = pwm_channel_for(pin);
255 auto channel =
static_cast<ledc_channel_t
>(ch);
256 uint32_t duty = (1023 * duty_per_mille) / 1000;
257 if (ledc_set_duty(LEDC_LOW_SPEED_MODE, channel, duty) != ESP_OK)
return HOST_ERR_GENERIC;
258 if (ledc_update_duty(LEDC_LOW_SPEED_MODE, channel) != ESP_OK)
return HOST_ERR_GENERIC;
264 int ch = pwm_channel_for(pin);
266 ledc_stop(LEDC_LOW_SPEED_MODE,
static_cast<ledc_channel_t
>(ch), 0);
267 s_pwm_channels[ch] = PwmChannel{};
274 int rc = acquire_lock(pin);
278 struct AdcMap { uint8_t pin; adc_channel_t ch; };
279 constexpr AdcMap PINS[] = {
280 { 2, ADC_CHANNEL_1 }, { 3, ADC_CHANNEL_2 },
281 { 4, ADC_CHANNEL_3 }, { 5, ADC_CHANNEL_4 },
282 { 6, ADC_CHANNEL_5 }, { 7, ADC_CHANNEL_6 },
283 { 9, ADC_CHANNEL_8 },
285 adc_channel_t ch =
static_cast<adc_channel_t
>(-1);
286 for (
const auto& m : PINS)
if (m.pin == pin) ch = m.ch;
289 adc_oneshot_unit_handle_t unit;
290 adc_oneshot_unit_init_cfg_t unit_cfg{};
291 unit_cfg.unit_id = ADC_UNIT_1;
292 unit_cfg.ulp_mode = ADC_ULP_MODE_DISABLE;
293 if (adc_oneshot_new_unit(&unit_cfg, &unit) != ESP_OK)
return HOST_ERR_GENERIC;
295 adc_oneshot_chan_cfg_t chan_cfg{};
296 chan_cfg.atten = ADC_ATTEN_DB_12;
297 chan_cfg.bitwidth = ADC_BITWIDTH_DEFAULT;
298 if (adc_oneshot_config_channel(unit, ch, &chan_cfg) != ESP_OK) {
299 adc_oneshot_del_unit(unit);
304 esp_err_t err = adc_oneshot_read(unit, ch, &reading);
305 adc_oneshot_del_unit(unit);
308 if (raw) *raw =
static_cast<uint16_t
>(reading);
311 *millivolt =
static_cast<uint16_t
>(reading * 3300 / 4095);
324 auto* b = expansion_bus();
334 auto* b = expansion_bus();
340 const uint8_t* wr,
size_t wr_len,
341 uint8_t* rd,
size_t rd_len)
346 auto* b = expansion_bus();
356 auto* b = expansion_bus();
359 const size_t cap = *count;
361 for (uint8_t addr = 0x08; addr < 0x78 && n < cap; ++addr) {
362 if (b->probe(addr)) found_addrs[n++] = addr;
373 auto* b = expansion_bus();
383 auto* b = expansion_bus();
Single source of truth for plugin-accessible GPIO pins.
Owned WAMR module instance + per-plugin state.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
int host_i2c_write(uint8_t bus, uint8_t addr, const uint8_t *data, size_t len)
I2C write transaction.
int host_adc_read(uint8_t pin, uint16_t *raw, uint16_t *millivolt)
Single-shot ADC read.
int host_i2c_scan(uint8_t bus, uint8_t *found_addrs, size_t *count)
Scan the I2C bus for responding addresses.
int host_gpio_write(uint8_t pin, bool level)
Drive a digital output high/low.
int host_gpio_set_direction(uint8_t pin, uint8_t direction)
Configure pin direction (one of GPIO_DIR_*).
int host_gpio_set_pull(uint8_t pin, uint8_t pull)
Configure internal pull resistor (one of GPIO_PULL_*).
int host_sao_eeprom_write(uint16_t off, const uint8_t *buf, size_t len)
Write to the SAO addon EEPROM at byte offset.
int host_gpio_pwm_start(uint8_t pin, uint32_t freq_hz, uint16_t duty_per_mille)
Start LEDC PWM on pin.
int host_gpio_read(uint8_t pin, bool *level)
Sample a digital input.
int host_i2c_write_read(uint8_t bus, uint8_t addr, const uint8_t *wr, size_t wr_len, uint8_t *rd, size_t rd_len)
I2C write-then-read transaction with repeated start.
int host_gpio_pwm_set_duty(uint8_t pin, uint16_t duty_per_mille)
Update PWM duty without restarting the timer.
int host_gpio_release(uint8_t pin)
Release the pin claim so other plugins can use it.
int host_gpio_pwm_stop(uint8_t pin)
Stop PWM and release the LEDC channel.
int host_sao_eeprom_read(uint16_t off, uint8_t *buf, size_t len)
Read from the SAO addon EEPROM at byte offset.
int host_i2c_read(uint8_t bus, uint8_t addr, uint8_t *data, size_t len)
I2C read transaction.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_NO_CAPABILITY
#define HOST_ERR_NOT_SUPPORTED
#define HOST_ERR_INVALID_ARG
#define HOST_ERR_NO_MEMORY
void plg_log_warn(const char *msg)
void * plg_get_active_plugin(void)
void plg_gpio_on_unload(void *plugin)
II2cBus * getI2cBus1()
Returns singleton instance of I2C bus 1.
bool isBlocked(uint8_t pin)