当前位置: 首页 > news >正文

【STM32F1标准库】代码——SPI通信

目录

一、W25Q64介绍与使用

1.W25Q64内部结构

2.W25Q64指令集

3.W25Q64使用流程

4.W25Q64使用注意事项

二、使用软件模拟SPI时序

1.SPI时序层

0)引脚配置

1)开始

2)结束

3)交换一个字节(以符合从机的高位先行为例)

2.W25Q64驱动层(均为设备私有时序)

三、使用硬件SPI接口

1.配置流程

2.代码示例


一、W25Q64介绍与使用

W25Q64是一款由华邦公司生产的64M-bit串行Flash存储器芯片。

1.W25Q64内部结构

  • 256字节。这是编程(写入) 的最小单位。一次最多可以连续写入一页的数据。

  • 扇区4KB。这是擦除的最小单位。在写入新数据前,必须先擦除对应的扇区。

  • 64KB。一个大容量的擦除单位,可以一次性擦除整个块。

  • 整个芯片8MB。可以执行整片擦除命令。

一页可以存储256字节数据,刚好是2^8,所以用一字节刚好可以描述一页内每一个字节数据的地址。

2.W25Q64指令集

指令名指令码描述
写使能0x06在执行任何写/擦除操作前,必须发送此命令!
写禁止0x04禁用写操作。
读数据0x03从指定地址开始读取数据。
页编程0x02向指定地址开始的页写入数据(最多256字节)。
扇区擦除0x20擦除一个4KB的扇区。写入前必须先擦除!
芯片擦除0xC7擦除整个芯片,时间较长。
读状态寄存器0x05

读取状态寄存器,主要用于检查BUSY位

读取设备ID0x9F读JEDEC ID(返回制造商ID和设备ID)

任何写/擦除操作后,都必须轮询检查BUSY位,直到它变为0,才能进行下一步操作。

3.W25Q64使用流程

  1. 读取器件ID(验证通信)
  2. 擦除扇区(在写入新数据前,必须先擦除目标扇区)
  3. 写入/读取数据

4.W25Q64使用注意事项

  1. 先擦后写:Flash存储器只支持将1改写为0,不支持将0改写为1,因此在写入数据之前,必须通过擦除将整个扇区/块恢复为1

  2. 写前使能:任何改变数据的操作(编程、擦除)前,都必须发送0x06写使能命令。

  3. 检查忙状态:在写/擦除操作前(或者事后判断),必须等待BUSY位清零,才能进行下一步操作。

  4. 页边界:页编程不能跨页。如果你要写入的数据跨越了页边界会回到页首覆盖页首的数据,所以对于超过一页的数据进行存储,需要拆分成两次页编程操作。

  5. 读取操作:直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

  6. 驱动优化:对于大量数据的读写,可以考虑使用DMA来提高效率,避免CPU被长时间占用。

二、使用软件模拟SPI时序

1.SPI时序层

0)引脚配置

/****************************** 片选线电平控制 ******************************/
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}/****************************** 时钟线电平控制 ******************************/
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);		//根据BitValue,设置SCK引脚的电平
}/****************************** MOSI线电平控制 ******************************/
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);		//根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}/****************************** MISO线电平检测 ******************************/
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);			//读取MISO电平并返回
}/******************************** 引脚初始化 ********************************/
void MySPI_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4、PA5和PA7引脚初始化为推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入/*设置默认电平*/MySPI_W_SS(1);											//SS默认高电平MySPI_W_SCK(0);											//SCK默认低电平
}

1)开始

主机将与从机相连的片选线SS线由高电平拉低到低电平,表示选中该从机进行通信,整个通信期间SS线都保持在低电平状态

void MySPI_Start(void)
{MySPI_W_SS(0);				//拉低SS,开始时序
}

2)结束

void MySPI_Stop(void)
{MySPI_W_SS(1);				//拉高SS,终止时序
}

3)交换一个字节(以符合从机的高位先行为例)

  • 模式0(常用)
    • 第一个时钟脉冲还没有到就需要把数据放到数据线上
    • SCK下降沿将数据从寄存器移出到数据线上
    • SCK上升沿将数据从数据线移入到寄存器中
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00作为与从机的交换数据,此处必须赋初值0x00,后面会用到for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据{MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据}return ByteReceive;								//返回接收到的一个字节数据
}

2.W25Q64驱动层(均为设备私有时序)

/****************************** 引脚初始化 ******************************/
void W25Q64_Init(void)
{MySPI_Init();					//先初始化底层的SPI
}/****************************** 读取设备ID ******************************/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_JEDEC_ID);			//交换发送读取ID的指令*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//交换接收MID,通过输出参数返回*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//交换接收DID高8位*DID <<= 8;									//高8位移到高位*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//或上交换接收DID的低8位,通过输出参数返回MySPI_Stop();								//SPI终止
}/********************************* 写使能 *********************************/
void W25Q64_WriteEnable(void)
{MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_WRITE_ENABLE);		//交换发送写使能的指令MySPI_Stop();								//SPI终止
}/**************************** 等待BUSY标志位清零 ****************************/
void W25Q64_WaitBusy(void)
{uint32_t Timeout;MySPI_Start();								                //SPI起始MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);				//交换发送读状态寄存器1的指令Timeout = 100000;						                	//给定超时计数时间while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	//循环等待忙标志位{Timeout --;								                //等待时,计数值自减if (Timeout == 0)						                //自减到0后,等待超时{/*超时的错误处理代码,可以添加到此处*/break;								                //跳出等待,不等了}}MySPI_Stop();								                //SPI终止
}/****************************** 指定扇区数据擦除 ******************************/
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable();						//写使能MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位MySPI_SwapByte(Address);					//交换发送地址7~0位MySPI_Stop();								//SPI终止W25Q64_WaitBusy();							//等待忙
}/****************************** 指定页写入数据 ******************************/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{uint16_t i;W25Q64_WriteEnable();						//写使能MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位MySPI_SwapByte(Address);					//交换发送地址7~0位for (i = 0; i < Count; i ++)				//循环Count次{MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据}MySPI_Stop();								//SPI终止W25Q64_WaitBusy();							//等待忙
}/****************************** 指定页读取数据 ******************************/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{uint32_t i;MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_READ_DATA);			//交换发送读取数据的指令MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位MySPI_SwapByte(Address);					//交换发送地址7~0位for (i = 0; i < Count; i ++)				//循环Count次{DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据}MySPI_Stop();								//SPI终止
}

三、使用硬件SPI接口

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
  • 可配置8位/16位数据帧、高位先行/低位先行
  • 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • 支持多主机模型、主或从操作
  • 可精简为半双工/单工通信
  • 支持DMA
  • 兼容I2S协议
  • STM32F103C8T6 硬件SPI资源:SPI1、SPI2

1.配置流程

  1. 开启时钟
  2. 将引脚配置为复用推挽输出
  3. 初始化SPI
  4. 使能SPI
  5. 调用库函数,使用数据寄存器中的数据与从设备进行数据交换

2.代码示例

仅在SPI时序层做出改动,使用硬件SPI代替软件SPI

W5Q64层代码与软件SPI代码一致

/****************************** 片选线电平控制 ******************************/
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}/****************************** 发起开始时序 ******************************/
void MySPI_Start(void)
{MySPI_W_SS(0);				
}/****************************** 发起结束时序 ******************************/
void MySPI_Stop(void)
{MySPI_W_SS(1);				
}/****************************** 硬件SPI接口初始化 ******************************/
void MySPI_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4引脚初始化为推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA5和PA7引脚初始化为复用推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入/*SPI初始化*/SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1/*SPI使能*/SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行/*设置默认电平*/MySPI_W_SS(1);											//SS默认高电平
}/****************************** 交换一个字节 ******************************/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空SPI_I2S_SendData(SPI1, ByteSend);								//写入数据到发送数据寄存器,开始产生时序while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待接收数据寄存器非空return SPI_I2S_ReceiveData(SPI1);								//读取接收到的数据并返回
}
http://www.dtcms.com/a/499991.html

相关文章:

  • 丽水品牌网站设计做网站商家
  • 如何使用若依解决多选字段的问题——方案一
  • MySQL实战45讲之实战篇(中)
  • STM32上实现AUTOSAR FEE模块功能
  • 黄岛开发区做网站网络公司长沙做网站建设
  • 网站建设的扩展性分析网站推广方案策划案例
  • JSON 字符串反斜杠问题
  • 制作可以赚钱的网站doooor设计
  • 备案 网站名称 修改o2o的四种营销模式
  • 泰安企业建站公司排行广州番禺建网站
  • Java程序免安装JDK运行方案:内置JDK实现完全独立部署
  • ES 别名:核心用法与避坑
  • 建设工程交易中心官网十堰网站优化
  • 北仑网站建设培训学校wordpress编辑器模板
  • 7种python常见漏洞与大模型检测思路
  • 网站开发策略六安seo
  • NumPy zeros() 函数详解
  • 政务治理与战略决策总署——未来国策的“反脆弱”锻造熔炉系统
  • 第4讲:Go运行时系统剖析
  • 做外文翻译的网站深圳龙岗区地图全图
  • 安徽城乡建设 厅网站小企业网站如何建设好
  • Unity Addressables笔记
  • 重庆公司网站建设步骤5元购买已备案域名
  • 负载均衡spring-cloud-starter-openfeign教程
  • 怎么提高网站seo优化关键字排名龙岩一线网络有限公司
  • 采集的网站怎么做收录如何建设一个博客网站
  • 风车网站做花盆磨具附近的招聘工作
  • 影楼行业网站ui交互设计软件
  • 千享科技网站建设动画设计培训
  • 做网站怎么报价做门户网站主要技术哪一块