Reputation: 1508
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:
Or with echo option...
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:
Cons:
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:
Cons:
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!
Upvotes: 6
Views: 3763
Reputation: 44316
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
}
};
Upvotes: 4
Reputation: 7378
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;
};
Upvotes: 0