赞
踩
串口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口模拟的;模拟收数据,其实就是读电平,即读到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; //停止计时 }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。