STM32标准库——(21)Flash闪存

2024-03-09 23:44
文章标签 21 stm32 flash 标准 闪存

本文主要是介绍STM32标准库——(21)Flash闪存,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.简介

  • 第一个用途,对于我们这个C8T6芯片来说,它的程序存储器容量是64K,一般我们写个简单的程序,可能就只占前面的很小一部分空间,剩下的大片空余空间我们就可以加以利用,比如存储一些我们自定义的数据,这样就非常方便,而且可以充分利用资源,不过这里要注意我们在选取存储区域时,一定不要覆盖了原有的程序,要不然程序自己把自己给破坏了,一般存储少量的参数,我们就选最后几页存储就行了,关于如何查看程序所占用空间的大小,这个我们下小节也会介绍,然后第二个用途就是通过在程序中编程IAP,实现程序的自我更新,我们在存储用户数据时要避开程序本身,以免破坏程序,但如果我们就非要修改程序本身,这会发生什么呢,那这就是第二点提到的功能,在程序中编程,利用程序来修改程序本身,实现程序的自我更新,这个在程序中编程就是IAP。
  • 在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序, ICP英文直译过来也可以叫在电路中编程,意思就是下载程序,你只需要留几个引脚就行,不用拆芯片了,就叫在电路中进行编程,ICP的作用是用于更新程序存储器的全部内容,它通过JTAG SWD协议或系统加载程序BOOTLOADER下载程序,这个JTAG SWD就是仿真器下载程序,就是我们目前用的stlink,使用SWD下载程序,每次下载都是把整个程序完全更新掉,那系统加载程序就是系统存储器的BOOTLOADER,也就是串口下载,串口下载也是更新整个程序,这就是我们一直在用的ICP下载方式,之后更高级的下载方式就是在程序中编程(In-Application Programming – IAP),简称IAP,它可以使用微控制器支持的任意一种通信接口下载程序,怎么实现呢,那比如这是整个程序存储器,我们首先需要自己写一个BOOTLOADER程序,并且存放在程序更新时不会覆盖的地方,比如我们放在最后面,然后需要更新程序时,我们控制程序跳转到这个自己写的BOOTLOADER这里来,在这里面我们就可以接收任意一种通讯接口传过来的数据,比如串口、USB、蓝牙转串口、WIFI转串口等等,这个传过来的数据就是待更新的程序,然后我们控制flash读写,把收到的程序写入到前面程序正常运行的地方,写完之后再控制程序跳转回正常运行的地方,或者直接复位,这样程序就完成了自我升级

2.闪存模块组织

  1.  对于主存储器(程序存储器 用来存放程序代码),这里对它进行了分页,分页是为了更好的管理闪存擦除和写保护都是以页为单位的,这点和之前W25Q64芯片的闪存一样,同为闪存它们的特性基本一样,写入前必须擦除,擦除必须以最小单位进行,擦除后数据位全变为1,数据只能1写0,不能0写1,擦除和写入之后都需要等待忙,这些都是一样的,学习这节之前,大家可以再复习一下W25Q64,再学这一节就会非常轻松了,那W25Q64的分配方式是先分为块block,再分为扇区sector比较复杂,这里就比较简单了,它只有一个基本单位就是页,每一页的大小都是1K,0到127总共128页,总量就是128K,对于C8T6来说,它只有64K,所以C8T6的页只有一半0~63总共64页共64K
  2. 第一个页的起始地址就程序存储器的起始地址0x08000000,之后就是一个字节一个地址依次线性分配的,看一下每页起始地址的规律,首先是0000然后0400、0800、0400,再之后1000、1400、1800,最后一直到1FC00,所以地址只要以000、400、800、400,结尾的都一定是页的起始地址,这个稍微记一下。
  3. 启动程序代码就是简介中的系统存储器 用户选择字节就是选项字节

3.基本结构

整个闪存分为程序存储器、系统存储器和选项字节三部分,这里程序存储器为以C8T6为例,它是64K的,所以总共只有64页,最后一页的起始地址是0800FC00,左边这里是闪存存储器接口,手册里还有个名称,闪存编程和擦除控制器LPEC,大家也知道这两个名称其实是一个东西就行,然后这个控制器就是闪存的管理员,他可以对程序存储器进行擦除和编程,也可以对选项字节进行擦除和编程,系统容器是不能擦除和编程的,这个选项字节里面有很大一部分配置位,其实是配置主程序存储器的读写保护的,所以右边画的写入选项字节,可以配置程序存储器的读写保护,当然选项字节还有几个别的配置参数,这个待会再讲,那这就是整个闪存的基本结构。

4.Flash解锁

首先第一步是flash解锁,这和之前W25Q64一样,W25Q64操作之前需要写使能,这个flash操作之前需要解锁,目的都是为了防止误操作,那这里解锁的方式和之前独立看门狗一样,都是通过在键寄存器写入指定的键值来实现,使用键寄存容器的好处就是更能防止误操作,每一个指令必须输密码才能完成,通过英文名称也能看出来,键的英文是KEY,直译是不是钥匙的意思,所以这个更形象的翻译我们可以把它叫做钥匙寄存器,密钥寄存器,首先LPEC共有三个键值,也就是三把开锁的钥匙,RDPRT键是解除读保护的密钥,值是0XA5,KEY1键值是0X45670123,KEY2键值是0XCDEF89AB,为什么是这些值呢,实际上是随便定义的,只要你定义的不是很简单就行,继续看怎么解锁呢,第一个是复位后FPEC被保护,不能写入FLASH_CR,也就是复位后flash默认是锁着的,然后在FLASH_KEYR键寄存器中,先写入KEY1,再写入KEY2解锁,我们找到了锁,这个锁是KEYR寄存器,怎么解呢,要先用K1钥匙解,再用K2钥匙解,最终才能解锁成功,所以这个锁的安全性非常高,有两道锁,即使程序跑飞了,歪打正着正好写入了KEY1,那也难以保证下一次又歪打正着写入了KEY2,所以非人为情况下基本不可能解锁,然后第三条还有进一步的保护措施,就是错误的操作序列会在下次复位前锁死FPEC和FLASH_CR,于是他发现有程序在尝试撬锁时,一旦没有先写入KEY1,再写入KEY2,整个模块就会完全锁死,除非复位,这是整个解锁操作,可以看到安全性非常高,接着继续看,解锁之后如何加锁呢,我们操作完成之后,要尽快把flash重新加锁,以防止意外情况,加锁的操作是设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR,这个比较简单,就是控制寄存器里面有个LOCK位,我们在这一位写1就能重新锁住闪存

4.使用指针访问存储器

  • 将0x08000000强转成__IO uint16_t *类型 即将该地址强转成uint16_t的指针类型 代表指针变量指向0x08000000的地址 此时再在外面取* 则可以得到数据
  • __IO是stm32中的宏定义 该宏定义对应C语言中的volatile 直译就是易变数据

5.程序存储器全擦除

第一步是读取lock位,看一下芯片锁没锁,下面如果lock位等于1锁住了,就执行解锁过程解锁过程就是在KEYR寄存器先写入KEY1,再写入KEY2,这里如果它当前没锁住,就不用解锁了,这是流程图里给的解锁步骤,如果锁住了就解锁,如果没锁住就不用解锁,但是在库函数中并没有这个判断,库函数是直接执行解锁过程,管你锁没锁都执行解锁,这个比较简单直接,不过效果都一样,然后继续解锁之后,首先置控制寄存器里的MER(Mass Erase)位为1,然后再置STRT(Start)位为1,其中STRT为1是触发条件,STRT为1之后芯片开始干活,然后现在看到MER位是1,它就知道接下来要干的活就是全删除,这样内部电路就会自动执行全擦除的过程,然后继续擦除也是需要花一段时间的,所以擦除过程开始后,程序要执行等待判断状态寄存器的BSY(Busy)位是否为1,BSY位表示芯片是否处于忙状态,BSY位为1表示芯片忙,所以这里如果判断BSY位等于1,就跳转回来继续循环判断,直到BSY位等于0跳出循环,最后一步这里写的是读出并验证所有页的数据,这个是测试程序才要做的,正常情况下全删除完成了,我们默认就成功了,如果还要再全读出来验证一下,这个工作量太大了,所以这里的最后一步我们就不管了,这是全擦除的流程。

6. 程序存储器页擦除

 页擦除,这个也是类似的过程,第一步一样的是解锁的流程,第二步,这个方框里的置控寄存器的PER(Page Erase)位为1,然后在AR(Address Register)地址寄存器中选择要擦除的页,最后置控制寄存器的STRT位为1,也是触发条件,芯片开始干活,然后芯片看到PER等于1,它就知道接下来要执行页擦除,然后闪存不止一页,页擦除芯片就要知道要具体擦哪一页,所以它会继续看AR寄存器的数据,AR寄存器我们要提前写入一个页的起始地址,这样芯片就会把我们指定的一页给擦除掉,然后擦除开始之后,我们也要等待BSY位,最后读出并验证数据,这个就不用看了。

7.程序存储器编程

擦除之后我们就可以执行写入的流程了,另外说明一下,STM32的闪存,在写入之前会检查指定地址有没有擦除,如果没有擦除就写入STM32则不执行写入操作,除非写入的全是0,这个数据是例外,因为不擦除就写入,可能会写入错误,但全写入0的话,写入肯定是没问题的,来看一下流程图,写入的第一步也是解锁,然后第二步我们需要置控制寄存器的PG(Programming)位为1,表示我们即将写入数据第三步就在指定的地址写入半字,这一步我们需要用到刚才说的这句代码,使用指针在指定地址写入数据,想写入什么数据,在这里指定即可,另外这里注意一下,写入操作只能以半字的形式写入,在STM32中有几个术语,字、半字和字节,其中字word就是32位数据,半字half word就是16位数据,字节byte就是8位数据,那这里只能以半字写入,意思就是只能以16位的形式写入,一次性写入两个字节,如果你要写入32位,就分两次完成,如果你只要写入八位,这个就比较麻烦了,如果你想单独写入一个字节,还要保留另一个字节的原始数据的话那只能把整页数据都读到SRAM,再随意修改SRAM数据修改全部完成之后,再把整页都擦除,最后再把整页都写回去,所以如果你想像SRAM一样随心所欲的读写,那最好的办法就先把闪存的一页读到SRAM中,读写完成后再擦除一页,整体写回去,那回到流程图这里,写入数据这个代码就触发开始的条件,不需要像擦除一样置STRT位了,写了半字之后,芯片会处于忙状态,我们等待一下BUSY清0,这样写入数据的过程就完成了,那每执行这样一个流程,只能写入一个半字,如果要写出很多数据,要不断循环调用这个流程就可以了

8.选项字节

这里是对应的16个字节,其中有一半的名称前面都带了个N,比如RDP和nRDP ,USER和nUSER等,这个意思就是你在写入RDP数据时,要同时在NRDP写入数据的反码,其他的这些都是一样,写这个存储器时,要在带N的对应的存储器写入反码,这样写入操作才是有效的,如果芯片检测到这两个存储器不是反码的关系,那就代表数据无效有错误,对应的功能就不执行,这是一个安全保障措施,但这个写入反码的过程,硬件会自动计算并写入,不需要我们操心,使用库函数的话,那就更简单了,函数都给我们分装好了,直接调用函数就行

第一个RDP(Read Protect)是读保护配置位,下面有解释,在RDP存储器写入RDPRT键,就刚才说的A5,然后解除读保护,如果RDP不是A5,那闪存就是读保护状态,无法通过调试器读取程序,避免程序被别人窃取,接着看第二个字节USER,这个是一些零碎的配置位,可以配置硬件看门狗和进入停机待机模式是否产生复位,这个了解即可,然后第三个和第四个字节data0和data1,这个在芯片中没有定义功能,用户可自定义使用,最后四个字节,WRP(Write Protect)0、1、2、3这四个字节配置的是写保护在中容量产品里是每一个位对应保护四个存储页,四个字节(1个字节有8位)总共32位,一位对应保护四页,总共保护32×4等于128页,正好对应中容量量的最大128页,那对于小容量和大容量产品呢,可以看一下手册,2.5选项字节说明这里,对于小容量产品,也是每一位对应保护四个存储页,但小容量产品最大只有32K,所以只需要一个字节WRP0就行,4×8=32,其他三个字节没用到,然而对于大容量产品,每一个位只能保护两个存储页,这样的话四个字节就不够用了,所以这里规定WRP3的最高位,这一位直接把剩下的所有页一起都保护了,这是写保护的定义。

9.选项字节擦除

第一步其实也是解锁闪存,这里文字并没有写,然后第二步这里文字版的流程多了一步,检查SR的BSY位,以确认没有其他正在进行的闪存操作,这个实际上就是事前等待,如果当前已经在忙了,我先等一下,这一步在刚才的流程图里并没有体现,然后下一步解锁CR的OPTWRE(Option Write Enable)位,这一步是选项字节的解锁选项字节里面还有一个单独的锁,在解锁闪存后,还需要再解锁选项字节的锁,之后才能操作选项字节,解锁选项字节的话看一下前面的寄存器(前面闪存模块组织图),整个闪存的锁是KEYR,里面选项字节的小锁是下面的OPTKEYR(Option Key Register),解锁这个小锁也是类似的流程,我们需要在OPTKEYR里先写入KEY1,再写入KEY2,这样就能解锁选项字节的小锁了,然后继续解除小锁之后和之前的擦除类似,先设置CR的OPTER(Option Erase)位为1,表示即将擦除选项字节,之后设置CR的STRT位为1,触发芯片开始干活,这样芯片就会启动擦除选项字节的工作之后等待BUSY位变为0,擦除选项字节就完成了,擦除之后就可以看写入了。

10.选项字节写入

11.器件电子签名

使用指针读指定地址下的存储器,可获取电子签名,电子签名其实就是STM32的id号它的存放区域是系统存储器,它不仅有BOOTLOADER程序,还有几个字节的id号,系统存储器起始地址是1FFFF000,看下这里,这里有两段数据,第一个是闪存容量存储器,基地址是1FFF F7E0,通过地址也可以确定它的位置,就是系统存储器,这个存储器的大小是16位,它的值就是闪存的容量单位是KB,然后第二个是产品唯一身份标识寄存器,就是每个芯片的身份证号,这个数据存放的基地址是1FFFF7E8,大小是96位,每一个芯片的这96位数据都是不一样的,使用这个唯一id号可以做一些加密的操作,比如你想写入一段程序,只能在指定设备运行,那也可以在程序的多处加入id号判断,如果不是指定设备的id号,就不执行程序功能,这样即使你的程序被盗,在别的设备上也难以运行,这是STM32的电子签名。

12.读写内部Flash

12.1 接线图

12.2 相关代码

12.2.1 MyFLASH.c
#include "stm32f10x.h"                  // Device header/*** 函    数:FLASH读取一个32位的字* 参    数:Address 要读取数据的字地址* 返 回 值:指定地址下的数据*/
uint32_t MyFLASH_ReadWord(uint32_t Address)
{return *((__IO uint32_t *)(Address));	//使用指针访问指定地址下的数据并返回
}/*** 函    数:FLASH读取一个16位的半字* 参    数:Address 要读取数据的半字地址* 返 回 值:指定地址下的数据*/
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{return *((__IO uint16_t *)(Address));	//使用指针访问指定地址下的数据并返回
}/*** 函    数:FLASH读取一个8位的字节* 参    数:Address 要读取数据的字节地址* 返 回 值:指定地址下的数据*/
uint8_t MyFLASH_ReadByte(uint32_t Address)
{return *((__IO uint8_t *)(Address));	//使用指针访问指定地址下的数据并返回
}/*** 函    数:FLASH全擦除* 参    数:无* 返 回 值:无* 说    明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在*/
void MyFLASH_EraseAllPages(void)
{FLASH_Unlock();					//解锁FLASH_EraseAllPages();			//全擦除FLASH_Lock();					//加锁
}/*** 函    数:FLASH页擦除* 参    数:PageAddress 要擦除页的页地址* 返 回 值:无*/
void MyFLASH_ErasePage(uint32_t PageAddress)
{FLASH_Unlock();					//解锁FLASH_ErasePage(PageAddress);	//页擦除FLASH_Lock();					//加锁
}/*** 函    数:FLASH编程字* 参    数:Address 要写入数据的字地址* 参    数:Data 要写入的32位数据* 返 回 值:无*/
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{FLASH_Unlock();							//解锁FLASH_ProgramWord(Address, Data);		//编程字FLASH_Lock();							//加锁
}/*** 函    数:FLASH编程半字* 参    数:Address 要写入数据的半字地址* 参    数:Data 要写入的16位数据* 返 回 值:无*/
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{FLASH_Unlock();							//解锁FLASH_ProgramHalfWord(Address, Data);	//编程半字FLASH_Lock();							//加锁
}
12.2.2 MyFLASH.h
#ifndef __MYFLASH_H
#define __MYFLASH_Huint32_t MyFLASH_ReadWord(uint32_t Address);
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
uint8_t MyFLASH_ReadByte(uint32_t Address);void MyFLASH_EraseAllPages(void);
void MyFLASH_ErasePage(uint32_t PageAddress);void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);#endif
12.2.3 Store.c
#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"#define STORE_START_ADDRESS		0x0800FC00		//存储的起始地址
#define STORE_COUNT				512				//存储数据的个数uint16_t Store_Data[STORE_COUNT];				//定义SRAM数组/*** 函    数:参数存储模块初始化* 参    数:无* 返 回 值:无*/
void Store_Init(void)
{/*判断是不是第一次使用*/if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)	//读取第一个半字的标志位,if成立,则执行第一次使用的初始化{MyFLASH_ErasePage(STORE_START_ADDRESS);					//擦除指定页MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);	//在第一个半字写入自己规定的标志位,用于判断是不是第一次使用for (uint16_t i = 1; i < STORE_COUNT; i ++)				//循环STORE_COUNT次,除了第一个标志位{MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);		//除了标志位的有效数据全部清0}}/*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/for (uint16_t i = 0; i < STORE_COUNT; i ++)					//循环STORE_COUNT次,包括第一个标志位{Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);		//将闪存的数据加载回SRAM数组}
}/*** 函    数:参数存储模块保存数据到闪存* 参    数:无* 返 回 值:无*/
void Store_Save(void)
{MyFLASH_ErasePage(STORE_START_ADDRESS);				//擦除指定页for (uint16_t i = 0; i < STORE_COUNT; i ++)			//循环STORE_COUNT次,包括第一个标志位{MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);	//将SRAM数组的数据备份保存到闪存}
}/*** 函    数:参数存储模块将所有有效数据清0* 参    数:无* 返 回 值:无*/
void Store_Clear(void)
{for (uint16_t i = 1; i < STORE_COUNT; i ++)			//循环STORE_COUNT次,除了第一个标志位{Store_Data[i] = 0x0000;							//SRAM数组有效数据清0}Store_Save();										//保存数据到闪存
}
12.2.4 Strore.h
#ifndef __STORE_H
#define __STORE_Hextern uint16_t Store_Data[];void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);#endif
12.2.5 main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"uint8_t KeyNum;					//定义用于接收按键键码的变量int main(void)
{/*模块初始化*/OLED_Init();				//OLED初始化Key_Init();					//按键初始化Store_Init();				//参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失/*显示静态字符串*/OLED_ShowString(1, 1, "Flag:");OLED_ShowString(2, 1, "Data:");while (1){KeyNum = Key_GetNum();		//获取按键键码if (KeyNum == 1)			//按键1按下{Store_Data[1] ++;		//变换测试数据Store_Data[2] += 2;Store_Data[3] += 3;Store_Data[4] += 4;Store_Save();			//将Store_Data的数据备份保存到闪存,实现掉电不丢失}if (KeyNum == 2)			//按键2按下{Store_Clear();			//将Store_Data的数据全部清0}OLED_ShowHexNum(1, 6, Store_Data[0], 4);	//显示Store_Data的第一位标志位OLED_ShowHexNum(3, 1, Store_Data[1], 4);	//显示Store_Data的有效存储数据OLED_ShowHexNum(3, 6, Store_Data[2], 4);OLED_ShowHexNum(4, 1, Store_Data[3], 4);OLED_ShowHexNum(4, 6, Store_Data[4], 4);}
}

现象:Flag显示闪存的第一个标志位 Data中的数据 每按下一次按键 第一个数据自增1 第二个数据自增2 依次类推 自增后调用Store_Save 将数据全部存储到闪存中 于是每次上电后 函数初始化会将闪存中的数据读取到数组中并显示在OLED屏 实现掉电不丢失

13.读取芯片ID

13.1 接线图

13.2 相关代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"int main(void)
{OLED_Init();						//OLED初始化OLED_ShowString(1, 1, "F_SIZE:");	//显示静态字符串OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);		//使用指针读取指定地址下的闪存容量寄存器OLED_ShowString(2, 1, "U_ID:");		//显示静态字符串OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);		//使用指针读取指定地址下的产品唯一身份标识寄存器OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);while (1){}
}

 

现象:显示屏第二行以16位半字显示地址 低端先行 所以从左到右是001E 002C 第三行以32位显示则为30313432 第四行也是32位 即42313130

这篇关于STM32标准库——(21)Flash闪存的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/792322

相关文章

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

21.手绘Spring IOC运行时序图

1.再谈IOC与 DI IOC(lnversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建、依赖的代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让 容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们所看到的配置文件。 DI(Dependency Injection)依赖注入:就是指对象是被动接受依赖类

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述。以下是从不同角度对气象站的种类和应用范围的介绍: 一、气象站的种类 根据用途和安装环境分类: 农业气象站:专为农业生产服务,监测土壤温度、湿度等参数,为农业生产提供科学依据。交通气象站:用于公路、铁路、机场等交通场所的气象监测,提供实时气象数据以支持交通运营和调度。林业气象站:监测林区风速、湿度、温度等气象要素,为林区保护和

C++标准模板库STL介绍

STL的六大组成部分 STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,提供了丰富的通用数据结构和算法,使得 C++ 编程变得更加高效和方便。STL 包括了 6 大类组件,分别是算法(Algorithm)、容器(Container)、空间分配器(Allocator)、迭代器(Iterator)、函数对象(Functor)、适配器(Adapter)

6月21日训练 (东北林业大学)(个人题解)

前言:   这次训练是大一大二一起参加的训练,总体来说难度是有的,我和队友在比赛时间内就写出了四道题,之后陆陆续续又补了了三道题,还有一道题看了学长题解后感觉有点超出我的能力范围了,就留给以后的自己吧。话不多说,上正文。 正文:   Problem:A 幸运数字: #include <bits/stdc++.h>using namespace std;int sum,ans;in

STM32单片机PWR电源控制详解

文章目录 1. PWR概述 2. 电源结构框图 3. 上电复位和掉电复位 4. 可编程电压监测器 5. 低功耗模式 6. 模式选择 6.1 睡眠模式 6.2 停止模式 6.3 待机模式 7. 代码示例 1. PWR概述 PWR(Power Control)电源控制,负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能。 可编程电压监测器

google gemini1.5 flash视频图文理解能力初探(一)

市面能够对视频直接进行分析的大模型着实不多,而且很多支持多模态的大模型那效果着实也不好。 从这篇公众号不只是100万上下文,谷歌Gemini 1.5超强功能展示得知,Gemini 1.5可以一次性处理1小时的视频、11小时的音频或100,000行代码,并衍生出更多的数据分析玩法。能力覆盖: 跨模式理解和推理,当给出一部 44 分钟的巴斯特-基顿(Buster Keaton)无声电影时,该模型能准

STM32学习之一:什么是STM32

目录 1.什么是STM32 2.STM32命名规则 3.STM32外设资源 4. STM32的系统架构 5. 从0到1搭建一个STM32工程 学习stm32已经很久了,因为种种原因,也有很久一段时间没接触过stm32了。等我捡起来的时候,发现很多都已经忘记了,重新捡起来吧。 每次谈到stm32如何如何,那么该如何解释什么是stm32呢? 1.什么是STM32 stm32

Ubuntu安装火狐Flash Player插件

1、进入官网下载页面选择:.tar.gz,适用于其他Linux。此次文件名为install_flash_player_11_linux.x86_64.tar.gz 2、解压文件 tar -zxvf install_flash_player_11_linux.x86_64.tar.gz 3、利用whereis命令查找mozilla文件夹的路径 whereis mozill

STM32学习 修改系统主频

前面时钟树的学习说明单片机的主频是可以修改的,那么怎么更改系统的主频,这里做一个简单的介绍。首先要明白,单片机的程序是如何运行,这里简单说明一下。 对应的代码在startup_stm32....文件里面,这里是复位程序的汇编代码。 复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数 _mian,最终调用 main 函数去到 C