问题
Am I complicating things?
I'm architecting my code to talk from a 8051 micro to a peripheral device over UART. The peripheral responds to commands from the host and can only respond to one command at a time. It's a simple send and receive protocol. (tx1, rx1, tx2, rx2, tx3, rx3) Each TX message is terminated with a CR, each response is terminated with a >. I can't send a new message until I receive the response to the last one. Responses can also echo print the original TX message in the beginning if I enable that option (but this causes more traffic)
An example message would be:
- TX: Hello
- RX: World!>
Or with echo option...
- TX: Hello
- RX: Hello\rWorld!>
Option A A function such as getHello would consist of both the send and receive. A parallel ISR routine would gather the incoming bytes and throw a flag when the '>' character is received.
char* getHello(char * buf){
sendMsg("Hello\r");
delay(10ms); //wait a little bit
//wait for receive to come in or timeout to occur
while(!receiveFlag || !timeoutFlag); //thrown by ISR
receiveMsg(buf);
//parse the message and do some other stuff
return buf;
}
Pros:
- Everything is contained within one function.
- Easier to debug
Cons:
- This function is blocking and could hang if the peripheral never responds so a timeout must be implemented.
- Messages can't be received out of order, must be in series (ie, tx1, rx1, tx2, rx2, tx3, rx3)
Option B A parallel approach is taken. Two separate functions would created. one to send the message, and one that would vertexed upon receiving a response from the ISR.
void sendHello(){
sendMsg("Hello\r");
//do some other stuff if needed
}
char* receiveMsg(char * buf){
//figure out from echo print what the tx message was
//use a switch statement to decide which response parser to call
switch(txMessage){ //pseudo code
case "Hello":
receiveMsg(buf);
//parse the message and do some other stuff
break;
}
return buf;
}
Pros:
- Can handle parallel messages coming back out of order because it relies on the echo printing of the tx message to figure out how to parse it. (ie, tx1, tx2, tx3, rx1,rx2,rx3)
Cons:
- quite difficult to debug
- spawns multiple threads
- lots of extra code
- not worth it since messages will definitely come back in order
Right now, I'm doing Option B, but as I continue on with the project, I begin to feel like this is getting overly complex. I'm curious what you guys think.
Thanks!
回答1:
I tend to do this kind of stuff, though, Id tend to have a separate serial port "class" ( struct + functions ) and a protocol class that lives over the top of serial port. I used these all the time in my embedded systems. This gives you the best of both worlds, a blocking synchronous call and an async call so you can pseudo multitask.
typedef struct serial_port_s serial_port;
typedef void (*serial_on_recived_proc)(serial_port* p);
typedef struct serial_port_s{
bool timeoutFlag;
bool receiveFlag;
void* context;
serial_on_recived_proc response_handler;
};
void send_serial(serial_port* p, char* message)
{
//SendMsg?
}
void receive_serial(serial_port* p, char* response)
{
//receiveMsg?
}
bool has_data(serial_port* p)
{
return p->receiveFlag;
}
bool has_timed_out(serial_port* p)
{
return p->timeoutFlag;
}
bool is_serial_finished(serial_port* p)
{
return has_data(p) || has_timed_out(p);
}
bool serial_check(serial_port* p)
{
if(is_serial_finished(p) && p->response_handler != NULL)
{
p->response_handler(p)
p-> response_handler = NULL;
return true;
}
return false;
}
void send(serial_port* p, char* message, char* response)
{
p->response_handler=NULL;
send_serial(p, message);
while(!is_serial_finished(p));
receive_serial(p, response);
}
void sendAsync(serial_port* p, char* message, serial_on_recived_proc handler, void* context)
{
p->response_handler = handler;
p->context = context;
send_serial(p, message);
}
void pow_response(serial_port* p)
{
// could pass a pointer to a struct, or anything depending on what you want to do
char* r = (char*)p->context;
receive_serial(p, r);
// do stuff with the pow response
}
typedef struct
{
char text[100];
int x;
bool has_result;
} bang_t;
void bang_parse(bang_t* bang)
{
bang->x = atoi(bang->text);
}
void bang_response(serial_port* p)
{
bang_t* bang = (bang_t*)p->context;
receive_serial(p, bang->text);
bang_parse(bang);
bang->has_result=true;
}
void myFunc();
{
char response[100];
char pow[100];
bang_t bang1;
bang_t bang2;
serial_port p; //
int state = 1;
// whatever you need to do to set the serial port
// sends and blocks till a response/timeout
send(&p, "Hello", response);
// do what you like with the response
// alternately, lets do an async send...
sendAsync(&p, "Pow", pow_response, pow);
while(true)
{
// non block check, will process the response when it arrives
if(serial_check(p))
{
// it has responded to something, we can send something else...
// using a very simple state machine, work out what to send next.
// in practice I'd use enum for states, and functions for managing state
// transitions, but for this example I'm just using an int which
// I just increment to move to the next state
switch(state)
{
case 1:
// bang1 is the context, and will receive the data
sendAsync(&p, "Bang1", bang_response, &bang1);
state++;
break;
case 2:
// now bang2 is the context and will get the data...
sendAsync(&p, "Bang2", bang_response, &bang2);
state++;
break;
default:
//nothing more to send....
break;
}
}
// do other stuff you want to do in parallel
}
};
回答2:
Take things simple. An ISR routine must be very fast so for me the best approch is to have a global RXBuffer like this:
#include <cstdint>
#include <deque>
#include <algorithm>
class RXBuffer {
public:
friend class ISR;
typedef std::deque<uint8_t>::const_iterator const_iterator;
RXBuffer();
size_t size() const { m_buffer.size(); }
// read from the buffer in a container in the range [first, last)
template <typename Iterator>
void read(Iterator first, Iterator last, Iterator to)
{
// how many bytes do you want to read?
size_t bytes_to_read = std::distance(first, last);
if (bytes_to_read >= size())
{
// read the whole buffer
std::copy(begin(), end(), first);
// empty the buffer
m_buffer.clear();
return size();
}
else
{
// copy the data
copy(begin(), begin() + bytes_to_read, firt);
// now enque the element
m_buffer.erase(begin(), begon() + bytes_to_read);
return bytes_to_read;
}
}
private:
void put(uint8_t data)
{
// check buffer overflow
m_buffer.push_back(data);
}
const_iterator begin() const { return m_buffer.begin(); }
const_iterator end() const { return m_buffer.end(); }
private:
std::deque<uint8_t> m_buffer; // buffer where store data
size_t m_size; // effective size of the container
};
class ISR {
public:
ISR(RXBuffer& buffer) : m_buffer(buffer) {}
// ISR Routine
void operator () (uint8_t data)
{
m_buffer.put(data);
}
private:
RXBuffer& m_buffer;
};
RXBuffer g_uart1_rx_buffer;
Now you have the ISR and a RXBuffer where search data, so you need something to wrap the UART functions. You can implement as follow:
class UART {
public:
UART(unsigned int uart_device, RXBuffer& rx_buffer) :
m_uart(uart_device), m_buffer(rx_buffer)
{
}
unsigned int uart_device() const { return m_uart; }
// set the timeout during a read operation
void timeout(unsigned ms) { m_timer.countdown(ms); }
template <typename InputIterator>
void read(InputIterator first, InputIterator last)
{
// start the timer
m_timer.start();
size_t size = std::distance(first, last);
size_t read_bytes = 0;
while (read_bytes != size && !m_timer.is_expired())
{
read_bytes += m_buffer.read(first + read_bytes, last);
}
if (read_bytes != size) throw std::exception("timeout");
}
template <typename OutputIterator>
void send(OutputIterator first, OutputIterator last)
{
size_t size = std::distance(first, last);
uart_send(m_uart, &(*first), size);
}
private:
unsigned int m_uart;
RXBuffer& m_buffer;
timer m_timer;
};
来源:https://stackoverflow.com/questions/13520272/uart-isr-tx-rx-architecture