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

江协科技STM32学习- P38 软件SPI读写W25Q64

        🚀write in front🚀  
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 

💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚 

🚀Projeet source code🚀   

💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com

引用:

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客

STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客

0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客

【STM32】江科大STM32学习笔记汇总(已完结)_stm32江科大笔记-CSDN博客

江科大STM32学习笔记(上)_stm32博客-CSDN博客

STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客

STM32 MCU学习资源-CSDN博客

stm32学习笔记-作者: Vera工程师养成记

stem32江科大自学笔记-CSDN博客

术语:

英文缩写描述
GPIO:General Purpose Input Onuput通用输入输出
AFIO:Alternate Function Input Output复用输入输出
AO:Analog Output模拟输出
DO:Digital Output数字输出
内部时钟源 CK_INT:Clock Internal内部时钟源
外部时钟源 ETR:External Trigger 时钟源 External 触发
外部时钟源 ETR:External Trigger mode 1外部时钟源 External 触发 时钟模式1
外部时钟源 ETR:External Trigger mode 2外部时钟源 External 触发 时钟模式2
外部时钟源 ITRx:Internal Trigger inputs外部时钟源,ITRx (Internal trigger inputs)内部触发输入
外部时钟源 TIx:exTernal Input pin 外部时钟源 TIx (external input pin)外部输入引脚
CCR:Capture/Comapre Register捕获/比较寄存器
OC:Output Compare输出比较
IC:Input Capture输入捕获
TI1FP1:TI1 Filter Polarity 1Extern Input 1 Filter Polarity 1,外部输入1滤波极性1
TI1FP2:TI1 Filter Polarity 2Extern Input 1 Filter Polarity 2,外部输入1滤波极性2
DMA:Direct Memory Access直接存储器存取

正文:

0. 概述

从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。

感兴趣的一件事:

STM32HAL-最简单的长、短、多击按键框架

STM32HAL-最简单的长、短、多击按键框架(多按键)-CSDN博客


这就是一个简易的操作系统代码(瞎说的),基于时间片调度,没有抢占,但是有任务优先级。也就100行。
https://www.zhihu.com/question/658017543

1.🚚 软件SPI读写W25Q64

接线图

CS(片选)接到PA4 ,DO(从机输出)接到PA6 ,CLK时钟接到PA5 ,DI(从机输入)接到PA7,当然我这里引脚其实并不是任意选的,实际上是接到了硬件SPI的引脚上,这样的话软件SPI和硬件SPI都可以任意切换。

PA6为上拉输出入,PA4,PA5 ,PA7为推挽输出
交换字节:W25Q64支持模式0和模式3,这里一般选择模式0

程序的整体框架:

在这里插入图片描述

指令集:读取ID号时序:起始,先交换发送指令9F,随后连续交换接收3个字节,停止
第一个字节是厂商ID,后两个是设备ID,其中设备ID高8位,表示存储器类型,低8位表示容量。

  • 💎第一个是写使能指令,发送一个指令码06
  • 💎第二个是读状态寄存器1,作用是判断芯片是不是忙状态,要读取BUSY位,是否置1,1表示芯片在忙,0表示芯片不忙了。还要实现一个等待BUSY为0的函数,调用这个函数,BUSY为1,进入等待;BUSY为0,函数执行完毕。
  • 💎第三个是页编程函数,先发送一个指令码02,再发3个字节的地址,最后发数据。
  • 💎第四个扇区擦除,执行扇区擦除,先发送指令20,再发送3个字节的地址,这样指定地址,所在的扇区就会擦除。

 

最后一个读取数据,流程是:交换发送指令03,再发送3个字节的地址,随后转入接收,就可以依次接收数据

MySPI.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"


void MySPI_W_SS(uint8_t bitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)bitValue);
}

void MySPI_W_SCK(uint8_t bitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)bitValue);
}


void MySPI_W_MOSI(uint8_t bitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)bitValue);
}


uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

void MySPI_Init()
{
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef gpioInitStructure;
	gpioInitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	gpioInitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
	gpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpioInitStructure);
	
	gpioInitStructure.GPIO_Mode = GPIO_Mode_IPU;
	gpioInitStructure.GPIO_Pin = GPIO_Pin_6;
	gpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpioInitStructure);
	
	
	
	MySPI_W_SS(1);
	MySPI_W_SCK(0);	//use SPI mode0
	
}

void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t byteSend)
{
	uint8_t byteRecv = 0;
	
	for(int i=0; i<8; i++)
	{
		MySPI_W_MOSI(byteSend & (0x80 >> i));		//SPI MOSI发送数据
		MySPI_W_SCK(1);								//SPI CLK上升沿
		if(MySPI_R_MISO()) byteRecv |= (0x80 >> i);	//SPI MISO读取数据
		MySPI_W_SCK(0);								//SPI下降沿
	}

	
	return byteRecv;
}

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64.h"

void W25Q64_Init(void)
{
	MySPI_Init();

}


void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	uint8_t Data =  0;
	
	MySPI_Start();	//SPI Start
	
	MySPI_SwapByte(0x9F);
	*MID = MySPI_SwapByte(0xFF);
	Data = MySPI_SwapByte(0xFF);
	Data = Data<< 8 | MySPI_SwapByte(0xFF);
	*DID = Data;
	
	MySPI_Stop();	//SPI Stop
}

Main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"
#include <stdio.h>
#include "Key.h"
#include "String.h"
#include "LED.h"
#include "MyI2C.h"
#include "MP6050.h"
#include "MP6050_Reg.h"
#include "W25Q64.h"



uint8_t MID;
uint16_t DID;

int main(int argc, char *argv[])
{

	
	OLED_Init();

	
	W25Q64_Init();
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 1, MID, 2);
	OLED_ShowHexNum(1, 4, DID, 4);

	while(1)                                                                                 
	{

	}
	
	return 1;
}

💎现象
注意:写入数据前必须擦除,如不执行擦除,读出的数据=原始数据&写入的数据。

1.1 实验1-读取芯片ChipID

1.2实验2-写入读取W25Q64

写入读取W25Q64,写入第一个扇区起始地址 0x000000 写入数组 [0x01,0x02,0x03,0x04]后再独处数组显示在OLED屏幕上。

源码:

W25Q64.C

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64.h"
#include "W25Q64_Ins.h"

void W25Q64_Init(void)
{
	MySPI_Init();

}


void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	uint8_t Data =  0;
	
	MySPI_Start();	//SPI Start
	
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	Data = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	Data = Data<< 8 | MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = Data;
	
	MySPI_Stop();	//SPI Stop
}

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

uint8_t W25Q64_Read_StatusReg1(void)
{
	uint8_t Data =  0;
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_STATUS_REGISTER);
	Data = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
	
	return Data;
}

void W25Q64_WaitBusy(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	
	while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & (0x01)) == 0x01);
	
	MySPI_Stop();
}


void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArry, uint16_t Count)
{
	W25Q64_WriteEnable();	//写使能
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for(uint16_t i=0; i<Count; i++)
	{
		MySPI_SwapByte(DataArry[i]);
	}
	MySPI_Stop();
	
	//事后等待
	//事前等待,读取操作也需要等待
	W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();	//写使能	
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	//事后等待
	//事前等待,读取操作也需要等待
	W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint32_t Count)
{

	MySPI_Start();
	
	MySPI_SwapByte(W25Q64_READ_DATA);
	
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	
	for(uint16_t i=0; i<Count; i++)
	{
		DataArry[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	
	MySPI_Stop();
}


W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

 Main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"
#include <stdio.h>
#include "Key.h"
#include "String.h"
#include "LED.h"
#include "MyI2C.h"
#include "MP6050.h"
#include "MP6050_Reg.h"
#include "W25Q64.h"



uint8_t MID;
uint16_t DID;

uint8_t ArryWrite[4] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArryRead[4];

int main(int argc, char *argv[])
{

	
	OLED_Init();

	
	W25Q64_Init();
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 1, MID, 2);
	OLED_ShowHexNum(1, 4, DID, 4);

	OLED_ShowString(2,1,"W");
	OLED_ShowString(3,1,"R");
	
	W25Q64_SectorErase(0x000000);
	
	W25Q64_PageProgram(0x000000, ArryWrite, 4);
	W25Q64_ReadData(0x000000, ArryRead, 4);
	
	OLED_ShowHexNum(2, 3, ArryWrite[0], 2);
	OLED_ShowHexNum(2, 6, ArryWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArryWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArryWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArryRead[0], 2);
	OLED_ShowHexNum(3, 6, ArryRead[1], 2);
	OLED_ShowHexNum(3, 9, ArryRead[2], 2);
	OLED_ShowHexNum(3, 12, ArryRead[3], 2);
	
	while(1)                                                                                 
	{

	}
	
	return 1;
}

1.3实验3-擦除W25Q64扇区

在Mian.c中擦除扇区之后,注释掉PageProgram的函数,擦除扇区后直接读删除0x000000起始的4个字节,读取到的值都是 0xFF,表示扇区擦除后Flash里都是0.

	W25Q64_SectorErase(0x000000);
	
	//W25Q64_PageProgram(0x000000, ArryWrite, 4);
	W25Q64_ReadData(0x000000, ArryRead, 4);

1.4实验4-验证Flash中的数据只能从1写0,不能从0写1

验证Flash中的数据只能从1写0,不能从0写1

写入数据之前必须擦除,否则写入数据就是原始数据和新写入数据的按位与的结果。

 依次在0x000000地址处,先也擦除载依次协议0xAA, 0xBB, 0xCC , 0xDD。读取出来的结果是

uint8_t ArryWrite[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t ArryRead[4];


//页擦除
W25Q64_SectorErase(0x000000);
//依次写入0xAA 0xBB 0xCC 0xDD
W25Q64_PageProgram(0x000000, ArryWrite, 4);
W25Q64_ReadData(0x000000, ArryRead, 4);

注释掉页擦除,然后网0x000000地址处,尝试写入 0x55 0x66 0x77 0x88,再读取出来Flash地址处的值显示在OLED屏幕上。读取出来的值不是写入的值,和预期的结果不一样,愿意是因为Flash只能支持从1写0不能从0写1。

uint8_t ArryWrite[4] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArryRead[4];


//页擦除
//W25Q64_SectorErase(0x000000);
//依次写入0xAA 0xBB 0xCC 0xDD
W25Q64_PageProgram(0x000000, ArryWrite, 4);
W25Q64_ReadData(0x000000, ArryRead, 4);

Flahs的特性就是:“只能从1写0不能从0写1”,所以写入数据之前必须擦除,否则写入数据就是原始数据和新写入数据的按位与的结果。

1.5 实验5-验证写数据不能跨页

从第一页的0xFF地址开始写,因为一页大小是256字节,所以从0xff开始写4个字节,后面3个字节会发生跨页。然后从地址0xFF读取4个字节,看从0xFF开始的4个字节地址里Flash中的数据。

uint8_t ArryWrite[4] = {0x66, 0x77, 0x88, 0x99};
uint8_t ArryRead[4];


//页擦除
W25Q64_SectorErase(0x000000);
//从第一页的0xFF地址开始写,因为一页大小是256字节,所以从0xff开始写4个字节,后面3个字节会发生跨页
W25Q64_PageProgram(0x0000FF, ArryWrite, 4);
//从地址0xFF读取4个字节
W25Q64_ReadData(0x0000FF, ArryRead, 4);

看到0xFF地址后面的3个字节的内容是第二页的起始3个字节的内容,其中数据时0xFF也就是扇区擦除后的内容全为1(0xFF)。 

然后把读取地址修改为 0x00,看写入后第一个扇区的的前4个字节的内容

	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x0000FF, ArryWrite, 4);
	W25Q64_ReadData(0x000000, ArryRead, 4);

看到0x00第一页起始三个字节的内容是写入数据跨页之后重新回卷到页的起始地址写入的数据。

结论是:Flash不能跨页写,如果写入数据长度超过页的结尾将回卷到页的起始地址开始写。

Flash不能跨页写,但是可以跨页读取。 


http://www.kler.cn/a/385962.html

相关文章:

  • IQ Offset之工厂实例分析
  • [Codesys]常用功能块应用分享-BMOV功能块功能介绍及其使用实例说明
  • MuMu模拟器安卓12安装Xposed 框架
  • HARCT 2025 分论坛4:智能系统传感、传感器开发和数据融合中的智能数据分析
  • JVM双亲委派与自定义类加载器
  • Python sys模块介绍
  • 4-1-1.C# 数据容器 - List(List 的定义、List 元素的基本操作、List 元素的遍历、List 的常用方法)
  • 智能出行助手:SpringBoot共享汽车管理平台
  • Coppelia Sim (v-REP)仿真 机器人3D相机手眼标定与实时视觉追踪 (三)
  • GBase 8a MPP Cluster V9安装部署
  • TikTok Spark Ads火花广告是什么?如何设置?
  • 图像算法之 OCR 识别算法:原理与应用场景
  • Unity Windows 2023 Release-Notes
  • 软考系统架构设计师论文:论面向对象的建模及应用
  • 聊一聊:今天是记者节,你觉得大模型时代还需要专业的记者与内容吗?
  • 抖音小程序流量主掘金新玩法——看广告娱乐与收益的双赢新机遇
  • MATLAB和Python及R聚类和亚群识别
  • Spring Boot 接口与单元测试
  • RHCE的学习(14)
  • MAN TruckScenes数据集:第一个用于自动驾驶卡车的大规模多模式数据集。
  • ubuntu中apt-get的默认安装路径。安装、卸载以及查看的方法总结
  • vscode使用之vscode-server离线安装
  • TCP连接如何保障数据传输安全
  • 【C++】哈希表模拟:开散列技术与哈希冲突处理
  • 这是一个bug求助帖子--安装kali 遇坑
  • 【青牛科技】GC5931:工业风扇驱动芯片的卓越替代者