一、背景介绍
一般单片机之间通信由Mcu的硬件模块完成,一般有:Spi、IIC、Uart、Can等,但也会遇到通信接口全部用完,则可以使用IO来模拟做通信协议。以下例程中,主机使用3个IO,从机也使用3个IO,实现主机发数据给从机的目的。(因没有使用到从机给主机发数据,所以没有设计)
二、使用过程设计
在比较熟悉Iic、Spi等通信协议,了解时钟、数据线等之后,便可以尝试使用IO来模拟实现通信协议。
在此应用中,主机使用3个普通Io,从机使用3个普通Io。分别用作:片选CS,时钟线SCL,数据线SDA。CS线从机控制,SCL和SDA线主机控制。时序图如下所示
由于从机比较空闲(此实际应用中从机在2Ms内有80%时间处于空闲),所以在从机可以接收数据时,把CS线拉高,此时主机在想发数据时查询CS线是否处于高电平,处于高电平时,通过控制SCL和SDA线发送串行16Bit数据。
三、主机程序设计思路
主机通过查询CS线到拉高时,则通过控制SDA和SCL发送数据。
/*调用此函数,下发2个8Bit(16Bit)数据*/
void ArrayShowDriveProcess(UINT8 data1, UINT8 data2)
{
UINT8 i;
UINT16 tempTransferData;
UINT32 tempData;
tempData = GetMs(0);
while((GpioInDataGet(COM_PORT)&COM_CS_PIN) == 0)
{//等待CS线拉高
if((GetMs(0) - tempData) > 3)
{//OverTime, return back. //3Ms内还没等到CS线拉高,发送失败,退出发送
return;
}
}
{//SDA High,开始传输,大约需要75Us传输完成16Bit。Clk=200KHz
GpioOutDataClrBits(COM_PORT,COM_SCL_PIN); //拉低SCL
tempTransferData = ((data2<<8)|data1);
for(i = 0 ; i < 16 ; i++ ) //循环16次,发送16Bit数据
{
GpioOutDataClrBits(COM_PORT,COM_SCL_PIN);
if(tempTransferData&BIT15)
GpioOutDataSetBits(COM_PORT,COM_SDA_PIN);//拉SDA = 1
else
GpioOutDataClrBits(COM_PORT,COM_SDA_PIN);//拉SDA = 0
tempTransferData <<= 1;
__ASM
{
/*1*//*等待一定时间,此处用Nop方便控制时钟频率*/
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
}
GpioOutDataSetBits(COM_PORT,COM_SCL_PIN); //拉高CS
__ASM
{
/*1*//*等待一定时间,此处用Nop方便控制时钟频率*/
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
}
}
}
return; //发送完成,退出
}
四、从机程序设计
从机在可接收数据时,拉高CS线后通过查询SCL和接收SDA发来的数据。
UINT32 tempData;
UINT32 tempData2;
UINT8 i,ioRec;
tempData2 = GetMs(0);
GpioOutDataSetBits(COM_PORT, COM_CS_PIN); //拉高CS,从机可接收数据
while((GetMs(0) - tempData2) < 2) //在2Ms内查询是否需要接收数据
{
if( (GpioInDataGet(COM_PORT)&COM_SCL_PIN) == 0) //判断CS线
{//查询到时钟线下降,开始传输
ioRec = 0;
tempData = GetMs(0);
for(i = 0; i < 16;) //循环查收16Bit数据
{
if((ioRec == 0)&&(GpioInDataGet(COM_PORT)&COM_SCL_PIN))
{//Low To High //SCL电平从低变高
ioRec = 1;
recData <<= 1;
if((GpioInDataGet(COM_PORT)&COM_SDA_PIN))
{
recData |= BIT0; //SDA电平高,接收1
}
else// if(GpioInDataGet(COM_PORT)&COM_SDA_PIN)
{
recData &= (~BIT0); //SDA电平低,接收0
}
i++;
}
else if((ioRec == 1)&&((GpioInDataGet(COM_PORT)&COM_SCL_PIN) == 0))
{//High to Low //SCL电平从高变低
ioRec = 0;
}
else
{//Judge Over Time
if((GetMs(0) - tempData) > 2) //2Ms Over Time.
{ //判断接收是否超时。超过2Ms,拉高CS退出
GpioOutDataClrBits(COM_PORT, COM_CS_PIN);
recData = 0xFF;
break;
}
}
}
GpioOutDataClrBits(COM_PORT, COM_CS_PIN); //接收完成,拉高CS线
}
GpioOutDataClrBits(COM_PORT, COM_CS_PIN); //等待接收时间完成,退出后拉高拉高CS线
五、总结
一般做模拟主机发送比较简单,只需在发送时控制信号线和时钟线,按照速率发送数据即可。
做从机的接收都比较复杂,而且对从机的响应速度有要求(如果从机中断程序较多,则会影响响应速度),需要从机比较空闲才能做接收。另外,一般做从机的模拟接收也可通过IO的中断,来达到及时响应的目的。
在此应用中,因为从机固定在2Ms内有80%的时间可拉高接收数据,而且从机程序架构比较简单,也没有中断服务程序,才可以这样做。以后遇到问题具体问题再具体分析。