通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是电脑硬件的一部分。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。
具体实物表现为独立的模块化芯片,或作为集成于微处理器中的周边设备。一般是RS-232C规格的,与类似Maxim的MAX232之类的标准信号幅度变换芯片进行搭配,作为连接外部设备的接口。在UART上追加同步方式的序列信号变换电路的产品,被称为USART(Universal Synchronous Asynchronous Receiver Transmitter)。
UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。
UART接收数据,一个字节一个字节接收,底层硬件只能知道现在收到了一个字节,然后保存在一个buffer里面。怎么去判断现在一帧协议收完了呢?也就是说,我要发送一个协议帧,根据协议他是不定长的,怎么判断现在收完了呢?
方法一:
也许有人会想到,我变收变判断,就是在接收驱动函数里,去解析协议,一般是这个样子:
#pragma vector = INTSR2_vect
__interrupt staTIc void r_uart2_interrupt_receive(void)
{
buffer[CNT] = RXD2;
CNT++;
if(CNT 》 帧头长度){
找帧头,然后记住帧头位置;
}
//找帧长位置
//等待接收完
//判断帧尾或者校验
//通知APP,一帧接收完毕
//请标志
SRIF2 = 0;
}
这么做,我感觉是效率最高的,我的驱动层封装的时候,得暴露__interrupt staTIc void r_uart2_interrupt_receive(void)给用户,同时提供用户底层接收完请标志等API。
方法二:
也会有人会想到我在协议前加一个字节长度不就完了,根据这个区接收,然后接收完了,告诉APP层或者协议解析层。这种方法实现的前提是大家都按这个做,否则通信失败,适合公司内部使用。
方法三:
我开始用的是TIMER_OUT方法,什么意思呢?举例说,9600波特率,发送一个字节大约需要1ms时间,假如认为发送是连续的而不是断续的,那么我是否可以认为你在超过1MS时间(为了留有足够的富裕时间,认为20MS)没有接收中断发生,我就可以认为接收完毕。接着通知APP或者协议解析模块去解析协议。但是这么处理有几个问题需要注意:
1、如果对方用的查询方式发送,那个需要获得对方的最大中断处理时间;
2、帧于帧之间发送间隔必须大于接收方设定的TIMER_OUT时间;(其实这段不满足的话,也可以处理,在RAM资源足够的情况下,协议解析模块按着解析多帧的思路去写)
3、整个通信带宽被拉低,因为帧间隔是TIMER_OUT时间,肯定是MS级以上,一般20~50ms吧;
但是这种方法是最简单的,也适合解耦,便于模块化封装。
方法四:
我现在用的方法是环形buffer。也就是UART一直在收数据,并且放到一个环形buffer里面(是为了防止溢出),APP或者协议解析模块不停的去读取数据,并做协议解析。这种方法优点就是处理速度快,没有了TIMER_OUT时间。目前问题是,我不知道怎么把buffer去解耦,索性我就把buffer全部丢给了协议解析模块。
既然谈到了UART驱动封装,我就说说我目前做法,其实我也是刚学习封装这块,水平有限,就是想和大家聊聊,大神轻喷。
首先我定义了几个接口:
//UART0
extern void uart0_drive_mode_init(void (*pRxCallBack)(uint8_t));
extern bool uart0_cfg(uart_cfg_t *ptUartCfg,uint32_t wBaudRate,uint16_t hwErrorRange,void (*callback)());
extern bool uart0_txd_query(uint8_t *pchTxdBuffer,uint16_t hwTxdNum);
extern void uart0_enable_rx_interrupt(interrupt_level_t tLevel);
extern void uart0_disable_rx_interrupt(void);
extern void uart0_set_tx_interrupt_level(interrupt_level_t tLevel);
extern bool uart0_txd_interrupt_start(uint8_t *pchBuffer,uint16_t hwTxdLong);
/**************************** UART0 ****************************/
//模块初始化
#define USER_UART0_DRIVE_MODE_INIT(__CALLBACK) uart0_drive_mode_init(__CALLBACK)
//USER0
#define USER_UART0_CFG(_CONFIG,_BAUDRATE,_Rang,_CALLBACK) uart0_cfg((_CONFIG),(_BAUDRATE),(_Rang),(_CALLBACK))
#define USER_UART0_TXD_QUERY(_BUFFER,_TXD_NUM) uart0_txd_query((_BUFFER),(_TXD_NUM))
#define USER_UART0_ENABLE_RX_INTERRUPT(_LEVEL) uart0_enable_rx_interrupt(_LEVEL)
#define USER_UART0_DISABLE_RX_INTERRUPT() uart0_disable_rx_interrupt()
#define USER_UART0_SET_TX_INTERRUPT_LEVEL(_LEVEL) uart0_set_tx_interrupt_level(_LEVEL)
#define USER_UART0_TXD_INTERRUPT_START(__BUFFER,__TXD_NUM) uart0_txd_interrupt_start((__BUFFER),(__TXD_NUM))
解释:
uart0_drive_mode_init(),模块初始化函数,需要传入一个void (*pRxCallBack)(uint8_t)型函数指针,这个函数是为了接收中断回调用户接收处理函数,把RXD0数据放到环形接收缓存区;
uart0_txd_query()查询发送函数,线程安全的
uart0_enable_rx_interrupt()使能接收中断,并设置优先级
uart0_disable_rx_interrupt()关闭接收中断
uart0_set_tx_interrupt_level()设置发送优先级
uart0_txd_interrupt_start()中断发送,线程安全的
然后,我把所有的中断和函数封装到底层里面。
这里有一点我不明白怎么做,这个接收环形buffer怎么设计实现解耦?
《-------------------------------------------------------------------------华丽分割线------------------------------------------------------------------------------》
最近一直在学习OOPC和数据结构方面的知识,突然领悟到怎么把环形buffer分离出来,原来方法是如此简单,怪我以前想复杂了。
首先我们定义一个queue的类,定义四个接口:
bool init_byte_queue(byte_queue_t* ptQueue,uint8_t* pchBuffer,uint16_t hwSize);
bool is_queue_empty(byte_queue_t* ptQueue);
void enqueue_byte(byte_queue_t* ptQueue,uint8_t chInData);
void dequeue_byte(byte_queue_t* ptQueue,uint8_t* pchOutData);
然后定义个数据结构体:
struct byte_queue_t{
uint8_t *pchBuffer;
uint16_t hwSize;
uint16_t hwHead;
uint16_t hwTail;
uint16_t hwLength;
};
实现如下:
#define this (*ptThis)
bool init_byte_queue(byte_queue_t* ptQueue,uint8_t* pchBuffer,uint16_t hwSize)
{
CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;
if(NULL == ptQueue){
return false;
}
this.pchBuffer = pchBuffer;
this.hwSize = hwSize;
this.hwHead = 0;
this.hwTail = 0;
this.hwLength = 0;
}
bool is_queue_empty(byte_queue_t* ptQueue)
{
CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;
if(NULL == ptQueue){
return true;
}
return (0 == this.hwLength);
}
void enqueue_byte(byte_queue_t* ptQueue,uint8_t chInData)
{
CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;
if(NULL == ptQueue){
return;
}
this.pchBuffer[this.hwHead] = chInData;
this.hwHead++;
if(this.hwHead 》= this.hwSize){
this.hwHead = 0;
}
this.hwLength++;
}
void dequeue_byte(byte_queue_t* ptQueue,uint8_t* pchOutData)
{
CLASS(byte_queue_t) *ptThis = (CLASS(byte_queue_t) *)ptQueue;
if(NULL == ptQueue){
return;
}
if(NULL == pchOutData){
return;
}
if(!this.hwLength){
return;
}
*pchOutData = this.pchBuffer[this.hwTail];
this.hwTail++;
if(this.hwTail 》= this.hwSize){
this.hwTail = 0;
}
this.hwLength--;
}
到此基本UART就说完了,但是我今天看了篇关于环形队列同步加锁问题,有必要再补充下。
上面的环形队列是有问题的,什么问题呢?就是hwLength的同步问题,hwLength--和hwLength++
位于不同的线程,所以需要加锁,以确保原子性问题。
QUEUE_ENTER_CRITICAL();
this.hwLength--; //必须保证原子性
QUEUE_LEAVE_CRITICAL();
//QUEUE_ENTER_CRITICAL();
this.hwLength++; //放在了中断中,本身任务优先级就高
//QUEUE_LEAVE_CRITICAL();
如果写入和读出线程优先级是平级的,那么都需要加锁;如果有一个优先级高,一个优先级低,那个低优先级
线程需要加锁,高优先级不需要。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)