5.7. 分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板

标签:
51armit开发板神舟 |
分类: 神舟51单片机开发板 |
5.7.1 硬件原理图分析
现在,知道可以访问处理器所有的寄存器了,我们可以通过改写这些寄存器的值,控制芯片做不同的功能和操作。
下面我们正式写个例程来感受一下.这个例程用C语言来修改这个内存地址的内容,从而控制寄存器,通过寄存器控制STM32芯片的PD4管脚使得一个灯亮和灭的。
原理图如下,上面已经有介绍:
http://s5/bmiddle/002aCJOcgy6Ma8yhdRy44&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
例程main.c源代码(可以直接运行):
以下是main.c的源文件,读者可以直接粘贴编译:
typedef
unsigned
typedef __IO
uint32_t
typedef unsigned
short
#define
GPIO_Pin_0
#define
GPIO_Pin_1
#define
GPIO_Pin_2
#define
GPIO_Pin_3
#define
GPIO_Pin_4
#define
GPIO_Pin_5
#define
GPIO_Pin_6
#define
GPIO_Pin_7
#define
GPIO_Pin_8
#define
GPIO_Pin_9
#define
GPIO_Pin_10
#define
GPIO_Pin_11
#define
GPIO_Pin_12
#define
GPIO_Pin_13
#define
GPIO_Pin_14
#define
GPIO_Pin_15
#define
GPIO_Pin_All
#define
RCC_APB2Periph_AFIO
#define
RCC_APB2Periph_GPIOA
#define
RCC_APB2Periph_GPIOB
#define
RCC_APB2Periph_GPIOD
typedef struct
{
} GPIO_TypeDef;
typedef struct
{
} RCC_TypeDef;
#define
PERIPH_BASE
#define
APB2PERIPH_BASE
#define
GPIOD_BASE
#define
GPIOD
#define
AHBPERIPH_BASE
#define
RCC_BASE
#define
RCC
void Delay(vu32 nCount);
int main(void) //main是程序入口
{
}
void Delay(vu32
nCount)
{
}
1.
http://s5/mw690/002aCJOcgy6Ma9F5qBe34&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s8/mw690/002aCJOcgy6Ma9FcI8D57&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
2.
http://s14/mw690/002aCJOcgy6Ma9F8l657d&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s12/bmiddle/002aCJOcgy6MaaupVTZcb&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
3.
http://s15/mw690/002aCJOcgy6Ma9GV6kC4e&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
4.
http://s8/mw690/002aCJOcgy6Ma9GUWHBb7&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
5.
http://s10/mw690/002aCJOcgy6Ma9GSNnjb9&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
6.可以看到目前工程里只有一个文件:http://s8/mw690/002aCJOcgy6Ma9IRu3tc7&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
7.新建一个main.c文件存放在路径STM32神舟III号开发板第1个例程\Project\main.c下,然后按照以下图标操作过程把main.c文件添加到工程里:
http://s2/bmiddle/002aCJOcgy6Ma9IMtQ5b1&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s13/bmiddle/002aCJOcgy6Ma9J23yk7c&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
这个例程,我们将所有的代码都写到了一个mian.c文件,不涉及到任何库函数,也没有包含任何的头文件,下面我们的截图:
http://s4/mw690/002aCJOcgy6Ma9ITcMX73&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
8.把5.61节的代码直接全盘拷贝到main.c文件里
http://s1/mw690/002aCJOcgy6Ma9J4jFC70&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
9.编译成功,我们可以看到编译后的HEX文件,我们可以直接在光盘中找到这个文件,直接进入Project文件夹,打开即可:
http://s2/mw690/002aCJOcgy6Ma9KtFZve1&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s10/mw690/002aCJOcgy6Ma9KxVEZ09&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
10.该代码可以直接下载到神舟IV号开发板中,按一下复位按键,可以看到LED灯一亮一灭,具体下载方式我们推荐有三种,具体下载设置请参考手册其他章节:
1)JLINK V8仿真器下载(我们推荐)
2)ULINK2仿真器下载
3)串口下载
11.或者直接打开我们已经编译好的例程“STM32神舟IV号开发板从零开始建立一个模板工程”,在http://blog.sina.com.cn/s/blog_7691b90c0101edpx.html链接中,STM32神舟IV号开发板区域可以找到。
可以看到STM32神舟IV号开发板的LED3灯一亮一灭的闪烁。
5.7.3
代码详细分析:
http://s12/mw690/002aCJOcgy6Ma9KyCRtbb&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
分析1:volatile是什么?怎么用?
答:简单的说,就是不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改,防止从缓存处读取的值是过期了的,所以加了这个volatile可以保证每次读的值绝对是实时的:
一般说来,volatile用在如下的几个地方:
1.中断服务程序中修改的供其它程序检测的变量需要加volatile;
2.多任务环境下各任务间共享的标志应该加volatile;
3.存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义。
我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到 volatile变量。不懂得volatile的内容将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
分析2:__I、 __O 、__IO是什么?
答:如下:
__I :输入口。既然是输入,那么寄存器的值就随时会外部修改,那就不能进行优化,每次都要重新从寄存器中读取。也不能写,即只读,不然就不是输入而是输出了。
__O :输出口,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变,就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出。
__IO:输入输出口,同上。
分析3:为什么加下划线?
答:原因是避免命名冲突,一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。
经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。
分析4:typedef是什么意思,怎么使用?
答:typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等);在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
1)
2)
例如:typedef struct tagMyStruct
{
int iNum;
long lLength;
} MyStruct;
这语句实际上完成两个操作
操作1:定义一个新的结构类型tagMyStruct, struct 关键字和tagMyStruct一起,构成了这个结构类型,不论是否有typedef,这个结构都存在。我们可以用struct tagMyStruct varName来定义变量,但要注意,使用tagMyStruct varName来定义变量是不对的,因为struct 和tagMyStruct合在一起才能表示一个结构类型。
struct tagMyStruct
{
int iNum;
long lLength;
};
操作2:typedef为这个新的结构起了一个名字,叫MyStruct。
typedef struct tagMyStruct MyStruct;
因此,MyStruct实际上相当于struct tagMyStruct,我们可以使用MyStruct varName来定义变量。
分析5:所以具体的typedef代码解释如下:
1)例:typedef
表示使用uint32_t符号表示unsigned
2)例:typedef
表示使用vu32符号表示typedef
3)例:typedef
表示使用uint16_t符号表示unsigned short int符号
2.初始化宏定义详细分析分解:
http://s4/mw690/002aCJOcgy6Ma9KYXx953&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
分析1:可以看出规律,GPIO_Pin_0、GPIO_Pin_1到GPIO_Pin_15总共16个define定义每个都是一个16比特(uint16_t)的对象。
每个端口都有16个GPIO管脚,比如GPIOA,GPIOB,GPIOC等,我们用16bit的位来表示,即2个字节,每个bit表示16个GPIO管脚中的一个,可以看下面2个寄存器,就是控制GPIO对应的具体管脚是高电平还是低电平的,通过对设置对应位为0或为1,就可以使得管脚的电平为高或低。
每个GPIO_Pin_x占用这16个比特中的1个位,其他剩余的15个位都是0,这16个GPIO_Pin_x就被用来表示芯片各个不同端口的16个管脚,比如PA0、PA1一直到PA15分别对应GPIO_Pin_0、GPIO_Pin_1到GPIO_Pin_15,这样定义好之后具体如何使用我们后面还会再说。
#define
GPIO_Pin_0
#define
GPIO_Pin_1
#define
GPIO_Pin_2
#define
GPIO_Pin_3
#define
GPIO_Pin_4
#define
GPIO_Pin_5
#define
GPIO_Pin_6
#define
GPIO_Pin_7
#define
GPIO_Pin_8
#define
GPIO_Pin_9
#define
GPIO_Pin_10
#define
GPIO_Pin_11
#define GPIO_Pin_12
#define
GPIO_Pin_13
#define
GPIO_Pin_14
#define
GPIO_Pin_15
#define
GPIO_Pin_All
分析2:这里是定义GPIO端口B的一些初始化变量,后面那些具体的地址需要查看芯片参考手册
http://s3/mw690/002aCJOcgy6Ma9KOk38c2&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s13/mw690/002aCJOcgy6Ma9MtcbW8c&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
1)定义总线的基地址(这个需要参考手册)
#define
2)APB2PERIPH_BASE(APB2时钟总线)的地址是在总线基地址上加多0x10000,刚好就是上图的AFIO寄存器地址,具体可以看参考手册
#define
3)定义GPIO端口D的基地址,该地址是0x40001 1400。
#define
4)定义一个GPIO_TypeDef的struct结构,从GPIO端口F的基地址开始进行覆盖
#define
分析3:这里是初始化RCC时钟总线的基地址,详细分析与上面同原理,具体的地址需要查看芯片参考手册
http://s3/bmiddle/002aCJOcgy6Ma9McQxQ02&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.http://s14/mw690/002aCJOcgy6Ma9MuQkB3d&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
分析4:设置端口的偏移量,后面我们会详细解释http://s14/mw690/002aCJOcgy6Ma9MtgeN4d&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
3.定义这两个结构体与芯片参考手册中的寄存器进行对应,芯片参考手册中对应的寄存器都是32bit的,所以在这个结构体的各个对象都被定义成uint32_t类型,并且是__IO类型,表示每次操作寄存器都是实时获取数据:
http://s11/mw690/002aCJOcgy6Ma9MDDRgaa&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
4.下面是main函数的剖析,总共来说分为4个步骤,下面一一介绍:
http://s13/mw690/002aCJOcgy6Ma9NZzf6bc&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
步骤1:使能APB2总线的时钟。
对GPIO的端口D时钟使能,这个是芯片厂家所规定的操作,我们先按照这样来操作就可以,具体实现方式也是将对应GPIOD寄存器使能,同时,也有RCC,串口接口,CAN接口,485接口等时钟的使能寄存器,使用前都需要先对时钟总线使能的。
使能操作完毕,就相当于我们对要使用的这个接口功能进行使能和激活,每个接口在使用前都必须要求使能和激活,只有激活后才可以使用。
步骤2:配置GPIO端口的状态。
输入还是输出,速度多少,和大家可以参考对应的芯片寄存器手册,可以看到我们将PB2设置成‘00:通用推挽输出模式’并且速度是‘11:输出模式,最大速度50MHz’
具体可以参考GPIOD_CRL寄存器,我们将这个寄存器设置为了0x0300 0000。
步骤3:进入while死循环
可以使得我们的点灯程序一直不会退出,达到重复一亮一灭的功能。
步骤4:GPIO输入和输出使得灯亮灭
GPIOD_BSRR对应位设置1,可以使得对应管脚的ODR位为1;GPIO_BRR的对应位设置1,可以使得对应管脚的ODR位为0
http://s5/mw690/002aCJOcgy6Ma9OkP1W24&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s14/mw690/002aCJOcgy6Ma9O90OV8d&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
5.7.4 程代码详细说明
http://s11/mw690/002aCJOcgy6Ma9ObGMG9a&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
通过这几个define可以算出来一下地址:
GPIOD= 0x4000 0000 + 0x1 0000 + 0x1400 = 0x4001 1400刚好与PortD在内存中的位置对应上
RCC = 0x4000 0000 + 0x2 0000 + 0x1000 = 0x4002 1000 刚好也与RCC在内存中的位置对应上
http://s9/bmiddle/002aCJOcgy6Ma9Ous8U28&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
更多内存映射可以找STM32F10XXX的数据手册资料,如下图,可以详细的进行了解了http://s10/mw690/002aCJOcgy6Ma9Q9v7389&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
通过以上这个存储器映像图,我们就可以通过代码与存储器地址关联起来了。
5.7.5 码如何映射到映射到芯片内部的寄存器
http://s12/mw690/002aCJOcgy6Ma9Q9OTF9b&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s14/bmiddle/002aCJOcgy6Ma9REq3Xcd&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
可以看到上图,每个寄存器都是32位的,左边是而且顺序刚好分别对应,结构体是会分配内存的,这样这些C语言中的struct结构体中定义的成员会对应映射到对应的寄存器上,那么我们就可以通过操纵程序中的该结构体的对应成员,就相当于操作的是对应的寄存器,这个是C语言和单片机软硬件对应上的又一大关键点,请不熟悉的读者好好理解一下,如实在不理解,可以致电STM32神舟系列开发板的官方工程师。
5.7.6 main函数寄存器级分析(重点)
1.LED灯为什么会一亮一灭呢?
http://s3/mw690/002aCJOcgy6Ma9RPhmOe2&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
我们看一下参考手册中的GPIOx_BRR和GPIOx_BSRR两个寄存器:
http://s5/mw690/002aCJOcgy6Ma9RR1n6a4&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s9/mw690/002aCJOcgy6Ma9S7hTW58&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s1/mw690/002aCJOcgy6Ma9SenFSd0&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
分析1:从以上两个寄存器里的内容可以知道到:
l
l
分析2:进一步分析,我们如何通过代码来改变这个ODRy呢?大家请看下面:
l
l
2.要使用PB2管脚需要做哪些初始化的工作呢?
http://s1/mw690/002aCJOcgy6Ma9ToTYIa0&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s10/mw690/002aCJOcgy6Ma9TDVeV19&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s15/mw690/002aCJOcgy6Ma9TLzUG9e&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
点击进入文档第61页可以看到6.3.7节,可以看到GPIOB端口的时钟设置选项:
http://s16/mw690/002aCJOcgy6Ma9U5d1R5f&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
我们通过代码RCC->APB2ENR |= RCC_APB2Periph_GPIOB;来对RCC_APB2ENR寄存器的位3进行操作置位,那么RCC_APB2Periph_GPIOF的值请见:
代码:#define
RCC_APB2Periph_GPIOF
从这句代码可以知道0x00000080的8化成二进制是1000,刚好是对RCC->APB2ENR寄存器也就是RCC_APB2ENR寄存器的第7位置位,使得IOPF EN为1,使得IO端口F时钟使能。
分析3:配置GPIO端口F的工作模式,我们这里可以看到它配置改变了CRL这个寄存器。
http://s1/mw690/002aCJOcgy6Ma9V3EfC40&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
我们找到这个CRL寄存器完整的名称叫GPIOx_CRL寄存器的内容:
http://s2/mw690/002aCJOcgy6Ma9Vrn9vc1&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
http://s16/mw690/002aCJOcgy6Ma9VAQbB6f&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
我们开始分析一下代码:
l
GPIOB->CRL
&=
可以看到将CRL寄存器的第16、17、18、19位清0,其他位默认值不变,看上表可以知道,这4位刚好是管脚PB2的配置寄存器http://s5/mw690/002aCJOcgy6Ma9VD2yE24&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
l
GPIOB->CRL
|=
可以看到将CRL寄存器的第19、18、17、16位 分别置成0、0、1、1,其他位的GPIO端口值都清0,意思就是只配置PB2这个管脚,其他PB口的管脚配置寄存器都全部变成0,这样MODE4=11;CNF4=00;查表可以知道:
http://s13/mw690/002aCJOcgy6Maauun0g9c&690分析一个最简单的例程-STM32(初学宝典)神舟IV号开发板" TITLE="5.7.
这样配置后PD4被配置成输出模式,输出速度为50MHz,状态是通用推挽输出模式;因为我们这个例程中需要点LED灯,这是一种输出模式。
5.7.7 函数与我们这个例程之间的关系
库函数同我们这个例程的原理是一样,后续的例程,都是源自库函数的,阅读原理一样,只是我们将一些相关功能比较密切的代码封装到一起,变成一个完整的函数。
后续该神舟文档还会升级,库函数分析版本请见下个版本的书籍。