单片机IO口模拟串口程序(发送+接收 )

本文详细介绍了如何在单片机上模拟一个串口,包括硬件平台选择、初始化过程、发送与接收数据的实现方法。同时,文章针对现有方法中存在的问题进行了优化,如采用外部中断提高接收效率,引入模拟串口接收缓冲区避免数据丢失,以及调整WByte、RByte和WaitTF0函数以提升程序流畅性。通过实例代码展示了改进后的测试程序。

前一阵一直在做单片机的程序,由于串口不够,需要用IO口来模拟出一个串口。经过若干曲折并参考了一些现有的资料,基本上完成了。现在将完整的测试程序,以及其中一些需要总结的部分贴出来。

  程序硬件平台:11.0592M晶振,STC单片机(兼容51)

  /***************************************************************

  *    在单片机上模拟了一个串口,使用P2.1作为发送端

  *    把单片机中存放的数据通过P2.1作为串口TXD发送出去

  ***************************************************************/

  #i nclude <reg51.h>

  #i nclude <stdio.h>

  #i nclude <string.h>

  typedef unsigned char uchar;

  int i;

  uchar code info[] =

  {

  0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55

  };

  sbit newTXD = P2^1;//模拟串口的发送端设为P2.1

  void UartInit()

  {

  SCON  = 0x50;   // SCON: serail mode 1, 8-bit UART

  TMOD |= 0x21;   // T0工作在方式1,十六位定时

  PCON |= 0x80;   // SMOD=1;

  TH0      = 0xFE;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz

  TL0   = 0x7F;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz

  //    TH0      = 0xFD;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=18.432MHz

  //    TL0   = 0x7F;    // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=18.432MHz

  }

  void WaitTF0(void)

  {

  while(!TF0);

  TF0=0;

  TH0=0xFE;    // 定时器重装初值 fosc=11.0592MHz

  TL0=0x7F;    // 定时器重装初值 fosc=11.0592MHz

  //    TH0      = 0xFD;    // 定时器重装初值 fosc=18.432MHz

  //    TL0   = 0x7F;    // 定时器重装初值 fosc=18.432MHz

  }

  void WByte(uchar input)

  {

  //发送启始位

  uchar j=8;

  TR0=1;

  newTXD=(bit)0;

  WaitTF0();

  //发送8位数据位

  while(j--)

  {

  newTXD=(bit)(input&0x01);      //先传低位

  WaitTF0();

  input=input>>1;

  }

  //发送校验位(无)

  //发送结束位

  newTXD=(bit)1;

  WaitTF0();

  TR0=0;

  }

  void Sendata()

  {

  for(i=0;i<sizeof(info);i++)//外层循环,遍历数组

  {

  WByte(info[i]);

  }

  }

  void main()

  {

  UartInit();

  while(1)

  {

  Sendata();

  }

  }

  ##############################################################################

  /***************************************************************

  *       模拟接收程序,这个程序的作用从模拟串口接收数据,然后将这些数据发送到实际串口

  *    在单片机上模拟了一个串口,使用P3.2作为发送和接收端

  *    以P3.2模拟串口接收端,从模拟串口接收数据发至串口

  ***************************************************************/

  #i nclude<reg51.h>

  #i nclude<stdio.h>

  #i nclude<string.h>

  typedef unsigned char uchar ;

  //这里用来切换晶振频率,支持11.0592MHz和18.432MHz

  //#define F18_432

  #define F11_0592

  uchar tmpbuf2[64]={0};

  //用来作为模拟串口接收数据的缓存

  struct

  {

  uchar recv :6 ;//tmpbuf2数组下标,用来将模拟串口接收到的数据存放到tmpbuf2中

  uchar send :6 ;//tmpbuf2数组下标,用来将tmpbuf2中的数据发送到串口

  }tmpbuf2_point={0,0};

  sbit newRXD=P3^2 ;//模拟串口的接收端设为P3.2

  void UartInit()

  {

  SCON=0x50 ;// SCON: serail mode 1, 8-bit UART

  TMOD|=0x21 ;// TMOD: timer 1, mode 2, 8-bit reload,自动装载预置数(自动将TH1送到TL1);T0工作在方式1,十六位定时

  PCON|=0x80 ;// SMOD=1;

  #ifdef F11_0592

  TH1=0xE8 ;// Baud:2400  fosc=11.0592MHz 2400bps为从串口接收数据的速率

  TL1=0xE8 ;// 计数器初始值,fosc=11.0592MHz 因为TH1一直往TL1送,所以这个初值的意义不大

  TH0=0xFF ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz

  TL0=0xA0 ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz

  #endif

  #ifdef F18_432

  TH1=0xD8 ;     // Baud:2400  fosc=18.432MHz 2400bps为从串口接收数据的速率

  TL1=0xD8 ;     // 计数器初始值,fosc=18.432MHz 因为TH1一直往TL1送,所以这个初值的意义不大

  TH0=0xFF ;// 定时器0初始值,延时104us,目的是令模拟串口的波特率为9600bps fosc=18.432MHz

  TL0=0x60 ;// 定时器0初始值,延时104us,目的是令模拟串口的波特率为9600bps fosc=18.432MHz

  #endif

  IE|=0x81 ;// 中断允许总控制位EA=1;使能外部中断0

  TF0=0 ;

  IT0=1 ;// 设置外部中断0为边沿触发方式

  TR1=1 ;// 启动TIMER1,用于产生波特率

  }

  void WaitTF0(void)

  {

  while(!TF0);

  TF0=0 ;

  #ifdef F11_0592

  TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

  TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

  #endif

  #ifdef F18_432

  TH0=0xFF ;

  // 定时器重装初值 fosc=18.432MHz

  TL0=0x60 ;

  // 定时器重装初值 fosc=18.432MHz

  #endif

  }

  //接收一个字符

  uchar RByte()

  {

  uchar Output=0 ;

  uchar i=8 ;

  TR0=1 ;     //启动Timer0

  #ifdef F11_0592

  TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

  TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

  #endif

  #ifdef F18_432

  TH0=0xFF ;// 定时器重装初值 fosc=18.432MHz

  TL0=0x60 ;// 定时器重装初值 fosc=18.432MHz

  #endif

  TF0=0 ;

  WaitTF0();//等过起始位

  //接收8位数据位

  while(i--)

  {

  Output>>=1 ;

  if(newRXD)Output|=0x80 ;//先收低位

  WaitTF0();//位间延时

  }

  TR0=0 ;//停止Timer0

  return Output ;

  }

  //向COM1发送一个字符

  void SendChar(uchar byteToSend)

  {

  SBUF=byteToSend ;

  while(!TI);

  TI=0 ;

  }

  void main()

  {

  UartInit();

  while(1)

  {

  if(tmpbuf2_point.recv!=tmpbuf2_point.send)//差值表示模拟串口接收数据缓存中还有多少个字节的数据未被处理(发送至串口)

  {

  SendChar(tmpbuf2[tmpbuf2_point.send++]);

  }

  }

  }

  //外部中断0,说明模拟串口的起始位到来了

  void Simulated_Serial_Start()interrupt 0

  {

  EX0=0 ;     //屏蔽外部中断0

  tmpbuf2[tmpbuf2_point.recv++]=RByte();     //从模拟串口读取数据,存放到tmpbuf2数组中

  IE0=0 ;     //防止外部中断响应2次,防止外部中断函数执行2次

  EX0=1 ;     //打开外部中断0

  }

  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[NextPage]

  以上是两个独立的测试程序,分别是模拟串口发送的测试程序和接收的测试程序

  上面两个程序在编写过程中参考了这篇文章《51单片机模拟串口的三种方法》(在后文中简称《51》),但在它的基础上做了一些补充,下面是若干总结的内容:

  1、《51》在接收数据的程序中,采用的是循环等待的方法来检测起始位(见《51》的“附:51 IO口模拟串口通讯C源程序(定时器计数法)” 部分),这种方法在较大程序中,可能会错过起始位(比如起始位到来的时候程序正好在干别的,而没有处于判断起始位到来的状态),或者一直在检测起始位,而没有办法完成其他工作。为了避免这个问题,在本接收程序中采用了外部中断的方法,将外部中断0引脚作为模拟串口的接收端,设IT0=1(将外部中断0设为边缘触发)。这样当起始位(低电平)到来时,就会引发外部中断,然后在外部中断处理函数中接收余下的数据。这种方法可以保证没数据的时候程序该干什么干什么,一旦模拟串口接收端有数据,就可以立即接收到。

  2、加入了模拟串口接收缓冲区。在较大程序中,单片机要完成的工作很多,在模拟串口接收到了数据之后立即处理的话,有可能处理不过来造成丢失数据,或者影响程序其他部分执行。本程序中加入了64个字节的缓冲区,从模拟串口接收到的数据先存放在缓冲区中。这样就算程序一时没工夫处理这些数据,腾出手来之后也能在缓冲区中找到它们。

  3、《51》文中的WByte函数和RByte函数中都先打开计数器后关闭计数器。如果使用本文的外部中断法来接收数据,并且外部中断处理函数里外都调用了WByte或RByte的话,需要将这两个函数中的TR0=1,TR0=0操作的语句除去,并在UartInit()中加入一句TR0=1;即让TR0始终开着就可以。

  由于之前没有意识到这个问题,因此在具体应用时出现了奇怪的问题:表现为中断处理函数执行完毕之后,似乎回不到主程序,程序停在了一个不知道的地方。后来经过排查后找到了问题所在,那个程序的中断处理函数中用了RByte,中断处理函数外用到了WByte,而这两个函数的最后都有TR0=0。这样当中断处理函数执行完毕后,TR0实际上是为0的,返回主程序后(中断前的主程序可能正好处于其他的WByte或RByte执行中),原先以来定时器0溢出改变TF0才能执行下去的WByte函数就无法进行下去,从而导致整个程序停下来不动。(在本文的接收测试程序中不存在这个问题,因为中断处理程序中虽调用了RByte,但中断处理程序外却没有调用RByte或WByte)

  下面是修改后的RByte、WByte和WaitTF0函数,仅供参考:

  /**********************************************

  *            定时器0溢出后重装初值

  **********************************************/

  void WaitTF0(void)

  {

  TF0=0 ;

  #ifdef F11_0592

  TH0=0xFF ;// 定时器重装初值 fosc=11.0592MHz

  TL0=0xA0 ;// 定时器重装初值 fosc=11.0592MHz

  #endif

  #ifdef F18_432

  TH0=0xFF ;// 定时器重装初值 fosc=18.432MHz

  TL0=0x60 ;// 定时器重装初值 fosc=18.432MHz

  #endif

  while(!TF0);

  TF0=0 ;

  }

  /**********************************************

  *            从串口B接收一个字符

  **********************************************/

  uchar RByte()

  {

  uchar Output=0 ;

  uchar i=8 ;

  //    TR0=1;                             //启动Timer0

  /*

  #ifdef F11_0592

  TH0      = 0xFF;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

  TL0   = 0xA0;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz

  #endif

  #ifdef F18_432

  TH0      = 0xFF;    // 定时器重装初值 fosc=18.432MHz

  TL0   = 0x60;    // 定时器重装初值 fosc=18.432MHz

  #endif

  */

  WaitTF0();//等过起始位

  //接收8位数据位

  while(i--)

  {

  Output>>=1 ;

  if(newRXD)Output|=0x80 ;          //先收低位

  WaitTF0();//位间延时

  }

  //  while(!TF0) if(newRXD) break;    //此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收

  //    WaitTF0();                        //等过结束位

  //    TR0=0;                             //停止Timer0

  return Output ;

  }

  /**********************************************

  *            发送一个字节到串口B

  **********************************************/

  void WByte(uchar input)

  {

  //发送启始位

  uchar j=8 ;

  //TR0=1;

  newTXD=(bit)0 ;

  WaitTF0();

  //发送8位数据位

  while(j--)

  {

  newTXD=(bit)(input&0x01);//先传低位

  WaitTF0();

  input=input>>1 ;

  }

  //发送校验位(无)

  //发送结束位

  newTXD=(bit)1 ;

  WaitTF0();

  //TR0=0;

  }

  4、在上面的新修改后的RByte()函数中,有被注释掉的如下两句:

  //  while(!TF0) if(newRXD) break;    //此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收

  //    WaitTF0();                        //等过结束位

  这两句在《51》文中的程序是存在的,但是使用中断接收法后,加上这两句后出现了问题。表现为接收到的下一个字节的数据不完整或直接接收不到,似乎这两句占用了过多的时间。

  看这两句的目的似乎是要延时以跳过结束位,但是我感觉这个结束位可以不用管它,反正结束位是个高电平,不会妨碍下一个字节是否到来的判断(下一个字节的起始位是低电平)。那就由它去吧,没有必要为了它而占用CPU的时间。

  在本文的程序中,去掉这两句后程序执行正确,如果其他朋友在使用时真的出现问题,可以试着再把它们加上试一下

内容概要:本文介绍了一个基于Simulink的混合储能驱动永磁同步电机全系统仿真模型,涵盖了系统整体架构与关键控制策略,重点实现了电流环的二阶滑模控制(STSMC)、有限集模型预测控制(FCS-MPC)和PI控制等多种先进控制方法。该模型集成了混合储能系统与永磁同步电机驱动系统,能够模拟复杂工况下的动态响应、能量管理过程及多变量耦合特性,适用于高性能电机控制系统的设计、分析与验证,尤其在新能源汽车、电动驱动系统和工业自动化等领域具有重要应用价值。; 适合人群:具备Simulink仿真基础、电力电子与电机控制背景的高校研究生、科研人员及自动化、电气工程领域的研发工程师。; 使用场景及目标:①用于研究和对比不同电流控制策略(如STSMC、FCS-MPC、PI)在永磁同步电机系统中的动态性能、鲁棒性与抗干扰能力;②支撑混合储能系统在电动驱动、新能源汽车、智能电网等领域的系统级仿真与优化设计;③为先进控制算法的开发与工程化落地提供高保真、模块化的仿真平台。; 阅读建议:建议结合Simulink模型与相关控制理论进行对照学习,重点关注各功能模块之间的信号交互、控制逻辑设计及参数整定方法,可通过修改负载条件、切换控制模式等方式开展对比实验,深入理解系统动态行为与控制效果差异。
软件概述 UG(Unigraphics NX)是一款由西门子(Siemens PLM Software)开发的交互式CAD/CAM/CAE系统。作为全球领先的产品工程解决方案,它集成了产品设计、工程仿真与制造加工于一体。其功能强大且应用广泛,能够轻松实现各种复杂实体和造型的构造,为模具、汽车、航空航天及通用机械等行业提供了高性能的机械设计与制图灵活性。 软件基础信息 • 支持系统: 64位 Windows 10、Windows 11 核心功能模块 一、创新设计:高效、灵活、无缝协同 全链路产品设计 涵盖从2D布局、3D建模、装配设计到图纸文档记录的各个环节,大幅提升设计吞吐量,缩短交付周期超35%。 强大的同步建模技术 打破数据壁垒,可无缝导入并直接修改来自其他CAD系统的几何模型,是跨平台协同设计的理想选择。 复杂装配管理 专为大型复杂产品打造,即使面对成千上万的零件也能从容应对,快速识别并解决数字样机中的干涉等问题。 集成设计验证 内置自动验证功能,实时监控设计是否符合公司及行业标准;结合PLM数据可视化合成,辅助工程师做出更明智的决策。 二、综合仿真(Simcenter 3D):精准预测,降低试错成本 极速前后处理 依托先进的几何引擎,将强大的分析命令与几何编辑紧密集成,相比传统有限元工具,可缩短高达70%的仿真建模时间。 全方位结构分析 在同一环境中集成线性静力学、动态、疲劳及非线性分析,底层由业界顶尖的NX Nastran解算器提供支持,确保计算的高精度与可靠性。 声学与热管理分析 提供内外声学仿真以优化音质、降低噪音;具备一流的热传导仿真能力,帮助电子产品和工业机械实现最佳热管理方案。 多物理场耦合 简化了结构动力学、热传导、流体流动等复杂物理现象的模拟过程,消除外部数据传输错误,真实还原产品运行工况。 三、智能制造(CAM):打通从计划到车间的数字主线 全面的制造解决方案 提供从工装设计、CAM编程到机床控制器(如Sinumerik)的一体化支持,助力制定更科学的生产决策。 深度集成的PLM环境 借助Teamcenter实现数据和流程的统一管理,避免多数据库冲突,支持重用验证过的加工工艺与刀具库。 车间级互联 通过DNC系统与车间无缝对接,直接将加工数据和刀具清单下发至CNC机床,实现计划与生产的紧密结合。 提质增效 优化NC编程与刀具路径,提升表面精加工水平与零件精度;减少人为错误,显著提高新机床部署成功率及制造资源利用率。 总结 UG NX 2023作为一款集成化的产品工程解决方案,通过其强大的设计、仿真和制造功能,为现代制造业提供了完整的数字化产品开发平台。无论是复杂产品的设计验证,还是精密制造的流程优化,UG NX 2023都能为工程师团队提供高效、可靠的解决方案,助力企业提升产品创新能力和市场竞争力。 适用领域 模具设计、汽车制造、航空航天、通用机械、消费电子等
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值