SoftUART module fixes and code simplification (#3104)

* SoftUART fixes:

- Simplify code by using lua_L* functions and using userdata properly
- Fix some edge-cases
- Add more examples to documentation

* Don't de-register interrupt hook if there is more RX instances

* More bug fixes and registering simplification with luaL_reref and unref2

* Correct documentation of SoftUART module
This commit is contained in:
galjonsfigur 2020-09-04 17:02:33 +02:00 committed by GitHub
parent a0d27059bc
commit e7620b0647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 125 additions and 172 deletions

View File

@ -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;
}

View File

@ -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)
```