单片机之间用普通IO做模拟通信设计(类似于IIC的主机和从机)

一、背景介绍

一般单片机之间通信由Mcu的硬件模块完成,一般有:Spi、IIC、Uart、Can等,但也会遇到通信接口全部用完,则可以使用IO来模拟做通信协议。以下例程中,主机使用3个IO,从机也使用3个IO,实现主机发数据给从机的目的。(因没有使用到从机给主机发数据,所以没有设计)

二、使用过程设计

在比较熟悉Iic、Spi等通信协议,了解时钟、数据线等之后,便可以尝试使用IO来模拟实现通信协议。

在此应用中,主机使用3个普通Io,从机使用3个普通Io。分别用作:片选CS,时钟线SCL,数据线SDACS线从机控制,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%的时间可拉高接收数据,而且从机程序架构比较简单,也没有中断服务程序,才可以这样做。以后遇到问题具体问题再具体分析。

本人新建了个QQ群,如想进一步沟通可添加:947187213。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个示例代码,用于两个STM32单片之间IIC通信。在该例程中,一个STM32作为从设备(Slave)发送数据给另一个STM32作为主设备(Master)进行接收。 从设备发送数据给主设备: ```c #include "stm32f4xx.h" #include "stm32f4xx_i2c.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #define I2C_ADDRESS 0x08 // 主设备地址 I2C_InitTypeDef I2C_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; void I2C_Configuration(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1); I2C_DeInit(I2C1); I2C_InitStruct.I2C_ClockSpeed = 100000; I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x07; // 从设备地址 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); } void I2C_SendData(uint8_t* data, uint8_t dataLength) { while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); for (uint8_t i = 0; i < dataLength; i++) { I2C_SendData(I2C1, data[i]); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } I2C_GenerateSTOP(I2C1, ENABLE); } int main(void) { uint8_t data[] = "Hello"; I2C_Configuration(); while (1) { I2C_SendData(data, sizeof(data)); delay(500); } } ``` 主设备接收从设备发送的数据: ```c #include "stm32f4xx.h" #include "stm32f4xx_i2c.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #include "stm32f4xx_usart.h" #define I2C_ADDRESS 0x07 // 从设备地址 I2C_InitTypeDef I2C_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; void I2C_Configuration(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1); I2C_DeInit(I2C1); I2C_InitStruct.I2C_ClockSpeed = 100000; I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x08; // 主设备地址 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); } void USART_Configuration(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); USART_DeInit(USART1); USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStruct); USART_Cmd(USART1, ENABLE); } void I2C_ReceiveData(void) { uint8_t receivedData[10]; uint8_t dataLength = 0; while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_AcknowledgeConfig(I2C1, ENABLE); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); while (dataLength < sizeof(receivedData)) { if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) { receivedData[dataLength++] = I2C_ReceiveData(I2C1); } } I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); for (uint8_t i = 0; i < dataLength; i++) { while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)); USART_SendData(USART1, receivedData[i]); } } int main(void) { I2C_Configuration(); USART_Configuration(); while (1) { I2C_ReceiveData(); delay(500); } } ``` 在从设备中,我们使用`I2C_SendData()`函数发送数据给主设备。首先,我们等待IIC总线空闲,然后生成START信号,接着发送主设备地址和写入位,然后通过循环发送数据字节。最后,我们生成STOP信号结束传输。 在主设备中,我们使用`I2C_ReceiveData()`函数接收从设备发送的数据。首先,我们等待IIC总线空闲,然后生成START信号,接着发送从设备地址和读取位。然后,我们循环接收数据字节,并通过USART发送到串口进行打印。 请注意:以上示例代码是基于STM32F4系列的,使用了STM32标准外设库(Standard Peripherals Library)。如果你使用其他型号的STM32单片机或者使用了不同的开发库,可能需要进行相应的调整。 希望这个例程对你有所帮助!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值