第二十九章 读写内部FLASH

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机

W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。

在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。

W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入网站或者私信获取。

此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。

为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。

若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页,我们期待与您共同探索W55MH32的无限可能。

wKgZPGgbOfaANhwzACodXd3sVzg463.png

第二十九章 读写内部FLASH

1 W55MH32的内部FLASH简介

在W55MH32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中, 由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行, 见下图,W55MH32的内部框架图:

除了使用外部的工具(如下载器)读写内部FLASH外,W55MH32芯片在运行的时候,也能对自身的内部FLASH进行读写,因此, 若内部FLASH存储了应用程序后还有剩余的空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。

由于访问内部FLASH的速度要比外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭, 有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。

1.1 内部FLASH的构成

W55MH32的内部FLASH包含主存储器、系统存储器以及选项字节区域, 它们的地址分布及大小见下表,W55MH32大容量产品内部FLASH的构成:

模块 名称 地址 大小
主存储块 页 0 0x0800 0000 - 0x0800 07FF 2K
页 1 0x0800 0800 - 0x0800 0FFF 2K
页 2 0x0800 1000 - 0x0800 17FF 2K
页 3 0x0800 1800 - 0x0800 1FFF 2K
页 255 0x0807 F800 - 0x0807 FFFF 2K
信息块 系统存储器 0x1FFF F000 - 0x1FFF F7FF 2K
选择字节 0x1FFF F000 - 0x1FFF F7FF 16
闪存存储器接口寄存器 FLASH_ACR 0x4002 2000 - 0x4002 2003 4
FALSH_KEYR 0x4002 2004 - 0x4002 2007 4
FLASH_OPTKEYR 0x4002 2008 - 0x4002 200B 4
FLASH_SR 0x4002 200C - 0x4002 200F 4
FLASH_CR 0x4002 2010 - 0x4002 2013 4
FLASH_AR 0x4002 2014 - 0x4002 2017 4
保留 0x4002 2018 - 0x4002 201F 4
FLASH_OBR 0x4002 201C - 0x4002 201F 4
FLASH_WRPR 0x4002 2020 - 0x4002 2023 4

主存储

型号 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℃

系统存储区

系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。

选项字节

选项字节用于配置FLASH的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共16字节。可以通过修改FLASH的选项控制寄存器修改。

2 对内部FLASH的写入过程

2.1 解锁

由于内部FLASH空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器FLASH_CR上锁, 这个时候不允许设置FLASH的控制寄存器,从而不能修改FLASH中的内容。

2.2 页擦除

在写入新的数据前,需要先擦除存储区域,W55MH32提供了页(扇区)擦除指令和整个FLASH擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。

页擦除的过程如下:

检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;

在 FLASH_CR 寄存器中,将“激活页擦除寄存器位PER ”置 1;

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

将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;

等待 BSY 位被清零时,表示擦除完成。

2.3 写入数据

擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下:

检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;

将 FLASH_CR 寄存器中的 “激活编程寄存器位PG” 置 1;

向指定的FLASH存储器地址执行数据写入操作,每次只能以16位的方式写入;

等待 BSY 位被清零时,表示写入完成。

3 查看工程的空间分布

由于内部FLASH本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容, 所以在使用内部FLASH存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。 通过查询应用程序编译时产生的“*.map”后缀文件,可以了解程序存储到了哪些区域, 它在工程中的打开方式见下图,打开工程的map文件 , 也可以到工程目录中的“Listing”文件夹中找到,关于map文件的详细说明可参考《MDK的编译过程及文件类型》章节。

wKgZPGgxkfmAXuU6AAHJXtT-GG4757.png

3.1 程序ROM的加载与执行空间

上述说明中有两段分别以“Load Region LR_ROM1”及“Execution Region ER_IROM1”开头的内容, 它们分别描述程序的加载及执行空间。 在芯片刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从ROM复制到RAM空间, 以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。

在上面map文件的描述中,我们了解到加载及执行空间的基地址(Base)都是0x08000000,它正好是W55MH32内部FLASH的首地址, 即W55MH32的程序存储空间就直接是执行空间;它们的大小(Size)分别为0x000017a8及0x0000177c, 执行空间的ROM比较小的原因就是因为部分RW-data类型的变量被拷贝到RAM空间了; 它们的最大空间(Max)均为0x00080000,即512K字节,它指的是内部FLASH的最大空间。

计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应用程序使用的内部FLASH是从0x08000000至(0x08000000+0x000017a8)地址的空间区域。

3.2 ROM空间分布表

在加载及执行空间总体描述之后,紧接着一个ROM详细地址分布表,它列出了工程中的各个段(如函数、常量数据)所在的地址BaseAddr及占用的空间Size, 列表中的Type说明了该段的类型,CODE表示代码,DATA表示数据,而PAD表示段之间的填充区域,它是无效的内容, PAD区域往往是为了解决地址对齐的问题。

观察表中的最后一项,它的基地址是0x0800175c,大小为0x00000020,可知它占用的最高的地址空间为0x0800177c,跟执行区域的最高地址0x0000177c一样, 但它们比加载区域说明中的最高地址0x80017a8要小,所以我们以加载区域的大小为准。 对比表 W55MH32大容量产品内部FLASH的构成 的内部FLASH页地址分布表, 可知仅使用页0至页2就可以完全存储本应用程序,所以从页3(地址0x08001800)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。

4 操作内部FLASH的库函数

为简化编程,W55MH32标准库提供了一些库函数,它们封装了对内部FLASH写入数据操作寄存器的过程。

4.1 FLASH解锁、上锁函数

对内部FLASH解锁、上锁的函数见代码清单:FLASH-2:

代码清单:FLASH-2 FLASH解锁、上锁

#define FLASH_KEY1               ((uint32_t)0x45670123)
#define FLASH_KEY2               ((uint32_t)0xCDEF89AB)
/**
* @brief  对FLASH控制寄存器解锁,使能访问
* @param  None
* @retval None
*/
void FLASH_Unlock(void)
{
    if ((FLASH->CR & FLASH_CR_LOCK) != RESET) {
        /* 写入确认验证码 */
        FLASH->KEYR = FLASH_KEY1;
        FLASH->KEYR = FLASH_KEY2;
    }
}

/**
* @brief  对FLASH控制寄存器上锁,禁止访问
* @param  None
* @retval None
*/
void FLASH_Lock(void)
{
    /* 设置FLASH寄存器的LOCK位 */
    FLASH->CR |= FLASH_CR_LOCK;
}

解锁的时候,它对FLASH_KEYR寄存器写入两个解锁参数,上锁的时候,对FLASH_CR寄存器的FLASH_CR_LOCK位置1。

4.2 设置操作位数及页擦除

解锁后擦除扇区时可调用FLASH_EraseSector完成,见代码清单:FLASH-3:

代码清单:FLASH-3 擦除扇区

/**
* @brief  擦除指定的页
* @param  Page_Address: 要擦除的页地址.
* @retval FLASH Status:
            可能的返回值: FLASH_BUSY, FLASH_ERROR_PG,
*           FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
{
    FLASH_Status status = FLASH_COMPLETE;
    /* 检查参数 */
    assert_param(IS_FLASH_ADDRESS(Page_Address));
    /*...此处省略XL超大容量芯片的控制部分*/
    /* 等待上一次操作完成 */
    status = FLASH_WaitForLastOperation(EraseTimeout);

    if (status == FLASH_COMPLETE) {
        /* 若上次操作完成,则开始页擦除 */
        FLASH->CR|= CR_PER_Set;
        FLASH->AR = Page_Address;
        FLASH->CR|= CR_STRT_Set;

        /* 等待操作完成 */
        status = FLASH_WaitForLastOperation(EraseTimeout);

        /* 复位 PER 位 */
        FLASH->CR &= CR_PER_Reset;
    }

    /* 返回擦除结果 */
    return status;
}

本函数包含一个输入参数用于设置要擦除的页地址,即目标页的在内部FALSH的首地址,函数获取地址后,根据前面的流程检查状态位、 向控制寄存器FLASH_CR及地址寄存器FLASH_AR写入参数,配置开始擦除后,需要等待一段时间,函数中使用使用FLASH_WaitForLastOperation()等待, 擦除完成的时候才会退出FLASH_EraseSector()函数。

4.3 写入数据

对内部FLASH写入数据不像对SDRAM操作那样直接指针操作就完成了,还要设置一系列的寄存器, 利用FLASH_ProgramWord()和FLASH_ProgramHalfWord()函数可按字、半字的单位单位写入数据, 见代码清单:FLASH-4:

代码清单:FLASH-4 写入数据

/**
* @brief  向指定的地址写入一个字的数据(32位)
* @param  Address: 要写入的地址
* @param  Data: 要写入的数据
* @retval FLASH Status:
        可能的返回值: FLASH_ERROR_PG,
*           FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
    FLASH_Status status = FLASH_COMPLETE;
    __IO uint32_t tmp = 0;

    /* 检查参数 */
    assert_param(IS_FLASH_ADDRESS(Address));
    /*...此处省略XL超大容量芯片的控制部分*/
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(ProgramTimeout);

    if (status == FLASH_COMPLETE) {
        /* 若上次操作完成,则开始页入低16位的数据(输入参数的第1部分) */
        FLASH->CR |= CR_PG_Set;

        *(__IO uint16_t*)Address = (uint16_t)Data;
        /* 等待上一次操作完成 */
        status = FLASH_WaitForLastOperation(ProgramTimeout);

        if (status == FLASH_COMPLETE) {
            /* 若上次操作完成,则开始页入高16位的数据(输入参数的第2部分) */
            tmp = Address + 2;

            *(__IO uint16_t*) tmp = Data >> 16;

            /* 等待操作完成 */
            status = FLASH_WaitForLastOperation(ProgramTimeout);

            /* 复位 PG 位  */
            FLASH->CR &= CR_PG_Reset;
        } else {
            /* 复位 PG 位 */
            FLASH->CR &= CR_PG_Reset;
        }
    }

    /* 返回写入结果 */
    return status;
}

从函数代码可了解到,它设置FLASH->CR 寄存器的PG位允许写入后,使用16位的指针往指定的地址写入数据,由于每次只能按16位写入, 所以这个按字写入的过程使用了两次指针赋值,分别写入指定数据的低16位和高16位,每次赋值操作后,调用FLASH_WaitForLastOperation()函数等待写操作完毕。 标准库里还提供了FLASH_ProgramHalfWord()函数用于每次写入半个字,即16位,该函数内部的执行过程类似。

5 读写内部FLASH

5.1 代码分析

1.宏定义

#define FLASH_TEST_ADDR (0x0800F400) // 测试用Flash地址(32KB处)

地址选择:需确保该地址未被程序代码占用,且属于可擦写区域。

2.主函数逻辑

int main(void)
{
    // 初始化系统时钟、串口等
    delay_init();
    UART_Configuration(115200);
    printf("系统时钟信息...");

    while (1)
    {
        FLASH_EraseProgram(number++); // 执行擦除编程测试
        if (number == 0xFFFFFFFF)    // 防止溢出,结束测试
        {
            printf("Test Endn");
            while (1);
        }
    }
}

无限循环:持续测试,直到number溢出。

3.FLASH 擦除与编程函数

void FLASH_EraseProgram(uint32_t number)
{
    // 1. 擦除页测试
    SYSTICK_Reset();
    FLASH_ErasePage(FLASH_TEST_ADDR);
    if (擦除成功)
        printf("擦除时间:%d msn", 计算时间);
    else
        进入死循环;

    // 2. 编程半字测试
    SYSTICK_Reset();
    for (i = 0; i < 512; i++)
        FLASH_ProgramHalfWord(地址+i*2, 0x5A5A);
    if (编程成功)
        printf("编程时间:%d msn", 计算时间);
    else
        进入死循环;
}

擦除操作:使用FLASH_ErasePage擦除整页(通常为 2KB)。

编程操作:逐个半字写入0x5A5A,验证写入正确性。

4.时间测量函数

void SYSTICK_Reset(void)
{
    SysTick->CTRL = 0;          // 停止定时器
    SysTick->LOAD = 0xFFFFFF;   // 最大计数值
    SysTick->VAL = 0;           // 清零当前值
    SysTick->CTRL = 0x05;       // 使能定时器,选择HCLK时钟
}

时间计算:通过0xFFFFFF - SysTick->VAL得到操作耗时(单位:时钟周期)。

5. 串口配置函数

void UART_Configuration(uint32_t bound)
{
    // 使能USART1和GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // 配置TX引脚(PA9)为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 初始化USART1参数(波特率、数据位等)
    USART_Init(USART_TEST, &USART_InitStructure);
    USART_Cmd(USART_TEST, ENABLE);
}

功能:配置 USART1 为 115200 波特率,用于调试输出。

5.2 下载验证

6 FLASH_Eeprom

6.1 代码分析

1.主函数 main()

int main(void) {
    uint8_t datatemp[SIZE], i;
    delay_init();
    UART_Configuration(115200);
    // 打印系统时钟信息
    RCC_GetClocksFreq(&clocks);
    printf("SYSCLK: %3.1fMhz...n", ...);
    printf("FLASH EEPROM Tset.n");
    // 循环读写FLASH
    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("%sn", datatemp);         }
        memset(datatemp, 0x00, sizeof(datatemp)); // 清空数组
        delay_ms(1000); // 延时
    }
}

2.串口配置 UART_Configuration

void UART_Configuration(uint32_t bound) {
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    // 配置串口引脚
    GPIO_Init(GPIOA, &GPIO_InitStructure); // PA9(发送)复用推挽,PA10(接收)浮空输入
    // 配置串口参数:115200波特率、8位数据等
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE); // 使能串口
}

配置 USART1,用于输出调试信息。

3.串口重定向

int SER_PutChar(int ch) {
    while (!USART_GetFlagStatus(USART1, USART_FLAG_TC)); // 等待发送完成
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}
int fputc(int c, FILE *f) {
    if (c == 'n') SER_PutChar('r'); // 换行处理
    return SER_PutChar(c); // 实现printf通过串口输出
}

4. FLASH 操作

依赖eeprom.h的WIZFLASH_Write和WIZFLASH_Read,需确保其实现 FLASH 底层操作(擦除、读写等)。

地址:FLASH_SAVE_ADDR 0X08008000,需在可用 FLASH 空间内。

6.2 下载验证

wKgZO2gxkfmAQcJlAACl42trMJs297.png

为您推荐

第十九章 ADC——电压采集

第十九章 ADC——电压采集

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机 W55MH32是WIZnet重磅推出的高性能以...

第十八章 I2C通信测试

第十八章 I2C通信测试

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机 W55MH32是WIZnet重磅推出的高性能以...

零碳园区建设的突破之路:智慧能源管理解决方案引领未来

零碳园区建设的突破之路:智慧能源管理解决方案引领未来

中国超过80%的工业企业已集中在园区,园区工业总产值平均占到全国的50%,碳排放量占全国的31%。2024年中央经济工...

2025-06-25 标签:能源管理能源碳排放阿里
CMOS的逻辑门如何应用在电路中

CMOS的逻辑门如何应用在电路中

CMOS的逻辑门如何应用在电路中 前言 在如今的电子电路中,CMOS逻辑门有着接近零静态功耗和超高集成度的特点...

2025-06-25 标签:逻辑门cmos电路光伏热点
替代自建物联平台的最优解?揭秘Tuya物联网平台阿里云版的全托管力量

替代自建物联平台的最优解?揭秘Tuya物联网平台阿里云版的全托管力量

在全球智能化浪潮持续演进的当下,企业对AIoT平台的需求已经从“能连接”转向“高可靠、高性能、全球化”。与此同时,企业在...

当前非电脑浏览器正常宽度,请使用移动设备访问本站!