diff --git a/mrbgems/hw-esp32-uart/mrbgem.rake b/mrbgems/hw-esp32-uart/mrbgem.rake new file mode 100644 index 000000000..f96d5f083 --- /dev/null +++ b/mrbgems/hw-esp32-uart/mrbgem.rake @@ -0,0 +1,7 @@ +MRuby::Gem::Specification.new('hw-esp32-uart') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'UART HAL for ESP32' + + spec.add_dependency 'hw-uart' +end diff --git a/mrbgems/hw-esp32-uart/src/uart.c b/mrbgems/hw-esp32-uart/src/uart.c new file mode 100644 index 000000000..7d63f1575 --- /dev/null +++ b/mrbgems/hw-esp32-uart/src/uart.c @@ -0,0 +1,152 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "driver/uart.h" +#include + +#define RX_TASK_BUF_SIZE 128 +#define QUEUE_LENGTH 20 +#define TASK_STACK_SIZE 4096 +#define TASK_PRIORITY 12 + +typedef struct { + int unit; + QueueHandle_t queue; + mrb_uart_ringbuf *rxbuf; +} uart_ctx; + +static uart_ctx ctx[UART_NUM_MAX]; + +static void +rx_task(void *arg) +{ + uart_ctx *c = (uart_ctx*)arg; + uart_event_t event; + uint8_t buf[RX_TASK_BUF_SIZE]; + + for (;;) { + if (xQueueReceive(c->queue, &event, portMAX_DELAY)) { + if (event.type == UART_DATA) { + size_t n = event.size > RX_TASK_BUF_SIZE ? RX_TASK_BUF_SIZE : event.size; + uart_read_bytes(c->unit, buf, n, portMAX_DELAY); + for (size_t i = 0; i < n; i++) { + mrb_uart_ringbuf_push(c->rxbuf, buf[i]); + } + } + } + } +} + +int +mrb_uart_unit_name_to_num(const char *name) +{ + if (strcmp(name, "ESP32_UART0") == 0) return UART_NUM_0; + if (strcmp(name, "ESP32_UART1") == 0) return UART_NUM_1; +#ifdef UART_NUM_2 + if (strcmp(name, "ESP32_UART2") == 0) return UART_NUM_2; +#endif + return MRB_UART_ERROR_UNIT; +} + +mrb_uart_status +mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf) +{ + if (unit < 0 || unit >= UART_NUM_MAX) return MRB_UART_ERROR_UNIT; + + uart_config_t cfg = { + .baud_rate = 9600, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + + int bufsize = (rxbuf->mask + 1); + uart_driver_install(unit, bufsize, 0, QUEUE_LENGTH, &ctx[unit].queue, 0); + uart_param_config(unit, &cfg); + uart_set_pin(unit, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + + ctx[unit].unit = unit; + ctx[unit].rxbuf = rxbuf; + + char name[32]; + snprintf(name, sizeof(name), "uart_rx_%d", unit); + xTaskCreate(rx_task, name, TASK_STACK_SIZE, &ctx[unit], TASK_PRIORITY, NULL); + + return MRB_UART_OK; +} + +uint32_t +mrb_uart_set_baudrate(int unit, uint32_t baudrate) +{ + uart_set_baudrate(unit, baudrate); + return baudrate; +} + +void +mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits, + uint8_t parity) +{ + static const uart_word_length_t wl[] = { + UART_DATA_5_BITS, UART_DATA_6_BITS, UART_DATA_7_BITS, UART_DATA_8_BITS + }; + static const uart_stop_bits_t sb[] = { + UART_STOP_BITS_1, UART_STOP_BITS_2 + }; + static const uart_parity_t pr[] = { + UART_PARITY_DISABLE, UART_PARITY_EVEN, UART_PARITY_ODD + }; + if (data_bits >= 5 && data_bits <= 8) + uart_set_word_length(unit, wl[data_bits - 5]); + if (stop_bits >= 1 && stop_bits <= 2) + uart_set_stop_bits(unit, sb[stop_bits - 1]); + if (parity <= 2) + uart_set_parity(unit, pr[parity]); +} + +void +mrb_uart_set_flow_control(int unit, bool cts, bool rts) +{ + uart_hw_flowcontrol_t mode = UART_HW_FLOWCTRL_DISABLE; + if (cts && rts) mode = UART_HW_FLOWCTRL_CTS_RTS; + else if (cts) mode = UART_HW_FLOWCTRL_CTS; + else if (rts) mode = UART_HW_FLOWCTRL_RTS; + uart_set_hw_flow_ctrl(unit, mode, 122); +} + +void +mrb_uart_write(int unit, const uint8_t *src, size_t len) +{ + uart_write_bytes(unit, (const char*)src, len); +} + +void +mrb_uart_flush(int unit) +{ + uart_wait_tx_done(unit, 100); +} + +void +mrb_uart_send_break(int unit, uint32_t duration_ms) +{ + uart_write_bytes_with_break(unit, NULL, 0, duration_ms); +} + +void +mrb_uart_clear_rx(int unit) +{ + uart_flush_input(unit); +} + +void +mrb_uart_clear_tx(int unit) +{ + /* not supported on ESP-IDF */ +} + +#include + +void mrb_hw_esp32_uart_gem_init(mrb_state *mrb) {} +void mrb_hw_esp32_uart_gem_final(mrb_state *mrb) {} diff --git a/mrbgems/hw-rp2040-uart/mrbgem.rake b/mrbgems/hw-rp2040-uart/mrbgem.rake new file mode 100644 index 000000000..0e56e581f --- /dev/null +++ b/mrbgems/hw-rp2040-uart/mrbgem.rake @@ -0,0 +1,7 @@ +MRuby::Gem::Specification.new('hw-rp2040-uart') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'UART HAL for RP2040' + + spec.add_dependency 'hw-uart' +end diff --git a/mrbgems/hw-rp2040-uart/src/uart.c b/mrbgems/hw-rp2040-uart/src/uart.c new file mode 100644 index 000000000..f8277d96a --- /dev/null +++ b/mrbgems/hw-rp2040-uart/src/uart.c @@ -0,0 +1,141 @@ +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/uart.h" +#include "hardware/irq.h" +#include + +#define UNIT_SELECT(u) \ + uart_inst_t *inst; \ + switch (u) { \ + case 0: inst = uart0; break; \ + case 1: inst = uart1; break; \ + default: return MRB_UART_ERROR_UNIT; \ + } + +/* void-returning variant for functions that can't return error */ +#define UNIT_SELECT_V(u) \ + uart_inst_t *inst; \ + switch (u) { \ + case 0: inst = uart0; break; \ + case 1: inst = uart1; break; \ + default: return; \ + } + +static mrb_uart_ringbuf *rx_bufs[2]; + +static void +on_uart0_rx(void) +{ + while (uart_is_readable(uart0)) { + mrb_uart_ringbuf_push(rx_bufs[0], uart_getc(uart0)); + } +} + +static void +on_uart1_rx(void) +{ + while (uart_is_readable(uart1)) { + mrb_uart_ringbuf_push(rx_bufs[1], uart_getc(uart1)); + } +} + +int +mrb_uart_unit_name_to_num(const char *name) +{ + if (strcmp(name, "RP2040_UART0") == 0) return 0; + if (strcmp(name, "RP2040_UART1") == 0) return 1; + return MRB_UART_ERROR_UNIT; +} + +mrb_uart_status +mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf) +{ + UNIT_SELECT(unit); + uart_init(inst, 9600); + + gpio_set_function(tx_pin, GPIO_FUNC_UART); + gpio_set_function(rx_pin, GPIO_FUNC_UART); + + rx_bufs[unit] = rxbuf; + + uint irq; + if (unit == 0) { + irq = UART0_IRQ; + irq_set_exclusive_handler(irq, on_uart0_rx); + } + else { + irq = UART1_IRQ; + irq_set_exclusive_handler(irq, on_uart1_rx); + } + irq_set_enabled(irq, true); + uart_set_irq_enables(inst, true, false); + + return MRB_UART_OK; +} + +uint32_t +mrb_uart_set_baudrate(int unit, uint32_t baudrate) +{ + UNIT_SELECT(unit); + return uart_set_baudrate(inst, baudrate); +} + +void +mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits, + uint8_t parity) +{ + UNIT_SELECT_V(unit); + uart_set_format(inst, data_bits, stop_bits, (uart_parity_t)parity); +} + +void +mrb_uart_set_flow_control(int unit, bool cts, bool rts) +{ + UNIT_SELECT_V(unit); + uart_set_hw_flow(inst, cts, rts); +} + +void +mrb_uart_write(int unit, const uint8_t *src, size_t len) +{ + UNIT_SELECT_V(unit); + uart_write_blocking(inst, src, len); +} + +void +mrb_uart_flush(int unit) +{ + UNIT_SELECT_V(unit); + uart_tx_wait_blocking(inst); +} + +void +mrb_uart_send_break(int unit, uint32_t duration_ms) +{ + UNIT_SELECT_V(unit); + uart_set_break(inst, true); + sleep_ms(duration_ms); + uart_set_break(inst, false); +} + +void +mrb_uart_clear_rx(int unit) +{ + UNIT_SELECT_V(unit); + while (uart_is_readable(inst)) { + uart_getc(inst); + } +} + +void +mrb_uart_clear_tx(int unit) +{ + /* not supported on RP2040 */ +} + +#include + +void mrb_hw_rp2040_uart_gem_init(mrb_state *mrb) {} +void mrb_hw_rp2040_uart_gem_final(mrb_state *mrb) {} diff --git a/mrbgems/hw-uart/README.md b/mrbgems/hw-uart/README.md new file mode 100644 index 000000000..d174bbd89 --- /dev/null +++ b/mrbgems/hw-uart/README.md @@ -0,0 +1,182 @@ +# hw-uart - UART peripheral interface for mruby + +This gem provides the `UART` class for serial communication from mruby. +It is designed for embedded platforms such as ESP32 and RP2040. + +## Architecture + +- **hw-uart** (this gem) - Ruby API, C bindings, ring buffer, and HAL + function declarations +- **hw-esp32-uart** - HAL implementation for ESP32 (using ESP-IDF UART + driver with FreeRTOS task for RX) +- **hw-rp2040-uart** - HAL implementation for RP2040 (using Pico SDK + with IRQ-driven RX) + +Received data is buffered in a ring buffer (allocated by the common +gem) that the platform HAL populates via interrupt or task. The ring +buffer size must be a power of two. + +## Build Configuration + +```ruby +# For ESP32 +MRuby::CrossBuild.new('esp32') do |conf| + # ... + conf.gem "#{root}/mrbgems/hw-esp32-uart" +end + +# For RP2040 +MRuby::CrossBuild.new('rp2040') do |conf| + # ... + conf.gem "#{root}/mrbgems/hw-rp2040-uart" +end +``` + +## Ruby API + +### Constants + +| Constant | Value | Description | +|-------------------------------|-------|---------------------| +| `UART::PARITY_NONE` | `0` | No parity | +| `UART::PARITY_EVEN` | `1` | Even parity | +| `UART::PARITY_ODD` | `2` | Odd parity | +| `UART::FLOW_CONTROL_NONE` | `0` | No flow control | +| `UART::FLOW_CONTROL_RTS_CTS` | `1` | Hardware flow ctrl | + +### UART.new + +```ruby +uart = UART.new( + unit: :ESP32_UART1, # UART unit name (required) + tx_pin: 17, # TX GPIO pin (default: -1) + rx_pin: 16, # RX GPIO pin (default: -1) + baudrate: 9600, # baud rate (default: 9600) + data_bits: 8, # 5-8 (default: 8) + stop_bits: 1, # 1-2 (default: 1) + parity: UART::PARITY_NONE, + flow_control: UART::FLOW_CONTROL_NONE, + rx_buffer_size: 256 # must be power of two (default: 256) +) +``` + +#### Unit Names + +| Platform | Available Units | +|----------|--------------------------------------------------| +| ESP32 | `:ESP32_UART0`, `:ESP32_UART1`, `:ESP32_UART2`\* | +| RP2040 | `:RP2040_UART0`, `:RP2040_UART1` | + +\*UART2 availability depends on ESP32 variant. + +### Instance Methods + +#### UART#write(str) + +Write a string to the UART. Returns number of bytes written. + +```ruby +uart.write("Hello\r\n") +``` + +#### UART#read(len = nil) + +Read from the RX buffer. Returns `nil` if no data is available. + +- Without argument: returns all available data +- With `len`: returns exactly `len` bytes, or `nil` if fewer are + available + +```ruby +data = uart.read # all available +data = uart.read(10) # exactly 10 bytes or nil +``` + +#### UART#readpartial(maxlen) + +Read up to `maxlen` bytes from the RX buffer. Returns `nil` if empty. + +```ruby +data = uart.readpartial(64) +``` + +#### UART#gets + +Read a line (up to and including `"\n"`). Returns `nil` if no +complete line is available. + +```ruby +line = uart.gets +``` + +#### UART#bytes_available + +Returns the number of bytes in the RX buffer. + +```ruby +n = uart.bytes_available +``` + +#### UART#puts(str) + +Write string with line ending appended (if not already present). + +```ruby +uart.puts("Hello") # writes "Hello\n" +``` + +#### UART#flush + +Wait for all TX data to be sent. + +#### UART#clear_rx_buffer / UART#clear_tx_buffer + +Discard buffered data. + +#### UART#send_break(duration_ms = 100) + +Send a UART break signal for the specified duration. + +#### UART#setmode(baudrate:, data_bits:, stop_bits:, parity:, flow_control:) + +Reconfigure UART parameters after initialization. All parameters are +optional. + +#### UART#baudrate + +Returns the current baud rate. + +#### UART#line_ending=(ending) + +Set the line ending used by `puts`. Must be `"\n"`, `"\r"`, or +`"\r\n"`. + +## HAL Interface + +To add support for a new platform, create a gem that depends on +`hw-uart` and implements the following C functions declared in +``: + +```c +int mrb_uart_unit_name_to_num(const char *name); +mrb_uart_status mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf); +uint32_t mrb_uart_set_baudrate(int unit, uint32_t baudrate); +void mrb_uart_set_format(int unit, uint32_t data_bits, + uint32_t stop_bits, uint8_t parity); +void mrb_uart_set_flow_control(int unit, bool cts, bool rts); +void mrb_uart_write(int unit, const uint8_t *src, size_t len); +void mrb_uart_flush(int unit); +void mrb_uart_send_break(int unit, uint32_t duration_ms); +void mrb_uart_clear_rx(int unit); +void mrb_uart_clear_tx(int unit); +``` + +The `rxbuf` parameter passed to `mrb_uart_init` is a ring buffer +allocated by the common gem. The platform must arrange for received +bytes to be pushed into it using `mrb_uart_ringbuf_push()` (e.g., +from an interrupt handler or RTOS task). + +## License + +MIT diff --git a/mrbgems/hw-uart/include/mruby/uart.h b/mrbgems/hw-uart/include/mruby/uart.h new file mode 100644 index 000000000..d38c2052b --- /dev/null +++ b/mrbgems/hw-uart/include/mruby/uart.h @@ -0,0 +1,60 @@ +#ifndef MRUBY_UART_H +#define MRUBY_UART_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MRB_UART_PARITY_NONE 0 +#define MRB_UART_PARITY_EVEN 1 +#define MRB_UART_PARITY_ODD 2 + +#define MRB_UART_FLOW_NONE 0 +#define MRB_UART_FLOW_RTS_CTS 1 + +typedef enum { + MRB_UART_OK = 0, + MRB_UART_ERROR_UNIT = -1, +} mrb_uart_status; + +/* Ring buffer for interrupt-driven RX. + Allocated by common gem, populated by platform interrupt handler. + size must be a power of two. */ +typedef struct { + volatile int head; + volatile int tail; + int mask; + uint8_t data[]; +} mrb_uart_ringbuf; + +/* Ring buffer helpers (implemented in hw-uart/src/ringbuf.c) */ +bool mrb_uart_ringbuf_init(mrb_uart_ringbuf *rb, int size); +bool mrb_uart_ringbuf_push(mrb_uart_ringbuf *rb, uint8_t ch); +int mrb_uart_ringbuf_pop(mrb_uart_ringbuf *rb, uint8_t *dst, int len); +int mrb_uart_ringbuf_available(const mrb_uart_ringbuf *rb); +void mrb_uart_ringbuf_clear(mrb_uart_ringbuf *rb); +int mrb_uart_ringbuf_search(const mrb_uart_ringbuf *rb, uint8_t ch); + +/* HAL functions - implemented by hw--uart gems */ +int mrb_uart_unit_name_to_num(const char *name); +mrb_uart_status mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf); +uint32_t mrb_uart_set_baudrate(int unit, uint32_t baudrate); +void mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits, + uint8_t parity); +void mrb_uart_set_flow_control(int unit, bool cts, bool rts); +void mrb_uart_write(int unit, const uint8_t *src, size_t len); +void mrb_uart_flush(int unit); +void mrb_uart_send_break(int unit, uint32_t duration_ms); +void mrb_uart_clear_rx(int unit); +void mrb_uart_clear_tx(int unit); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_UART_H */ diff --git a/mrbgems/hw-uart/mrbgem.rake b/mrbgems/hw-uart/mrbgem.rake new file mode 100644 index 000000000..fe758f757 --- /dev/null +++ b/mrbgems/hw-uart/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-uart') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'UART peripheral interface' +end diff --git a/mrbgems/hw-uart/mrblib/uart.rb b/mrbgems/hw-uart/mrblib/uart.rb new file mode 100644 index 000000000..34a1b44b4 --- /dev/null +++ b/mrbgems/hw-uart/mrblib/uart.rb @@ -0,0 +1,54 @@ +class UART + PARITY_NONE = 0 + PARITY_EVEN = 1 + PARITY_ODD = 2 + FLOW_CONTROL_NONE = 0 + FLOW_CONTROL_RTS_CTS = 1 + + attr_reader :baudrate + + def initialize(unit:, tx_pin: -1, rx_pin: -1, baudrate: 9600, + data_bits: 8, stop_bits: 1, parity: PARITY_NONE, + flow_control: FLOW_CONTROL_NONE, rx_buffer_size: 256) + __open_rx_buffer(rx_buffer_size) + @unit_num = __open_connection(unit.to_s, tx_pin, rx_pin) + @baudrate = __set_baudrate(baudrate) + __set_format(data_bits, stop_bits, parity) + set_flow_control(flow_control) + @line_ending = "\n" + end + + def setmode(baudrate: nil, data_bits: nil, stop_bits: nil, + parity: nil, flow_control: nil) + @baudrate = __set_baudrate(baudrate) if baudrate + __set_format(data_bits || 8, stop_bits || 1, parity || PARITY_NONE) + set_flow_control(flow_control || FLOW_CONTROL_NONE) + self + end + + def line_ending=(ending) + unless ["\n", "\r", "\r\n"].include?(ending) + raise ArgumentError, "invalid line ending" + end + @line_ending = ending + end + + def puts(str) + write str + write @line_ending unless str.end_with?(@line_ending) + nil + end + + private + + def set_flow_control(mode) + case mode + when FLOW_CONTROL_NONE + __set_flow_control(false, false) + when FLOW_CONTROL_RTS_CTS + __set_flow_control(true, true) + else + raise ArgumentError, "invalid flow control mode" + end + end +end diff --git a/mrbgems/hw-uart/src/ringbuf.c b/mrbgems/hw-uart/src/ringbuf.c new file mode 100644 index 000000000..a67e324e6 --- /dev/null +++ b/mrbgems/hw-uart/src/ringbuf.c @@ -0,0 +1,59 @@ +#include + +bool +mrb_uart_ringbuf_init(mrb_uart_ringbuf *rb, int size) +{ + /* size must be a power of two */ + if (size <= 0 || (size & (size - 1)) != 0) return false; + rb->head = 0; + rb->tail = 0; + rb->mask = size - 1; + return true; +} + +bool +mrb_uart_ringbuf_push(mrb_uart_ringbuf *rb, uint8_t ch) +{ + int next = (rb->head + 1) & rb->mask; + if (next == rb->tail) return false; /* full */ + rb->data[rb->head] = ch; + rb->head = next; + return true; +} + +int +mrb_uart_ringbuf_pop(mrb_uart_ringbuf *rb, uint8_t *dst, int len) +{ + int i; + for (i = 0; i < len; i++) { + if (rb->tail == rb->head) break; /* empty */ + dst[i] = rb->data[rb->tail]; + rb->tail = (rb->tail + 1) & rb->mask; + } + return i; +} + +int +mrb_uart_ringbuf_available(const mrb_uart_ringbuf *rb) +{ + return (rb->head - rb->tail) & rb->mask; +} + +void +mrb_uart_ringbuf_clear(mrb_uart_ringbuf *rb) +{ + rb->tail = rb->head; +} + +int +mrb_uart_ringbuf_search(const mrb_uart_ringbuf *rb, uint8_t ch) +{ + int pos = rb->tail; + int i = 0; + while (pos != rb->head) { + if (rb->data[pos] == ch) return i; + pos = (pos + 1) & rb->mask; + i++; + } + return -1; +} diff --git a/mrbgems/hw-uart/src/uart.c b/mrbgems/hw-uart/src/uart.c new file mode 100644 index 000000000..139479a55 --- /dev/null +++ b/mrbgems/hw-uart/src/uart.c @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include +#include + +#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError)) + +#define DEFAULT_RX_BUF_SIZE 256 + +static void +rxbuf_free(mrb_state *mrb, void *ptr) +{ + mrb_free(mrb, ptr); +} + +static const struct mrb_data_type rxbuf_type = { "UART", rxbuf_free }; + +/* UART#__open_rx_buffer(size) */ +static mrb_value +mrb_uart_m_open_rxbuf(mrb_state *mrb, mrb_value self) +{ + mrb_int size; + mrb_get_args(mrb, "i", &size); + if (size <= 0) size = DEFAULT_RX_BUF_SIZE; + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_malloc(mrb, + sizeof(mrb_uart_ringbuf) + sizeof(uint8_t) * size); + if (!mrb_uart_ringbuf_init(rb, (int)size)) { + mrb_free(mrb, rb); + mrb_raise(mrb, E_ARGUMENT_ERROR, "rx_buffer_size must be a power of two"); + } + DATA_PTR(self) = rb; + DATA_TYPE(self) = &rxbuf_type; + return mrb_nil_value(); +} + +/* UART#__open_connection(unit_name, tx_pin, rx_pin) */ +static mrb_value +mrb_uart_m_open_conn(mrb_state *mrb, mrb_value self) +{ + const char *name; + mrb_int tx_pin, rx_pin; + mrb_get_args(mrb, "zii", &name, &tx_pin, &rx_pin); + + int num = mrb_uart_unit_name_to_num(name); + if (num < 0) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown UART unit: %s", name); + } + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + mrb_uart_status st = mrb_uart_init(num, (uint32_t)tx_pin, (uint32_t)rx_pin, rb); + if (st != MRB_UART_OK) { + mrb_raise(mrb, E_IO_ERROR, "UART init failed"); + } + return mrb_fixnum_value(num); +} + +/* UART#__set_baudrate(baud) */ +static mrb_value +mrb_uart_m_set_baudrate(mrb_state *mrb, mrb_value self) +{ + mrb_int baud; + mrb_get_args(mrb, "i", &baud); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + uint32_t actual = mrb_uart_set_baudrate((int)unit, (uint32_t)baud); + return mrb_fixnum_value(actual); +} + +/* UART#__set_format(data_bits, stop_bits, parity) */ +static mrb_value +mrb_uart_m_set_format(mrb_state *mrb, mrb_value self) +{ + mrb_int data_bits, stop_bits, parity; + mrb_get_args(mrb, "iii", &data_bits, &stop_bits, &parity); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_set_format((int)unit, (uint32_t)data_bits, (uint32_t)stop_bits, (uint8_t)parity); + return mrb_nil_value(); +} + +/* UART#__set_flow_control(cts, rts) */ +static mrb_value +mrb_uart_m_set_flow(mrb_state *mrb, mrb_value self) +{ + mrb_bool cts, rts; + mrb_get_args(mrb, "bb", &cts, &rts); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_set_flow_control((int)unit, cts, rts); + return mrb_nil_value(); +} + +/* UART#write(str) */ +static mrb_value +mrb_uart_m_write(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_get_args(mrb, "S", &str); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + size_t len = RSTRING_LEN(str); + mrb_uart_write((int)unit, (const uint8_t*)RSTRING_PTR(str), len); + return mrb_fixnum_value(len); +} + +/* UART#read(len=nil) */ +static mrb_value +mrb_uart_m_read(mrb_state *mrb, mrb_value self) +{ + mrb_int len = -1; + mrb_get_args(mrb, "|i", &len); + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + int avail = mrb_uart_ringbuf_available(rb); + if (avail == 0) return mrb_nil_value(); + + if (len >= 0) { + if (avail < len) return mrb_nil_value(); + avail = (int)len; + } + + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, avail); + int n = mrb_uart_ringbuf_pop(rb, buf, avail); + mrb_value str = mrb_str_new(mrb, (const char*)buf, n); + mrb_free(mrb, buf); + return str; +} + +/* UART#readpartial(maxlen) */ +static mrb_value +mrb_uart_m_readpartial(mrb_state *mrb, mrb_value self) +{ + mrb_int maxlen; + mrb_get_args(mrb, "i", &maxlen); + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + int avail = mrb_uart_ringbuf_available(rb); + if (avail == 0) return mrb_nil_value(); + if (avail > maxlen) avail = (int)maxlen; + + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, avail); + int n = mrb_uart_ringbuf_pop(rb, buf, avail); + mrb_value str = mrb_str_new(mrb, (const char*)buf, n); + mrb_free(mrb, buf); + return str; +} + +/* UART#bytes_available */ +static mrb_value +mrb_uart_m_bytes_available(mrb_state *mrb, mrb_value self) +{ + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + return mrb_fixnum_value(mrb_uart_ringbuf_available(rb)); +} + +/* UART#gets */ +static mrb_value +mrb_uart_m_gets(mrb_state *mrb, mrb_value self) +{ + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + int pos = mrb_uart_ringbuf_search(rb, (uint8_t)'\n'); + if (pos < 0) return mrb_nil_value(); + int len = pos + 1; + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, len); + mrb_uart_ringbuf_pop(rb, buf, len); + mrb_value str = mrb_str_new(mrb, (const char*)buf, len); + mrb_free(mrb, buf); + return str; +} + +/* UART#flush */ +static mrb_value +mrb_uart_m_flush(mrb_state *mrb, mrb_value self) +{ + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_flush((int)unit); + return self; +} + +/* UART#clear_tx_buffer */ +static mrb_value +mrb_uart_m_clear_tx(mrb_state *mrb, mrb_value self) +{ + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_clear_tx((int)unit); + return self; +} + +/* UART#clear_rx_buffer */ +static mrb_value +mrb_uart_m_clear_rx(mrb_state *mrb, mrb_value self) +{ + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + mrb_uart_ringbuf_clear(rb); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_clear_rx((int)unit); + return self; +} + +/* UART#send_break(duration_ms=100) */ +static mrb_value +mrb_uart_m_send_break(mrb_state *mrb, mrb_value self) +{ + mrb_int ms = 100; + mrb_get_args(mrb, "|i", &ms); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_send_break((int)unit, (uint32_t)ms); + return self; +} + +void +mrb_hw_uart_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(UART), mrb->object_class); + MRB_SET_INSTANCE_TT(cls, MRB_TT_CDATA); + + mrb_define_method_id(mrb, cls, MRB_SYM(__open_rx_buffer), mrb_uart_m_open_rxbuf, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(__open_connection), mrb_uart_m_open_conn, MRB_ARGS_REQ(3)); + mrb_define_method_id(mrb, cls, MRB_SYM(__set_baudrate), mrb_uart_m_set_baudrate, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(__set_format), mrb_uart_m_set_format, MRB_ARGS_REQ(3)); + mrb_define_method_id(mrb, cls, MRB_SYM(__set_flow_control), mrb_uart_m_set_flow, MRB_ARGS_REQ(2)); + mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_uart_m_write, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_uart_m_read, MRB_ARGS_OPT(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(readpartial), mrb_uart_m_readpartial, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(bytes_available), mrb_uart_m_bytes_available, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(gets), mrb_uart_m_gets, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(flush), mrb_uart_m_flush, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(clear_tx_buffer), mrb_uart_m_clear_tx, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(clear_rx_buffer), mrb_uart_m_clear_rx, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(send_break), mrb_uart_m_send_break, MRB_ARGS_OPT(1)); +} + +void +mrb_hw_uart_gem_final(mrb_state *mrb) +{ +}