问题
I am making a class that inherits std::streambuf
(to asynchronously write to a UART). I need to be able to tell in my class whenever the std::ostream
that holds the streambuf
writes characters to it (so I can enable the "write ready" interrupt and actually write the data). I was under the impression that I just needed to override xsputn()
, but that does not seem to get called.
I could:
- Keep the interrupt enabled (inefficient)
- Use
std::endl
to callstd::streambuf::sync()
(blocking, ugly) - Expose another function to start the write (very ugly)
- Inherit
std::ostream
(a lot of work) - Something else
From a design standpoint, what is the "right" way to do this?
Code:
#include <algorithm>
#include <ostream>
extern "C" void UART0_IRQHandler(void);
#define UART_BUFLEN 128
class uartbuf : public std::streambuf
{
public:
uartbuf(/*hardware stuff*/);
~uartbuf() {};
protected:
int sync();
std::streambuf::int_type overflow(std::streambuf::int_type ch);
std::streamsize xsputn(const char* s, std::streamsize count);
private:
UART_MemMapPtr regs;
char buffer[UART_BUFLEN];
uint8_t fifo_depth;
bool empty() {return pbase() == pptr();}
uint8_t fifo_space() {return /*hardware stuff*/;}
void adjust();
void write_some();
uartbuf(const uartbuf&);
friend void UART0_IRQHandler(void);
friend void UART_Init();
};
//global instances
uartbuf uart0_sb(/*hardware stuff*/);
std::ostream uart0(&uart0_sb);
//buffer management...
uartbuf::uartbuf(/*hardware stuff*/)
: regs(r), fifo_depth(1)
{
setp(buffer, buffer, buffer + UART_BUFLEN);
//A bunch of hardware setup
}
//move back to the start of the buffer
void uartbuf::adjust()
{
if (pbase() == buffer)
return;
//move unwritten characters to beginning of buffer
std::copy(pbase(), pptr(), buffer);
//(pptr - pbase) stays the same, same number of characters
setp(buffer, buffer + (pptr() - pbase()), epptr());
}
//flush the entire buffer
int uartbuf::sync()
{
while (!empty())
write_some();
return 0; //always succeeds
}
std::streambuf::int_type uartbuf::overflow(std::streambuf::int_type ch)
{
//entirely full, can't adjust yet
if (pbase() == buffer)
write_some();
adjust();
//this is guaranteed to not call overflow again
if (ch != std::streambuf::traits_type::eof())
sputc(ch);
return 1; //always succeeds
}
//hardware management...
//writes at least one character
void uartbuf::write_some()
{
//spin until there is some room
while(!fifo_space()) ;
while(!empty() && fifo_space())
{
//clear interrupt flag
clear_interrupt();
write_char_to_fifo(*pbase());
setp(pbase() + 1, pptr(), epptr());
}
//don't generate any more TDRE interrupts if the buffer is empty
if (empty())
turn_off_interrupt();
}
std::streamsize uartbuf::xsputn(const char* s, std::streamsize count)
{
//don't need to do anything special with the data
std::streamsize result = std::streambuf::xsputn(s, count);
//start the TDRE interrupt cycling
turn_on_interrupt();
return result;
}
extern "C" void UART0_IRQHandler(void)
{
//it's a TDRE interrupt
if (/*it's the interrupt we want*/)
uart0_sb.write_some(); //this won't block
}
回答1:
You need to override overflow()
, and nothing else. On
initialization, std::streambuf
sets the buffer pointers to
nullptr
; if you don't actively change this, overflow
will be
called for every character which is output. (You use setp
to
set the buffer.)
Whether you want to activate input for every character or not,
I don't know. You mention "asynchrously write" at one point.
This would suggest more than one buffer. In sync
, you start
the output for the current buffer, and set things up to use the
next. At some point, you will also need to check that the
asynchronous output has finished, in order to recycle the
buffer.
来源:https://stackoverflow.com/questions/23295693/intercept-all-writes-to-a-stdstreambuf