关于stm32通信协议:软件模拟SPI、软件模拟I2C的总结(fishing_8)

趁着帮老师代上嵌入式实验课的机会,又重新熟悉了一遍stm32的通信协议:串口协议、SPI协议、I2C协议、RS485协议。大概半年前,是过了一遍的,但也只停留于读了遍代码,跑了下例程,最近又过了一遍(自己仔细的看了一遍,老师还给我们讲了一遍,自己又讲了一遍),然后还写了一遍软件模拟SPI、软件模拟I2C的代码,才彻底的懂了个皮毛 ,:)。稍微总结下吧,总结的不好,都是自己的理解,仅供参考主要说软件模拟SPI、I2C,硬件SPI和硬件I2C就不说了。

串口协议

串口协议没什么可说的,现在常用的串口协议,是基于以前的RS232的协议,因为RS232的引脚太多而改进过来的。
物理层只用三根引脚:TXD、RXD、GND(最好接,不然有点影响),然后TXD接RXD、RXD接TXD、GND接GND(这里我第一次接错过的,所以写出来),通过有两个收发引脚就能看出,串口协议是支持全双工的;
协议层的话,就是起始信号(1个0表示)+数据包(5、6、7、8位可选)+校验位(无、0、1、奇、偶可选)+停止信号(0.5、1、1.5、2个1表示)。
串口协议,很常见,多用于打印调试信息,也比较简单;

RS485协议

RS485协议,协议层未改,只是在串口协议的物理层做了修改,外接了一个物理收发器,然后在通信双方的两条A、B线加了电阻,通过测A、B线的电压差来传送高低电平信号,所以485通信协议的特点就是抗干扰性强、传输距离远,可以组网。由于是通过A线、B线的电压差传输高低电平信号,所以是半双工的,同一时刻只能发送或者接收。物理连接方式:A线接A线,B线接B线。

如何理解半双工、全双工,我看到网上一个很好的例子可以帮助理解。就是说,把半双工通信比作是对讲机通信,全双工就是手机通信,对讲机同一时刻只能说或者是听,而手机是可以同时说、听的。

前面两个协议比较简单,而SPI协议、I2C协议稍微麻烦点,主要说一下。

SPI协议

SPI协议,多应用于ADC、DA、LCD等设备与MCU间,要求通信速率较高的场合。一般需要4根信号线,分别是MOSI、MISO、SCK、CS线。但是!敲黑板!有可能可以只用3根,当你通过SPI和DA设备通信的时候,MISO线就可以不要了,然后我们老师更夸张,说如果你只连一个DA设备的话,那么CS线也可以不要,这里我有点不敢苟同,因为毕竟片选线是控制通讯开始结束的,但这种思想是可取的了,规则是死的,人是活的,要活学活用,这里当时听到老师讲这点的时候还是有点震撼的。扯的有点远了。。。但其实,这里要说的软件模拟。

还是回到正题,接着说软件模拟SPI,当MCU的SPI外设不够用的时候,我们就会用GPIO去模拟SPI的方式,去和支持SPI的从设备通信。下面直接贴代码了,代码已经调通的了。我做的实验是用F429IGT6通过软件模拟SPI读写一款W25Q128的FLASH芯片,模拟的是模式3(CPOL=1,CPHA=1),有两点原因:①模式3的SCK空闲电平为高电平,由高电平向低电平翻转快,而且容易;②模式3在偶数边沿采样,防止第一个信号没采到。

首先,是GPIO的初始化,CS引脚、MOSI引脚、MISO引脚、SCK引脚。除了MISO引脚配成输入模式,其余三个引脚都配成输出模式(推挽输出)。

void SPI_FLASH_Init(void)
{
/*定义一个GPIO_InitTypeDef类型的(基本IO)结构体*/
  GPIO_InitTypeDef GPIO_InitStructure;
  
  /***** 使能 GPIO 时钟 *****/
	
 /* 使能 FLASH_SPI引脚的GPIO时钟< SPI_CS; SPI_MOSI; SPI_MISO; SPI_SCK > ( F口 )*/
  RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOF, ENABLE);

/* < 配置 SPI_FLASH_SPI 引脚: SCK; MISO; MOSI > */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7  | GPIO_Pin_9;	
 /* 设置引脚模式为 SPI_5 复用功能*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; 
/* 设置引脚速率为50MHz */   
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
  /* 设置引脚的输出类型为推挽输出*/
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  /* 设置引脚为无上拉 下拉模式*/
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;  
	
  /* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO_F*/
GPIO_Init(GPIOF, &GPIO_InitStructure);
  
/* < 配置 SPI_FLASH_SPI 引脚: CS > */
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(GPIOF, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;	
GPIO_Init(GPIOF, &GPIO_InitStructure);
	
GPIOF->BSRRL = GPIO_Pin_6;//拉高CS线
GPIO_SetBits(GPIOF, GPIO_Pin_7);//拉高时钟线,模拟模式3

}

接下来,就是基本的发送、接收函数了。延时是用Symtick来延时,为了延时精确,符合W25Q128芯片数据手册的时序,理论上应该也可以用软件延时(未尝试)。

//软件模拟SPI写(发送)
void Soft_SPI_Write(uint8_t a)
{
	uint8_t cnt;
	
	for(cnt=0;cnt<8;cnt++)
	{
		GPIO_ResetBits(GPIOF, GPIO_Pin_7);//拉低CLK
		Delay_us(10);//这个延时时间任意,但要大于芯片数据手册上的(纳秒级的)
		if(a &0x80)
		{
			GPIO_SetBits(GPIOF, GPIO_Pin_9);
		}
		else
		{
			GPIO_ResetBits(GPIOF, GPIO_Pin_9);
		}
		a <<= 1;
		Delay_us(10);
		GPIO_SetBits(GPIOF, GPIO_Pin_7);//拉高CLK
		Delay_us(10);
	}
}
//软件模拟SPI读(接收)
uint8_t Soft_SPI_Read(void)
{
	uint8_t cnt;
	uint8_t Rxdata = 0;
	
	for(cnt=0;cnt<8;cnt++)
	{
		GPIO_ResetBits(GPIOF, GPIO_Pin_7);//拉低CLK
		Delay_us(10);
		Rxdata <<= 1;
		if(GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_8))
		{
			Rxdata |= 0x01;
		}
		GPIO_SetBits(GPIOF, GPIO_Pin_7);//拉高CLK
		Delay_us(10);
	}
	return Rxdata;
}

然后是,W25Q128芯片的等待准备好的函数。通过读取该芯片的BUSY位是否为1(繁忙)实现,这款FLAH 是这样的,你要根据你要执行的操作找到要发送的指令是哪个,以及对应的时序,按照它的时序来,注意时序线上的时间

void Soft_WaitFlahToBeReady(void)
{
	u8 FLASH_Status = 0x01;
	
	GPIOF->BSRRH = GPIO_Pin_6;  //**CS低**	
	
	Soft_SPI_Write(0x05);
	
	do
	{
		Soft_SPI_Write(0xFF);
		FLASH_Status = Soft_SPI_Read();
	}
	while((FLASH_Status & 0x01) == 1);

	printf("\r\n 繁忙状态位为: 0x%X\r\n", FLASH_Status);	
	GPIOF->BSRRL = GPIO_Pin_6;  //**CS高**

}

再是,FLASH 的扇区擦除函数了,所有的FLASH每次写入前都要进行擦除。

void Soft_Flash_SectorErase(u32 EraseAddr)
{
	GPIOF->BSRRH = GPIO_Pin_6;  //**CS低**	
	
	Soft_SPI_Write(0x06);//写使能指令
	
	GPIOF->BSRRL = GPIO_Pin_6;  //**CS高**

	GPIOF->BSRRH = GPIO_Pin_6;  //**CS低**	
	
	Soft_SPI_Write(0x20);//扇区擦除指令
	
	Soft_SPI_Write((EraseAddr & 0xFF0000) >> 16);
	Soft_SPI_Write((EraseAddr & 0xFF00) >> 8);
	Soft_SPI_Write((EraseAddr & 0xFF));
	
	GPIOF->BSRRL = GPIO_Pin_6;  //**CS高**
	
	Soft_WaitFlahToBeReady();
}

再就是,页写入函数了,这款FLASH芯片支持三种写入方式,单字节写入、页写入(<256Bytes)、多字节写入(基于页写入)。显然,页写入方式比单字节写入快,这里我只做了页写入的方式,用于验证是否成功,多字节写入的方式可以在此方式上拓展,秉火的例程上有,可以参考。

void Soft_SPIFlashPageWrite(u8* Pbuffer,u32 Writeaddr,u16 NumberByteToWrite)
{
	GPIOF->BSRRH = GPIO_Pin_6;  //**CS低**	
	
	Soft_SPI_Write(0x06);//写使能指令
	
	GPIOF->BSRRL = GPIO_Pin_6;  //**CS高**

	GPIOF->BSRRH = GPIO_Pin_6;  //**CS低**	
	Soft_SPI_Write(0x02);//写页写入指令

	Soft_SPI_Write((Writeaddr & 0xFF0000) >> 16);
	Soft_SPI_Write((Writeaddr & 0xFF00) >> 8);
	Soft_SPI_Write((Writeaddr & 0xFF));	
	
	if(NumberByteToWrite > 256)
	{
		NumberByteToWrite = 256;
		printf("写入的字节数大于256");
	}
	while(NumberByteToWrite--)
	{
		Soft_SPI_Write(*Pbuffer);
		Pbuffer++;
	}
	GPIOF->BSRRL = GPIO_Pin_6;  //**CS高**
	
	Soft_WaitFlahToBeReady();
}

最后就是读FLASH函数了,该芯片支持多字节一直读。

void Soft_SPIFlashRead(u8* Pbuffer,u32 ReadAddr,u16 NumberByteToRead)
{

	GPIOF->BSRRH = GPIO_Pin_6;  //**CS低**
	Delay_us(10);
	
	Soft_SPI_Write(0x03);//写读使能指令

	Soft_SPI_Write((ReadAddr & 0xFF0000) >> 16);
	Soft_SPI_Write((ReadAddr & 0xFF00) >> 8);
	Soft_SPI_Write((ReadAddr & 0xFF));	
	
	while(NumberByteToRead--)
	{
		//Soft_SPI_Write(0xFF);//这里一开始是加了的,因为SPI是全双工的,
		*Pbuffer = Soft_SPI_Read();//但是加了,读就有问题,然后仔细看了时序图,发现其实不加也可以
		Pbuffer++;
	}
	
	GPIOF->BSRRL = GPIO_Pin_6;  //**CS高**
}


参考资料来源:
秉火的《零死角玩转stm32——F429》以及例程
网上代码

  • 21
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
STMicroelectronics(ST)是一家微控制器和半导体解决方案的供应商,其产品STM32是一种功能强大的微控制器系列。U8g2是一款开源的用于驱动显示屏的库,它提供了对许多不同显示屏的支持。在STM32上移植U8g2并使用软件模拟SPI的方法如下: 首先,在STM32的开发环境中创建一个新的项目,并包含U8g2库的文件。确保安装了适用于STM32的C编译器和开发工具。 然后,阅读U8g2库的文档,了解如何在STM32上使用软件模拟SPI进行通信。通常,这涉及到定义和配置GPIO引脚,以及模拟SPI时钟、数据线和片选线等。 接下来,使用STM32的GPIO库函数进行引脚的初始化和配置。根据U8g2库的文档,将相关的GPIO引脚配置为软件模拟SPI所需的输入和输出。 然后,根据U8g2库的文档,编写适配代码来实现软件模拟SPI通信。这涉及到模拟SPI时钟、数据线和片选线的读写操作。可以使用STM32的GPIO库函数来控制引脚的电平。 最后,根据项目的需求,使用U8g2库的函数来初始化和控制显示屏。这些函数通常包括设置显示屏的尺寸、清除屏幕内容、绘制图形和显示文本等。 在完成以上步骤后,可以使用STM32的开发工具编译和烧录代码到目标设备上,然后运行程序。如果一切顺利,显示屏应该能够正常工作,并显示预期的内容。 需要注意的是,软件模拟SPI可能会导致通信的速度较慢,因为它依赖于处理器的计算能力。如果需要更高的通信速度,可以考虑使用硬件SPI接口。 总之,将U8g2库移植到STM32并使用软件模拟SPI进行通信需要对STM32的GPIO库函数和U8g2库的文档有一定的了解。同时,根据具体的项目需求进行适配和调试,以确保显示屏能够正确工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值