????????51单片机是指8051内核的8位单片机,因其内部结构相对简单,成本低廉,所以应用非常广泛!串口作为单片机最基本的通信接口,无论是开发调试,日常使用都是用得比较频繁的一个基本外设!但是很多教程包括官方提供的资料都是使用查询法发送数据!基本流程就是等待忙闲标志归0,忙闲标志置1,写SBUF寄存器,等待发送完成进入中断,忙闲标志清零。发送下一字节。
源代码已经打包上传,大家可以免费下载使用!如有问题,欢迎留言讨论!???????
STC官方示例代码如下:
#include "reg51.h" //这是官方例程 没有耐心认真看的使劲往下划
#include "intrins.h"
#define FOSC 11059200UL
#define BRT (65536 - FOSC / 115200 / 4)
bit busy;
char wptr;
char rptr;
char buffer[16];
void UartIsr() interrupt 4
{
if (TI)
{
TI = 0;
busy = 0;
}
if (RI)
{
RI = 0;
buffer[wptr++] = SBUF;
wptr &= 0x0f;
}
}
void UartSend(char dat)
{
while (busy); //这是官方例程 没有耐心认真看的使劲往下划
busy = 1;
SBUF = dat;
}
void UartSendStr(char *p)
{
while (*p)
{
UartSend(*p++);
}
}
void main()
{
UartInit();
ES = 1;
EA = 1;
UartSendStr("Uart Test !\r\n");
while (1)
{
if (rptr != wptr)
{
UartSend(buffer[rptr++]);
rptr &= 0x0f;
}
}
}
????????这种方法对于通信而言一般情况不会有什么问题,但是在发送完第一个字节,准备发送第二个字节这段时间,单片机会停下来等待标志位归0.尤其是低波特率情况,发送一个字符串可能会浪费比较长的时间!而且会被别的中断打断,导致偶发性通信错误或数据丢失!
? ? ? ? 一般32单片机提供的库会使用DMA,中断封装成一个非阻塞式发送函数,51单片机虽然没有DMA,但是中断还是有的。我们可以利用串口中断封装一个非阻塞式发送函数!代码如下:
#include "stc8g.h"
#include "intrins.h"
#define uint8_t unsigned char
//串口发送缓存定义
typedef struct uartstruct{
unsigned int tx_fifo[3][64]; //3个缓冲区 每个缓冲区64字节 缓存区数据加载满,继续写入会覆盖未发送的
unsigned int tx_fifo_p_w; //缓存区写指针
unsigned int tx_fifo_p_r; //缓存区写指针
unsigned int tx_fifo_t; //缓存发送指针
unsigned int tx_fifo_w; //缓存写入指针
} uart_fofi_typedef;
uart_fofi_typedef uart_fofi={{0},0,0,0,0};
void UART1_Isr() interrupt 4
{
if (TI)
{
if(uart_fofi.tx_fifo_w) //缓存不为空,FIFO写指针当前值为已经装入个数
{
SBUF = uart_fofi.tx_fifo[0][uart_fofi.tx_fifo_t]; //从FIFO的起始地址发送
uart_fofi.tx_fifo_t++; //发送指针指向FIFO下个地址
if(uart_fofi.tx_fifo_t==uart_fofi.tx_fifo_w) //发送指针等于写指针,代表发送完成
uart_fofi.tx_fifo_t=uart_fofi.tx_fifo_w=0; //指针全部归零 一帧数据结束
}
TI = 0; //清中断标志
}
if (RI)
{
RI = 0; //清中断标志
// P11 = !P11; //测试端口
}
}
void UartSendStr(uint8_t *str)
{
while(uart_fofi.tx_fifo_w); //缓冲器空闲 否则等等,后期加FIFO 深度
while(*str)
{
uart_fofi.tx_fifo[0][uart_fofi.tx_fifo_w]=(*str);//装入缓存
str++; //指向字符串下个字节
uart_fofi.tx_fifo_w++; //指向缓存下个字节
}
TI=1; //进中断开始发送
}
void main()
{
UartInit();
while (1)
{
UartSendStr("uartstr 1\r\n");
}
}
以上代码的基本工作流程:首先定义一个缓存区,这里定义的是一个二维数组,为的是避免数据发送完之前,又来了新的数据把缓存区覆盖掉。此处暂不讨论,姑且当作1维数组使用。为了方便使用,同时把缓存区的控制指针(单缓冲区用不到),缓存的写入,发送指针定义为一个结构。
//串口发送缓存定义
typedef struct uartstruct{
unsigned int tx_fifo[3][64]; //3个缓冲区 每个缓冲区64字节 缓存区数据加载满,继续写入会覆盖未发送的
unsigned int tx_fifo_p_w; //缓存区写指针
unsigned int tx_fifo_p_r; //缓存区写指针
unsigned int tx_fifo_t; //缓存发送指针
unsigned int tx_fifo_w; //缓存写入指针
} uart_fofi_typedef;
uart_fofi_typedef uart_fofi={{0},0,0,0,0};
第一步把字符串装入缓存数组,完成之后触发串口发送中断。然后主循环就可以去忙别的事情了!
void UartSendStr(uint8_t *str)
{
while(uart_fofi.tx_fifo_w); //缓冲器空闲 否则等等,后期加FIFO 深度
while(*str)
{
uart_fofi.tx_fifo[0][uart_fofi.tx_fifo_w]=(*str);//装入缓存
str++; //指向字符串下个字节
uart_fofi.tx_fifo_w++; //指向缓存下个字节
}
TI=1; //进中断开始发送
}
第二步进入中断后判断写指针是不是不为0,如果不为0发送指针就开始从缓存区的起始地址发送数据。因为每次写SBUF硬件都会在发送完成时自动触发中断,所以后边的字节就不需要再去手动触发。直到发送指针=写缓存指针,说明数据都发送完了。此时两个指针归零,下次进入中断后仅清理发送完成标志TI,完成一帧数据发送,不再进入中断,直到下次调用UartSendStr函数。
void UART1_Isr() interrupt 4
{
if (TI)
{
if(uart_fofi.tx_fifo_w) //缓存不为空,FIFO写指针当前值为已经装入个数
{
SBUF = uart_fofi.tx_fifo[0][uart_fofi.tx_fifo_t]; //从FIFO的起始地址发送
uart_fofi.tx_fifo_t++; //发送指针指向FIFO下个地址
if(uart_fofi.tx_fifo_t==uart_fofi.tx_fifo_w) //发送指针等于写指针,代表发送完成
uart_fofi.tx_fifo_t=uart_fofi.tx_fifo_w=0; //指针全部归零 一帧数据结束
}
TI = 0; //清中断标志
}
if (RI)
{
RI = 0; //清中断标志
}
}
至此我们就通过串口中断,完成了数据的非阻塞式发送。让本来就处理数据比较慢的51单片机效率得到了提升!
? ? ? ? 我来这里写文章的目的不是为了舞文弄墨,主要是分享一些我觉得还不错的小知识,还有就是遇到问题记录的笔记。如果您有不同看法,欢迎评论区留言,如果能帮到您,更是荣幸之至!
|