Initial version of software UART C module (#2673)

This commit is contained in:
galjonsfigur 2019-12-27 14:20:52 +01:00 committed by Marcel Stör
parent 863dfb59ed
commit db1e4454de
4 changed files with 492 additions and 0 deletions

View File

@ -55,6 +55,7 @@
//#define LUA_USE_MODULES_SIGMA_DELTA
//#define LUA_USE_MODULES_SJSON
//#define LUA_USE_MODULES_SNTP
//#define LUA_USE_MODULES_SOFTUART
//#define LUA_USE_MODULES_SOMFY
#define LUA_USE_MODULES_SPI
//#define LUA_USE_MODULES_SQLITE3

412
app/modules/softuart.c Normal file
View File

@ -0,0 +1,412 @@
#include "ets_sys.h"
#include "osapi.h"
#include "gpio.h"
#include "os_type.h"
#include "user_interface.h"
#include "module.h"
#include "lauxlib.h"
#include "task/task.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>
#define SOFTUART_MAX_RX_BUFF 128
#define SOFTUART_GPIO_COUNT 13
typedef struct {
char receive_buffer[SOFTUART_MAX_RX_BUFF];
uint8_t length;
uint8_t buffer_overflow;
} 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;
} 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
static int softuart_rx_cb_ref[SOFTUART_GPIO_COUNT];
// Task for receiving data
static task_handle_t uart_recieve_task = NULL;
// Receiving buffer for callback usage
static char softuart_rx_buffer[SOFTUART_MAX_RX_BUFF];
static inline int32_t asm_ccount(void) {
int32_t r;
asm volatile ("rsr %0, ccount" : "=r"(r));
return r;
}
static inline uint8_t checkbit(uint8_t data, uint8_t bit)
{
if ((data & bit) != 0) {
return 1;
} else {
return 0;
}
}
uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status)
{
// Disable all interrupts
ets_intr_lock();
int32_t start_time = asm_ccount();
uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
uint32_t gpio_bits = gpio_status;
for (uint8_t gpio_bit = 0; gpio_bits != 0; gpio_bit++, gpio_bits >>= 1) {
// Check all pins for interrupts
if (! (gpio_bits & 0x01)) continue;
// We got pin that was interrupted
// 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]);
// 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
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);
// Wait till start bit is half over so we can sample the next one in the center
if (elapsed_time < s->bit_time / 2) {
uint16_t wait_time = s->bit_time / 2 - elapsed_time;
while ((uint32_t)(asm_ccount() - start_time) < wait_time);
start_time += wait_time;
}
// Sample bits
// TODO: How many bits? Add other configs to softuart
for (uint8_t i = 0; i < 8; i ++ ) {
while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);
//shift d to the right
byte >>= 1;
// Read bit
if(GPIO_INPUT_GET(GPIO_ID_PIN(pin_num[s->pin_rx]))) {
// If high, set msb of 8bit to 1
byte |= 0x80;
}
// Recalculate start time for next bit
start_time += s->bit_time;
}
// Wait for stop bit
// TODO: Add config for stop bits and parity bits
while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);
// Store byte in buffer
// If buffer full, set the overflow flag and return
uint8 next = s->buffer.length + 1 % SOFTUART_MAX_RX_BUFF;
if (next != 0) {
s->buffer.receive_buffer[s->buffer.length] = byte; // save new byte
s->buffer.length = next;
// Run callback when buffer is filled with enough data or last char is the triggering one
if (((s->need_len != 0) && (s->buffer.length >= s->need_len)) || \
((s->need_len == 0) && ((char)byte == s->end_char))) {
s->armed = 0;
task_post_low(uart_recieve_task, (task_param_t)s); // Send the pointer to task handler
}
} else {
//TODO: use this information somehow?
s->buffer.buffer_overflow = 1;
}
}
}
// re-enable all interrupts
ets_intr_unlock();
return ret_gpio_status;
}
static void softuart_putchar(softuart_t *s, char data)
{
// Disable all interrupts
ets_intr_lock();
int32_t start_time = asm_ccount();
// Set start bit
GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), 0);
for (uint32_t i = 0; i < 8; i++) {
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));
// Recalculate start time for next bit
start_time += s->bit_time;
}
// Stop bit
while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);
GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), 1);
// Delay after byte, for new sync
os_delay_us(s->bit_time*6 / system_get_cpu_freq());
// Re-enable all interrupts
ets_intr_unlock();
}
static void softuart_init(softuart_t *s)
{
NODE_DBG("SoftUART initialize gpio\n");
if (s->pin_tx != 0xFF){
// Init tx pin
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){
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);
}
}
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;
NODE_DBG("SoftUART setup called\n");
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 {
rx_gpio_id = 0xFF;
}
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;
//set bit time
suart->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);
return 1;
}
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]);
// Copy volatile data to static buffer
for (int i = 0; i < softuart->buffer.length; i++) {
softuart_rx_buffer[i] = softuart->buffer.receive_buffer[i];
}
lua_pushlstring(L, softuart_rx_buffer, softuart->buffer.length);
softuart->buffer.length = 0;
softuart->armed = 1;
lua_call(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;
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++;
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;
} else {
return luaL_error(L, "Wrong argument type");
}
if (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION) {
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");
}
return 0;
}
static int softuart_write(lua_State *L)
{
NODE_DBG("SoftUART write called\n");
softuart_userdata *suart = NULL;
uint8_t stack = 1;
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) {
// 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)) {
// Send string
const char *string = luaL_checklstring(L , stack, &str_len);
for (size_t i = 0; i < str_len; i++) {
softuart_putchar(suart->softuart, string[i]);
}
} else {
return luaL_error(L, "Wrong argument type");
}
return 0;
}
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);
return 0;
}
// Port function map
LROT_BEGIN(softuart_port)
LROT_FUNCENTRY( on, softuart_on)
LROT_TABENTRY( __index, softuart_port)
LROT_FUNCENTRY( write, softuart_write)
LROT_FUNCENTRY( __gc, softuart_gcdelete)
LROT_END(ads1115, softuart_port, LROT_MASK_GC_INDEX)
// Module function map
LROT_BEGIN(softuart)
LROT_FUNCENTRY( setup, softuart_setup)
LROT_TABENTRY(__metatable, softuart_port)
LROT_END(softuart, NULL, 0 )
static int luaopen_softuart(lua_State *L)
{
for(int i = 0; i < SOFTUART_GPIO_COUNT; i++) {
softuart_rx_cb_ref[i] = LUA_NOREF;
}
uart_recieve_task = task_get_id((task_callback_t) softuart_rx_callback);
luaL_rometatable(L, "softuart.port", (void *)softuart_port_map);
return 0;
}
NODEMCU_MODULE(SOFTUART, "softuart", softuart, luaopen_softuart);

78
docs/modules/softuart.md Normal file
View File

@ -0,0 +1,78 @@
# SoftUART Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
|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.
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.
!!! note
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.
#### Syntax
`softuart.setup(baudrate, txPin, rxPin)`
#### Parameters
- `baudrate`: SoftUART baudrate. Maximum supported is 230400.
- `txPin`: SoftUART tx pin. If set to `nil` `write` method will not be supported.
- `rxPin`: SoftUART rx pin. If set to `nil` `on("data")` method will not be supported.
#### Returns
`softuart` instance.
#### Example
```lua
-- Create new software UART with baudrate of 9600, D2 as Tx pin and D3 as Rx pin
s = softuart.setup(9600, 2, 3)
```
# SoftUART port
## softuart.port:on()
Sets up the callback function to receive data.
#### Syntax
`softuart.port:on(event, trigger, function(data))`
#### Parameters
- `event`: Event name. Currently only `data` is supported.
- `trigger`: Can be a character or a number. If character is set, the callback function will only be run when that character gets received. When a number is set, the callback function will only be run when buffer will have as many characters as number.
- `function(data)`: Callback function. the `data` parameter is software UART receiving buffer.
#### Returns
`nil`
#### Example
```lua
-- Create new software UART with baudrate of 9600, D2 as Tx pin and D3 as Rx pin
s = softuart.setup(9600, 2, 3)
s:on("data", 10, function(data)
print("Lua handler called!")
print(data)
end)
```
## softuart.port:write()
Transmits a byte or sequence of them.
#### Syntax
`softuart.port:write(data)`
#### Parameters
- `data`: Can be a number or string. When a number is passed, only one byte will be sent. When a string is passed, whole sequence will be transmitted.
#### Returns
`nil`
#### Example
```lua
-- 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!")
```

View File

@ -99,6 +99,7 @@ pages:
- 'sigma delta': 'modules/sigma-delta.md'
- 'sjson': 'modules/sjson.md'
- 'sntp': 'modules/sntp.md'
- 'softuart': 'modules/softuart.md'
- 'somfy': 'modules/somfy.md'
- 'spi': 'modules/spi.md'
- 'sqlite3': 'modules/sqlite3.md'