mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
hw-adc: add ADC peripheral gem for embedded platforms
Add hw-adc gem with analog-to-digital conversion support: - Common Ruby API with read, read_raw, and read_voltage - ports/esp32/ using ESP-IDF ADC oneshot driver - ports/rp2040/ using Pico SDK ADC hardware Based on picoruby-adc by HASUMI Hitoshi. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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/<name>/`
|
||||
directory and implement the following C functions declared in
|
||||
`<mruby/adc.h>`:
|
||||
|
||||
```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
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef MRUBY_ADC_H
|
||||
#define MRUBY_ADC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* HAL functions - implemented in ports/<platform>/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 */
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
class ADC
|
||||
def initialize(pin)
|
||||
@input = __init(pin)
|
||||
end
|
||||
|
||||
attr_reader :input
|
||||
end
|
||||
@@ -0,0 +1,147 @@
|
||||
#include <stdbool.h>
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include <mruby/adc.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <stdbool.h>
|
||||
#include "hardware/adc.h"
|
||||
#include <mruby/adc.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/adc.h>
|
||||
|
||||
/* 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)
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user