diff --git a/app/modules/softuart.c b/app/modules/softuart.c index ee09b998..39559fec 100644 --- a/app/modules/softuart.c +++ b/app/modules/softuart.c @@ -14,6 +14,7 @@ #define SOFTUART_GPIO_COUNT 13 //TODO: Overflow flag as callback function + docs + typedef struct { char receive_buffer[SOFTUART_MAX_RX_BUFF]; uint8_t buffer_first; @@ -23,19 +24,15 @@ typedef struct { } softuart_buffer_t; typedef struct { - uint8_t pin_rx; - uint8_t pin_tx; volatile softuart_buffer_t buffer; uint16_t bit_time; uint16_t need_len; // Buffer length needed to run callback function char end_char; // Used to run callback if last char in buffer will be the same uint8_t armed; + uint8_t pin_rx; + uint8_t pin_tx; } softuart_t; -typedef struct { - softuart_t *softuart; -} softuart_userdata; - // Array of pointers to SoftUART instances softuart_t * softuart_gpio_instances[SOFTUART_GPIO_COUNT] = {NULL}; // Array of callback reference to be able to find which callback is used to which rx pin @@ -60,7 +57,7 @@ static inline uint8_t checkbit(uint8_t data, uint8_t bit) } } -uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status) +static uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status) { // Disable all interrupts ets_intr_lock(); @@ -74,16 +71,16 @@ uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status) // Load instance which has rx pin on interrupt pin attached softuart_t *s = softuart_gpio_instances[pin_num_inv[gpio_bit]]; if (s == NULL) continue; - if (softuart_rx_cb_ref[pin_num_inv[gpio_bit]] == LUA_NOREF) continue; - if (!s->armed) continue; - // There is SoftUART rx instance on that pin // Clear interrupt status on that pin GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & (1 << pin_num[s->pin_rx])); ret_gpio_status &= ~(1 << pin_num[s->pin_rx]); + if (softuart_rx_cb_ref[pin_num_inv[gpio_bit]] == LUA_NOREF) continue; + if (!s->armed) continue; + // There is an armed SoftUART rx instance on that pin // Start listening to transmission // TODO: inverted if (! (GPIO_INPUT_GET(GPIO_ID_PIN(pin_num[s->pin_rx])))) { - //pin is low - therefore we have a start bit + //pin is low - therefore we have a start bit unsigned byte = 0; // Casting and using signed types to always be able to compute elapsed time even if there is a overflow uint32_t elapsed_time = (uint32_t)(asm_ccount() - start_time); @@ -104,7 +101,7 @@ uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status) // Read bit if(GPIO_INPUT_GET(GPIO_ID_PIN(pin_num[s->pin_rx]))) { - // If high, set msb of 8bit to 1 + // If high, set MSB of byte to 1 byte |= 0x80; } // Recalculate start time for next bit @@ -125,7 +122,7 @@ uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status) ((s->need_len == 0) && ((char)byte == s->end_char))) { // Send the pointer to task handler s->armed = 0; - task_post_low(uart_recieve_task, (task_param_t)s); + task_post_medium(uart_recieve_task, (task_param_t)s); } } // Check for overflow after appending new byte @@ -136,11 +133,11 @@ uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status) if (s->buffer.buffer_last == SOFTUART_MAX_RX_BUFF) { s->buffer.buffer_last = 0; } - // Wait for stop bit // TODO: Add config for stop bits and parity bits while ((uint32_t)(asm_ccount() - start_time) < s->bit_time); - + // Break the loop after reading of the frame + break; } } // re-enable all interrupts @@ -156,6 +153,7 @@ static void softuart_putchar(softuart_t *s, char data) // Set start bit GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), 0); for (uint32_t i = 0; i < 8; i++) { + // Wait to transmit another bit while ((uint32_t)(asm_ccount() - start_time) < s->bit_time); GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), checkbit(data, 1 << i)); @@ -172,25 +170,29 @@ static void softuart_putchar(softuart_t *s, char data) ets_intr_unlock(); } -static void softuart_init(softuart_t *s) +static int softuart_init(softuart_t *s) { - NODE_DBG("SoftUART initialize gpio\n"); // Init tx pin - if (s->pin_tx != 0xFF){ + if (s->pin_tx != 0xFF) { platform_gpio_mode(s->pin_tx, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP); platform_gpio_write(s->pin_tx, PLATFORM_GPIO_HIGH); } // Init rx pin - if (s->pin_rx != 0xFF){ + if (s->pin_rx != 0xFF) { platform_gpio_mode(s->pin_rx, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP); - uint32_t mask = 1 << pin_num[s->pin_rx]; - platform_gpio_register_intr_hook(mask, softuart_intr_handler); - - softuart_gpio_instances[s->pin_rx] = s; // Enable interrupt for pin on falling edge platform_gpio_intr_init(s->pin_rx, GPIO_PIN_INTR_NEGEDGE); + softuart_gpio_instances[s->pin_rx] = s; + // Preserve other rx gpio pins + uint32_t mask = 0; + for (uint8_t i = 0; i < SOFTUART_GPIO_COUNT; i++) { + if (softuart_gpio_instances[i] != NULL) { + mask = mask | (1 << pin_num[softuart_gpio_instances[i]->pin_rx]); + } + } + return platform_gpio_register_intr_hook(mask, softuart_intr_handler); } } @@ -199,70 +201,59 @@ static int softuart_setup(lua_State *L) { uint32_t baudrate; uint8_t tx_gpio_id, rx_gpio_id; - uint8_t stack = 1; - softuart_userdata *suart = NULL; + softuart_t *softuart = NULL; - NODE_DBG("SoftUART setup called\n"); + NODE_DBG("[SoftUART]: setup called\n"); + baudrate = (uint32_t)luaL_checkinteger(L, 1); // Get Baudrate from + luaL_argcheck(L, (baudrate > 0 && baudrate < 230400), 1, "Invalid baud rate"); + lua_remove(L, 1); // Remove baudrate argument from stack + if (lua_gettop(L) == 2) { // 2 arguments: 1st can be nil + if (lua_isnil(L, 1)) { + tx_gpio_id = 0xFF; + } else { + tx_gpio_id = (uint8_t)luaL_checkinteger(L, 1); + luaL_argcheck(L, (platform_gpio_exists(tx_gpio_id) && tx_gpio_id != 0) + , 2, "Invalid SoftUART tx GPIO"); + } + rx_gpio_id = (uint8_t)luaL_checkinteger(L, 2); + luaL_argcheck(L, (platform_gpio_exists(rx_gpio_id) && rx_gpio_id != 0) + , 3, "Invalid SoftUART rx GPIO"); + luaL_argcheck(L, softuart_gpio_instances[rx_gpio_id] == NULL + , 3, "SoftUART rx already configured on the pin"); - if(lua_isnumber(L, stack)) { - baudrate = (uint32_t)luaL_checkinteger( L, stack); - //230400 Is the max baudrate the author of Arduino-Esp8266-Software-UART tested - if (baudrate <= 0 || baudrate > 230400) { - return luaL_error(L, "Invalid baud rate" ); - } - stack++; - } else { - return luaL_error(L, "Invalid argument type"); - } - - if(lua_isnumber(L, stack)) { - tx_gpio_id = (uint8_t)luaL_checkinteger( L, stack); - if (!platform_gpio_exists(tx_gpio_id) || tx_gpio_id == 0) { - return luaL_error(L, "SoftUART tx GPIO not valid"); - } - stack++; - } else { - tx_gpio_id = 0xFF; - stack++; - } - if (lua_isnumber(L, stack)) { - rx_gpio_id = (uint8_t)luaL_checkinteger( L, stack); - if (!platform_gpio_exists(rx_gpio_id) || rx_gpio_id == 0) { - return luaL_error(L, "SoftUART rx GPIO not valid"); - } - if (softuart_gpio_instances[rx_gpio_id] != NULL) { - return luaL_error( L, "SoftUART rx already configured on the pin."); - } - } else { + } else if (lua_gettop(L) == 1) { // 1 argument: transmit part only rx_gpio_id = 0xFF; + tx_gpio_id = (uint8_t)luaL_checkinteger(L, 1); + luaL_argcheck(L, (platform_gpio_exists(tx_gpio_id) && tx_gpio_id != 0) + , 2, "Invalid SoftUART tx GPIO"); + } else { + // SoftUART object without receive and transmit part would be useless + return luaL_error(L, "Not enough arguments"); } - suart = (softuart_userdata*)lua_newuserdata(L, sizeof(softuart_userdata)); - suart->softuart = malloc(sizeof(softuart_t)); - if (!suart->softuart) { - free(suart->softuart); - suart->softuart = NULL; - return luaL_error(L, "Not enough memory"); - } - suart->softuart->pin_rx = rx_gpio_id; - suart->softuart->pin_tx = tx_gpio_id; - suart->softuart->need_len = RX_BUFF_SIZE; - suart->softuart->armed = 0; + softuart = (softuart_t*)lua_newuserdata(L, sizeof(softuart_t)); + softuart->pin_rx = rx_gpio_id; + softuart->pin_tx = tx_gpio_id; + softuart->need_len = SOFTUART_MAX_RX_BUFF; + softuart->armed = 0; // Set buffer - suart->softuart->buffer.buffer_first = 0; - suart->softuart->buffer.buffer_last = 0; - suart->softuart->buffer.bytes_count = 0; - suart->softuart->buffer.buffer_overflow = 0; + softuart->buffer.buffer_first = 0; + softuart->buffer.buffer_last = 0; + softuart->buffer.bytes_count = 0; + softuart->buffer.buffer_overflow = 0; // Set bit time - suart->softuart->bit_time = system_get_cpu_freq() * 1000000 / baudrate; + softuart->bit_time = system_get_cpu_freq() * 1000000 / baudrate; // Set metatable luaL_getmetatable(L, "softuart.port"); lua_setmetatable(L, -2); // Init SoftUART - softuart_init(suart->softuart); + int result = softuart_init(softuart); + if (result == 0) { + luaL_error(L, "Couldn't register interrupt"); + } return 1; } @@ -271,13 +262,14 @@ static void softuart_rx_callback(task_param_t arg) softuart_t *softuart = (softuart_t*)arg; //Receive pointer from ISR lua_State *L = lua_getstate(); lua_rawgeti(L, LUA_REGISTRYINDEX, softuart_rx_cb_ref[softuart->pin_rx]); + // Clear overflow flag if needed if(softuart->buffer.bytes_count == SOFTUART_MAX_RX_BUFF) { softuart->buffer.buffer_overflow = 0; } // Copy volatile data to static buffer - uint8_t buffer_lenght = softuart->buffer.bytes_count; - for (int i = 0; i < buffer_lenght; i++) { + uint8_t buffer_length = softuart->buffer.bytes_count; + for (int i = 0; i < buffer_length; i++) { softuart_rx_buffer[i] = softuart->buffer.receive_buffer[softuart->buffer.buffer_first]; softuart->buffer.buffer_first++; softuart->buffer.bytes_count--; @@ -285,111 +277,63 @@ static void softuart_rx_callback(task_param_t arg) softuart->buffer.buffer_first = 0; } } - lua_pushlstring(L, softuart_rx_buffer, buffer_lenght); + lua_pushlstring(L, softuart_rx_buffer, buffer_length); softuart->armed = 1; luaL_pcallx(L, 1, 0); } + // Arguments: event name, minimum buffer filled to run callback, callback function static int softuart_on(lua_State *L) { - NODE_DBG("SoftUART on called\n"); - softuart_userdata *suart = NULL; + NODE_DBG("[SoftUART] on: called\n"); size_t name_len, arg_len; - uint8_t stack = 1; - suart = (softuart_userdata *)luaL_checkudata(L, 1, "softuart.port"); - luaL_argcheck(L, suart, stack, "softuart.port expected"); - if (suart == NULL) { - NODE_DBG("Userdata is nil\n"); - return 0; - } - stack++; + softuart_t *softuart = (softuart_t*)luaL_checkudata(L, 1, "softuart.port"); + const char *method = luaL_checklstring(L, 2, &name_len); + luaL_argcheck(L, lua_isfunction(L, 4), -1, "No callback function specified"); + luaL_argcheck(L, (name_len == 4 && strcmp(method, "data") == 0), 2, "Method not supported"); + luaL_argcheck(L, softuart->pin_rx != 0xFF, 1, "Rx pin was not declared"); - const char *method = luaL_checklstring(L, stack, &name_len); - if (method == NULL) - return luaL_error(L, "Wrong argument type"); - stack++; - - if (lua_type(L, stack) == LUA_TNUMBER) { - suart->softuart->need_len = (uint16_t)luaL_checkinteger( L, stack ); - stack++; - suart->softuart->end_char = 0; - if (suart->softuart->need_len > SOFTUART_MAX_RX_BUFF) { - suart->softuart->need_len = 0; - return luaL_error(L, "Argument bigger than SoftUART buffer"); - } - suart->softuart->armed = 1; - } else if (lua_isstring(L, stack)) { - const char *end = luaL_checklstring(L , stack, &arg_len); - stack++; - if ( arg_len != 1) { - return luaL_error(L, "Wrong end char length"); - } - suart->softuart->end_char = end[0]; - suart->softuart->need_len = 0; - suart->softuart->armed = 1; + if (lua_isnumber(L, 3)) { + luaL_argcheck(L, luaL_checkinteger(L, 3) < SOFTUART_MAX_RX_BUFF, + 2, "Argument bigger than SoftUART buffer"); + softuart->end_char = 0; + softuart->need_len = (uint16_t) luaL_checkinteger(L, 3); + } else if (lua_isstring(L, 3)) { + const char *end = luaL_checklstring(L , 3, &arg_len); + luaL_argcheck(L, arg_len == 1, 3, "Wrong end char length"); + softuart->end_char = end[0]; + softuart->need_len = 0; } else { return luaL_error(L, "Wrong argument type"); } - - if (lua_type(L, stack) == LUA_TFUNCTION) { - lua_pushvalue(L, stack); // Copy to top of the stack - } else { - lua_pushnil(L); - } - - if (name_len == 4 && strcmp(method, "data") == 0) { - - if(suart->softuart->pin_rx == 0xFF) { - return luaL_error(L, "Rx pin was not declared"); - } - - if (softuart_rx_cb_ref[suart->softuart->pin_rx] != LUA_NOREF) { - luaL_unref(L, LUA_REGISTRYINDEX, softuart_rx_cb_ref[suart->softuart->pin_rx]); - softuart_rx_cb_ref[suart->softuart->pin_rx] = LUA_NOREF; - } - if (! lua_isnil(L, -1)) { - softuart_rx_cb_ref[suart->softuart->pin_rx] = luaL_ref(L, LUA_REGISTRYINDEX); - } else { - lua_pop(L, 1); - } - } else { - lua_pop(L, 1); - return luaL_error(L, "Method not supported"); - } + lua_settop(L, 4); // Move to the top of the stack + // Register callback or reregister new one + luaL_reref(L, LUA_REGISTRYINDEX, &softuart_rx_cb_ref[softuart->pin_rx]); + // Arm the instance + softuart->armed = 1; return 0; } static int softuart_write(lua_State *L) { - NODE_DBG("SoftUART write called\n"); - softuart_userdata *suart = NULL; - uint8_t stack = 1; + softuart_t *softuart = NULL; size_t str_len; - suart = (softuart_userdata *)luaL_checkudata(L, 1, "softuart.port"); - luaL_argcheck(L, suart, stack, "softuart.port expected"); - if (suart == NULL) { - NODE_DBG("Userdata is nil\n"); - return 0; - } - stack++; - if(suart->softuart->pin_tx == 0xFF) { - return luaL_error(L, "Tx pin was not declared"); - } - if (lua_type(L, stack) == LUA_TNUMBER) { + softuart = (softuart_t*) luaL_checkudata(L, 1, "softuart.port"); + luaL_argcheck(L, softuart->pin_tx != 0xFF, 1, "Tx pin was not declared"); + + if (lua_isnumber(L, 2)) { // Send byte - uint32_t byte = (uint32_t)luaL_checkinteger( L, stack ); - if (byte > 255) { - return luaL_error(L, "Integer too large for a byte"); - } - softuart_putchar(suart->softuart, (char)byte); - } else if (lua_isstring(L, stack)) { + uint32_t byte = (uint32_t)luaL_checkinteger(L, 2); + luaL_argcheck(L, byte < 256, 2, "Integer too large for a byte"); + softuart_putchar(softuart, (char)byte); + } else if (lua_isstring(L, 2)) { // Send string - const char *string = luaL_checklstring(L , stack, &str_len); + const char *string = luaL_checklstring(L, 2, &str_len); for (size_t i = 0; i < str_len; i++) { - softuart_putchar(suart->softuart, string[i]); + softuart_putchar(softuart, string[i]); } } else { return luaL_error(L, "Wrong argument type"); @@ -400,17 +344,18 @@ static int softuart_write(lua_State *L) static int softuart_gcdelete(lua_State *L) { NODE_DBG("SoftUART GC called\n"); - softuart_userdata *suart = NULL; - suart = (softuart_userdata *)luaL_checkudata(L, 1, "softuart.port"); - luaL_argcheck(L, suart, 1, "softuart.port expected"); - if (suart == NULL) { - NODE_DBG("Userdata is nil\n"); - return 0; - } - softuart_gpio_instances[suart->softuart->pin_rx] = NULL; - luaL_unref(L, LUA_REGISTRYINDEX, softuart_rx_cb_ref[suart->softuart->pin_rx]); - softuart_rx_cb_ref[suart->softuart->pin_rx] = LUA_NOREF; - free(suart->softuart); + softuart_t *softuart = NULL; + softuart = (softuart_t*) luaL_checkudata(L, 1, "softuart.port"); + uint8_t last_instance = 1; + for(uint8_t instance = 0; instance < SOFTUART_GPIO_COUNT; instance++) + if (softuart_gpio_instances[instance] != NULL && instance != softuart->pin_rx) + last_instance = 0; + + softuart_gpio_instances[softuart->pin_rx] = NULL; + luaL_unref2(L, LUA_REGISTRYINDEX, softuart_rx_cb_ref[softuart->pin_rx]); + // Try to unregister the interrupt hook if this was last or the only instance + if (last_instance) + platform_gpio_register_intr_hook(0, softuart_intr_handler); return 0; } diff --git a/docs/modules/softuart.md b/docs/modules/softuart.md index d976ec35..7bc5b420 100644 --- a/docs/modules/softuart.md +++ b/docs/modules/softuart.md @@ -3,16 +3,21 @@ | :----- | :-------------------- | :---------- | :------ | |2019-12-27 | [pleningerweb](https://github.com/plieningerweb/), [juancgalvez](https://github.com/juancgalvez/), [crasu](https://github.com/crasu/), [galjonsfigur](https://github.com/galjonsfigur/)| [galjonsfigur](https://github.com/galjonsfigur/) | [softuart.c](../../app/modules/softuart.c) | -The SoftUART module provides access to multiple software-based UART ports. +The SoftUART module offers bit-banged serial ports over GPIO pins. -ESP8266 has only 1 full hardware UART port that is used to program the chip and communicate with NodeMCU firmware. The second port is transmit-only. More information can be found in [uart module documentation](uart/). This module provides access to more UART ports and can be used to communicate with devices like GSM or GPS modules. The code is based on [esp8266-software-uart](https://github.com/plieningerweb/esp8266-software-uart) and [Arduino-esp8266-Software-UART](https://github.com/juancgalvez/Arduino-esp8266-Software-UART) projects. Currently doesn't support inverted serial data logic or modes other than 8N1. It's important to notice that this is a software implementation of the serial protocol. There could be some interrupts that make the transmition or reception fail due to invalid timing. +!!! warning + + Software implementation of serial port can be unreliable and some reception errors are to be expected. + +ESP8266 has only 1 full hardware UART port that is used to program the chip and communicate with NodeMCU firmware. The second port is transmit-only. More information can be found in [uart module documentation](uart/). This module provides access to more UART ports and can be used to communicate with devices like GSM or GPS modules. The code is based on [esp8266-software-uart](https://github.com/plieningerweb/esp8266-software-uart) and [Arduino-esp8266-Software-UART](https://github.com/juancgalvez/Arduino-esp8266-Software-UART) projects. Currently doesn't support inverted serial data logic or modes other than 8N1. It's important to notice that this is a software implementation of the serial protocol. There could be some interrupts that make the transmission or reception fail due to invalid timing. !!! note -SoftUART cannot be used on D0 pin. + + SoftUART cannot be used on D0 pin. ## softuart.setup() -Creates new SoftUART instance. Note that rx pin cannot be shared between instances but tx pin can. +Creates new SoftUART instance. Note that rx pin cannot be shared between instances but tx pin can. #### Syntax `softuart.setup(baudrate, txPin, rxPin)` @@ -52,6 +57,7 @@ Sets up the callback function to receive data. ```lua -- Create new software UART with baudrate of 9600, D2 as Tx pin and D3 as Rx pin s = softuart.setup(9600, 2, 3) +-- Set callback to run when 10 characters show up in the buffer s:on("data", 10, function(data) print("Lua handler called!") print(data) @@ -75,4 +81,6 @@ Transmits a byte or sequence of them. -- Create new software UART with baudrate of 9600, D2 as Tx pin and D3 as Rx pin s = softuart.setup(9600, 2, 3) s:write("Hello!") +-- Send character 'a' +s:write(97) ```