1. 程式人生 > 其它 >以NRF52832為例,IO口模擬串列埠

以NRF52832為例,IO口模擬串列埠

技術標籤:nrf52832UART

什麼是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;					//停止計時
		
}