mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
hw-gpio: add GPIO peripheral gems for embedded platforms
Add three new gems for GPIO pin control: - hw-gpio: common Ruby API, C bindings, and HAL header - hw-esp32-gpio: ESP32 HAL using ESP-IDF GPIO driver - hw-rp2040-gpio: RP2040 HAL using Pico SDK Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('hw-esp32-gpio') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'GPIO HAL for ESP32'
|
||||
|
||||
spec.add_dependency 'hw-gpio'
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "driver/gpio.h"
|
||||
#include <mruby/gpio.h>
|
||||
|
||||
void
|
||||
mrb_gpio_init(uint8_t pin)
|
||||
{
|
||||
gpio_reset_pin(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_set_dir(uint8_t pin, uint8_t flags)
|
||||
{
|
||||
if (flags & MRB_GPIO_IN) {
|
||||
gpio_set_direction(pin, GPIO_MODE_INPUT);
|
||||
}
|
||||
else if (flags & MRB_GPIO_OUT) {
|
||||
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
/* HIGH_Z: not yet implemented */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_pull_up(uint8_t pin)
|
||||
{
|
||||
gpio_pullup_en(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_pull_down(uint8_t pin)
|
||||
{
|
||||
gpio_pulldown_en(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_open_drain(uint8_t pin)
|
||||
{
|
||||
/* not yet implemented */
|
||||
}
|
||||
|
||||
int
|
||||
mrb_gpio_read(uint8_t pin)
|
||||
{
|
||||
return gpio_get_level(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_write(uint8_t pin, uint8_t val)
|
||||
{
|
||||
gpio_set_level(pin, val);
|
||||
}
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
void mrb_hw_esp32_gpio_gem_init(mrb_state *mrb) {}
|
||||
void mrb_hw_esp32_gpio_gem_final(mrb_state *mrb) {}
|
||||
@@ -0,0 +1,148 @@
|
||||
# hw-gpio - GPIO peripheral interface for mruby
|
||||
|
||||
This gem provides the `GPIO` class for controlling general-purpose
|
||||
input/output pins from mruby.
|
||||
It is designed for embedded platforms such as ESP32 and RP2040.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **hw-gpio** (this gem) - Ruby API, C bindings, and HAL function declarations
|
||||
- **hw-esp32-gpio** - HAL implementation for ESP32 (using ESP-IDF GPIO driver)
|
||||
- **hw-rp2040-gpio** - HAL implementation for RP2040 (using Pico SDK)
|
||||
|
||||
The platform gems depend on hw-gpio, so you only need to specify the
|
||||
platform gem in your build configuration.
|
||||
|
||||
## Build Configuration
|
||||
|
||||
```ruby
|
||||
# For ESP32
|
||||
MRuby::CrossBuild.new('esp32') do |conf|
|
||||
# ...
|
||||
conf.gem "#{root}/mrbgems/hw-esp32-gpio"
|
||||
end
|
||||
|
||||
# For RP2040
|
||||
MRuby::CrossBuild.new('rp2040') do |conf|
|
||||
# ...
|
||||
conf.gem "#{root}/mrbgems/hw-rp2040-gpio"
|
||||
end
|
||||
```
|
||||
|
||||
## Ruby API
|
||||
|
||||
### Constants (direction/mode flags)
|
||||
|
||||
These flags can be combined with bitwise OR.
|
||||
|
||||
| Flag | Value | Description |
|
||||
|---------------------|--------|----------------------------|
|
||||
| `GPIO::IN` | `0x01` | Input mode |
|
||||
| `GPIO::OUT` | `0x02` | Output mode |
|
||||
| `GPIO::HIGH_Z` | `0x04` | High-impedance (tri-state) |
|
||||
| `GPIO::PULL_UP` | `0x08` | Enable pull-up resistor |
|
||||
| `GPIO::PULL_DOWN` | `0x10` | Enable pull-down resistor |
|
||||
| `GPIO::OPEN_DRAIN` | `0x20` | Enable open-drain output |
|
||||
|
||||
### GPIO.new
|
||||
|
||||
```ruby
|
||||
gpio = GPIO.new(pin, flags)
|
||||
```
|
||||
|
||||
- `pin` - GPIO pin number (Integer)
|
||||
- `flags` - direction and mode flags (combined with `|`)
|
||||
- Exactly one of `IN`, `OUT`, or `HIGH_Z` must be specified
|
||||
- `PULL_UP` and `PULL_DOWN` are mutually exclusive
|
||||
- Raises `ArgumentError` on invalid flag combinations
|
||||
|
||||
```ruby
|
||||
# Input with pull-up
|
||||
button = GPIO.new(2, GPIO::IN | GPIO::PULL_UP)
|
||||
|
||||
# Output
|
||||
led = GPIO.new(25, GPIO::OUT)
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
|
||||
#### GPIO#read
|
||||
|
||||
Read the current pin value.
|
||||
|
||||
```ruby
|
||||
val = gpio.read # => 0 or 1
|
||||
```
|
||||
|
||||
#### GPIO#write
|
||||
|
||||
Set the pin output value.
|
||||
|
||||
```ruby
|
||||
gpio.write(1) # set high
|
||||
gpio.write(0) # set low
|
||||
```
|
||||
|
||||
- Raises `ArgumentError` if value is not 0 or 1
|
||||
|
||||
#### GPIO#high? / GPIO#low?
|
||||
|
||||
```ruby
|
||||
gpio.high? # => true if read != 0
|
||||
gpio.low? # => true if read == 0
|
||||
```
|
||||
|
||||
#### GPIO#pin
|
||||
|
||||
```ruby
|
||||
gpio.pin # => pin number passed to new
|
||||
```
|
||||
|
||||
#### GPIO#setmode
|
||||
|
||||
Reconfigure pin direction and mode flags after initialization.
|
||||
|
||||
```ruby
|
||||
gpio.setmode(GPIO::OUT)
|
||||
```
|
||||
|
||||
### Class Methods
|
||||
|
||||
These operate directly on pin numbers without creating an instance.
|
||||
|
||||
```ruby
|
||||
GPIO.read_at(pin) # => 0 or 1
|
||||
GPIO.write_at(pin, val) # set pin output (0 or 1)
|
||||
GPIO.set_dir_at(pin, flags) # set direction (IN/OUT/HIGH_Z)
|
||||
GPIO.pull_up_at(pin) # enable pull-up
|
||||
GPIO.pull_down_at(pin) # enable pull-down
|
||||
GPIO.open_drain_at(pin) # enable open-drain
|
||||
```
|
||||
|
||||
## HAL Interface
|
||||
|
||||
To add support for a new platform, create a gem (e.g., `hw-myboard-gpio`)
|
||||
that depends on `hw-gpio` and implements the following C functions
|
||||
declared in `<mruby/gpio.h>`:
|
||||
|
||||
```c
|
||||
void mrb_gpio_init(uint8_t pin);
|
||||
void mrb_gpio_set_dir(uint8_t pin, uint8_t flags);
|
||||
void mrb_gpio_pull_up(uint8_t pin);
|
||||
void mrb_gpio_pull_down(uint8_t pin);
|
||||
void mrb_gpio_open_drain(uint8_t pin);
|
||||
int mrb_gpio_read(uint8_t pin);
|
||||
void mrb_gpio_write(uint8_t pin, uint8_t val);
|
||||
```
|
||||
|
||||
The `flags` parameter for `mrb_gpio_set_dir()` uses the bitmask
|
||||
constants defined in the header (`MRB_GPIO_IN`, `MRB_GPIO_OUT`,
|
||||
`MRB_GPIO_HIGH_Z`).
|
||||
|
||||
The gem must also provide empty `mrb_<gemname>_gem_init()` and
|
||||
`mrb_<gemname>_gem_final()` functions (with hyphens replaced by
|
||||
underscores).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef MRUBY_GPIO_H
|
||||
#define MRUBY_GPIO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* direction/mode flags (bitmask) */
|
||||
#define MRB_GPIO_IN 0x01
|
||||
#define MRB_GPIO_OUT 0x02
|
||||
#define MRB_GPIO_HIGH_Z 0x04
|
||||
#define MRB_GPIO_PULL_UP 0x08
|
||||
#define MRB_GPIO_PULL_DOWN 0x10
|
||||
#define MRB_GPIO_OPEN_DRAIN 0x20
|
||||
|
||||
/* HAL functions - implemented by hw-<platform>-gpio gems */
|
||||
void mrb_gpio_init(uint8_t pin);
|
||||
void mrb_gpio_set_dir(uint8_t pin, uint8_t flags);
|
||||
void mrb_gpio_pull_up(uint8_t pin);
|
||||
void mrb_gpio_pull_down(uint8_t pin);
|
||||
void mrb_gpio_open_drain(uint8_t pin);
|
||||
int mrb_gpio_read(uint8_t pin);
|
||||
void mrb_gpio_write(uint8_t pin, uint8_t val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MRUBY_GPIO_H */
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('hw-gpio') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||
spec.summary = 'GPIO peripheral interface'
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
class GPIO
|
||||
IN = 0x01
|
||||
OUT = 0x02
|
||||
HIGH_Z = 0x04
|
||||
PULL_UP = 0x08
|
||||
PULL_DOWN = 0x10
|
||||
OPEN_DRAIN = 0x20
|
||||
|
||||
attr_reader :pin
|
||||
|
||||
def initialize(pin, flags)
|
||||
@pin = pin
|
||||
__init(pin)
|
||||
setmode(flags)
|
||||
end
|
||||
|
||||
def setmode(flags)
|
||||
dir = flags & (IN | OUT | HIGH_Z)
|
||||
n = (flags & IN != 0 ? 1 : 0) + (flags & OUT != 0 ? 1 : 0) + (flags & HIGH_Z != 0 ? 1 : 0)
|
||||
if n == 0
|
||||
raise ArgumentError, "specify one of IN, OUT, or HIGH_Z"
|
||||
elsif n > 1
|
||||
raise ArgumentError, "IN, OUT, and HIGH_Z are exclusive"
|
||||
end
|
||||
GPIO.set_dir_at(@pin, dir)
|
||||
|
||||
pull = flags & (PULL_UP | PULL_DOWN)
|
||||
if pull == (PULL_UP | PULL_DOWN)
|
||||
raise ArgumentError, "PULL_UP and PULL_DOWN are exclusive"
|
||||
end
|
||||
GPIO.pull_up_at(@pin) if pull == PULL_UP
|
||||
GPIO.pull_down_at(@pin) if pull == PULL_DOWN
|
||||
GPIO.open_drain_at(@pin) if flags & OPEN_DRAIN != 0
|
||||
nil
|
||||
end
|
||||
|
||||
def high?
|
||||
read != 0
|
||||
end
|
||||
|
||||
def low?
|
||||
read == 0
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,119 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/gpio.h>
|
||||
|
||||
static mrb_value
|
||||
mrb_gpio_m_init(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_int pin;
|
||||
mrb_get_args(mrb, "i", &pin);
|
||||
mrb_gpio_init((uint8_t)pin);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/* GPIO.set_dir_at(pin, flags) */
|
||||
static mrb_value
|
||||
mrb_gpio_s_set_dir_at(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int pin, flags;
|
||||
mrb_get_args(mrb, "ii", &pin, &flags);
|
||||
mrb_gpio_set_dir((uint8_t)pin, (uint8_t)flags);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/* GPIO.pull_up_at(pin) */
|
||||
static mrb_value
|
||||
mrb_gpio_s_pull_up_at(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int pin;
|
||||
mrb_get_args(mrb, "i", &pin);
|
||||
mrb_gpio_pull_up((uint8_t)pin);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/* GPIO.pull_down_at(pin) */
|
||||
static mrb_value
|
||||
mrb_gpio_s_pull_down_at(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int pin;
|
||||
mrb_get_args(mrb, "i", &pin);
|
||||
mrb_gpio_pull_down((uint8_t)pin);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/* GPIO.open_drain_at(pin) */
|
||||
static mrb_value
|
||||
mrb_gpio_s_open_drain_at(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int pin;
|
||||
mrb_get_args(mrb, "i", &pin);
|
||||
mrb_gpio_open_drain((uint8_t)pin);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/* GPIO.read_at(pin) */
|
||||
static mrb_value
|
||||
mrb_gpio_s_read_at(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int pin;
|
||||
mrb_get_args(mrb, "i", &pin);
|
||||
return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin));
|
||||
}
|
||||
|
||||
/* GPIO.write_at(pin, val) */
|
||||
static mrb_value
|
||||
mrb_gpio_s_write_at(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
mrb_int pin, val;
|
||||
mrb_get_args(mrb, "ii", &pin, &val);
|
||||
if (val != 0 && val != 1) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1");
|
||||
}
|
||||
mrb_gpio_write((uint8_t)pin, (uint8_t)val);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
/* GPIO#read */
|
||||
static mrb_value
|
||||
mrb_gpio_m_read(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
|
||||
return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin));
|
||||
}
|
||||
|
||||
/* GPIO#write(val) */
|
||||
static mrb_value
|
||||
mrb_gpio_m_write(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_int val;
|
||||
mrb_get_args(mrb, "i", &val);
|
||||
if (val != 0 && val != 1) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1");
|
||||
}
|
||||
mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
|
||||
mrb_gpio_write((uint8_t)pin, (uint8_t)val);
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hw_gpio_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(GPIO), mrb->object_class);
|
||||
|
||||
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_gpio_m_init, MRB_ARGS_REQ(1));
|
||||
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_gpio_m_read, MRB_ARGS_NONE());
|
||||
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_gpio_m_write, MRB_ARGS_REQ(1));
|
||||
|
||||
mrb_define_class_method_id(mrb, cls, MRB_SYM(set_dir_at), mrb_gpio_s_set_dir_at, MRB_ARGS_REQ(2));
|
||||
mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_up_at), mrb_gpio_s_pull_up_at, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_down_at), mrb_gpio_s_pull_down_at, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, cls, MRB_SYM(open_drain_at), mrb_gpio_s_open_drain_at, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, cls, MRB_SYM(read_at), mrb_gpio_s_read_at, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, cls, MRB_SYM(write_at), mrb_gpio_s_write_at, MRB_ARGS_REQ(2));
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hw_gpio_gem_final(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('hw-rp2040-gpio') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'GPIO HAL for RP2040'
|
||||
|
||||
spec.add_dependency 'hw-gpio'
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <stdbool.h>
|
||||
#include "hardware/gpio.h"
|
||||
#include <mruby/gpio.h>
|
||||
|
||||
void
|
||||
mrb_gpio_init(uint8_t pin)
|
||||
{
|
||||
gpio_init(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_set_dir(uint8_t pin, uint8_t flags)
|
||||
{
|
||||
if (flags & MRB_GPIO_IN) {
|
||||
gpio_set_dir(pin, false);
|
||||
}
|
||||
else if (flags & MRB_GPIO_OUT) {
|
||||
gpio_set_dir(pin, true);
|
||||
}
|
||||
/* HIGH_Z: not yet implemented */
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_pull_up(uint8_t pin)
|
||||
{
|
||||
gpio_pull_up(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_pull_down(uint8_t pin)
|
||||
{
|
||||
gpio_pull_down(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_open_drain(uint8_t pin)
|
||||
{
|
||||
/* not yet implemented */
|
||||
}
|
||||
|
||||
int
|
||||
mrb_gpio_read(uint8_t pin)
|
||||
{
|
||||
return gpio_get(pin);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_gpio_write(uint8_t pin, uint8_t val)
|
||||
{
|
||||
gpio_put(pin, val == 1);
|
||||
}
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
void mrb_hw_rp2040_gpio_gem_init(mrb_state *mrb) {}
|
||||
void mrb_hw_rp2040_gpio_gem_final(mrb_state *mrb) {}
|
||||
Reference in New Issue
Block a user