13.5 串口通信原理和控制程序

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

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

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

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

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

  1. /*****************************Uart.c 文件程序源代码*****************************/
  2. #include <reg52.h>
  3. bit flagFrame = 0; //帧接收完成标志,即接收到一帧新数据
  4. bit flagTxd = 0; //单字节发送完成标志,用来替代 TXD 中断标志位
  5. unsigned char cntRxd = 0; //接收字节计数器
  6. unsigned char pdata bufRxd[64]; //接收字节缓冲区
  7. extern void UartAction(unsigned char *buf, unsigned char len);
  8. /* 串口配置函数,baud-通信波特率 */
  9. void ConfigUART(unsigned int baud){
  10. SCON = 0x50; //配置串口为模式 1
  11. TMOD &= 0x0F; //清零 T1 的控制位
  12. TMOD |= 0x20; //配置 T1 为模式 2
  13. TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
  14. TL1 = TH1; //初值等于重载值
  15. ET1 = 0; //禁止 T1 中断
  16. ES = 1; //使能串口中断
  17. TR1 = 1; //启动 T1
  18. }
  19. /* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
  20. void UartWrite(unsigned char *buf, unsigned char len){
  21. while (len--){ //循环发送所有字节
  22. flagTxd = 0; //清零发送标志
  23. SBUF = *buf++; //发送一个字节数据
  24. while (!flagTxd); //等待该字节发送完成
  25. }
  26. }
  27. /* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */
  28. unsigned char UartRead(unsigned char *buf, unsigned char len){
  29. unsigned char i;
  30. //指定读取长度大于实际接收到的数据长度时,
  31. //读取长度设置为实际接收到的数据长度
  32. if (len > cntRxd){
  33. len = cntRxd;
  34. }
  35. for (i=0; i<len; i++){ //拷贝接收到的数据到接收指针上
  36. *buf++ = bufRxd[i];
  37. }
  38. cntRxd = 0; //接收计数器清零
  39. return len; //返回实际读取长度
  40. }
  41. /* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */
  42. void UartRxMonitor(unsigned char ms){
  43. static unsigned char cntbkp = 0;
  44. static unsigned char idletmr = 0;
  45. if (cntRxd > 0){ //接收计数器大于零时,监控总线空闲时间
  46. if (cntbkp != cntRxd){ //接收计数器改变,即刚接收到数据时,清零空闲计时
  47. cntbkp = cntRxd;
  48. idletmr = 0;
  49. }else{ //接收计数器未改变,即总线空闲时,累积空闲时间
  50. if (idletmr < 30){ //空闲计时小于 30ms 时,持续累加
  51. idletmr += ms;
  52. if (idletmr >= 30){ //空闲时间达到 30ms 时,即判定为一帧接收完毕
  53. flagFrame = 1; //设置帧接收完成标志
  54. }
  55. }
  56. }
  57. }else{
  58. cntbkp = 0;
  59. }
  60. }
  61. /* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */
  62. void UartDriver(){
  63. unsigned char len;
  64. unsigned char pdata buf[40];
  65. if (flagFrame){ //有命令到达时,读取处理该命令
  66. flagFrame = 0;
  67. len = UartRead(buf, sizeof(buf)); //将接收到的命令读取到缓冲区中
  68. UartAction(buf, len); //传递数据帧,调用动作执行函数
  69. }
  70. }
  71. /* 串口中断服务函数 */
  72. void InterruptUART() interrupt 4{
  73. if (RI){ //接收到新字节
  74. RI = 0; //清零接收中断标志位
  75. //接收缓冲区尚未用完时,保存接收字节,并递增计数器
  76. if (cntRxd < sizeof(bufRxd)){{
  77. bufRxd[cntRxd++] = SBUF;
  78. }
  79. }
  80. if (TI){ //字节发送完毕
  81. TI = 0; //清零发送中断标志位
  82. flagTxd = 1; //设置字节发送完成标志
  83. }
  84. }

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

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

2、串口数据写入函数 UartWrite,它把数据指针 buf 指向的数据块连续的由串口发送出去。虽然我们的串口程序启用了中断,但这里的发送功能却没有在中断中完成,而是仍然靠查询发送中断标志 flagTxd(因中断函数内必须清零
TI,否则中断会重复进入执行,所以另置了一个 flagTxd 来代替 TI)来完成,当然也可以采用先把发送数据拷贝到一个缓冲区中,然后再在中断中发缓冲区数据发送出去的方式,但这样一是要耗费额外的内存,二是使程序更复杂。这里也还是想告诉大家,简单方式可以解决的问题就不要搞得更复杂。

  1. /*****************************main.c 文件程序源代码******************************/
  2. #include <reg52.h>
  3. sbit BUZZ = P1^6; //蜂鸣器控制引脚
  4. bit flagBuzzOn = 0; //蜂鸣器启动标志
  5. unsigned char T0RH = 0; //T0 重载值的高字节
  6. unsigned char T0RL = 0; //T0 重载值的低字节
  7. void ConfigTimer0(unsigned int ms);
  8. extern void UartDriver();
  9. extern void ConfigUART(unsigned int baud);
  10. extern void UartRxMonitor(unsigned char ms);
  11. extern void UartWrite(unsigned char *buf, unsigned char len);
  12. extern void InitLcd1602();
  13. extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
  14. extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);
  15. void main(){
  16. EA = 1; //开总中断
  17. ConfigTimer0(1); //配置 T0 定时 1ms
  18. ConfigUART(9600); //配置波特率为 9600
  19. InitLcd1602(); //初始化液晶
  20. while (1){
  21. UartDriver(); //调用串口驱动
  22. }
  23. }
  24. /* 内存比较函数,比较两个指针所指向的内存数据是否相同,
  25. ptr1-待比较指针 1,ptr2-待比较指针 2,len-待比较长度
  26. 返回值-两段内存数据完全相同时返回 1,不同返回 0 */
  27. bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len){
  28. while (len--){
  29. if (*ptr1++ != *ptr2++){ //遇到不相等数据时即刻返回 0
  30. return 0;
  31. }
  32. }
  33. return 1; //比较完全部长度数据都相等则返回 1
  34. }
  35. /* 串口动作函数,根据接收到的命令帧执行响应的动作
  36. buf-接收到的命令帧指针,len-命令帧长度 */
  37. void UartAction(unsigned char *buf, unsigned char len){
  38. unsigned char i;
  39. unsigned char code cmd0[] = "buzz on"; //开蜂鸣器命令
  40. unsigned char code cmd1[] = "buzz off"; //关蜂鸣器命令
  41. unsigned char code cmd2[] = "showstr "; //字符串显示命令
  42. unsigned char code cmdLen[] = { //命令长度汇总表
  43. sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,
  44. };
  45. unsigned char code *cmdPtr[] = { //命令指针汇总表
  46. &cmd0[0], &cmd1[0], &cmd2[0],
  47. };
  48. for (i=0; i<sizeof(cmdLen); i++){ //遍历命令列表,查找相同命令
  49. if (len >= cmdLen[i]){ //首先接收到的数据长度要不小于命令长度
  50. if (CmpMemory(buf, cmdPtr[i], cmdLen[i])){ //比较相同时退出循环
  51. break;
  52. }
  53. }
  54. }
  55. switch (i){ //循环退出时 i 的值即是当前命令的索引值
  56. case 0:
  57. flagBuzzOn = 1; //开启蜂鸣器
  58. break;
  59. case 1:
  60. flagBuzzOn = 0; //关闭蜂鸣器
  61. break;
  62. case 2:
  63. buf[len] = ‘\0‘; //为接收到的字符串添加结束符
  64. LcdShowStr(0, 0, buf+cmdLen[2]); //显示命令后的字符串
  65. i = len - cmdLen[2]; //计算有效字符个数
  66. if (i < 16){ //有效字符少于 16 时,清除液晶上的后续字符位
  67. LcdAreaClear(i, 0, 16-i);
  68. }
  69. break;
  70. default: //未找到相符命令时,给上机发送“错误命令”的提示
  71. UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
  72. return;
  73. }
  74. buf[len++] = ‘\r‘; //有效命令被执行后,在原命令帧之后添加
  75. buf[len++] = ‘\n‘; //回车换行符后返回给上位机,表示已执行
  76. UartWrite(buf, len);
  77. }
  78. /* 配置并启动 T0,ms-T0 定时时间 */
  79. void ConfigTimer0(unsigned int ms){
  80. unsigned long tmp; //临时变量
  81. tmp = 11059200 / 12; //定时器计数频率
  82. tmp = (tmp * ms) / 1000; //计算所需的计数值
  83. tmp = 65536 - tmp; //计算定时器重载值
  84. tmp = tmp + 33; //补偿中断响应延时造成的误差
  85. T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
  86. T0RL = (unsigned char)tmp;
  87. TMOD &= 0xF0; //清零 T0 的控制位
  88. TMOD |= 0x01; //配置 T0 为模式 1
  89. TH0 = T0RH; //加载 T0 重载值
  90. TL0 = T0RL;
  91. ET0 = 1; //使能 T0 中断
  92. TR0 = 1; //启动 T0
  93. }
  94. /* T0 中断服务函数,执行串口接收监控和蜂鸣器驱动 */
  95. void InterruptTimer0() interrupt 1{
  96. TH0 = T0RH; //重新加载重载值
  97. TL0 = T0RL;
  98. if (flagBuzzOn){ //执行蜂鸣器鸣叫或关闭
  99. BUZZ = ~BUZZ;
  100. }else{
  101. BUZZ = 1;
  102. }
  103. UartRxMonitor(1); //串口接收监控
  104. }

main 函数和主循环的结构我们已经做过很多了,就不多说了,这里重点把串口接收数据的具体解析方法给大家分析一下,这种用法具有很强的普遍性,掌握并灵活运用它可以使你将来的开发工作事半功倍。

首先来看 CmpMemory 函数,这个函数很简单,就是比较两段内存数据,通常都是数组中的数据,函数接收两段数据的指针,然后逐个字节比较——if (*ptr1++ != *ptr2++),这行代码既完成了两个指针指向的数据的比较,又在比较完后把两个指针都各自+1,从这里是不是也能领略到一点
C 语言的简洁高效的魅力呢。这个函数的用处自然就是用来比较我们接收到的数据和事先放在程序里的命令字符串是否相同,从而找出相符的命令了。

接下来是 UartAction 函数对接收数据的解析和处理方法,先把接收的数据与所支持的命令字符串逐条比较,这个比较中首先要确保接收的长度大于命令字符串的长度,然后再用上述的 CmpMemory 函数逐字节比较,如果比较相同就立即退出循环,不同则继续对比下一条命令。当找到相符的命令字符串时,最终
i 的值就是该命令在其列表中的索引位置,当遍历完命令列表都没有找到相符的命令时,最终 i 的值将等于命令总数,那么接下来就用 switch语句根据 i 的值来执行具体的动作,这个就不需要再详细说明了。

  1. /***************************Lcd1602.c 文件程序源代码*****************************/
  2. #include <reg52.h>
  3. #define LCD1602_DB P0
  4. sbit LCD1602_RS = P1^0;
  5. sbit LCD1602_RW = P1^1;
  6. sbit LCD1602_E = P1^5;
  7. /* 等待液晶准备好 */
  8. void LcdWaitReady(){
  9. unsigned char sta;
  10. LCD1602_DB = 0xFF;
  11. LCD1602_RS = 0;
  12. LCD1602_RW = 1;
  13. do {
  14. LCD1602_E = 1;
  15. sta = LCD1602_DB; //读取状态字
  16. LCD1602_E = 0;
  17. } while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重复检测直到其等于 0 为止
  18. }
  19. /* 向 LCD1602 液晶写入一字节命令,cmd-待写入命令值 */
  20. void LcdWriteCmd(unsigned char cmd){
  21. LcdWaitReady();
  22. LCD1602_RS = 0;
  23. LCD1602_RW = 0;
  24. LCD1602_DB = cmd;
  25. LCD1602_E = 1;
  26. LCD1602_E = 0;
  27. }
  28. /* 向 LCD1602 液晶写入一字节数据,dat-待写入数据值 */
  29. void LcdWriteDat(unsigned char dat){
  30. LcdWaitReady();
  31. LCD1602_RS = 1;
  32. LCD1602_RW = 0;
  33. LCD1602_DB = dat;
  34. LCD1602_E = 1;
  35. LCD1602_E = 0;
  36. }
  37. /* 设置显示 RAM 起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
  38. void LcdSetCursor(unsigned char x, unsigned char y){
  39. unsigned char addr;
  40. if (y == 0){ //由输入的屏幕坐标计算显示 RAM 的地址
  41. addr = 0x00 + x; //第一行字符地址从 0x00 起始
  42. }else{
  43. addr = 0x40 + x; //第二行字符地址从 0x40 起始
  44. }
  45. LcdWriteCmd(addr | 0x80); //设置 RAM 地址
  46. }
  47. /* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */
  48. void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
  49. LcdSetCursor(x, y); //设置起始地址
  50. while (*str != ‘\0‘){ //连续写入字符串数据,直到检测到结束符
  51. LcdWriteDat(*str++);
  52. }
  53. }
  54. /* 区域清除,清除从(x,y)坐标起始的 len 个字符位 */
  55. void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len){
  56. LcdSetCursor(x, y); //设置起始地址
  57. while (len--){ //连续写入空格
  58. LcdWriteDat(‘ ‘);
  59. }
  60. }
  61. /* 初始化 1602 液晶 */
  62. void InitLcd1602(){
  63. LcdWriteCmd(0x38); //16*2 显示,5*7 点阵,8 位数据接口
  64. LcdWriteCmd(0x0C); //显示器开,光标关闭
  65. LcdWriteCmd(0x06); //文字不动,地址自动+1
  66. LcdWriteCmd(0x01); //清屏
  67. }

液晶文件与上一个例程的液晶文件基本是一样的,唯一的区别是删掉了一个本例中用不到的全屏清屏函数,其实留着这个函数也没关系,只是 Keil 会提示一个警告,告诉你有未被调用的函数而已,可以不理会它。

经过这几个多文件工程的练习后,大家是否发现,在采用多文件模块化编程后,不光是某些函数,甚至整个 c 文件,如有需要,我们都可以直接复制到其它的新工程中使用,非常方便功能程序的移植,这样随着实践积累的增加,你会发现工作效率变得越来越高了。

时间: 2024-10-18 23:16:35

13.5 串口通信原理和控制程序的相关文章

串口通信原理

转:http://www.cnblogs.com/genli/articles/1988459.html 今天被经理狠批了一顿,事后仔细想了想,是自己的原因,是自己太笨了,不能怪领导的. 主要起因是串口通信的问题,我们做测试,需要上位机和下位机以及测试仪器通信.通信的底层函数是经理写的,而且已经封装在dll文件中,我们看不见,只能进行调用.于是问题出现了,从PC机向测试仪器发送命令没有问题,但是从测试仪器返回到PC机上却得到的不符.和同事讨论研究了半天,一开始认为是接收时间的问题,后来经过调试发

单片机串口通信原理及原理图

转:http://bbs.elecfans.com/forum.php?mod=viewthread&tid=206905  串口通信的概念非常简单,串口按位(bit)发送和接收字节.尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据.它很简单并且能够实现远距离通信.比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米:而对于串口而言,长度可达1200米.典型地,串口用于ASCII码字符的传输.通信使

【C51】UART串口通信

我们常需要单片机和其他模块进行通信,数据传输,常用的方式就是串口通信技术. 常用来 单片机<-->电脑,  单片机<-->单片机之间通信. 串行通信 versus 并行通信 并行传输:将字节的各个 bit 用多条传输线路同时发送出去.每个bit使用一条线路. 优点:速度相对快,控制简单. 缺点:控制线路多,耗费的硬件资源多. 串行传输:将一个字节的数据的各个 bit 在一条线路上 分时发送.一个字节8位,则至少需要分8次发送完. 优点:需要的线路少,成本低. 缺点:控制复杂,因为它

VC++串口通信编程详解

总结来看串口通信原理,(也可以说大多数通信原理也是如此). 通信首先要有个通信,可以简单的把通信看成一个小桶,发送方住水桶里装水,接收方从水桶中取水.如果你要和对方通信首先需要将桶盖打开,再将水装入到桶中,这时接收方才能够从桶中取到水.这里就存在着一定的问题, 1,如果桶盖还没有打开,发送方已经发送了.这时接收方再从桶中取水,肯定取的水不对,会不一部分缺失了.解决方式就是让桶盖打开再往其中加水. 2,但是桶盖何时打开,发送方何时发送,这个不好把握.解决方法:接收方接到数据时,要返回一个应答标志,

RS-232通信原理

rs232串口通信原理 串口是计算机上一种非常通用设备通信的协议(不要与通用串行总线Universal Serial Bus或者USB混淆).大多数计算机包含两个基于RS232的串口.串口同时也是仪器仪表设备通用的通信协议:很多GPIB兼容的设备也带有RS-232口.同时,串口通信协议也可以用于获取远程采集设备的数据. 串口通信的概念非常简单,串口按位(bit)发送和接收字节.尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据.它很简单并且能够实现远距

C#实现简单的串口通信

前言 本着学习研究的态度,用c#语言实现简单的串口通信工具. 一.串口通信原理 串口通信 串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节.尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据.它很简单并且能够实现远距离通信.由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据.其他线用于握手,但不是必须的.串口通信最重要的参数是波特率.数据位.停止位和奇偶校验.对于两个进行通信的

用VC6.0实现上位机串口通信

串口是常用的计算机与外部串行设备之间的数据传输通道,由于串行通信方便易行,所以应用广泛.我们可以利用Windows API 提供的通信函数编写出高可移植性的串行通信程序.本实例介绍在Visual C++6.0下如何利用Win32 API 实现串行通信程序.程序编译运行后的界面效果如图一所示: 图一.串口通信示例程序 一.实现方法 在Win16中,可以利用OpenComm().CloseComm()和WriteComm()等函数打开.关闭和读写串口.但在Win32中,串口和其他通信设备均被作为文件

51单片机串口通信

一.原理简介 51 单片机内部有一个全双工串行接口.什么叫全双工串口呢?一般来说,只能接受或只能发送的称为单工串行:既可接收又可发送,但不能同时进行的称为半双工:能同时接收和发送的串行口称为全双工串行口.串行通信是指数据一位一位地按顺序传送的通信方式,其突出优点是只需一根传输线,可大大降低硬件成本,适合远距离通信.其缺点是传输速度较低. 与之前一样,首先我们来了解单片机串口相关的寄存器. SBUF 寄存器:它是两个在物理上独立的接收.发送缓冲器,可同时发送.接收数据,可通过指令对SBUF 的读写

【单片机实验】串口通信实验

实验三:串口通信实验 内容: (1)利用电脑串口与实验台串口进行连接: (2)根据帧格式定义.波特率定义,编程配置串口,并编写控制程序: (3)实现从电脑上输入控制字符,由单片机通过串口接收,并控制LED显示的功能: (4)对所编程序进行调试及验证. 要求: (1)掌握波特率的概念.学会编程设置波特率和工作方式: (2)掌握串口通信的编程控制方法: (3)理解解MAX232串口通信的硬件电路结构. 注意实验三,需要同学们自己编写程序,实现从电脑串口助手(利用单片机下载软件提供的串口调试助手),发