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

代码:

复制代码
/*
  《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;
}

仿真效果图:

相关推荐
智者知已应修善业2 小时前
【51单片机用数码管显示流水灯的种类是按钮控制数码管加一和流水灯】2022-6-14
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
智商偏低8 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen9 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森11 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白11 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D12 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术15 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt15 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘15 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang16 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c