UTR ISR Tx Rx Architecture

Am I complicating things?

I am archiving my code to talk from the 8051 microphone to a peripheral device via UART. The peripheral device responds to commands from the host and can only respond to one command at a time. This is a simple send and receive protocol. (tx1, rx1, tx2, rx2, tx3, rx3) Each TX message ends with CR, each response ends with>. I cannot send a new message until I receive a reply to the last one. Responses can also echo the original TX message at the beginning if I enable this option (but this causes more traffic)

Message example:

  • TX: hello
  • RX: Peace! >

Or with the echo option ...

  • TX: hello
  • RX: Hello \ rWorld! >

Option A A function such as getHello will consist of sending and receiving. The parallel ISR procedure will collect the incoming bytes and throw a flag when it receives the '>' character.

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 in one function.
  • Easier to debug

Minuses:

  • This function is blocked and may freeze if the peripheral device never answers, so you need to time out.
  • Messages cannot be received out of order, must be sequentially (i.e. tx1, rx1, tx2, rx2, tx3, rx3)

Option B A parallel approach is applied. Two separate functions are created. one to send the message, and one that will be the tops when 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:

  • It can handle concurrent messages that fail because it relies on echo printing the tx message to figure out how to parse it. (i.e. tx1, tx2, tx3, rx1, rx2, rx3)

Minuses:

  • quite difficult to debug
  • generates multiple threads
  • a lot of extra code
  • not worth it, as the messages will definitely be back in order

Now I am doing option B, but as the project continues, I begin to feel that it is becoming too complicated. I'm curious what you guys think.

Thanks!

+6
source share
2 answers

I usually do such things, although Id usually has a separate serial port "class" (struct + functions) and a protocol class that lives on top of the top port. I used them all the time on my embedded systems. This gives you the best of both worlds, blocking synchronous calling and asynchronous calling, so you can use multitasking.

 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 } }; 
+4
source

Make it simple. The ISR procedure should be very fast, so it’s best for me to have a global RXBuffer, for example:

 #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 ISR and RXBuffer where there is search data, so you need to wrap UART functions something. You can implement the following:

 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; }; 
0
source

All Articles