基于STM32系列的模拟串口(非阻塞式)

    STM32单片机一般少则3个串口,多则5个,而我这次的项目还偏偏5个硬件串口还是不够用.

至于不够用的原因,哎,是项目做到后面有定制,随便哪个串口都省不得,没得办法,只能另想法子咯~

    板子上有几个预留IO口,可以用来模拟串口. 模拟串口一般都选9600,速度最快试了也才19200,

所以限制还是较多的,一般不得以情况下才会用到.

    在写程序之前我也是参考了前辈们,觉得写的不咋地,照抄他的代码,那我的系统啥事情也

不要干了,就在那里delay吧!!! 模拟串口分收和发:收比较难,发送比较容易,那就先将接收这块吧。

    接收:

        有2种思路: 一种是第一个下降沿开始启动定时器,每个bit都去采样Rx电平;

                           从第一个边沿开始统计时间戳,接收完10个bit后再解析.

        我这边碰巧选择的是第二种方法,具体实现思路是:

        ① 启动定时器2用来做背景时间,定时器分频后的计数频率为1MHz,那么对于9600bps

            来说1个bit就是104.16us,我们取整数104就可以了;

        ② 再同时启动一个定时器3,定时时间为104*9.5,这个定时器中断发生的时候表示

            一个字节接收完毕了;

        

我们以0x37串口通讯时序图为例, ,传输的时候是LSB 1st, Rx引脚选择边沿触发, 第1次边沿到的时候

启动定时器2,同时启动定时器3, 第2个边沿的时候将定时器2的计数值存到数组里,此时是104,第3个

边沿触发的时候将其计数104*5计数值,同理将4,5,6三个边沿对应的TIM2值存到缓存里,最终

是 0,104,416,520,728,936,除以104变成0,1,4,5,7,9;

如何将0,1,4,5,7,9解析成0x37,看似简单的问题其实还是有点麻烦的~ 给个思路,先将0和9这样的

数字去掉,变成1,4,5,7;

然后从1开始数到8,跟数组里元素是否有匹配,如果匹配就将状态取反,没有就维持之前的状态:

    1有,2,3没有,4有,5有,6没有,7有,8没有-->11101100->倒序后就是0x37了;

再来分析几个处理后的值:4,5,7: 1,2,3没有,4有,5有,6没有,7有,8没有-->00010011->逆序后11001000->0xC8

1,2: 1有,2有,3,4,5,6,7,8没有->10000000->逆序后00000001->0x01

1,2,3,4,6,7,8:1有,2有,3有,4有,5没有,6有,7有,8有->10100101->逆序后10100101->0xA5

7,8:1,2,3,4,5,6没有,7有,8有->00000010->逆序后01000000->0x40

总结下如果第一个是1开头的那么得出的bit就是1,如果第一个是其他数字开头的,则 "1到其他数字" 之间用0来填充;

下面我们就根据总结出来的规律编写解析代码:

int process_byte(int nums)
{
	int i;
	u8 a=0;
	u8 byte=0;
	u8 new_array[8];
	memset(new_array,0,sizeof(new_array));
	
	for(i=0;i<=nums;i++) //1...7
	{		
		timerecode[i]+=ONE_BIT_TIME/2;
		timerecode[i]/=ONE_BIT_TIME;		
	}
	
	if(timerecode[nums]>=9)	//计算下标是从1开始的,并且去掉了9,所以剩下的也不多了
		nums--;
	
	if(nums<=0)	//全0特殊处理
		return 0;
	
	find_max=nums; //去掉第一个0
	/*	
	假设收到的是0,2,4,9
	经过处理后就剩下2,4
	将2放到new_array下标为1的地方,4放到下标为3的地方
	*/
	for(i=0;i<find_max;i++)
	{
		find_array[i]=timerecode[i+1];
		if(find_array[i]!=0)
		{
			new_array[find_array[i]-1]=find_array[i];
		}
	}
	
	/*
	然后在for(i=find_array[0]-1;i<8;i++)
	这个循环里找,如果对应的位有边沿改变,就改变a的值
	找不到就复制a的值,这里有点要注意,i循环是从高电平
	开始的
	*/
	for(i=find_array[0]-1;i<8;i++)
	{
		byte>>=1;
		if(i==new_array[i]-1)
			a=!a;		

		if(a)
			byte|=0x80;
	}
	
	return byte;
}

对于上图0x37的例子,当定时器3中断时,就调用process_byte(nums-1),其中nums=6,就

是边沿次数,来解析我们接收到的byte啦.

下面的截图是我用模拟串口编写的收发例子:



收发48个任意字节都正常,没有哪个是解析错的.

其他一些函数都比较简单,建议大家自己动手! 有感兴趣的,我会接着写

模拟中断发送!今天到此为止,希望各位喜欢~


发送部分:这部分完全依赖定时器7了,定时周期104us。

发送的原理很简单,我这里没有使用delay方式来发送,而是在定时器里实现发送的;

#define VIR_TXBUFF_SIZ	128
#define VIR_RXBUFF_SIZ	128

typedef struct {
//---------Rx------------
	u8 rxov;
	u8 rxlen;
	u8 rxbuff_idx;
	u8 rx_decode_flag;
	u8 RxBuff[VIR_RXBUFF_SIZ];//大小一定要是2的次方关系
	u8 RXREG;
//---------Tx------------
	u8 send_flag;
	u8 send_max;
	u8 send_cnt;
	u8 send_mode;	//0-阻塞式发送  1-中断发送(放到sendbuff里,指定send_max即可)
	u8 sendbuff[VIR_TXBUFF_SIZ];
	u8 TXREG;
	
}VIRTUAL_UART_t;
static u8 send_a_byte(u8 dat)
{
	if(VirtualUart.send_flag)
		return 1;
	
	VirtualUart.TXREG=dat;
	TIM7->CR1 |= TIM_CR1_CEN;
	Tx_Pin=0;
	VirtualUart.send_flag=1;
	
	return 0;
}

static void send_remain_byte(void)
{
	if(VirtualUart.send_cnt>=VirtualUart.send_max)
	{
		VirtualUart.send_flag=0;	//发送完毕
	}
	else
	{
		VirtualUart.TXREG=VirtualUart.sendbuff[VirtualUart.send_cnt++];
		Tx_Pin=0; 			//产生START信号
	}
}

//TIM7定时器里调用
static u8 tim_send_byte(void (*Callback)(void))
{
	static int sendidx=0;
	sendidx++;
	
	if(sendidx<=8)					//DATA 1,2,3...8
	{
		Tx_Pin=VirtualUart.TXREG&0x01;
		VirtualUart.TXREG>>=1;	
	} else if(sendidx==9) { 			//STOP
		Tx_Pin=1;
	} 
	else if(sendidx==10) {				//STOP的发送完毕了
		sendidx=0;
				
		if(Callback!=NULL)			//有多个字节要发送,调用回调函数继续发送下一个字节
			Callback();
		else
			VirtualUart.send_flag=0;	//已经发送完毕了
		
		return 0;
	}
	
	return 1;	//1-busy
}

//阻塞式发送
void vu_send_string(u8 *s)
{
	VirtualUart.send_mode=0;
	while(*s)
	{
		while(VirtualUart.send_flag!=0);
		send_a_byte(*s);
		s++;
	}
}
//阻塞式发送
void vu_send_len(u8 *s,int len)
{
	VirtualUart.send_mode=0;
	while(len--)
	{
		while(VirtualUart.send_flag!=0);
		send_a_byte(*s);
		s++;
	}
}
//中断发送
int vu_send_some_byte_noblock(int len)
{
	if(VirtualUart.send_flag)
		return -1;
	if(len>VIR_TXBUFF_SIZ)
		return -2;
	
	VirtualUart.send_mode=1;
	VirtualUart.TXREG=VirtualUart.sendbuff[0];
	TIM7->CR1 |= TIM_CR1_CEN;
	Tx_Pin=0;						//产生START信号
	VirtualUart.send_flag=1;
	VirtualUart.send_max=len;
	VirtualUart.send_cnt=1;
	return 0;
}


//主要发送部分代码就在这儿了
void TIM7_IRQHandler(void)
{
	TIM7->SR = (uint16_t)~TIM_IT_Update;
	
	if(VirtualUart.send_mode) {
		if(tim_send_byte(send_remain_byte)==0) {
			if(VirtualUart.send_flag==0)
				TIM7->CR1 &= ~TIM_CR1_CEN;
			TIM7->CNT=0;
		}
	}else {
		if(tim_send_byte(NULL)==0) {
			TIM7->CR1 &= ~TIM_CR1_CEN;
			TIM7->CNT=0;
		}
	}
}


        

猜你喜欢

转载自blog.csdn.net/yunjie167/article/details/79808464