1. 程序存储器(Flash)的读取与校验

程序存储器(Flash)的读取与校验 学习卡片

任务说明

本案例面向8051内核单片机(以STC89C52为例),要求编写程序读取芯片内部程序存储器(Flash)中已烧录的代码内容,逐字节累加计算校验和,并将校验结果输出到串口,用于验证程序在烧录后是否完整、未被篡改或损坏。

实现原理

8051单片机采用哈佛架构,程序存储器和数据存储器独立编址。读取程序存储器中代码字节不能通过普通数据指针(DPTR)直接完成,必须使用程序存储器读指令MOVC A, @A+DPTR。该指令将DPTR指向的Flash地址内容送入累加器A。通过循环递增DPTR并累加读取到的每个字节,即可计算整个程序空间的校验和。本案例将读取从地址0x0000开始、长度为CODE_LEN字节的Flash区域,累加结果通过串口以十六进制格式输出。

代码实现


#include <reg52.h>

#define CODE_LEN  0x1000   // 读取4KB程序空间(可根据实际固件大小调整)
#define BUF_LEN   8        // 串口发送缓冲区长度

typedef unsigned char u8;
typedef unsigned int  u16;

// 串口初始化:9600bps,晶振11.0592MHz
void UART_Init(void) {
    SCON = 0x50;           // 模式1,8位UART,允许接收
    TMOD = 0x20;           // 定时器1工作在模式2(8位自动重装)
    TH1  = 0xFD;           // 波特率9600
    TL1  = 0xFD;
    TR1  = 1;              // 启动定时器1
    ES   = 0;              // 不使用中断,采用查询发送
}

// 串口发送单字节
void UART_SendByte(u8 dat) {
    SBUF = dat;
    while (!TI);           // 等待发送完成
    TI = 0;
}

// 将16位校验和按十六进制格式发送(4位)
void UART_SendHex(u16 val) {
    u8 nibble;
    u8 i;
    u8 code hex_tab[16] = {'0','1','2','3','4','5','6','7',
                           '8','9','A','B','C','D','E','F'};
    for (i = 0; i < 4; i++) {
        nibble = (val >> (12 - i*4)) & 0x0F;  // 从高位到低位取半字节
        UART_SendByte(hex_tab[nibble]);
    }
}

// 读取Flash并计算校验和
u16 Flash_Checksum(void) {
    u16 sum = 0;
    u16 i;
    u8  code *ptr;         // code指针指向程序存储器

    ptr = (u8 code *)0x0000;   // 从Flash起始地址开始
    for (i = 0; i < CODE_LEN; i++) {
        sum += *ptr;           // 编译器自动生成MOVC指令读取Flash
        ptr++;
    }
    return sum;
}

void main(void) {
    u16 checksum;

    UART_Init();
    checksum = Flash_Checksum();

    // 发送提示符
    UART_SendByte('C');
    UART_SendByte('S');
    UART_SendByte(':');
    UART_SendHex(checksum);
    UART_SendByte('\r');
    UART_SendByte('\n');

    while (1);             // 任务完成,停留
}

关键说明

代码中u8 code *ptr声明了一个指向程序存储器的指针,编译器在解引用该指针时会自动生成MOVC A, @A+DPTR指令,从而正确读取Flash中的代码字节。累加使用16位无符号整数,若总字节数超过65535则自然溢出,但本案例读取4KB空间远小于该上限,不存在溢出问题。串口输出格式为CS:XXXX,其中XXXX为4位十六进制校验和。

验证方法

验证步骤:

  1. 使用Keil C51编译本代码,生成HEX文件;
  2. 将HEX文件烧录到STC89C52单片机(烧录时选择11.0592MHz晶振);
  3. 用USB转TTL模块连接单片机串口(P3.0-RXD, P3.1-TXD, GND共地);
  4. 打开串口调试助手,设置波特率9600、8数据位、1停止位、无校验;
  5. 复位单片机,观察串口助手接收窗口。

判定标准:

  • 串口能收到以CS:开头、后跟4位十六进制数字的字符串;
  • 将本代码编译生成的HEX文件用文本方式打开,对HEX文件中所有数据记录(以:10开头的数据行)的十六进制数据字节进行累加(不含行号、长度、地址和校验字节),所得累加值低16位应与串口输出的校验和一致;
  • 若人为修改HEX文件中任意一个数据字节后重新烧录,串口输出的校验和应发生变化,证明程序能有效检测Flash内容变更。
Flash读取校验流程与数据通路 程序存储器 (Flash) 地址范围: 0x0000~0x0FFF 0x0000: 0x02 0x00 0x30 ... 0x0010: 0x75 0x81 0x52 ... 0x0020: 0x12 0x34 0x56 ... ... ... ... ... MOVC @A+DPTR 8051 CPU DPTR 逐字节递增 累加器A = Flash[DPTR] 逐字节 累加 校验和累加器 sum += byte 16位无符号 串口输出 CS:3A7F 验证方法:将HEX文件数据字节累加,低16位与串口输出比对 一致 → 程序完整;不一致 → Flash内容已变化

落地与运行

所需工具:Keil C51编译器、STC-ISP烧录软件、STC89C52开发板或最小系统板、USB转TTL串口模块。

操作步骤:

  1. 在Keil中新建工程,选择芯片型号STC89C52RC,将上述代码粘贴到main.c并加入工程;
  2. 编译生成HEX文件(需勾选"Create HEX File"选项);
  3. 使用STC-ISP软件将HEX烧录到单片机;
  4. 连接串口,打开串口助手观察输出。

注意事项:若实际固件大小不足4KB,可减小CODE_LEN至实际代码长度(如0x0800对应2KB),但需保证该长度覆盖全部有效代码区域。读取未烧录的空白Flash区域(值为0xFF)也会计入校验和,因此CODE_LEN应精确对应实际代码占用空间,否则校验和会包含空白区数据。