一、介绍

首先我们需要了解一个内存映射:

stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同。

RAM起始地址是0x2000 0000,结束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。

Flash中的内容一般用来存储代码和一些定义为const的数据,断电不丢失, 
RAM可以理解为内存,用来存储代码运行时的数据,变量等等。掉电数据丢失。

STM32将外设等都映射为地址的形式,对地址的操作就是对外设的操作。 
stm32的外设地址从0x4000 0000开始,可以看到在库文件中,是通过基于0x4000 0000地址的偏移量来操作寄存器以及外设的。

一般情况下,程序文件是从 0x0800 0000 地址写入,这个是STM32开始执行的地方,0x0800 0004是STM32的中断向量表的起始地址。 
在使用keil进行编写程序时,其编程地址的设置一般是这样的:

程序的写入地址从0x08000000(数好零的个数)开始的,其大小为0x80000也就是512K的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08080000,RAM的地址从0x20000000开始,大小为0x10000也就是64K的RAM。这与STM32的内存地址映射关系是对应的。

M3复位后,从0x08000004取出复位中断的地址,并且跳转到复位中断程序,中断执行完之后会跳到我们的main函数,main函数里边一般是一个死循环,进去后就不会再退出,当有中断发生的时候,M3将PC指针强制跳转回中断向量表,然后根据中断源进入对应的中断函数,执行完中断函数之后,再次返回main函数中。大致的流程就是这样。

1.1、内部Flash的构成:

STM32F429 的内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,它们的地址分布及大小如下:

STM32F103的中容量内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,它们的地址分布及大小如下:

注意STM32F105VC的是有64K或128页x2K=256k字节的内置闪存存储器,用于存放程序和数据。

  1. 主存储器:一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域它是存储用户应用程序的空间,芯片型号说明中的 1M FLASH、 2M FLASH 都是指这个区域的大小。与其它 FLASH 一样,在写入数据前,要先按扇区擦除,
  2. 系统存储区:系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB 以及 CAN 等 ISP 烧录功能。
  3. OTP 区域:OTP(One Time Program),指的是只能写入一次的存储区域,容量为 512 字节,写入后数据就无法再更改, OTP 常用于存储应用程序的加密密钥。
  4. 选项字节:选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能,这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。

1.2、对内部Flash的写入过程:

1. 解锁 (固定的KEY值)
(1) 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123 
(2) 再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB 
2. 数据操作位数 
最大操作位数会影响擦除和写入的速度,其中 64 位宽度的操作除了配置寄存器位外,还需要在 Vpp 引脚外加一个 8-9V 的电压源,且其供电间不得超过一小时,否则 FLASH可能损坏,所以 64 位宽度的操作一般是在量产时对 FLASH 写入应用程序时才使用,大部分应用场合都是用 32 位的宽度。 
3. 擦除扇区 
在写入新的数据前,需要先擦除存储区域, STM32 提供了扇区擦除指令和整个FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。 
扇区擦除的过程如下: 
(1) 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 
Flash 操作; 
(2) 在 FLASH_CR 寄存器中,将“激活扇区擦除寄存器位 SER ”置 1,并设置“扇 
区编号寄存器位 SNB”,选择要擦除的扇区; 
(3) 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除; 
(4) 等待 BSY 位被清零时,表示擦除完成。 
4. 写入数据 
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下: 
(1) 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作; 
(2) 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1; 
(3) 针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作; 
(4) 等待 BSY 位被清零时,表示写入完成。

1.3、查看工程内存的分布:

由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的“ *.map”后缀文件, 
打开 map 文件后,查看文件最后部分的区域,可以看到一段以“ Memory Map of the 
image”开头的记录(若找不到可用查找功能定位),

【注】ROM加载空间

这一段是某工程的 ROM 存储器分布映像,在 STM32 芯片中, ROM 区域的内容就是 指存储到内部 FLASH 的代码。 
在上面 map 文件的描述中,我们了解到加载及执行空间的基地址(Base)都是0x08000000,它正好是 STM32 内部 FLASH 的首地址,即 STM32 的程序存储空间就直接是执行空间;它们的大小(Size)分别为 0x00000b50 及 0x00000b3c,执行空间的 ROM 比较小的原因就是因为部分 RW-data 类型的变量被拷贝到 RAM 空间了;它们的最大空间(Max)均为 0x00100000,即 1M 字节,它指的是内部 FLASH 的最大空间。

计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应用程序使用 
的内部 FLASH 是从 0x08000000 至(0x08000000+0x00000b50)地址的空间区域。 
所以从扇区 1(地址 0x08004000)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。 
具体可参考原子的例程:实验四十一:FLASH 模拟 EEPROM 实验

文章引用地址:https://blog.csdn.net/qq_33559992/article/details/77676716

感谢原文作者

二、代码拆分介绍(以STM32F105系列为例,如上图表5所示)

 2.1 读/写入数据流程

写数据流程

2.1.1、Flash 解锁,直接调用#include "stm32f10x_flash.h"中的void FLASH_Unlock(void)函数,这个函数是官方提供的,其内部代码如下:

 1 /**2   * @brief  Unlocks the FLASH Program Erase Controller.3   * @note   This function can be used for all STM32F10x devices.4   *         - For STM32F10X_XL devices this function unlocks Bank1 and Bank2.5   *         - For all other devices it unlocks Bank1 and it is equivalent 6   *           to FLASH_UnlockBank1 function.. 7   * @param  None8   * @retval None9   */
10 void FLASH_Unlock(void)
11 {
12   /* Authorize the FPEC of Bank1 Access */
13   FLASH->KEYR = FLASH_KEY1;
14   FLASH->KEYR = FLASH_KEY2;
15
16 #ifdef STM32F10X_XL
17   /* Authorize the FPEC of Bank2 Access */
18   FLASH->KEYR2 = FLASH_KEY1;
19   FLASH->KEYR2 = FLASH_KEY2;
20 #endif /* STM32F10X_XL */
21 }

2.1.2、擦除扇区,也是直接调用固件库官方的函数FLASH_Status FLASH_ErasePage(uint32_t Page_Address),这个官方函数代码也贴出来看看,代码如下:

 1 /**2   * @brief  Erases a specified FLASH page.3   * @note   This function can be used for all STM32F10x devices.4   * @param  Page_Address: The page address to be erased.5   * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,6   *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.7   */8 FLASH_Status FLASH_ErasePage(uint32_t Page_Address)9 {
10   FLASH_Status status = FLASH_COMPLETE;
11   /* Check the parameters */
12   assert_param(IS_FLASH_ADDRESS(Page_Address));
13
14 #ifdef STM32F10X_XL
15   if(Page_Address < FLASH_BANK1_END_ADDRESS)
16   {
17     /* Wait for last operation to be completed */
18     status = FLASH_WaitForLastBank1Operation(EraseTimeout);
19     if(status == FLASH_COMPLETE)
20     {
21       /* if the previous operation is completed, proceed to erase the page */
22       FLASH->CR|= CR_PER_Set;
23       FLASH->AR = Page_Address;
24       FLASH->CR|= CR_STRT_Set;
25
26       /* Wait for last operation to be completed */
27       status = FLASH_WaitForLastBank1Operation(EraseTimeout);
28
29       /* Disable the PER Bit */
30       FLASH->CR &= CR_PER_Reset;
31     }
32   }
33   else
34   {
35     /* Wait for last operation to be completed */
36     status = FLASH_WaitForLastBank2Operation(EraseTimeout);
37     if(status == FLASH_COMPLETE)
38     {
39       /* if the previous operation is completed, proceed to erase the page */
40       FLASH->CR2|= CR_PER_Set;
41       FLASH->AR2 = Page_Address;
42       FLASH->CR2|= CR_STRT_Set;
43
44       /* Wait for last operation to be completed */
45       status = FLASH_WaitForLastBank2Operation(EraseTimeout);
46
47       /* Disable the PER Bit */
48       FLASH->CR2 &= CR_PER_Reset;
49     }
50   }
51 #else
52   /* Wait for last operation to be completed */
53   status = FLASH_WaitForLastOperation(EraseTimeout);
54
55   if(status == FLASH_COMPLETE)
56   {
57     /* if the previous operation is completed, proceed to erase the page */
58     FLASH->CR|= CR_PER_Set;
59     FLASH->AR = Page_Address;
60     FLASH->CR|= CR_STRT_Set;
61
62     /* Wait for last operation to be completed */
63     status = FLASH_WaitForLastOperation(EraseTimeout);
64
65     /* Disable the PER Bit */
66     FLASH->CR &= CR_PER_Reset;
67   }
68 #endif /* STM32F10X_XL */
69
70   /* Return the Erase Status */
71   return status;
72 }

注意这个擦除扇区函数是你提供一个STM32f105系列扇区的开始地址即可,擦除是按照页擦除(每页2KB=1024Byte)或者整个擦除(见STM32参考手册的第二章2.3.3嵌入式闪存部分介绍)

比如我们要擦除互联网型的127页,我们只需要FLASH_ErasePage(0x0803f800);执行后,第127页的0x0803f800-0x0803FFFF数据都将被擦除。

当然官方提供的也不知一个擦除函数,而是三个,具体如下,对于32位系统:一个是字节=4byte=32bite;一个是半字=2byte=16bite;一个是字节=1byte=8bite;进行擦除。

FLASH_Status FLASH_ErasePage(uint32_t Page_Address);

FLASH_Status FLASH_EraseAllPages(void);

FLASH_Status FLASH_EraseOptionBytes(void);

2.1.3、接下来是写/读数据函数,该函数也是官方给出的,我们只需要用就好了。但要注意,这个是个半字的写操作,为什么uint16_t 的数据算半字呢,因为单片机是32的,对于32位单片机系统来说,一个字是4个字节的,8位的比如51单片机系统一个字就是2位的,64位单片机系统一个字就是8个字节,脱离单片机系统说字是多少个字节是没意义的。所以这里写入/读出半字也就是一次写入2个字节,写完/读出一次地址会加2。

写数据操作:

 1 /**2   * @brief  Programs a half word at a specified address.3   * @note   This function can be used for all STM32F10x devices.4   * @param  Address: specifies the address to be programmed.5   * @param  Data: specifies the data to be programmed.6   * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,7   *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT. 8   */9 FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
10 {
11   FLASH_Status status = FLASH_COMPLETE;
12   /* Check the parameters */
13   assert_param(IS_FLASH_ADDRESS(Address));
14
15 #ifdef STM32F10X_XL
16   /* Wait for last operation to be completed */
17   status = FLASH_WaitForLastOperation(ProgramTimeout);
18
19   if(Address < FLASH_BANK1_END_ADDRESS)
20   {
21     if(status == FLASH_COMPLETE)
22     {
23       /* if the previous operation is completed, proceed to program the new data */
24       FLASH->CR |= CR_PG_Set;
25
26       *(__IO uint16_t*)Address = Data;
27       /* Wait for last operation to be completed */
28       status = FLASH_WaitForLastBank1Operation(ProgramTimeout);
29
30       /* Disable the PG Bit */
31       FLASH->CR &= CR_PG_Reset;
32     }
33   }
34   else
35   {
36     if(status == FLASH_COMPLETE)
37     {
38       /* if the previous operation is completed, proceed to program the new data */
39       FLASH->CR2 |= CR_PG_Set;
40
41       *(__IO uint16_t*)Address = Data;
42       /* Wait for last operation to be completed */
43       status = FLASH_WaitForLastBank2Operation(ProgramTimeout);
44
45       /* Disable the PG Bit */
46       FLASH->CR2 &= CR_PG_Reset;
47     }
48   }
49 #else
50   /* Wait for last operation to be completed */
51   status = FLASH_WaitForLastOperation(ProgramTimeout);
52
53   if(status == FLASH_COMPLETE)
54   {
55     /* if the previous operation is completed, proceed to program the new data */
56     FLASH->CR |= CR_PG_Set;
57
58     *(__IO uint16_t*)Address = Data;
59     /* Wait for last operation to be completed */
60     status = FLASH_WaitForLastOperation(ProgramTimeout);
61
62     /* Disable the PG Bit */
63     FLASH->CR &= CR_PG_Reset;
64   }
65 #endif  /* STM32F10X_XL */
66
67   /* Return the Program Status */
68   return status;
69 }

当然官方给的不止是这一个函数写数据,官方提供了3个

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);//一次写一个字,对于32系统,一次写的是4个字节,uint32_t 变量大小,32bit

FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);//一次写一个半字,对于32系统,一次写的是2个字节,uint16_t 变量大小,16bit

FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);//一次写一个字节,对于32系统,一次写的是1个字节,uint8_t 变量大小,8bit

读数据操作:

读数据的函数,官方并没有给出:下面我们自己给出,具体的读法代码如下

1 //读取指定地址的半字(16位数据)
2 //也是按照半字读出,即每次读2个字节数据返回
3 uint16_t FLASH_ReadHalfWord(uint32_t address)
4 {
5   return *(__IO uint16_t*)address;
6 }

如果要连续都区多个地址数据,可以进行如下代码操作

1 //从指定地址开始读取多个数据
2 void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead)
3 {
4   uint16_t dataIndex;
5   for(dataIndex=0;dataIndex<countToRead;dataIndex++)
6   {
7     readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2);
8   }
9 }

2.1.4、这步骤应该就是再次上锁,保护存储区不被重写覆盖了,直接使用官方的函数即可:FLASH_Lock();//上锁写保护

具体官方代码贴出如下

 1 /**2   * @brief  Locks the FLASH Program Erase Controller.3   * @note   This function can be used for all STM32F10x devices.4   *         - For STM32F10X_XL devices this function Locks Bank1 and Bank2.5   *         - For all other devices it Locks Bank1 and it is equivalent 6   *           to FLASH_LockBank1 function.7   * @param  None8   * @retval None9   */
10 void FLASH_Lock(void)
11 {
12   /* Set the Lock Bit to lock the FPEC and the CR of  Bank1 */
13   FLASH->CR |= CR_LOCK_Set;
14
15 #ifdef STM32F10X_XL
16   /* Set the Lock Bit to lock the FPEC and the CR of  Bank2 */
17   FLASH->CR2 |= CR_LOCK_Set;
18 #endif /* STM32F10X_XL */
19 }

三、简单的小例程代码实现

例子功能:

1、将数据存储在stm32F105单片机的主存储区0x08036000地址开始的扇区,(0x08036000应该是该单片机大约108个扇区的开始地址位置即页108起始地址)。

2、将该单片机的页108(page108=0x08036000)处的数据再读出来;

具体实现代码如下,作为例子,只进行了半字的读写操作,我们写的数据buff为空,内容默认值为0

 1 #include "stm32f10x_flash.h"2 3 #define StartServerManageFlashAddress    ((u32)0x08036000)//读写起始地址(内部flash的主存储块地址从0x08036000开始)4 5 //从指定地址开始写入多个数据6 void FLASH_WriteMoreData(uint32_t startAddress,uint16_t *writeData,uint16_t countToWrite)7 {    8     uint32_t offsetAddress=startAddress - FLASH_BASE;               //计算去掉0X08000000后的实际偏移地址9   uint32_t sectorPosition=offsetAddress/SECTOR_SIZE;            //计算扇区地址,对于STM32F103VET6为0~255
10   uint32_t sectorStartAddress=sectorPosition*SECTOR_SIZE+FLASH_BASE;    //对应扇区的首地址
11     uint16_t dataIndex;
12
13   if(startAddress<FLASH_BASE||((startAddress+countToWrite*2)>=(FLASH_BASE + SECTOR_SIZE * FLASH_SIZE)))
14   {
15     return;//非法地址
16   }
17   FLASH_Unlock();         //解锁写保护
18
19   FLASH_ErasePage(sectorStartAddress);//擦除这个扇区
20
21   for(dataIndex=0;dataIndex<countToWrite;dataIndex++)
22   {
23     FLASH_ProgramHalfWord(startAddress+dataIndex*2,writeData[dataIndex]);
24   }
25
26   FLASH_Lock();//上锁写保护
27 }
28
29 //读取指定地址的半字(16位数据)
30 uint16_t FLASH_ReadHalfWord(uint32_t address)
31 {
32   return *(__IO uint16_t*)address;
33 }
34
35 //从指定地址开始读取多个数据
36 void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead)
37 {
38   uint16_t dataIndex;
39   for(dataIndex=0;dataIndex<countToRead;dataIndex++)
40   {
41     readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2);
42   }
43 }
44
45 void write_to_flash(void)
46 {
47     u16 buff[1200];
48     u16 count_len = 2272 / 2;
50     FLASH_WriteMoreData(StartServerManageFlashAddress,buff,count_len);
55 }
56
57 void read_from_flash(void)
58 {
59     u16 buff[1200];
60     u16 count_len = 2272 / 2;
61     FLASH_WriteMoreData(StartServerManageFlashAddress,buff,count_len);
66
67  }

 1 void mian(void)2 {3     .........//初始化其他外设4     while(1)5     {6         ...........//其他外设执行函数7         if(满足条件真)//写数据操作8        {9             write_to_flash();
10         }
11         else //读数据操作
12         {
13             read_from_flash();
14         }
15
16     }
17  }

四、附言

值得的注意的是,我们读写的地址是0x08036000,读写方式是半字,这里地址空间对于stm32f105芯片来说是第108扇区,每个扇区2KB,stm32F105VC总共是256KB空间,128页。所以地址能取到0x08036000,像小中容量stm32f103单片机,64KB和128KB的主存储区地址都是到不了0x08036000,除非是stm32f103VE的256KB芯片的主存储快,0x08036000才是有效的存储地址,中小型这个地址都不是有效的主存储开地址(超出了)

STM32学习笔记:读写内部Flash(介绍+附代码)相关推荐

  1. STM32学习笔记:读写内部FLASH

    ST提供的库函数基本能搞定 简介 由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行,亦可当做EEROM来使用保存所需要的数据,以便下次启用 ...

  2. php 运营商授权,PHP判断手机号运营商(详细介绍附代码)

    道理很简单,知道手机号规则 进行正则判断就可以 移动:134.135.136.137.138.139.150.151.157(TD).158.159.187.188 联通:130.131.132.15 ...

  3. STM32学习笔记:FLASH读写之二

    因为关于STM32的Flash相关的知识点比较多,所以该内容的学习我们分为以下4个部分 1.RAM和ROM的一些基本概念 -- STM32学习笔记:FLASH读写之一 2.STM32的Flash寄存器 ...

  4. STM32学习笔记:FLASH读写之一

    因为关于STM32的Flash相关的知识点比较多,所以该内容的学习我们分为以下4个部分 1.RAM和ROM的一些基本概念 -- STM32学习笔记:FLASH读写之一 2.STM32的Flash寄存器 ...

  5. STM32学习笔记(9)——(I2C续)读写EEPROM

    STM32学习笔记(9)--(I2C续)读写EEPROM 一.概述 1. 背景介绍 2. EEPROM简介 二.AT24C02--常用的EEPROM 1. 电路原理图 2. 写操作 (1)按字节写操作 ...

  6. STM32 HAL库学习笔记2 HAL库介绍

    STM32 HAL库学习笔记2 HAL库介绍 CMSIS标准 一.再次认识HAL库 HAL库设计思想 HAL库实现方式 以GPIO模块为例 GPIO外设数据类型 GPIO外设接口函数 二.使用HAL库 ...

  7. 《STM32学习笔记》4——核心功能电路与编程(下)

    接上文,文中的图片,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.蜂鸣器驱动 1.蜂鸣器介绍 2.蜂鸣器电路 3.蜂鸣器程序 二. MIDI 音乐 ...

  8. 《STM32学习笔记》3——核心功能电路与编程(上)

    接上文,文中的图片,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.核心板电路分析 二.点灯 LED 1.LED电路 2.LED功能相关初始化配置 ...

  9. STM32学习笔记(10)——高级定时器TIM

    前排提示:本笔记参考了野火PPT的大部分内容.本人初学定时器,倍感冗杂,有错烦请指出,谢谢! STM32学习笔记(10)--高级定时器TIM 一.时钟源 1. 内部时钟源 2. 外部时钟模式 1 (1 ...

  10. 《STM32学习笔记》2——开发环境的建立

    接上文,文中的图片知识,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.接口定义 1 芯片引脚整体简介 2 各部分引脚介绍 二.开发板简介 三.IS ...

最新文章

  1. python socket通信 recv 丢包_关于socket网络传输数据的阿里云论坛用户知识和技术交流...
  2. Spring入门第五课
  3. c++设计模式之简单工厂模式
  4. Sublime Text 3 注册激活码
  5. Intel Realsense D435 如何通过图像识别出的比例点(x, y)获得实际点相对于摄像机原点的三维坐标(x, y, z)?
  6. 王建宙:中国移动还未与苹果就iPhone达成协议
  7. Qt工作笔记-QString中arg的使用(可用于SQL语句拼接)
  8. 苹果cms V8模板 黑色自适应电影模板
  9. 设计灵感|电商美工,年年都能借鉴的新年
  10. 生成.properties文件(bat文件生成)
  11. [ZPG TEST 115] 字符串【归类思想】
  12. 烂泥:利用IIS、Zblog、SQLServer搭建ASP博客
  13. 如何自动生成和安装requirements.txt依赖
  14. linux mint 8812ac网卡,kali虚拟机连接无线网卡comfast cf-812AC(Realtek 8812BU芯片)(不支持wlan0mon)...
  15. seo关键词排名优化的方法
  16. C语言及程序设计 套餐 课程主页
  17. C. 实验7-2-7 方阵循环右移
  18. 文字识别(二)--字符识别技术总览
  19. 自动化测试 appium 会报错 Could not proxy command to remote server. Original error: Error: socket hang up
  20. 100道经典Hadoop常见面试/笔试题及答案解析

热门文章

  1. 如何只用一部手机拍出绝美证件照(换底色,改尺寸,换装美颜,排版)?
  2. 智慧管廊综合监管平台
  3. 【Python】格式化打印杨辉三角形
  4. cf621E. Wet Shark and Blocks
  5. TOP100案例分享 “预测性维护”
  6. LimeSDR 信号生成发射与接收分析
  7. vue/cli4、webpack 简阅(希望能持续更新)
  8. php某年某月某日是否闰年,【后端开发】PHP判断一个年份是否为闰年
  9. android 判断U盘是否挂载
  10. 使VLC媒体播放器看起来很棒的10大皮肤