学习STM32第十五天

SPI外设

一、简介

STM32F4XX内部集成硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU负担,可配置8位/16位数据帧,高位(最常用)/低位先行,三组SPI接口,支持DMA

SPI框图
由上图可知SPI是通过接收/发送缓冲区移位寄存器进行通信,其中SPI1是在APB2总线,SPI2、SPI3在APB1总线。发送和接收共用一个SR,即SPI是同步通信接口。SS引脚一般用GPIO口指定从机,硬件NSS引脚一般是用来配置多主机模式。
发送:数据先进入TDR,经SR通过MOSI向从机输出
接收:数据由MISO进入SR,然后经过RDR向地址数据总线输出
由此可对上面SPI框图进行简化,基本结构如下
SPI基本结构
这里给出SPI主模式全双工连续传输模式下的时序图,SPI主模式全双工
上图选择的是模式3,SCK高电平为空闲状态,在SCK第一个边沿移出数据,第二个编译移入数据。上面时序图采用小端模式,低位先行,这里对进行分析
发送:

  • SS置低电平,开始时序,选中从机。此时TXE = 1,TDR为空;RXNE = 0,RDR为空。BSY = 1
  • 软件写入0xF1到SPI_DR,即要发送的第一个数据,此时TXE = 0,RXNE = 0,TDR非空
  • TDR中的0xF1会立刻转入到SR中,TDR清空,MOSI开始发送同时TXE = 1
  • 软件等待TXE = 1,然后写入0xF2到SPI_DR,即要发送的第二个数据,此时TXE = 0,RXNE = 0
  • TDR中的0xF2会随后自动进入SR,MOSI在发送完第一个数据会自动发送第二个数据
  • TDR发送完所有数据,TXE会自动置1,SR发送完所有数据后,BSY = 0

接收:

  • SS置低电平,开始时序,选中从机。此时TXE = 1,TDR为空;RXNE = 0,RDR为空。BSY = 1
  • MISO依次接收从机的数据,输出到SR
  • SR中的数据以小端模式进入到SPI_DR中
  • 软件等待RXNE = 1,然后数据总线读取RDR中的数据0xA1,同时RXNE = 0,RDR变为空
  • MISO接收第二个数据,输出到SR
  • SR中的第二个数据以小端模式进入到SPI_DR中
  • 软件等待RXNE = 1,然后数据总线读取RDR中的第二个数据,同时RXNE = 0,RDR变为空
  • RDR接收完所有数据,RXNE = 0

由上图可知,SPI全双工连续通信是交叉进行的,发送数据1,发送数据2,再接收数据1;发送数据3,再接收数据2;在时序上要求操作之间的间隙非常小。
SPI非连续全双工通信
非连续传输模式,只需要四行代码。上图是SPI模式3,SCK高电平为空闲状态,分析如下

  • SS置低电平,选中从机,开始时序此时TXE = 1,RXNE = 0,TDR为空
  • 软件写入0xF1到SPI_DR,此时TDR = 0xF1,TXE = 0
  • TDR中的0xF1立即进入SR中,MOSI开始发送0xF1,TDR清空,TXE = 1
  • 等待MOSI将第一个字节数据发送完毕,此时接收第一个字节数据的时序也完成,即RXNE = 1
  • 读取接收到的第一个字节数据,然后将第二个字节数据写入TDR,开始发送第二个数据
  • 等待MOSI发送完第二个字节数据,此时接收到了第二个字节数据
  • 读取完第二个字节数据,然后将第三个字节数据写入TDR,开始发送第三个数据

整体流程就是:等待TXE = 1,写入数据到TDR,等待RXNE = 1,读取RDR数据。这样实现发送数据1接收数据1,发送数据2接收数据2。但是字节之间存在一定的间隙,降低传输效率。

二、实验案例

进行STM32F4XX对板载W25Q16读写,代码如下

#include "stm32f4xx.h"                  // Device header

//硬件SPI通信,采用非连续传输方案
/*PB0引脚模拟SS输出*/
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)BitValue);//片选引脚输出
}

/*SS->PB0,MISO->PB4,MOSI->PB5,SCK->PB3,板载W25Q16支持SPI模式0和模式3*/
/*
*	SPI1是在APB2总线,SPI2、SPI3在APB1总线
*	PB3: SPI1_SCK、SPI3_SCK
*	PB4: SPI1_MISO、SPI3_MISO
*	PB5: SPI1_MOSI、SPI3_MOSI
*	PB0: 使用GPIO模拟SS
*/
void MySPI_Init()
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;	//PB3复用为SPI1_SCK,PB4复用为SPI1_MISO,PB5复用为SPI1_MOSI
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				//使用GPIO模拟片选信号SS
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);	//GPIO引脚复用
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);
	
	//SPI配置
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC校验根据实际需求填写
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据帧大小
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//高位先行
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//这里选择主机
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//软件模拟片选SS
	SPI_Cmd(SPI1, ENABLE);

//	
	MySPI_W_SS(1);											//默认是终止条件
}

/*起始条件*/
void MySPI_Start()
{
	MySPI_W_SS(0);
}
/*终止条件*/
void MySPI_End()
{
	MySPI_W_SS(1);
}
/*交换一个字节,这里选择模式0*/
/*			SCK低电平为空闲状态
*	SS下降沿启动,主机移出高位数据到MOSI
*	SCK上升沿,主机移入高位数据MISO
*	SCK下降沿,主机移出高位数据MOSI
*			非连续传输需要四步
*/
uint8_t MySPI_SwapByte_Mode0(uint8_t ByteSend)
{
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待TXE = 1
 	SPI_I2S_SendData(SPI1, ByteSend);								//将数据写入到DR中
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待RXNE = 1
	return SPI_I2S_ReceiveData(SPI1);								//读取RDR的数据
}

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
鉴于引用内容的长度,我将分为两部分回答你的问题。 第一部分回答: 引用\[1\]中提到了一个学习STM32的初学者,他想通过博客记录自己的学习历程并与其他学习者交流互助。作为dalao,我可以给他一些建议。首先,我建议他在博客中详细记录自己的学习过程,包括遇到的问题和解决方案。这样不仅可以帮助他自己回顾和总结,也可以帮助其他学习者。其次,我建议他积极参与STM32的相关社区和论坛,与其他学习者交流经验和解决问题。这样可以扩展他的知识和技能,并且可以从其他人的经验中学习。最后,我建议他保持耐心和坚持,学习STM32是一个长期的过程,需要不断的实践和探索。只有坚持下去,才能取得进步。 第二部分回答: 引用\[2\]中提到了一个关于STM32点阵屏的项目实现。这个项目实现了点阵屏的操作,包括自动更改显示内容和通过串口控制显示内容。在这个项目中,使用了STM32 F103C8T6单片机和共阳1588BS型号的LED点阵管数码管。通过Keil uVision5编程工具进行开发。在接线方面,点阵屏的1-8接口连接到A0-A7引脚,9-16接口连接到B0-B15引脚。项目测试时,可以通过串口助手进行控制和观察显示效果。具体的代码和操作步骤可以参考提供的链接。 引用\[3\]中提到了另一个关于8*8点阵的STM32项目。作者使用的是STM32F103ZET6单片机和88点阵模块。他在实验室顺到了这个模块,并花时间研究了它。虽然条件有限,但他仍然能够进行学习和实验。 综上所述,对于学习STM32和使用8*8点阵的项目,建议你积极记录学习过程,参与社区交流,保持耐心和坚持。这样可以不断提升自己的技能和知识。 #### 引用[.reference_title] - *1* *3* [STM32驱动8*8点阵模块](https://blog.csdn.net/qq_45698227/article/details/116209395)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [STM32的8*8点阵屏开发(小项目)](https://blog.csdn.net/dong_xiao_dong/article/details/106655191)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值