diff --git a/esp8266/Makefile b/esp8266/Makefile index 5e61fc16d8aa1..bb465127b6c04 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 \ @@ -82,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_common.ld b/esp8266/esp8266_common.ld index f721c28b038bf..bbbd9a059c150 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*) @@ -136,6 +137,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/esp8266/esp_mphal.c b/esp8266/esp_mphal.c index f5e284fde654a..7ecc7776aaa50 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/espmissingincludes.h b/esp8266/espmissingincludes.h new file mode 100644 index 0000000000000..c95561c194971 --- /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<>= 1) { 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])); + } } } } gc_unlock(); + mp_sched_unlock(); } pyb_pin_obj_t *mp_obj_get_pin_obj(mp_obj_t pin_in) { @@ -344,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)]; @@ -365,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(); diff --git a/esp8266/machine_softuart.c b/esp8266/machine_softuart.c new file mode 100644 index 0000000000000..457c2d8982e78 --- /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_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &softuart_stream_p, + .locals_dict = (mp_obj_dict_t*)&pyb_softuart_locals_dict, +}; diff --git a/esp8266/modmachine.c b/esp8266/modmachine.c index 222ed004d8262..836afd925c6b3 100644 --- a/esp8266/modmachine.c +++ b/esp8266/modmachine.c @@ -256,6 +256,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) }, diff --git a/esp8266/modmachine.h b/esp8266/modmachine.h index 414aaa85b0a14..ebda7460abb89 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; diff --git a/esp8266/mpconfigport.h b/esp8266/mpconfigport.h index 04b9792ebfbde..cb120648ef105 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) diff --git a/esp8266/softuart.c b/esp8266/softuart.c new file mode 100644 index 0000000000000..c4b5177d66469 --- /dev/null +++ b/esp8266/softuart.c @@ -0,0 +1,455 @@ +/* +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" +#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; + //mark the start of the data bit pattern + uint32_t start_time = system_get_time(); + for(i = 0; i < 8; i ++ ) + { + //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; + + //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) +{ + //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 Softuart_Putchar(Softuart *s, char data) +{ + unsigned i; + + //if rs485 set tx enable + if(s->is_rs485 == 1) + { + 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 ++ ) + { + //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); + 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 + 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 new file mode 100644 index 0000000000000..70003062a3a47 --- /dev/null +++ b/esp8266/softuart.h @@ -0,0 +1,76 @@ +/* +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_ + +#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_ */ diff --git a/lib/utils/interrupt_char.c b/lib/utils/interrupt_char.c index ab4efd9113a5b..2ab25ab940b45 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 675d169cc4cb2..1467615814b49 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 1b47d822f1854..fa52f22a0a2fc 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 54392a9949621..d017b799e4975 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 01a8026744435..3ef3ce4b4c4b6 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 db6a6f18f91dd..0e189f6ed0325 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 954833b67a942..71786e0eacf96 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 0000000000000..d54bf12171828 --- /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 7a906cd8048d7..90f7a4eaab9f3 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 diff --git a/stmhal/extint.c b/stmhal/extint.c index dacf8dd568d97..59b00cb73cf1c 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 38a5ac5da9e1f..7255cd10a8181 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 ade05d74d7522..dfc9205f969fa 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(); diff --git a/unix/modtime.c b/unix/modtime.c index 85d1f55327ded..080d321ee4f53 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;