UART ISR Tx Rx 架构
Posted
技术标签:
【中文标题】UART ISR Tx Rx 架构【英文标题】:UART ISR Tx Rx Architecture 【发布时间】:2012-11-11 07:09:55 【问题描述】:我是不是把事情复杂化了?
我正在构建我的代码以通过 UART 从 8051 微型设备与外围设备通信。外设响应来自主机的命令,并且一次只能响应一个命令。这是一个简单的发送和接收协议。 (tx1, rx1, tx2, rx2, tx3, rx3) 每个 TX 消息以 CR 结束,每个响应以 > 结束。在收到对最后一条消息的回复之前,我无法发送新消息。如果我启用该选项,响应也可以在开头回显原始 TX 消息(但这会导致更多流量)
一个示例消息是:
TX:你好 RX:世界!>或者使用回显选项...
TX:你好 RX:你好\rWorld!>选项 A 像 getHello 这样的函数将包含发送和接收。并行 ISR 例程将收集传入字节并在接收到“>”字符时抛出一个标志。
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;
优点:
一切都包含在一个函数中。 更容易调试缺点:
此函数处于阻塞状态,如果外围设备从不响应,则可能会挂起,因此必须实现超时。 消息不能乱序接收,必须串联(即tx1、rx1、tx2、rx2、tx3、rx3)选项 B 采取平行的方法。将创建两个单独的函数。一种用于发送消息,另一种用于在收到 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;
优点:
可以处理乱序返回的并行消息,因为它依赖于 tx 消息的回显打印来确定如何解析它。 (即 tx1、tx2、tx3、rx1、rx2、rx3)缺点:
很难调试 产生多个线程 很多额外的代码 不值得,因为消息肯定会按顺序返回现在,我正在做选项 B,但随着我继续这个项目,我开始觉得这变得过于复杂。我很好奇你们是怎么想的。
谢谢!
【问题讨论】:
case "Hello":
你是在比较指针,而不是字符串。
一般来说我会:清空接收缓冲区并等待一段时间以验证没有任何内容进入。然后发送提示并启动计时器。然后在每个进入缓冲区的字节中断时,缓冲它直到你得到 end > 字符,或者发生超时。然后,中断可以触发一个标志,向主指示返回超时或收到有效数据包。
Ouah- 是的,我知道,我对伪代码很懒惰。
Myforwik - 您的建议似乎与选项 b 非常相似。有什么不同吗?
【参考方案1】:
我倾向于做这种事情,不过,我倾向于有一个单独的串行端口“类”( struct + functions )和一个位于串行端口顶部的协议类。我一直在我的嵌入式系统中使用这些。这为您提供了两全其美,阻塞同步调用和异步调用,因此您可以伪多任务。
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
;
【讨论】:
基思,令人着迷的回应。我现在要实现这样的东西,看看它是如何工作的。我真的很感谢你的时间 没问题,我刚刚更新了它,以便在处理响应后清除处理程序,否则,它将继续触发。在实践中可能还有其他小问题。我也倾向于使硬件依赖非常小,所以我可以在带有存根的 PC 上测试很多代码(这样我就可以使用 PC 串行端口并在尝试嵌入之前对逻辑进行排序,它也更容易对其进行单元测试) 您能推荐一个特定的开发环境来在 PC 上测试代码吗?现在我正在 IAR 工作台中开发并直接构建到嵌入式系统。 我使用 Visual Studio,但如果在 Linux 上,您可以使用 gcc 和您喜欢的工具。诀窍是尽可能隔离所有硬件特定的东西,然后将所有这些功能排除在外。不像最初看起来那么难,一旦你这样做了,在 PC 上的开发会快很多(我还编写了一个名为 SeaTest 的 C 单元测试框架,我也使用 code.google.com/p/seatest 进行单元测试)。通过模拟您的系统和单元测试,当您将它放在设备上时,它会变得容易得多。硬件上的常见问题是硬件和时间问题的怪癖。 嘿 Keith,我真的很喜欢异步调用的函数指针想法。你能解释一下上下文是做什么的吗?【参考方案2】:把事情简单化。 ISR 例程必须非常快,所以对我来说最好的方法是拥有一个像这样的全局 RXBuffer:
#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;
现在你有了 ISR 和 RXBuffer 来搜索数据,所以你需要一些东西来包装 UART 函数。您可以按如下方式实现:
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;
;
【讨论】:
以上是关于UART ISR Tx Rx 架构的主要内容,如果未能解决你的问题,请参考以下文章