diff --git a/mrbgems/hw-adc/README.md b/mrbgems/hw-adc/README.md new file mode 100644 index 000000000..c0d233840 --- /dev/null +++ b/mrbgems/hw-adc/README.md @@ -0,0 +1,74 @@ +# hw-adc - ADC peripheral interface for mruby + +This gem provides the `ADC` class for reading analog input from +mruby. It is designed for embedded platforms such as ESP32 and +RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using ESP-IDF ADC oneshot driver +- `ports/rp2040/` - RP2040 using Pico SDK ADC hardware + +## Build Configuration + +```ruby +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-adc' +end +``` + +## Ruby API + +### ADC.new + +```ruby +adc = ADC.new(pin) +``` + +- `pin` - ADC-capable GPIO pin number (Integer) +- Raises `ArgumentError` if the pin is not valid for ADC + +### ADC#read / ADC#read_voltage + +Read the analog value as voltage (Float). + +```ruby +voltage = adc.read # => 1.65 +voltage = adc.read_voltage # same +``` + +### ADC#read_raw + +Read the raw ADC value (Integer, typically 0-4095 for 12-bit). + +```ruby +raw = adc.read_raw # => 2048 +``` + +### ADC#input + +Returns the ADC input channel number assigned during initialization. + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +int mrb_adc_init(uint8_t pin); +uint32_t mrb_adc_read_raw(uint8_t input); +float mrb_adc_read_voltage(uint8_t input); +``` + +- `mrb_adc_init` returns the input channel number (>= 0) on + success, negative on error +- `input` parameter for read functions is the value returned by + `mrb_adc_init` + +## License + +MIT diff --git a/mrbgems/hw-adc/include/mruby/adc.h b/mrbgems/hw-adc/include/mruby/adc.h new file mode 100644 index 000000000..cc8d97441 --- /dev/null +++ b/mrbgems/hw-adc/include/mruby/adc.h @@ -0,0 +1,19 @@ +#ifndef MRUBY_ADC_H +#define MRUBY_ADC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* HAL functions - implemented in ports//adc.c */ +int mrb_adc_init(uint8_t pin); +uint32_t mrb_adc_read_raw(uint8_t input); +float mrb_adc_read_voltage(uint8_t input); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_ADC_H */ diff --git a/mrbgems/hw-adc/mrbgem.rake b/mrbgems/hw-adc/mrbgem.rake new file mode 100644 index 000000000..b96d7b564 --- /dev/null +++ b/mrbgems/hw-adc/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-adc') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'ADC peripheral interface' +end diff --git a/mrbgems/hw-adc/mrblib/adc.rb b/mrbgems/hw-adc/mrblib/adc.rb new file mode 100644 index 000000000..0c983c090 --- /dev/null +++ b/mrbgems/hw-adc/mrblib/adc.rb @@ -0,0 +1,7 @@ +class ADC + def initialize(pin) + @input = __init(pin) + end + + attr_reader :input +end diff --git a/mrbgems/hw-adc/ports/esp32/adc.c b/mrbgems/hw-adc/ports/esp32/adc.c new file mode 100644 index 000000000..3b5b7f08e --- /dev/null +++ b/mrbgems/hw-adc/ports/esp32/adc.c @@ -0,0 +1,147 @@ +#include +#include "esp_adc/adc_oneshot.h" +#include + +#define VOLTAGE_MAX 3.3f +#define RESOLUTION 4095 +#define UNIT_NUM 2 + +static adc_oneshot_unit_handle_t adc_handles[UNIT_NUM]; +static bool adc_initialized; + +static adc_unit_t +pin_to_unit(uint8_t pin) +{ + switch (pin) { +#if CONFIG_IDF_TARGET_ESP32C3 + case 0: case 1: case 2: case 3: case 4: + return ADC_UNIT_1; + case 5: + return ADC_UNIT_2; +#elif CONFIG_IDF_TARGET_ESP32 + case 32: case 33: case 34: case 35: case 36: case 39: + return ADC_UNIT_1; + case 0: case 2: case 4: case 12: case 13: case 14: case 15: + case 25: case 26: case 27: + return ADC_UNIT_2; +#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2) + case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 9: case 10: + return ADC_UNIT_1; + case 11: case 12: case 13: case 14: case 15: + case 16: case 17: case 18: case 19: case 20: + return ADC_UNIT_2; +#endif + } + return -1; +} + +static adc_channel_t +pin_to_channel(uint8_t pin) +{ + switch (pin) { +#if CONFIG_IDF_TARGET_ESP32C3 + case 0: return ADC_CHANNEL_0; + case 1: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 3: return ADC_CHANNEL_3; + case 4: return ADC_CHANNEL_4; + case 5: return ADC_CHANNEL_0; +#elif CONFIG_IDF_TARGET_ESP32 + case 36: return ADC_CHANNEL_0; + case 39: return ADC_CHANNEL_3; + case 32: return ADC_CHANNEL_4; + case 33: return ADC_CHANNEL_5; + case 34: return ADC_CHANNEL_6; + case 35: return ADC_CHANNEL_7; + case 4: return ADC_CHANNEL_0; + case 0: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 15: return ADC_CHANNEL_3; + case 13: return ADC_CHANNEL_4; + case 12: return ADC_CHANNEL_5; + case 14: return ADC_CHANNEL_6; + case 27: return ADC_CHANNEL_7; + case 25: return ADC_CHANNEL_8; + case 26: return ADC_CHANNEL_9; +#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2) + case 1: return ADC_CHANNEL_0; + case 2: return ADC_CHANNEL_1; + case 3: return ADC_CHANNEL_2; + case 4: return ADC_CHANNEL_3; + case 5: return ADC_CHANNEL_4; + case 6: return ADC_CHANNEL_5; + case 7: return ADC_CHANNEL_6; + case 8: return ADC_CHANNEL_7; + case 9: return ADC_CHANNEL_8; + case 10: return ADC_CHANNEL_9; + case 11: return ADC_CHANNEL_0; + case 12: return ADC_CHANNEL_1; + case 13: return ADC_CHANNEL_2; + case 14: return ADC_CHANNEL_3; + case 15: return ADC_CHANNEL_4; + case 16: return ADC_CHANNEL_5; + case 17: return ADC_CHANNEL_6; + case 18: return ADC_CHANNEL_7; + case 19: return ADC_CHANNEL_8; + case 20: return ADC_CHANNEL_9; +#endif + } + return -1; +} + +static int +init_units(void) +{ + adc_unit_t units[] = { ADC_UNIT_1, ADC_UNIT_2 }; + for (int i = 0; i < UNIT_NUM; i++) { + adc_oneshot_unit_init_cfg_t cfg = { + .unit_id = units[i], + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + if (adc_oneshot_new_unit(&cfg, &adc_handles[i]) != ESP_OK) + return -1; + } + return 0; +} + +int +mrb_adc_init(uint8_t pin) +{ + if (!adc_initialized) { + if (init_units() != 0) return -1; + adc_initialized = true; + } + + int ch = pin_to_channel(pin); + if (ch < 0) return -1; + + adc_unit_t unit = pin_to_unit(pin); + if (unit < 0 || unit >= UNIT_NUM) return -1; + + adc_oneshot_chan_cfg_t cfg = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + if (adc_oneshot_config_channel(adc_handles[unit], ch, &cfg) != ESP_OK) + return -1; + + return (int)pin; +} + +uint32_t +mrb_adc_read_raw(uint8_t input) +{ + adc_unit_t unit = pin_to_unit(input); + int ch = pin_to_channel(input); + int raw = 0; + if (unit >= 0 && unit < UNIT_NUM) + adc_oneshot_read(adc_handles[unit], ch, &raw); + return (uint32_t)raw; +} + +float +mrb_adc_read_voltage(uint8_t input) +{ + return (float)mrb_adc_read_raw(input) * VOLTAGE_MAX / RESOLUTION; +} diff --git a/mrbgems/hw-adc/ports/rp2040/adc.c b/mrbgems/hw-adc/ports/rp2040/adc.c new file mode 100644 index 000000000..8d317c58c --- /dev/null +++ b/mrbgems/hw-adc/ports/rp2040/adc.c @@ -0,0 +1,43 @@ +#include +#include "hardware/adc.h" +#include + +#define VOLTAGE_MAX 3.3f +#define RESOLUTION 4095 +#define TEMP_INPUT 4 + +static bool adc_initialized; + +int +mrb_adc_init(uint8_t pin) +{ + if (!adc_initialized) { + adc_init(); + adc_initialized = true; + } + + uint input; + switch (pin) { + case 26: input = 0; break; + case 27: input = 1; break; + case 28: input = 2; break; + case 29: input = 3; break; + default: return -1; + } + adc_gpio_init(pin); + return (int)input; +} + +uint32_t +mrb_adc_read_raw(uint8_t input) +{ + adc_select_input(input); + return (uint32_t)adc_read(); +} + +float +mrb_adc_read_voltage(uint8_t input) +{ + adc_select_input(input); + return (float)adc_read() * VOLTAGE_MAX / RESOLUTION; +} diff --git a/mrbgems/hw-adc/src/adc.c b/mrbgems/hw-adc/src/adc.c new file mode 100644 index 000000000..7c12e904d --- /dev/null +++ b/mrbgems/hw-adc/src/adc.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +/* ADC#__init(pin) */ +static mrb_value +mrb_adc_m_init(mrb_state *mrb, mrb_value self) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + int input = mrb_adc_init((uint8_t)pin); + if (input < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid ADC pin"); + } + return mrb_fixnum_value(input); +} + +/* ADC#read_raw */ +static mrb_value +mrb_adc_m_read_raw(mrb_state *mrb, mrb_value self) +{ + mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input))); + return mrb_fixnum_value(mrb_adc_read_raw((uint8_t)input)); +} + +/* ADC#read_voltage (also aliased as read) */ +static mrb_value +mrb_adc_m_read_voltage(mrb_state *mrb, mrb_value self) +{ + mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input))); + return mrb_float_value(mrb, (mrb_float)mrb_adc_read_voltage((uint8_t)input)); +} + +void +mrb_hw_adc_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(ADC), mrb->object_class); + mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_adc_m_init, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(read_raw), mrb_adc_m_read_raw, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(read_voltage), mrb_adc_m_read_voltage, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_adc_m_read_voltage, MRB_ARGS_NONE()); +} + +void +mrb_hw_adc_gem_final(mrb_state *mrb) +{ +}