mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
hw-uart: add UART peripheral gems for embedded platforms
Add three new gems for UART serial communication: - hw-uart: common Ruby API, C bindings, ring buffer, and HAL header - hw-esp32-uart: ESP32 HAL using ESP-IDF UART driver with FreeRTOS RX task - hw-rp2040-uart: RP2040 HAL using Pico SDK with IRQ-driven RX Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,152 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/uart.h"
|
||||
#include <mruby/uart.h>
|
||||
|
||||
#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 <mruby.h>
|
||||
|
||||
void mrb_hw_esp32_uart_gem_init(mrb_state *mrb) {}
|
||||
void mrb_hw_esp32_uart_gem_final(mrb_state *mrb) {}
|
||||
@@ -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
|
||||
@@ -0,0 +1,141 @@
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/uart.h"
|
||||
#include "hardware/irq.h"
|
||||
#include <mruby/uart.h>
|
||||
|
||||
#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 <mruby.h>
|
||||
|
||||
void mrb_hw_rp2040_uart_gem_init(mrb_state *mrb) {}
|
||||
void mrb_hw_rp2040_uart_gem_final(mrb_state *mrb) {}
|
||||
@@ -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
|
||||
`<mruby/uart.h>`:
|
||||
|
||||
```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
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef MRUBY_UART_H
|
||||
#define MRUBY_UART_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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-<platform>-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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <mruby/uart.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/data.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/uart.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user