GPIO模拟UART串口时序

GPIO模拟UART串口时序

模拟时序:

GPIO模拟UART串口时序_第1张图片

平台:Cortex-M0

与FPGA UART的区别:

  FPGA发送、接收使用的是独立的时序,并行处理易于实现。而单片机只能使用定时器来模拟时序,并通过外部下降沿中断触发启动,实时性受到限制;对于实时性要求较高的应用,需要同时处理发送和接收时(全双工)1路UART需要使用2个定时器;而半双工应用可以只使用一个定时器即可实现。基于50MHZ的M0一般9600是可以实现的,在向上估计会不稳定。主要是应用于对于特定设计临时增加低速串口通信,降低成本。

示例代码:

V1.5:双定时器方案

1、myiouart.h+ myiouart.c

2、资源占用:1个输入中断+2个定时器(针对实时要求高的场合)

myiouart.h:
//<<< Use Configuration Wizard in Context Menu >>>
#ifndef _MYIOUART_H_
#define _MYIOUART_H_
#include "LPC11xx.h"

// MyIouartConfig [2017.2.20]
//   注:双定时器模拟串口!CT32B0+CT32B1
	//ECHO <0=> No <1=> Yes
	//回显测试功能: 
	//打开回显功能后,模拟串口将自动返回自己所接收到的数据,仅作测试串口功能使用!
	#define ENABLE_ECHO 	0					 

	// COM_BAUDRATE <1200=> 1200 <9600=> 9600  <57600=> 57600  <115200=> 115200
	// 串口通讯波特率配置
	// 默认为9600!
	#define IOUART_BAUDRATE	9600


//


//--------------------------------------------------------------
#define IOUART_RECV_NULL 0         //没有接收数据
#define IOUART_RECV_OK 1           //接收数据成功返回值
#define IOUART_RECV_WRONG_ST 11    //接收数据起始位出错返回值
#define IOUART_RECV_WRONG_EN 12    //接收数据结束位出错返回值

//
#define   SET_UART_BAUD   48000000UL/(IOUART_BAUDRATE) //波特率设置
#define   MyUart_STOPBITS 1



//

//config  iouart3
#define  MyIOUART_TX_PORT      PIO2_8
#define  MyIOUART_RX_PORT      PIO2_7
#define  MyIOUART_TX_P         LPC_GPIO2
#define  MyIOUART_RX_P         LPC_GPIO2
#define  MyIOUART_TX_P_INDEX   8
#define  MyIOUART_RX_P_INDEX   7
//
#define  MyIOUART_RX_IRQn   EINT2_IRQn


//------------------------------------------

extern char g_strtemp[32];	
extern unsigned long g_rx_cnt;//For Debug. Record the count value of recieved bytes!
extern unsigned long g_tx_cnt;//For Debug. Record the count value of sended bytes!


//
#define BUFFER_LEN 128

//
char* NumToStrEx(long Number,char*PStr,unsigned char Len);
//FIFO Buffer
void Iouart_FifoInit(void);
unsigned char GetLen_RecvedData(void);
unsigned char* GetTxbuf_RecvedData(void);
unsigned char ReadByte_RxFiFo(void);
int WriteByte_TxFifo(unsigned char *T,unsigned char len);//非阻塞

//
void put_char(char *cp);//阻塞
void iouart1_send(char *pData,unsigned char pLen);//阻塞
void print_dat(char sp[],char len);//阻塞
//
void IOUART1_Init(void);

void Timer2Init(void);
void Timer3Init(void);





//--------------------------------------------------------------
#endif
// <<< end of configuration section >>>

myiouart.c:
#include "myiouart.h"
//
char g_strtemp[32]={0};	
unsigned long g_rx_cnt = 0;//For Debug. Record the count value of recieved bytes!
unsigned long g_tx_cnt = 0;//For Debug. Record the count value of sended bytes!

//RX
static volatile unsigned char l_recv_byte=0;
static volatile unsigned char l_recv_st=0;
static volatile unsigned char l_recv_cnt=8;

//TX
static volatile unsigned char l_send_byte=0;
static volatile unsigned char l_send_st=0;
static volatile unsigned char l_send_cnt=8;
static volatile unsigned char l_send_style=0;//根据send_style判断是通过缓存发送还是直接发送

//
static volatile unsigned char l_channel=0;
//
//l=local g=global  
//FIFO Buffer
unsigned char l_Txbuffer[8+BUFFER_LEN]={0};
volatile unsigned char l_Rxbuffer[8+BUFFER_LEN]={0};
volatile unsigned char l_RxDataLen=0;
volatile unsigned char l_RxWrongRecord[2+BUFFER_LEN]={0};//接收数据出错记录BUFFER
//
void Iouart_FifoInit(void)
{
	//仅记录一个BUFFER_LEN长度的接收错误记录
	l_RxWrongRecord[0]=2;//sp:2->(2+BUFFER_LEN)-1
	l_RxWrongRecord[1]=(2+BUFFER_LEN)-1;//end addr
	//
	l_RxDataLen=0;
	
	//Rx
	//接收数据的起始地址(使用相对地址从8->BUFFER_LEN-1)
	l_Rxbuffer[0]=8;
	l_Rxbuffer[1]=0;
  //接收数据的结束地址
	l_Rxbuffer[2]=8;
	l_Rxbuffer[3]=0;
	//Buffer的结束地址
  l_Rxbuffer[4]=(BUFFER_LEN-1)+8;
	l_Rxbuffer[5]=0;
	//接收数据的长度
	l_Rxbuffer[6]=0;
	//Buffer的长度
	l_Rxbuffer[7]=BUFFER_LEN;
	
	//Tx
	//Tx数据的起始地址
	l_Txbuffer[0]=8;
	l_Txbuffer[1]=0;
  //Tx数据的结束地址
	l_Txbuffer[2]=8;
	l_Txbuffer[3]=0;
	//Buffer的结束地址
  l_Txbuffer[4]=(BUFFER_LEN-1)+8;
	l_Txbuffer[5]=0;
	//Tx数据的长度
	l_Txbuffer[6]=0;
	//Buffer的长度
	l_Txbuffer[7]=BUFFER_LEN;
	
}

/*
获取Buffer中接收到的数据长度
*/
unsigned char GetLen_RecvedData(void)
{
   return l_RxDataLen;
}

unsigned char* GetTxbuf_RecvedData(void)
{
   return l_Txbuffer;
}
/*
 从模拟串口读取一个字节数据 
 调用该函数前先判断GetRecvDataLen()返回值,否则在没有接收到数据时读到的数据为0
*/
unsigned char ReadByte_RxFiFo(void)
{
	unsigned char Data0=0;
	if(l_Rxbuffer[6])
	{
		 Data0 = l_Rxbuffer[l_Rxbuffer[0]];
		 //
		 if(l_Rxbuffer[0]< l_Txbuffer[4])
		 {
		   l_Rxbuffer[0]++;
		 }
		 else
		 {
			 l_Rxbuffer[0]=8;
		 }
		 //
		 l_Rxbuffer[6]--;
	}
  //update rx len
	l_RxDataLen=l_Rxbuffer[6];
	
  return Data0;
}

/*
 从模拟串口发送一个数据Buffer,属于一次性连续发送!
 若发送缓冲区满返回-1
*/
int WriteByte_TxFifo(unsigned char *T,unsigned char len)
{
  unsigned char i = 0;
	if(len)
	while(len--){
	if(l_Txbuffer[6] < l_Txbuffer[7]) {
     l_Txbuffer[6]++;//TxLength
		 l_Txbuffer[l_Txbuffer[2]]=T[i++];
		 if(l_Txbuffer[2]TCR = 1;
	 l_send_st=1;

   //-----------
   return 1;
	
}



/*
  数字转字符串函数
*/
char* NumToStrEx(long Number,char*PStr,unsigned char Len)
{
	unsigned long NumberT=0;
	unsigned char Count=0;
	if(Number<0)  
	{
		*PStr='-';
		Number=-Number;
		Count=1;
	}
	else if(Number==0)
	{
	  *PStr='0';
		*(PStr+1)=0;
		return PStr;
	}
	NumberT=Number;
	while(NumberT)
	{
	   	 NumberT/=10;
		 Count++;
	}
	if(Len<=Count)  return 0;
	//
	*(PStr+Count--)=0;
	//
	while(Number)
	{
		*(PStr+Count--)='0'+Number%10;
		Number/=10;
	}
	return PStr;
}

void put_char(char *cp)
{
    //LPC_GPIO2->IE &= ~(1<<7);LPC_GPIO2->IC=(1<<7); //NVIC_DisableIRQ(EINT2_IRQn);
	  //LPC_GPIO3->IE &= ~(1<<1);LPC_GPIO3->IC=(1<<1);
	  //---------------------------------------------
  	
		l_send_byte = *cp;	
		l_send_cnt = 9;
		l_send_style = 0;
  	l_send_st=1;
		// LPC_TMR16B1->TCR = 1;//start counter 
	  LPC_TMR32B0->TCR = 1;//根据使用的定时器选择
		while(l_send_st){}

	  //--------------------------------------------
	
	  //LPC_GPIO2->IE |= (1<<7);
	  //NVIC_EnableIRQ(EINT2_IRQn); 
	  //LPC_GPIO3->IE |= (1<<1);

}


/*
    阻塞发送字符串,不使用缓存
*/
void iouart1_send(char *pData,unsigned char pLen)
{
	while(pLen--)
	{
		put_char(pData++);
	}
}

/*
    阻塞发送数据,不使用缓存
*/
void print_dat(char sp[],char len)
{
  LPC_GPIO0->DATA |= (1 << 7);//H	485TxMode
	//-----------------------------------------
	while(len--)
	{
		put_char(sp++);
	}
	//-----------------------------------------
	LPC_GPIO0->DATA &= ~(1 << 7);	//L	485RxMode	
}


void IOUART1_Init(void)
{
	Iouart_FifoInit();
	//
	LPC_SYSCON->SYSAHBCLKCTRL |= (1ul << 6); 
	//UART3 [channel=0] & 4852  EN--P0.7   RX--P2.7  TX--P2.8 
	//TX PIO2_8
	LPC_IOCON->MyIOUART_TX_PORT &= ~(0x07);	/*IO功能*/
	MyIOUART_TX_P->DIR  |= (1 << MyIOUART_TX_P_INDEX);	/*Output*/
	MyIOUART_TX_P->DATA |= (1 << MyIOUART_TX_P_INDEX);//H	
	//RX  PIO2_7    
	LPC_IOCON->MyIOUART_RX_PORT &= ~(0x07);//IO fucntion 
	MyIOUART_RX_P->DIR &= ~(1<IS  &= ~(1<IEV &= ~(1<IBE &= ~(1<IC=(1<IE |= (1<DATA &= ~(1 << 3);//L	
	 //Record wrong case.Just For Debug.
	 l_RxWrongRecord[l_RxWrongRecord[0]]=IOUART_RECV_WRONG_ST;
	 if(l_RxWrongRecord[0]IC |= (1<IE |= (1<DATA |= (1 << 3);//H  //LED
	//
	if((MyIOUART_RX_P->DATA & (1<MIS&(1<IE &= ~(1<IC |= (1<TCR = 1;
			 LPC_TMR32B1->IR = 1;
			
			 //
			 //LPC_GPIO2->IC=(1<<7);//clear interrupt flag
			 //LPC_GPIO2->IE |= (1<<7);//enable interrupt
			 //NVIC_EnableIRQ(EINT2_IRQn);
			 //
	 }
	 //
   LPC_GPIO3->DATA &= ~(1 << 3);//L	
}


//32位定时器  CT32B0/1  【注:32位定时器和16位定时器功能一样,仅仅将16改成32,并修改一下时钟使能位(C32B0=9;C32B1=10),即可】
void Timer2Init(void)//CT32B0
{
  LPC_SYSCON->SYSAHBCLKCTRL	|= (1<<9); //enable ct32b1 clk 
  LPC_TMR32B0->CTCR &= ~(3);//timer[function sel]
	LPC_TMR32B0->MCR = 3;//enable interrupt and reset autoself
	//-------------------------------------------------------------
	LPC_TMR32B0->PR = 0;//16bits[max=2^16=65536]  48MHZ(sysahbclk)/48=1000KHZ
	//IOuart要支持高的波特率,PR值要设置尽量小,然后不断调试MR0的值即可达要想要的波特率,一开始
	//调试不成功就是因为PR设为了100太大,导致9600接收总是失败
	//-------------------------------------------------------------
	//One timer can gennerate four interrupts for MR0、MR1、MR2、MR3.
	LPC_TMR32B0->MR0 = SET_UART_BAUD;
	//--------------------------------------------------------------
	LPC_TMR32B0->TCR = 2;//reset 
	LPC_TMR32B0->IR =1;//clear interrupt flag
	LPC_TMR32B0->TCR = 1;//load cfg of ct16b1
	//
	LPC_TMR32B0->TCR =2 ;//reset
	LPC_TMR32B0->TCR =0 ;//stop counter
	 
	//
	NVIC_EnableIRQ(TIMER_32_0_IRQn);	
    NVIC_SetPriority(TIMER_32_0_IRQn,1);	
	
}

void Timer3Init(void)//CT32B1
{
	LPC_SYSCON->SYSAHBCLKCTRL	|= (1<<10); //enable ct32b1 clk 
	LPC_TMR32B1->CTCR &= ~(3);//timer[function sel]
	LPC_TMR32B1->MCR = 3;//enable interrupt and reset autoself
	//-------------------------------------------------------------
	LPC_TMR32B1->PR = 0;//10;//16bits[max=2^16=65536]  48MHZ(sysahbclk)/48=1000KHZ
	//IOuart要支持高的波特率,PR值要设置尽量小,然后不断调试MR0的值即可达要想要的波特率,一开始
	//调试不成功就是因为PR设为了100太大,导致9600接收总是失败
	//-------------------------------------------------------------
	//One timer can gennerate four interrupts for MR0、MR1、MR2、MR3.
	LPC_TMR32B1->MR0 = SET_UART_BAUD;
	//--------------------------------------------------------------
	LPC_TMR32B1->TCR = 2;//reset 
	LPC_TMR32B1->IR =1;//clear interrupt flag
	LPC_TMR32B1->TCR = 1;//load cfg of ct16b1
	//
	LPC_TMR32B1->TCR =2 ;//reset
	LPC_TMR32B1->TCR =0 ;//stop counter
	//
	NVIC_EnableIRQ(TIMER_32_1_IRQn);	
	NVIC_SetPriority(TIMER_32_1_IRQn,1);	
}

//
void TIMER32_0_IRQHandler (void)//For send destination.
{
   
    static unsigned char  tx_k=0;
    //
    unsigned char tx_s2;
	 
    //
    LPC_GPIO0->DATA &= ~(1 << 3);//L LED
    LPC_TMR32B0->IR =1;//clear interrupt flag

  
    //TX
		if(l_send_st){
				if(l_send_cnt)
				{
						if(l_send_cnt == 9)//start bit
						{
								if(l_channel==0)
								{
										MyIOUART_TX_P->DATA &= ~(1 << MyIOUART_TX_P_INDEX);//L
								}
								else if(l_channel==1)
								{ }
								else if(l_channel==2)
								{ }
								l_send_cnt=8;
						}
						else
						{
								switch(l_send_cnt)
								{
											case 8:
													tx_s2=0x01;
													break;
											case 7:
													tx_s2=0x02;
													break;
											case 6:
													tx_s2=0x04;
													break;
											case 5:
													tx_s2=0x08;
													break;
											case 4:
													tx_s2=0x10;
													break;
											case 3:
													tx_s2=0x20;
													break;
											case 2:
													tx_s2=0x40;
													break;
											case 1:
													tx_s2=0x80;tx_k=0;
													break;
											default:
													tx_s2=0x0;
													break;
								}
								//-------------------------------------------------------------
								if(l_channel==0)
								{//TX
										if(!(l_send_byte&tx_s2)) MyIOUART_TX_P->DATA &= ~(1 << MyIOUART_TX_P_INDEX);//L
										else MyIOUART_TX_P->DATA |= (1 << MyIOUART_TX_P_INDEX);//H
								}
								else if(l_channel==1)
								{ }
								else if(l_channel==2)
								{ }
								//
								l_send_cnt--;
						}
				}
				else
				{
						if(tx_k<=(MyUart_STOPBITS-1)) //1->两位停止位  0->1位停止位
						{
								if(l_channel==0)
								{   MyIOUART_TX_P->DATA |= (1 << MyIOUART_TX_P_INDEX);//H
								}
								else if(l_channel==1)
								{ }
								else if(l_channel==2)
								{ }
								//
								tx_k++;
						}
						else
						{
							tx_k=0;l_send_cnt  = 9;
							if(l_send_style)//如果是FIFO发送
							{
									l_send_style=0;
									//一个字节发送完
									l_Txbuffer[6]--;
									if(l_Txbuffer[0]TCR =2;//[0-stop counter  1-start  2-reset]
										LPC_TMR32B0->IR = 1;		  
									
									}
							}
							else
							{
								l_send_st=0; //reset	
								//reset timer	
								LPC_TMR32B0->TCR =2;//[0-stop counter  1-start  2-reset]
								LPC_TMR32B0->IR = 1;		  
							}
						}
				}
    }

		//--------------------------------
    LPC_GPIO0->DATA |= (1 << 3);//LED
}






//
void TIMER32_1_IRQHandler (void)//For Receive destination.
{
    static unsigned char  rx_k=0;
	  static char  stc_recv_check=0;//静态变量
		static char  rx_interrupt_flag=0;
	  //
    unsigned char rx_s1;
    //
    LPC_GPIO0->DATA &= ~(1 << 3);//L LED
    LPC_TMR32B1->IR =1;//clear interrupt flag
   	///////
		//RX
    if(l_recv_st)
    {
        if(l_recv_cnt)
				{
						switch(l_recv_cnt)
						{
										case 8:
														rx_s1=0;l_recv_byte=0;
												break;
										case 7:
														rx_s1=1;
												break;
										case 6:
														rx_s1=2;
												break;
										case 5:
														rx_s1=3;
												break;
										case 4:
														rx_s1=4;
												break;
										case 3:
														rx_s1=5;
												break;
										case 2:
														rx_s1=6;
												break;
										case 1:
														rx_s1=7;rx_k=0;stc_recv_check=0;
												break;
										default:
														rx_s1=0;
										 break;
						}
						//-------------------------------------------------------------
						if(l_channel==0)
						{//Rx
							if(MyIOUART_RX_P->DATA & (1<两位停止位  0->1位停止位
						{
								if(l_channel==0)
								{//Rx 
										if(MyIOUART_RX_P->DATA & (1<DATA & (1<IC |= (1<IE |= (1<TCR = 2;//[0-stop counter  1-start  2-reset]
									  LPC_TMR32B1->IR = 1;

								}
						}
			  }
    }
		//--------------------------------
    LPC_GPIO0->DATA |= (1 << 3);//LED
		//	
		#if ENABLE_ECHO
    if(rx_interrupt_flag)
    {
			rx_interrupt_flag = 0;//
			recv_interrupt();//此函數必須立即返回!(用于回显测试)   
    }
	  #endif
}

/*
*/

/*USE EXAMPLE:
*/


V1.6:单定时器方案

1、DrvIOUART.h+DrvIOUART.c+DrvIOUART1.h+DrvIOUART1.c;蓝色文件为库文件,绿色文件为实例化参考

2、资源占用:1个输入中断+1个定时器(针对实时要求不高的场合)

      文件详细见下载,末尾。

使用小技巧:

软件中,对.h文件的第一行添加"//<<< Use Configuration Wizard in Context Menu >>>",可使用其自带的配置功能,方便参数灵活设置。

GPIO模拟UART串口时序_第2张图片

附录:

1、IOUART模拟串口调试记录

  2016/10/20

1、波特率可调节:支持收发波特率1200->57600

[注: r字符进行回显测试,9600才能稳定不出错]

GPIO模拟UART串口时序_第3张图片

  

19200(停止位为1)时:(单按时回显正常,按住不放回显会出错)

GPIO模拟UART串口时序_第4张图片

停止位为2时,情况一样。

结论:ZLG的模拟串口可以支持到57600,但连续接收数据过快时,baud>9600易出错。

2、两路模拟串口实现

两路模拟串口使用注意事项:

1、两个模拟串口分布在不同PORT LINE,即一个用PORT2,一个用PORT3,不能用同一个PORT

2、各自使用自己的定时器。

3、每一个函数都要能立即退出,不要使其出现卡死现象。将每个函数想象成在一进就出、各不干扰、独立运行的状态执行。

4、发送过程中产生接收,则以接收的优先级为高。

   增加发送自动退让,发送过程随时可能被接收中断,这样会导致发送的数据出现错误但接收的数据都是正常的。BUG.......................

   周立功串口写的很好在只使用一个定时器的情况下,做到了无一错发,无一漏收,可连续收发,波特率稳定可调,这才是正真的高手!!!

   增加接收数据计数,发送数据计数!

   增加当接收过程终止发送时拉高TX引脚操作!

   用串口调试助手测线序数据连续发送且能正常回显的最小时间,目前是300ms

   测试串:12 34 56 78 90 1A 3E 5B 12 34 56 78 90 1A 3E 5B

GPIO模拟UART串口时序_第5张图片

  

GPIO模拟UART串口时序_第6张图片

  

5、对于连续接收情况的处理

   根据精确的停止位的后一位电平高低判断是否为连续接收的情况

   使用停止位判断,只能选择一位(k=0)或两位停止位(K=1

   没有校验位

   要求连续收发10个字节以上,不行的话调节BAUD系数

6、一旦错过了起始位是否会一直出错!

myiouart:

特点

1、支持一个定时器多通道引脚分时复用,通过channel选择。

     配置一个串口支持多个引脚发送,或接收通过channel变量选择哪一对引脚通道。

GPIO模拟UART串口时序_第7张图片

3、测试模拟串口性能方法:

使用回显测试:

在secureCRT中: 按住两个按键不放看是否能正常显示,或同时按住多个看是否有乱码现象。当同时按住两个字符按键不放时,正常程序每次返回两个按下的字符,此时串口工作在连发状态下。

在串口调试助手中:连续发送多个字节看返回值是否正确。

4、Myiouart_Lpc11c14_V1程序DEBUG记录:

V1.0 -- 只支持发送,接收波特率一高就有问题,4800接收

V1.1 -- 修改了收发(只是单字节收发),跟换了32位定时器1,并将驱动单独列在一个文件中。

V1.2 -- 增加了BUFFER功能

V1.3 -- 增加了连续收发,但发送会被接收终止,导致发送的数据错误

V1.4 --

V1.5 -- 改为双定器模拟串口

V1.6 -- 单定时器串口升级至V1.6使用了结构体操作方式

参考:

IO 模拟 UART 实现-ZLG

链接:https://pan.baidu.com/s/1OgQshNoEe5oI0_g5cQPXHg 密码:0svc

下载:

V1.5

链接:https://pan.baidu.com/s/11pwEpICOpuX6S5OYy6xUtg 密码:f5l9

V1.6

链接:https://pan.baidu.com/s/1UimbCwUY3uINvajBQ5d8HQ 密码:m044

你可能感兴趣的:(C/C++,ARM)