历史上的今天
返回首页

历史上的今天

今天是:2024年09月03日(星期二)

正在发生

2019年09月03日 | 关于stm32通信协议:软件模拟SPI、软件模拟I2C的总结

2019-09-03 来源:eefocus

趁着帮老师代上嵌入式实验课的机会,又重新熟悉了一遍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("rn 繁忙状态位为: 0x%Xrn", 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高**

}

推荐阅读

史海拾趣

Elite Semiconductor Products Inc公司的发展小趣事

人才是企业发展的根本。Elite非常重视人才的培养和引进工作。公司建立了完善的人才选拔和培养机制,为员工提供了广阔的职业发展空间和良好的工作环境。同时,Elite还积极引进外部优秀人才,为公司注入新的活力和创新力。这些人才的加入不仅提升了公司的整体实力,也为公司的长远发展提供了有力保障。

ECM [ECM Electronics Limited.]公司的发展小趣事

随着公司规模的扩大和产品线的丰富,ECM Electronics Limited.开始积极拓展市场。公司不仅在国内市场取得了良好的销售业绩,还积极开拓国际市场,将产品出口到多个国家和地区。为了更好地服务全球客户,公司还设立了海外办事处和研发中心,加强与国际市场的联系和合作。通过国际化战略的实施,ECM进一步提升了品牌知名度和市场竞争力。

3D PLUS公司的发展小趣事

面对日新月异的电子行业,3D PLUS公司始终保持创新精神,不断推动3D技术的升级。公司研发团队成功研发出全方位彩色人体扫描仪,这一设备能够在极短的时间内实现360度人像扫描,且清晰度极高,为互联网人体应用提供了强有力的支持。此外,公司还不断在软件、算法等方面进行优化,提升3D技术的精度和效率,满足市场的不断需求。

Future Designs, Inc. (FDI)公司的发展小趣事

FDI深知客户需求的重要性,因此他们始终将客户服务放在首位。公司建立了一套完善的客户服务体系,从售前咨询、方案设计到售后支持,全程为客户提供专业、贴心的服务。此外,FDI还针对客户的特定需求,提供定制化解决方案。他们能够从概念设计、原型开发到大规模生产,全程参与客户的项目,确保产品能够完全符合客户的期望和要求。这种服务模式赢得了客户的广泛赞誉和信赖。

Global Communications公司的发展小趣事
温度传感器的输出与温度之间的线性关系也很重要,因为这直接影响到补偿电路的准确性和稳定性。
Econais公司的发展小趣事

作为一家在电子行业有影响力的企业,Econais深知自己的社会责任。公司积极参与各种公益活动,并致力于推动可持续发展。Econais的产品在设计时就考虑到了环保因素,采用低功耗技术减少能源消耗,并选用环保材料降低对环境的影响。此外,Econais还通过技术创新帮助其他企业实现节能减排的目标。

以上五个故事概述了Econais在电子行业发展中的一些重要里程碑和关键事件。这些故事不仅展示了Econais的技术实力和市场竞争力,也体现了公司的社会责任感和可持续发展的理念。

问答坊 | AI 解惑

关于AD初始化

请问一下为何在AD初始化时    AdcRegs.ADCTRL3.bit.ADCBGRFDN = 0x3 总是写不进去 这是一个朋友出现的问题。 使用的是外部时钟参考。我让他先对ADCREFSEL做了设置,然后再对ADCBGRFDN做如上设置,延时5ms后再进行AD转换,但他说AdcRegs ...…

查看全部问答>

用VHDL编程实现一个双工系统

本人求一个程序,用VHDL编程实现一个双工系统 具体为:一个开关控制3个LED显示(如数字516)并且下载到CPLD中检查 …

查看全部问答>

驱动程序开发学习入门历程!!

本人一直是做上层应用程序的开发,对底层一无所知,现在公司需要,我必须转入WinCE网卡驱动方面的开发(尤其是NDIS),或者是Windows网卡驱动方面的开发(我想应该能逐类旁通),对我来说可以说是从零开始学起,请教各位大大,我该从哪学起,再学什么?告诉我历 ...…

查看全部问答>

Lattice TransFR技术

概述 在广泛的应用中,由于现场逻辑更新的前所未有的灵活性,它为设计者提供了修正错误、应对不断变化的标准,升级设备和添加其他服务的特性,现场逻辑更新的重要性不断增加。同时在越来越多的应用中,要求系统能够在99.999 %的时间内运行。莱 ...…

查看全部问答>

STM32初学者感悟,欢迎大家拍砖

最近一直想熟悉下STM32的开发环境,包括程序的烧写调试之类的,对STM32过分苛刻,希望在外部flash调试运行代码。但是实际上,这样做的速度极慢。 这样做也失去了STM32作为 MCU的优势,工程应用上是不会这么用的,诚如大侠说所想要大的存储空 ...…

查看全部问答>

51单片机应用开发案例精选》源代码及图片

51单片机应用开发案例精选》源代码及图片…

查看全部问答>

巴特沃斯滤波器二阶

  巴特沃斯滤波器是电子滤波器的一种。巴特沃斯滤波器的特点是通频带的频率响应曲线最平滑。这种滤波器最先由英国工程师斯替芬·巴特沃斯(Stephen Butterworth)在1930年发表在英国《无线电工程》期刊的一篇论文中提出的。    ...…

查看全部问答>

DSP编程 中断程序

同时使用4个中断    定时器中断、内部中断、SPI中断、外部中断,IMR=?  始终进不了外部中断   内部中断的中断标志位值乱跑。。。。 [ 本帖最后由 zdhsteven 于 2013-4-26 22:51 编辑 ]…

查看全部问答>

LPC800 mini kit ISP问题

本帖最后由 autodash 于 2014-1-28 18:29 编辑 今天测试LPC800  mini kit   CRP读保护的时候,写入CRP2(0x87654321),然后 下载完,悲剧了,ISP不能再用了,难道这个芯片就这样挂了??实在不甘心,那位有解决放方法,十分 ...…

查看全部问答>

NVDC注意事项(一)

本视频介绍了NVDC充电器的注意事项和利弊权衡,回顾电池充电器配置,讲解NVDC充电器设计以及设计时需要注意的几个方面。 …

查看全部问答>