第二十五章 FLASH

第二十五章 FLASH

目录

第二十五章 FLASH

1 W55MH32 FLASH 简介

2 闪存的读取

3 闪存的编程和擦除

4 FLASH 寄存器

4.1 FPEC 键寄存器(FLASH_KEYR)

4.2 FLASH 控制寄存器(FLASH_CR)

4.3 闪存状态寄存器(FLASH_SR)

4.4 闪存地址寄存器(FLASH_AR)

5 例程设计

5.1 FLASH_Eeprom

5.1 函数声明

5.2 常量与数组定义

5.3 主函数main()

5.4 串口配置函数UART_Coniguration()

5.5 字符发送函数SER_PutChar()

5.6 重定向函数fputc()

6 下载验证

6.1 FLASH_Eeprom


1 W55MH32 FLASH 简介

WMH32的闪存模块由主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。

主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每一页 2K 字节。注意,小容量和中容量产品则每页只有 1K 字节。从上图可以看出主存储器的起始地址就是 0x08000000,B0、B1 都接 GND 的时候,就是从0x08000000 开始运行代码的。

信息块,该部分分为 2 个小部分,其中启动程序代码,用来存储 ST 自带的启动程序,用来串口下载代码,当 B0 接 3V3,B1 接 GND 的时候,运行的就是这部分代码。用户选中字节,则一般用于配置写保护、读保护等功能,本章不作介绍了。

闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制结构。对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行。既在进行写或擦除操作时,不能进行代码或数据的读取操作。

型号

W55MH32L

W55MH32Q

Flash(KB)

1024

1024

SRAM(KB)

96

96

高级定时器

2

2

通用定时器

10

10

基本定时器

2

2

SPI

2

2

I2C

2

2

USART/UART

5

3

USB

1

1

CAN

1

1

SDIO

1

-

Ethernet

1

1

GPIO 端口

66

36

12 位 ADC(通道数)

3(12 个通道)

3(12 个通道)

12 位 DAC(通道数)

2(2 个通道)

2(2 个通道)

随机数模块

支持

支持

硬件加密算法单元

支持

支持

页大小(K 字节)

4

4

CPU 频率

216M

216M

工作电压

2.0 - 3.6V

2.0 - 3.6V

工作温度

-40 - +85℃

-40 - +85℃

2 闪存的读取

内置闪存模块可以在通用地址空间直接寻址,任何 32 位数据的读操作都能访问闪存模块的内容并得到相应的数据。读接口在闪存端包含一个读控制器,还包含一个 AHB 接口与 CPU衔接。这个接口的主要工作是产生读内存的控制信号并预取 CPU 要求的指令块,预取指令块仅用于在 I-Code 总线上的取指操作,数据常量是通过 D-Code 总线访问的。这两条总线的访问目标是相同的闪存模块,访问 D-Code 将比预取指令优先级高。

这里要特别留意一个闪存等待时间,因为 CPU 运行速度比 FLASH 快得多,W55MH32的 FLASH 最快访问速度≤24Mhz,如果 CPU 频率超过这个速度,那么必须加入等待时间,比如我们一般使用 72Mhz 的主频,那么 FLASH 等待周期就必须设置为 2,该设置通过FLASH_ACR 寄存器设置。

例如,我们要从地址 addr,读取一个半字(半字为 16 位,字为 32 位),可以通过如下的

语句读取:

data = *(vu16*)addr;

将 addr 强制转换为 vu16 指针,然后取该指针所指向的地址的值,即得到了 addr 地址的值。类似的,将上面的 vu16 改为 vu8,即可读取指定地址的一个字节。相对 FLASH 读取来说,W55MH32 FLASH 的写就复杂一点了。

3 闪存的编程和擦除

W55MH32 的闪存编程是由 FPEC(闪存编程和擦除控制器)模块处理的,这个模块包含 7 个 32位寄存器,它们分别是:

  • FPEC 键寄存器(FLASH_KEYR)
  • 选择字节键寄存器(FLASH_OPTKEYR)
  • 闪存控制寄存器(FLASH_CR)
  • 闪存状态寄存器(FLASH_SR)
  • 闪存地址寄存器(FLASH_AR)
  • 选择字节寄存器(FLASH_WRPR)

其中 FPEC 键寄存器总共有 3 个键值:

  • RDPRT 键 = 0X0000 00A5
  • KEY1 = 0X4567 0123
  • KEY2 = 0XCDEF 89AB

W55MH32 复位后,FPEC 模块是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列到 FLASH_KEYR 寄存器可以打开 FPEC 模块(即写入 KEY1 和 KEY2),只有在写保护被解除后,我们才能操作相关寄存器。

W55MH32 闪存的编程每次必须写入 16 位(不能单纯的写入 8 位数据),当 FLASH_CR 寄存器的 PG 位为‘1’时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据,FPEC 都会产生总线错误。在编程过程中(BSY 位为’1’),任何读写内存的操作都会使CPU 暂停,直到此次闪存编程结束。同样,W55MH32 的 FLASH 在编程的时候,也必须要求其写入地址的 FLASH 是被擦除了的(其值必须是 0xFFFF),否则无法写入,在 FLASH_SR 寄存器的 PGERR 位将得到一个警告。W55MH32 的 FLASH 编程过程如图所示:

闪存编程过程

从上图可以得到闪存的编程顺序如下:

1)检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁

2)检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的编程操作

3)设置 FLASH_CR 寄存器的 PG 位为‘1’

4)在指定的地址写入要编程的半字

5)等待 BSY 位变为‘0’

6)读出写入地址并验证数据

前面提到,我们在 W55MH32 的 FLASH 编程的时候,要先判断缩写地址是否被擦出了,所以,我们有必要再介绍一下 W55MH32 的闪存擦除,W55MH32 的闪存擦除分为两种:页擦除和整片擦除。页擦除过程如图 所示:

闪存页擦除过程

从上图可以看出,W55MH32 的页擦除顺序为:

1)检查 FLASH_CR 和 LOCK 是否解锁,如果没有则先解锁

2)检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的闪存操作

3)设置 FLASH_CR 寄存器的 PER 位为‘1’

4)用 FLASH_AR 寄存器选择要擦除的页

5)设置 FLASH_CR 寄存器的 STRT 位为‘1’

6)等待 BSY 位变为‘0’

7)读出被擦除的页并做验证

本章我们只用到了 W55MH32 页擦除功能,整片擦除功能我们在这里就不介绍了。

4 FLASH 寄存器

通过上面的讲解,我们基本对 W55MH32 闪存的读写执行步骤有所了解。接下来,我们介绍本实验需要用到的一些 FLASH 寄存器。

4.1 FPEC 键寄存器(FLASH_KEYR)

FPEC 键寄存器描述如图 所示:

FLASH_KEYR 寄存器

该寄存器主要用来解锁 FPEC,必须在该寄存器写入特定的序列(KEY1 和 KEY2)解锁后,才能对 FLASH_CR 寄存器进行写操作。

4.2 FLASH 控制寄存器(FLASH_CR)

FLASH 控制寄存器描述如图所示:

图 FLASH_CR 寄存器

该寄存器我们本章只用到了它的 LOCK、STRT、PER 和 PG 等 4 个位。LOCK 位,该位用于指示 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。

STRT 位,该位用于开始一次擦除操作。在该位写入 1,将执行一次擦除操作。

PER 位,该位用于选择页擦除操作,在页擦除的时候,需要将该位置 1。

PG 位,该位用于选择编程操作,在往 FLASH 写数据的时候,该位需要置 1。

其他位,我们就不在这里介绍了,请大家参考《W55MH32xxx参考手册》。

4.3 闪存状态寄存器(FLASH_SR)

闪存状态寄存器描述如图所示:

图 FLASH_SR 寄存器

该寄存器主要用来指示当前 FPEC 的操作编程状态。由于寄存器中描述比较详细,这里就不重复了。

4.4 闪存地址寄存器(FLASH_AR)

闪存地址寄存器描述如图所示:

图FLASH_AR 寄存器

该寄存器在本章,我们主要用来设置要擦除的页。关 于 W55MH32 FLASH 的 介 绍 , 我 们 就 介 绍 到 这 里 。 更 详 细 的 介 绍 , 可 以 参 考《W55MH32参考手册》。

5 例程设计

5.1 FLASH_Eeprom

该通过例程串口输出系统时钟信息和测试提示,在无限循环里不断对 WIZnet FLASH 进行读写操作,并且把读取的数据通过串口输出,以此实现对 FLASH 的读写测试。

5.1 函数声明

声明了UART_Configuration()函数,其功能是配置串口参数。

5.2 常量与数组定义

定义了一个常量字符串数组TEXT_Buffer,内容为"WIZnet FLASH TEST"。

借助SIZE宏定义获取数组长度。

设定了FLASH_SAVE_ADDR常量,此为 FLASH 的保存地址。

5.3 主函数main()

int main(void)
{
    uint8_t datatemp[SIZE], i;

    RCC_ClocksTypeDef clocks;
    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);
    printf("FLASH EEPROM Tset.\n");

    while (1)
    {
        WIZFLASH_Write(FLASH_SAVE_ADDR, (u16 *)TEXT_Buffer, SIZE);
        WIZFLASH_Read(FLASH_SAVE_ADDR, (u16 *)datatemp, SIZE);
        for (i = 0; i < SIZE; i++)
        {
            printf("%s\n", datatemp);
        }

        memset(datatemp, 0x00, sizeof(datatemp));
        delay_ms(1000);
    }
}

定义了一个用于存储从 FLASH 读取数据的数组datatemp,以及一个循环变量i。

初始化延迟函数、串口配置,并且获取系统时钟频率。

通过printf()函数输出系统时钟频率信息以及测试提示信息。

进入一个无限循环:调用WIZFLASH_Write()函数把TEXT_Buffer数组内容写入到指定的 FLASH 地址。

  • 调用WIZFLASH_Read()函数从该地址读取数据到datatemp数组。
  • 运用for循环遍历datatemp数组,然后通过printf()函数输出数组内容。
  • 调用memset()函数把datatemp数组清零。
  • 调用delay_ms()函数延迟 1 秒。

5.4 串口配置函数UART_Coniguration()

void UART_Configuration(uint32_t bound)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate            = bound;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART_TEST, &USART_InitStructure);
    USART_Cmd(USART_TEST, ENABLE);
}

对 GPIO 和 USART 的初始化结构体进行定义。

使能 USART1 和 GPIOA 的时钟。

配置 GPIOA 的引脚 9 为复用推挽输出模式,引脚 10 为浮空输入模式。

对 USART 的波特率、数据位、停止位、奇偶校验等参数进行配置。

初始化 USART 并使能。

5.5 字符发送函数SER_PutChar()

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);

    return ch;
}

等待串口发送完成标志位USART_FLAG_TC置位。

把字符发送到串口。

5.6 重定向函数fputc()

若要发送的字符是换行符\n,则先发送回车符\r。

调用SER_PutChar()函数发送字符。

6 下载验证

6.1 FLASH_Eeprom

进入主循环后,程序会不断执行以下操作:

  • 把TEXT_Buffer数组中的字符串 "WIZnet FLASH TEST" 写入到指定的 FLASH 地址。
  • 从该 FLASH 地址读取数据到datatemp数组。
  • 借助printf()函数将datatemp数组的内容通过串口输出。

由于for循环的存在,每次读取到的数据会被重复输出SIZE次。不过,原代码里printf("%s\n", datatemp);存在错误,它会把整个datatemp数组当作字符串输出,而不是逐个字符输出。正确的做法应该是printf("%c", datatemp[i]);。修正后,串口会每隔 1 秒输出一次 "WIZnet FLASH TEST"。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值