STM32 I2C总线通信与SPI总线通信专题讲解

总线介绍: 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总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
i2c总线协议1.i2c协议规定: 总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。
2.空闲状态:scl和sda都保持着高电平。
3.起始信号: 当scl为高电平而sda由高到低的跳变,表示产生一个起始条件,所有的从设备都能感受到这个跳变,做好准备等待被选择。
4.结束信号:当scl为高而sda由低到高的跳变,表示产生一个 停止条件
5.数据传输:数据传输以字节为单位 , 主设备在scl线上产生每个时钟脉冲的过程中将在sda线上传输一个数据位,数据在时钟的高电平被采样这时候采集到是1就是1,是0就是0,所以在传输数据时,当时钟处于高电平时一定要保持稳定,时钟处于低电平时可以变换数据。(高电平采样,低电平变换)一个字节按数据位从高位到低位的顺序进行传输。主设备在传输有效数据之前 要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位, 0表示主设备向从设备写数据,1表示主设备向从设备读数据。主从设备以字节为单位(8位)进行数据传输,开始传输数据时把从设备地址加上方向位组成一个8位的字节进行发送并接收一个应答。
6.应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。
a.主设备向从设备写数据:
b.主设备读从设备的数据:
c.主设备读从设备的某个寄存器:读设备的寄存器首先应该对该设备发送写命令,很多设备都可以看成是一段内存,所以写命令写给从设备,指明要读取哪个地址(寄存器)的数据,接下来才是真正的读数据。不同的从设备是由区别的,在驱动i2c从设备时应当查明设备的时序图,又怎样的要求,不同的时序对应了不同的命令。
stm32f4-i2c控制器特性软件模拟i2c时序:由于直接控制 gpio 引脚电平产生通讯时序时,需要由 cpu 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。我们知道,驱动i2c设备只需要两根管脚,即使单片机上没有i2c控制器,根据协议控制每根管脚每一时刻的电平状态,一根模拟数据线,一根模拟时钟线,就可以驱动从设备,相对而言效率低,但是可以实现控制驱动。stm32内部具备专门的i2c控制器,使用时只需对其进行相应的配置即可。
硬件控制产生i2c时序:stm32 的 i2c 片上外设专门负责实现 i2c 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,cpu只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理i2c协议的方式减轻了 cpu 的工作,且使软件设计更加简单。
控制器功能:配置主从模式(一般都把stm32当作主机使用,作为从机时应当对其赋一个地址),通过配置其内部的寄存器产生一些中断和错误信号,配置通信速率位标准模式、快速模式、超快速模式等
stm32芯片有3组i2c外设,可以同时进行3组i2c传输。它们的i2c通讯信号引出到不同的gpio引脚上,使用时必须配置到这些指定的引脚。
eeprom(at24cxx)存储芯片介绍一个典型的i2c接口的从设备,专门用于存储数据的芯片。eeprom (electrically erasableprogrammable read only memory),带电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。eeprom可以在电脑上或专用设备上擦除已有信息,重新编程。
eeprom常用来存储一些配置信息,以便系统重新上电的时候加载之,容量不会很高。eepom 芯片最常用的通讯方式就是i2c协议。xx表示容量,常用值为01、02、04、16、32、64等,单位kbit。一般的存储芯片都具有写保护功能,对wp管脚加一个高电平就开启了写保护功能,就无法往芯片内写数据了。在开发中通常将该管脚接地,确保能够写数据
典型24cxx芯片引脚如下:
例:24c65的设备地址为7位,高4位恒定为1010,低3位取决于a0-a2的电平状态,般主机在读写24cxx都是把设备地址连同读写位组合成一个字节一起发送。
24c65的电气连线如下,根据电气连线可知,a0-a2均接地,因此读地址为1010 0001,即0xa1;写地址为10100000,即0xa0 ,且wp接地,用户随时可向芯片内部写入数据。
24c65写时序:首先发送一个起始信号,接着发送从设备地址以及方向位,收到应答后,向从设备发送要写的存储区域的首地址,24c65的存储地址是16位,先发送高8位,收到应答后再发送低8位,再次收到应答后开始写数据。64kbit大小位8k字节,需要13位即可表示,所以高3位固定定为0,如下图。
这里是byte write,一次写一个字节,此芯片还支持page write,一次写一页,也就是8个字节,如果想写更多,可设置一个for循环实现。
24c65 读时序与写时序基本相同,只不过在读之前要发送再发送重复开始位进行读操作。
i2c读写eeprom实例
由电气原理图可知scl和sda分别接入了pb6和pb7管脚,读地址为1010 0001,即0xa1;写地址为10100000,即0xa0
步骤:
1.配置rcc
2.配置pb6和pb7管脚
3.配置i2c协议参数
4.编写代码
//mian.c#include main.h#include stm32f4xx_hal.h#include i2c.h#include usart.h#include gpio.h#define readaddr 0xa1#define writeaddr 0xa0uint8_t wbuf[20] = eeprom test ok!;uint8_t rbuf[20] = {0};/********* 24c65写数据函数*****************************/void eeprom_write(uint16_t memaddr, uint8_t *wbuf, uint16_t len ){ while(len--){ //i2c_memadd_size_16bit表示存储单元大小 //默认为两个参数,分别是i2c_memadd_size_16bit和i2c_memadd_size_8bit //由于24c65的存储地址是16位的 //所以我们选择i2c_memadd_size_16bit //1表示一次写一个字节 while(hal_i2c_mem_write(&hi2c1, writeaddr, memaddr, i2c_memadd_size_16bit, wbuf, 1, 100) != hal_ok){};memaddr++;wbuf++;}}/********* 24c65读数据函数*****************************/void eeprom_read(uint16_t memaddr, uint8_t *rbuf, uint16_t len ){ //可以连续读,所以无需循环 while(hal_i2c_mem_read(&hi2c1, readaddr, memaddr, i2c_memadd_size_16bit, rbuf, len, 100) != hal_ok );}int mian(){ mx_gpio_init(); mx_i2c1_init(); mx_usart1_uart_init(); printf(this is i2c eeprom testn); eeprom_write(0, wbuf, sizeof(wbuf) ); hal_delay(500); eeprom_read(0 , rbuf, sizeof(rbuf)); printf(read: %sn, rbuf); while(){ }}stm32 spi总线通信专题讲解spi接口是motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(master slave)架构;支持多slave模式应用,一般仅支持单master。时钟由master控制,在时钟移位脉冲下,数据按位传输,是高位在前还是低位在前是可以配置的,配置时根据从设备的通信进行相应配置,一般是高位在前,低位在后(msb first)。spi接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几mbps的水平。
spi总线被广泛地使用在flash、adc、lcd等设备与mcu间,要求通讯速率较高的场合。
spi接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
(1)mosi:主器件数据输出,从器件数据输入,连接从机的mosi,与串口不同,串口需要反着连接(rx-----tx)
(2)miso:主器件数据输入,从器件数据输出,连接从机的miso
(3)sclk :时钟信号,由主器件产生
(4)/ss:从器件使能信号,由主器件控制(片选),一般情况下为地电平选中设备,高电平释放设备。
spi总线协议1.数据交换逻辑:主机和从机都包含一个串行移位寄存器,主机通过向它的spi串行寄存器写入一个字节发起一次传输。寄存器通过mosi信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过miso信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此spi成为一个很有效的协议。
如果主机只想写不想读,只需把数据放在数据寄存器,spi控制器会自动传给外设,同时忽略掉外设传过来的数据即可;如果主机只想读不想写,主机写给外设一个空字符或者随便写一个数据,外设就会把数据传过来,不管是只读还是只写,主机与外设的读和写都h会发生且同时进行。
2.起始信号: nss信号线由高变低,是spi通讯的起始信号。
3.结束信号:nss信号由低变高,是spi通讯的停止信号。
4.数据传输:spi使用mosi及miso信号线来传输数据,使用sck信号线进行数据同步。mosi及miso数据线在sck的每个时钟周期传输一位数据,按位传输,且数据输入输出是同时进行的。spi每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制,要么是8位,要么是16位,可以配置。
spi的4种通信模式在spi操作中,最重要的两项设置就是时钟极性(cpol)和时钟相位(cpha)这两项即是主从设备间数据采样的约定方式。由cpol及cpha的不同状态,spi分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,因此通常主机要按照从机支持的模式去设置。同样在配置时一定要弄明白从机支持什么通信模式进行相应的配置。
1.时钟极性cpol : 设置时钟空闲时的电平:
a.当cpol= 0 ,sck引脚在空闲状态保持低电平;
b.当cpol= 1 ,sck引脚在空闲状态保持高电平。
2.时钟相位cpha :设置数据采样时的时钟沿:
a.当 cpha=0时,mosi或 miso 数据线上的信号将会在 sck时钟线的奇数边沿被采样
b.当 cpha=1时, mosi或 miso 数据线上的信号将会在 sck时钟线的偶数边沿被采样
stm32f4-spi控制器特性
1.通讯引脚:
stm32f4芯片最多支持6个spi外设控制器,它们的spi通讯信号引出到不同的gpio引脚上,使用时必须配置到这些指定的引脚,以《stm32f4xx规格书》为准。f407只有spi1、spi2、spi3。
其中spi1、spi4、spi5、spi6是apb2上的设备,最高通信速率达42mbtis/s,spi2、spi3是apb1上的设备,最高通信速率为21mbits/s。其它功能上没有差异。
2.时钟控制逻辑:
sck线的时钟信号,由波特率发生器根据“控制寄存器cr1”中的br[0:2]位控制,该位是对f pclk 时钟的分频因子,对f pclk 的分频结果就是sck引脚的输出时钟频率。
其中的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个可擦除块。
1.w25x16的硬件连线如下:
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 芯片)把这些数据解释成不同的意义,所以才成为指令。
a.读制造商/设备id(90):该指令通常在调试程序的时候用到,判断spi通信是否正常。该指令通过主器件拉低/cs片选使能器件开始传输,首先通过di线传输“90h”指令,接着传输000000h的24位地址(a23-a0),之后从器件会通过do线返回制造商id(efh)和设备id。(注:spi为数据交换通信,主器件在发送“90h”指令时也会接收到一个字节ffh,但此数据为无效数据)
b.写使能命令(06h):在向 flash 芯片存储矩阵写入数据前,首先要使能写操作,通过“write enable”命令即可写使能。
c.扇区擦除(20h):由于 flash 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。
在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
d.读状态寄存器(05h):flash 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认flash芯片“空闲”。我们只需要读取flash芯片内部的状态寄存器srp的s0即可(当这个位为“1”时,表明 flash芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)
e.读数据(03h):读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/cs电平使能设备开始传输,然后传输“03h”指令,接着通过di管脚传输24位芯片存储地址,从器件接到地址后,寻址存储器中的数据通过do引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
f.写数据——页编程(02h):页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/cs引脚电平,接着传输“02h”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。
注:当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。
stm32 spi_flash基本配置和操作根据如下的硬件连线图进行配置
步骤:
1.使能时钟rcc
2.使能spi1,配置相应管脚
3.配置spi协议
4.编码
//main.c#include w25x16.huint8_t rd_buffer[5000] = {0};uint8_t wr_buffer[5000] = spi flash write testn;int main(){ uint16_t flash_id = 0; uint32_t i; mx_gpio_init(); mx_spi1_init(); flash_id = sflash_readid(); /******测试擦除******/ 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_erasesector(4096*0); sflash_writepage(wr_buffer,0, 20); sflash_readbuffer(rd_buffer,0,20); printf(read data: %sn,rd_buffer); /******测试写操作2*****/ //写之前都需要擦除扇区 sflash_erasesector(4096*0); sflash_erasesector(4096*1); 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]); } /*****************/ while(){}}//w25x16.h#ifndef __w25x16_h#define __w25x16_h#include stm32f4xx_hal.h //使用宏定义芯片指令#define w25x_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(gpioh,gpio_pin_2,gpio_pin_reset)/* 释放芯片,拉高信号 */#define sflash_cs_high() hal_gpio_writepin(gpioh,gpio_pin_2,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//w25x16.c#include w25x16.hextern spi_handletypedef hspi1;/*读写一个字节函数,因为spi读和写同时完成*//*发送数据一定会接收到一个数据*/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); //读s0的值,为1表示忙碌,为0表示停止 do{ //发一个空字节,得到s0的值 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();}/*读设备id*/ uint16_t sflash_readid(void){ uint16_t flash_id; uint8_t temp0,temp1; sflash_cs_low(); sflash_sendbyte(w25x_manufactdeviceid); //读设备指令后要发24位地址,所以要发三次 sflash_sendbyte(sflash_dummy_byte); sflash_sendbyte(sflash_dummy_byte); sflash_sendbyte(sflash_dummy_byte); //制造商id temp0 = sflash_sendbyte(sflash_dummy_byte); //设备商id temp1 = sflash_sendbyte(sflash_dummy_byte); sflash_cs_high(); flash_id = (temp0 < >16) & 0xff); 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();}//写一页最多只能写256个字节,一个扇区16页,一个块16个扇区 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; //求writeaddr在某一页的位置 offset = writeaddr % sflash_spi_pagesize; //求某一页剩余的大小 count = sflash_spi_pagesize - offset; /*处理页不对齐的情况,防止页内覆盖*/ //先把某一页剩下的部分写掉,之后的就能新页的起始处开始写 /*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); }}为什么会有两种写操作函数,是因为这里的写操作有两个特点:1.无法突破页限制,超过一页需要重新发起页编程信号。另外如果要写的数据大于剩余一页剩余的容量,那么超出的数据会写到当前页起始地址出。例如,初始输入的写地址为200,而要写的数据大小为100,那么要写的前56个字节会从地址200开始依次写入,剩下的44个字节会从当前页的0地址开始依次写入,这很有可能覆盖之前的数据。
2.无法突破扇区的限制,当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

Nexperia推出首款采用SMD铜夹片LFPAK88封装的热插拔专用MOSFET(ASFET),管脚尺寸缩小60%
台积电2018年高端机销量退步 加密货币或成公司业务增长主力
投影仪挑选成难题?小白科技分享五大高性价比投影仪推荐
传感器与AT89S52单片机的接口电路设计:测量模块电路
算力网络发展的三个阶段分别是
STM32 I2C总线通信与SPI总线通信专题讲解
一加5什么时候上市?一加5最新消息:捏死小米6命门,一加5现货,不玩抢购!
什么是发电机逆功率保护
科兴成功分离奥密克戎变异株并完成直线电机模组基因测序
配电箱接地作用
校园安全再次敲响警钟 校园安防建设可以从以下几点着手
如何控制SMT锡膏回温时间?
广东省已超额完成全年5G基站建设目标
软件测试中的功能测试和非功能测试
布局车载高精定位新蓝海,北云科技切入自动驾驶
阿里云全球布局加速 人工智能技术被认可
最安全SUV已不是volvo,马自达CX-9安全颜值集一身,不知各位觉得怎么样?还要选择汉兰达吗?
焊接层数的选择_焊条直径的选择
什么是真菌毒素快速检仪?
什么是P2P下载