Skip to content

Commit 3ca6b84

Browse files
authored
Merge pull request #713 from ourairquality/tsoftuart
tsoftuart: add a softare timer base UART driver, and example.
2 parents 7d2a1d4 + 5ab0d05 commit 3ca6b84

File tree

8 files changed

+349
-0
lines changed

8 files changed

+349
-0
lines changed

examples/tsoftuart/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
!local.mk
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#define configUSE_TRACE_FACILITY 1
2+
#define configGENERATE_RUN_TIME_STATS 1
3+
#define portGET_RUN_TIME_COUNTER_VALUE() (RTC.COUNTER)
4+
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() {}
5+
6+
/* Use the defaults for everything else */
7+
#include_next<FreeRTOSConfig.h>

examples/tsoftuart/Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Makefile for tsfotuart example
2+
PROGRAM=tsoftuart
3+
EXTRA_COMPONENTS=extras/dhcpserver extras/wificfg extras/mactimer extras/tsoftuart
4+
5+
# For the mDNS responder included with lwip:
6+
EXTRA_CFLAGS += -DLWIP_MDNS_RESPONDER=1 -DLWIP_NUM_NETIF_CLIENT_DATA=1 -DLWIP_NETIF_EXT_STATUS_CALLBACK=1
7+
8+
# Avoid writing the wifi state to flash when using wificfg.
9+
EXTRA_CFLAGS += -DWIFI_PARAM_SAVE=0
10+
11+
EXTRA_CFLAGS += -DWIFICFG_CLIENT_TASK=1 -DWIFICFG_IRAM_TEST=1
12+
13+
include ../../common.mk

examples/tsoftuart/local.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FLASH_SIZE ?= 32

examples/tsoftuart/tsoftuart.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Example timer based software UART drive.
3+
*
4+
* Copyright (C) 2019 OurAirQuality.org
5+
*
6+
* Licensed under the Apache License, Version 2.0, January 2004 (the
7+
* "License"); you may not use this file except in compliance with the
8+
* License. You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/
10+
*
11+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
12+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
13+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14+
* NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
15+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
* DEALINGS WITH THE SOFTWARE.
19+
*
20+
*/
21+
22+
#include <string.h>
23+
#include <ctype.h>
24+
25+
#include <espressif/esp_common.h>
26+
#include <espressif/user_interface.h>
27+
#include <esp/uart.h>
28+
#include <FreeRTOS.h>
29+
#include <task.h>
30+
31+
#include "lwip/sockets.h"
32+
33+
#include "wificfg/wificfg.h"
34+
#include "tsoftuart/tsoftuart.h"
35+
36+
static void tsoftuart_task(void *pvParameters)
37+
{
38+
/* Initialize the UART Tx. */
39+
uint32_t tx_pin = *(uint32_t *)pvParameters;
40+
struct tsoftuart *uart = tsoftuart_init(tx_pin, 9600);
41+
42+
for (;;) {
43+
/* Reset the timing error records. */
44+
uart->output_queue_error_low = 0;
45+
uart->output_queue_error_high = 0;
46+
47+
char str[] = "Hello 0123456789 abcdefghijklmnopqrstuvwxyz\r\n";
48+
for (size_t i = 0; i < strlen(str); i++) {
49+
tsoftuart_putc(uart, str[i]);
50+
}
51+
52+
/* Check the timing error. */
53+
if (uart->output_queue_error_high > 2 || uart->output_queue_error_low < -2) {
54+
tsoftuart_write(uart, "X\r\n", 3);
55+
}
56+
57+
vTaskDelay(200 / portTICK_PERIOD_MS);
58+
}
59+
}
60+
61+
void user_init(void)
62+
{
63+
uart_set_baud(0, 115200);
64+
printf("SDK version:%s\n", sdk_system_get_sdk_version());
65+
66+
wificfg_init(80, NULL);
67+
68+
/* Start two tasks writing to different pins. */
69+
70+
static uint32_t tx_pin1 = 1;
71+
xTaskCreate(&tsoftuart_task, "tsoftuart1", 256, &tx_pin1, 1, NULL);
72+
73+
static uint32_t tx_pin2 = 2;
74+
xTaskCreate(&tsoftuart_task, "tsoftuart2", 256, &tx_pin2, 1, NULL);
75+
}

extras/tsoftuart/component.mk

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Component makefile for extras/tsoftuart
2+
3+
# Expected anyone using tsoftuart includes it as 'tsoftuart/tsoftuart.h'
4+
INC_DIRS += $(tsoftuart_ROOT)..
5+
6+
# args for passing into compile rule generation
7+
tsoftuart_INC_DIR =
8+
tsoftuart_SRC_DIR = $(tsoftuart_ROOT)
9+
10+
$(eval $(call component_compile_rules,tsoftuart))

extras/tsoftuart/tsoftuart.c

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Software timer based UART driver.
3+
*
4+
* Copyright (C) 2018 to 2019 OurAirQuality.org
5+
*
6+
* Licensed under the Apache License, Version 2.0, January 2004 (the
7+
* "License"); you may not use this file except in compliance with the
8+
* License. You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/
10+
*
11+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
12+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
13+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14+
* NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
15+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
* DEALINGS WITH THE SOFTWARE.
19+
*
20+
*/
21+
22+
#include <stdint.h>
23+
#include <sys/types.h>
24+
#include <stdlib.h>
25+
#include <unistd.h>
26+
#include <esp/uart.h>
27+
#include <stdio.h>
28+
#include <espressif/esp_system.h>
29+
#include "FreeRTOS.h"
30+
#include "task.h"
31+
#include "tsoftuart/tsoftuart.h"
32+
33+
34+
/*
35+
* The design builds a sequence of UART output transitions - the delay between
36+
* each toggling of the output. This sequence is then followed by a timer and
37+
* the timer handler toggles the state and resets the timer for the next
38+
* transition until done. This design avoids the code having to spin to
39+
* implement delays, and avoid having to disable interrupts for more reliable
40+
* timing.
41+
*
42+
* The MAC timer interrupt is used here as it has a high priority which helps
43+
* keep accurate output transition times. The MAC timer interrupt is a NMI and
44+
* the handler needs to take care not to interact widely. The handler only
45+
* accesses the timer queue, which has already been initialized.
46+
*
47+
* This software UART is not completely reliable, but might suit debug output
48+
* or communication that has an error detection layer, and it is more reliable
49+
* at lower baud rates. While it can run up to 115200 baud it is not very
50+
* reliable at these higher rates. It is not uncommon for the MAC timer
51+
* handler to be delayed 20us, so at UART baud rates above 19200 errors are
52+
* expected. This driver attempts to measure the timing errors and this can be
53+
* used to help detect when timing errors have occurred.
54+
*/
55+
56+
static void IRAM output_handler(void *arg)
57+
{
58+
tsoftuart_t *uart = arg;
59+
uint32_t tail = uart->output_queue_tail;
60+
61+
do {
62+
uint32_t state = uart->output_queue_state ^ 1;
63+
uint64_t current = mactime_get_count();
64+
gpio_write(uart->tx_pin, state);
65+
uart->output_queue_state = state;
66+
67+
if (tail == 0) {
68+
// First transition.
69+
uart->output_start_time = current;
70+
uart->output_expected_time = current;
71+
}
72+
73+
/* The difference can be negative because the delay is skipped
74+
* if very short, see below. */
75+
int32_t err = current - uart->output_expected_time;
76+
if (err > uart->output_queue_error_high) {
77+
uart->output_queue_error_high = err;
78+
}
79+
if (err < uart->output_queue_error_low) {
80+
uart->output_queue_error_low = err;
81+
}
82+
83+
if (tail >= uart->output_queue_head) {
84+
// Done.
85+
uart->output_queue_tail = tail;
86+
uart->output_done = 1;
87+
return;
88+
}
89+
90+
/* Offset from the start. */
91+
uint32_t next = uart->output_queue[tail++];
92+
uint64_t target = uart->output_start_time + next;
93+
uart->output_expected_time = target;
94+
/* Target an earlier time, that would not give an error if
95+
* actually met, to give more room for the response delay. */
96+
target -= 4;
97+
int64_t diff = target - current;
98+
if (diff >= 0) {
99+
uart->output_queue_tail = tail;
100+
mactime_add_pending(&uart->output_mactimer, target);
101+
break;
102+
}
103+
} while(1);
104+
}
105+
106+
void tsoftuart_putc(tsoftuart_t *uart, uint8_t ch)
107+
{
108+
uart->output_queue_state = 1;
109+
gpio_write(uart->tx_pin, uart->output_queue_state);
110+
111+
uart->output_queue_head = 0;
112+
uart->output_queue_tail = 0;
113+
114+
uart->output_queue_error_high = 0;
115+
uart->output_queue_error_low = 0;
116+
117+
uart->output_done = 0;
118+
119+
uint32_t state = 0;
120+
uint32_t count = 1;
121+
size_t head = 0;
122+
uint32_t cumulative = 0;
123+
uint32_t td = uart->td;
124+
125+
126+
for (size_t i = 0; i < 8; i++) {
127+
if ((ch & 1) == state) {
128+
/* No change */
129+
count++;
130+
} else {
131+
cumulative += count * td;
132+
uart->output_queue[head++] = (cumulative + 128) >> 8;
133+
state ^= 1;
134+
count = 1;
135+
}
136+
ch >>= 1;
137+
}
138+
139+
if (state == 0) {
140+
cumulative += count * td;
141+
uart->output_queue[head++] = (cumulative + 128) >> 8;
142+
state ^= 1;
143+
count = 1;
144+
}
145+
146+
uart->output_queue_head = head;
147+
148+
/* Trigger the first transition in the future. */
149+
mactimer_arm(&uart->output_mactimer, 20);
150+
151+
/* Wait until the transmittions is expected to have completed. */
152+
uint32_t delay = (td * 11 + 128) >> 8;
153+
vTaskDelay(((delay / 1000) + portTICK_PERIOD_MS) / portTICK_PERIOD_MS);
154+
155+
/* Double check that it is done. There is a possibility that the timer has
156+
* failed to trigger, and this needed to be detected and the timer removed
157+
* from the pending list before retrying. */
158+
size_t i;
159+
for (i = 0; uart->output_done == 0 && i < 10; i++) {
160+
vTaskDelay(1);
161+
}
162+
163+
if (uart->output_done == 0) {
164+
/* Remove the timer. */
165+
mactimer_disarm(&uart->output_mactimer);
166+
/* Set the output high */
167+
gpio_write(uart->tx_pin, 1);
168+
}
169+
}
170+
171+
ssize_t tsoftuart_write(tsoftuart_t *uart, const void *ptr, size_t len)
172+
{
173+
for(int i = 0; i < len; i++) {
174+
tsoftuart_putc(uart, ((char *)ptr)[i]);
175+
}
176+
return len;
177+
}
178+
179+
tsoftuart_t *tsoftuart_init(uint8_t tx_pin, uint32_t baud_rate)
180+
{
181+
tsoftuart_t *uart = malloc(sizeof(tsoftuart_t));
182+
183+
if (uart) {
184+
uart->tx_pin = tx_pin;
185+
uart->td = 256000000 / baud_rate;
186+
gpio_enable(tx_pin, GPIO_OUTPUT);
187+
gpio_set_pullup(tx_pin, true, false);
188+
gpio_write(tx_pin, 1);
189+
mactimer_init();
190+
mactimer_setfn(&uart->output_mactimer, output_handler, uart);
191+
}
192+
193+
return uart;
194+
}
195+

extras/tsoftuart/tsoftuart.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Software timer based UART driver.
3+
*
4+
* Copyright (C) 2018 to 2019 OurAirQuality.org
5+
*
6+
* Licensed under the Apache License, Version 2.0, January 2004 (the
7+
* "License"); you may not use this file except in compliance with the
8+
* License. You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/
10+
*
11+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
12+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
13+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14+
* NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
15+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18+
* DEALINGS WITH THE SOFTWARE.
19+
*
20+
*/
21+
22+
#ifndef _TSOFTUART_H
23+
#define _TSOFTUART_H
24+
25+
#include "mactimer/mactimer.h"
26+
27+
typedef struct tsoftuart {
28+
uint32_t tx_pin;
29+
/* Bit time period in usec * 256 */
30+
uint32_t td;
31+
mactimer_t output_mactimer;
32+
uint32_t output_queue[16];
33+
volatile uint64_t output_start_time;
34+
volatile size_t output_queue_head;
35+
volatile size_t output_queue_tail;
36+
size_t output_queue_state;
37+
uint64_t output_expected_time;
38+
int32_t output_queue_error_high;
39+
int32_t output_queue_error_low;
40+
uint32_t output_done;
41+
} tsoftuart_t;
42+
43+
void tsoftuart_putc(tsoftuart_t *uart, uint8_t ch);
44+
ssize_t tsoftuart_write(tsoftuart_t *uart, const void *ptr, size_t len);
45+
tsoftuart_t *tsoftuart_init(uint8_t tx_pin, uint32_t baud_rate);
46+
47+
#endif /* _TSOFTUART_H */

0 commit comments

Comments
 (0)