From 5ab0d0576822557b2304ab2fcf437681ce053b40 Mon Sep 17 00:00:00 2001 From: Our Air Quality Date: Thu, 11 Apr 2019 23:13:25 +1000 Subject: [PATCH] tsoftuart: add a softare timer base UART driver, and example. --- examples/tsoftuart/.gitignore | 1 + examples/tsoftuart/FreeRTOSConfig.h | 7 + examples/tsoftuart/Makefile | 13 ++ examples/tsoftuart/local.mk | 1 + examples/tsoftuart/tsoftuart.c | 75 +++++++++++ extras/tsoftuart/component.mk | 10 ++ extras/tsoftuart/tsoftuart.c | 195 ++++++++++++++++++++++++++++ extras/tsoftuart/tsoftuart.h | 47 +++++++ 8 files changed, 349 insertions(+) create mode 100644 examples/tsoftuart/.gitignore create mode 100644 examples/tsoftuart/FreeRTOSConfig.h create mode 100644 examples/tsoftuart/Makefile create mode 100644 examples/tsoftuart/local.mk create mode 100644 examples/tsoftuart/tsoftuart.c create mode 100644 extras/tsoftuart/component.mk create mode 100644 extras/tsoftuart/tsoftuart.c create mode 100644 extras/tsoftuart/tsoftuart.h diff --git a/examples/tsoftuart/.gitignore b/examples/tsoftuart/.gitignore new file mode 100644 index 0000000..e067149 --- /dev/null +++ b/examples/tsoftuart/.gitignore @@ -0,0 +1 @@ +!local.mk diff --git a/examples/tsoftuart/FreeRTOSConfig.h b/examples/tsoftuart/FreeRTOSConfig.h new file mode 100644 index 0000000..baf25a4 --- /dev/null +++ b/examples/tsoftuart/FreeRTOSConfig.h @@ -0,0 +1,7 @@ +#define configUSE_TRACE_FACILITY 1 +#define configGENERATE_RUN_TIME_STATS 1 +#define portGET_RUN_TIME_COUNTER_VALUE() (RTC.COUNTER) +#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() {} + +/* Use the defaults for everything else */ +#include_next diff --git a/examples/tsoftuart/Makefile b/examples/tsoftuart/Makefile new file mode 100644 index 0000000..a37e702 --- /dev/null +++ b/examples/tsoftuart/Makefile @@ -0,0 +1,13 @@ +# Makefile for tsfotuart example +PROGRAM=tsoftuart +EXTRA_COMPONENTS=extras/dhcpserver extras/wificfg extras/mactimer extras/tsoftuart + +# For the mDNS responder included with lwip: +EXTRA_CFLAGS += -DLWIP_MDNS_RESPONDER=1 -DLWIP_NUM_NETIF_CLIENT_DATA=1 -DLWIP_NETIF_EXT_STATUS_CALLBACK=1 + +# Avoid writing the wifi state to flash when using wificfg. +EXTRA_CFLAGS += -DWIFI_PARAM_SAVE=0 + +EXTRA_CFLAGS += -DWIFICFG_CLIENT_TASK=1 -DWIFICFG_IRAM_TEST=1 + +include ../../common.mk diff --git a/examples/tsoftuart/local.mk b/examples/tsoftuart/local.mk new file mode 100644 index 0000000..c6c3129 --- /dev/null +++ b/examples/tsoftuart/local.mk @@ -0,0 +1 @@ +FLASH_SIZE ?= 32 diff --git a/examples/tsoftuart/tsoftuart.c b/examples/tsoftuart/tsoftuart.c new file mode 100644 index 0000000..44dee02 --- /dev/null +++ b/examples/tsoftuart/tsoftuart.c @@ -0,0 +1,75 @@ +/* + * Example timer based software UART drive. + * + * Copyright (C) 2019 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * 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 CONTRIBUTORS 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 WITH THE SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "lwip/sockets.h" + +#include "wificfg/wificfg.h" +#include "tsoftuart/tsoftuart.h" + +static void tsoftuart_task(void *pvParameters) +{ + /* Initialize the UART Tx. */ + uint32_t tx_pin = *(uint32_t *)pvParameters; + struct tsoftuart *uart = tsoftuart_init(tx_pin, 9600); + + for (;;) { + /* Reset the timing error records. */ + uart->output_queue_error_low = 0; + uart->output_queue_error_high = 0; + + char str[] = "Hello 0123456789 abcdefghijklmnopqrstuvwxyz\r\n"; + for (size_t i = 0; i < strlen(str); i++) { + tsoftuart_putc(uart, str[i]); + } + + /* Check the timing error. */ + if (uart->output_queue_error_high > 2 || uart->output_queue_error_low < -2) { + tsoftuart_write(uart, "X\r\n", 3); + } + + vTaskDelay(200 / portTICK_PERIOD_MS); + } +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + wificfg_init(80, NULL); + + /* Start two tasks writing to different pins. */ + + static uint32_t tx_pin1 = 1; + xTaskCreate(&tsoftuart_task, "tsoftuart1", 256, &tx_pin1, 1, NULL); + + static uint32_t tx_pin2 = 2; + xTaskCreate(&tsoftuart_task, "tsoftuart2", 256, &tx_pin2, 1, NULL); +} diff --git a/extras/tsoftuart/component.mk b/extras/tsoftuart/component.mk new file mode 100644 index 0000000..f8c0b70 --- /dev/null +++ b/extras/tsoftuart/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/tsoftuart + +# Expected anyone using tsoftuart includes it as 'tsoftuart/tsoftuart.h' +INC_DIRS += $(tsoftuart_ROOT).. + +# args for passing into compile rule generation +tsoftuart_INC_DIR = +tsoftuart_SRC_DIR = $(tsoftuart_ROOT) + +$(eval $(call component_compile_rules,tsoftuart)) diff --git a/extras/tsoftuart/tsoftuart.c b/extras/tsoftuart/tsoftuart.c new file mode 100644 index 0000000..dbcc7cf --- /dev/null +++ b/extras/tsoftuart/tsoftuart.c @@ -0,0 +1,195 @@ +/* + * Software timer based UART driver. + * + * Copyright (C) 2018 to 2019 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * 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 CONTRIBUTORS 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 WITH THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "FreeRTOS.h" +#include "task.h" +#include "tsoftuart/tsoftuart.h" + + +/* + * The design builds a sequence of UART output transitions - the delay between + * each toggling of the output. This sequence is then followed by a timer and + * the timer handler toggles the state and resets the timer for the next + * transition until done. This design avoids the code having to spin to + * implement delays, and avoid having to disable interrupts for more reliable + * timing. + * + * The MAC timer interrupt is used here as it has a high priority which helps + * keep accurate output transition times. The MAC timer interrupt is a NMI and + * the handler needs to take care not to interact widely. The handler only + * accesses the timer queue, which has already been initialized. + * + * This software UART is not completely reliable, but might suit debug output + * or communication that has an error detection layer, and it is more reliable + * at lower baud rates. While it can run up to 115200 baud it is not very + * reliable at these higher rates. It is not uncommon for the MAC timer + * handler to be delayed 20us, so at UART baud rates above 19200 errors are + * expected. This driver attempts to measure the timing errors and this can be + * used to help detect when timing errors have occurred. + */ + +static void IRAM output_handler(void *arg) +{ + tsoftuart_t *uart = arg; + uint32_t tail = uart->output_queue_tail; + + do { + uint32_t state = uart->output_queue_state ^ 1; + uint64_t current = mactime_get_count(); + gpio_write(uart->tx_pin, state); + uart->output_queue_state = state; + + if (tail == 0) { + // First transition. + uart->output_start_time = current; + uart->output_expected_time = current; + } + + /* The difference can be negative because the delay is skipped + * if very short, see below. */ + int32_t err = current - uart->output_expected_time; + if (err > uart->output_queue_error_high) { + uart->output_queue_error_high = err; + } + if (err < uart->output_queue_error_low) { + uart->output_queue_error_low = err; + } + + if (tail >= uart->output_queue_head) { + // Done. + uart->output_queue_tail = tail; + uart->output_done = 1; + return; + } + + /* Offset from the start. */ + uint32_t next = uart->output_queue[tail++]; + uint64_t target = uart->output_start_time + next; + uart->output_expected_time = target; + /* Target an earlier time, that would not give an error if + * actually met, to give more room for the response delay. */ + target -= 4; + int64_t diff = target - current; + if (diff >= 0) { + uart->output_queue_tail = tail; + mactime_add_pending(&uart->output_mactimer, target); + break; + } + } while(1); +} + +void tsoftuart_putc(tsoftuart_t *uart, uint8_t ch) +{ + uart->output_queue_state = 1; + gpio_write(uart->tx_pin, uart->output_queue_state); + + uart->output_queue_head = 0; + uart->output_queue_tail = 0; + + uart->output_queue_error_high = 0; + uart->output_queue_error_low = 0; + + uart->output_done = 0; + + uint32_t state = 0; + uint32_t count = 1; + size_t head = 0; + uint32_t cumulative = 0; + uint32_t td = uart->td; + + + for (size_t i = 0; i < 8; i++) { + if ((ch & 1) == state) { + /* No change */ + count++; + } else { + cumulative += count * td; + uart->output_queue[head++] = (cumulative + 128) >> 8; + state ^= 1; + count = 1; + } + ch >>= 1; + } + + if (state == 0) { + cumulative += count * td; + uart->output_queue[head++] = (cumulative + 128) >> 8; + state ^= 1; + count = 1; + } + + uart->output_queue_head = head; + + /* Trigger the first transition in the future. */ + mactimer_arm(&uart->output_mactimer, 20); + + /* Wait until the transmittions is expected to have completed. */ + uint32_t delay = (td * 11 + 128) >> 8; + vTaskDelay(((delay / 1000) + portTICK_PERIOD_MS) / portTICK_PERIOD_MS); + + /* Double check that it is done. There is a possibility that the timer has + * failed to trigger, and this needed to be detected and the timer removed + * from the pending list before retrying. */ + size_t i; + for (i = 0; uart->output_done == 0 && i < 10; i++) { + vTaskDelay(1); + } + + if (uart->output_done == 0) { + /* Remove the timer. */ + mactimer_disarm(&uart->output_mactimer); + /* Set the output high */ + gpio_write(uart->tx_pin, 1); + } +} + +ssize_t tsoftuart_write(tsoftuart_t *uart, const void *ptr, size_t len) +{ + for(int i = 0; i < len; i++) { + tsoftuart_putc(uart, ((char *)ptr)[i]); + } + return len; +} + +tsoftuart_t *tsoftuart_init(uint8_t tx_pin, uint32_t baud_rate) +{ + tsoftuart_t *uart = malloc(sizeof(tsoftuart_t)); + + if (uart) { + uart->tx_pin = tx_pin; + uart->td = 256000000 / baud_rate; + gpio_enable(tx_pin, GPIO_OUTPUT); + gpio_set_pullup(tx_pin, true, false); + gpio_write(tx_pin, 1); + mactimer_init(); + mactimer_setfn(&uart->output_mactimer, output_handler, uart); + } + + return uart; +} + diff --git a/extras/tsoftuart/tsoftuart.h b/extras/tsoftuart/tsoftuart.h new file mode 100644 index 0000000..e1a9250 --- /dev/null +++ b/extras/tsoftuart/tsoftuart.h @@ -0,0 +1,47 @@ +/* + * Software timer based UART driver. + * + * Copyright (C) 2018 to 2019 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * 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 CONTRIBUTORS 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 WITH THE SOFTWARE. + * + */ + +#ifndef _TSOFTUART_H +#define _TSOFTUART_H + +#include "mactimer/mactimer.h" + +typedef struct tsoftuart { + uint32_t tx_pin; + /* Bit time period in usec * 256 */ + uint32_t td; + mactimer_t output_mactimer; + uint32_t output_queue[16]; + volatile uint64_t output_start_time; + volatile size_t output_queue_head; + volatile size_t output_queue_tail; + size_t output_queue_state; + uint64_t output_expected_time; + int32_t output_queue_error_high; + int32_t output_queue_error_low; + uint32_t output_done; +} tsoftuart_t; + +void tsoftuart_putc(tsoftuart_t *uart, uint8_t ch); +ssize_t tsoftuart_write(tsoftuart_t *uart, const void *ptr, size_t len); +tsoftuart_t *tsoftuart_init(uint8_t tx_pin, uint32_t baud_rate); + +#endif /* _TSOFTUART_H */