当前位置:首页 > 单片机 > 单片机
[导读]我们前边学串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单的收发字符或者字符串。在实际应用中,往往串口还要和电脑上的上位机软件进行交互,实现电脑软件发送不同的指令,单片机对应执行

我们前边学串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单的收发字符或者字符串。在实际应用中,往往串口还要和电脑上的上位机软件进行交互,实现电脑软件发送不同的指令,单片机对应执行不同操作的功能,这就要求我们组织一个比较合理的通信机制和逻辑关系,用来实现我们想要的结果。


本节所提供程序的功能是,通过电脑串口调试助手下发三个不同的命令,第一条指令:buzz on 可以让蜂鸣器响;第二条指令:buzz off 可以让蜂鸣器不响;第三条指令:showstr ,这个命令空格后边,可以添加任何字符串,让后边的字符串在 1602 液晶上显示出来,同时不管发送什么命令,单片机收到后把命令原封不动的再通过串口发送给电脑,以表示“我收到了??你可以检查下对不对”。这样的感觉是不是更像是一个小项目了呢?


对于串口通信部分来说,单片机给电脑发字符串好说,有多大的数组,我们就发送多少个字节即可,但是单片机接收数据,接收多少个才应该是一帧完整的数据呢?数据接收起始头在哪里,结束在哪里?这些我们在接收到数据前都是无从得知的。那怎么办呢?


我们的编程思路基于这样一种通常的事实:当需要发送一帧(多个字节)数据时,这些数据都是连续不断的发送的,即发送完一个字节后会紧接着发送下一个字节,期间没有间隔或间隔很短,而当这一帧数据都发送完毕后,就会间隔很长一段时间(相对于连续发送时的间隔来讲)不再发送数据,也就是通信总线上会空闲一段较长的时间。于是我们就建立这样一种程序机制:设置一个软件的总线空闲定时器,这个定时器在有数据传输时(从单片机接收角度来说就是接收到数据时)清零,而在总线空闲时(也就是没有接收到数据时)时累加,当它累加到一定时间(例程里是 30ms)后,我们就可以认定一帧完整的数据已经传输完毕了,于是告诉其它程序可以来处理数据了,本次的数据处理完后就恢复到初始状态,再准备下一次的接收。那么这个用于判定一帧结束的空闲时间取多少合适呢?它取决于多个条件,并没有一个固定值,我们这里介绍几个需要考虑的原则:第一,这个时间必须大于波特率周期,很明显我们的单片机接收中断产生是在一个字节接收完毕后,也就是一个时刻点,而其接收过程我们的程序是无从知晓的,因此在至少一个波特率周期内你绝不能认为空闲已经时间达到了。第二,要考虑发送方的系统延时,因为不是所有的发送方都能让数据严格无间隔的发送,因为软件响应、关中断、系统临界区等等操作都会引起延时,所以还得再附加几个到十几个 ms 的时间。我们选取的 30ms 是一个折中的经验值,它能适应大部分的波特率(大于1200)和大部分的系统延时(PC 机或其它单片机系统)情况。


我先把这个程序最重要的 UART.c 文件中的程序贴出来,一点点给大家解析,这个是实际项目开发常用的用法,大家一定要认真弄明白。

/*****************************Uart.c 文件程序源代码*****************************/

#include

bit flagFrame = 0; //帧接收完成标志,即接收到一帧新数据

bit flagTxd = 0; //单字节发送完成标志,用来替代 TXD 中断标志位

unsigned char cntRxd = 0; //接收字节计数器

unsigned char pdata bufRxd[64]; //接收字节缓冲区

extern void UartAction(unsigned char *buf, unsigned char len);

/* 串口配置函数,baud-通信波特率 */

void ConfigUART(unsigned int baud){

SCON = 0x50; //配置串口为模式 1

TMOD &= 0x0F; //清零 T1 的控制位

TMOD |= 0x20; //配置 T1 为模式 2

TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值

TL1 = TH1; //初值等于重载值

ET1 = 0; //禁止 T1 中断

ES = 1; //使能串口中断

TR1 = 1; //启动 T1

}

/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */

void UartWrite(unsigned char *buf, unsigned char len){

while (len--){ //循环发送所有字节

flagTxd = 0; //清零发送标志

SBUF = *buf++; //发送一个字节数据

while (!flagTxd); //等待该字节发送完成

}

}

/* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */

unsigned char UartRead(unsigned char *buf, unsigned char len){

unsigned char i;

//指定读取长度大于实际接收到的数据长度时,

//读取长度设置为实际接收到的数据长度

if (len > cntRxd){

len = cntRxd;

}

for (i=0; i

*buf++ = bufRxd[i];

}

cntRxd = 0; //接收计数器清零

return len; //返回实际读取长度

}

/* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */

void UartRxMonitor(unsigned char ms){

static unsigned char cntbkp = 0;

static unsigned char idletmr = 0;

if (cntRxd > 0){ //接收计数器大于零时,监控总线空闲时间

if (cntbkp != cntRxd){ //接收计数器改变,即刚接收到数据时,清零空闲计时

cntbkp = cntRxd;

idletmr = 0;

}else{ //接收计数器未改变,即总线空闲时,累积空闲时间

if (idletmr < 30){ //空闲计时小于 30ms 时,持续累加

idletmr += ms;

if (idletmr >= 30){ //空闲时间达到 30ms 时,即判定为一帧接收完毕

flagFrame = 1; //设置帧接收完成标志

}

}

}

}else{

cntbkp = 0;

}

}

/* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */

void UartDriver(){

unsigned char len;

unsigned char pdata buf[40];

if (flagFrame){ //有命令到达时,读取处理该命令

flagFrame = 0;

len = UartRead(buf, sizeof(buf)); //将接收到的命令读取到缓冲区中

UartAction(buf, len); //传递数据帧,调用动作执行函数

}

}

/* 串口中断服务函数 */

void InterruptUART() interrupt 4{

if (RI){ //接收到新字节

RI = 0; //清零接收中断标志位

//接收缓冲区尚未用完时,保存接收字节,并递增计数器

if (cntRxd < sizeof(bufRxd)){{

bufRxd[cntRxd++] = SBUF;

}

}

if (TI){ //字节发送完毕

TI = 0; //清零发送中断标志位

flagTxd = 1; //设置字节发送完成标志

}

}

大家可以对照注释和前面的讲解分析下这个 Uart.c 文件,在这里指出其中的两个要点希望大家多注意下。


1、接收数据的处理,在串口中断中,将接收到的字节都存入缓冲区 bufRxd 中,同时利用另外的定时器中断通过间隔调用 UartRxMonitor 来监控一帧数据是否接收完毕,判定的原则就是我们前面介绍的空闲时间,当判定一帧数据结束完毕时,设置 flagFrame 标志,主循环中可以通过调用 UartDriver 来检测该标志,并处理接收到的数据。当要处理接收到的数据时,先通过串口读取函数 UartRead 把接收缓冲区 bufRxd 中的数据读取出来,然后再对读到的数据进行判断处理。也许你会说,既然数据都已经接收到 bufRxd 中了,那我直接在这里面用不就行了嘛,何必还得再拷贝到另一个地方去呢?我们设计这种双缓冲的机制,主要是为了提高串口接收到响应效率:首先如果你在 bufRxd 中处理数据,那么这时侯就不能再接收任何数据,因为新接收的数据会破坏原来的数据,造成其不完整和混乱;其次,这个处理过程可能会耗费较长的时间,比如说上位机现在就给你发来一个延时显示的命令,那么在这个延时的过程中你都无法去接收新的命令,在上位机看来就是你暂时失去响应了。而使用这种双缓冲机制就可以大大改善这个问题,因为数据拷贝所需的时间是相当短的,而只要拷贝出去后,bufRxd 就可以马上准备去接收新数据了。


2、串口数据写入函数 UartWrite,它把数据指针 buf 指向的数据块连续的由串口发送出去。虽然我们的串口程序启用了中断,但这里的发送功能却没有在中断中完成,而是仍然靠查询发送中断标志 flagTxd(因中断函数内必须清零 TI,否则中断会重复进入执行,所以另置了一个 flagTxd 来代替 TI)来完成,当然也可以采用先把发送数据拷贝到一个缓冲区中,

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

上位机串口通信是指在上位机(通常是计算机或微控制器)与外部设备之间通过串行通信协议进行数据传输和通信的过程。在串口通信中,数据以位为单位按顺序传送,通常使用一个或两个物理线路(通常是两条线)来实现数据的发送和接收。

关键字: 上位机 串口通信

串口通信(Serial Communication)是一种常用的数据传输方式,它通过串行数据线逐位传输数据,具有成本低、简单易用、可靠性高等优点。在工业控制、通信设备、嵌入式系统等领域得到广泛应用。

关键字: 串口通信 嵌入式系统

串口通信是一种常见的通信方式,用于在不同设备之间传输数据。它通过串行数据线(通常是一对TXD和RXD线)在设备之间进行通信。串口通信具有简单、可靠、成本低等优点,因此在工业控制、嵌入式系统、数据采集等领域得到广泛应用。

关键字: 串口通信 串口编程

串口通信是一种常见的通信方式,主要应用于计算机和其他设备之间的数据传输。它的主要原理是利用串行数据传输的方式,将数据一位一位地按顺序传输,而不是同时传输所有的数据。下面我们来看看串口通信的主要原理以及如何提高其传输速度。

关键字: 串口通信 串口速度

串口通信是一种常见的通信方式,它通过串行方式传输数据。串口通信的原理相对简单,主要涉及到数据位的传输和接收。在串口通信中,数据按照一定的波特率(baud rate)逐位传输。通常,一个字节的数据由8位组成,所以传输一个字...

关键字: 串口通信 波特率

RS 232串口通信是一种常见的串行通信协议,广泛应用于计算机和其他设备之间的数据传输。它是由美国电子工业协会(EIA)制定的一种标准,规定了数据传输的物理层和部分数据链路层的规范。

关键字: RS 232 串口通信

虽然 USB 几乎完全取代那些旧电缆和连接器,但 UART 绝对不会成为过去。您会发现许多 DIY 电子项目都使用 UART。

关键字: UART 串口通信 USB

STM32是一款由STMicroelectronics生产的微控制器系列,具有高性能、低功耗和丰富的外设资源。其中,串口通信是一种常用的通信方式,可以实现与其他设备之间的数据传输。

关键字: STM32 串口通信 微控制器

伴随物联网、车联网、人工智能等新兴应用领域的拓展和深化,智能家居、智慧楼宇、智慧城市和智能工业等行业快速发展,带动物联网无线模组需求释放,进而带动串口WiFi模块,BLE蓝牙模块以及ZigBee模块的需求增长

关键字: uart 串口通信 物联网

串口通信作为一种最传统的通信方式,在工业自动化、通讯、控制等领域得到广泛使用。

关键字: Linux 串口通信 通讯
关闭
关闭