STM32嵌入式开发-JKX笔记
10-3软件IIC,手动反转两个GPIO口的电平首先建立IIC通信层的.c和.h模块,在通信层里,写好IIC底层的GPIO初始化和六个时序基本单元,也就是起始、终止、发送一个字节、接收一个字节、发送应答和接收应答。写好IIC通信层之后,再建立MPU6050的.c和.h模块。在这一层,我们将基于IIC通信的模块,来实现指定地址读、指定地址写,再实现写寄存器对芯片进行配置,读寄存器得到传感器数据。最终
STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili
忘记点发布结果昨天写的全部丢失了,10-4到11-1全没了,麻了。为什么已发布的博客不能点保存草稿!!!
目录
一、简介
Ⅰ课程简介
1-1
略
Ⅱ、STM32简介
1-2
本节,我们把STM32的基本情况、ARM内核、参数、外设、命名规则、系统结构、引脚定义、启动配置、最小系统都介绍了
所用STM32

这里的RAM就是运行内存,实际的存储质是SRAM。
这里的ROM就是程序存储器,实际的存储介质是Flash闪存。
如果是5V电压,那需要加一个稳芯片,把电压降到3.3V,再给STM32供电。

外设(peripheral)。前两个深色的是内核里面的外设。
13:30——18:50 各外设简略介绍。
以上是STM32F1整个系列的所有外设,芯片具体有哪个外设要看手册。


引脚相关介绍在34:11之前。

这个启动配置的作用就是指定程序开始运行的位置。一般情况下,程序都是在Fash程序存储器开始执行。即第一种模式,这个模式是最常用的模式。
系统存储器被选为启动区域时,这个模式就是用来做串口下载用的。这个系统存储器存的就是STM32中的一段BootLoader程序,BootLoader程序的作用就是接收串口的数据,然后刷新到主闪存中,这样就可以使用串口下载程序了。一般我们需要串口下载程序的时候会配置到这个模式上。
内置SRAM被选为启动区域时,主要是用来进行程序调试的,现阶段用的比较少。
BOOT引脚的值是在上电复位后的一瞬间有效的。

一般来说,我们单片机只有一个芯片是无法工作的,我们需要为他连接最基本的电路,这些最基本的电路就叫做最小系统电路。
在供电的3.3V和GND之间,一般会连接一个滤波电容,这个电容可以保证供电电压的稳定。
晶振电路接了一个8MHz的主时钟晶振,STM32的主晶振般都是8MHZ,8MHz经过内部锁相环倍频,得到72MHz的主频。这个晶振的两根引脚分别通过这两个网络标号,接到STM32的5、6号引脚。另外还需要接两个20pF的电容,作为启震电容,电容的另一端接地即可。
如果需要RTC功能的话,还需要再接一个32.768KHz的晶振,电路和这个一样,接在3、4号引脚。这个OSC32就是32.768KHZ晶振的意思,32768是2的15次方。内部RTC电路经过2的15次方分频,就可以生成1秒的时间信号了。
复位电路是一个10k的电阻和0.1uF的电容组成的,它用来给单片机提供复位信号,这中间的NRST接在STM32的7号引脚。NRST是低电平复位的,当这个复位电路在上电的瞬间,电容是没有电的。

二、软件安装与新建工程
Ⅰ、软件安装

1.安装支持包
支持包安装有两种安装方式,一种是离线安装,一种是在线安装。
离线安装:选中对应支持包双击安装即可。

在线安装:点击keil中的此绿色按钮。从官网获取可安装的支持包,加载速度较慢。下载完成后提醒重新加载支持包,点是。



2.新建工程
Project——New ~ Project——建立工程文件——选择芯片

Ⅱ、新建工程
1.STM32的三种开发方式
目前STM32的开发方式主要有基于寄存器的方式、基于标准库也就是库函数的方式和基于HAL库的方式。
基于寄存器的方式是用程序直接配置寄存器,来达到我们想要的功能。这种方式最底层、最直接、效率会更高一些。但是由于STM32的结构复杂、寄存器太多,所以基于寄存器的方式目前是不推荐的。
基于库函数的方式是使用ST官方提供的封装好的函数,通过调用这些函数来间接地配置寄存器。由于ST对寄存器封装的比较好,所以这种方式既能满足对寄存器的配置,对开发人员也比较友好,有利于提高开发效率。
我们本课程使用的就是库函数的开发方式。
最后一个基于HAL库的方式可以用图形化界面快速配置STM32。这个比较适合快速上手STM32的情况,但是这种方式隐藏了底层逻辑,如果你对STM32不熟悉,基本只能停留在很浅的水平。所以目前暂时不推荐HAL库,但是推荐你学过标准库之后,去了解一下这个方式,毕竟这个HAL库还是非常方便的。
那使用库函数的方式,我们需要准备一个STM32库函数的压缩包。并解压得到库函数的文件夹目录。





第一个文件夹是俩图片没用。
第二个Libraries里面就是库函数的文件了,我们之后建工程时会用到。
第三个Project是官方提供的工程示例和模板,以后使用库函数的时候可以参考一下。
第四个Utilities是STM32官方评估板的相关例程。这个评估板就是官方用STM32做的一个小电路板,用来测评STM32的。这个文件夹里面存的就是这个小电路板的测评程序。
接下来后面两个文件,一个是库函数的发布文档,一个是使用手册。
2.新建工程
接下来我们就正式开始新建一个基于标准库的工程:
首先,我们需要先建立一个存放工程的文件夹。起个名字,可以叫STM32Project。以后我们的工程都存在这个文件夹下,这样比较方便管理。

接着,我们打开Keil5软件,点击Project——New uVision Project

然后,选择我们刚才新建的文件夹,在这里要再新建一个文件夹,用来存放本次的工程。起个名字,叫2-1 STM32工程模板。

然后,点进去,接下来给工程文件起个名字,在这里,我们可以起个通用一点的名字,这个工程是干啥的我们可以在文件夹名称说明,文件夹的名称是很方便改的,这个名称以后不太方便改,所以我们就起个Project的名称,然后点击保存。

接下来选择器件型号,我们的芯片型号的STM32F103C8T6,所以这里选择STM32F103C8这个,点击OK。

这里弹出的是Keil软件的一个新建工程小助手,这个可以帮助我们快速新建工程。我们暂时不用这个小助手,可以把它关掉。

接下来工程就建好了,但是这里的工程文件是空空如也,现在这个工程还是不能直接用的,我们需要给它添加一些工程的必要文件。

打开固件库的文件夹。打FLibraries, CMSlS,CM3, Devicesupport,ST,STM32F10x,startup, arm。这些就是STM32的启动文件,STM32的程序就是从启动文件开始执行的。我们把这些文件全部都复制下来,然后回到工程模板文件夹里。


这些就是我们刚才新建工程自动生成的文件。

如果直接把启动文件也放在这里,就有点太乱了。所以我们需要新建一个文件夹,可以叫做Start,然后把启动文件粘贴到这里面。


接着我们回到固件库的STM32F10x文件夹,可以看到stm32f10x.h和两个system开头的文件。
这个stm32f10xh,就是STM32的外设寄存器描述文件。它的作用就跟51单片机的头文件REGX52.H一样,是用来描述STM32有哪些寄存器和它对应的地址的。
这两个system文件是用来配置时钟的。STM32主频72MHz,就是system文件里的函数配置的。
我们把这三个文件复制下来,也粘贴到Start文件夹下。


接下来,因为这个STM32是内核和内核外围的设备组成的,而且这个内核的寄存器描述和外围设备的描述文件不是在一起的,所以我们还需要添加一个内核寄存器的描述文件。
我们可以打开CM3,CoreSupport,这两个cm3(Cortex-M3)文件就是内核的寄存器描述。当然他还带了一些内核的配置函数,所以多了个.c文件,我们把它俩一并复制下来,也粘贴到Start文件夹下。
到此为止,我们工程的必要文件就复制完成了。


然后我们回到Keil软件,把我们刚才复制的那些文件添加到工程里来。我们可以点击选中这个Source Group,然后再单击一下,把这个组改一下名字,也叫Start。


接着右键,选择添加已经存在的文件到组里来。

打开Start文件夹,把下面这个文件过滤器选择All files,这样我们就能看到文件实里的所有文件了。

我们首先添加一下启动文件,这个启动文件有很多分类,我们只能添加其中一个。我们视频所用型号需要选择这个后缀为md.s的启动文件。至于启动文件这个怎么选择,我等会再讲。我们选中它,点击add。
然后剩下的.c和.h文件都要添加进来。我们可以按住Ctr键,然后依次选择它们,点击Add,然后Close。


这样,我们Start文件夹里面的文件就添加好了。这里的文件都是STM32里最基本的文件,是不需要我们修改的,我们添加进来即可。文件图标上带了个小钥匙,这个意思是它是个只读的文件,文件内容都是不让我们修改的。

最后我们还需要在工程选项里添加上这个文件夹的头文件路径,要不然软件是找不到.h文件的。
我们点击这个魔术棒按钮,打开工程选项,在C/C++里。

找到这个Include Paths栏,然后点击右边三个点的按钮

在这里新建路径,然后再点三个点的按钮,把Start的路径添加进来。



点击OK,这样就把这个文件夹的头文件路径添加进来了。


接下来我们再新建一个main函数,看看这个工程是不是可行。
我们打开工程文件夹,然后新建一个文件夹,叫做User,我们的main函数就放在这个文件夹里。

然后Keil里,在Target这里右键,点击添加组。改个名字叫User。



然后在User上右键,点击添加新文件。

选择C文件,名字叫main。下面的路径注意一下啊,要选择User文件夹,要不然默认是放在文件夹外面的。然后点击Add,这样我们就有了main.c文件了。




在工程文件夹的User目录下,也可以看到我们新建的main.c文件。

在这个main.c里,我们先右键,插入头文件,选择stmm32f10x.h。然后写一个main函数。这里注意main函数是一个int型返回值,void参数的函数。
文件的最后一行必须要是空行,要不然会报警告。
然后我们点击编译并建立工程


可以看到编译后下面提示的是0错误0警告。这个工程目前还没有添加STM32的库函数,所以它还是一个基于寄存器开发的工程。如果你想用寄存器开发STM32,那工程建到这里就可以了。

接下来,我先给大家演示一下如何通过配置寄存器来完成点灯的操作。当然直接操作寄存器的方式不是我们本课程的重点,了解一下即可。
字体大小及编码格式。


按图连接STLink

在Keil中配置一下调试器。
点击魔术棒按钮,选择Debug,这个调试器默认是ULINK,我们用的是STLINK,所以选择ST-Link Debugger。


然后再点击右边的设置按钮,在Flash下载这一项,把这个Reset and Run这一项勾上。勾上这一项之后,我们下载程序后会立马复位并执行,这样方便一些。否则每次下载之后,还需要按一下板子上的复位按键才能执行程序。
配置好调试器之后点击确认、ok,然后重新编译一下,没有报错。


再点击这个LOAD按钮,如果一切正常的话,这个程序就会下载到STM32里面了。

接下来,我们就配置一下寄存器,来点亮这个灯。只需要配置三个寄存器就可以点亮这个灯了。
我们打开STM32的参考手册,首先是RCC的一个寄存器,来使能GPIOC的时钟,GPIO都是APB2的外设,所以在这个APB2外设时钟使能寄存器RCC_APB2ENR里面配置。这里有个IOPCEN,这一位就是使能GPIOC的时钟的,这一位写1就是打开GPIOC的时钟。其他的无关项都先给0,二进制转换为16进制写入即可,这样就可以打开GPIOC的时钟了。


然后第二个寄存器,我们需要配置一下PC13口的模式。
我们可以找到端口配置高寄存器GPIOx_CRH,这个X可以是A到E的任何一个字母。然后右边的CNF13和MODE13就是用来配置13号口的。
这个CNF我们需要配置为通用推挽输出模式,也就是这两位为00。
MODE要配置为输出模式,最大速度可以给50MHz,也就是这两位为11。
最后我们对照上面的寄存器,这四位为0011,其他的我们也都给它配置为为0,这样整个寄存器的值换算成16进制就是00300000。

写入keil,接下来我们就可以给PC13口输出数据了。

我们可以看到这个端口输出数据寄存器GPIOX ODR,中间有一位ODR13,这一位写1,13号口就是高电平,写0就是低电平.如果写1的话,ODR的值就是00002000。

那在这里我们写上GPIOC的ODR=0x00002000;因为这个灯是低电工点亮的,所以我们给ODR全为0就是亮,给ODR 0x00002000就是就是灭。

这就是配置寄存器的方式进行点灯的操作,可以看出来,这种方式需要不断地查手册来了解每个寄存器的每一位都是干啥的。而且这个操作方式也有个弊端,就是我们把除了PC13之外的位都配置成了0,这样就会影响到其他端口的原有配置,如果要做到只配置PC13而不影响其他位,那还需要&=和|=的操作。所以这种寄存器的操作方式,虽然代码简洁,但是还是不太方便。
那接下来我们就要为这个工程添加库函数了,看看库函数和寄存器的操作方式有哪些区别。
我们打开工程文件实,在这里新建一个文件卖,叫Library,用来存放库函数。

接着打开固件库的文件夹,打开Libraries,STM32标准外设驱动,src,这些就是库函数的源文件。
这个misc是内核的库函数,其他的就是内核外的外设库函数了。这个misc就是混杂的意思。
我们按Ctrt+A全选,然后复制,在Library文件夹下粘贴。
然后再打开固件库的inc文件夹,这些是库函数的头文件,我们继续Ctrl+A全选,然后复制,在Library文件夹下粘贴。



接着回到keil软件,同样,在Target处右键,然后添加组,改个名字叫Library。

再右键,添加已经存在的文件,打开Library,Ctrl+A, Add,这样就把所有的库函数文件都添加进来了。

但是对于这个库函数来说,现在还不能直接使用,我们需要再添加一个文件。
我们打开固件库文件夹,打开Project,STM32Template。可以看到stm32f10x_confh和两个it结尾的文件。
这个conf(configuration)文件是用来配置库函数头文件的包含关系的,另外这里面还有个用来参数检查的函数定义,这是所有库函数都需要的
三、GPIO通用输入输出口
四、OLED显示屏





stm32的引脚上电后,如果不初始化,默认是浮空输入的模式。在这个模式下,引脚不会输出电平,所以不会有什么影响。
用GPIO口直接给OLED供电,这个也是没问题的。因为这个OLED功率很小,所以也是可以驱动的。不过这样不规范,己自玩玩的时候用就行了,要做实际项目的话最好还是用电源供电。
调试

左边的这个窗口是寄存器组和状态标志位等信息,这个是单片机硬件底层很重要的东西。
调试功能用到的操作及各窗口:
![]()
十、IIC通信协议
Ⅰ、软件IIC原理


Ⅱ、软件IIC程序
10-3 软件IIC读写MPU6050
软件IIC,手动反转两个GPIO口的电平
整体框架:
首先建立IIC通信层的.c和.h模块,在通信层里,写好IIC底层的GPIO初始化和六个时序基本单元,也就是起始、终止、发送一个字节、接收一个字节、发送应答和接收应答。
写好IIC通信层之后,再建立MPU6050的.c和.h模块。在这一层,我们将基于IIC通信的模块,来实现指定地址读、指定地址写,再实现写寄存器对芯片进行配置,读寄存器得到传感器数据。
最终在main.c里,调用MPU6050的模块,初始化,拿到数据,显示数据,这就是程序的整体架构。

流程:
IIC.c
1.GPIO初始化:PB10、PB11初始化为开漏输出模式,SCL和SDA置高电平释放总线
2.编写IIC的六个时序基本单元 函数
注意点:
(1)对引脚操作的封装和改名(给端口换个名字方便修改):宏定义或再套个函数(定义一个函数,对操作端口的库函数进行封装)

![]()



(2)按位发送:通过 单独与上某一位、for循环按位右移 来实现

(3)遍历
MPU6050.c
将IIC.c中的函数封装为发送和接收两个函数。
初始化:初始化第一步,配置电源管理寄存器,我们一般会用宏定义先把寄存器的地址都用一个字符串来表示,要不然每次都查手册比较麻烦。而且光写一个数据的地址放这,也不容易理解。
寄存器如果比较少的话,可以直接在源文件最上面进行宏定义。

如果比较多的话,我们可以再新建一个单独的头文件存放。省的放在这里比较占地方。
这个宏定义和手册是对应的,比如电源管理寄存器1,地址是0x6B。个人习惯:模块的东西,我一般都习惯加一个模块名字当前缀。


配置完之后,陀螺仪内部就在连续不断的进行数据转换了,输出的数据,就存放在这里的数据寄存器里。

接下来想要获取数据的话,只要在写一个获取数据寄存器的函数即可。
多返回值函数:1.定义六个全局变量
2.用指针实现变量的地址传递
3.用结构体


main.c
同过MPU6050.c封装函数的读写寄存器。
寄存器也是一种存储器,只不过是普通的存储器只能写和读,里面的数据并没有赋予什么实际意义,但是寄存器就不一样了。寄存器的每一位数据,都对应了硬件电路的状态,寄存器和外设的硬件电路,是可以进行互动的。所以,程序到这里,我们就可以通过寄存器来控制电路了。

Ⅲ、硬件IIC原理
10-4
根据数据移位寄存器和数据寄存器是否清空或者占满,置标志位表示传输完成。


Ⅳ、硬件IIC程序
10-5
思路同软件,不过执行六个时序基本单元的函数都已经封装好了,直接调用并配置参数即可。
十一、SPI通信协议

Ⅰ、软件SPI原理
11-1
SPl的数据收发,都是基于字节交换,这个基本单元来进行的


11-2

写入数据会先写到页缓存区里,再往flash里写,因为缓存区是ram,速度很快,能跟得上spi。而flash由于是掉电不丢失,写入速度较慢。但此缓存区只有256字节。
W65Q24的容量是8MB。一整块存储空间,先划分为若干个块(Block),其中每一块在划分为若干个扇区(Sector),对于每个扇区,内部又可以分为很多页(Page)。
W65Q24的地址宽度是24位,三个字节。000000h-7FFFFFh,24位地址,最大寻址范围为16MB,但我们这个芯片只有8MB,所以地址空间,我们只用了一半,所以不是FFFFFFh。
总共8MB,每64KB划分为一块,(8*1024)/64=128,即块0-块127,块0:000000h-00FFFFh。FFFF=65535相当于64kb(64kb/页)的最后一位。
一块是64KB,每4KB划分为一个扇区,64/4=16,即扇区0-扇区15,每个扇区内的地址范围是xxx000h-xxxFFFh
4kb就是2的12次方,也就是3个十六进制数
一个扇区是4KB,每256个字节划分为1页,(4*1026)/256=16,每个页的地址范围是xxxx00h-xxxxFFh
1KB==2^10Bytes=1024Bytes,部分存储设备厂商为简化计算,使用 1 KB=1000 B(如硬盘标称容量),但操作系统和程序开发中一律采用 1024 进制。
24位地址三级寻址结构--块-扇-页,前16位(2B)指定在哪个块的哪个扇,后8位(1B)指定在哪页(扇内偏移)


不能跨页写数据,可跨页读。
Flash的擦除,有最小擦除单元的限制。你不能指定某一个字节去擦除,要擦就得一大片一起擦。此芯片最小擦除单元是一个扇区(4KB,即4096个字节)。
弹幕:这就是4k对齐,这也是为什么我们电脑中文件很小时也会占用大于文件本身的空间。
若此扇区中仍有其他数据,要想数据不丢失,就只能将4096个字节全都读出来,擦除,改写完毕后再写回去。或其他方法优化。

Ⅱ、软件SPI程序
11-3
整体框架:
先建一个MySPI的模块,这个模块主要包含通信引脚封装、初始化,以及SPI通信的三个拼图:起始、终止和交换一个字节,这是SPI通信层的内容。
然后基于SPI层,我们再建一个W25Q64的模块。在这个模块里,调用底层SPl的拼图,来拼接各种指令和功能的完整时序,比如写使能、擦除、页编程、读数据等等,所以这一块可以叫作W25Q64的硬件区动层。
最后,在主函数里,我们调用驱动层的函数,来完成我们想要实现的功能。

流程:
BitAction是个枚举类型的变量有两个取值Resetset
ByteSend <<= 1:左移等于1相当于ByteSend = ByteSend << 1;
用死循环进行等待时,为防止意外卡死,可以设置一个变量减减,为0退出。

Ⅲ、硬件SPI原理
11-4
SPI和IIC都是高位先行,但UART是低位先行。

这两个缓冲区,实际上就是数据寄存器DR。
和串口那里一样,TDR和RDR占用同一个地址,统一叫作DR。
15:00之前的逻辑
如果我们需要连续发送一批数据,第一个数据,写入到TDR。当移位寄存器没有数据移位时,TDR的数据会立刻转入移位寄存器,开始移位,这个转入时刻,会置状态寄存器的TXE为1,表示发送寄存器空。当我们检查TXE置1后,紧跟着,下一个数据,就可以提前写入到TDR里候着了,一旦上一个数据发完,下一个数据就可以立刻跟进,实现不间断的连续传输。
然后移位寄存器这里,一旦有数据过来了,它就会自动产生时钟,将数据移出去。在移出的过程中,MISO的数据也会移入,一旦数据移出完成,数据移入是不是也完成了。这时,移入的数据就会整体地从移位寄存器转入到接收缓冲区RDR,这个时刻,会置状态寄存器的RXNE为1,表示接收寄存器非空。当我们检查RXNE置1后,就要尽快把数据从RDR读出来,在下一个数据到来之前,读出RDR,就可以实现连续接收。否则,如果下一个数据已经收到了,上一个数据还没从RDR读出来,那RDR的数据就会被覆盖,就不能实现连续的数据流了。这就是移位寄存器配合数据寄存器实现连续数据流的过程。

TDR和RDR的配合,可以实现连续的数据流。
TDR数据,整体转入移位寄存器的时刻,置TE标志位
移位存器数据,整体转入RDR的时刻,置RXNE标志位

Ⅳ、硬件SPI程序
11-5
由于软件SPI程序是隔离封装的,所以可以直接修改底层的MySPI.c,而不用动W25Q64.c
整体框架:
硬件SPI的代码,实际上就是两部分:
第一部分,是写上SPI外设的初始化代码。
第二部分,是写上SPI外设操作时序,完成交换一个字节的流程。

流程:
SPI初始化流程:
1.开启时钟,开启SPI和GPIO的时钟。
2.初始化GPIO口。其中SCK和MOSl,是由硬件外设控制的输出信号,所以配置为复用推挽输出。MISO是硬件外设的输入信号,我们可以配置为上拉输入。因为输入设备可以有多个,所以不存在复用输入这个东西,直接上拉输入就行,普通GPIO口可以输入,外设也可以输入。最后,还有SS引脚。SS,是软件控制的输出信号,所以配置为通用推挽输出。
3.配置SPI外设。这一块,使用一个结构体选参数即可。调用一下SPl_Init,这里面的各种参数,比如,8位/16位数据帧、高位先行/低位先行。
4.开关控制。调用SPI_Cmd,给SPI使能即可。
通过硬件SPI交换一个字节的程序:
第一步,等待TXE为1;
第二步,写发送的数据至TDR,一旦TDR写入数据了,时序就会自动生成;
第三步,等待RXNE为1,发送完成,即接收完成,RXNE置1;
第四步,读取RDR接收的数据,就是置换接收的一个字节。
在这里面,我们并不需要像软件SPI那样,手动给SCK、MOSI置高低电平,也不用关心怎么把数据位一个个取出来,这些工作,硬件电路会自动帮我们完成。
硬件SPI必须是发送,同时接收。要想接收,必须得先发送。因为只有你给TDR写东西,才会触发时序的生成
TXE和RXNE会分别在发送和接收数据后自动清除。

TXE=1,发送寄存器(缓冲区)为空,TXE=0,发送寄存器有数据,移位寄存器为空,数据进入移位寄存器进行发送,之后接收外部数据,进入移位寄存器,移位寄存器发送给接收寄存器,RXNE=1,接收数据
十二、RTC实时时钟
实时时钟本质上是一个定时器,但这个定时器是专门用来产生年月日时分秒,这种日期和时间信息的。

Ⅰ、Unix时间戳
12-1 Unix时间戳及相关数据类型的转换
BKP备用寄存器:如果你的STM32接了备用电池,那BKP可以完成一些主电源掉电时,保存少量数据的任务。
RTC这个复位和主电源掉电后,数据不丢失,是借助BKP实现的。
时间戳是一个计数器数值。




1.time_t time(time_t*)
第一个time函数的返回值是time_t,表示当前系统时钟的秒计数器值,参数是time_t*,这是一个输出参数,输出的内容和返回值是一样的。所以这个函数,可以通过返回值获取系统时钟,也可以通过输出参数,获取系统时钟。

以上两种写法效果是一样的,都会传回当前时间戳给time_cnt。第一钟写法中写空指针是因为它是指针要传地址,所以要写空指针。
NULL是一个宏,表示空指针常量。它通常被定义为((void*)0),表示一个指向内存地址 0 的空指针。
2.struct tm* gmtime(const time_t*)
struct tm* gmtime(const time_t*) //秒计数器转换为日期时间(格林尼治时间)
参数是const timet*,秒计数器指针类型,是输入参数;
返回值struct tm*是日期时间结构体指针类型;
##include <stdio.h>
##include <time.h>
time_t time_cnt;
struct tm time_date;
char *time_str
int main(void)
{
time_cnt = 1672588795;
time_date = gmtime(&time_cnt); //不行,指针跨级赋值,等号右边是地址,左边是变量
time_date = *gmtime(&time_cnt); //右边加上*取内容,这样等号左右就都是变量了
或者将struct tm time_date;变为struct tm *time_date; //这样等号左右就都是指针了
return 0;
}
3.strftime
自定义格式
Ⅱ、BKP及RTC
1.BKP
BKP本质上是RAM寄存器,没有掉电保存的能力。


橙色为后备区域,当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电。
2.RTC

另:
Reset and clock control (RCC):时钟配置,控制提供给各模块时钟信号的通断。

为什么要经过128分频,最多只能是1MHz,因为后面最大分频2的20次方分频就是1M。

2^15=32768,所以320768kHz=32768Hz,经过一个15位分频器的自然溢出,就能很方便的得到1Hz的频率。
一秒32768hz,那么这个频率完成2^15次计数产生溢出所用的时间就是1s,也就是1s一次自然溢出。
每32768分之1秒计数一次,然后计数32767个数,在计数就清0,此时不是刚好一秒嘛
因为2的15次方,也就是32kHz左右好加工,容易做,晶振性能好。
只要见到32.768kHz的晶振,八成就是提供给RTC的。
内部的RC振荡器,一般精度没有外部晶振高
上面那一路,主要作为系统主时钟;下面那一路,主要作为看门狗时钟。中间那路作为RTC的时钟,且只有这路可以通过VBAT备用电池供电。其他两路作为备用选项。

每来32768个输入脉冲,计数器溢出一次,产生一个输出脉冲,这就是32768分频了,分频输出后的时钟频率为1Hz,提供给后续的秒计数器。

左边这一块是核心的分频和计数计时部分;右边这一块是中断输出使能和NVIC部分;上面这一块是APB1总线读写部分;下面这一块是和PWR关联的部分。
分频器其实就是一个计数器,记几个数溢出一次就是几分频。
所以对于可编程的分频器来说,需要有两个寄存器,一个寄存器用来不断地计数,另一个寄存器,我们写入一个计数目标值,用来配置是几分频。
这些F(Flag)结尾的是对应的中断标志位;IE(lnterrupt Enable)结尾的是中断使能。



好像手册说APB1的时钟必须和RTC时钟是同步开始的才可以正常读RTC的寄存器。
因为代码执行用的高速晶振,而RTC用的低速晶振,所以要等,就这么简单。
Ⅲ、程序
12-3 读写备份寄存器&实时时钟
BKP模块较短,省略记录。
13:50
RTC整体框架:


1.开启PWR和BKP时钟,使能BKP和RTC的访问;
2.启动RTC的时钟。这里,我们计划使用LSE作为系统时钟,所以,我们要使用RCC模块里的函数,开启LSE的时钟。LES默认关闭;
3.配置RTCCLK这个数据选择器,指定LSE为RTCCLK
4.调用两个等待的函数,一个是等待同步,一个是等待上一次操作完成
5.配置预分频器,给PRL重装寄存器一个合适的分频值,以确保输出给计数器的频率是1HZ
6.配置CNT的值,给RTC一个初始时间
7.如果需要,可以配置闹钟值或中断
零碎知识点:
在C语言中,前面补0的数字,代表是八进制。如01代表八进制的1,没影响。但如果是09,就会出错,因为在八进制中,数字9是无效的。同理,if(123 == 0123)是不成立的。
.c中定义的全局变量放到.h中再加上extern(数组不加也行 )即可声明为外部可调用,进而可使用全局变量传参,如:
可利用BKP避免重复初始化重置初值,如:在BKP中写一个标志位,主电源断电备用电源不断电时标志位不为0,主电源和备用电源均断电时标志位清零,此时可进行初始化。
十三、PWR电源控制
Ⅰ、低功耗模式
13-1 PWR电源控制
低功耗模式简介及配置:
四个程序:



低功耗模式,我们要考虑:关闭哪些硬件,保留哪些硬件,以及如何去唤醒。这也是这3种低功耗模式在设计时,所区分的地方。

可分为三部分:
最上面是模拟部分供电,叫做VDDA(VDD Analog)。
中间是数字部分供电。包括两块区域,VDD供电区域和1.8V供电区域。
下面是后备供电,叫做VBAT(V Battery)。
使用低电压运行的主要目的是降低功耗,电压越低,内部电路运行的功耗就相对越低。如STM32内部的大部分关键点路都是以1.8V的低电压运行的。要与外界通信时再调整为3.3V.



低功耗状态下,只有外部中断可以唤醒停止模式,其他这些设备像唤醒停止模式的话,都可以通过借道外部中断来实现。

这三种模式从上到下关闭的电路越来越多,对应的也越来越省电,越来越难唤醒。
WFI和WFE是内核的置零,有库函数可以直接调用。WFI:wait for interrupt(等待中断)。WFE:wait for event(等待事件)。
睡眠模式只把CPU时钟关了,程序暂停,不再继续运行。
关闭电路通常有两种做法:
一个是关闭时钟,所有的运算和涉及时序的操作都会暂停,但是寄存器和存储器里面保存的数据还可以维持,不会消失;
另一个是关闭电源,就是电路直接断电,电路的操作和数据都会直接丢失。所以关闭电源比关闭时钟更省电。电压调节器实际上就是1.8V区域的电源。
如何进入停机模式:首先,SLEEPDEEP位置1。PDDS这一位,用来区分它是停机模式还是待机模式,0为停机模式,1为待机模式。LPDS用来设置电压调节器,LPDS=0电压调节器开启,LPDS=1电压调节器进入低功耗模式。
将这些位提前设置好后再调用WFI或者WFE即可。
待机模式:基本上能关的全关了,但它并不会主动关团LSI和LSE两个低速时钟,因为这两个时钟还要维持RTC和独立看门狗的运行,因为要保留几个唤醒的功能。
WFI/WFE这两个指令是最终开启低功耗的触发条件,配置其他的寄存器,都要在这两个指令之前

**PDS**:Power Down Sleep(掉电睡眠模式)
**LPDS**:Low Power Deep Sleep(低功耗深度睡眠模式)
三种模式的一些特性:
1.睡眠模式

睡眠后软件程序不运行,是靠原来的硬件电路来触发唤醒

2.停止模式

我们的程序,默认在Systeminit函数里面的配置,是使用的HSE外部高速时钟,通过PLL倍频,得到72MHz主频。但是进入停止模式后,PLL和HSE都停止了,而且在退出停止模式时,它并不会再自动帮我们开启PLL和HSE,而是默认用HSI的8MHz直接作为主频。
所以如果忽略了这个问题,就会出现一个现象,你程序刚上电时,是72MHz的主频。但是进入停止模式再唤醒之后,就变成8MHz的主频了。
所以我们一般在停止模式唤醒后,第一时间就是重新启动HSE,配置主频为72MHz。
3.待机模式

实际上GPIO的配置里,没有高阻态这个配置,它其实就是浮空输入配置,浮空输入对于输出而言就是高阻态,GPIO对外不输出高低电平,也不流过电流。
假设正常运行电流是30mA,停机模式电流是30μA,待机模式是3μA。那么对于一个300mAh的电池来说,正常运行能用10个小时,停机模式能用1万个小时,待机模式能用10万个小时。
Ⅱ、代码部分
1.时钟相关代码(修改主频)
(1)配置原理
system_stm32f10x.c和system_stm32f10x.h。
这两个system文件是用来配置系统时钟的,也就是配置RCC时钟树。


这个图就是RCC时钟树的全部电路,左边是四个时钟源:HSI、HSE、LSE、LSI,用于提供时钟;右边就是各个外设,就是使用时钟的地方;我们用的最多的就是AHB时钟,APB1时钟和APB2时钟;另外还有一些外设,他们的时钟不是来自AHB和APB,比如IIC的时钟直接来自SYSCLK、USB的时钟直接来源于PLL。
我们主要关心的就是,外部的8MHz晶振,如何经过选择、如何倍频才能得到这个72MHz的SYSCLK(系统主频),然后系统主频如何去分配,才能得到AHB,APB1和APB2的时钟频率。

在我们之前的课程里,我们一直保持了默认的配置:晶振接的是8MHz、主频是72MHz、AHB和APB2是72MHz、APB1是36MHz。但其实这些都不是绝对的,可以根据自己的需求进行更改。当然一般还是不要改,因为目前绝大多数程序都是按照默认的配置来写的,随意更改可能会造成一些问题。
(2)文件脉络
1.system_stm32f10x这两个文件提供了两个外部可调用的函数和一个外部可调用的变量(可看注释或头文件),两个函数是SystemInit和SystemCoreClockUpdate,一个变量是SystemCoreClock。
①函数 SystemInit,这个函数用来配置时钟树,也是这个文件最主要的,使用HSE配置主频为72MHz,就是这个函数干的活。并且这个函数,在复位后,执行main函数之前,在启动文件里自动调用了,所以main函数一进来,时钟就配置好了,不用我们操心。
②变量 SystemCoreClock,表示主频频率的值,我们想知道目前的主频是多少,直接显示一下这个变量就行。
③函数 SystemCoreClockUpdate,这个是用来更新上面这个变量的,因为这个变量只有最开始的一次赋值,之后如果我们改变了主频频率,这个值不会自动跟着变换,所以我们就需要调用一下这个函数,根据当前时钟树的配置,更新一下上面这个变量。
2.解除对应的注释来选择想要的系统主频

①如果你使用的是VL这些设备,也就是超值系列,那可选主频只有两个:24MHz和HSE的8MHz。
否则的话,以下均可选。当前解除注释的是72MHz,所以主频默认就是72MHz。
②#if 叫做预编译,主要是用来兼容不同型号的设备。意思就是如果我们define了后面的东西,那问个文件就是if这里有效。
③
在文件列表里,这些文件的图标带了个钥匙符号,就代表它是只读的。解除只读的方法:打开工程文件夹,或在选项卡这里右键,点击打开包含的文件夹,这样它就可以直接打开文件实并定位到这个文件。之后右键点击.c文件,把只读选项的√去掉,点确定即可。也可对整个文件夹或整个工程这样操作。
3.执行配置主频缩略流程
①选择外部8MHz晶振作为锁相环输入,锁相环执行9倍频,输出的72MHz选择为SYSCLK,这就是库函数默认的时钟配置,这个SystemInit函数就是执行此流程。
锁相环是用来把晶振产生的频率倍频并提供给内部或外设使用,另外晶振有时不稳定,锁相环可以反馈调节保证时钟精度。
②画图总结:
我们简单的修改一个宏定义,它是如何作用于电路的配置的呢?SystemInit都是直接操作寄存器的写法。
首先是systeminit函数。进来首先启动HSI,之后就是各种回复缺省配置,最后,调用SetSysClock。SetSysClock是一个分配函数,根据我们前面接触注释的宏定义,选择执行不同的配置函数,比如SetSysClockTo72、To56、To48等等。最后在To72等这些函数里,才是真正的配置,比如To72的配置是,选择HSE作为锁相环输入,之后锁相环进行9倍频,再选择锁相环输出作为主频,这样主频就是72MHz了,这就是配置时钟的整个流程。

(3)修改代码
略
2.睡眠模式+串口发送+接收
(1)程序执行流程
首先程序开始,初始化,把串口配置好。之后进入主循环,检查一下标志位,Running闪烁一次,在主循环的最后执行WFI,这时CPU就会立刻睡眠,程序停在WFI指令这里。这时CPU睡眠,但是各个外设,比如USART还是工作状态,等到我们用串口助手发送数据时,USART收到数据,产生中断,唤醒CPU。睡眠模式唤醒之后,程序在暂停的地方继续运行,所以程序会先进入USART的中断函数,之后回到主循环。

3.停止模式+对射式红外传感器计次
在停止模式下1.8V区域的时钟关闭,CPU和外设都没有时钟了,但是外部中断的工作是不需要时钟的。这一点从代码里也可以看出来,因为初始化的时候根本就没有开启EXTI时钟的参数,这也是EXTI能在时钟关闭的情况下工作的原因,因为它不需要时钟。

停止模式和待机模式,都需要PWR外设干活,所以首先我们要开启PWR的时钟,随后直接调用PWR_EnterSTOPMode函数,此函数会在其中配置各种寄存器和调用指令。

如果外设没开时钟,那么写入寄存器是无效的,读取寄存器也全都是0。
进入停止模式再退出时,HSI被选为系统时钟。在首次复位后,systeminit函数里配置的是HSE*9倍频的72MHz主频,而进入停止模式再退出时,默认时钟就变成HSI了,HSI是8MHz,所以唤醒之后的程序,运行就会明显变慢。解决方式为退出停止模式后重新调用systeminit

4.待机模式+实时时钟

由手册的寄存器描述部分,寄存器下面有一排字母w,这个就是对应位的读写特征,此处为只能写入不能读出,rw为可读可写,r为只读。

耦合性:41:00
使用待机模式,一定要把外部能关的一切耗电电路都关掉,否则就无法真正的省电。
十四、WDG看门狗
Ⅰ、原理


看门狗其实就是定时器,定时器溢出产生中断,看门狗定时器溢出直接产生复位信号,而喂狗就是重置计数器(计数器递减)。
独立看门狗:时钟是专用的LSI,内部低速时钟。即使主时钟出现问题了,看门狗也能正常工作。独立看门狗只有一个最晚时间界限。
窗口看门狗:使用APB1的时钟,没有专用的时钟。喂狗的时间有一个最晚的界限,也有一个最早的界限,必须在这个界限的窗口内喂狗。
1.独立看门狗



写保护:执行指令,必须写入指定的键值。

独立看门狗超时时间也就是定时器溢出时间。0xFFF即4095。
LSI是输入时钟40KHz,所以TLSI为0.025ms。每隔0.025ms来一个输入时钟。之后输入时钟进行分频,相当于计数时间加倍。
这个公式用的是时间计算,用频率计算也一样:超时频率,就等于LSI的频率/预分频/重装值。对应定时器的话,就是72M/(PSC+1)/(ARR+1)
2.窗口看门狗

窗口看门狗没有重装寄存器,可直接在CNT写入数据来重装计数器进行喂狗。看门狗配置寄存器为窗口值,也就是喂狗的最早时间界限。
PCLK默认为36MHz。
WWDG_CR为六位计数器,最高位T6是溢出标志位,T6为0时表示溢出。
计数器减到100 0000时,转为16进制是0x40,此时,如果把T6位当作计数器的值,那计数器的值实际才减一半。但是如果把T6位当成溢出标志位,第六位当作计数器,那此时的状态就是标志位为1,计数器为00 0000,已经减到0了,再减一个0就是011 1111,此时最高位T6由1变0,代表计数器溢出,此时计数器T6就会通过线路产生复位信号。
如果把T6位看作计数器的一部分,那就是整个计数器,值减到0x40之后溢出,而如果把T6当成溢出标志位,低六位当作计数器,那就是第六位的计数值减到0之后溢出。
复位信号输出部分:
WDGA是窗口看门狗的激活位,也就是使能,WDGA写1启用看门狗。
喂狗时把当前计数值和窗口值进行比较,由于是递减计数器所以大于窗口值即复位。
喂狗太晚,六位计数器减到0了,复位。喂狗太早,计数器的值超过窗口值了,复位。


这里要多乘一个4096是因为PCLK1进来之后,其实是先执行了一个固定的4096分频,因为36MHz的频率还是太快了。只不过框图没画出来。
计算与独立看门狗类似。
3.两者对比

Ⅱ、程序
14-2
1.独立看门狗
整体框架:
独立看门狗配置流程:
1.开启LSI时钟,开启独立看门狗时LSI自动打开。
2.写入0x5555解除写保护。
3.写入预分频器和重装寄存器。具体写入多少根据根据超时时间公式来计算。
4.写入0xCCCC启动独立看门狗
5.在主程序里不断执行0xAAAA来进行喂狗。
流程:
关于流程的几点解释
1.开启看门狗并喂狗只调用各个库函数即可,本质上就是在各个寄存器中写入对应的值。

2.喂狗会启用写保护。因为在第二步已经解除了写保护,而下面的配置PR和RLR,都没有启用写保护,因为它们都没有往键寄存器中写入值。后面的喂狗和使能IWDG,才在键寄存器中写值,启用了写保护。

3.程序执行对喂狗时间要留有余量,因为独立看门狗时钟不是那么准。如喂狗时间为1000ms,那么程序执行时间可以限制在800ms之内。

4.为什么会显示while循环之前的LED显示
因为不松手会卡在while里,Key_GetNum()后面的程序都没法执行;没法及时喂狗就复位了,重新开始执行。
2.窗口看门狗
整体框架:
1.因为窗口看门狗的时钟来源是PCLK1,所以第一步,需要我们自己开启窗口看门狗APB1的时钟。
2.配置各个寄存器,比如预分频和窗口值,窗口看门狗没有写保护,所以可以直接写这些寄存器。
3.写入控制寄存器CR。控制寄存器包含看门狗使能位、计数器溢出标志位和计数器有效位。
4.之后,在运行过程中,我们不断的向计数器中写入想要的重装值,这样我们就可以不断地进行喂狗了。

流程:
1.库函数

2.因为递减计数器是自由运行状态,你在使能的时候,计数器可能是任何值。为了避免刚一使能就立马复位,所以我们在使能的时候需要顺便同时喂一下狗。在库函数里的体现就是,使能函数里也有个参数,需要指定使能的时候喂狗多少。

3.超时时间计算
因为36MHZ == 1s,那么36000KHZ就是1ms,所以换khz就可以,如果直接换成hz,那算出来的单位就是us微秒了。
计算窗口时间之前,一定要先把超时时间计算出来,因为算好超时时间了,预分频系数和T[5:0]就确定好了,下面再算窗口时间就易如反掌了。

4.寄存器W6这一位,我们也需要或上0x40,给置1
16进制数和10进制数可以进行逻辑或运算。不过计算机在计算是,会转化为2进制。
5.“读-改-写”操作

第一步,先把寄存器读到临时变量里;
第二步,用|=、&=的操作,改变临时变量的指定几位;
第三步,把临时变量写回到寄存器里。

这样做的好处是:
一、可以单独改变寄存器的某几位,而不影响其他位的值;
二、如果连续更改多次不同的位,这样的操作效率比较高;
三、所有更改的位,在最终写回到寄存器时同时生效。
6.此代码中喂狗代码不能放在延时之前,因为这样使能第一次喂狗和while中第二次喂狗时间间隔过短,会导致喂狗太快从而不断复位,喂狗代码需要放在延时之后。也可以将使能和喂狗放在一起加个if判断看是否是第一次执行。

十五、FLASH闪存
Ⅰ、原理
1.基础与结构

闪存是一个通用名词,表示非易失性存储器(也就是掉电不丢失的存储器)。本节特指STM32的内部闪存,也就是我们下载程序时程序所储存的地方。
闪存存储器接口是一个外设,是闪存的管理员,毕竟闪存的操作很麻烦,涉及到擦除、编程、等待忙、解锁等等操作。所以这里我们要把我们的指令和数据,写入到这个外设的相应寄存器,然后这个外设就会自动去操作对应的存储空间。
系统存储器是原厂写入的Bootloader程序,是不允许我们修改的。
Bootloader也就是串口下载。
IAP:我们首先需要自己写一个Bootloader程序,并且存放在程序更新时不会覆盖的地方。然后,需要更新程序时,我们控制程序跳转到这个自己写的Bootloader里来,在这里面,我们就可以接收任意一种通信接口传过来的数据,比如串口、USB、蓝牙转串口、WIFI转串口等等,这个传过来的数据,就是待更新的程序。然后我们控制FLASH读写,把收到的程序,写入到前面程序正常运行的地方。写完之后,再控制程序跳转回正常运行的地方,或者直接复位。这样,程序就完成了自我升级。这个过程,其实就是和系统存储器这个的Bootloader一样,因为程序要实现自我升级,在升级过程中,肯定需要布置一个辅助的小机器人来临时干活。只不过是系统存储器的Bootloader写死了,只能用串口下载到指定位置,启动方式也不方便,只能配置BOOT引l脚触发启动。而我们自己写Bootloader的话,就可以想怎么收怎么收,想写到哪就写到哪,想怎么启动就怎么启动。并且这整个升级过程,程序都可以自主完成,实现在程序中编程。更进一步,就可以直接实现远程升级了,非常灵活方便。

主存储器即程序存储器。
信息块中的启动程序代码就是系统存储器,存放的是原厂写入的Bootloader,用于串口下载。
信息块中的用户选择字节就是选项字节(Option Bytes),
闪存存储器接口寄存器就是一个普通的外设,和之前讲的GPIO、定时器、串口等等都是一个性质的东西,这一块的存储器实际上并不属于闪存,这些存储器的存储介质都是SRAM。这一部分是闪存的管理员,我们擦除和编程,就通过读写这些寄存器来完成。没有读取是因为:读取指定存储器,直接使用指针读即可,用不到这个外设。
擦除和写保护都是以页为单位的。基础知识详见W25Q64,是一样的。只不过这里只有一个单位,就是页,一页1K。
C8T6只有64K。
地址只要以000、400、800、C00结尾的,都一定是页的起始地址。
我们平时说的,芯片闪存容量是64K、128K,它指的只是主存储器的容量。下面信息块的两个东西,虽然也是闪存,但是并不统计在这个容量里。

2.FLASH擦写流程
FLASH操作之前需要解锁,目的是为了防止误操作。解锁方式和之祈安的独立看门狗一样,都是通过在键寄存器写入指定的键值来实现。

STM32内部的存储器是直接挂在总线上的,所以这时再读写某个存储器就非常简单了,直接使用C语言的指针,来访问即可。

在地址前面加上了强制类型转换,转换为指针,这里如果不强制类型转化,编译器只会认为是简单的数字而已。指针的类型表示指针所指向的数据占用的内存大小。如果你想以16位的方式读出指定地址的数据,那就转换成uint16_t*;如果你想以8位的方式读出来呢,就转换成uint8_t*。
加关键字volatile的目的是防止编译器优化。这里的volatile就要求每次对于Data的存取都找到原地址,而不会去中间变量中取值。
如果开启了编译器优化,在“无意义加减变量,多线程更改变量,读写与硬件相关的存储器”时,都需要加上volatile,防止被编译器优化。
指针外加 * 号,指针取内容,把这个指针指向的存储器取出来了。这个值,就是指定存储器的值。
对于闪存的读写来说,是不需要解锁的。因为读取只是看看存储器,不对存储器进行更改。所需权限很低,不用解锁,直接就能读。
使用指针写指定地址下的存储器:先给定地址,再强转为指针,最后指针取内容。
程序运行时写闪存需要提前解锁,而SRAM可以直接读写。



详细的流程库函数已经都写好了。
控制寄存器PG位:programming。
写入操作,只能以半字的形式写入。
字:word,32位数据; 半字:halfword,16位数据; 字节:byte,8位数据。
读半字写半字是可以的,但问题是擦除必须是全页擦除,而且不擦除不能写入,麻烦在这里。


3.参考手册



就是闪存忙的时候,代码执行会暂停。因为执行代码需要读闪存,存在忙,没法读,所以CPU也就没法运行了。
这里是执行闪存的代码在读写,此时闪存被占用,CPU停止其它代码停止执行,等待读写代码的完成。
如:读写闪存时,可能会导致中断无法即使响应。如果你的程序里有需要频繁执行,且对时间要求严格的中断函数,那就要慎用这个内部闪存来存储用户数据。

Ⅱ、程序
15-2
整体框架:
创建两个工程模块。
最底层叫MyFLASH,在这里面实现闪存最基本的三个功能,也就是读取、擦写和编程。
在此模块之上在建立一个模块Store,主要实现参数数据的读写和存储管理。因为我们最终应用层想要实现的功能是,任意读写参数,并且这些参数是掉电不丢失的。
最后就是main.c里的应用层函数。想要保存参数,就写到Store层的数组,再调用保存函数,备份到闪存,这样就能实现最终功能了。
流程:
MyFLASH.c
相关函数大多为再包装一下,并没有太多操作,因为库函数已经集成的很完善了。解锁、相关库函数、加锁三步即可。

写入之前一定要先执行擦除

Store.c
1.闪存初始化,将闪存的各个参数都置零。
main.c
一般大量出现的可配置参数,或者为了强调某个数据的特殊意义,就可以用宏定义替换一下。

目前的闪存,前面一部分,存储的是程序文件,最后一页,存储的是用户数据。如果两者冲突,可能会产生非常隐蔽的bug。为了避免这个问题,我们可以给程序文件,限定一个存储范围,不让它分配到后面我们用户数据的空间来。

我们打开工程选项,在这下面,就是编译器给各个数据分配的空间地址和范围了。比如片上的ROM,起始地址是0800 0000,最高位的0省略了;他的size是0x10000,默认全部的64K闪存都是程序代码分配的空间。如果你想把闪存的尾部空间留着自己用,那就可以把这个程序空间的Size改小点,比如可以改成0xFC00(63个),这样,编译后的代码无论如何,也不会分配到最后一页了。如果Size过小,那编译的时候也会报错。所以如果你计划把闪存尾部的很多空间都留着自己用,那就把这个程序代码的空间改小点,以免冲突。
然后这个下载程序的起始地址,也可以改。比如你想写一个Bootloader程序放在闪存尾部,那就可以在这里修改下载到闪存的起始位置。之后右边这里是片上RAM的起始地址和大小,2000开始,大小5000,对应就是20K。

打开Debug,Settings,Flash Download。在这里是配置下载选项,我们要选第二个,擦除扇区,也就是页擦除。第一个是每次下载代码,都全擦除,再下载;第二个是用到多少页,就擦多少页,这个下载速度更快一些,如果你想在闪存尾部存储数据,那也最好选择页擦除的下载,要不然每次下载程序,芯片都全擦除了。

我们想知道目前这个程序编译之后,到底占用了多大的空间,这个怎么看呢?
我们可以全部编译一下,有四个数:代码,只读数据,读写数据,零初始化数据
前三个数相加,得到的就是程序占用闪存的大小;后两个数相加,得到的是占用SRAM的大小。

程序大小也可以在Target1这里双击,会打开一个.map文件(看完就关掉,否则每次打开都弹窗口),这就是详细的编译信息,最后面有程序的大小。

倒数第二行,是占用SRAM的大小;最后一行,是占用闪存的大小,4576字节,4.47kB。

读取芯片id,直接用指针读指定地址下的数据,就可以获取ID号了

更多推荐



所有评论(0)