• STM32基于Flash对结构体读写暨再认识结构体内存分配


    目录

     前言

    认识结构体内存分布

    模拟结构体搬运

    实现FLASH的结构体读写


     前言

            记录本篇主要是记录我进行stm32学习中利用stm32的flash(闪存读写结构体的总结。

    认识结构体内存分布


            结构体,主要要来存放我们的自定义类型的数据,结构体可以再次嵌套结构体不断套娃,从C++的角度上说,结构体内部还能嵌套别的类对象,以及结构体本身就是一个类,类方法和变量默认是public修饰。

            对于结构体的大小,也就是考虑其内部变量的对齐,对齐是指与有效对齐量对齐,有效对齐量是指变量或是结构体本身(结构体本身作为一个整体也要对齐)需要与自身对齐量与指定对齐量中较小的那个进行对齐。 

    所谓对齐就是就内存所在地址%其数据大小=0

    可写成以下公式

    有效对齐量=MIN(自身对齐量,指定对齐量)

    • 结构体的自身对齐量是其数据成员最大的自身对齐量!
    • 若不进行设定,默认的指定对齐量是4个字节(在stm32中)。
    • 若是结构体内部有数组,那么数组的自身对齐量就是其数据类型的自身对齐量。
    • 若结构体内部嵌套了一个结构体或是一个类对象,那么被嵌套的结构体或者是类对象是自身对齐量为其本身结构体的所占空间大小。
    • 数据类型在内存的存放次序是小端,其中最低位所在字节存放在较低的起始地址,最高位所在字节存放在较高的结束地址。
    1. typedef struct { //需要写入flash的信息
    2. u8 lightlimit;
    3. u8 CID[8];
    4. u8 UID[8];
    5. u16 poisition;
    6. } FlashInfo;
    7. int main() {
    8. u16 arr[5] = { 0 };
    9. M test = { 1,2,3 };
    10. FlashInfo q = { 0,"\0","\0",1 };
    11. printf("%d\r\n", sizeof(FlashInfo));
    12. }

    VS调试输出20 。 

             通过debug看内存:结构体的首地址为0x00d3fd5c,于是结构体自上而下第一个数据大小为一个字节,自身对齐量为1,VS默认指定对齐量为8,有效对齐值=MIN(1,8)=1,q.CID是一个u8类型的数组,其自身对齐值为数组成员的自身对齐值也就是1,所以有效对齐值为1,所以其内存分布在0x00d3fd5d-0x00d3fd64(共3+5=8个字节),同理q.UID分布在0x00d3fd65-0x00d3fd6c(8个字节),q.position的数据类型为u16占2个字节,其有效对齐量=MIN(2,8)=2个字节,而0x00d3fd6d%2!=0,所以偏移到0x00d3fd5c与其有效对齐量对齐。其内存分布为0x00d3fd6e-0x00d3fd6f(占2个字节),最终考虑结构体与其自身对齐,其有效对齐量=MIN(2(数据成员的最大自身对齐量,指定对齐量)=2, 内存分布为0x00d3fd5c到0x00d3fd6f,共20个字节,20%2=0,到此结构体不需要再往后偏移无用字节对齐。

    再打开VS的内存

    四种图案分别代表  lightlimit ,u8 CID[8] u8 UID[8] u16 poisition.箭头所指的CC为偏移的一个字节,CC是微软windows初始化时默认值可做守卫判断是否越界等等。

    在此更进一步,指定对齐值是可以自己设定的,

    #pragma pack(1)   // 1 bytes对齐

    若是这样设置,那么结构体的大小就是成员数据的大小之和。

    #pragma pack()  括号内空,默认指定对齐为8(vs中)

    模拟结构体搬运

            好了,到这认识到位了,接下来就可以试着操作flash,如何操作,试着把结构体当做一个数组,把结构体的地址映射成数组的地址(一个字节,或者是2个字节都可以),为此,我在真正操作Flash前用vs写个测试代码。

            定义一个结构体,把结构体的地址映射成一个u8 类型的数组的地址,把结构体的数据取出放入另一个准备好的u8 类型的数组,再把数组地址映射成结构体地址再输出.

    如下(设置数据和结构体的指定对齐量为1个字节,结构体大小就为子成员总和

    1. #include "stdio.h"
    2. typedef unsigned short u16;
    3. typedef unsigned char u8;
    4. #pragma pack(1)
    5. typedef struct {
    6. char a;
    7. int c;
    8. short b;
    9. } M;
    10. int main() {
    11. M test = { 1,2,3 };
    12. u8 arr[sizeof(M)] = { 0 };
    13. u8* p = (u8 *) & test;
    14. printf("结构体大小 %d", sizeof(M));
    15. for (int i = 0; i < sizeof(test); i++) {
    16. arr[i] = p[i];
    17. }
    18. M* test_arr = (M*)arr;//转换成结构体地址
    19. printf("[END}:%d %d %d\r\n", test_arr->a, test_arr->b, test_arr->c);
    20. return 0;
    21. }

    输出结果 

    在此,我使用的开发板正点原子stm32F103ZET6精英版的开发文档中有写到:

    于是把8位的u8,转换成u16,也就是一个字节到2个字节。

    我这里又用在vs测试一下,由于我指定对齐量为1会导致结构体大小可能会是奇数。平时使用不需要设置指定对齐量,默认的指定对齐量是偶数,刚好是u16,2个字节的倍数。

    1. #include "stdio.h"
    2. typedef unsigned short u16;
    3. typedef unsigned char u8;
    4. #pragma pack(1)
    5. typedef struct {
    6. char a;
    7. int c;
    8. short b;
    9. } M;
    10. int main() {
    11. M test = { 1,2,3 };
    12. u16 arr[sizeof(M)%2==0?sizeof(M)/2: sizeof(M) / 2+1] = {0};//因为指定对齐量是1个字节,结构体\
    13. 为奇数/2会模1,使得数组装不下。
    14. u16* p = (u16 *) & test;
    15. printf("结构体大小 %d", sizeof(M));
    16. for (int i = 0; i < sizeof(test)/2; i++) {
    17. arr[i] = p[i];
    18. }
    19. M* test_arr = (M*)arr;
    20. printf("[END}:%d %d %d\r\n", test_arr->a, test_arr->b, test_arr->c);
    21. return 0;
    22. }

    到这验证想法成功,试着操作STM32的FLASH。

    stm32中指定对齐量是4个字节。

    实现FLASH的结构体读写

    定义结构体

     typedef struct {  //需要写入flash的信息
          u8 lightlimit; //温度阈值
          u8 CID[8];  //设备编号
          u8 UID[8];   //所绑定的RF卡号
          u16 poisition;   //位置编号
              
    } FlashInfo;

    FlashInfo info={9,"123456","987654",2};  //这是需要写入结构体内部的数据

     读结构体。(需要注意每读一次,地址向上增长2个字节)

     void ReadMyStruct(void){
         
         u16 *p=(u16*)&info;
         
         
         STMFLASH_Read(FLASH_COMMON_ADDR,p,sizeof(info)/2);
         printf("Test:%d--%s--%s--%d\r\n",info.lightlimit,(char * )info.CID,(char *)info.UID,info.poisition);
         
         
     }

    void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)       
    {
        u16 i;
        for(i=0;i     {
            pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
            ReadAddr+=2;//偏移2个字节.    
        }
    }

    写结构体

    先擦除所在页再写数据

     static  void WriteMyStruct(void){
             u16 *p=(u16*)&info;
           
                HAL_FLASH_Unlock(); //FLASH解锁
                FLASH_PageErase(FLASH_COMMON_ADDR); //擦除页
                FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成
                CLEAR_BIT(FLASH->CR, FLASH_CR_PER);        //清除标记
                u32 addr=FLASH_COMMON_ADDR;
                for(int i=0;i             HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,addr,p[i]);//写一个字(32位)数据,若是一个半字则写半字16位     
                addr+=2;
                }
                            HAL_FLASH_Lock();//上锁
                
     
      
         
    }

  • 相关阅读:
    docker安装kafka并配置kafka可视化界面
    基于QuartusII9.1的ADC和DAC控制仿真设计
    JS系列2-怎么把一个对象当做数组使用
    如何发布一个属于自己的 npm 包
    java计算机毕业设计支部党建工作源码+数据库+系统+lw文档+mybatis+运行部署
    【JVM系列】- 类加载子系统与加载过程
    Spark On Yarn基本原理及部署
    PMP每日一练 | 考试不迷路-8.20(包含敏捷+多选)
    【Linux】:体系结构与进程概念
    基于FPGA的有限脉冲响应(FIR)数字滤波器设计与实现(使用Matlab)
  • 原文地址:https://blog.csdn.net/PHILICS7/article/details/127082670