首页 > 编程知识 正文

51单片机spi通信程序,51单片机外设

时间:2023-05-03 20:19:30 阅读:51348 作者:3936

提示:转载时,请记下来源。 谢谢你。

本文前言: SPI协议的特点1 .优点2 .缺点3 .机制2、SPI协议分析1 .模式概念理解2 .通信过程分析3. SPI个人协议理解4、使用SPI协议操作SPI外围设备芯片总结

前言中写道单片机,但实际上即使您的主板上没有系统(自由RTOS,Linux ),协议也不会改变。 题外话:在工作中,他植入了别人写的SPI协议,与外设芯片(如闪存芯片、网卡芯片等)进行了通信,但都没有深入底层,下午一边看代码,一边总结写了3个多小时这个博客

另一方面,串行外围设备接口(SPI )是SPI协议的特征,它通过四个常见接口(这些接口)提供芯片选择、时钟和该规则称为协议,因此是SPI协议,在能够高速、全双工、输出的今天,集成了这种通信协议的外围设备芯片越来越多,FLASH、AD转换器、NFC芯片等非常常见。

1 .优势支持全双工,信号完整性好

支持高速(100MHz以上)

协议对应字长不限于8bits,可以根据APP应用的特征灵活地选择消息字长。 (上一层还是下一层需要查看外围设备芯片的文档,主要保证两个SPI通信设备之间使用相同的协议。)。

硬件连接简单;

2 .与缺点IIC相比,两条线多,有四条线

因为没有寻址机制,所以只能在切片选择中选择不同的设备。 这意味着在发送数据之前,在IO中降低设备的芯片选择信号,然后发送数据,并在操作完成时提高芯片选择信号

主设备不知道发送是否成功,因为没有从设备接收到ACK;

典型的APP应用程序仅支持单主控;

与RS232 RS485和CAN总线相比,SPI传输距离更短,并且仅限于PCB板;

3 .结构信号定义为: http://www.Sina.com/:串行时钟http://www.Sina.com/: master output、Slave Input主收发信号http://www

二、SPI协议分析1 .模式概念理解首先要知道SCK的概念。 概念为自行百度,根据CPOL和CPHA的状态,SPI分为四种模式。 如果要写软SPI协议,就需要知道这四种模式。 使用硬SPI协议时,根据周边芯片的不同,初始化时有以下4种模式。

例如,一个名为W25Q64的FLSH芯片同时支持模式0和模式3,以便MCU在初始化SPI时可以选择其中一种模式。

2 .通讯流程分析这是野火STM32F103手册的照片,参考这张照片分析通讯流程

(1)拉低NSS信号线,产生启动信号(未示出); (需要软件操作)2)将发送的数据写入“数据寄存器DR”,并将该数据存储在发送缓冲器中; (需要软件操作)3)通信开始,SCK时钟开始工作。 MOSI逐位传输发送缓冲器中的数据; MISO将数据逐位存储在接收缓冲器中; 我们不用管,单片机就能自动完成! )

* )4)在一个帧的数据发送结束后,“状态寄存器SR”的“TXE标志位”被设置为1,表示一个帧的发送结束,发送缓冲器变空。 同样,“RXNE标志位”在接收完一帧的数据时设置为1,表示接收缓冲区不为空。 )需要软件操作。 为了进行状态查询,通常在while死循环中保证数据的发送接收。 )5)等待“TXE标志位”变为1后继续发送数据时,只需再次向“数据寄存器DR”写入数据即可。 等待“RXNE标志位”变为1,通过读取“数据寄存器DR”即可获取接收缓冲区的内容。 )6)拉上NSS信号线并生成终止信号(需要软件操作) (3. SPI个人协议)我们知道,实际上对于任何一个MCU支持的协议,我们要做的是三个步骤。

1、初始化2、发送数据3、接收数据

但是,spi协议只是在发送和接收数据之前降低芯片选择信号。 对于MCU的运行,每个MCU制造商都有不同的寄存器,在创建发送或接收函数时,会为每个MCU创建不同的函数。 现在,分析两家公司的,拿到一个芯片后,请参考制造商的demo进行制作。 这是最正确的。 不要傻傻的自己从头写到尾。MOSI如上所述,由于SPI协议没有从设备发送ACK,所以主设备不知道发送是否成功,但可以知道是否发送了数据buff。 简而言之,我不知道是否发送了数据,但我知道没有发送数据。 每个制造商的设计都不同,但STM32检测buff发送完成的原因是接收缓冲区(不是写入错误,而是接收缓冲区)不为空。 )我觉得这样设计很奇怪。 没办法,制造商是这样设计的。 )

1 )在发送之前,检测TX

E,若发送缓冲区位空,则将数据写入发送数据寄存器;2)等待数据发送完成(若RXNE为非空,则表示发送完成); // 发送函数u8 SPI_FLASH_SendByte(u8 byte){ SPITimeout = SPIT_FLAG_TIMEOUT; /* 等待发送缓冲区为空,TXE事件 */ while (SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_TXE) == RESET) { if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0); } /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */ SPI_I2S_SendData(FLASH_SPIx, byte); // 将一个字节的数据写入spi数据寄存器 SPITimeout = SPIT_FLAG_TIMEOUT; /* 判断发送buff的数据是否完成,等待接收缓冲区非空,RXNE事件 */ while (SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_RXNE) == RESET) { if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1); } /* 读取数据寄存器 */ return SPI_I2S_ReceiveData(FLASH_SPIx );}// 接收函数u8 SPI_FLASH_ReadByte(void){// 通过写的方式,来读数据,感觉挺奇怪的 return (SPI_FLASH_SendByte(Dummy_Byte));//Dummy_Byte为任意字节,无意义,但是必须要写,一般我们写0XFF} 第二家,国内HUA芯片, 这款芯片就有专门的发送完成和是否接受到数据的状态寄存器,发送和接收逻辑符合我们通常的认知。写这两个函数的时候需要参考厂家demo。 // 发送函数void Spim0SendData(UINT8 *data_buf, UINT16 len){ UINT16 *phalfword = (UINT16*)data_buf;UINT32 *pword = (UINT32*)data_buf;Spim0ClrFifo();//清空发送缓冲区 Spim0RecAutorcvDis(); // 禁用自动接收Spim0TransStart();// 开始发送Spim0ClrStatus(SPIM0_TXEND);// 清空发送完成寄存器while(len) { if(len >= 8) {/*send 8 Byte data*/for (UINT8 i = 0; i < 8; i++){SPIM0->DR = *data_buf;data_buf++;}len -= 8;wrcnt += 8; } else if(len >= 4) {/*send 4 Byte data*/for (UINT8 i = 0; i < 4; i++){SPIM0->DR = *data_buf;data_buf++;}len -= 4;wrcnt += 4; } else{for (UINT8 i = 0; i < len; i++){SPIM0->DR = *data_buf;data_buf++;len--;}}while(!(Spim0GetStatus() & SPIM0_TXEND)); Spim0ClrStatus(SPIM0_TXEND);}Spim0TransStop();}// 接收函数void Spim0RecvData( UINT8 *data_buf, UINT16 rev_len){ UINT16 *phalfword = (UINT16*)data_buf;UINT32 *pword = (UINT32*)data_buf;Spim0SetClk(rev_len & 0x3ff);/*set rx frames,the maxlen is 0x3ff bytes*/Spim0ClrFifo();Spim0RecAutorcvEn();/*only receive mode en*/ Spim0TransStart();while(rev_len != 0){if(rev_len >= 4){ /*receive 4 byte data*/while(!(Spim0GetStatus() & SPIM0_RXHF));*data_buf++ = SPIM0->DR;*data_buf++ = SPIM0->DR;*data_buf++ = SPIM0->DR;*data_buf++ = SPIM0->DR;rev_len -= 4;} else{ while(!(Spim0GetStatus() & SPIM0_RXNE));for(; rev_len>0; rev_len--){*data_buf++ = SPIM0->DR;}}}Spim0TransStop(); } 4、使用SPI协议操作SPI外设芯片

需要先看外设芯片的数据手册,例如W25Q64 flash芯片的操作指令为,(下图中括号的数据为接收的数据):

举个简单的例子,使用stm32读flash的设备ID:

u32 SPI_FLASH_ReadDeviceID(void){ u32 Temp = 0; /* Select the FLASH: Chip Select low */ SPI_FLASH_CS_LOW(); /* Send "RDID " instruction */ SPI_FLASH_SendByte(W25X_DeviceID);// 0xAB SPI_FLASH_SendByte(Dummy_Byte); SPI_FLASH_SendByte(Dummy_Byte); SPI_FLASH_SendByte(Dummy_Byte); /* Read a byte from the FLASH */ Temp = SPI_FLASH_ReadByte();// 等价于 Temp = SPI_FLASH_SendByte(Dummy_Byte); /* Deselect the FLASH: Chip Select high */ SPI_FLASH_CS_HIGH(); return Temp;} 总结 1、SPI协议主要写的就是发送和接收函数,发送和接收的数据需要看外设芯片的数据手册;2、若MCU支持硬SPI协议,那我们一般用硬spi协议,若用软的,移植的时候不好移植,因为你不知道你的外设芯片支持哪种spi模式。如果MCU不支持SPI,现在你又需要SPI,这时就可以写个软的SPI协议。不过现在芯片一般都支持硬SPI了,除非为了节省成本,你的芯片很Low很Low。软spi协议很简单,关于波特率,你不需要太过关系,只要不超过外设芯片的波特率就可以,至于具体是多少Hz,如果不追求速度的话,没有太大的关系,可以先调通spi,然后在调速。软SPI协议如下(模式0): 可以看到,先操作的是数据IO,然后在操作SCK的IO。

请务必参考上面的时序图,来看下面软spi模式0对应的代码,不然不知道原由: // spi发送函数void SpiByteWrite(unsigned char dat) { unsigned char mask; for (mask=0x01; mask!=0; mask<<=1) //低位在前,逐位移出 { if ((mask&dat) != 0) //首先输出该位数据 Set_MOSI_IO(1); // IO拉高 else Set_MOSI_IO(0); // IO拉低 Set_SPI_CK(1); //然后拉高时钟,数据采样,IO拉高 Set_SPI_CK(0); //再拉低时钟,完成一个位的操作 ,IO拉低 } Set_MOSI_IO(1); //最后确保释放 IO 引脚,IO拉高} // spi总线上读取一个字节 unsigned char DS1302ByteRead() { unsigned char mask; unsigned char dat = 0; for (mask=0x01; mask!=0; mask<<=1) //低位在前,逐位读取 { if (Get_MISO_IO!= 0) //首先读取此时的 IO 引脚,并设置 dat 中的对应位 { dat |= mask; } Set_SPI_CK(1); //然后拉高时钟,数据采样,IO拉高 Set_SPI_CK(0); //再拉低时钟,完成一个位的操作 ,IO拉低 } return dat; //最后返回读到的字节数据 } 若其他模式,参考下面的图片,相信你也能自己写出对应的软SPI协议。

在时序上,SPI 比 I2C 简单多,没有了起始、停止和应答,和UART一样, SPI 在通信的时候,只负责通信,不管是否通信成功,而 I2C 却要通过应答信息来获取通信成功失败的信息,所以相对来说,UART 和 SPI 的时序都要比 I2C 简单一些。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。