From 70487a5b014394a9686ba9fdbaff3a269dd8957c Mon Sep 17 00:00:00 2001 From: "Craig Wm. Versek" Date: Sat, 7 Jan 2017 03:58:54 +0000 Subject: [PATCH 01/13] pure C module for bit-banged serial comm --- esp8266/Makefile | 1 + esp8266/espmissingincludes.h | 75 ++++++++++++++++++++++++++++++++++++ esp8266/softuart.c | 1 + esp8266/softuart.h | 1 + 4 files changed, 78 insertions(+) create mode 100644 esp8266/espmissingincludes.h create mode 120000 esp8266/softuart.c create mode 120000 esp8266/softuart.h diff --git a/esp8266/Makefile b/esp8266/Makefile index 1d50a0fb3170..2ad9a4aeb494 100644 --- a/esp8266/Makefile +++ b/esp8266/Makefile @@ -62,6 +62,7 @@ LDFLAGS += --gc-sections endif SRC_C = \ + softuart.c \ strtoll.c \ main.c \ help.c \ diff --git a/esp8266/espmissingincludes.h b/esp8266/espmissingincludes.h new file mode 100644 index 000000000000..c95561c19497 --- /dev/null +++ b/esp8266/espmissingincludes.h @@ -0,0 +1,75 @@ +#ifndef ESPMISSINGINCLUDES_H +#define ESPMISSINGINCLUDES_H + +#include +#include + + +int strcasecmp(const char *a, const char *b); +#ifndef FREERTOS +#include +#include +//Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere. +//MOST OF THESE ARE GUESSED! but they seem to swork and shut up the compiler. +typedef struct espconn espconn; + +int atoi(const char *nptr); +void ets_install_putc1(void *routine); +void ets_isr_mask(unsigned intr); +void ets_isr_unmask(unsigned intr); +int ets_memcmp(const void *s1, const void *s2, size_t n); +void *ets_memcpy(void *dest, const void *src, size_t n); +void *ets_memset(void *s, int c, size_t n); +int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); +int ets_str2macaddr(void *, void *); +int ets_strcmp(const char *s1, const char *s2); +char *ets_strcpy(char *dest, const char *src); +size_t ets_strlen(const char *s); +int ets_strncmp(const char *s1, const char *s2, int len); +char *ets_strncpy(char *dest, const char *src, size_t n); +char *ets_strstr(const char *haystack, const char *needle); +void ets_timer_disarm(os_timer_t *a); +void ets_timer_setfn(os_timer_t *t, ETSTimerFunc *fn, void *parg); +void ets_update_cpu_frequency(int freqmhz); +void *os_memmove(void *dest, const void *src, size_t n); +int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__ ((format (printf, 3, 4))); +int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +void uart_div_modify(int no, unsigned int freq); +uint8 wifi_get_opmode(void); +uint32 system_get_time(); +int rand(void); +void ets_bzero(void *s, size_t n); +void ets_delay_us(int ms); + +//Hack: this is defined in SDK 1.4.0 and undefined in 1.3.0. It's only used for this, the symbol itself +//has no meaning here. +#ifndef RC_LIMIT_P2P_11N +//Defs for SDK <1.4.0 +void *pvPortMalloc(size_t xWantedSize); +void *pvPortZalloc(size_t); +void vPortFree(void *ptr); +void *vPortMalloc(size_t xWantedSize); +void pvPortFree(void *ptr); +#else +void *pvPortMalloc(size_t xWantedSize, const char *file, int line); +void *pvPortZalloc(size_t, const char *file, int line); +void vPortFree(void *ptr, const char *file, int line); +void *vPortMalloc(size_t xWantedSize, const char *file, int line); +void pvPortFree(void *ptr, const char *file, int line); +#endif + +//Standard PIN_FUNC_SELECT gives a warning. Replace by a non-warning one. +#ifdef PIN_FUNC_SELECT +#undef PIN_FUNC_SELECT +#define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \ + WRITE_PERI_REG(PIN_NAME, \ + (READ_PERI_REG(PIN_NAME) \ + & (~(PERIPHS_IO_MUX_FUNC< Date: Sat, 7 Jan 2017 04:06:01 +0000 Subject: [PATCH 02/13] python interface wrapping softuart functionality --- esp8266/Makefile | 1 + esp8266/esp8266.ld | 1 + esp8266/machine_softuart.c | 246 +++++++++++++++++++++++++++++++++++++ esp8266/modmachine.h | 1 + 4 files changed, 249 insertions(+) create mode 100644 esp8266/machine_softuart.c diff --git a/esp8266/Makefile b/esp8266/Makefile index 2ad9a4aeb494..c0e979ab36d1 100644 --- a/esp8266/Makefile +++ b/esp8266/Makefile @@ -83,6 +83,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/esp8266/esp8266.ld b/esp8266/esp8266.ld index a6a4b930bb71..d5ef46c1b4a0 100644 --- a/esp8266/esp8266.ld +++ b/esp8266/esp8266.ld @@ -144,6 +144,7 @@ SECTIONS *machine_rtc.o(.literal*, .text*) *machine_adc.o(.literal*, .text*) *machine_uart.o(.literal*, .text*) + *machine_softuart.o(.literal*, .text*) *modpybi2c.o(.literal*, .text*) *modmachine.o(.literal*, .text*) *machine_wdt.o(.literal*, .text*) diff --git a/esp8266/machine_softuart.c b/esp8266/machine_softuart.c new file mode 100644 index 000000000000..5ef4b35ee7e1 --- /dev/null +++ b/esp8266/machine_softuart.c @@ -0,0 +1,246 @@ +/* + * 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/mphal.h" +//#include "py/malloc.h" +#include "modmachine.h" + +// UartDev is defined and initialized in rom code. +//FXIME //extern UartDevice UartDev; + +Softuart softuartDevice; + +typedef struct _pyb_softuart_obj_t { + 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", "1", "0"}; + +/******************************************************************************/ +// 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_timeout, ARG_timeout_char}; + static const mp_arg_t allowed_args[] = { + { 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 + self->tx = mp_obj_get_pin_obj(args[ARG_tx].u_obj); + Softuart_SetPinTx(&softuartDevice, mp_obj_get_pin(self->tx)); + self->rx = mp_obj_get_pin_obj(args[ARG_rx].u_obj); + Softuart_SetPinRx(&softuartDevice, mp_obj_get_pin(self->rx)); + + // set baudrate + if (args[ARG_baudrate].u_int > 0) { + self->baudrate = args[ARG_baudrate].u_int; + Softuart_Init(&softuartDevice, self->baudrate); + //UartDev.baut_rate = self->baudrate; // Sic! + } + + // set data bits + self->bits = 8; //no other options are supported + + + // set parity + self->parity = 0; //"NONE" no other options are supported + + // set stop bits + self->stop = 1; //"NONE" no other options are supported + + // 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 + //FIXME //uart_setup(self->uart_id); +} + +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) { + mp_arg_check_num(n_args, n_kw, 1, 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; + //FIXME removed //self->uart_id = uart_id; + //self->softuart_ptr = os_malloc(sizeof(Softuart)); + self->baudrate = 9600; + self->bits = 8; + self->parity = 0; + self->stop = 1; + self->timeout = 0; + self->timeout_char = 0; + + // 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) { + 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, 1, pyb_softuart_init); + +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(&softuartDevice); //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_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 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(&softuartDevice, self->timeout * 1000)) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } + + // read the data + uint8_t *buf = buf_in; + while (Softuart_Available(&softuartDevice)) { + *buf++ = Softuart_Read(&softuartDevice); + if (--size == 0 || !Softuart_rxWait(&softuartDevice, 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(&softuartDevice, *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) { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; +} + +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, + .iternext = mp_stream_unbuffered_iter, + .protocol = &softuart_stream_p, + .locals_dict = (mp_obj_dict_t*)&pyb_softuart_locals_dict, +}; diff --git a/esp8266/modmachine.h b/esp8266/modmachine.h index 414aaa85b0a1..ebda7460abb8 100644 --- a/esp8266/modmachine.h +++ b/esp8266/modmachine.h @@ -8,6 +8,7 @@ extern const mp_obj_type_t pyb_pwm_type; extern const mp_obj_type_t pyb_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; From 399df38e22e038b398e79b85b7cdf84842965bd6 Mon Sep 17 00:00:00 2001 From: "Craig Wm. Versek" Date: Sat, 7 Jan 2017 04:38:19 +0000 Subject: [PATCH 03/13] add SoftUART extension class to the machine module globals table; VERY important to add softuart.o to the irom section, or iram would be used up quickly --- esp8266/esp8266.ld | 1 + esp8266/modmachine.c | 1 + 2 files changed, 2 insertions(+) diff --git a/esp8266/esp8266.ld b/esp8266/esp8266.ld index d5ef46c1b4a0..e6e56a078723 100644 --- a/esp8266/esp8266.ld +++ b/esp8266/esp8266.ld @@ -144,6 +144,7 @@ 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*) diff --git a/esp8266/modmachine.c b/esp8266/modmachine.c index a5073d96c026..6d90e6f85a91 100644 --- a/esp8266/modmachine.c +++ b/esp8266/modmachine.c @@ -254,6 +254,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&pyb_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&pyb_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) }, { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hspi_type) }, From 6995d1d486f11399007b7372cb86c1be97b22de0 Mon Sep 17 00:00:00 2001 From: "Craig Wm. Versek" Date: Sat, 7 Jan 2017 05:39:22 +0000 Subject: [PATCH 04/13] replace symbolic links (oops) with actually source files --- esp8266/softuart.c | 427 ++++++++++++++++++++++++++++++++++++++++++++- esp8266/softuart.h | 53 +++++- 2 files changed, 478 insertions(+), 2 deletions(-) mode change 120000 => 100644 esp8266/softuart.c mode change 120000 => 100644 esp8266/softuart.h diff --git a/esp8266/softuart.c b/esp8266/softuart.c deleted file mode 120000 index b2c28edc1e45..000000000000 --- a/esp8266/softuart.c +++ /dev/null @@ -1 +0,0 @@ -/vagrant/softuart.c \ No newline at end of file diff --git a/esp8266/softuart.c b/esp8266/softuart.c new file mode 100644 index 000000000000..7064232ac221 --- /dev/null +++ b/esp8266/softuart.c @@ -0,0 +1,426 @@ +#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 "espmissingincludes.h" + +#include "softuart.h" + +//array of pointers to instances +Softuart *_Softuart_GPIO_Instances[SOFTUART_GPIO_COUNT]; +uint8_t _Softuart_Instances_Count = 0; + +//intialize list of gpio names and functions +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) +}; + +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; +} + +void Softuart_SetPinRx(Softuart *s, uint8_t gpio_id) +{ + if(! Softuart_IsGpioValid(gpio_id)) { + //os_printf("SOFTUART GPIO not valid %d\r\n",gpio_id); + } else { + 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; + } +} + +void Softuart_SetPinTx(Softuart *s, uint8_t gpio_id) +{ + if(! Softuart_IsGpioValid(gpio_id)) { + //os_printf("SOFTUART GPIO not valid %d\r\n",gpio_id); + } else { + 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) +{ + //os_printf("SOFTUART RS485 init\r\n"); + + //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); + + //os_printf("SOFTUART RS485 init done\r\n"); +} + +void Softuart_Init(Softuart *s, uint32_t baudrate) +{ + //disable rs485 + s->is_rs485 = 0; + + if(! _Softuart_Instances_Count) { + //os_printf("SOFTUART initialize gpio\r\n"); + //Initilaize gpio subsystem + gpio_init(); + } + + //set bit time + if(baudrate <= 0) { + //os_printf("SOFTUART ERROR: Set baud rate (%d)\r\n",baudrate); + } else { + s->bit_time = (1000000 / baudrate); + if ( ((100000000 / baudrate) - (100*s->bit_time)) > 50 ) s->bit_time++; + //os_printf("SOFTUART bit_time is %d\r\n",s->bit_time); + } + + + //init tx pin + if(!s->pin_tx.gpio_mux_name) { + //os_printf("SOFTUART ERROR: Set tx pin (%d)\r\n",s->pin_tx.gpio_mux_name); + } 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); + os_delay_us(100000); + + //os_printf("SOFTUART TX INIT DONE\r\n"); + } + + //init rx pin + if(!s->pin_rx.gpio_mux_name) { + //os_printf("SOFTUART ERROR: Set rx pin (%d)\r\n",s->pin_rx.gpio_mux_name); + } 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 and a pointer that will be passed around each time + ETS_GPIO_INTR_ATTACH(Softuart_Intr_Handler, s); + + //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(); + + //os_printf("SOFTUART RX INIT DONE\r\n"); + } + + //add instance to array of instances + _Softuart_GPIO_Instances[s->pin_rx.gpio_id] = s; + _Softuart_Instances_Count++; + + //os_printf("SOFTUART INIT DONE\r\n"); +} + +void Softuart_Intr_Handler(void *p) +{ + uint8_t level, gpio_id; + // clear gpio status. Say ESP8266EX SDK Programming Guide in 5.1.6. GPIO interrupt handler + + //ignore void* pointer and make a new pointer + Softuart *s; + + + 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 + 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); + + // Do something, for example, increment whatyouwant indirectly + //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 + + //wait till start bit is half over so we can sample the next one in the center + os_delay_us(s->bit_time/2); + + //now sample bits + unsigned i; + unsigned d = 0; + unsigned start_time = 0x7FFFFFFF & system_get_time(); + + for(i = 0; i < 8; i ++ ) + { + while ((0x7FFFFFFF & system_get_time()) < (start_time + (s->bit_time*(i+1)))) + { + //If system timer overflow, escape from while loop + if ((0x7FFFFFFF & system_get_time()) < start_time){break;} + } + //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; + } + } + + //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; // save new byte + s->buffer.receive_buffer_tail = next; + } + else + { + s->buffer.buffer_overflow = 1; + } + + //wait for stop bit + os_delay_us(s->bit_time); + //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); + } +} + + + +// 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; + + // 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; +} + +// 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) +{ + uint32_t start = system_get_time(); + for (;;) { + if (Softuart_Available(s)) { + return true; // have at least 1 char ready for reading + } + if (system_get_time() - start >= timeout_us) { + 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 Softuart_Putchar(Softuart *s, char data) +{ + unsigned i; + unsigned start_time = 0x7FFFFFFF & system_get_time(); + + //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 ((0x7FFFFFFF & system_get_time()) < (start_time + (s->bit_time*(i+1)))) + { + //If system timer overflow, escape from while loop + if ((0x7FFFFFFF & system_get_time()) < start_time){break;} + } + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), chbit(data,1<bit_time*9))) + { + //If system timer overflow, escape from while loop + if ((0x7FFFFFFF & system_get_time()) < start_time){break;} + } + 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); + + //if rs485 set tx disable + if(s->is_rs485 == 1) + { + GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_rs485_tx_enable), 0); + } +} + +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/esp8266/softuart.h b/esp8266/softuart.h deleted file mode 120000 index 62d39fb9b2be..000000000000 --- a/esp8266/softuart.h +++ /dev/null @@ -1 +0,0 @@ -/vagrant/softuart.h \ No newline at end of file diff --git a/esp8266/softuart.h b/esp8266/softuart.h new file mode 100644 index 000000000000..2e8830172d7d --- /dev/null +++ b/esp8266/softuart.h @@ -0,0 +1,52 @@ +#ifndef SOFTUART_H_ +#define SOFTUART_H_ + +#define SOFTUART_MAX_RX_BUFF 64 + +#define SOFTUART_GPIO_COUNT 16 + +typedef struct softuart_pin_t { + uint8_t gpio_id; + uint32_t gpio_mux_name; + uint8_t gpio_func; +} softuart_pin_t; + +typedef struct softuart_buffer_t { + 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; + uint16_t bit_time; +} 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); +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 ); + +//define mapping from pin to functio mode +typedef struct { + uint32_t gpio_mux_name; + uint8_t gpio_func; +} softuart_reg_t; + +#endif /* SOFTUART_H_ */ From 4c250ee34236d2b3be4fb0f998f27027435d1ece Mon Sep 17 00:00:00 2001 From: Craig Versek Date: Sat, 7 Jan 2017 00:59:24 -0500 Subject: [PATCH 05/13] Update softuart.h Include MIT license in header --- esp8266/softuart.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/esp8266/softuart.h b/esp8266/softuart.h index 2e8830172d7d..70003062a3a4 100644 --- a/esp8266/softuart.h +++ b/esp8266/softuart.h @@ -1,3 +1,27 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 plieningerweb + +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. +*/ + #ifndef SOFTUART_H_ #define SOFTUART_H_ From 3c6928f41b639a8775e64ac85022588a89287a8f Mon Sep 17 00:00:00 2001 From: Craig Versek Date: Sat, 7 Jan 2017 01:00:56 -0500 Subject: [PATCH 06/13] Update softuart.c include MIT license in header --- esp8266/softuart.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/esp8266/softuart.c b/esp8266/softuart.c index 7064232ac221..03d85512e4b2 100644 --- a/esp8266/softuart.c +++ b/esp8266/softuart.c @@ -1,3 +1,27 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 plieningerweb + +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 "ets_sys.h" #include "etshal.h" #include "osapi.h" From 535db92d4e160f0c9fafe1734bd6f80415a7ed14 Mon Sep 17 00:00:00 2001 From: "Craig Wm. Versek" Date: Sun, 8 Jan 2017 05:31:09 +0000 Subject: [PATCH 07/13] fixed system_get_time() roll-over glitch in bit-pattern timing --- esp8266/softuart.c | 51 +++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/esp8266/softuart.c b/esp8266/softuart.c index 7064232ac221..360541a5b319 100644 --- a/esp8266/softuart.c +++ b/esp8266/softuart.c @@ -144,7 +144,7 @@ void Softuart_Init(Softuart *s, uint32_t baudrate) //os_printf("SOFTUART ERROR: Set rx pin (%d)\r\n",s->pin_rx.gpio_mux_name); } else { //enable pin as gpio - PIN_FUNC_SELECT(s->pin_rx.gpio_mux_name, s->pin_rx.gpio_func); + 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); @@ -226,20 +226,22 @@ void Softuart_Intr_Handler(void *p) //therefore we have a start bit //wait till start bit is half over so we can sample the next one in the center - os_delay_us(s->bit_time/2); + os_delay_us(s->bit_time/2); //now sample bits unsigned i; unsigned d = 0; - unsigned start_time = 0x7FFFFFFF & system_get_time(); - + //mark the start of the data bit pattern + uint32_t start_time = system_get_time(); for(i = 0; i < 8; i ++ ) { - while ((0x7FFFFFFF & system_get_time()) < (start_time + (s->bit_time*(i+1)))) - { - //If system timer overflow, escape from while loop - if ((0x7FFFFFFF & system_get_time()) < start_time){break;} - } + //delay for bit timing + // NOTE signed arithmetic handles system_get_time() wrap-around + // NOTE using repeated calls to `os_delay_us` can accumulate overhead errors + // see https://forum.micropython.org/viewtopic.php?f=16&t=2204&start=10#p16935 + int32_t end_time = (int32_t) (start_time + (i + 1)*s->bit_time); + while ( end_time - (int32_t) system_get_time() > 0){}; + //shift d to the right d >>= 1; @@ -318,12 +320,14 @@ BOOL Softuart_Available(Softuart *s) // Returns true if something available, false if not. BOOL Softuart_rxWait(Softuart *s, uint32_t timeout_us) { - uint32_t start = system_get_time(); + //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 } - if (system_get_time() - start >= timeout_us) { + //handles system_get_time()-wrap around + if (when_timedout - (int32_t) system_get_time() <= 0) { return false; // timeout } ets_event_poll(); @@ -346,7 +350,6 @@ static inline u8 chbit(u8 data, u8 bit) void Softuart_Putchar(Softuart *s, char data) { unsigned i; - unsigned start_time = 0x7FFFFFFF & system_get_time(); //if rs485 set tx enable if(s->is_rs485 == 1) @@ -354,24 +357,26 @@ void Softuart_Putchar(Softuart *s, char data) GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_rs485_tx_enable), 1); } + //mark the start of the data bit pattern + uint32_t start_time = system_get_time(); + int32_t end_time; //calculated in loop + //Start Bit GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), 0); for(i = 0; i <= 8; i ++ ) { - while ((0x7FFFFFFF & system_get_time()) < (start_time + (s->bit_time*(i+1)))) - { - //If system timer overflow, escape from while loop - if ((0x7FFFFFFF & system_get_time()) < start_time){break;} - } + //delay for bit timing + // NOTE signed arithmetic handles system_get_time() wrap-around + // NOTE using repeated calls to `os_delay_us` can accumulate overhead errors + // see https://forum.micropython.org/viewtopic.php?f=16&t=2204&start=10#p16935 + end_time = (int32_t) (start_time + (i + 1)*s->bit_time); + while ( end_time - (int32_t) system_get_time() > 0){}; GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), chbit(data,1<bit_time*9))) - { - //If system timer overflow, escape from while loop - if ((0x7FFFFFFF & system_get_time()) < start_time){break;} - } + end_time = (int32_t) (start_time + 9*s->bit_time); + while ( end_time - (int32_t) system_get_time() > 0){}; GPIO_OUTPUT_SET(GPIO_ID_PIN(s->pin_tx.gpio_id), 1); // Delay after byte, for new sync @@ -387,7 +392,7 @@ void Softuart_Putchar(Softuart *s, char data) void Softuart_Puts(Softuart *s, const char *c ) { while ( *c ) { - Softuart_Putchar(s,( u8 )*c++); + Softuart_Putchar(s,( u8 )*c++); } } From 4aaf133bb6e815916caea3d058552a48a2cfa857 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 16 Feb 2017 18:05:06 +1100 Subject: [PATCH 08/13] py: Add micropython.schedule() function and associated runtime code. --- lib/utils/interrupt_char.c | 5 ++ py/modmicropython.c | 14 +++++ py/mpconfig.h | 10 ++++ py/mpstate.h | 16 +++++ py/py.mk | 1 + py/runtime.c | 4 ++ py/runtime.h | 10 ++++ py/scheduler.c | 120 +++++++++++++++++++++++++++++++++++++ py/vm.c | 20 +++++++ 9 files changed, 200 insertions(+) create mode 100644 py/scheduler.c diff --git a/lib/utils/interrupt_char.c b/lib/utils/interrupt_char.c index ab4efd9113a5..2ab25ab940b4 100644 --- a/lib/utils/interrupt_char.c +++ b/lib/utils/interrupt_char.c @@ -46,4 +46,9 @@ void mp_keyboard_interrupt(void) { #else MP_STATE_VM(mp_pending_exception) = MP_STATE_PORT(mp_kbd_exception); #endif + #if MICROPY_SCHEDULER + if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { + MP_STATE_VM(sched_state) = MP_SCHED_PENDING; + } + #endif } diff --git a/py/modmicropython.c b/py/modmicropython.c index 675d169cc4cb..1467615814b4 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -29,6 +29,7 @@ #include "py/mpstate.h" #include "py/builtin.h" #include "py/stackctrl.h" +#include "py/runtime.h" #include "py/gc.h" // Various builtins specific to MicroPython runtime, @@ -128,6 +129,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_unlock_obj, mp_micropython_ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_alloc_emergency_exception_buf_obj, mp_alloc_emergency_exception_buf); #endif +#if MICROPY_SCHEDULER +STATIC mp_obj_t mp_micropython_schedule(mp_obj_t function, mp_obj_t arg) { + if (!mp_sched_schedule(function, arg)) { + mp_raise_msg(&mp_type_RuntimeError, "schedule stack full"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_micropython_schedule_obj, mp_micropython_schedule); +#endif + STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_micropython) }, { MP_ROM_QSTR(MP_QSTR_const), MP_ROM_PTR(&mp_identity_obj) }, @@ -151,6 +162,9 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_heap_lock), MP_ROM_PTR(&mp_micropython_heap_lock_obj) }, { MP_ROM_QSTR(MP_QSTR_heap_unlock), MP_ROM_PTR(&mp_micropython_heap_unlock_obj) }, #endif + #if MICROPY_SCHEDULER + { MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_micropython_globals, mp_module_micropython_globals_table); diff --git a/py/mpconfig.h b/py/mpconfig.h index 1b47d822f185..fa52f22a0a2f 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -616,6 +616,16 @@ typedef double mp_float_t; #define MICROPY_USE_INTERNAL_PRINTF (1) #endif +// Support for internal scheduler +#ifndef MICROPY_SCHEDULER +#define MICROPY_SCHEDULER (0) +#endif + +// Maximum number of entries in the scheduler +#ifndef MICROPY_SCHEDULER_DEPTH +#define MICROPY_SCHEDULER_DEPTH (4) +#endif + // Support for generic VFS sub-system #ifndef MICROPY_VFS #define MICROPY_VFS (0) diff --git a/py/mpstate.h b/py/mpstate.h index 54392a994962..d017b799e497 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -50,6 +50,16 @@ typedef struct mp_dynamic_compiler_t { extern mp_dynamic_compiler_t mp_dynamic_compiler; #endif +// These are the values for sched_state +#define MP_SCHED_IDLE (1) +#define MP_SCHED_LOCKED (-1) +#define MP_SCHED_PENDING (0) // 0 so it's a quick check in the VM + +typedef struct _mp_sched_item_t { + mp_obj_t func; + mp_obj_t arg; +} mp_sched_item_t; + // This structure hold information about the memory allocation system. typedef struct _mp_state_mem_t { #if MICROPY_MEM_STATS @@ -129,6 +139,12 @@ typedef struct _mp_state_vm_t { // pending exception object (MP_OBJ_NULL if not pending) volatile mp_obj_t mp_pending_exception; + #if MICROPY_SCHEDULER + volatile int16_t sched_state; + uint16_t sched_sp; + mp_sched_item_t sched_stack[MICROPY_SCHEDULER_DEPTH]; + #endif + // current exception being handled, for sys.exc_info() #if MICROPY_PY_SYS_EXC_INFO mp_obj_base_t *cur_exception; diff --git a/py/py.mk b/py/py.mk index 01a802674443..3ef3ce4b4c4b 100644 --- a/py/py.mk +++ b/py/py.mk @@ -143,6 +143,7 @@ PY_O_BASENAME = \ persistentcode.o \ runtime.o \ runtime_utils.o \ + scheduler.o \ nativeglue.o \ stackctrl.o \ argcheck.o \ diff --git a/py/runtime.c b/py/runtime.c index db6a6f18f91d..0e189f6ed032 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -63,6 +63,10 @@ void mp_init(void) { // no pending exceptions to start with MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + #if MICROPY_SCHEDULER + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + MP_STATE_VM(sched_sp) = 0; + #endif #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF mp_init_emergency_exception_buf(); diff --git a/py/runtime.h b/py/runtime.h index 954833b67a94..71786e0eacf9 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -64,6 +64,16 @@ extern const qstr mp_binary_op_method_name[]; void mp_init(void); void mp_deinit(void); +void mp_handle_pending(void); +void mp_handle_pending_tail(mp_uint_t atomic_state); + +#if MICROPY_SCHEDULER +void mp_sched_lock(void); +void mp_sched_unlock(void); +static inline unsigned int mp_sched_num_pending(void) { return MP_STATE_VM(sched_sp); } +bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); +#endif + // extra printing method specifically for mp_obj_t's which are integral type int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); diff --git a/py/scheduler.c b/py/scheduler.c new file mode 100644 index 000000000000..d54bf1217182 --- /dev/null +++ b/py/scheduler.c @@ -0,0 +1,120 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "py/runtime.h" + +#if MICROPY_SCHEDULER + +// A variant of this is inlined in the VM at the pending exception check +void mp_handle_pending(void) { + if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + if (obj != MP_OBJ_NULL) { + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + if (!mp_sched_num_pending()) { + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + nlr_raise(obj); + } + mp_handle_pending_tail(atomic_state); + } +} + +// This function should only be called be mp_sched_handle_pending, +// or by the VM's inlined version of that function. +void mp_handle_pending_tail(mp_uint_t atomic_state) { + MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; + if (MP_STATE_VM(sched_sp) > 0) { + mp_sched_item_t item = MP_STATE_VM(sched_stack)[--MP_STATE_VM(sched_sp)]; + MICROPY_END_ATOMIC_SECTION(atomic_state); + mp_call_function_1_protected(item.func, item.arg); + } else { + MICROPY_END_ATOMIC_SECTION(atomic_state); + } + mp_sched_unlock(); +} + +void mp_sched_lock(void) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + if (MP_STATE_VM(sched_state) < 0) { + --MP_STATE_VM(sched_state); + } else { + MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); +} + +void mp_sched_unlock(void) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + if (++MP_STATE_VM(sched_state) == 0) { + // vm became unlocked + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL || mp_sched_num_pending()) { + MP_STATE_VM(sched_state) = MP_SCHED_PENDING; + } else { + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + } + } + MICROPY_END_ATOMIC_SECTION(atomic_state); +} + +bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg) { + if (MP_STATE_VM(sched_state) >= 0) { + // not locked, run straightaway + mp_call_function_1_protected(function, arg); + return true; + } else { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + bool ret; + if (MP_STATE_VM(sched_sp) < MICROPY_SCHEDULER_DEPTH) { + MP_STATE_VM(sched_stack)[MP_STATE_VM(sched_sp)].func = function; + MP_STATE_VM(sched_stack)[MP_STATE_VM(sched_sp)].arg = arg; + ++MP_STATE_VM(sched_sp); + ret = true; + } else { + // schedule stack is full + ret = false; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + return ret; + } +} + +#else // MICROPY_SCHEDULER + +// A variant of this is inlined in the VM at the pending exception check +void mp_handle_pending(void) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + nlr_raise(obj); + } +} + +#endif // MICROPY_SCHEDULER diff --git a/py/vm.c b/py/vm.c index 7a906cd8048d..90f7a4eaab9f 100644 --- a/py/vm.c +++ b/py/vm.c @@ -1266,12 +1266,32 @@ unwind_jump:; pending_exception_check: MICROPY_VM_HOOK_LOOP + + #if MICROPY_SCHEDULER + // This is an inlined variant of mp_handle_pending + if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { + MARK_EXC_IP_SELECTIVE(); + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + if (obj != MP_OBJ_NULL) { + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + if (!mp_sched_num_pending()) { + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + RAISE(obj); + } + mp_handle_pending_tail(atomic_state); + } + #else + // This is an inlined variant of mp_handle_pending if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { MARK_EXC_IP_SELECTIVE(); mp_obj_t obj = MP_STATE_VM(mp_pending_exception); MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; RAISE(obj); } + #endif #if MICROPY_PY_THREAD_GIL #if MICROPY_PY_THREAD_GIL_VM_DIVISOR From fb78c58f70345c2c16eba82c6566727e95609c8b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 15 Feb 2017 23:05:38 +1100 Subject: [PATCH 09/13] unix: Use mp_handle_pending() in time.sleep(). --- unix/modtime.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/unix/modtime.c b/unix/modtime.c index 85d1f55327de..080d321ee4f5 100644 --- a/unix/modtime.c +++ b/unix/modtime.c @@ -108,9 +108,7 @@ STATIC mp_obj_t mod_time_sleep(mp_obj_t arg) { if (res != -1 || errno != EINTR) { break; } - if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { - return mp_const_none; - } + mp_handle_pending(); //printf("select: EINTR: %ld:%ld\n", tv.tv_sec, tv.tv_usec); #else break; From fa84e2c30d5073ee02d3364809250c323f0be0c7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 15 Feb 2017 23:04:53 +1100 Subject: [PATCH 10/13] stmhal: Enable micropython.schedule(). --- stmhal/extint.c | 3 ++- stmhal/mpconfigport.h | 11 ++++++++++- stmhal/systick.c | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/stmhal/extint.c b/stmhal/extint.c index dacf8dd568d9..59b00cb73cf1 100644 --- a/stmhal/extint.c +++ b/stmhal/extint.c @@ -28,7 +28,6 @@ #include #include -#include "py/nlr.h" #include "py/runtime.h" #include "py/gc.h" #include "py/mphal.h" @@ -412,6 +411,7 @@ void Handle_EXTI_Irq(uint32_t line) { if (line < EXTI_NUM_VECTORS) { mp_obj_t *cb = &MP_STATE_PORT(pyb_extint_callback)[line]; if (*cb != mp_const_none) { + mp_sched_lock(); // When executing code within a handler we must lock the GC to prevent // any memory allocations. We must also catch any exceptions. gc_lock(); @@ -427,6 +427,7 @@ void Handle_EXTI_Irq(uint32_t line) { mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); } gc_unlock(); + mp_sched_unlock(); } } } diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h index 38a5ac5da9e1..7255cd10a818 100644 --- a/stmhal/mpconfigport.h +++ b/stmhal/mpconfigport.h @@ -69,6 +69,8 @@ #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_USE_INTERNAL_ERRNO (1) +#define MICROPY_SCHEDULER (1) +#define MICROPY_SCHEDULER_DEPTH (8) #define MICROPY_VFS (1) #define MICROPY_VFS_FAT (1) @@ -303,6 +305,8 @@ static inline mp_uint_t disable_irq(void) { #if MICROPY_PY_THREAD #define MICROPY_EVENT_POLL_HOOK \ do { \ + extern void mp_handle_pending(void); \ + mp_handle_pending(); \ if (pyb_thread_enabled) { \ MP_THREAD_GIL_EXIT(); \ pyb_thread_yield(); \ @@ -312,7 +316,12 @@ static inline mp_uint_t disable_irq(void) { } \ } while (0); #else -#define MICROPY_EVENT_POLL_HOOK __WFI(); +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(void); \ + mp_handle_pending(); \ + __WFI(); \ + } while (0); #endif // There is no classical C heap in bare-metal ports, only Python diff --git a/stmhal/systick.c b/stmhal/systick.c index ade05d74d752..dfc9205f969f 100644 --- a/stmhal/systick.c +++ b/stmhal/systick.c @@ -26,7 +26,7 @@ #include STM32_HAL_H -#include "py/obj.h" +#include "py/runtime.h" #include "irq.h" #include "systick.h" #include "pybthread.h" @@ -34,6 +34,7 @@ // We provide our own version of HAL_Delay that calls __WFI while waiting, in // order to reduce power consumption. // Note: Upon entering this function we may or may not have the GIL. +// TODO: make 2 versions of this, a simple one called HAL_Delay, and one for mp_hal_delay_ms. void HAL_Delay(uint32_t Delay) { if (query_irq() == IRQ_STATE_ENABLED) { // IRQs enabled, so can use systick counter to do the delay @@ -42,6 +43,7 @@ void HAL_Delay(uint32_t Delay) { // Wraparound of tick is taken care of by 2's complement arithmetic. while (uwTick - start < Delay) { // Enter sleep mode, waiting for (at least) the SysTick interrupt. + mp_handle_pending(); #if MICROPY_PY_THREAD if (pyb_thread_enabled) { pyb_thread_yield(); From 7101b156d576dd606083e10d2716809d7986477b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 15 Feb 2017 17:45:36 +1100 Subject: [PATCH 11/13] esp8266: Enable micropython.schedule() with locking in pin callback. --- esp8266/esp8266_common.ld | 1 + esp8266/esp_mphal.c | 7 ++----- esp8266/machine_pin.c | 2 ++ esp8266/mpconfigport.h | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/esp8266/esp8266_common.ld b/esp8266/esp8266_common.ld index f721c28b038b..1da835681a27 100644 --- a/esp8266/esp8266_common.ld +++ b/esp8266/esp8266_common.ld @@ -100,6 +100,7 @@ SECTIONS *py/qstr.o*(.literal* .text*) *py/repl.o*(.literal* .text*) *py/runtime.o*(.literal* .text*) + *py/scheduler.o*(.literal* .text*) *py/scope.o*(.literal* .text*) *py/sequence.o*(.literal* .text*) *py/showbc.o*(.literal* .text*) diff --git a/esp8266/esp_mphal.c b/esp8266/esp_mphal.c index f5e284fde654..7ecc7776aaa5 100644 --- a/esp8266/esp_mphal.c +++ b/esp8266/esp_mphal.c @@ -33,6 +33,7 @@ #include "ets_alt_task.h" #include "py/obj.h" #include "py/mpstate.h" +#include "py/runtime.h" #include "extmod/misc.h" #include "lib/utils/pyexec.h" @@ -130,11 +131,7 @@ void mp_hal_delay_ms(uint32_t delay) { void ets_event_poll(void) { ets_loop_iter(); - if (MP_STATE_VM(mp_pending_exception) != NULL) { - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - nlr_raise(obj); - } + mp_handle_pending(); } void __assert_func(const char *file, int line, const char *func, const char *expr) { diff --git a/esp8266/machine_pin.c b/esp8266/machine_pin.c index a1e94e898eef..9ea5197bc87d 100644 --- a/esp8266/machine_pin.c +++ b/esp8266/machine_pin.c @@ -100,6 +100,7 @@ void pin_init0(void) { } void pin_intr_handler(uint32_t status) { + mp_sched_lock(); gc_lock(); status &= 0xffff; for (int p = 0; status; ++p, status >>= 1) { @@ -111,6 +112,7 @@ void pin_intr_handler(uint32_t status) { } } gc_unlock(); + mp_sched_unlock(); } pyb_pin_obj_t *mp_obj_get_pin_obj(mp_obj_t pin_in) { diff --git a/esp8266/mpconfigport.h b/esp8266/mpconfigport.h index 04b9792ebfbd..cb120648ef10 100644 --- a/esp8266/mpconfigport.h +++ b/esp8266/mpconfigport.h @@ -28,6 +28,7 @@ #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_USE_INTERNAL_ERRNO (1) +#define MICROPY_SCHEDULER (1) #define MICROPY_PY_ALL_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_COMPLEX (0) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) From 3a5a411d2870e6ee29117d298b804b1f81ba0d77 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 15 Feb 2017 17:45:53 +1100 Subject: [PATCH 12/13] esp8266/machine_pin: Add "hard" parameter to pin.irq, soft by default. --- esp8266/machine_pin.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/esp8266/machine_pin.c b/esp8266/machine_pin.c index 9ea5197bc87d..0f5057d22011 100644 --- a/esp8266/machine_pin.c +++ b/esp8266/machine_pin.c @@ -87,11 +87,15 @@ STATIC uint8_t pin_mode[16 + 1]; // forward declaration STATIC const pin_irq_obj_t pin_irq_obj[16]; +// whether the irq is hard or soft +STATIC bool pin_irq_is_hard[16]; + void pin_init0(void) { ETS_GPIO_INTR_DISABLE(); ETS_GPIO_INTR_ATTACH(pin_intr_handler_iram, NULL); // disable all interrupts memset(&MP_STATE_PORT(pin_irq_handler)[0], 0, 16 * sizeof(mp_obj_t)); + memset(pin_irq_is_hard, 0, sizeof(pin_irq_obj)); for (int p = 0; p < 16; ++p) { GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << p); SET_TRIGGER(p, 0); @@ -107,7 +111,11 @@ void pin_intr_handler(uint32_t status) { if (status & 1) { mp_obj_t handler = MP_STATE_PORT(pin_irq_handler)[p]; if (handler != MP_OBJ_NULL) { - mp_call_function_1_protected(handler, MP_OBJ_FROM_PTR(&pyb_pin_obj[p])); + if (pin_irq_is_hard[p]) { + mp_call_function_1_protected(handler, MP_OBJ_FROM_PTR(&pyb_pin_obj[p])); + } else { + mp_sched_schedule(handler, MP_OBJ_FROM_PTR(&pyb_pin_obj[p])); + } } } } @@ -346,10 +354,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_pin_high_obj, pyb_pin_high); // pin.irq(*, trigger, handler=None) STATIC mp_obj_t pyb_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_trigger, ARG_handler }; + enum { ARG_trigger, ARG_handler, ARG_hard }; static const mp_arg_t allowed_args[] = { { MP_QSTR_trigger, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_handler, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; pyb_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -367,6 +376,7 @@ STATIC mp_obj_t pyb_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k } ETS_GPIO_INTR_DISABLE(); MP_STATE_PORT(pin_irq_handler)[self->phys_port] = handler; + pin_irq_is_hard[self->phys_port] = args[ARG_hard].u_bool; SET_TRIGGER(self->phys_port, args[ARG_trigger].u_int); GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << self->phys_port); ETS_GPIO_INTR_ENABLE(); From 5583ac9c33e2cd500f43bd60c4d27ec9a01ef1dc Mon Sep 17 00:00:00 2001 From: "Craig Wm. Versek" Date: Sun, 19 Feb 2017 01:47:54 -0500 Subject: [PATCH 13/13] new mp interpreter name 'mp_identity_getiter' --- esp8266/machine_softuart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp8266/machine_softuart.c b/esp8266/machine_softuart.c index 5ef4b35ee7e1..457c2d8982e7 100644 --- a/esp8266/machine_softuart.c +++ b/esp8266/machine_softuart.c @@ -239,7 +239,7 @@ const mp_obj_type_t pyb_softuart_type = { .name = MP_QSTR_SoftUART, .print = pyb_softuart_print, .make_new = pyb_softuart_make_new, - .getiter = mp_identity, + .getiter = mp_identity_getiter, .iternext = mp_stream_unbuffered_iter, .protocol = &softuart_stream_p, .locals_dict = (mp_obj_dict_t*)&pyb_softuart_locals_dict,