首页 > 其他分享 >STM32学习笔记—SPI总线通信原理与实验

STM32学习笔记—SPI总线通信原理与实验

时间:2025-01-17 12:57:06浏览次数:3  
标签:sFLASH 总线 STM32 SPI SendByte CS pBuffer 时钟

SPI总线通信原理

SPI总线简介

SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
在这里插入图片描述

SPI总线物理·拓扑结构

SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
在这里插入图片描述
(1)MOSI:主器件数据输出,从器件数据输入
(2)MISO:主器件数据输入,从器件数据输出
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选)

SPI总线协议

在这里插入图片描述
起始信号: NSS信号线由高变低,是SPI通讯的起始信号
结束信号: NSS信号由低变高,是SPI通讯的停止信号
数据传输: SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

主从设备间数据交换逻辑示意

主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。
在这里插入图片描述

SPI的四种通信模式

在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。
时钟极性CPOL : 设置时钟空闲时的电平
当CPOL = 0 ,SCK引脚在空闲状态保持低电平;
当CPOL = 1 ,SCK引脚在空闲状态保持高电平。
时钟相位CPHA : 设置数据采样时的时钟沿
当 CPHA=0 时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样;
当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样。
通信模式的设置:
由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。
在这里插入图片描述

STM32F4-SPI控制器特性

在这里插入图片描述

通讯引脚

STM32F4芯片有多个SPI外设,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。
在这里插入图片描述
注:其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。

时钟控制逻辑

SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。
在这里插入图片描述
注:其中的f pclk 频率是指SPI所在的APB总线频率,APB1为f pclk1 ,APB2为f pckl2。

数据控制逻辑

STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。
• 通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。
• 通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。
• 其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB(高位)先行还是LSB(低位)先行。

整体控制逻辑

• 整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式等等。
• 在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
• 实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

NOR FLASH介绍

FLASH是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。
FLASH是有一个物理特性:只能写0,不能写1,写1靠擦除。
FLASH主要有NOR Flash和NAND Flash两种类型,NOR和NAND是两种数字门电路。
在这里插入图片描述

NM25Q128 简介

NM25Q128,串行闪存器件,属于NOR FLASH中的一种,容量为128 Mb。擦写周期可达10W次,可以将数据保存达20年之久。在这里插入图片描述

NM25Q128常用指令

NOR FLASH的指令总数比较多, 但是如果只需要实现基本操作, 还是比较简单的。一般我们只需要:5条指令即可完成对NOR FLASH的基本使用(以NM25Q128为例)。
在这里插入图片描述

读数据(03H)

读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
在这里插入图片描述

写数据——页编程(02H)

页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
注: 当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
在这里插入图片描述

CubeMX参数设置

根据自己的硬件外设(NM25Q128)连接配置芯片引脚功能

参考“NM25Q128 简介”部分的图片
在这里插入图片描述
在这里插入图片描述

SPI参数设置

在这里插入图片描述
Mode: 模式选择双向主模式
其他参数根据上面的原理介绍部分选择即可。

实验程序设计

NM25Q128.h如下:

#ifndef __NM25Q128_H
#define __NM25Q128_H

#include "stm32f4xx_hal.h"
#include "stdio.h"

#define NM25Q128_ManufactDeviceID  0x90  		/* Read identification */
#define sFLASH_CMD_WREN            0x06			/* Write enable instruction */
#define sFLASH_CMD_RDSR            0x05			/* Read Status Register instruction  */
#define sFLASH_CMD_SE              0x20			/* Sector Erase instruction */
#define sFLASH_CMD_WRITE           0x02  		/* Write to Memory instruction */
#define sFLASH_CMD_READ            0x03			/* Read from Memory instruction */	

#define sFLASH_DUMMY_BYTE          0x00
#define sFLASH_BUSY_FLAG           0x01
#define sFLASH_SPI_PAGESIZE        0x100

/* 选中芯片: 拉低片选 */
#define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_RESET)
/* 释放芯片: 拉高片选 */
#define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET)

uint8_t sFLASH_SendByte(uint8_t byte);
uint16_t sFLASH_ReadID(void);
void sFLASH_EraseSector(uint32_t SectorAddr);
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);

#endif

NM25Q128.c如下:

#include "NM25Q128.h"

extern SPI_HandleTypeDef hspi1;

/*读写一个字节函数*/
uint8_t sFLASH_SendByte(uint8_t byte)
{
	uint8_t TX_DATA = byte;
	uint8_t RX_DATA = 0;
	HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
	
	return RX_DATA;
}

/*等待擦除或者写数据完成*/
void sFLASH_WaitForEnd(void)
{
	uint8_t sr_value = 0;
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_RDSR);
	
	do{	
		sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
		
	}while( sr_value & sFLASH_BUSY_FLAG);
	
	sFLASH_CS_HIGH();
}

void sFLASH_WriteEnable(void)
{
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_WREN);
	
	sFLASH_CS_HIGH();
}

uint16_t sFLASH_ReadID(void)
{
	uint16_t FLASH_ID;
	uint8_t temp0,temp1;
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(NM25Q128_ManufactDeviceID);
	
	sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	
	temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	
	sFLASH_CS_HIGH();
	
	FLASH_ID = (temp0 << 8) | temp1;
	return FLASH_ID;
}

void sFLASH_EraseSector(uint32_t SectorAddr)
{
	sFLASH_WriteEnable();  //开启写使能
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_SE);
	sFLASH_SendByte( (SectorAddr>>16) & 0xff);   //传送高8位
	sFLASH_SendByte( (SectorAddr>>8) & 0xff);     //传送中8位
	sFLASH_SendByte( (SectorAddr>>0) & 0xff);      //传送低8位
	 
	sFLASH_CS_HIGH();
	/*等待擦除完成*/
	sFLASH_WaitForEnd();
}

void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
{
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_READ);
	sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位
	sFLASH_SendByte( (ReadAddr>>8) & 0xff);     //传送中8位
	sFLASH_SendByte( (ReadAddr>>0) & 0xff);      //传送低8位
	while(NumByteToRead--)
	{
		* pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
		pBuffer++;
	}
	sFLASH_CS_HIGH();
}

void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
	if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
	{
		NumByteToWrite = sFLASH_SPI_PAGESIZE;
		
		printf("写数据量太大,超过一页的大小\n");
	}
	sFLASH_WriteEnable();  //开启写使能
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_WRITE);
	sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
	sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
	sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
	
	while(NumByteToWrite--)
	{
		sFLASH_SendByte(* pBuffer);
		pBuffer++;
	}
	sFLASH_CS_HIGH();
	/*等待写完成*/
	sFLASH_WaitForEnd();
}

void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
	uint16_t NumOfPage, NumOfBytes, count, offset;
	
	offset = WriteAddr % sFLASH_SPI_PAGESIZE;
	count = sFLASH_SPI_PAGESIZE - offset;
	
	/* 处理页不对齐的情况*/
	if(offset && (NumByteToWrite > count ))
	{
		sFLASH_WritePage(pBuffer,WriteAddr,count);
		
		NumByteToWrite -= count;
		
		pBuffer += count;
		WriteAddr += count;
	}
	NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;
	NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
	if(NumOfPage)
	{
			while(NumOfPage--)
			{
				sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
				pBuffer += sFLASH_SPI_PAGESIZE;
				WriteAddr += sFLASH_SPI_PAGESIZE;
			}
	}
	
	if(NumOfBytes)
	{
		sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
	}
}

主程序中的测试程序如下:

	printf("this is spi flash test\n");
	FLASH_ID = sFLASH_ReadID();
	printf("FLASH_ID = %x\n",FLASH_ID);
	
		/*测试擦除*/
	sFLASH_EraseSector(4096*0);
	sFLASH_EraseSector(4096*1);
	
//	sFLASH_ReadBuffer(RD_Buffer,0,4096);
//	printf("读数据开始\n");
//	for(i=0; i<4096; i++)
//	{
//		printf("%x ",RD_Buffer[i]);
//	}
//	printf("读数据结束\n");
//	
//	
//	/*测试写操作1*/
//	sFLASH_WritePage(WR_Buffer,0, 20);
//	sFLASH_ReadBuffer(RD_Buffer,0,20);
//	printf("READ DATA: %s\n",RD_Buffer);
//	
	/*测试写操作2*/
	for(i=0; i<4096; i++)
	{
		WR_Buffer[i] = 0x55;
	}
	sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
	sFLASH_ReadBuffer(RD_Buffer,4090,1000);
	for(i=0; i<1000; i++)
	{
		printf("%x ",RD_Buffer[i]);
	}

标签:sFLASH,总线,STM32,SPI,SendByte,CS,pBuffer,时钟
From: https://blog.csdn.net/weixin_51824176/article/details/145167248

相关文章

  • 基于STM32单片机自动售货机扫码支付无人超市语音播报无线蓝牙APP/WIFI-APP控制/WIFI视
    STM32-S147语音播报+二维码付+4种商品+4路电机出货+选货+手付+库存+缺货+找零+声光+按键+TFT屏+(无线方式选择)产品功能描述:本系统由STM32F103C8T6单片机核心板、1.44寸TFT彩屏、(无线蓝牙/无线WIFI/无线视频监控模块-可选)、步进电机控制电路、语音播报模块接口、蜂鸣器报警电......
  • stm32步进电机曲线控制程序
    在STM32上实现步进电机的曲线控制涉及多个步骤,包括硬件配置、步进电机驱动器的接口、PWM信号生成、以及通过算法实现速度或位置的曲线控制。以下是一个基本的步骤和代码示例,帮助你入门。硬件配置连接步进电机和驱动器:确保步进电机和驱动器正确连接,并且驱动器的控制信号(如脉冲......
  • STM32使用PWM制作呼吸灯
    一、引言在STM32微控制器的众多应用中,PWM(脉冲宽度调制)功能是一项非常实用的技术。PWM可以通过控制信号的占空比来实现对输出功率的精确控制,其应用范围广泛,从电机速度控制、灯光亮度调节到音频信号的生成等。其中,使用PWM制作呼吸灯是一个经典的入门示例,能够帮助我们很......
  • STM32 RTC 功能详解与代码示例
    一、引言STM32微控制器的实时时钟(RTC)功能在许多应用中都非常重要,它允许设备保持精确的时间和日期信息,即使在系统断电或复位后,只要有备用电源(如锂电池)为RTC供电,就能继续运行。这对于需要时间戳、定时任务、日历功能以及其他需要精确时间信息的应用程序来说是必不可少的,例......
  • stm32与昆仑通态modbus通讯
    一、引言在工业自动化和物联网等领域,STM32微控制器和昆仑通态人机界面(HMI)的组合应用十分广泛。为了实现它们之间的数据交换和设备控制,通常采用Modbus通信协议。Modbus是一种简单且通用的通信协议,支持多种物理接口,如RS-485、RS-232和TCP/IP等,其具有易于实现、开放性......
  • STM32H563 HAL库 LWIP裸机移植
    一、STM32H563与LWIP简介STM32H563是STMicroelectronics推出的一款高性能32位微控制器,具有丰富的外设和强大的处理能力,适用于各种复杂的嵌入式系统应用。LWIP(LightweightIP)是一个小型开源的TCP/IP协议栈,旨在为嵌入式系统提供轻量级的网络功能,它能够在资源有限的......
  • STM32F103使用flash_algo解析FLM相关
    1、全局区(.bss段和.data段)根据实际情况修改2、栈顶地址根据实际情况修改/*FlashOSRoutines(AutomagicallyGenerated)*Copyright(c)2009-2015ARMLimited*/#include"flash_blob.h"//代码区flash_code[]使用JLINK/STLINK等放到RAM,一般是0x20000000staticconst......
  • stm32 无源蜂鸣器实验 播放音乐 猪八戒背媳妇
    前言在8051及stm32各类教辅资料中,均有无源蜂鸣器相关的实验。可以通过单片机控制无源蜂鸣器发出指定频率和时长的声音,从而实现播放音乐的功能。在以往的此类案例中,乐谱的谱写非常不方便,除了案例提供的乐谱数据外,学者要将一个其它的简谱转换成单片机可以播放的数据,基本不可......
  • STM32简介
    1、STM32是基于ARM-Cortex-M内核开发的32位微控制器。STM32分为高性能系列,主流系列,低功耗系列、无线系列:视频采用STM32F1系列高性能系列:STM32F2,F4,F7,H7(3224内核跑分,双核微控制器=550MHz的Cortex-M7+240MHz的Cortex-M4)2、ARM内核型号:经典ARM处理器:ARM7、ARM9、ARM11Corte......
  • 基于STM32的AI物联网计算实现指南
    基于STM32的AI物联网计算实现指南版权所有©深圳市为也科技有限公司摘要随着人工智能(AI)和物联网(IoT)的快速发展,智能设备在各行各业中的应用日益广泛。STM32系列微控制器凭借其高性能、低功耗和丰富的外设接口,成为实现AI物联网计算的理想选择。本文将全面介绍如何利用S......