你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32(ARM)开发经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-12 16:18

微信图片_20230412161833.jpg

本系列为ARM开发的进阶知识,讲解与ARM芯片进行通信的大部分高级外设的原理及使用方法,不局限于ARM芯片内部,实操性较强且晦涩,需要一定的读者具备一定代码阅读和理解能力。

  • STM32 I2C总线通信专题讲解


  • 总线介绍:I2C(Inter-Integrated Circuit)总线(也称IIC或I2C)是由PHILIPS公司开发的两线式串行总线(单双工),用于连接微控制器及其外围设备,在这两根线上可以挂很多设备,同一时刻只能有一个节点处于主机模式,其他节点处于从机模式,总线上数据的传送都由主机发起。I2C总线没有片选信号线,所以需要通过协议来找到对应操作的芯片。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,期间封装形式少,通信速率高等优点。


  • 总线特征:

1.两条总线线路:一条串行数据SDA,一条串行时钟线SCL(主从设备使用同一时钟,属于同步通信)来完成数据的传输及外围器件的扩展

2.I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,通常是7位,有时候是10位

3.I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。在开发配置的时候,最好检查从设备的传输速率从而对主设备(一般是MCU)进行相应的配置。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

4.I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输。

  • 拓扑结构——总线型

I2C 总线在物理连接上分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成,SCL由主机发出,SCL越快,通讯速率越快。通信原理是通过对SCL和SDA线高低电平时序的控制来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

微信图片_20230412161906.png

  • I2C总线协议

1.I2C协议规定:  总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。

2.空闲状态:SCL和SDA都保持着高电平。

3.起始信号:  当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。

4.结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件

微信图片_20230412161928.png

5.数据传输:数据传输以字节为单位 , 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。

6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

微信图片_20230412161941.png

a.主设备向从设备写数据:

微信图片_20230412161956.png

b.主设备读从设备的数据:

1.png

c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动I2C从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。

2.png


  • STM32F4-I2C控制器特性

软件模拟I2C时序:由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动I2C设备只需要两根管脚,即使单片机上没有I2C控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。STM32内部具备专门的I2C控制器,使用时只需对其进行相应的配置即可。

硬件控制产生I2C时序:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了 CPU 的工作,且使软件设计更加简单。

控制器功能:配置主从模式(一般都把STM32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等

STM32芯片有3组I2C外设,可以同时进行3组I2C传输。它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

微信图片_20230412162110.png

  • EEPROM(AT24CXX)存储芯片介绍

一个典型的I2C接口的从设备,专门用于存储数据的芯片。EEPROM (Electrically ErasableProgrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。

EEPROM常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。EEPOM 芯片最常用的通讯方式就是I2C协议。XX表示容量,常用值为01、02、04、16、32、64等,单位Kbit。一般的存储芯片都具有写保护功能,对WP管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24CXX芯片引脚如下:

微信图片_20230412162134.png

例:24C65的设备地址为7位,高4位恒定为1010,低3位取决于A0-A2的电平状态,般主机在读写24CXX都是把设备地址连同读写位组合成一个字节一起发送。

3.png

24C65的电气连线如下,根据电气连线可知,A0-A2均接地,因此读地址为1010 0001,即0xA1;写地址为10100000,即0xA0 ,且WP接地,用户随时可向芯片内部写入数据。

4.png

24C65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24C65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64Kbit大小位8K字节,需要13位即可表示,所以高3位固定定为0,如下图。

这里是BYTE WRITE,一次写一个字节,此芯片还支持PAGE WRITE,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。

5.png

24C65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。

6.png

  • I2C读写EEPROM实例


由电气原理图可知SCL和SDA分别接入了PB6和PB7管脚,读地址为1010 0001,即0xA1;写地址为10100000,即0xA0

步骤:
1.配置RCC

2.配置PB6和PB7管脚

7.jpg

3.配置I2C协议参数

8.png

4.编写代码

  1. //mian.c

  2. #include "main.h"
  3. #include "stm32f4xx_hal.h"
  4. #include "i2c.h"
  5. #include "usart.h"
  6. #include "gpio.h"

  7. #define ReadAddr   0xA1
  8. #define WriteAddr  0xA0

  9. uint8_t Wbuf[20] = "EEPROM TEST OK!";
  10. uint8_t Rbuf[20] = {0};

  11. /*********  24C65写数据函数*****************************/

  12. void  Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){
  13.         while(len--){
  14.         //I2C_MEMADD_SIZE_16BIT表示存储单元大小
  15.         //默认为两个参数,分别是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT
  16.         //由于24C65的存储地址是16位的
  17.         //所以我们选择I2C_MEMADD_SIZE_16BIT
  18.         //1表示一次写一个字节
  19.                 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};
  20. MemAddr++;
  21. Wbuf++;
  22. }
  23. }


  24. /*********  24C65读数据函数*****************************/

  25. void  Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){
  26.         //可以连续读,所以无需循环
  27.         while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );
  28. }
  29. int mian(){
  30.       MX_GPIO_Init();
  31.       MX_I2C1_Init();
  32.       MX_USART1_UART_Init();
  33.       printf("this is i2c eeprom test\n");

  34.       Eeprom_Write(0, Wbuf, sizeof(Wbuf) );
  35.       HAL_Delay(500);
  36.       Eeprom_Read(0 , Rbuf, sizeof(Rbuf));
  37.       printf("READ:  %s\n", Rbuf);

  38.       while(){

  39.       }
  40. }
复制代码

  • STM32 SPI总线通信专题讲解

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

微信图片_20230412162643.png

SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

微信图片_20230412162658.png

(1)MOSI:主器件数据输出,从器件数据输入,连接从机的MOSI,与串口不同,串口需要反着连接(Rx-----Tx)
(2)MISO:主器件数据输入,从器件数据输出,连接从机的MISO
(3)SCLK :时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。

  • SPI总线协议


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

如果主机只想写不想读,只需把数据放在数据寄存器,SPI控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。

微信图片_20230412162734.png

2.起始信号:  NSS信号线由高变低,是SPI通讯的起始信号。

3.结束信号:NSS信号由低变高,是SPI通讯的停止信号。

4.数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。

微信图片_20230412162755.png

  • SPI的4种通信模式


在SPI操作中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。

13.png

1.时钟极性CPOL : 设置时钟空闲时的电平:
a.当CPOL= 0 ,SCK引脚在空闲状态保持低电平;
b.当CPOL= 1 ,SCK引脚在空闲状态保持高电平。

2.时钟相位CPHA :设置数据采样时的时钟沿:
a.当 CPHA=0时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
b.当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样

微信图片_20230412162837.png

11.png
  • STM32F4-SPI控制器特性

12.png

1.通讯引脚:
STM32F4芯片最多支持6个SPI外设控制器,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F4xx规格书》为准。f407只有SPI1、SPI2、SPI3。

微信图片_20230412163014.png

其中SPI1、SPI4、SPI5、SPI6是APB2上的设备,最高通信速率达42Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为21Mbits/s。其它功能上没有差异。

2.时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是SCK引脚的输出时钟频率。

微信图片_20230412161739.png

其中的fpclk 频率是指SPI所在的APB总线频率,APB1为fpclk1 ,APB2为fpckl2

3.数据控制逻辑:
STM32F4的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源来源于接收缓冲区及发送缓冲区。

a.通过写SPI的“数据寄存器DR”把数据填充到发送缓冲区中。

b.通过读“数据寄存器DR”,可以获取接收缓冲区中的内容。

c.其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行(高位在前)还是LSB先行(低位在前)。

4.整体控制逻辑:
a.整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式(同时发送和接收、只发送关掉接收、只接收关掉发送)等等。

b.在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。

c.实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
  • 串行FLASH_W25X16简介


FLSAH 存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。

W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,即一个扇区包含16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

微信图片_20230412161735.png

1.W25X16的硬件连线如下:

微信图片_20230412161643.png

CS:    片选引脚,低电平有效,连接到STM32-PH2管脚
SO:   连接到STM32-PB4管脚(MISO)
SI:    连接到STM32-PB5管脚(MOSI)
CLK:  连接到STM32-PA5管脚(CLK)
WP:   写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用
HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

2.W25X16控制指令:
我们需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32利用 SPI总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。
而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。

微信图片_20230412161719.png

a.读制造商/设备ID(90):该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)

微信图片_20230412161715.png

b.写使能命令(06H):在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

微信图片_20230412161711.png

c.扇区擦除(20H):由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

微信图片_20230412161707.png
微信图片_20230412161704.png

d.读状态寄存器(05H):FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH芯片“空闲”。我们只需要读取FLASH芯片内部的状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)

微信图片_20230412161658.png

微信图片_20230412161654.png

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

微信图片_20230412161650.png

f.写数据——页编程(02H):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。

注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

微信图片_20230412161646.png

  • STM32 SPI_FLASH基本配置和操作


根据如下的硬件连线图进行配置

微信图片_20230412161729.png

步骤:
1.使能时钟RCC

2.使能SPI1,配置相应管脚

微信图片_20230412161640.png

微信图片_20230412161637.png

3.配置SPI协议

微信图片_20230412161632.png


4.编码
  1. //main.c

  2. #include "w25x16.h"

  3. uint8_t RD_Buffer[5000] = {0};
  4. uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";

  5. int main(){
  6.     uint16_t FLASH_ID = 0;
  7.     uint32_t i;
  8.    
  9.     MX_GPIO_Init();
  10.     MX_SPI1_Init();
  11.     FLASH_ID = sFLASH_ReadID();
  12.    
  13.     /******测试擦除******/
  14.     sFLASH_EraseSector(4096*0);
  15.     //sFLASH_EraseSector(4096*1);
  16.     sFLASH_ReadBuffer(RD_Buffer,0,4096);
  17.     printf("读数据开始\n");
  18.     for(i=0; i<4096; i++)
  19.     {
  20.     printf("%x ",RD_Buffer[i]);
  21.     }
  22.     printf("读数据结束\n");


  23.     /******测试写操作1*****/
  24.     //写之前都需要擦除扇区
  25.     sFLASH_EraseSector(4096*0);
  26.     sFLASH_WritePage(WR_Buffer,0, 20);
  27.     sFLASH_ReadBuffer(RD_Buffer,0,20);
  28.     printf("READ DATA: %s\n",RD_Buffer);
  29.    
  30.     /******测试写操作2*****/
  31.     //写之前都需要擦除扇区
  32.     sFLASH_EraseSector(4096*0);
  33.     sFLASH_EraseSector(4096*1);                           
  34.     for(i=0; i<4096; i++)
  35.     {
  36.     WR_Buffer[i] = 0x55;
  37.     }
  38.     sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
  39.     sFLASH_ReadBuffer(RD_Buffer,4090,1000);
  40.     for(i=0; i<1000; i++)
  41.     {
  42.     printf("%x ",RD_Buffer[i]);
  43.     }
  44.     /*****************/
  45.     while(){}
  46. }
复制代码
  1. //w25x16.h

  2. #ifndef __W25X16_H
  3. #define __W25X16_H

  4. #include "stm32f4xx_hal.h"

  5. //使用宏定义芯片指令
  6. #define W25X_ManufactDeviceID  0x90  /* Read identification */
  7. #define sFLASH_CMD_WREN        0x06/* Write enable instruction */
  8. #define sFLASH_CMD_RDSR        0x05/* Read Status Register instruction  */
  9. #define sFLASH_CMD_SE          0x20/* Sector Erase instruction */
  10. #define sFLASH_CMD_WRITE       0x02  /* Write to Memory instruction */
  11. #define sFLASH_CMD_READ        0x03/* Read from Memory instruction */
  12. #define sFLASH_DUMMY_BYTE      0x00 //空字节,用于只读传回来的数据
  13. #define sFLASH_BUSY_FLAG       0x01
  14. #define sFLASH_SPI_PAGESIZE    0x100

  15. /* 选中芯片,拉低信号 */
  16. #define sFLASH_CS_LOW()       HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
  17. /* 释放芯片,拉高信号 */
  18. #define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)

  19. //定义函数
  20. uint8_t sFLASH_SendByte(uint8_t byte);
  21. uint16_t sFLASH_ReadID(void);
  22. void sFLASH_EraseSector(uint32_t SectorAddr);
  23. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
  24. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
  25. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);
  26. #endif
复制代码

  1. //w25x16.c

  2. #include "w25x16.h"

  3. extern SPI_HandleTypeDef hspi1;

  4. /*读写一个字节函数,因为SPI读和写同时完成*/
  5. /*发送数据一定会接收到一个数据*/
  6. uint8_t sFLASH_SendByte(uint8_t byte)
  7. {
  8.     uint8_t TX_DATA = byte;
  9.     uint8_t RX_DATA = 0;
  10.     HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
  11.     return RX_DATA;
  12. }

  13. /*等待擦除或者写数据完成*/
  14. void sFLASH_WaitForEnd(void)
  15. {
  16.     uint8_t sr_value = 0;
  17.     sFLASH_CS_LOW();
  18.     sFLASH_SendByte(sFLASH_CMD_RDSR);
  19.             //读S0的值,为1表示忙碌,为0表示停止         
  20.     do{
  21.                 //发一个空字节,得到S0的值            
  22.                 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  23.     }while( sr_value & sFLASH_BUSY_FLAG);

  24.     sFLASH_CS_HIGH();
  25. }

  26. void sFLASH_WriteEnable(void)
  27. {
  28.     sFLASH_CS_LOW();
  29.    
  30.     sFLASH_SendByte(sFLASH_CMD_WREN);
  31.    
  32.     sFLASH_CS_HIGH();
  33. }


  34. /*读设备ID*/
  35. uint16_t sFLASH_ReadID(void)
  36. {
  37.     uint16_t FLASH_ID;
  38.     uint8_t temp0,temp1;
  39.    
  40.    
  41.     sFLASH_CS_LOW();
  42.    
  43.     sFLASH_SendByte(W25X_ManufactDeviceID);
  44.     //读设备指令后要发24位地址,所以要发三次
  45.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  46.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  47.     sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  48.     //制造商ID
  49.     temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  50.     //设备商ID
  51.             temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  52.    
  53.     sFLASH_CS_HIGH();
  54.    
  55.     FLASH_ID = (temp0 << 8) | temp1;
  56.    
  57.     return FLASH_ID;
  58. }

  59. //擦除扇区,擦除为1,因为只能由1变为0 ,不能0变1
  60. void sFLASH_EraseSector(uint32_t SectorAddr)
  61. {
  62.     //SectorAddr表示擦除第几个扇区
  63.     sFLASH_WriteEnable();  //开启写使能
  64.     sFLASH_CS_LOW();//拉低,片选
  65.     //擦除命令
  66.     sFLASH_SendByte(sFLASH_CMD_SE);
  67.             //传24位地址
  68.             //传送高8位,将中8位和低8位一共16位移出去,得到高8位                  
  69.     sFLASH_SendByte( (SectorAddr>>16) & 0xff);   
  70.     sFLASH_SendByte( (SectorAddr>>8) & 0xff);    //传送中8位
  71.     sFLASH_SendByte( (SectorAddr>>0) & 0xff);    //传送低8位
  72.      
  73.     sFLASH_CS_HIGH();
  74.    
  75.     /*读状态寄存器,等待擦除完成*/
  76.     sFLASH_WaitForEnd();
  77. }

  78. //读数据
  79. //读命令和读地址发送后,芯片内部会自动不断递增读数据
  80. void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
  81. {
  82.     sFLASH_CS_LOW();
  83.    
  84.     sFLASH_SendByte(sFLASH_CMD_READ);
  85.     sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位
  86.     sFLASH_SendByte( (ReadAddr>>8) & 0xff);    //传送中8位
  87.     sFLASH_SendByte( (ReadAddr>>0) & 0xff);    //传送低8位
  88.    
  89.     while(NumByteToRead--)
  90.     {
  91.     * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
  92.     pBuffer++;
  93.     }
  94.    
  95.     sFLASH_CS_HIGH();
  96. }

  97. //写一页最多只能写256个字节,一个扇区16页,一个块16个扇区                              
  98. void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
  99. {
  100.     if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
  101.     {
  102.     NumByteToWrite = sFLASH_SPI_PAGESIZE;
  103.    
  104.     printf("写数据量过大,超过一页大小\n");
  105.     }
  106.    
  107.     sFLASH_WriteEnable();  //开启写使能
  108.    
  109.     sFLASH_CS_LOW();
  110.    
  111.     sFLASH_SendByte(sFLASH_CMD_WRITE);
  112.     sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
  113.     sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
  114.     sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送低8位
  115.    
  116.     while(NumByteToWrite--)
  117.     {
  118.     sFLASH_SendByte(* pBuffer);
  119.     pBuffer++;
  120.     }
  121.    
  122.     sFLASH_CS_HIGH();
  123.    
  124.     /*擦除和写数据都涉及到写动作,一定要等待完成*/
  125.     sFLASH_WaitForEnd();
  126. }

  127. //写任意地址、任意长度
  128. void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
  129. {
  130.     uint16_t NumOfPage, NumOfBytes, count, offset;
  131.     //求WriteAddr在某一页的位置        
  132.     offset = WriteAddr % sFLASH_SPI_PAGESIZE;
  133.     //求某一页剩余的大小
  134.     count = sFLASH_SPI_PAGESIZE - offset;
  135.    
  136.     /*处理页不对齐的情况,防止页内覆盖*/
  137.     //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写        /*offset有值表示需要页对齐,如果要写的字节数小于某一页剩余的部分,那就无需对齐*/                  
  138.     /*这两个条件必须同时满足*/
  139.     if(offset && (NumByteToWrite > count ))
  140.     {
  141.     sFLASH_WritePage(pBuffer,WriteAddr,count);
  142.     NumByteToWrite -= count;//去掉已经写了的,从新页开始
  143.     pBuffer += count;
  144.     WriteAddr += count;
  145.     }
  146.    
  147.     /*最多可分多少页*/
  148.     NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;
  149.     /*剩余多少字节*/
  150.     NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
  151.    
  152.     if(NumOfPage)
  153.     {
  154.         while(NumOfPage--)
  155.         {
  156.              //每一页都发起页编程
  157.              sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
  158.              pBuffer += sFLASH_SPI_PAGESIZE;
  159.      WriteAddr += sFLASH_SPI_PAGESIZE;
  160.          }
  161.     }
  162.    
  163.     if(NumOfBytes)
  164.     {
  165.     sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
  166.     }
  167. }
复制代码

  • 为什么会有两种写操作函数,是因为这里的写操作有两个特点:



1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。

2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

转载自: 骆驼听海
如有侵权请联系删除

收藏 评论0 发布时间:2023-4-12 16:18

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版