欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一种实现微观单线程,宏观上多线程的方法

程序员文章站 2022-05-18 11:17:03
...

1.

纵所周知的是,C语言是顺序程序设计的,那么在一些MCU中,例如STM32,Atmega168等等,在微观上程序都是单线程的,那么应该如何实现微观上的多线程呢?这个用到两个东西:一是中断,二是switch语句。听老夫为你细细道来。

 

2.

举个例子来说,比如我想要实现的是:MCU每2秒通过6个USART向外发送数据。一般大家首先想到的是,配置一个定时器,每2S进入一个中断函数,然后中断函数里面写入:

USART0();	//启动串口0发送
USART1();	//启动串口1发送
USART2();	//启动串口2发送
USART3();	//启动串口3发送
USART4();	//启动串口4发送
USART5();	//启动串口5发送

但是这样会存在一个问题呀。首先我可以把启动串口发送到发送数据成功分为几个部分:

1. 拉高串口读写控制端I/O,使其变为发送模式; 2. 准备要发送的数据,对数据进行组包,这一步是很耗时间的; 3. 检测串口数据是否发送完成 ; 4.在串口发送完成的时候,拉低串口读写控制端I/O,使其变为接收模式  

仅仅在串口发送的就会有两个while(1),如下图所示,这无疑拖慢了CPU的运行速度,那么应该如何优化呢?

  //--------------------启动发送-----------------------------//
	UART0_TX;                             //使串口处于发送状态
	i=0;
	while(i<30)                           //一共发送30个字节
	{
		while(!(UCSR0A & (1<<UDRE0)));    //等待 “准备数据接收”
		UDR0 = *(TX_buf_MCU+(i++));       //开始发送数据
		while( !( UCSR0A & (1<<TXC0)) );  //等待“发送完成”
		UCSR0A |= (1<<TXC0);              //发送完成后清除中断标志位
	}
	UART0_RC; //发送完毕,再次使串口处于接收状态

一种实现微观单线程,宏观上多线程的方法

 

 

3.

先贴出代码,再详细说出

void PollingSending()
{
	for (uint8_t i = 0;i < 7;i ++)              //6个通道轮询发送,故循环6次
	{ 
		switch(ComState[i])                     //ComState[i]表示第i个串口所处的状态
		{
			case 0:                 
				if (PollingSending_Flag == 1)   //当2S到,PollingSending_Flag置1
				{
					ComState[i] = 1;             //跳到状态1
				}
			break;
			case 1:
				PollingSending_Flag = 0;         //清除2S标志位
				prepareData(i);		             //准备待发送数据包
				RS485_out(i);                    //拉高IO口,准备发数据
				ComState[i] = 2;
			break;
			case 2:
				USARTSendStart(i);			   	//第i个串口开始发送,此时会进入串口发送函数
				ComState[i] = 3;	
			break;
			case 3:
				if (TxComplete[i] == 1)          //发送完成
				{
					RS485_in(i);                 //拉低I0口,变为待接收状态
					ComState[i] = 0;
				}
			break;	
		}
	}	
}


ISR(USART0_TXC_vect)     //串口0的中断发送函数
{	

	if (TxLen[i] < 6)     //发送6个字节
	{
	  USART0_UDR0 =  txbuf[TxLen]; //发送一个字节后会进入发送中断,进而发送另外一个字节
	  TxLen[chn]++;
    }
	else
	{
		TxLen[i] = 0;
		TxComplete[i] = 1;	//发送完毕
	}
}




int main(void)
{

	sysinit();	//初始化
	while(1)
	{
		wdt_reset();  //复位看门狗
		PollingSending();			
	}
}

此程序的优点所在是:每一个串口都有4种状态,比如 

 USART0:ComState[0]=0  准备发送数据

 USART1:ComState[1]=3  发送完成状态

 USART2:ComState[2]=2  正在发送状态

即单线程系统每次走一步,但这一步可能USART0再走,也有可能USART1拿去了。且之前也提过了,是中断发送,系统有可能处在USART1配置发送数据包的时候,这时 USART0的UCSR0A & (1<<UDRE0) == 1 了;    //等待 “准备数据接收”  准备好了,那么系统就去发送USART0的数据了,关键在于,若下一时刻( UCSR0A & (1<<TXC0)) ;  //等待“发送完成”  还没发送完,则系统可以不用一直在while( !( UCSR0A & (1<<TXC0)) );  //等待“发送完成”,系统可以跳出中断函数,走主程序,即继续配置USART1的数据包,这样就实现了微观上单线程,而宏观上多线程的目的。