Skip to content

Commit

Permalink
Merge pull request #713 from ourairquality/tsoftuart
Browse files Browse the repository at this point in the history
tsoftuart: add a softare timer base UART driver, and example.
  • Loading branch information
UncleRus committed Apr 11, 2019
2 parents 7d2a1d4 + 5ab0d05 commit 3ca6b84
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/tsoftuart/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!local.mk
7 changes: 7 additions & 0 deletions examples/tsoftuart/FreeRTOSConfig.h
Original file line number Diff line number Diff line change
@@ -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<FreeRTOSConfig.h>
13 changes: 13 additions & 0 deletions examples/tsoftuart/Makefile
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions examples/tsoftuart/local.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FLASH_SIZE ?= 32
75 changes: 75 additions & 0 deletions examples/tsoftuart/tsoftuart.c
Original file line number Diff line number Diff line change
@@ -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 <string.h>
#include <ctype.h>

#include <espressif/esp_common.h>
#include <espressif/user_interface.h>
#include <esp/uart.h>
#include <FreeRTOS.h>
#include <task.h>

#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);
}
10 changes: 10 additions & 0 deletions extras/tsoftuart/component.mk
Original file line number Diff line number Diff line change
@@ -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))
195 changes: 195 additions & 0 deletions extras/tsoftuart/tsoftuart.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <esp/uart.h>
#include <stdio.h>
#include <espressif/esp_system.h>
#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;
}

47 changes: 47 additions & 0 deletions extras/tsoftuart/tsoftuart.h
Original file line number Diff line number Diff line change
@@ -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 */

0 comments on commit 3ca6b84

Please sign in to comment.