Keil实例仿真AT89C51串口UART收发数据(附程序)

目录

一、引言

二、所用软件

1、串口调试工具

 2、虚拟串口软件

 3、Keil μVision5

 三、软件设置

1、串口调试助手软件设置

2、虚拟串口软件设置

3、Keil C51设置

A、调试(.ini)文件建立

B、Debug调试引入虚拟串口设置

C、COM1和COM2虚拟串口连接配对原理图

四、仿真实例介绍

1、UART配置程序段

2、发送程序段

3、中断接收程序段

4、延时程序段

5、P3初始化程序段

6、MAIN函数段

五、仿真结果

 六、已调程序

七、软件使用提示


一、引言

新近发现用Proteus配Keil for C51编译器调试UART串口收发通讯,发送没有问题,接收总是有问题,输入RXD的波形、波特率以及串口配置都是对的,但AT89C51就是不接收数据,RI不置位,也不能进入串口中断4程序,同样的程序移到Keil μVision5里,调试就妥妥的,收发自如,网上也看了别人的帖子和文章,大都是用Proteus自带的Virtual Termimal看到收到调试助手借助虚拟串口发的数据,但并不代表AT89C51真真收到了数据,拿网上贴出来的程序测试,也同样不能进入接收中断程序,RI不置位,严重怀疑Proteus的AT89C51模块有小问题,有同样问题或解决了该问题的高手,请回帖讨论。这里只实例介绍一下Keil仿真AT89C51串口收发数据的详细过程,相互学习,共同进步。

二、所用软件

1、串口调试工具

SSCOM是一款很不错的串口调试软件,收发数据行云流水不卡顿,就是界面略显花哨,不过还是要感谢开发者,不停地迭代更新,给大家带来的惊喜连连,在这里就顺带声援支持一下。 

Keil实例仿真AT89C51串口UART收发数据(附程序)_第1张图片

 

 2、虚拟串口软件

Virtual Serial Port Driver 是由 Electronic Team 开发的软件包,其功能如同 Windows机器上的虚拟 COM 端口仿真器。 该软件可以创建虚拟 COM 端口,能使用虚拟零调制解调器电缆成对连接,所创建的每个虚拟串口都将与串行应用程序进行通信,就好像它们是实际物理端口一样。

Keil实例仿真AT89C51串口UART收发数据(附程序)_第2张图片

 3、Keil μVision5

Keil C51编译器自1988年引入市场以来成为事实上的行业标准,并支持超过500种8051变种。Keil公司在2005年被ARM收购,通过这次收购,Keil将能更好地向高速发展的32位微控制器市场提供完整的解决方案,同时继续在μVision环境下支持8051和C16x编译器。

Keil实例仿真AT89C51串口UART收发数据(附程序)_第3张图片

 三、软件设置

1、串口调试助手软件设置

SSCOM在本例中设置:1)9600波特率,10位一帧,0起始位+8位数据+1停止位,无校验;2)加时间戳和分包显示,分包以大于等于一段完整的收发数据为宜;3)视程序设计定时重复发送数据给Keil中预设的虚拟串口。

Keil实例仿真AT89C51串口UART收发数据(附程序)_第4张图片

2、虚拟串口软件设置

VSPD设置分3步:1)选好配对虚拟串口;2)点击添加虚拟串口对;3)生成配对好的虚拟串口对,一发一收,就像实际连接在一起的串口鸳鸯对。

Keil实例仿真AT89C51串口UART收发数据(附程序)_第5张图片

 

3、Keil C51设置

A、调试(.ini)文件建立

1)先在Keil项目文件夹目录下面建立一个“.ini”文件,第一行写入指定AT89C51侧的虚拟串口号,并配置好串口(如波特率9600等信息);2)第二行指定AT89C51侧的虚拟串口输入输出(本例为COM1),写好后保存文件(本例起名为debug_C51.ini)。两行语句模式如下:

        MODE COMx baudrate, parity, databits, stopbits
        ASSIGN COMx SOUT

Keil实例仿真AT89C51串口UART收发数据(附程序)_第6张图片

B、Debug调试引入虚拟串口设置

1)点击μVision5的魔法棒;2)在Debug页面把上面A步建立好的debug_C51.ini添加进来。

Keil实例仿真AT89C51串口UART收发数据(附程序)_第7张图片

C、COM1和COM2虚拟串口连接配对原理图

Keil实例仿真AT89C51串口UART收发数据(附程序)_第8张图片

 

四、仿真实例介绍

1、UART配置程序段

串口参数配置函数,本例配置为9600波特率,1位停止位,8位数据位,无校验;定时器Timer1以定时模式工作在方式2:8位常数自动装入定时器;允许串口接收数据REN=1,打开串口中断ES=1及总中断EA=1,具体看下面代码。

/* 串口参数配置函数,这里配置为9600波特率,1位停止位,8位数据位,无校验  */
void uart_init(void)
{
   TMOD = 0x20;  //Timer1以定时模式工作在方式2:8位常数自动装入定时器/计数器
   SCON = 0x40;  //SM0=0,SM1=1=1,方式1,10位UART "0 D0~D7 1",波特率可变
   REN  = 1;     //允许串口接收数据位		 
   TH1  = 0xFD;  //9600波特率:晶振的频率/(12*(256-初值))=波特率*32
   TL1  = 0xFD;  //方式2的TH1,TL1是相等的,TL1自动重装TH1初值
   PCON = 0x00;  //SMOD=0波特率不加倍
   IE   = 0x90;  //允许总中断,允许串口中断,禁止其他中断
   PS   = 0;     //设置串行口中断优先级
   TR1  = 1;     //当GATE=0,TR1置“1” 启动定时器1
}

2、发送程序段

字符串发送函数被调用后,该函数再进一步调用字符发送函数。

/* send_char函数, ch为待发送的字符 */
void send_char(uint8_t ch)
{
   SBUF = ch;   //SBUF是指串行口同地址的两个缓冲寄存器,一个是发送寄存器,一个是接收寄存器,	       
   while (!TI); //等待一个字符(一帧)发送完毕
   TI = 0;      //TI软件清零,等待下一次发送后的置位  
}
/* send_string函数, *str为待发送的字符串首元素地址 */
void send_string(uint8_t *str)
{    
   while (*str != '\0') //字符串里的数据发送不到结尾  
   {
      send_char(*str); //调用字符发送函数
      str++; // 指针指向下一个待发字符
   }
}

3、中断接收程序段

从COM1接收外部串口调试助手通过虚拟串口COM2发来的数据,保存进RXD_Buf字符数组中,添加好字符串结束字符'\0',并把正确的接收状态赋给Buf_Flag标志,如果输入超过32个字节,标记为溢出BufOverflow,正确接收带结束符\r\n或\n\r数据的话,Buf_Flag标记为BufReady。

/*串口中断接收数据函数 */
void uart_interrupt(void) interrupt 4 
// 中断默认优先级:0外部中断0 > 1定时/计数器0 > 2外部中断1 > 3定时/计数器1 > 4串行中断;
{
   static uint8_t n = 0; //一次性接收输入字符串字符个数计数,静态变量只在编译时赋一次初值   
   if (TI==1) //如果是发送进入中断,中断直接返回
   {
       TI = 0; //TI清0       
       return;  
    } 
   if (RI==1)  //RI置位表示一帧数据接收完毕,RI必须用软件清0
   {
      RI = 0; //中断标志位清零
      ES = 0; //关闭串口中断,预防而已,防止下面程序执行时被打断
      if (n < BufSize - 1) // 接收缓冲区未溢出
      {
		RXD_Buf[n++] = SBUF; //读入的数据依次放入RXD_Buf中	
		if (((RXD_Buf[n - 2] == R_SYM) && (RXD_Buf[n - 1] == N_SYM))
             \||((RXD_Buf[n - 2] == N_SYM) && (RXD_Buf[n - 1] == R_SYM))) 
			// 判断是否接收到结束符(回车 "\r\n"或者"\r\n")	
		 {
			Buf_Flag = BufReady; // 成功接收到带结束标志符的一串字符
			RXD_Buf[n] = '\0';   // 在字符串末尾补字符数组(串)结束标识
			n = 0; //n清0
		  }  
	  }
	  else     // 接收缓冲区溢出
	  {
	 	Buf_Flag = BufOverflow;  // 串口接收标志:溢出
		n = 0;  //n清0	 
		}
	  ES = 1; //打开串口中断  
	}    
}

4、延时程序段

延时函数,没什么好解释的。

/* 延时函数  */
void delay(uint16_t i)
{
   uint8_t j;
   for(;i>0;i--)
      for(j=0;j<125;j++)
	 ;
}

5、P3初始化程序段

这段代码非必须

/* P3串口初始化函数  */
void uart_P3_init(void)   
{
    P3 = 0x0F; //P3.0 P3.1用作串口的RXD和TXD
}

6、MAIN函数段

声明自己是虚拟串口COM1,打个招呼之后,等待输入的数据就绪标记BufReady,没输入的话Buf_Flag = BufEmpty,提示通过串口提示助手通过COM2输入内容,只提示一次,有输入之后,把输入的数据再返还给COM2,并提示是你刚输入的内容,如果一次性输入超过32Byte,提示溢出标记BufOverflow。

/* main 主函数*/
void main()
{
   uint8_t i = 0;   
   Buf_Flag = BufEmpty; //接收缓冲区标志赋初值
   uart_P3_init();      //初始化P3口
   uart_init();         //初始化串口
   ES = 0;  //发送前关闭串口中断,以防发送完每个字符都没必要地进入中断程序
	 send_string("I am AT89C51 Serial COM1!\r\n"); //发送字符串
   ES = 1;  //发送完打开串口中断  
   while (1)
   {  
    if (Buf_Flag == BufEmpty) //如果还没有收到COM2的数据,发送请求输入提示
	{		   
	  ES = 0; //发送前关闭串口中断,以防发送完每个字符都没必要地进入中断程序				    
		send_string("\r\nPlease input form Serial Assistant(<32Bytes)!\r\n"); 
	  ES = 1; //发送完打开串口中断 
			 Buf_Flag = WaitInput; //请求输入提示只发送一次
			}		 
    else if (Buf_Flag == BufReady) //如果收到并存入了RXD_Buf中
    {
	  ES = 0; //发送前关闭串口中断
		send_string("Your Input: "); //提示你刚才输入了什么
		send_string(RXD_Buf);        // 把接收到的字符串发送回去				
		i = 0; //i计数清零
	    while (i < BufSize) //接收缓冲区清零
		{
			RXD_Buf[i++] = 0; //清零RXD_Buf
		}
		Buf_Flag = BufEmpty; //串口接收标志:空
	  ES = 1;  //处理完打开串口中断
    }
	else if (Buf_Flag == BufOverflow)
	{
      ES = 0; //发送前关闭串口中断
		send_string("ERROR:RXD_Buf is full!\r\n"); // 提示缓冲区溢出
		delay(2000); //按需忽略之后再从COM2输入的内容	
		i = 0; //i计数清零
		while (i < BufSize)   //接收缓冲区清零
		{
    		RXD_Buf[i++] = 0; //清零RXD_Buf
		}
		Buf_Flag = BufEmpty;  //串口接收标志:空
	   ES = 1; //处理完打开串口中断
	}			 
  }     
}	

五、仿真结果

串口调试助手收发标注如下,实心的菱形表示串口助手收到的内容,空心的菱形表示串口助手发出的内容,关键设置也在下面的截图中做了标注。 

Keil实例仿真AT89C51串口UART收发数据(附程序)_第9张图片

 上面的图是停下了串口调试助手翻回去截的图,动态的截屏(闪电GIF)如下:

 六、已调程序

完整的程序代码如下,头文件reg52.h没有贴出来。

/* Main.c file generated by Baoyufei
 * UART AT89C51 Frequency 11.0592MHz 
 * receive data and tranfer them back
 * Created:   Saturday April 19, 2022
 * Processor: AT89C51
 * Compiler:  Keil μVision5
 * Version:   v1.0
 */

#include 
#include 

 
// 类型重定义,便于移植
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;

// 定义串口接收标志枚举类型
enum
{
	BufEmpty= 0, // 接收缓冲区为空
	BufOverflow , // 接收缓冲区溢出
	BufReady,       // 成功接收到结束标志符
	WaitInput
} Buf_Flag;   // 串口接收标志

#define BufSize 32   // 定义接收缓冲区大小为 32字节
// 定义结束符为回车符 "\r(回车符)+\n(换行符)"
#define R_SYM '\r'
#define N_SYM '\n'

// 外部可见函数声明
void uart_P3_init(void); 
void uart_init(void);
void send_char(uint8_t ch);
void send_string(uint8_t *str);
void delay(uint16_t i);

// 定义串口接收缓冲区
uint8_t RXD_Buf[BufSize] = {0};

/* main 主函数*/
void main()
{
   uint8_t i = 0;   
   Buf_Flag = BufEmpty; //接收缓冲区标志赋初值
   uart_P3_init();      //初始化P3口
   uart_init();         //初始化串口
   ES = 0;  //发送前关闭串口中断,以防发送完每个字符都没必要地进入中断程序
	 send_string("I am AT89C51 Serial COM1!\r\n"); //发送字符串
   ES = 1;  //发送完打开串口中断  
   while (1)
   {  
    if (Buf_Flag == BufEmpty) //如果还没有收到COM2的数据,发送请求输入提示
	{		   
	  ES = 0; //发送前关闭串口中断,以防发送完每个字符都没必要地进入中断程序				    
		send_string("\r\nPlease input form Serial Assistant(<32Bytes)!\r\n"); 
	  ES = 1; //发送完打开串口中断 
			 Buf_Flag = WaitInput; //请求输入提示只发送一次
			}		 
    else if (Buf_Flag == BufReady) //如果收到并存入了RXD_Buf中
    {
	  ES = 0; //发送前关闭串口中断
		send_string("Your Input: "); //提示你刚才输入了什么
		send_string(RXD_Buf);        // 把接收到的字符串发送回去				
		i = 0; //i计数清零
	    while (i < BufSize) //接收缓冲区清零
		{
			RXD_Buf[i++] = 0; //清零RXD_Buf
		}
		Buf_Flag = BufEmpty; //串口接收标志:空
	  ES = 1;  //处理完打开串口中断
    }
	else if (Buf_Flag == BufOverflow)
	{
      ES = 0; //发送前关闭串口中断
		send_string("ERROR:RXD_Buf is full!\r\n"); // 提示缓冲区溢出
		delay(2000); //按需忽略之后再从COM2输入的内容	
		i = 0; //i计数清零
		while (i < BufSize)   //接收缓冲区清零
		{
    		RXD_Buf[i++] = 0; //清零RXD_Buf
		}
		Buf_Flag = BufEmpty;  //串口接收标志:空
	   ES = 1; //处理完打开串口中断
	}			 
  }     
}	

/* 延时函数  */
void delay(uint16_t i)
{
   uint8_t j;
   for(;i>0;i--)
      for(j=0;j<125;j++)
	 ;
}

/* 串口参数配置函数,这里配置为9600波特率,1位停止位,8位数据位,无校验  */
void uart_init(void)
{
   TMOD = 0x20;  //Timer1以定时模式工作在方式2:8位常数自动装入定时器/计数器
   SCON = 0x40;  //SM0=0,SM1=1=1,方式1,10位UART "0 D0~D7 1",波特率可变
   REN  = 1;     //允许串口接收数据位		 
   TH1  = 0xFD;  //9600波特率:晶振的频率/(12*(256-初值))=波特率*32
   TL1  = 0xFD;  //方式2的TH1,TL1是相等的,TL1自动重装TH1初值
   PCON = 0x00;  //SMOD=0波特率不加倍
   IE   = 0x90;  //允许总中断,允许串口中断,禁止其他中断
   PS   = 0;     //设置串行口中断优先级
   TR1  = 1;     //当GATE=0,TR1置“1” 启动定时器1
}

/* P3串口初始化函数  */
void uart_P3_init(void)   
{
    P3 = 0x0F; //P3.0 P3.1用作串口的RXD和TXD
}

/* send_char函数, ch为待发送的字符 */
void send_char(uint8_t ch)
{
   SBUF = ch;   //SBUF是指串行口同地址的两个缓冲寄存器,一个是发送寄存器,一个是接收寄存器,	       
   while (!TI); //等待一个字符(一帧)发送完毕
   TI = 0;      //TI软件清零,等待下一次发送后的置位  
}
/* send_string函数, *str为待发送的字符串首元素地址 */
void send_string(uint8_t *str)
{    
   while (*str != '\0') //字符串里的数据发送不到结尾  
   {
      send_char(*str); //调用字符发送函数
      str++; // 指针指向下一个待发字符
   }
}

/*串口中断接收数据函数 */
void uart_interrupt(void) interrupt 4 
// 中断默认优先级:0外部中断0 > 1定时/计数器0 > 2外部中断1 > 3定时/计数器1 > 4串行中断;
{
   static uint8_t n = 0; //一次性接收输入字符串字符个数计数,静态变量只在编译时赋一次初值   
   if (TI==1) //如果是发送进入中断,中断直接返回
   {
       TI = 0; //TI清0       
       return;  
    } 
   if (RI==1)  //RI置位表示一帧数据接收完毕,RI必须用软件清0
   {
      RI = 0; //中断标志位清零
      ES = 0; //关闭串口中断,预防而已,防止下面程序执行时被打断
      if (n < BufSize - 1) // 接收缓冲区未溢出
      {
		RXD_Buf[n++] = SBUF; //读入的数据依次放入RXD_Buf中	
		if (((RXD_Buf[n - 2] == R_SYM) && (RXD_Buf[n - 1] == N_SYM))
             \||((RXD_Buf[n - 2] == N_SYM) && (RXD_Buf[n - 1] == R_SYM))) 
			// 判断是否接收到结束符(回车 "\r\n"或者"\r\n")	
		 {
			Buf_Flag = BufReady; // 成功接收到带结束标志符的一串字符
			RXD_Buf[n] = '\0';   // 在字符串末尾补字符数组(串)结束标识
			n = 0; //n清0
		  }  
	  }
	  else     // 接收缓冲区溢出
	  {
	 	Buf_Flag = BufOverflow;  // 串口接收标志:溢出
		n = 0;  //n清0	 
		}
	  ES = 1; //打开串口中断  
	}    
}

已调试程序如下:

七、软件使用提示

本实例使用软件纯属调试所需,绝无商业宣传意图;

另外提醒,商业用途请大家使用正版软件,尊重知识版权。

你可能感兴趣的:(Keil,μVision5,AT89C51,Keil,C51,51单片机,c语言,功能测试)