以NRF52832为例,IO口模拟串口

什么是uart ?

串口UART,也称为通用异步收发器。采用串行通信的方式,也就是只能一位一位的发。串口一般需要TX-发射端,RX-接收端,GND-地,三根线,主机的TX 、RX,接从机RX , TX,二者共地。

数据帧的结构

串口发送数据以字节为单位发送,发送的一个字节,叫一帧数据。

在这里插入图片描述

包括,1-bit 开始位,8-bit 数据,1-bit 校验位(奇/偶校验)和 1-bit 停止位;
串口的TX,RX大部分状态都是高电平;
开始位是0(低电平),停止位是1(高电平)
8bit数据是按照 先低位后高位的顺序发送。例如发送96(1001 0110),按照0-1-1-0-1-0-0-1的顺序发送。

校验位

校验分为奇校验和偶校验;怎么校验呢?简单来说,奇校验就是检查数据位中1(高电平)的个数,是否为奇数个,如果不是,校验位就为1(补1的个数为奇数)。相对的偶校验就是检查数据位中1的个数,然后补为偶数个。
例如:数据位是 0X55 , 奇校验位是1(数据位4个1),如果偶校验则是0。

波特率

波特率(baud)是指发送二进制数据位的速率。常见有115200,9600,4800等。发送1bit数据的时间 = 1/baud ;

如何用IO口模拟串口?

串口的作用其实是数据的收和发;其实是可以用IO口模拟的;模拟收数据,其实就是读电平,即读到8bit数据的电平,读1次,延时,再读,一直读8次,然后记录下来,还原成一个字节。模拟发数据,其实就是拉高拉低电平,模拟真实发数据时TX的电平变化;也是拉电平,延时,再拉。
模拟的关键是时间的把握,如果时序模拟不准,是会发错收错的。
自写算法如下:

模拟发送:
发送不用管其他的,只要配置一个io口,根据数据来拉高拉低电平,延时,移位后继续判断,再重复进行。最后直接在初始化调用发字符串的函数,或者放在定时器里面就可以一直发。


void TX_IO_INIT(void)
{
	nrf_gpio_cfg_output(virtual_tx_io);       //输出
}
void IO_LOW(void)
{
	nrf_gpio_pin_write(virtual_tx_io, 0);
}


void IO_HIGH(void)
{

	nrf_gpio_pin_write(virtual_tx_io, 1);
}
//模拟串口发一个字节

void virtual_uart_send_byte(uint8 pdata)
{
	int  i;
	int odd = 0;

	IO_LOW();                     //起始位
	nrf_timer_delay_us(208);     //波特率4800, t=1000ms/4800     0.2083ms
	for(i = 0; i < 8; i++)		//8位数据
	{
		if(pdata & 0x01)                  //bit 0   高还是低
		{
			IO_HIGH();
			odd++;
		}
		else
		{
			IO_LOW();
		}
	nrf_timer_delay_us(208);
		

		pdata >>= 1;                     //右移  bit 1, 2,...

	}
	
//     奇校验位	
	if(odd%2 == 0)
	{
		IO_HIGH();
		nrf_timer_delay_us(208);	
	}
	else
	{
	
		IO_LOW();
		nrf_timer_delay_us(208);
	}		

	
	
	IO_HIGH();			//停止位,拉高电平
	nrf_timer_delay_us(208);
	
}


//模拟串口发一个字符串
void virtual_uart_send_string(uint8  *str,uint8 datalen)
{
		uint8 i;
	for(i = 0; i < datalen; i++)
	{
		virtual_uart_send_byte(str[i]);
		nrf_timer_delay_us(500);                 //发送一个字节延时一下
	}
}


模拟接收:
接收是采用外部中断的形式进入的,当发来一帧数据时,RX口在起始位产生一个下降沿,可以以此标志进入中断,中断挂起,然后读数据。每次进入中断,都收到一帧数据。

void virtual_RX_GPIO_init(void)
	
{
	
	NRF_LOG_INFO("rx_io");
		ret_code_t errCode = nrf_drv_gpiote_init();		  // GPIOE驱动初始化
	APP_ERROR_CHECK(errCode);
	nrf_drv_gpiote_in_config_t inConfig = GPIOTE_CONFIG_IN_SENSE_HITOLO(false);		//high to low      port事件
	inConfig.pull = NRF_GPIO_PIN_PULLUP;												// 默认上拉
	inConfig.sense = GPIOTE_CONFIG_POLARITY_HiToLo;                  					// high to low            
	
errCode = nrf_drv_gpiote_in_init(virtual_rx_io, &inConfig, RX_irqCallbackFunc);

	APP_ERROR_CHECK(errCode);
	nrf_drv_gpiote_in_event_enable(virtual_rx_io, true);	

}
//中断函数
void RX_irqCallbackFunc(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
	if(nrf_gpio_pin_read(virtual_rx_io) == 0)          //检测到低电平  (开始位) 
	{
		
		s_irqValue = RX_DATA_VALUE;
		nrf_drv_gpiote_in_event_disable(virtual_rx_io);     //中断挂起
		nrf_timer_delay_us(209);
		virtual_RX_HandleIrq(s_irqValue);       //延时一个bit后,开始接收8bit数据
		
		nrf_drv_gpiote_in_event_enable(virtual_rx_io, true);    //打开
	}
void virtual_RX_HandleIrq(uint8 irqValue)
{
	uint8 RX_Data;
	uint8 RecvData ;
	uint8 i;

	
	if(irqValue & RX_DATA_VALUE) 
	{     
	for(i=0;i<8;i++)
	{
	RX_Data = nrf_gpio_pin_read(virtual_rx_io);
	
	if(RX_Data)                          // 收到 1
	{	
	RecvData |= 0x80;											
	}
	else                         		//收到 0
	{
	RecvData &= 0x7F;          				
	}	
	if(i<7)
	{
	RecvData>>=1;                    //移7次
	}
	nrf_timer_delay_us(209);
	
	}
       //校验位
	nrf_timer_delay_us(208);
	}
	NRF_LOG_INFO("RX %02X",RecvData);
}
关于数据接收准确的问题

前面也说过,影响准确性的最主要原因是时序。如何保证时序正确?最好的是在示波器上看,测试你发的间隔,判读你读取的位置,调整延时,使处于最佳位置。
比如,接收数据时,在起始位,你可以多延时半个bit的时间,这样下次读取,就在数据位的中间一段,这样数据会更准确。
你的工程中的其他优先级较高的进程,可能会响应你的准确性,比如nordic的广播和扫描。
最后就是你软件延时的准确性,有可能不准的,同样会影响你的结果。
自写的Us延时:

void nrf_timer_delay_us( uint16 number_of_us)
{

		NRF_TIMER4->PRESCALER  = 4;                //2^4   16分频得到1M timer时钟
        NRF_TIMER4->MODE = 0;                      //timer模式
        NRF_TIMER4->BITMODE = 3;                   // 设置32bit
		NRF_TIMER4->TASKS_CLEAR = 1;             //清定时器
        NRF_TIMER4->CC[0] = number_of_us;             //一个tick是1us,
  
        NVIC_SetPriority(TIMER4_IRQn, 0);          //设置中断优先级 
        NVIC_ClearPendingIRQ(TIMER4_IRQn);          //清除外部中断挂起
	
		 NRF_TIMER4->TASKS_START = 1;               //使能timer模块
		while (NRF_TIMER4->EVENTS_COMPARE[0] == 0)   //等待计时完成,EVENTS_COMPARE[0] == 1;
		{
		
		
		}
		NRF_TIMER4->EVENTS_COMPARE[0] = 0;           //清零
		NRF_TIMER4->TASKS_STOP = 1;					//停止计时
		
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值