使用普通定时器产生半双工软件串口

代码:

复制代码
/*
  《AVR专题精选》随书例程
  
  3.通信接口使用技巧

  项目:使用普通定时器和外中断实现半双工软件串口
  文件:softuart.c
  说明:软件串口驱动文件
        
  作者:邵子扬
  时间:2012年12月16日

*/
#include "softuart.h"

// 声明软件串口变量
volatile struct TSOFTUART stUART;

// 初始化软件串口
void sfUART_init(void)
{
  // 设置IO状态
  PINDIR(sfUART_TXDIO, PIN_OUTPUT);
  PINSET(sfUART_TXDIO);
  PINDIR(sfUART_RXDIO, PIN_INPUT);
  PINSET(sfUART_RXDIO);
  
  // 初始化内部变量
  stUART.TXDcnt = 0;
  stUART.RXDcnt = 0;
  stUART.sfTXC  = 0;
  stUART.sfRXC  = 0;
  stUART.sfMODE = 0;

  // 设置定时器参数
  // CTC Mode
  // 分频系数=1
  OCR2 = F_CPU / sfBAUDRATE - 1;
  TCCR2 = (1 << WGM21);
  sf_START_TMR();  // 启动定时器

  // 设置外中断
  // 允许外中断1,下降沿触发方式
  MCUCR = (1 << ISC11);
  sfUART_mode(sfUART_mode_RXD);

}

// 设置软件串口工作模式
void sfUART_mode(char mode)
{
  char itmp;

  itmp = __save_interrupt();   // 保存中断状态
  cli();                       // 关中断

  stUART.sfMODE = mode;        // 设置工作模式
  if(stUART.sfMODE == sfUART_mode_TXD)
  {
    sf_START_TMR();
    sf_DISABLE_RXINT();
  }
  else
  {
    sf_STOP_TMR();
    sf_ENABLE_RXINT();
  }
  __restore_interrupt(itmp);   // 恢复中断
}

// 检查数据接收标志
char sfUART_RXC(void)
{
  return stUART.sfRXC;
}

// 检查数据发送完成标志
char sfUART_TXC(void)
{
  return stUART.sfTXC;
}

// 清除数据发送完成标志
void sfUART_clrTXC(void)
{
  stUART.sfTXC = 0;
}

// 读取数据
char sfUART_getbyte(void)
{
  stUART.sfRXC = 0;     // 清除数据接收标志
  return stUART.RXDBUF;
}

// 发送数据
// 调用函数后,数据只是放入发送缓冲区
// 发送过程由软件串口服务程序完成
void sfUART_sendbyte(char dat)
{
  stUART.TXDBUF = dat;  // 数据放入缓冲区
  stUART.sfTXC = 0;     // 清除标志位
  stUART.TXDcnt = 1;    // 开始发送
}

// 软件串口服务程序
void sfUART_svr(void)
{
  // 数据发送
  if(stUART.sfMODE == sfUART_mode_TXD)
  {
    // send
    switch(stUART.TXDcnt)
    {
      case 0:// 无数据
        break;
      case 1:// 起始位
        stUART.TXDcnt++;
        PINCLR(sfUART_TXDIO); // 设置IO输出低电平
        break;
      case 2:// 8位数据, 先发送低位
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
      case 8:
      case 9:
        if(stUART.TXDBUF & 0x01)
          PINSET(sfUART_TXDIO);
        else
          PINCLR(sfUART_TXDIO);
        stUART.TXDBUF = stUART.TXDBUF >> 1;
        stUART.TXDcnt++;
        break;
      case 10:// 停止位
        stUART.TXDcnt = 0;
        PINSET(sfUART_TXDIO);
        stUART.sfTXC = 1;
      default:
        break;
    }
  }
  else
  {
    // 接收
    switch(stUART.RXDcnt)
    {
      case 0:// 起始位
        stUART.RXDcnt = 1;
        stUART.RXDBUFtmp = 0;
        break;
      case 1:// 采样数据
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
      case 8:
        stUART.RXDBUFtmp = stUART.RXDBUFtmp >> 1;
        if(PININ(sfUART_RXDIO))
          stUART.RXDBUFtmp |= 0x80;
        stUART.RXDcnt++;
        break;
      case 9:// 停止位
        stUART.sfRXC = 1;
        stUART.RXDcnt = 0;
        sf_STOP_TMR();      // 停止定时器
        stUART.RXDBUF = stUART.RXDBUFtmp;
        sf_ENABLE_RXINT();  // 允许外部中断, 可以接收后面的数据
        break;
      default:
        break;
    }
  }
}

// 定时器中断服务程序
ISR(TIMER2_COMP_vect, ISR_NOBLOCK)
{
  sfUART_svr();  // 需要调用软件串口服务程序

}

// 外部中断服务程序
ISR(INT1_vect, ISR_NOBLOCK)
{
  // 禁止INT1中断,直到接收完成,避免重复触发
  sf_DISABLE_RXINT();

  stUART.RXDcnt = 1;
  TCNT2 = OCR2 / 16;
  sf_START_TMR();  // 启动定时器, 接收数据
}

main.c

复制代码
/*
  《AVR专题精选》随书例程
  
  3.通信接口使用技巧

  项目:使用普通定时器和外中断实现半双工软件串口
  文件:main.c
  说明:主程序,演示软件串口的使用方法
        
  作者:邵子扬
  时间:2012年12月16日

*/
#include "cfg.h"
#include "macromcu.h"
#include "softuart.h"

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>


int main()
{
  unsigned int UBRRREG;
  unsigned char tmp;

  sfUART_init();  // 软件串口初始化

  // 设置硬件串口作为对比
  UBRRREG = F_CPU / ( 8 * sfBAUDRATE ) - 1;
  UBRRH = UBRRREG / 256;
  UBRRL = UBRRREG % 256;
  UCSRA = ( 1 << U2X );
  UCSRB = ( 1 << TXEN );
  UCSRC = ( 1 << UCSZ1 ) | ( 1 << UCSZ0 );

  sei();  // 开中断

  sfUART_mode(sfUART_mode_TXD);  // 设置为发送模式
  sfUART_sendbyte('>');          // 发送提示字符
  _delay_ms(100);                // 延时100ms,等待发送完成
  sfUART_mode(sfUART_mode_RXD);  // 切换为接收模式

  for(;;)
  {
    if(sfUART_RXC())             // 是否接收到数据
    {
      tmp = sfUART_getbyte();    // 读取数据
      UDR = tmp;                 // 发送到硬件串口
      sfUART_mode(sfUART_mode_TXD); // 切换到发送模式
      sfUART_sendbyte(tmp);      // 通过软件串口发送
    }
    if(sfUART_TXC())             // 发送完成
    {
      sfUART_clrTXC();           // 清除发送完成标志
      sfUART_mode(sfUART_mode_RXD); // 切换回接收模式
    }

  }

  return 0;
}

仿真效果图:

相关推荐
qq_401700412 分钟前
大彩串口屏DC80480M070使用以及软件配置
嵌入式硬件
LNN202212 小时前
STM32H7 + 迪文屏 DGUS 开发实战:从零构建工业级时间设置界面
stm32·单片机·嵌入式硬件
Z文的博客16 小时前
嵌入式MCU与迪文屏通信:DMA+环形FIFO+变长队列+状态机完整手册
stm32·单片机·串口·dma·中断·串口dma·嵌入式单片机
12.=0.16 小时前
【stm32_5】Systick嘀嗒定时器、解析时钟源、分析时钟树、应用Systick设计延时
c语言·stm32·单片机·嵌入式硬件
达不溜的日记17 小时前
CAN总线网络传输层CanTp详解
网络·stm32·嵌入式硬件·网络协议·网络安全·信息与通信·信号处理
森利威尔电子-18 小时前
森利威尔SL6129兼容 AL8805 / AL8806,输入电压 5.5V - 30V,最大输出电流 1.2A
单片机·嵌入式硬件·集成电路·芯片·电源芯片
FreakStudio18 小时前
嘉立创开源:应该是全网MicroPython教程最多的开发板
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy
qq_4416857519 小时前
CC26xx开发 第一节 前期准备
嵌入式硬件
史蒂芬_丁19 小时前
TI F28P65 使用 ePWM 模块模拟 SPI 时钟的详细方法
单片机·嵌入式硬件·fpga开发
LinuxRos19 小时前
I2C子系统与驱动开发:从协议到实战
linux·人工智能·驱动开发·嵌入式硬件·物联网