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

代码:

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

仿真效果图:

相关推荐
Zwawa2 小时前
C语言函数指针与宏实战:从回调、表驱动菜单到X-Macro,讲透嵌入式代码解耦
c语言·单片机·嵌入式
shimly1234563 小时前
(done) 嵌入式硬件电路图 (2) 熟悉常用元器件
嵌入式硬件
安妮细水长流8 小时前
ad7124
c语言·单片机
花月mmc10 小时前
【FPGA】神经网络内容简介预告
嵌入式硬件·深度学习·神经网络·fpga开发
en.en..11 小时前
STM32 HAL 库 | 核心知识点整理
单片机·嵌入式硬件
无敌贵点大王11 小时前
从寄存器层面重学STM32(9)——DMA理论
stm32·单片机·嵌入式硬件
希和202712 小时前
51 单片机入门笔记:江科大课程代码逐行讲解(按键控制 LED 流水灯)
笔记·单片机·mongodb
希和202712 小时前
51 单片机按键控制 LED:江科大课程代码小白笔记
笔记·单片机·嵌入式硬件
希和202712 小时前
51 单片机 LED 流水灯:从直接法到循环法(江科大课程笔记)
笔记·单片机·mongodb
Zwawa1 天前
用两个按键做一个OLED菜单:先把选择和执行分开
c语言·单片机·嵌入式