单片机IO口模拟串口实现原理

参考链接

1、使用GPIO来模拟UART
2、STM32之IO模拟串口篇

1、工作原理

单片机IO口模拟串口的实现原理通常是通过软件来模拟串行通信的传输和接收。下面说明了单片机IO口模拟串口的实现原理:

  1. 配置IO口:选择两个IO口作为模拟串口的发送和接收引脚。通常使用的是GPIO引脚。

  2. 设置通信参数:设置波特率、数据位、停止位和校验位等串口通信参数。

  3. 发送数据:通过控制发送引脚的电平变化来模拟串口数据的传输。根据通信参数,将要发送的数据进行位的切换操作,逐位通过发送引脚发送出去。

  4. 接收数据:通过读取接收引脚的电平变化来模拟串口数据的接收。根据通信参数,读取接收引脚的电平状态,逐位组装接收到的数据。

  5. 确保同步:为了确保发送和接收的同步性,可以在发送和接收的开头和结尾添加起始位和停止位。发送时在数据的起始位置输出一个低电平的起始位,接收时检测到低电平的起始位后开始接收数据,接收完毕后检测到高电平的停止位表示接收完成。

单片机IO口模拟串口的实现原理相对简单,但通信速率可能受到单片机性能和IO口速度的限制,因此在高速通信或实时性要求较高的场景下,可能需要选择硬件串口或其他更高级的通信方式。

2、UART协议:

空闲位:当uart处于空闲状态(线路没有数据传输)时,TX、RX线都处于高电平状态(逻辑“1”),一般需要把相应IO口配置为上拉。

起始位:由高电平跳变为低电平,且持续一个位宽度,表示触发起始信号。

数据位:数据位可以5、6、7或8位,从最低位开始一位接着一位的传送。

校验位:有奇、偶或无校验。

	 奇校验:数据位+校验位的“1”的位数总和为奇数;
	 偶校验:数据位+校验位的“1”的位数总和为偶数;

例如:奇校验中,数据位“1”的位数为偶数个,则此时校验位为“1”。

无校验:顾名思义就是没有校验位,数据位后面接停止位。(通常配置为无校验。)

停止位:将数据线拉为高电平,可以设置停止位宽度为1位、1.5位或者2位。

波特率:在串口中波特率为每秒传送的bit数;通信双方必须设置相同的波特率,否则接收的数据为乱码。波特率9600时,传输1bit所需时间为(1/9600)us,大约为104us。

3、代码实现(无OS)

/**
 */
 
#include "./BSP/MYPRINTF/myprintf.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "./BSP/TIMER/btim.h"
 
//开始接收数据标志
volatile unsigned char uartStartFlag = 0;
 
//串口接收缓存
unsigned char uartBuf[256] = {0};
unsigned char uartBufLen = 0;
unsigned char uartHaveDat = 0;
 
//超时错误处理
volatile unsigned int uartBufTimeout = 0;
volatile unsigned int uartBufStartTimeout = 0;
 
void myuart_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
   
    TX_GPIO_CLK_ENABLE();
    gpio_init_struct.Pin = TX_GPIO_PIN;                   
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;         
    HAL_GPIO_Init(TX_GPIO_PORT, &gpio_init_struct);       
          
    RX_GPIO_CLK_ENABLE(); 
    gpio_init_struct.Pin = RX_GPIO_PIN;   
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */   
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;                  
    HAL_GPIO_Init(RX_GPIO_PORT, &gpio_init_struct);                        
    HAL_NVIC_EnableIRQ(RX_INT_IRQn);
    Set_TX(0);                                               
}
 
void send_byte(uint8_t data){
   Set_TX(0);
   delay_us(104);
   for(int i = 0; i < 8; i++){
      if(data & 0x01){
         Set_TX(1);
      }
      else{
         Set_TX(0);
      }
      delay_us(104);
      data = data >> 1;
   }
   Set_TX(1);
   delay_us(104);
      
}
 
void send_str(char *dat){
   for(int i = 0; i < strlen(dat); i++){
      send_byte(dat[i]);
   }
}
 
void myprintf(char *fmt, ...){
   va_list ap;
   char string[512];
   va_start(ap, fmt);
   vsprintf(string, fmt, ap);
   send_str(string);
   va_end(ap);
}
 
 
void RX_INT_IRQHandler(void){
    HAL_GPIO_EXTI_IRQHandler(RX_GPIO_PIN);         /* 调用中断处理公用函数 清除KEY0所在中断线 的中断标志位 */
    __HAL_GPIO_EXTI_CLEAR_IT(RX_GPIO_PIN);         /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}
 
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
   if(GPIO_Pin == RX_GPIO_PIN){
      if(uartStartFlag == 0){
         uartStartFlag = 1;
         btim_timx_int_init(52 - 1, 72 - 1, BTIM_TIM6_INT);    //52us接收数据
      }
   }    
}
 
外部中断模拟串口,波特率不能超过65536 实验测试:发送57600可以正常,但接收只能 <= 38400 #include "Uart_EXT0.h" #include "MAIN.h" #define FOCS 22114800ul bit Over; bit bRxflag; unsigned char IEN0_NOW,IEN1_NOW; //中断临时变量 unsigned char idata bRxstate=0; //接收状态 unsigned char idata tmp_Len=0; //缓存数组下标 unsigned char idata bRxlen; //接收字节数 unsigned char xdata EX_buf[64]; //接收存放区 /*************************************************** baud = 56000 接收一字节 =178.6us ,接收会出现错误 baud = 38400 接收一字节 =260.4us //快速接收都容易出现错误 baud = 19200 接收一字节 =520us | baud = 14400 接收一字节 =694.5us | baud = 9600 接收一字节 =1041.6us | baud = 4800 接收一字节 =2.083ms 接收过长,定时器MODE2无法满足 //主机发送的数据最好加上校验字 发送:最大57600,发送还能正常。 ****************************************************/ void Time0_Uart(unsigned int baud) { TMOD &= 0XF0; TMOD |= 0X02; //使用定时器0方式2,使用方式1则2400波特也能有 TH0 = 256-(FOCS/12)/baud; TL0 = TH0; IP0 |= 0x02; //设置为最高优先级 IP1 |= 0x02; TR0 = 1; ET0 = 1; EA = 1; } void Time0_isr() interrupt 1 { //方式1则重装值 Over = 1; } /* 发送数据,1起始位,8数据,1停止位 发送波特率实验57600都不会错误,大量发送数据待测试 */ void Uart_set(unsigned char dat) { unsigned char i; Over = 0; TL0 = TH0; //防止发送数据开始时不知道TL0是多少 T_uart = 0; //起始位 while(Over==0); Over = 0; for(i=0;i>= 1; } // Over = 0; //数据位清零了 T_uart = 1; //停止位,此后如果没有数据则一直为高 while(Over==0); } unsigned char get_Uart() { unsigned char I=0,get_dat=0; Over = 0; TL0 = TH0; //重新赋值,防止出错 while(Over==0); Over = 0; for(I=0;I>= 1; if(Get_ex == 1) get_dat |= 0x80; else get_dat &= 0x7f; while(Over==0); Over = 0; } if(Get_ex == 1) { Over = 0; } return get_dat; } void EXuart_isr() interrupt 0 { unsigned char _chr; IEN0_NOW = IEN0; //进入接收1字节,关闭中断 IEN1_NOW = IEN1; IEN0 = 0X82; IEN1 = 0; _chr=get_Uart(); switch(bRxstate) { case 0: if(_chr==0x55) { bRxstate=1; } else bRxstate=0; break; case 1: if(_chr==0xaa) { bRxstate=2; } else bRxstate=0; break; case 2: //取得数据长度 bRxlen=_chr; bRxstate=3; break; case 3: EX_buf[tmp_Len]=_chr; tmp_Len++; if(tmp_Len==bRxlen) { bRxstate=0; tmp_Len=0; bRxflag=1; //一帧数据接收完毕,置位完成标志位 } break; default: break; } IE0 = 0; //清外部0中断标志,防止下次接收不到 IEN0 = IEN0_NOW; IEN1 = IEN1_NOW; } void main() { Time0_Uart(9600); EX_uart(); if(bRxflag==1) { bRxflag = 0; for(i=0;i<10;i++) RevBuffer[i] = EX_buf[i]; T0Uart_TX(10,RevBuffer); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值