

5 changed files with 461 additions and 1 deletions
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Michael Andersen <m.andersen@berkeley.edu> |
||||
* |
||||
* This file is subject to the terms and conditions of the GNU Lesser |
||||
* General Public License v2.1. See the file LICENSE in the top level |
||||
* directory for more details. |
||||
*/ |
||||
|
||||
/**
|
||||
* @defgroup sys_rtt_stdio SEGGER RTT stdio |
||||
* @ingroup sys |
||||
* |
||||
* @brief stdio init/read/write functions for SEGGER RTT. This is |
||||
* designed to shadow the functions in uart_stdio |
||||
* |
||||
* @{ |
||||
* @file |
||||
* |
||||
* @author Michael Andersen <m.andersen@cs.berkeley.edu> |
||||
*/ |
||||
#ifndef RTT_STDIO_H |
||||
#define RTT_STDIO_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief initialize the module. This is a noop. |
||||
*/ |
||||
void uart_stdio_init(void); |
||||
|
||||
/**
|
||||
* @brief read @p len bytes from stdio uart into @p buffer |
||||
* |
||||
* @param[out] buffer buffer to read into |
||||
* @param[in] len nr of bytes to read |
||||
* |
||||
* @return nr of bytes read |
||||
* @return <0 on error |
||||
*/ |
||||
int uart_stdio_read(char* buffer, int len); |
||||
|
||||
/**
|
||||
* @brief write @p len bytes from @p buffer into uart |
||||
* |
||||
* @param[in] buffer buffer to read from |
||||
* @param[in] len nr of bytes to write |
||||
* |
||||
* @return nr of bytes written |
||||
* @return <0 on error |
||||
*/ |
||||
int uart_stdio_write(const char* buffer, int len); |
||||
|
||||
/**
|
||||
* @brief enable stdin polling, at a power consumption cost. This is enabled |
||||
* by default unless RTT_STDIO_DISABLE_STDIN is defined. |
||||
*/ |
||||
void rtt_stdio_enable_stdin(void); |
||||
|
||||
/**
|
||||
* @brief enable stdout blocking and free space polling. This must be done |
||||
* with caution because if there is no RTT client attached, all |
||||
* writes to stdout will block indefinitely. This can be enabled |
||||
* automatically by defining RTT_STDIO_ENABLE_BLOCKING_STDOUT |
||||
*/ |
||||
void rtt_stdio_enable_blocking_stdout(void); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
/** @} */ |
||||
#endif /* RTT_STDIO_H */ |
@ -0,0 +1,45 @@
|
||||
# RTT STDIO |
||||
|
||||
This module will allow communication using SEGGER's Real Time Terminal protocol. |
||||
Briefly, it replaces UART stdio with a set of ringbuffers that are manipulated |
||||
over JTAG. There are several advantages to this system. The biggest is that |
||||
writing to stdout is extremely fast (as you are just copying to memory). This |
||||
is useful if you are adding print statements in timing-sensitive code as part |
||||
of debugging. The other advantage is that it frees your UART for other use |
||||
and enables stdio on platforms that do not have a UART. |
||||
|
||||
To use this module, add |
||||
|
||||
``` |
||||
USEMODULE += rtt_stdio |
||||
``` |
||||
|
||||
to your makefile. By default the module will drop bytes written to stdout if the |
||||
buffer is full. If you know for certain that the debugger is attached, you |
||||
can obtain lossless stdout by adding |
||||
|
||||
``` |
||||
CFLAGS += -DRTT_STDIO_ENABLE_BLOCKING_STDOUT |
||||
``` |
||||
|
||||
to your makefile. Note well that if you do NOT plug in the debugger and run |
||||
the SEGGER RTT software (or compatible software) this will then lock up the |
||||
system as it waits forever. Typically you would only define this during |
||||
development on the lab bench. |
||||
|
||||
If you are printing significant data out (pages a second), you can increase |
||||
your stdout bandwidth by lowering the poll interval. The default is 50ms. |
||||
A choice of 5ms is good during printf-heavy debugging: |
||||
|
||||
``` |
||||
CFLAGS += -DSTDIO_POLL_INTERVAL=5000U |
||||
``` |
||||
|
||||
SEGGER RTT supports stdin as well, and this is enabled by default. It requires |
||||
polling the stdin ringbuffer, however, which on low duty cycle systems |
||||
can increase the number of unnecessary wakeups from sleep. To disable stdin, |
||||
add this to your makefile: |
||||
|
||||
``` |
||||
CFLAGS += -DRTT_STDIO_DISABLE_STDIN |
||||
``` |
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Michael Andersen <m.andersen@cs.berkeley.edu> |
||||
* |
||||
* This file was based on SEGGER's reference implementation |
||||
* which can be found here: https://www.segger.com/jlink-rtt.html
|
||||
* (c) 2014-2016 SEGGER Microcontroller GmbH & Co. KG |
||||
* This implementation bore the following license notes: |
||||
********************************************************************** |
||||
* SEGGER MICROCONTROLLER GmbH & Co. KG * |
||||
* Solutions for real time microcontroller applications * |
||||
********************************************************************** |
||||
* * |
||||
* (c) 2014 - 2016 SEGGER Microcontroller GmbH & Co. KG * |
||||
* * |
||||
* www.segger.com Support: support@segger.com * |
||||
* * |
||||
********************************************************************** |
||||
* * |
||||
* SEGGER RTT * Real Time Transfer for embedded targets * |
||||
* * |
||||
********************************************************************** |
||||
* * |
||||
* All rights reserved. * |
||||
* * |
||||
* SEGGER strongly recommends to not make any changes * |
||||
* to or modify the source code of this software in order to stay * |
||||
* compatible with the RTT protocol and J-Link. * |
||||
* * |
||||
* Redistribution and use in source and binary forms, with or * |
||||
* without modification, are permitted provided that the following * |
||||
* conditions are met: * |
||||
* * |
||||
* o Redistributions of source code must retain the above copyright * |
||||
* notice, this list of conditions and the following disclaimer. * |
||||
* * |
||||
* o Redistributions in binary form must reproduce the above * |
||||
* copyright notice, this list of conditions and the following * |
||||
* disclaimer in the documentation and/or other materials provided * |
||||
* with the distribution. * |
||||
* * |
||||
* o Neither the name of SEGGER Microcontroller GmbH & Co. KG * |
||||
* nor the names of its contributors may be used to endorse or * |
||||
* promote products derived from this software without specific * |
||||
* prior written permission. * |
||||
* * |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * |
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * |
||||
* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * |
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * |
||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * |
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * |
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * |
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * |
||||
* DAMAGE. * |
||||
* * |
||||
**********************************************************************/ |
||||
|
||||
|
||||
/**
|
||||
* @ingroup sys |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief SEGGER RTT stdio implementation |
||||
* |
||||
* This file implements UART read/write functions, but it |
||||
* is actually a virtual UART backed by a ringbuffer that |
||||
* complies with SEGGER RTT. It is designed to shadow |
||||
* uart_stdio that is used by newlib. |
||||
* |
||||
* @author Michael Andersen <m.andersen@cs.berkeley.edu> |
||||
* |
||||
* @} |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <rtt_stdio.h> |
||||
|
||||
#include "thread.h" |
||||
#include "mutex.h" |
||||
#include "xtimer.h" |
||||
|
||||
/* This parameter affects the bandwidth of both input and output. Decreasing
|
||||
it will significantly improve bandwidth at the cost of CPU time. */ |
||||
#ifndef STDIO_POLL_INTERVAL |
||||
#define STDIO_POLL_INTERVAL 50000U |
||||
#endif |
||||
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b)) |
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
||||
|
||||
#ifndef STDIO_TX_BUFSIZE |
||||
#define STDIO_TX_BUFSIZE (512) |
||||
#endif |
||||
|
||||
#ifndef STDIO_RX_BUFSIZE |
||||
#define STDIO_RX_BUFSIZE (32) |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief use mutex for waiting on stdin being enabled |
||||
*/ |
||||
static mutex_t _rx_mutex = MUTEX_INIT; |
||||
|
||||
/**
|
||||
* @brief buffer holding stdout |
||||
*/ |
||||
static char up_buffer [STDIO_TX_BUFSIZE]; |
||||
|
||||
/**
|
||||
* @brief buffer holding stdin |
||||
*/ |
||||
static char down_buffer [STDIO_RX_BUFSIZE]; |
||||
|
||||
/**
|
||||
* @brief flag that enables stdin polling |
||||
*/ |
||||
static char stdin_enabled = 0; |
||||
|
||||
/**
|
||||
* @brief flag that enables stdout blocking/polling |
||||
*/ |
||||
static char blocking_stdout = 0; |
||||
|
||||
/**
|
||||
* @brief SEGGER's ring buffer implementation |
||||
*/ |
||||
typedef struct { |
||||
const char* channel_name; /* Optional name. Standard names so far are:
|
||||
"Terminal", "VCom" */ |
||||
char* buf_ptr; /* Pointer to start of buffer */ |
||||
int32_t buf_size; /* Buffer size in bytes. Note that one byte is
|
||||
lost, as this implementation does not fill up |
||||
the buffer in order to avoid the problem of |
||||
being unable to distinguish between full and |
||||
empty. */ |
||||
volatile int32_t wr_off; /* Position of next item to be written by either
|
||||
host (down-buffer) or target (up-buffer). Must |
||||
be volatile since it may be modified by host |
||||
(down-buffer) */ |
||||
volatile int32_t rd_off; /* Position of next item to be read by target
|
||||
(down-buffer) or host (up-buffer). Must be |
||||
volatile since it may be modified by host |
||||
(up-buffer) */ |
||||
int32_t flags; /* Contains configuration flags */ |
||||
} rtt_ringbuf_t; |
||||
|
||||
/**
|
||||
* @brief RTT control block which describes the number of buffers available |
||||
* as well as the configuration for each buffer. The struct definition |
||||
* is fixed, as it is expected by SEGGER's software |
||||
*/ |
||||
typedef struct { |
||||
char sentinel[16]; /* Initialized to "SEGGER RTT" */ |
||||
int32_t max_up_buffers; /* Initialized to 1 */ |
||||
int32_t max_down_buffers; /* Initialized to 1 */ |
||||
rtt_ringbuf_t up[1]; /* Up buffers, transferring information up
|
||||
from target via debug probe to host */ |
||||
rtt_ringbuf_t down[1]; /* Down buffers, transferring information
|
||||
down from host via debug probe to target */ |
||||
} segger_rtt_cb_t; |
||||
|
||||
|
||||
/**
|
||||
* @brief The SEGGER Real-Time-Terminal control block (CB) |
||||
*/ |
||||
static segger_rtt_cb_t rtt_cb = { |
||||
"SEGGER RTT", |
||||
1, |
||||
1, |
||||
{{ "Terminal", &up_buffer[0], sizeof(up_buffer), 0, 0, 0 }}, |
||||
{{ "Terminal", &down_buffer[0], sizeof(down_buffer), 0, 0, 0 }}, |
||||
}; |
||||
|
||||
|
||||
/**
|
||||
* @brief read bytes from the down buffer. This function does not block. |
||||
* The logic here is unmodified from SEGGER's reference, it is just |
||||
* refactored to match code style. The string is not null terminated. |
||||
* |
||||
* @return the number of bytes read |
||||
*/ |
||||
static int rtt_read(char* buf_ptr, uint16_t buf_size) { |
||||
int16_t num_bytes_rem; |
||||
uint16_t num_bytes_read; |
||||
int16_t rd_off; |
||||
int16_t wr_off; |
||||
|
||||
rd_off = rtt_cb.down[0].rd_off; |
||||
wr_off = rtt_cb.down[0].wr_off; |
||||
num_bytes_read = 0; |
||||
|
||||
/* Read from current read position to wrap-around of buffer, first */ |
||||
if (rd_off > wr_off) { |
||||
num_bytes_rem = rtt_cb.down[0].buf_size - rd_off; |
||||
num_bytes_rem = MIN(num_bytes_rem, (int)buf_size); |
||||
memcpy(buf_ptr, rtt_cb.down[0].buf_ptr + rd_off, num_bytes_rem); |
||||
num_bytes_read += num_bytes_rem; |
||||
buf_ptr += num_bytes_rem; |
||||
buf_size -= num_bytes_rem; |
||||
rd_off += num_bytes_rem; |
||||
/* Handle wrap-around of buffer */ |
||||
if (rd_off == rtt_cb.down[0].buf_size) { |
||||
rd_off = 0; |
||||
} |
||||
} |
||||
|
||||
/* Read remaining items of buffer */ |
||||
num_bytes_rem = wr_off - rd_off; |
||||
num_bytes_rem = MIN(num_bytes_rem, (int)buf_size); |
||||
if (num_bytes_rem > 0) { |
||||
memcpy(buf_ptr, rtt_cb.down[0].buf_ptr + rd_off, num_bytes_rem); |
||||
num_bytes_read += num_bytes_rem; |
||||
rd_off += num_bytes_rem; |
||||
} |
||||
if (num_bytes_read) { |
||||
rtt_cb.down[0].rd_off = rd_off; |
||||
} |
||||
return num_bytes_read; |
||||
} |
||||
|
||||
/**
|
||||
* @brief write bytes to the up buffer. This function does not block. |
||||
* The logic here is unmodified from SEGGER's reference, it is just |
||||
* refactored to match code style. The string does not need to be null |
||||
* terminated. |
||||
* |
||||
* @return the number of bytes read |
||||
*/ |
||||
int rtt_write(const char* buf_ptr, unsigned num_bytes) { |
||||
int num_bytes_to_write; |
||||
unsigned num_bytes_written; |
||||
int rd_off; |
||||
|
||||
rd_off = rtt_cb.up[0].rd_off; |
||||
num_bytes_to_write = rd_off - rtt_cb.up[0].wr_off - 1; |
||||
if (num_bytes_to_write < 0) { |
||||
num_bytes_to_write += rtt_cb.up[0].buf_size; |
||||
} |
||||
/* If the complete data does not fit in the buffer, trim the data */ |
||||
if ((int)num_bytes > num_bytes_to_write) { |
||||
num_bytes = num_bytes_to_write; |
||||
} |
||||
|
||||
/* Early out if there is nothing to do */ |
||||
if (num_bytes == 0) { |
||||
return 0; |
||||
} |
||||
|
||||
/* Write data to buffer and handle wrap-around if necessary */ |
||||
num_bytes_written = 0; |
||||
do { |
||||
/* May be changed by host (debug probe) in the meantime */ |
||||
rd_off = rtt_cb.up[0].rd_off; |
||||
num_bytes_to_write = rd_off - rtt_cb.up[0].wr_off - 1; |
||||
if (num_bytes_to_write < 0) { |
||||
num_bytes_to_write += rtt_cb.up[0].buf_size; |
||||
} |
||||
/* Number of bytes that can be written until buffer wrap-around */ |
||||
num_bytes_to_write = MIN(num_bytes_to_write, (rtt_cb.up[0].buf_size - |
||||
rtt_cb.up[0].wr_off)); |
||||
num_bytes_to_write = MIN(num_bytes_to_write, (int)num_bytes); |
||||
memcpy(rtt_cb.up[0].buf_ptr + rtt_cb.up[0].wr_off, buf_ptr, num_bytes_to_write); |
||||
num_bytes_written += num_bytes_to_write; |
||||
buf_ptr += num_bytes_to_write; |
||||
num_bytes -= num_bytes_to_write; |
||||
rtt_cb.up[0].wr_off += num_bytes_to_write; |
||||
if (rtt_cb.up[0].wr_off == rtt_cb.up[0].buf_size) { |
||||
rtt_cb.up[0].wr_off = 0; |
||||
} |
||||
} while (num_bytes); |
||||
return num_bytes_written; |
||||
} |
||||
|
||||
void uart_stdio_init(void) { |
||||
#ifndef RTT_STDIO_DISABLE_STDIN |
||||
stdin_enabled = 1; |
||||
#endif |
||||
|
||||
#ifdef RTT_STDIO_ENABLE_BLOCKING_STDOUT |
||||
blocking_stdout = 1; |
||||
#endif |
||||
|
||||
/* the mutex should start locked */ |
||||
mutex_lock(&_rx_mutex); |
||||
} |
||||
|
||||
void rtt_stdio_enable_stdin(void) { |
||||
stdin_enabled = 1; |
||||
mutex_unlock(&_rx_mutex); |
||||
} |
||||
|
||||
void rtt_stdio_enable_blocking_stdout(void) { |
||||
blocking_stdout = 1; |
||||
} |
||||
|
||||
/* The reason we have this strange logic is as follows:
|
||||
If we have an RTT console, we are powered, and so don't care |
||||
that polling uses a lot of power. If however, we do not |
||||
actually have an RTT console (because we are deployed on |
||||
a battery somewhere) then we REALLY don't want to poll |
||||
especially since we are not expecting to EVER get input. */ |
||||
int uart_stdio_read(char* buffer, int count) { |
||||
int res = rtt_read(buffer, count); |
||||
if (res == 0) { |
||||
if (!stdin_enabled) { |
||||
mutex_lock(&_rx_mutex); |
||||
/* We only unlock when rtt_stdio_enable_stdin is called
|
||||
Note that we assume only one caller invoked this function */ |
||||
} |
||||
uint32_t last_wakeup = xtimer_now(); |
||||
while(1) { |
||||
xtimer_periodic_wakeup(&last_wakeup, STDIO_POLL_INTERVAL); |
||||
res = rtt_read(buffer, count); |
||||
if (res > 0) |
||||
return res; |
||||
} |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
int uart_stdio_write(const char* buffer, int len) { |
||||
int written = rtt_write(buffer, len); |
||||
uint32_t last_wakeup = xtimer_now(); |
||||
while (blocking_stdout && written < len) { |
||||
xtimer_periodic_wakeup(&last_wakeup, STDIO_POLL_INTERVAL); |
||||
written += rtt_write(&buffer[written], len-written); |
||||
} |
||||
return written; |
||||
} |
Loading…
Reference in new issue