Interrupt driven stdin UART driver

This commit is contained in:
Johan Kanflo 2015-10-26 14:30:45 +01:00
parent 8f6c297849
commit 3b194e8894
8 changed files with 161 additions and 71 deletions

View file

@ -52,7 +52,7 @@ long _write_r(struct _reent *r, int fd, const char *ptr, int len )
}
/* syscall implementation for stdio read from UART */
long _read_r( struct _reent *r, int fd, char *ptr, int len )
__attribute__ ((weak)) long _read_r( struct _reent *r, int fd, char *ptr, int len )
{
int ch, i;

View file

@ -1,3 +1,3 @@
PROGRAM=terminal
EXTRA_COMPONENTS=extras/serial-driver
EXTRA_COMPONENTS=extras/stdin_uart_interrupt
include ../../common.mk

View file

@ -1,6 +1,6 @@
/* Serial terminal example
* UART RX is interrupt driven
* Read characters until \n and echo back
* Implements a simple GPIO terminal for setting and clearing GPIOs
*
* This sample code is in the public domain.
*/
@ -8,30 +8,110 @@
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include <espressif/esp_common.h>
#include <serial_driver.h>
#include <string.h>
#include <esp8266.h>
#include <esp/uart.h>
#include "FreeRTOS.h"
#include "task.h"
#define MAX_ARGC (10)
#define BUFFER_SIZE (81)
static void cmd_on(uint32_t argc, char *argv[])
{
if (argc >= 2) {
for(int i=1; i<argc; i++) {
uint8_t gpio_num = atoi(argv[i]);
gpio_enable(gpio_num, GPIO_OUTPUT);
gpio_write(gpio_num, true);
printf("On %d\n", gpio_num);
}
} else {
printf("Error: missing gpio number.\n");
}
}
static void cmd_off(uint32_t argc, char *argv[])
{
if (argc >= 2) {
for(int i=1; i<argc; i++) {
uint8_t gpio_num = atoi(argv[i]);
gpio_enable(gpio_num, GPIO_OUTPUT);
gpio_write(gpio_num, false);
printf("Off %d\n", gpio_num);
}
} else {
printf("Error: missing gpio number.\n");
}
}
static void cmd_help(uint32_t argc, char *argv[])
{
printf("on <gpio number> [ <gpio number>]+ Set gpio to 1\n");
printf("off <gpio number> [ <gpio number>]+ Set gpio to 0\n");
printf("sleep Take a nap\n");
printf("\nExample:\n");
printf(" on 0<enter> switches on gpio 0\n");
printf(" on 0 2 4<enter> switches on gpios 0, 2 and 4\n");
}
static void cmd_sleep(uint32_t argc, char *argv[])
{
printf("Type away while I take a 2 second nap (ie. let you test the UART HW FIFO\n");
vTaskDelay(2000 / portTICK_RATE_MS);
}
static void handle_command(char *cmd)
{
char *argv[MAX_ARGC];
int argc = 1;
char *temp, *rover;
memset((void*) argv, 0, sizeof(argv));
argv[0] = cmd;
rover = cmd;
// Split string "<command> <argument 1> <argument 2> ... <argument N>"
// into argv, argc style
while(argc < MAX_ARGC && (temp = strstr(rover, " "))) {
rover = &(temp[1]);
argv[argc++] = rover;
*temp = 0;
}
if (strlen(argv[0]) > 0) {
if (strcmp(argv[0], "help") == 0) cmd_help(argc, argv);
else if (strcmp(argv[0], "on") == 0) cmd_on(argc, argv);
else if (strcmp(argv[0], "off") == 0) cmd_off(argc, argv);
else if (strcmp(argv[0], "sleep") == 0) cmd_sleep(argc, argv);
else printf("Unknown command %s, try 'help'\n", argv[0]);
}
}
static void gpiomon()
{
char ch;
char cmd[81];
int i = 0;
printf("\n\n\nWelcome to gpiomon. Type 'help<enter>' for, well, help\n");
printf("%% ");
while(1) {
if (read(0, (void*)&ch, 1)) { // 0 is stdin
printf("%c", ch);
if (ch == '\n' || ch == '\r') {
cmd[i] = 0;
i = 0;
printf("\n");
handle_command((char*) cmd);
printf("%% ");
} else {
if (i < sizeof(cmd)) cmd[i++] = ch;
}
}
}
}
void user_init(void)
{
char buffer[BUFFER_SIZE];
uint32_t i = 0;
sdk_uart_div_modify(0, UART_CLK_FREQ / 115200);
while(1) {
char ch;
// The thread will block here until there is data available
// NB. read(...) may be called from user_init or from a thread
// We can check how many characters are available in the RX buffer
// with uint32_t uart0_num_char(void);
if (read(0, (void*)&ch, 1)) { // 0 is stdin
if (i == BUFFER_SIZE-2 || ch == '\n') {
buffer[i] = 0;
printf("Unknown command: %s\n", (char*) buffer);
i = 0;
} else {
buffer[i++] = ch;
}
}
}
uart_set_baud(0, 115200);
setbuf(stdout, NULL);
gpiomon();
}

View file

@ -1,11 +0,0 @@
# Component makefile for extras/serial-driver
#
# See examples/terminal for usage
INC_DIRS += $(ROOT)extras/serial-driver
# args for passing into compile rule generation
extras/serial-driver_INC_DIR = $(ROOT)extras/serial-driver
extras/serial-driver_SRC_DIR = $(ROOT)extras/serial-driver
$(eval $(call component_compile_rules,extras/serial-driver))

View file

@ -0,0 +1,11 @@
This module adds interrupt driven receive on UART 0. Using semaphores, a thread
calling read(...) when no data is available will block in an RTOS expected
manner until data arrives.
This allows for a background thread running a serial terminal in your program
for debugging and state inspection consuming no CPU cycles at all. Not using
this module will make that thread while(1) until data arrives.
No code changes are needed for adding this module, all you need to do is to add
it to EXTRA_COMPONENTS and add the directive configUSE_COUNTING_SEMAPHORES from
FreeRTOSConfig.h in examples/terminal to your project.

View file

@ -0,0 +1,13 @@
# Component makefile for extras/stdin_uart_interrupt
#
# See examples/terminal for usage. Well, actually there is no need to see it
# for 'usage' as this module is a drop-in replacement for the original polled
# version of reading from the UART.
INC_DIRS += $(ROOT)extras/stdin_uart_interrupt
# args for passing into compile rule generation
extras/stdin_uart_interrupt_INC_DIR = $(ROOT)extras/stdin_uart_interrupt
extras/stdin_uart_interrupt_SRC_DIR = $(ROOT)extras/stdin_uart_interrupt
$(eval $(call component_compile_rules,extras/stdin_uart_interrupt))

View file

@ -23,8 +23,6 @@
*/
#include <esp8266.h>
#include <espressif/esp8266/esp8266.h>
#include <espressif/esp8266/uart_register.h>
#include <FreeRTOS.h>
#include <semphr.h>
@ -35,64 +33,61 @@
// IRQ driven UART RX driver for ESP8266 written for use with esp-open-rtos
// TODO: Handle UART1
#define UART0_RX_SIZE (81)
#ifndef UART0
#define UART0 (0)
#endif
#define UART0_RX_SIZE (128) // ESP8266 UART HW FIFO size
static xSemaphoreHandle uart0_sem = NULL;
static char rx_buf[UART0_RX_SIZE];
static uint8_t rd_pos = 0;
static uint8_t wr_pos = 0;
static bool inited = false;
static void uart0_rx_init(void);
IRAM void uart0_rx_handler(void)
{
// TODO: Handle UART1, see reg 0x3ff20020, bit2, bit0 represents uart1 and uart0 respectively
if (UART_RXFIFO_FULL_INT_ST != (READ_PERI_REG(UART_INT_ST(UART0)) & UART_RXFIFO_FULL_INT_ST)) {
if (!UART(UART0).INT_STATUS & UART_INT_STATUS_RXFIFO_FULL) {
return;
}
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR);
while (READ_PERI_REG(UART_STATUS(UART0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) {
char ch = READ_PERI_REG(UART_FIFO(UART0)) & 0xff;
uint8_t wr_next = (wr_pos+1) % UART0_RX_SIZE;
if (wr_next != rd_pos) {
rx_buf[wr_pos] = ch;
wr_pos = (wr_pos+1) % UART0_RX_SIZE;
xSemaphoreGiveFromISR(uart0_sem, NULL);
// printf(" [%08x (%d)]\n", READ_PERI_REG(UART_INT_ST(UART0)), READ_PERI_REG(UART_STATUS(UART0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S));
if (UART(UART0).INT_STATUS & UART_INT_STATUS_RXFIFO_FULL) {
UART(UART0).INT_CLEAR = UART_INT_CLEAR_RXFIFO_FULL;
if (UART(UART0).STATUS & (UART_STATUS_RXFIFO_COUNT_M << UART_STATUS_RXFIFO_COUNT_S)) {
long int xHigherPriorityTaskWoken;
_xt_isr_mask(1 << INUM_UART);
_xt_clear_ints(1<<INUM_UART);
xSemaphoreGiveFromISR(uart0_sem, &xHigherPriorityTaskWoken);
if(xHigherPriorityTaskWoken) {
portYIELD();
}
}
} else {
printf("Error: unexpected uart irq, INT_STATUS 0x%02x\n", UART(UART0).INT_STATUS);
}
}
uint32_t uart0_num_char(void)
{
uint32_t count;
if (!inited) uart0_rx_init();
_xt_isr_mask(1 << INUM_UART);
if (rd_pos > wr_pos) count = rd_pos - wr_pos;
else count = wr_pos - rd_pos;
_xt_isr_unmask(1 << INUM_UART);
count = UART(UART0).STATUS & (UART_STATUS_RXFIFO_COUNT_M << UART_STATUS_RXFIFO_COUNT_S);
return count;
}
// _read_r in core/newlib_syscalls.c will be skipped in favour of this function
// _read_r in core/newlib_syscalls.c will be skipped by the linker in favour
// of this function
long _read_r(struct _reent *r, int fd, char *ptr, int len)
{
if (!inited) uart0_rx_init();
for(int i = 0; i < len; i++) {
char ch;
if (xSemaphoreTake(uart0_sem, portMAX_DELAY)) {
_xt_isr_mask(1 << INUM_UART);
ch = rx_buf[rd_pos];
rd_pos = (rd_pos+1) % UART0_RX_SIZE;
if (!(UART(UART0).STATUS & (UART_STATUS_RXFIFO_COUNT_M << UART_STATUS_RXFIFO_COUNT_S))) {
_xt_isr_unmask(1 << INUM_UART);
ptr[i] = ch;
if (!xSemaphoreTake(uart0_sem, portMAX_DELAY)) {
printf("\nFailed to get sem\n");
}
}
ptr[i] = UART(UART0).FIFO & (UART_FIFO_DATA_M << UART_FIFO_DATA_S);
}
return len;
}
@ -105,15 +100,18 @@ static void uart0_rx_init(void)
_xt_isr_unmask(1 << INUM_UART);
// clear rx and tx fifo,not ready
SET_PERI_REG_MASK(UART_CONF0(UART0), UART_RXFIFO_RST | UART_TXFIFO_RST);
CLEAR_PERI_REG_MASK(UART_CONF0(UART0), UART_RXFIFO_RST | UART_TXFIFO_RST);
uint32_t conf = UART(UART0).CONF0;
UART(UART0).CONF0 = conf | UART_CONF0_RXFIFO_RESET | UART_CONF0_TXFIFO_RESET;
UART(UART0).CONF0 = conf & ~(UART_CONF0_RXFIFO_RESET | UART_CONF0_TXFIFO_RESET);
// set rx fifo trigger
WRITE_PERI_REG(UART_CONF1(UART0), (trig_lvl & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S);
UART(UART0).CONF1 |= (trig_lvl & UART_CONF1_RXFIFO_FULL_THRESHOLD_M) << UART_CONF1_RXFIFO_FULL_THRESHOLD_S;
// clear all interrupts
UART(UART0).INT_CLEAR = 0x1ff;
//clear all interrupt
WRITE_PERI_REG(UART_INT_CLR(UART0), 0xffff);
// enable rx_interrupt
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_RXFIFO_FULL_INT_ENA);
UART(UART0).INT_ENABLE = UART_INT_ENABLE_RXFIFO_FULL;
inited = true;
}

View file

@ -22,12 +22,11 @@
* THE SOFTWARE.
*/
#ifndef __I2C_H__
#define __I2C_H__
#endif
#ifndef __STDIN_UART_INTERRUPT_H__
#define __STDIN_UART_INTERRUPT_H__
#include <stdint.h>
#include <stdbool.h>
// Return number of characters waiting in UART0
uint32_t uart0_num_char(void);
#endif