利用MDK的FLM文件生成通用flash驱动_keil flm

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

  1. Init
    函数Init为 Flash 编程初始化微控制器。每当尝试将程序下载到 Flash 时都会调用它。
int Init (unsigned long adr, unsigned long clk, unsigned long fnc);

参数adr指定设备的基址。

参数clk指定用于编程设备的时钟频率。

参数fnc是一个数字:

1 代表擦除。
2代表程序。
3代表验证。

Code Example:

int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
 
  // Zero Wait State
  FLASH->ACR  = 0x00000000;
 
  // Unlock Flash
  FLASH->KEYR = FLASH_KEY1;
  FLASH->KEYR = FLASH_KEY2;
 
  // Test if IWDG is running (IWDG in HW mode)
  if ((FLASH->OBR & 0x04) == 0x00) {
    // Set IWDG time out to ~32.768 second
    IWDG->KR  = 0x5555; // Enable write access to IWDG\_PR and IWDG\_RLR
    IWDG->PR  = 0x06;   // Set prescaler to 256
    IWDG->RLR = 4095;   // Set reload value to 4095
  }
 
  return (0);
}

  1. ProgramPage
    函数ProgramPage用于将代码写入闪存。它被调用以将程序下载到 Flash。由于flash通常以块或页的形式组织,因此函数ProgramPage的参数不得跨越这些闪存页的对齐边界。页面大小在结构 FlashDevice 中指定,值为Program Page Size。
int ProgramPage ( unsigned  long adr, unsigned  long sz, unsigned  char \*buf);

参数adr指定要编程的页面的起始地址。它由主机编程系统与flash页面的起始地址对齐。

参数sz指定数据缓冲区中的数据大小。主机编程系统确保不跨越页面边界。

参数buf指向包含要编程的数据的数据缓冲区。

Code Example:

int ProgramPage (unsigned long adr, unsigned long sz, unsigned char \*buf) {
 
  sz = (sz + 1) & ~1;                           // Adjust size for Half Words
 
  while (sz) {
 
    FLASH->CR |=  FLASH_PG;                     // Programming Enabled
 
    M16(adr) = \*((unsigned short \*)buf);        // Program Half Word
    while (FLASH->SR & FLASH_BSY);
 
    FLASH->CR &= ~FLASH_PG;                     // Programming Disabled
 
    // Check for Errors
    if (FLASH->SR & (FLASH_PGERR | FLASH_WRPRTERR)) {
      FLASH->SR |= FLASH_PGERR | FLASH_WRPRTERR;
      return (1);                               // Failed
    }
 
    // Go to next Half Word
    adr += 2;
    buf += 2;
    sz  -= 2;
  }
 
  return (0);                                   // Done
}

  1. EraseSector
    函数EraseSector删除从参数adr指定的地址开始的扇区内容。
int EraseSector (unsigned long adr);

参数adr扇区地址

Code Example:

int EraseSector (unsigned long adr) {
 
  FLASH->CR |=  FLASH_PER;                      // Page Erase Enabled
  FLASH->AR  =  adr;                            // Page Address
  FLASH->CR |=  FLASH_STRT;                     // Start Erase
 
  while (FLASH->SR & FLASH_BSY) {
    IWDG->KR = 0xAAAA;                          // Reload IWDG
  }
 
  FLASH->CR &= ~FLASH_PER;                      // Page Erase Disabled
 
  return (0);                                   // Done
}

  1. UnInit
    函数UnInit取消初始化微控制器,并在擦除、编程或验证步骤结束时调用。
int UnInit (unsigned long fnc);

Code Example

int UnInit (unsigned long fnc) {
 
  // Lock Flash
  FLASH->CR |=  FLASH_LOCK;
 
  return (0);
}

2.FlashPrg.c

文件FlashDev.c包含以下参数定义:

  1. Flash编程功能。
  2. FlashDevice结构:
struct FlashDevice const FlashDevice  =  {
   FLASH_DRV_VERS,              // 驱动版本,请勿修改!
   "New Device 256kB Flash" ,    // 设备名称
   ONCHIP,                      // 设备类型
   0x00000000,                  // 设备起始地址
   0x00040000,                  // 以字节为单位的设备大小 (256kB)
   1024,                        // 编程页面大小
   0,                           // 保留,必须为0
   0xFF,                        // 已擦除内存的初始内容
   100,                         // 程序页面超时 100 毫秒
   3000,                        // 擦除扇区超时 3000 毫秒
 
// 指定扇区的大小和地址
   0x002000, 0x000000,          // 扇区大小 8kB(8 个扇区)
   0x010000, 0x010000,          // 扇区大小 64kB(2 个扇区)
   0x002000, 0x030000,          // 扇区大小 8kB(8 个扇区)
   SECTOR_END
};

Device Name通常显示在工具中,用于识别 Flash 算法。确保此名称反映设备名称。
编程页面大小指定使用函数ProgramPage进行编程 的块大小。对于块大小较小的设备,最好指定物理块大小的倍数,因为这可以减少与目标的通信开销。快速编程的最佳块大小为 1024 字节,但系统本身并不限制此大小值。

三、解析FLM文件

1.解析flm文件

下面让我们解析一下现有的FLM文件,以STM32F4xx_1024.FLM为例:
将ARM:CMSIS Pack文件夹(通常在D:\Users\Administrator\AppData\Local\Arm\Packs\Keil\STM32F4xx_DFP\2.15.0\CMSIS\Flash)中的内容复制到一个新文件夹中。

打开命令行工具,输入arm-none-eabi-readelf -a STM32F4xx_1024.FLM:

$ arm-none-eabi-readelf -a STM32F4xx_1024.FLM
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          12172 (bytes into file)
  Start of section headers:          12236 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         16
  Section header string table index: 15

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] PrgCode           PROGBITS        00000000 000034 000144 00  AX  0   0  4
  [ 2] PrgData           PROGBITS        00000144 000178 000004 00  WA  0   0  4
  [ 3] DevDscr           PROGBITS        00000148 00017c 0010a0 00   A  0   0  4
  [ 4] .debug_abbrev     PROGBITS        00000000 00121c 0005a4 00      0   0  1
  [ 5] .debug_frame      PROGBITS        00000000 0017c0 000104 00      0   0  1
  [ 6] .debug_info       PROGBITS        00000000 0018c4 00064c 00      0   0  1
  [ 7] .debug_line       PROGBITS        00000000 001f10 000218 00      0   0  1
  [ 8] .debug_loc        PROGBITS        00000000 002128 0001b8 00      0   0  1
  [ 9] .debug_macinfo    PROGBITS        00000000 0022e0 000614 00      0   0  1
  [10] .debug_pubnames   PROGBITS        00000000 0028f4 000096 00      0   0  1
  [11] .symtab           SYMTAB          00000000 00298c 000110 10     12   9  4
  [12] .strtab           STRTAB          00000000 002a9c 000100 00      0   0  1
  [13] .note             NOTE            00000000 002b9c 00001c 00      0   0  4
  [14] .comment          PROGBITS        00000000 002bb8 000334 00      0   0  1
  [15] .shstrtab         STRTAB          00000000 002eec 0000a0 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (purecode), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000034 0x00000000 0x00000000 0x00148 0x00148 RWE 0x4
  LOAD           0x00017c 0x00000148 0x00000148 0x010a0 0x010a0 R   0x4

 Section to Segment mapping:
  Segment Sections...
   00     PrgCode PrgData
   01     DevDscr

There is no dynamic section in this file.

There are no relocations in this file.

There are no unwind sections in this file.

Symbol table '.symtab' contains 17 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 NOTYPE  LOCAL  DEFAULT    1 $t
     2: 00000122     0 NOTYPE  LOCAL  DEFAULT    1 $d
     3: 00000144     0 NOTYPE  LOCAL  DEFAULT    2 $d.realdata
     4: 00000148     0 NOTYPE  LOCAL  DEFAULT    3 $d.realdata
     5: 00000000     0 FILE    LOCAL  DEFAULT  ABS FlashPrg.c
     6: 00000000     0 SECTION LOCAL  DEFAULT    1 .text
     7: 00000000     0 FILE    LOCAL  DEFAULT  ABS FlashDev.c
     8: 00000148  4256 SECTION LOCAL  DEFAULT    3 .constdata
     9: 00000000     0 NOTYPE  GLOBAL HIDDEN   ABS BuildAttributes$$THM_ISAv
    10: 00000001    28 FUNC    GLOBAL HIDDEN     1 GetSecNum
    11: 0000001d    46 FUNC    GLOBAL HIDDEN     1 Init
    12: 0000004b    14 FUNC    GLOBAL HIDDEN     1 UnInit
    13: 00000059    44 FUNC    GLOBAL HIDDEN     1 EraseChip
    14: 00000085    76 FUNC    GLOBAL HIDDEN     1 EraseSector
    15: 000000d1    82 FUNC    GLOBAL HIDDEN     1 ProgramPage
    16: 00000148  4256 OBJECT  GLOBAL HIDDEN     3 FlashDevice

No version information found in this file.

Displaying notes found at file offset 0x00002b9c with length 0x0000001c:
  Owner                 Data size       Description
  ARM                  0x0000000c       Unknown note type: (0x40000000)

通过Symbol table信息我们可以找到Init、UnInit、EraseSector和ProgramPage函数所在的位置。

我们还需要根据Section Headers所描述的位置提取出Prgcode(代码),PrgData(数据),DevDscr(设备描述)的信息。

在命令行中输入arm-none-eabi-objdump -s -d STM32F4xx_1024.FLM:-s参数可以将所有段的内容一十六进制方式打印出来,-d参数可以将所有包含指令的段反汇编。

$ arm-none-eabi-objdump -s -d STM32F4xx_1024.FLM

STM32F4xx_1024.FLM:     file format elf32-littlearm

Contents of section PrgCode:
 0000 0003000e 202802d3 4009001d 70471028  .... (..@...pG.(
 0010 02d30009 c01c7047 80087047 42484149  ......pG..pGBHAI
 0020 41604249 41600021 0160c168 f0221143  A`BIA`.!.`.h.".C
 0030 c1604069 800606d4 3e483d49 01600621  .`@i....>H=I.`.!
 0040 41603d49 81600020 70473748 01694205  A`=I.`. pG7H.iB.
 0050 11430161 00207047 10b53348 01690424  .C.a. pG..3H.i.$
 0060 21430161 0169a203 11430161 3349314a  !C.a.i...C.a3I1J
 0070 00e01160 c368db03 fbd40169 a1430161  ...`.h.....i.C.a
 0080 002010bd 30b5fff7 bbff2749 ca68f023  . ..0.....'I.h.#
 0090 1a43ca60 02240c61 0a690007 400e0243  .C.`.$.a.i..@..C
 00a0 0a610869 e2031043 08612448 214a00e0  .a.i...C.a$H!J..
 00b0 1060cd68 ed03fbd4 0869a043 0861c868  .`.h.....i.C.a.h
 00c0 0006000f 03d0c868 1843c860 012030bd  .......h.C.`. 0.
 00d0 70b5154d c91c8908 eb688900 f0263343  p..M.....h...&3C
 00e0 eb600023 2b61164b 17e02c69 1c432c61  .`.#+a.K..,i.C,a
 00f0 14680460 ec68e403 fcd42c69 64086400  .h.`.h....,id.d.
 0100 2c61ec68 2406240f 04d0e868 3043e860  ,a.h$.$....h0C.`
 0110 012070bd 001d121d 091f0029 e5d10020  . p........)...
 0120 70bd0000 23016745 003c0240 ab89efcd  p...#.gE.<.@....
 0130 55550000 00300040 ff0f0000 aaaa0000  UU...0.@........
 0140 01020000                             ....
Contents of section PrgData:
 0144 00000000                             ....
Contents of section DevDscr:
 0148 01015354 4d333246 34787820 466c6173  ..STM32F4xx Flas
 0158 68000000 00000000 00000000 00000000  h...............
 0168 00000000 00000000 00000000 00000000  ................
 0178 00000000 00000000 00000000 00000000  ................
 0188 00000000 00000000 00000000 00000000  ................
 0198 00000000 00000000 00000000 00000000  ................
 01a8 00000000 00000000 00000000 00000000  ................
 01b8 00000000 00000000 00000000 00000000  ................
 01c8 00000100 00000008 00001000 00040000  ................
 01d8 00000000 ff000000 64000000 70170000  ........d...p...
 01e8 00400000 00000000 00000100 00000100  .@..............
 01f8 00000200 00000200 ffffffff ffffffff  ................

我们所需要的正是以上信息,接下来的任务只需要写一个上位机,将以上文件提取出来即可,这个工具我已经写好,如图:

选择STM32F4xx_1024.FLM,生成STM32F4xx_1024.FLM.c文件,然后直接添加到我们的工程中即可,生成的代码如下:

#include "flash\_blob.h" 

#define OPS\_OFFSET ((uint32\_t)&flash\_code)

#define DEV\_OFFSET ((uint32\_t)&flash\_dev)

#define RAM\_OFFSET ((uint32\_t)&rw\_data)

static const uint32\_t flash_code[] = 
{
    0X0E000300,0XD3022820,0X1D000940,0X28104770,0X0900D302,0X47701CC0,0X47700880,0X49414842,
    0X49426041,0X21036041,0X68C16001,0X431122F0,0X694060C1,0XD4060680,0X493D483E,0X21066001,
    0X493D6041,0X20006081,0X48374770,0X05426901,0X61014311,0X47702000,0X4833B510,0X24046901,
    0X61014321,0X03A26901,0X61014311,0X4A314933,0X6011E000,0X03DB68C3,0X6901D4FB,0X610143A1,
    0XBD102000,0XF7FFB530,0X4927FFBB,0X23F068CA,0X60CA431A,0X610C2402,0X0700690A,0X43020E40,
    0X6908610A,0X431003E2,0X48246108,0XE0004A21,0X68CD6010,0XD4FB03ED,0X43A06908,0X68C86108,
    0X0F000600,0X68C8D003,0X60C84318,0XBD302001,0X4D15B570,0X08891CC9,0X008968EB,0X433326F0,
    0X230060EB,0X4B16612B,0X692CE017,0X612C431C,0X60046814,0X03E468EC,0X692CD4FC,0X00640864,
    0X68EC612C,0X0F240624,0X68E8D004,0X60E84330,0XBD702001,0X1D121D00,0X29001F09,0X2000D1E5,
    0X0000BD70,0X45670123,0X40023C00,0XCDEF89AB,0X00005555,0X40003000,0X00000FFF,0X0000AAAA,
    0X00000201,0X00000000,
};

static const uint32\_t flash_dev[] = 
{
    0X54530101,0X4632334D,0X20787834,0X73616C46,0X00000068,0X00000000,0X00000000,0X00000000,
    0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
    0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
    0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
    0X00010000,0X08000000,0X00100000,0X00000400,0X00000000,0X000000FF,0X00000064,0X00001770,
    0X00004000,0X00000000,0X00010000,0X00010000,0X00020000,0X00020000,0XFFFFFFFF,0XFFFFFFFF,
};

static uint32\_t rw_data[] = 
{
    0X00000000,
};

static flash\_blob\_device\_t  flash_device =
{
    (void\*)(OPS_OFFSET + 0X001D),  // Init
    (void\*)(OPS_OFFSET + 0X004B),  // UnInit
    (void\*)(OPS_OFFSET + 0X0059),  // EraseChip
    (void\*)(OPS_OFFSET + 0X0085),  // EraseSector
    (void\*)(OPS_OFFSET + 0X00D1),  // ProgramPage
    (void\*)(DEV_OFFSET),
    (void\*)(RAM_OFFSET),
};
static int flash\_blob\_device\_register(void) 
{                                            
    flash\_dev\_register(&flash_device);       
    return 0 ;                                
}                                               
INIT\_BOARD\_EXPORT(flash_blob_device_register);


四、设计flash驱动抽象层

接下来,为了方便后续使用,需要设计一个flash驱动抽象层,代码如下:
flash_blob.h

#ifndef FLASH\_BLOB\_H
#define FLASH\_BLOB\_H
#include "rtthread.h"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#define VERS 1 // Interface Version 1.01

#define UNKNOWN 0 // Unknown
#define ONCHIP 1 // On-chip Flash Memory
#define EXT8BIT 2 // External Flash Device on 8-bit Bus
#define EXT16BIT 3 // External Flash Device on 16-bit Bus
#define EXT32BIT 4 // External Flash Device on 32-bit Bus
#define EXTSPI 5 // External Flash Device on SPI

#define SECTOR\_NUM 16 // Max Number of Sector Items

struct FlashSectors  {
  unsigned long   szSector;    // Sector Size in Bytes
  unsigned long AddrSector;    // Address of Sector
};

typedef struct FlashDevice  {
   unsigned short     Vers;    // Version Number and Architecture
   char       DevName[128];    // Device Name and Description
   unsigned short  DevType;    // Device Type: ONCHIP, EXT8BIT, EXT16BIT, ...
   unsigned long    DevAdr;    // Default Device Start Address
   unsigned long     szDev;    // Total Size of Device
   unsigned long    szPage;    // Programming Page Size
   unsigned long       Res;    // Reserved for future Extension
   unsigned char  valEmpty;    // Content of Erased Memory

   unsigned long    toProg;    // Time Out of Program Page Function
   unsigned long   toErase;    // Time Out of Erase Sector Function

   struct FlashSectors sectors[SECTOR_NUM];
}flash\_dev\_t;

typedef struct {
	int (\*Init)(uint32\_t adr, uint32\_t clk, uint32\_t fnc);
	int (\*UnInit)(uint32\_t fnc);
    int (\*EraseChip)(void);
	int (\*EraseSector)(uint32\_t adr);
	int (\*ProgramPage)(uint32\_t adr, uint32\_t sz, uint8\_t\* buf);  
}flash\_ops\_t;

typedef struct
{
    flash\_ops\_t tFlashops;
    flash\_dev\_t \*ptFlashDev;
    int \*pPrgData;
    rt\_slist\_t slist;      
}flash\_blob\_t;

extern void flash\_dev\_register(flash\_blob\_t \*ptFlashDevice);
extern bool target\_flash\_init(uint32\_t flash_start, int32\_t size);
extern bool target\_flash\_uninit(uint32\_t flash_start);
extern int32\_t target\_flash\_write(uint32\_t addr, const uint8\_t \*buf, int32\_t size);
extern int32\_t target\_flash\_erase(uint32\_t addr, int32\_t size);
extern int32\_t target\_flash\_read(uint32\_t addr, const uint8\_t \*buf, int32\_t size);
#endif


flash_blob.c

#include "flash\_blob.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

static bool s_bIsInit = false;
static rt\_slist\_t _slist_head = RT\_SLIST\_OBJECT\_INIT(_slist_head);
register int \*pPrgDataBase \_\_asm("r9");

void flash\_dev\_register(flash\_blob\_t \*ptFlashDevice)
{
    rt\_slist\_init(&(ptFlashDevice->slist));
    rt\_slist\_append(&_slist_head, &(ptFlashDevice->slist));
}

static flash\_blob\_t \* flash\_dev\_find(uint32\_t flash_start)
{
    rt\_slist\_t \*node;
    rt\_slist\_for\_each(node, &_slist_head) {
        flash\_blob\_t \*ptFlashDevice = rt\_slist\_entry(node, flash\_blob\_t, slist);

        if(flash_start >= ptFlashDevice->ptFlashDev->DevAdr &&
           flash_start < ptFlashDevice->ptFlashDev->DevAdr + ptFlashDevice->ptFlashDev->szDev) {
            return ptFlashDevice;
        }
    }
    return NULL;
}

bool target\_flash\_init(uint32\_t flash_start, int32\_t size)
{
    if (flash_start % 4 != 0) {
        LOG\_E("flash addr must be 4-byte alignment");
        return NULL;
    }

    flash\_blob\_t \*ptFlashDevice = flash\_dev\_find(flash_start);

    if(ptFlashDevice != NULL) {
        pPrgDataBase = ptFlashDevice->pPrgData;
        ptFlashDevice->tFlashops.Init(flash_start, 0, 0);
        return true;
    }

    return false;
}

bool target\_flash\_uninit(uint32\_t flash_start)
{
    flash\_blob\_t \*ptFlashDevice = flash\_dev\_find(flash_start);

    if(ptFlashDevice != NULL) {
        pPrgDataBase = ptFlashDevice->pPrgData;
        ptFlashDevice->tFlashops.UnInit(flash_start);
        return true;
    }

    return true;
}

int target\_flash\_write(uint32\_t addr, const uint8\_t \*buf, int32\_t size)
{
    flash\_blob\_t \*ptFlashDevice = flash\_dev\_find(addr);

    if(ptFlashDevice != NULL) {
        pPrgDataBase = ptFlashDevice->pPrgData;



**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/d298bf773ec283b34006a72c11d1419f.png)
![img](https://img-blog.csdnimg.cn/img_convert/b1cbe4e1ffbedb64c8c87021685f064a.png)

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**


int target\_flash\_write(uint32\_t addr, const uint8\_t \*buf, int32\_t size)
{
    flash\_blob\_t \*ptFlashDevice = flash\_dev\_find(addr);

    if(ptFlashDevice != NULL) {
        pPrgDataBase = ptFlashDevice->pPrgData;



**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-7kKNX68o-1715800647409)]
[外链图片转存中...(img-Lv6301Ji-1715800647409)]

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值