mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
hw-i2c: add I2C peripheral gems for embedded platforms
Add three new gems for I2C communication: - hw-i2c: common Ruby API, C bindings, and HAL header - hw-esp32-i2c: ESP32 HAL using ESP-IDF I2C master driver - hw-rp2040-i2c: RP2040 HAL using Pico SDK The HAL API provides init, read, write, and atomic write_read (repeated START) operations. Platform gems depend on hw-i2c and are only compiled when explicitly included in build config. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('hw-esp32-i2c') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'I2C HAL for ESP32'
|
||||
|
||||
spec.add_dependency 'hw-i2c'
|
||||
end
|
||||
@@ -0,0 +1,122 @@
|
||||
#include <string.h>
|
||||
#include "driver/i2c_master.h"
|
||||
#include <mruby/i2c.h>
|
||||
|
||||
typedef struct {
|
||||
i2c_master_bus_handle_t bus;
|
||||
uint32_t freq;
|
||||
bool initialized;
|
||||
} i2c_ctx;
|
||||
|
||||
/* ESP32 supports up to 2 I2C ports */
|
||||
static i2c_ctx ctx[2];
|
||||
|
||||
static bool
|
||||
valid_unit(int unit)
|
||||
{
|
||||
return unit >= 0 && unit <= 1 && ctx[unit].initialized;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
us_to_ms(uint32_t timeout_us)
|
||||
{
|
||||
uint32_t ms = (timeout_us + 999) / 1000;
|
||||
return (ms < 10) ? 10 : ms;
|
||||
}
|
||||
|
||||
static i2c_master_dev_handle_t
|
||||
add_device(int unit, uint8_t addr)
|
||||
{
|
||||
i2c_device_config_t cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = addr,
|
||||
.scl_speed_hz = ctx[unit].freq,
|
||||
};
|
||||
i2c_master_dev_handle_t dev;
|
||||
if (i2c_master_bus_add_device(ctx[unit].bus, &cfg, &dev) != ESP_OK)
|
||||
return NULL;
|
||||
return dev;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_unit_name_to_num(const char *name)
|
||||
{
|
||||
if (strcmp(name, "ESP32_I2C0") == 0) return 0;
|
||||
if (strcmp(name, "ESP32_I2C1") == 0) return 1;
|
||||
return MRB_I2C_ERROR_UNIT;
|
||||
}
|
||||
|
||||
mrb_i2c_status
|
||||
mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl)
|
||||
{
|
||||
if (unit < 0 || unit > 1) return MRB_I2C_ERROR_UNIT;
|
||||
|
||||
if (ctx[unit].initialized) {
|
||||
i2c_del_master_bus(ctx[unit].bus);
|
||||
ctx[unit].initialized = false;
|
||||
}
|
||||
|
||||
i2c_master_bus_config_t cfg = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = unit,
|
||||
.scl_io_num = scl,
|
||||
.sda_io_num = sda,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags.enable_internal_pullup = true,
|
||||
};
|
||||
|
||||
esp_err_t err = i2c_new_master_bus(&cfg, &ctx[unit].bus);
|
||||
if (err != ESP_OK) return MRB_I2C_ERROR_UNIT;
|
||||
|
||||
ctx[unit].initialized = true;
|
||||
ctx[unit].freq = freq;
|
||||
return MRB_I2C_OK;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
|
||||
uint32_t timeout_us)
|
||||
{
|
||||
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
|
||||
|
||||
i2c_master_dev_handle_t dev = add_device(unit, addr);
|
||||
if (!dev) return -1;
|
||||
|
||||
esp_err_t err = i2c_master_receive(dev, dst, len, us_to_ms(timeout_us));
|
||||
i2c_master_bus_rm_device(dev);
|
||||
return (err == ESP_OK) ? (int)len : -1;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
|
||||
uint32_t timeout_us)
|
||||
{
|
||||
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
|
||||
|
||||
i2c_master_dev_handle_t dev = add_device(unit, addr);
|
||||
if (!dev) return -1;
|
||||
|
||||
esp_err_t err = i2c_master_transmit(dev, src, len, us_to_ms(timeout_us));
|
||||
i2c_master_bus_rm_device(dev);
|
||||
return (err == ESP_OK) ? (int)len : -1;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
|
||||
uint8_t *dst, size_t rlen, uint32_t timeout_us)
|
||||
{
|
||||
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
|
||||
|
||||
i2c_master_dev_handle_t dev = add_device(unit, addr);
|
||||
if (!dev) return -1;
|
||||
|
||||
esp_err_t err = i2c_master_transmit_receive(dev, src, wlen, dst, rlen,
|
||||
us_to_ms(timeout_us));
|
||||
i2c_master_bus_rm_device(dev);
|
||||
return (err == ESP_OK) ? (int)rlen : -1;
|
||||
}
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
void mrb_hw_esp32_i2c_gem_init(mrb_state *mrb) {}
|
||||
void mrb_hw_esp32_i2c_gem_final(mrb_state *mrb) {}
|
||||
@@ -0,0 +1,175 @@
|
||||
# hw-i2c - I2C peripheral interface for mruby
|
||||
|
||||
This gem provides the `I2C` class for communicating with I2C devices from mruby. It is designed for embedded platforms such as ESP32 and RP2040.
|
||||
|
||||
## Architecture
|
||||
|
||||
The I2C support is split into a common gem and platform-specific HAL gems:
|
||||
|
||||
- **hw-i2c** (this gem) - Ruby API, C bindings, and HAL function declarations
|
||||
- **hw-esp32-i2c** - HAL implementation for ESP32 (using ESP-IDF I2C master driver)
|
||||
- **hw-rp2040-i2c** - HAL implementation for RP2040 (using Pico SDK)
|
||||
|
||||
The platform gems depend on hw-i2c, 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-i2c"
|
||||
end
|
||||
|
||||
# For RP2040
|
||||
MRuby::CrossBuild.new('rp2040') do |conf|
|
||||
# ...
|
||||
conf.gem "#{root}/mrbgems/hw-rp2040-i2c"
|
||||
end
|
||||
```
|
||||
|
||||
## Ruby API
|
||||
|
||||
### I2C.new
|
||||
|
||||
```ruby
|
||||
i2c = I2C.new(
|
||||
unit: :ESP32_I2C0, # I2C unit name (platform-specific, required)
|
||||
frequency: 100_000, # bus frequency in Hz (default: 100kHz)
|
||||
sda_pin: 21, # SDA GPIO pin number (default: -1 for platform default)
|
||||
scl_pin: 22, # SCL GPIO pin number (default: -1 for platform default)
|
||||
timeout: 500 # default timeout in ms (default: 500)
|
||||
)
|
||||
```
|
||||
|
||||
#### Unit Names
|
||||
|
||||
| Platform | Available Units |
|
||||
|----------|---------------------------------|
|
||||
| ESP32 | `:ESP32_I2C0`, `:ESP32_I2C1` |
|
||||
| RP2040 | `:RP2040_I2C0`, `:RP2040_I2C1` |
|
||||
|
||||
On RP2040, if `sda_pin` or `scl_pin` is -1, the Pico SDK default pins are used.
|
||||
|
||||
### I2C#write
|
||||
|
||||
Write data to an I2C device.
|
||||
|
||||
```ruby
|
||||
i2c.write(addr, *data, timeout: 500)
|
||||
```
|
||||
|
||||
- `addr` - 7-bit I2C device address (Integer)
|
||||
- `data` - one or more data arguments, each can be:
|
||||
- **Integer** - a single byte (0-255)
|
||||
- **Array of Integer** - multiple bytes
|
||||
- **String** - raw bytes
|
||||
- `timeout:` - optional timeout in ms (overrides instance default)
|
||||
- Returns the number of bytes written (Integer)
|
||||
- Raises `IOError` on failure
|
||||
|
||||
```ruby
|
||||
# Write a single byte
|
||||
i2c.write(0x3C, 0x00)
|
||||
|
||||
# Write multiple bytes
|
||||
i2c.write(0x3C, 0x00, [0xAE, 0xD5, 0x80])
|
||||
|
||||
# Write a string
|
||||
i2c.write(0x3C, "hello")
|
||||
|
||||
# Mix data types
|
||||
i2c.write(0x3C, 0x40, [0x01, 0x02], "data")
|
||||
```
|
||||
|
||||
### I2C#read
|
||||
|
||||
Read data from an I2C device. Optionally write data before reading (repeated START).
|
||||
|
||||
```ruby
|
||||
i2c.read(addr, length, *write_data, timeout: 500)
|
||||
```
|
||||
|
||||
- `addr` - 7-bit I2C device address (Integer)
|
||||
- `length` - number of bytes to read (Integer, must be positive)
|
||||
- `write_data` - optional data to write before reading (same format as `write`). When provided, the gem performs a write-then-read transaction using I2C repeated START condition. This is the standard way to read from a specific register.
|
||||
- `timeout:` - optional timeout in ms (overrides instance default)
|
||||
- Returns the data read (String)
|
||||
- Raises `IOError` on failure, `ArgumentError` if length <= 0
|
||||
|
||||
```ruby
|
||||
# Simple read (2 bytes from device)
|
||||
data = i2c.read(0x50, 2)
|
||||
|
||||
# Register read: write register address 0x00, then read 2 bytes
|
||||
data = i2c.read(0x50, 2, 0x00)
|
||||
|
||||
# Multi-byte register address
|
||||
data = i2c.read(0x50, 4, [0x00, 0x10])
|
||||
```
|
||||
|
||||
### I2C#scan
|
||||
|
||||
Scan the I2C bus for responsive devices.
|
||||
|
||||
```ruby
|
||||
i2c.scan(timeout: 500)
|
||||
```
|
||||
|
||||
- `timeout:` - optional timeout per probe in ms
|
||||
- Returns an Array of 7-bit addresses (Integer) that responded
|
||||
|
||||
```ruby
|
||||
found = i2c.scan
|
||||
# => [0x3C, 0x50, 0x68]
|
||||
```
|
||||
|
||||
## HAL Interface
|
||||
|
||||
To add support for a new platform, create a gem (e.g., `hw-myboard-i2c`) that depends on `hw-i2c` and implements the following C functions declared in `<mruby/i2c.h>`:
|
||||
|
||||
```c
|
||||
/* Convert platform-specific unit name string to unit number.
|
||||
Return MRB_I2C_ERROR_UNIT for unknown names. */
|
||||
int mrb_i2c_unit_name_to_num(const char *name);
|
||||
|
||||
/* Initialize an I2C bus unit.
|
||||
sda/scl: GPIO pin numbers (-1 for platform default if available).
|
||||
Return MRB_I2C_OK on success. */
|
||||
mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl);
|
||||
|
||||
/* Read len bytes from device at addr.
|
||||
Return number of bytes read on success, negative on error. */
|
||||
int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
|
||||
uint32_t timeout_us);
|
||||
|
||||
/* Write len bytes to device at addr.
|
||||
Return number of bytes written on success, negative on error. */
|
||||
int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
|
||||
uint32_t timeout_us);
|
||||
|
||||
/* Atomic write-then-read using repeated START condition.
|
||||
Write wlen bytes from src, then read rlen bytes into dst.
|
||||
Return number of bytes read on success, negative on error. */
|
||||
int mrb_i2c_write_read(int unit, uint8_t addr,
|
||||
const uint8_t *src, size_t wlen,
|
||||
uint8_t *dst, size_t rlen,
|
||||
uint32_t timeout_us);
|
||||
```
|
||||
|
||||
The gem must also provide empty `mrb_<gemname>_gem_init()` and `mrb_<gemname>_gem_final()` functions (with hyphens replaced by underscores).
|
||||
|
||||
### Error Codes
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
MRB_I2C_OK = 0,
|
||||
MRB_I2C_ERROR_UNIT = -1, /* invalid or uninitialized unit */
|
||||
MRB_I2C_ERROR_TIMEOUT = -2, /* communication timeout */
|
||||
MRB_I2C_ERROR_NACK = -3, /* device did not acknowledge */
|
||||
} mrb_i2c_status;
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef MRUBY_I2C_H
|
||||
#define MRUBY_I2C_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MRB_I2C_OK = 0,
|
||||
MRB_I2C_ERROR_UNIT = -1,
|
||||
MRB_I2C_ERROR_TIMEOUT = -2,
|
||||
MRB_I2C_ERROR_NACK = -3,
|
||||
} mrb_i2c_status;
|
||||
|
||||
/* HAL functions - implemented by hw-<platform>-i2c gems */
|
||||
mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl);
|
||||
int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, uint32_t timeout_us);
|
||||
int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, uint32_t timeout_us);
|
||||
int mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
|
||||
uint8_t *dst, size_t rlen, uint32_t timeout_us);
|
||||
int mrb_i2c_unit_name_to_num(const char *name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MRUBY_I2C_H */
|
||||
@@ -0,0 +1,5 @@
|
||||
MRuby::Gem::Specification.new('hw-i2c') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||
spec.summary = 'I2C peripheral interface'
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
class I2C
|
||||
DEFAULT_FREQUENCY = 100_000 # Hz
|
||||
DEFAULT_TIMEOUT = 500 # ms
|
||||
|
||||
def initialize(unit:, frequency: DEFAULT_FREQUENCY, sda_pin: -1, scl_pin: -1, timeout: DEFAULT_TIMEOUT)
|
||||
@timeout = timeout
|
||||
@unit_num = __init(unit.to_s, frequency, sda_pin, scl_pin)
|
||||
end
|
||||
|
||||
def scan(timeout: @timeout)
|
||||
found = []
|
||||
(0x08..0x77).each do |addr|
|
||||
begin
|
||||
read(addr, 1, timeout: timeout)
|
||||
found << addr
|
||||
rescue IOError
|
||||
end
|
||||
end
|
||||
found
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,200 @@
|
||||
#include <string.h>
|
||||
#include <mruby.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/i2c.h>
|
||||
|
||||
#define STACK_BUF_SIZE 256
|
||||
#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError))
|
||||
|
||||
static size_t
|
||||
i2c_fill_buf(mrb_state *mrb, uint8_t *buf, mrb_value *args, mrb_int argc)
|
||||
{
|
||||
size_t pos = 0;
|
||||
for (mrb_int i = 0; i < argc; i++) {
|
||||
switch (mrb_type(args[i])) {
|
||||
case MRB_TT_ARRAY: {
|
||||
mrb_int alen = RARRAY_LEN(args[i]);
|
||||
const mrb_value *aptr = RARRAY_PTR(args[i]);
|
||||
for (mrb_int j = 0; j < alen; j++) {
|
||||
if (!mrb_integer_p(aptr[j])) {
|
||||
mrb_raise(mrb, E_TYPE_ERROR, "array element must be Integer");
|
||||
}
|
||||
buf[pos++] = (uint8_t)mrb_integer(aptr[j]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MRB_TT_INTEGER:
|
||||
buf[pos++] = (uint8_t)mrb_integer(args[i]);
|
||||
break;
|
||||
case MRB_TT_STRING:
|
||||
memcpy(&buf[pos], RSTRING_PTR(args[i]), RSTRING_LEN(args[i]));
|
||||
pos += RSTRING_LEN(args[i]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
static size_t
|
||||
i2c_calc_size(mrb_state *mrb, mrb_value *args, mrb_int argc)
|
||||
{
|
||||
size_t total = 0;
|
||||
for (mrb_int i = 0; i < argc; i++) {
|
||||
switch (mrb_type(args[i])) {
|
||||
case MRB_TT_ARRAY:
|
||||
total += RARRAY_LEN(args[i]);
|
||||
break;
|
||||
case MRB_TT_INTEGER:
|
||||
total += 1;
|
||||
break;
|
||||
case MRB_TT_STRING:
|
||||
total += RSTRING_LEN(args[i]);
|
||||
break;
|
||||
default:
|
||||
mrb_raise(mrb, E_TYPE_ERROR, "Integer, Array, or String expected");
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/* Allocate write buffer, fill it, return pointer and size.
|
||||
Caller must free if need_free is set. */
|
||||
static uint8_t*
|
||||
i2c_build_buf(mrb_state *mrb, mrb_value *args, mrb_int argc,
|
||||
size_t *out_len, uint8_t *sbuf, mrb_bool *need_free)
|
||||
{
|
||||
size_t total = i2c_calc_size(mrb, args, argc);
|
||||
uint8_t *buf;
|
||||
if (total <= STACK_BUF_SIZE) {
|
||||
buf = sbuf;
|
||||
*need_free = FALSE;
|
||||
}
|
||||
else {
|
||||
buf = (uint8_t*)mrb_malloc(mrb, total);
|
||||
*need_free = TRUE;
|
||||
}
|
||||
i2c_fill_buf(mrb, buf, args, argc);
|
||||
*out_len = total;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static mrb_int
|
||||
get_timeout(mrb_state *mrb, mrb_value self, mrb_value kw)
|
||||
{
|
||||
if (mrb_undef_p(kw)) {
|
||||
return mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(timeout)));
|
||||
}
|
||||
return mrb_integer(kw);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_i2c_m_write(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value *args;
|
||||
mrb_int argc, addr;
|
||||
const mrb_sym kw_names[] = { MRB_SYM(timeout) };
|
||||
mrb_value kw_values[1];
|
||||
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
|
||||
|
||||
mrb_get_args(mrb, "i*:", &addr, &args, &argc, &kwargs);
|
||||
|
||||
mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]);
|
||||
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||
|
||||
uint8_t sbuf[STACK_BUF_SIZE];
|
||||
size_t wlen;
|
||||
mrb_bool need_free;
|
||||
uint8_t *buf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free);
|
||||
|
||||
int ret = mrb_i2c_write((int)unit, (uint8_t)addr, buf, wlen,
|
||||
(uint32_t)timeout_ms * 1000);
|
||||
if (need_free) mrb_free(mrb, buf);
|
||||
if (ret < 0) {
|
||||
mrb_raise(mrb, E_IO_ERROR, "I2C write failed");
|
||||
}
|
||||
return mrb_fixnum_value(ret);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_i2c_m_read(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value *args;
|
||||
mrb_int argc, addr, len;
|
||||
const mrb_sym kw_names[] = { MRB_SYM(timeout) };
|
||||
mrb_value kw_values[1];
|
||||
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
|
||||
|
||||
mrb_get_args(mrb, "ii*:", &addr, &len, &args, &argc, &kwargs);
|
||||
|
||||
if (len <= 0) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "read length must be positive");
|
||||
}
|
||||
|
||||
mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]);
|
||||
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||
uint32_t timeout_us = (uint32_t)timeout_ms * 1000;
|
||||
|
||||
uint8_t *rxbuf = (uint8_t*)mrb_malloc(mrb, len);
|
||||
int ret;
|
||||
|
||||
if (argc > 0) {
|
||||
/* write-then-read (repeated START) */
|
||||
uint8_t sbuf[STACK_BUF_SIZE];
|
||||
size_t wlen;
|
||||
mrb_bool need_free;
|
||||
uint8_t *wbuf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free);
|
||||
|
||||
ret = mrb_i2c_write_read((int)unit, (uint8_t)addr,
|
||||
wbuf, wlen, rxbuf, (size_t)len, timeout_us);
|
||||
if (need_free) mrb_free(mrb, wbuf);
|
||||
}
|
||||
else {
|
||||
ret = mrb_i2c_read((int)unit, (uint8_t)addr, rxbuf, (size_t)len, timeout_us);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
mrb_free(mrb, rxbuf);
|
||||
mrb_raise(mrb, E_IO_ERROR, "I2C read failed");
|
||||
}
|
||||
mrb_value str = mrb_str_new(mrb, (const char*)rxbuf, ret);
|
||||
mrb_free(mrb, rxbuf);
|
||||
return str;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_i2c_m_init(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
const char *unit;
|
||||
mrb_int freq, sda, scl;
|
||||
|
||||
mrb_get_args(mrb, "ziii", &unit, &freq, &sda, &scl);
|
||||
|
||||
int num = mrb_i2c_unit_name_to_num(unit);
|
||||
if (num < 0) {
|
||||
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown I2C unit: %s", unit);
|
||||
}
|
||||
mrb_i2c_status st = mrb_i2c_init(num, (uint32_t)freq, (int8_t)sda, (int8_t)scl);
|
||||
if (st != MRB_I2C_OK) {
|
||||
mrb_raise(mrb, E_IO_ERROR, "I2C init failed");
|
||||
}
|
||||
return mrb_fixnum_value(num);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hw_i2c_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(I2C), mrb->object_class);
|
||||
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_i2c_m_init, MRB_ARGS_REQ(4));
|
||||
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_i2c_m_write, MRB_ARGS_REQ(1)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
|
||||
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_i2c_m_read, MRB_ARGS_REQ(2)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
|
||||
}
|
||||
|
||||
void
|
||||
mrb_hw_i2c_gem_final(mrb_state *mrb)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
MRuby::Gem::Specification.new('hw-rp2040-i2c') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'mruby developers'
|
||||
spec.summary = 'I2C HAL for RP2040'
|
||||
|
||||
spec.add_dependency 'hw-i2c'
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/i2c.h"
|
||||
#include <mruby/i2c.h>
|
||||
|
||||
#define UNIT_SELECT(u) \
|
||||
i2c_inst_t *inst; \
|
||||
switch (u) { \
|
||||
case 0: inst = i2c0; break; \
|
||||
case 1: inst = i2c1; break; \
|
||||
default: return MRB_I2C_ERROR_UNIT; \
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_unit_name_to_num(const char *name)
|
||||
{
|
||||
if (strcmp(name, "RP2040_I2C0") == 0) return 0;
|
||||
if (strcmp(name, "RP2040_I2C1") == 0) return 1;
|
||||
return MRB_I2C_ERROR_UNIT;
|
||||
}
|
||||
|
||||
mrb_i2c_status
|
||||
mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl)
|
||||
{
|
||||
UNIT_SELECT(unit);
|
||||
i2c_init(inst, freq);
|
||||
|
||||
if (sda < 0) sda = PICO_DEFAULT_I2C_SDA_PIN;
|
||||
if (scl < 0) scl = PICO_DEFAULT_I2C_SCL_PIN;
|
||||
gpio_set_function(sda, GPIO_FUNC_I2C);
|
||||
gpio_set_function(scl, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(sda);
|
||||
gpio_pull_up(scl);
|
||||
|
||||
return MRB_I2C_OK;
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
|
||||
uint32_t timeout_us)
|
||||
{
|
||||
UNIT_SELECT(unit);
|
||||
return i2c_read_timeout_us(inst, addr, dst, len, false, timeout_us);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
|
||||
uint32_t timeout_us)
|
||||
{
|
||||
UNIT_SELECT(unit);
|
||||
return i2c_write_timeout_us(inst, addr, src, len, false, timeout_us);
|
||||
}
|
||||
|
||||
int
|
||||
mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
|
||||
uint8_t *dst, size_t rlen, uint32_t timeout_us)
|
||||
{
|
||||
UNIT_SELECT(unit);
|
||||
/* write with nostop=true (no STOP, keeps bus for repeated START) */
|
||||
int ret = i2c_write_timeout_us(inst, addr, src, wlen, true, timeout_us);
|
||||
if (ret < 0) return ret;
|
||||
/* read with nostop=false (STOP after read) */
|
||||
return i2c_read_timeout_us(inst, addr, dst, rlen, false, timeout_us);
|
||||
}
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
void mrb_hw_rp2040_i2c_gem_init(mrb_state *mrb) {}
|
||||
void mrb_hw_rp2040_i2c_gem_final(mrb_state *mrb) {}
|
||||
Reference in New Issue
Block a user