diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index e771d8f7d3460..2489efadf34f3 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -82,6 +82,7 @@ endif MPY_CROSS_FLAGS += -march=xtensa SRC_C = \ + softuart.c \ strtoll.c \ main.c \ help.c \ @@ -99,6 +100,7 @@ SRC_C = \ machine_rtc.c \ machine_adc.c \ machine_uart.c \ + machine_softuart.c \ machine_wdt.c \ machine_hspi.c \ modesp.c \ diff --git a/ports/esp8266/boards/esp8266_common.ld b/ports/esp8266/boards/esp8266_common.ld index f40ff1e5fce14..d443345f61940 100644 --- a/ports/esp8266/boards/esp8266_common.ld +++ b/ports/esp8266/boards/esp8266_common.ld @@ -157,6 +157,8 @@ SECTIONS *machine_rtc.o(.literal*, .text*) *machine_adc.o(.literal*, .text*) *machine_uart.o(.literal*, .text*) + *softuart.o(.literal*, .text*) + *machine_softuart.o(.literal*, .text*) *modpybi2c.o(.literal*, .text*) *modmachine.o(.literal*, .text*) *machine_wdt.o(.literal*, .text*) diff --git a/ports/esp8266/examples/softuart_read.py b/ports/esp8266/examples/softuart_read.py new file mode 100644 index 0000000000000..84403bd031a37 --- /dev/null +++ b/ports/esp8266/examples/softuart_read.py @@ -0,0 +1,26 @@ +# test suite for SoftUART + +from machine import SoftUART, Pin +import time +import select + +poll = select.poll() + +s1 = SoftUART(Pin(2), Pin(4), baudrate=9600, timeout=0) # tx=2 rx=4 +s2 = SoftUART(Pin(0), Pin(5), baudrate=9600, timeout=0) # tx=0 rx=5 +poll.register(s1) +poll.register(s2) + +print("polling...") + +while True: + read = poll.ipoll() + + for r, ev in read: + if (ev & select.POLLIN) != 0: + i = None + if r is s1: + i = "s1" + else: + i = "s2" + print(i, r.read()) diff --git a/ports/esp8266/examples/softuart_write.py b/ports/esp8266/examples/softuart_write.py new file mode 100644 index 0000000000000..aaae716ba7049 --- /dev/null +++ b/ports/esp8266/examples/softuart_write.py @@ -0,0 +1,24 @@ +# test suite for SoftUART + +from machine import SoftUART, Pin +import time + +s1 = SoftUART(tx=Pin(12), rx=Pin(4), baudrate=9600, timeout=0) +s2 = SoftUART(tx=Pin(0), rx=Pin(5), baudrate=9600, timeout=0) + +# Change tx pin and baudrate +s1.init(tx=Pin(2), rx=Pin(4), baudrate=115200) + +c = 10 +while c > 0: + print("sending... ", end="") + s1.write("Hello world s1\r\n") + print("1, ", end="") + s2.write("Hello world s2\r\n") + print("2, done.") + time.sleep(1) + c -= 1 + +s1.deinit() +s2.deinit() +print("done") diff --git a/ports/esp8266/machine_softuart.c b/ports/esp8266/machine_softuart.c new file mode 100644 index 0000000000000..083d55167fec8 --- /dev/null +++ b/ports/esp8266/machine_softuart.c @@ -0,0 +1,332 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "ets_sys.h" +#include "mem.h" +#include "softuart.h" + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "py/misc.h" +#include "py/mphal.h" +#include "modmachine.h" + +typedef struct { + mp_obj_base_t base; + // uint8_t uart_id; + Softuart *softuart_ptr; // point to instance of driver object + pyb_pin_obj_t *tx; + pyb_pin_obj_t *rx; + uint8_t bits; + uint8_t parity; + uint8_t stop; + uint32_t baudrate; + uint16_t timeout; // timeout waiting for first char (in ms) + uint16_t timeout_char; // timeout waiting between chars (in ms) +} pyb_softuart_obj_t; + +STATIC const char *_parity_name[] = {"None", "ODD", "EVEN"}; + +STATIC uint8 verify_gpio_pin(mp_obj_t obj_pin) { + // currently not possible to be MP_OBJ_NULL (because required) + if (obj_pin == MP_OBJ_NULL || obj_pin == mp_const_none) { + return 0xFF; + } else { + uint pin = mp_obj_get_pin(mp_obj_get_pin_obj(obj_pin)); + if (Softuart_IsGpioValid(pin)) { + return pin; + } else { + return 0xFF; + } + } +} + +/******************************************************************************/ +// MicroPython bindings for softUART + +STATIC void pyb_softuart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + pyb_softuart_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SoftUART(tx=%u, rx=%u, baudrate=%u, bits=%u, parity=%s, stop=%u, timeout=%u, timeout_char=%u)", + mp_obj_get_pin(self->tx), mp_obj_get_pin(self->rx), + self->baudrate, self->bits, _parity_name[self->parity], + self->stop, self->timeout, self->timeout_char); +} + +STATIC void pyb_softuart_init_helper(pyb_softuart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_tx, ARG_rx, ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_timeout, ARG_timeout_char}; + static const mp_arg_t allowed_args[] = { + // TODO make tx-only/rx-only UART possible (by passing None) + { MP_QSTR_tx, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_rx, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 10} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // assign the pins + // TODO make tx-only/rx-only UART possible (by passing None) + uint8 tx_pin = verify_gpio_pin(args[ARG_tx].u_obj); + if (tx_pin != 0xFF) { + // valid tx pin + Softuart_SetPinTx(self->softuart_ptr, tx_pin); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid or no tx pin")); + } + + uint8 rx_pin = verify_gpio_pin(args[ARG_rx].u_obj); + if (rx_pin != 0xFF) { + Softuart_SetPinRx(self->softuart_ptr, rx_pin); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid or no rx pin")); + } + + // set baudrate (0 = default) + if (args[ARG_baudrate].u_int < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("negative baudrate")); + } else if (args[ARG_baudrate].u_int > 0) { + self->baudrate = args[ARG_baudrate].u_int; + } + + // set data bits + switch (args[ARG_bits].u_int) { + case 0: + break; // keep default + case 8: + // TODO set self->softuart_ptr bits if supported + self->bits = 8; + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("unsupported or invalid data bits")); + break; + } + + // set parity + if (args[ARG_parity].u_obj == MP_OBJ_NULL || args[ARG_parity].u_obj == mp_const_none) { + // TODO disable self->softuart_ptr parity if supported + self->parity = 0; + } else { + mp_int_t parity = mp_obj_get_int(args[ARG_parity].u_obj); + // TODO enable self->softuart_ptr parity if supported + if (parity & 1) { + // TODO enable ODD parity + // self->parity = 1; + mp_raise_ValueError(MP_ERROR_TEXT("unsupported parity")); + } else { + // TODO enable EVEN parity + // self->parity = 2; + mp_raise_ValueError(MP_ERROR_TEXT("unsupported parity")); + } + } + + // set stop bits + switch (args[ARG_stop].u_int) { + case 0: + break; // keep default + case 1: + // TODO set self->softuart_ptr stop bits if supported + self->stop = 1; + break; + case 2: + mp_raise_ValueError(MP_ERROR_TEXT("unsupported stop bits")); + // self->stop = 2; + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid stop bits")); + break; + } + + // set timeout + self->timeout = args[ARG_timeout].u_int; + + // set timeout_char + // make sure it is at least as long as a whole character (13 bits to be safe) + self->timeout_char = args[ARG_timeout_char].u_int; + uint32_t min_timeout_char = 13000 / self->baudrate + 1; + if (self->timeout_char < min_timeout_char) { + self->timeout_char = min_timeout_char; + } + + // setup + Softuart_Init(self->softuart_ptr, self->baudrate); +} + +STATIC mp_obj_t pyb_softuart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // tx, rx pins moved to kwargs + // mp_arg_check_num(n_args, n_kw, 2, MP_OBJ_FUN_ARGS_MAX, true); + + // create instance + pyb_softuart_obj_t *self = m_new_obj(pyb_softuart_obj_t); + self->base.type = &pyb_softuart_type; + + // assign pointer to driver structure + self->softuart_ptr = m_new_obj(Softuart); + + // set defaults + self->baudrate = 9600; + self->bits = 8; + self->parity = 0; + self->stop = 1; + self->timeout = 1000; + self->timeout_char = 10; + + // init the peripheral + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + pyb_softuart_init_helper(self, n_args, args, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t pyb_softuart_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + // omit self (args[0]) and call init helper + pyb_softuart_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(pyb_softuart_init_obj, 0, pyb_softuart_init); + +STATIC mp_obj_t pyb_softuart_deinit(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + pyb_softuart_obj_t *self = MP_OBJ_TO_PTR(args[0]); + // free memory + m_del_obj(Softuart, self->softuart_ptr); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(pyb_softuart_deinit_obj, 0, pyb_softuart_deinit); + +STATIC mp_obj_t pyb_softuart_flush(mp_obj_t self_in) { + pyb_softuart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + Softuart_Flush(self->softuart_ptr); // reset the rx buffer to empty + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(pyb_softuart_flush_obj, pyb_softuart_flush); + +STATIC const mp_rom_map_elem_t pyb_softuart_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pyb_softuart_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&pyb_softuart_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&pyb_softuart_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + +}; +STATIC MP_DEFINE_CONST_DICT(pyb_softuart_locals_dict, pyb_softuart_locals_dict_table); + +STATIC mp_uint_t MP_FASTCODE(pyb_softuart_read)(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + pyb_softuart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // make sure we want at least 1 char + if (size == 0) { + return 0; + } + + // wait for first char to become available + if (!Softuart_rxWait(self->softuart_ptr, self->timeout * 1000)) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } + + // read the data + uint8_t *buf = buf_in; + while (Softuart_Available(self->softuart_ptr)) { + *buf++ = Softuart_Read(self->softuart_ptr); + if (--size == 0 || !Softuart_rxWait(self->softuart_ptr, self->timeout_char * 1000)) { + // return number of bytes read + return buf - (uint8_t *)buf_in; + } + } + return 0; // FIXME +} + +STATIC mp_uint_t pyb_softuart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + pyb_softuart_obj_t *self = MP_OBJ_TO_PTR(self_in); + const byte *buf = buf_in; + + /* TODO implement non-blocking + // wait to be able to write the first character + if (!uart_tx_wait(self, timeout)) { + *errcode = EAGAIN; + return MP_STREAM_ERROR; + } + */ + + // write the data + for (size_t i = 0; i < size; ++i) { + Softuart_Putchar(self->softuart_ptr, *buf++); + // FIXME //uart_tx_one_char(self->uart_id, *buf++); + } + + // return number of bytes written + return size; +} + +STATIC mp_uint_t pyb_softuart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { + pyb_softuart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_uint_t ret; + if (request == MP_STREAM_POLL) { + mp_uint_t flags = arg; + ret = 0; + if ((flags & MP_STREAM_POLL_RD) && Softuart_rx_any(self->softuart_ptr)) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && Softuart_tx_any_room(self->softuart_ptr)) { + ret |= MP_STREAM_POLL_WR; + } + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +STATIC const mp_stream_p_t softuart_stream_p = { + .read = pyb_softuart_read, + .write = pyb_softuart_write, + .ioctl = pyb_softuart_ioctl, + .is_text = false, +}; + +const mp_obj_type_t pyb_softuart_type = { + { &mp_type_type }, + .name = MP_QSTR_SoftUART, + .print = pyb_softuart_print, + .make_new = pyb_softuart_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &softuart_stream_p, + .locals_dict = (mp_obj_dict_t *)&pyb_softuart_locals_dict, +}; diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 39a890f56f172..48b274c01b5e8 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -427,6 +427,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftUART), MP_ROM_PTR(&pyb_softuart_type) }, #if MICROPY_PY_MACHINE_I2C { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, diff --git a/ports/esp8266/modmachine.h b/ports/esp8266/modmachine.h index 4a73d3b8e8013..41c16e434f884 100644 --- a/ports/esp8266/modmachine.h +++ b/ports/esp8266/modmachine.h @@ -7,6 +7,7 @@ extern const mp_obj_type_t pyb_pin_type; extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t pyb_rtc_type; extern const mp_obj_type_t pyb_uart_type; +extern const mp_obj_type_t pyb_softuart_type; extern const mp_obj_type_t pyb_i2c_type; extern const mp_obj_type_t machine_hspi_type; diff --git a/ports/esp8266/softuart.c b/ports/esp8266/softuart.c new file mode 100644 index 0000000000000..2d053a957207a --- /dev/null +++ b/ports/esp8266/softuart.c @@ -0,0 +1,431 @@ +#include "ets_sys.h" +#include "etshal.h" +#include "osapi.h" +#include "gpio.h" +#include "os_type.h" +#include "esp_mphal.h" +#include "user_interface.h" +#include "softuart.h" + +#include "py/misc.h" + +// array of pointers to instances +// ugly, but using *p in interrupt handler doesn't work... +Softuart *_Softuart_GPIO_Instances[SOFTUART_GPIO_COUNT]; +uint8_t _Softuart_Instances_Count = 0; + +// intialize list of gpio names and functions +const softuart_reg_t softuart_reg[] = +{ + { PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0 }, // gpio0 + { PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1 }, // gpio1 (uart) + { PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2 }, // gpio2 + { PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3 }, // gpio3 (uart) + { PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4 }, // gpio4 + { PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5 }, // gpio5 + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12 }, // gpio12 + { PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13 }, // gpio13 + { PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14 }, // gpio14 + { PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 }, // gpio15 + // @TODO TODO gpio16 is missing (?include) +}; + +// returns position of first 1 in a number +// used to determine which gpio caused the interrupt +uint8_t Softuart_Bitcount(uint32_t x) { + uint8_t count; + + for (count = 0; x != 0; x >>= 1) { + if (x & 0x01) { + return count; + } + count++; + } + // error: no 1 found! + return 0xFF; +} + +uint8_t Softuart_IsGpioValid(uint8_t gpio_id) { + if ((gpio_id > 5 && gpio_id < 12) || gpio_id > 15) { + return 0; + } + return 1; +} + +// gpio_id should be validated in machine_softuart +void Softuart_SetPinRx(Softuart *s, uint8_t gpio_id) { + s->pin_rx.gpio_id = gpio_id; + s->pin_rx.gpio_mux_name = softuart_reg[gpio_id].gpio_mux_name; + s->pin_rx.gpio_func = softuart_reg[gpio_id].gpio_func; +} + +// gpio_id should be validated in machine_softuart +void Softuart_SetPinTx(Softuart *s, uint8_t gpio_id) { + s->pin_tx.gpio_id = gpio_id; + s->pin_tx.gpio_mux_name = softuart_reg[gpio_id].gpio_mux_name; + s->pin_tx.gpio_func = softuart_reg[gpio_id].gpio_func; +} + +void Softuart_EnableRs485(Softuart *s, uint8_t gpio_id) { + // enable rs485 + s->is_rs485 = 1; + + // set pin in instance + s->pin_rs485_tx_enable = gpio_id; + + // enable pin as gpio + PIN_FUNC_SELECT(softuart_reg[gpio_id].gpio_mux_name,softuart_reg[gpio_id].gpio_func); + + PIN_PULLUP_DIS(softuart_reg[gpio_id].gpio_mux_name); + + // set low for tx idle (so other bus participants can send) + GPIO_OUTPUT_SET(GPIO_ID_PIN(gpio_id), 0); +} + +void Softuart_Init(Softuart *s, uint32_t baudrate) { + // disable rs485 + s->is_rs485 = 0; + + if (!_Softuart_Instances_Count) { + // Initilaize gpio subsystem + gpio_init(); + } + + // set bit time + // baudrate should be validated in machine_softuart + s->bit_time = system_get_cpu_freq() * 1000000 / baudrate; + + // init tx pin + if (!s->pin_tx.gpio_mux_name) { + // calls to os_* sometimes reset my ESP + // os_printf("SOFTUART ERROR: Set tx pin (%d)\r\n",s->pin_tx.gpio_mux_name); + // TODO Support Rx only uart + } else { + // enable pin as gpio + PIN_FUNC_SELECT(s->pin_tx.gpio_mux_name, s->pin_tx.gpio_func); + + // set pullup (UART idle is VDD) + PIN_PULLUP_EN(s->pin_tx.gpio_mux_name); + + // set high for tx idle + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), 1); + unsigned int delay = 100000; + os_delay_us(delay); + } + + // init rx pin + if (!s->pin_rx.gpio_mux_name) { + // calls to os_* sometimes reset my ESP + // os_printf("SOFTUART ERROR: Set rx pin (%d)\r\n",s->pin_rx.gpio_mux_name); + // TODO Support Tx only uart + } else { + // enable pin as gpio + PIN_FUNC_SELECT(s->pin_rx.gpio_mux_name, s->pin_rx.gpio_func); + + // set pullup (UART idle is VDD) + PIN_PULLUP_EN(s->pin_rx.gpio_mux_name); + + // set to input -> disable output + GPIO_DIS_OUTPUT(GPIO_ID_PIN(s->pin_rx.gpio_id)); + + // set interrupt related things + + // disable interrupts by GPIO + ETS_GPIO_INTR_DISABLE(); + + // attach interrupt handler + ETS_GPIO_INTR_ATTACH(Softuart_Intr_Handler, NULL); + + // not sure what this does... (quote from example): + // void gpio_register_set(uint32 reg_id, uint32 value); + // + // From include file + // Set the specified GPIO register to the specified value. + // This is a very general and powerful interface that is not + // expected to be used during normal operation. It is intended + // mainly for debug, or for unusual requirements. + // + // All people repeat this mantra but I don't know what it means + // + gpio_register_set(GPIO_PIN_ADDR(s->pin_rx.gpio_id), + GPIO_PIN_INT_TYPE_SET(GPIO_PIN_INTR_DISABLE) | + GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_DISABLE) | + GPIO_PIN_SOURCE_SET(GPIO_AS_PIN_SOURCE)); + + // clear interrupt handler status, basically writing a low to the output + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(s->pin_rx.gpio_id)); + + // enable interrupt for pin on any edge (rise and fall) + // @TODO: should work with ANYEDGE (=3), but complie error + gpio_pin_intr_state_set(GPIO_ID_PIN(s->pin_rx.gpio_id), 3); + + // globally enable GPIO interrupts + ETS_GPIO_INTR_ENABLE(); + } + + // add instance to array of instances + _Softuart_GPIO_Instances[s->pin_rx.gpio_id] = s; + _Softuart_Instances_Count++; +} + +// *********************************** + +#define RSR_CCOUNT(r) __asm__ __volatile__ ("rsr %0,ccount" : "=a" (r)) +static inline uint32_t MP_FASTCODE(get_ccount)(void) { + uint32_t ccount; + RSR_CCOUNT(ccount); + return ccount; +} + +// *********************************** + +void MP_FASTCODE(Softuart_Intr_Handler)(void *p) { + uint8_t level, gpio_id; + unsigned start_time = get_ccount(); + + // disable all interrupts + ets_intr_lock(); + + // clear gpio status. Say ESP8266EX SDK Programming Guide in 5.1.6. GPIO interrupt handler + + uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + gpio_id = Softuart_Bitcount(gpio_status); + + // if interrupt was by an attached rx pin + if (gpio_id != 0xFF) { + // load instance which has rx pin on interrupt pin attached + Softuart *s = _Softuart_GPIO_Instances[gpio_id]; + + // disable interrupt for GPIO0 + gpio_pin_intr_state_set(GPIO_ID_PIN(s->pin_rx.gpio_id), GPIO_PIN_INTR_DISABLE); + + // check level + level = GPIO_INPUT_GET(GPIO_ID_PIN(s->pin_rx.gpio_id)); + if (!level) { + // pin is low + // therefore we have a start bit + + // now sample bits + unsigned i; + unsigned d = 0; + unsigned elapsed_time = get_ccount() - start_time; + s->elapsed = elapsed_time; + + // wait till start bit is half over so we can sample the next one in the center + // os_delay_us(s->bit_time/2); + if (elapsed_time < s->bit_time / 2) { + unsigned wait_time = s->bit_time / 2 - elapsed_time; + while ((get_ccount() - start_time) < wait_time) { + ; + } + start_time += wait_time; + } + + for (i = 0; i < 8; i++) { + while ((get_ccount() - start_time) < s->bit_time) { + ; + } + // shift d to the right + d >>= 1; + + // read bit + if (GPIO_INPUT_GET(GPIO_ID_PIN(s->pin_rx.gpio_id))) { + // if high, set msb of 8bit to 1 + d |= 0x80; + } + // recalculate start time for next bit + start_time += s->bit_time; + } + + // wait for stop bit + // os_delay_us(s->bit_time); + while ((get_ccount() - start_time) < s->bit_time) { + ; + } + + // store byte in buffer + + // if buffer full, set the overflow flag and return + uint8 next = (s->buffer.receive_buffer_tail + 1) % SOFTUART_MAX_RX_BUFF; + if (next != s->buffer.receive_buffer_head) { + // save new data in buffer: tail points to where byte goes + s->buffer.receive_buffer[s->buffer.receive_buffer_tail] = d; + s->buffer.receive_buffer_tail = next; + } else { + s->buffer.buffer_overflow = 1; + } + + // done + } + + // clear interrupt + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); + + // Reactivate interrupts for GPIO0 + gpio_pin_intr_state_set(GPIO_ID_PIN(s->pin_rx.gpio_id), 3); + } else { + // clear interrupt, no matter from which pin + // otherwise, this interrupt will be called again forever + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); + } + + // re-enable all interrupts + ets_intr_unlock(); + +} + +// *********************************** + +// Read data from buffer +uint8_t Softuart_Read(Softuart *s) { + // Empty buffer? + if (s->buffer.receive_buffer_head == s->buffer.receive_buffer_tail) { + return 0; // TODO move return to argument + } + + // Read from "head" + uint8_t d = s->buffer.receive_buffer[s->buffer.receive_buffer_head]; // grab next byte + s->buffer.receive_buffer_head = (s->buffer.receive_buffer_head + 1) % SOFTUART_MAX_RX_BUFF; + return d; +} + +// Flush data from buffer +uint32_t Softuart_Flush(Softuart *s) { + uint32_t num_chars = s->buffer.receive_buffer_tail - s->buffer.receive_buffer_head; + // Empty buffer + s->buffer.receive_buffer_head = 0; + s->buffer.receive_buffer_tail = 0; + return num_chars; +} + +// Is data in buffer available? +BOOL Softuart_Available(Softuart *s) { + return (s->buffer.receive_buffer_tail + SOFTUART_MAX_RX_BUFF - s->buffer.receive_buffer_head) % SOFTUART_MAX_RX_BUFF; +} + +int Softuart_rx_any(Softuart *s) { + return Softuart_Available(s); +} + +int Softuart_tx_any_room(Softuart *s) { + // TODO always assume we have room... there is no send buffer + return true; +} + +// cversek: +// based on micropython/esp8266/uart.c bool uart_rx_wait(uint32_t timeout_us) +// Waits at most timeout microseconds for at least 1 char to become ready for reading. +// Returns true if something available, false if not. +BOOL Softuart_rxWait(Softuart *s, uint32_t timeout_us) { + // handles system_get_time() wrap-around + int32_t when_timedout = (int32_t)system_get_time() + timeout_us; + for (;;) { + if (Softuart_Available(s)) { + return true; // have at least 1 char ready for reading + } + // handles system_get_time()-wrap around + if (when_timedout - (int32_t)system_get_time() <= 0) { + return false; // timeout + } + ets_event_poll(); + } +} + +static inline u8 chbit(u8 data, u8 bit) { + if ((data & bit) != 0) { + return 1; + } else { + return 0; + } +} + +// Function for printing individual characters +void MP_FASTCODE(Softuart_Putchar)(Softuart * s, char data) +{ + unsigned i; + unsigned start_time; + + // is this needed? delay(0); + + start_time = get_ccount(); + + // disable all interrupts + + ets_intr_lock(); + + // if rs485 set tx enable + if (s->is_rs485 == 1) { + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_rs485_tx_enable), 1); + } + + // Start Bit + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), 0); + for (i = 0; i < 8; i++) + { + while ((get_ccount() - start_time) < s->bit_time) { + ; + } + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), chbit(data,1 << i)); + + // recalculate start time for next bit + start_time += s->bit_time; + } + + // Stop bit + while ((get_ccount() - start_time) < s->bit_time) { + ; + } + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), 1); + + // Delay after byte, for new sync + os_delay_us(s->bit_time * 6 / system_get_cpu_freq()); + + // if rs485 set tx disable + if (s->is_rs485 == 1) { + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_rs485_tx_enable), 0); + } + + // re-enable all interrupts + + ets_intr_unlock(); +} + +void Softuart_Puts(Softuart *s, const char *c) { + while (*c) { + Softuart_Putchar(s,(u8) * c++); + } +} + +uint8_t Softuart_Readline(Softuart *s, char *Buffer, uint8_t MaxLen) { + uint8_t NextChar; + uint8_t len = 0; + + while (Softuart_Available(s)) { + NextChar = Softuart_Read(s); + + if (NextChar == '\r') { + continue; + } else if (NextChar == '\n') { + // break only if we already found a character + // if it was .e.g. only \r, we wait for the first useful character + if (len > 0) { + break; + } + } else if (len < MaxLen - 1) { + *Buffer++ = NextChar; + len++; + } else { + break; + } + } + // add string terminator + *Buffer++ = '\0'; + + return len; +} diff --git a/ports/esp8266/softuart.h b/ports/esp8266/softuart.h new file mode 100644 index 0000000000000..61031886c28b1 --- /dev/null +++ b/ports/esp8266/softuart.h @@ -0,0 +1,59 @@ +#ifndef SOFTUART_H_ +#define SOFTUART_H_ + +#include "user_interface.h" + +#define SOFTUART_MAX_RX_BUFF 256 + +#define SOFTUART_GPIO_COUNT 16 + +typedef struct { + uint8_t gpio_id; + uint32_t gpio_mux_name; + uint8_t gpio_func; +} softuart_pin_t; + +typedef struct { + char receive_buffer[SOFTUART_MAX_RX_BUFF]; + uint8_t receive_buffer_tail; + uint8_t receive_buffer_head; + uint8_t buffer_overflow; +} softuart_buffer_t; + +typedef struct { + softuart_pin_t pin_rx; + softuart_pin_t pin_tx; + // optional rs485 tx enable pin (high -> tx enabled) + uint8_t pin_rs485_tx_enable; + // wether or not this softuart is rs485 and controlls rs485 tx enable pin + uint8_t is_rs485; + volatile softuart_buffer_t buffer; + uint32_t bit_time; + uint32_t elapsed; +} Softuart; + + +BOOL Softuart_Available(Softuart *s); +uint32_t Softuart_Flush(Softuart *s); +BOOL Softuart_rxWait(Softuart *s, uint32_t timeout_us); +void Softuart_Intr_Handler(void *p); // void* for type compatibility with etshal.h: void ets_isr_attach(int irq_no, void (*handler)(void *), void *arg); +uint8_t Softuart_IsGpioValid(uint8_t gpio_id); +void Softuart_SetPinRx(Softuart *s, uint8_t gpio_id); +void Softuart_SetPinTx(Softuart *s, uint8_t gpio_id); +void Softuart_EnableRs485(Softuart *s, uint8_t gpio_id); +void Softuart_Init(Softuart *s, uint32_t baudrate); +void Softuart_Putchar(Softuart *s, char data); +void Softuart_Puts(Softuart *s, const char *c); +uint8_t Softuart_Read(Softuart *s); +uint8_t Softuart_Readline(Softuart *s, char *Buffer, uint8_t MaxLen); +// check status of rx/tx +int Softuart_rx_any(Softuart *s); +int Softuart_tx_any_room(Softuart *s); + +// define mapping from pin to functio mode +typedef struct { + uint32_t gpio_mux_name; + uint8_t gpio_func; +} softuart_reg_t; + +#endif /* SOFTUART_H_ */ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy