文档介绍:该【第四章 STM32F4开发基础知识入门-正点原子探索者STM32F4开发板-STM32F4开发指南-库函数 】是由【小小布】上传分享,文档一共【31】页,该文档可以免费在线阅读,需要了解更多关于【第四章 STM32F4开发基础知识入门-正点原子探索者STM32F4开发板-STM32F4开发指南-库函数 】的内容,可以使用淘豆网的站内搜索功能,选择自己适合的文档,以下文字是截取该文章内的部分文字,如需要获得完整电子版,请下载此文档到您的设备,方便您编辑和打印。:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
第四章STM32F4开发基础知识入门
这一章,我们将着重STM32开发的一些基础知识,让大家对STM32开发有一个初步的了
解,为后面STM32的学****做一个铺垫,方便后面的学****这一章的内容大家第一次看的时候
可以只了解一个大概,后面需要用到这方面的知识的时候再回过头来仔细看看。这章我们分7
个小结,
·
·
·
·
·
·
·
这一节我们主要讲解一下C语言基础知识。C语言知识博大精深,也不是我们三言两语能
讲解清楚,同时我们相信学STM32F4这种级别MCU的用户,C语言基础应该都是没问题的。我
们这里主要是简单的复****一下几个C语言基础知识点,引导那些C语言基础知识不是很扎实的
用户能够快速开发STM32程序。同时希望这些用户能够多去复****一下C语言基础知识,C语言
毕竟是单片机开发中的必备基础知识。对于C语言基础比较扎实的用户,这部分知识可以忽略
不看。
C语言位操作相信学过C语言的人都不陌生了,简而言之,就是对基本类型变量可以在位级
别进行操作。这节的内容很多朋友都应该很熟练了,我这里也就点到为止,不深入探讨。下面
我们先讲解几种位操作符,然后讲解位操作使用技巧。
C语言支持如下6种位操作
运算符含义运算符含义
&按位与~取反
|按位或<<左移
^按位异或>>右移
这些与或非,取反,异或,右移,左移这些到底怎么回事,这里我们就不多做详细,相信
大家学C语言的时候都学****过了。如果不懂的话,可以百度一下,非常多的知识讲解这些操作
符。下面我们想着重讲解位操作在单片机开发中的一些实用技巧。
1)不改变其他位的值的状况下,对某几个位进行设值。
这个场景单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作,
然后用|操作符设值。比如我要改变GPIOA->BSRRL的状态,可以先对寄存器的值进行&
清零操作
GPIOA->BSRRL&=0XFF0F;//将第4-7位清0
然后再与需要设置的值进行|或运算
GPIOA->BSRRL|=0X0040;//设置相应位的值,不改变其他位的值
96:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
2)移位操作提高代码的可读性。
移位操作在单片机开发中也非常重要,我们来看看下面一行代码
GPIOx->ODR=(((uint32_t)0x01)<<pinpos);
这个操作就是将ODR寄存器的第pinpos位设置为1,为什么要通过左移而不是直接设
置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以
很直观明了的知道,是将第pinpos位设置为1。如果你写成
GPIOx->ODR=0x0030;
这样的代码就不好看也不好重用了。
3)~取反操作使用技巧
SR寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为0,同时
其他位都保留为1,简单的作法是直接给寄存器设置一个值:
TIMx->SR=0xFFF7;
这样的作法设置第3位为0,但是这样的作法同样不好看,并且可读性很差。看看库函数
代码中怎样使用的:
TIMx->SR=(uint16_t)~TIM_FLAG;
而TIM_FLAG是通过宏定义定义的值:
#defineTIM_FLAG_Update((uint16_t)0x0001)
#defineTIM_FLAG_CC1((uint16_t)0x0002)
看这个应该很容易明白,可以直接从宏定义中看出TIM_FLAG_Update就是设置的第0位了,
可读性非常强。
define是C语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供
方便。常见的格式:
#define标识符字符串
“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:
#definePLL_M8
定义标识符PLL_M的值为8。
至于define宏定义的其他一些知识,比如宏定义带参数这里我们就不多讲解。
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而
当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:
#ifdef标识符
程序段1
#else
程序段2
#endif
它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,
否则编译程序段2。其中#else部分也可以没有,即:
#ifdef
程序段1
#endif
97:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
这个条件编译在MDK里面是用得很多的,:
#ifdefined(STM32F40_41xxx)
STM32F40x系列和STM32F41x系列芯片需要的一些变量定义
#end
而(STM32F40_41xxx则是我们通过#define来定义的。条件编译也是c语言的基础知识,这里
也就点到为止吧。
C语言中extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编
译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于extern申明变量可以多
次,但定义只有一次。在我们的代码中你会看到看到这样的语句:
externu16USART_RX_STA;
这个语句是申明USART_RX_STA变量在其他文件中已经定义了,在这里要使用到。所以,你肯定
可以找到在某个地方有变量定义的语句:
u16USART_RX_STA;
的出现。下面通过一个例子说明一下使用方法。
,。
u8id;//定义只允许一次
main()
{
id=1;
printf("d%",id);//id=1
test();
printf("d%",id);//id=2
}
(void)函数中使用变量id,
里面去申明变量id是外部定义的了,因为如果不申明,
中。:
externu8id;//申明变量id是在外部定义的,申明可以在很多个文件中进行
voidtest(void){
id=2;
}
,。
对于extern申明函数在外部定义的应用,这里我们就不多讲解了。
typedef用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。
typedef在MDK用得最多的就是定义结构体的类型别名和枚举类型了。
struct_GPIO
{
__IOuint32_tMODER;
__IOuint32_tOTYPER;
98:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
…
};
定义了一个结构体GPIO,这样我们定义变量的方式为:
struct_GPIOGPIOA;//定义结构体变量GPIOA
但是这样很繁琐,MDK中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别
名GPIO_TypeDef,这样我们就可以在其他地方通过别名GPIO_TypeDef来定义结构体变量了。
方法如下:
typedefstruct
{
__IOuint32_tMODER;
__IOuint32_tOTYPER;
…
}GPIO_TypeDef;
Typedef为结构体定义一个别名GPIO_TypeDef,这样我们可以通过GPIO_TypeDef来定义结构体
变量:
GPIO_TypeDef_GPIOA,_GPIOB;
这里的GPIO_TypeDef就跟struct_GPIO是等同的作用了。这样是不是方便很多?
经常很多用户提到,他们对结构体使用不是很熟悉,但是MDK中太多地方使用结构体以及
结构体指针,这让他们一下子摸不着头脑,学****STM32的积极性大大降低,其实结构体并不是
那么复杂,这里我们稍微提一下结构体的一些知识,还有一些知识我们会在下一节的“寄存器
地址名称映射分析”中讲到一些。
声明结构体类型:
Struct结构体名{
成员列表;
}变量名列表;
例如:
StructU_TYPE{
IntBaudRate
IntWordLength;
}usart1,usart2;
在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:
Struct结构体名字结构体变量列表;
例如:structU_TYPEusart1,usart2;
结构体成员变量的引用方法是:
比如要引用usart1的成员BaudRate,方法是:;
结构体指针变量定义也是一样的,跟其他变量没有啥区别。
例如:structU_TYPE*usart3;//定义结构体指针变量usart1;
结构体指针成员变量引用方法是通过“->”符号实现,比如要访问usart3结构体指针指向的结
构体的成员变量BaudRate,方法是:
Usart3->BaudRate;
99:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
上面讲解了结构体和结构体指针的一些知识,其他的什么初始化这里就不多讲解了。讲到这里,
有人会问,结构体到底有什么作用呢?为什么要使用结构体呢?下面我们将简单的通过一个实
例回答一下这个问题。
在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态
是由几个属性来决定的,比如串口号,波特率,极性,以及模式等。对于这种情况,在我们没
有学****结构体的时候,我们一般的方法是:
voidUSART_Init(u8usartx,u32u32BaudRate,u8parity,u8mode);
这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函数里
面再传入一个参数,那么势必我们需要修改这个函数的定义,重新加入字长这个入口参数。于
是我们的定义被修改为:
voidUSART_Init(u8usartx,u32BaudRate,u8parity,u8mode,u8wordlength);
但是如果我们这个函数的入口参数是随着开发不断的增多,那么是不是我们就要不断的修改函
数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?
这样如果我们使用到结构体就能解决这个问题了。我们可以在不改变入口参数的情况下,
只需要改变结构体的成员变量,就可以达到上面改变入口参数的目的。
结构体就是将多个变量组合为一个有机的整体。上面的函数,BaudRate,wordlength,
Parity,mode,wordlength这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参
数的,所以我们可以将他们通过定义一个结构体来组合在一个。MDK中是这样定义的:
typedefstruct
{
uint32_tUSART_BaudRate;
uint16_tUSART_WordLength;
uint16_tUSART_StopBits;
uint16_tUSART_Parity;
uint16_tUSART_Mode;
uint16_tUSART_HardwareFlowControl;
}USART_InitTypeDef;
于是,我们在初始化串口的时候入口参数就可以是USART_InitTypeDef类型的变量或者指针变
量了,MDK中是这样做的:
voidUSART_Init(USART_TypeDef*USARTx,USART_InitTypeDef*USART_InitStruct);
这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需
要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义
就可以达到增加变量的目的。
理解了结构体在这个例子中间的作用吗?在以后的开发过程中,如果你的变量定义过多,
如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可
以提高你的代码的可读性。
使用结构体组合参数,可以提高代码的可读性,不会觉得变量定义混乱。当然结构体的作
用就远远不止这个了,同时,MDK中用结构体来定义外设也不仅仅只是这个作用,这里我们只
是举一个例子,通过最常用的场景,让大家理解结构体的一个作用而已。后面一节我们还会讲
解结构体的一些其他知识。
100:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
STM32F4的总线架构比51单片机就要强大很多了。STM32F4总线架构的知识可以在
《STM32F4XX中文参考手册》第二章有讲解,这里我们也把这一部分知识抽取出来讲解,是
为了大家在学****STM32F4之前对系统架构有一个初步的了解。这里的内容基本也是从中文参
考手册中参考过来的,让大家能通过我们手册也了解到,免除了到处找资料的麻烦吧。如果需
要详细深入的了解STM32的系统架构,还需要多看看《STM32F4XX中文参考手册》或者在网
上搜索其他相关资料学****br/>我们这里所讲的STM32F4系统架构主要针对的STM32F407系列芯片。首先我们看看
STM32的总线架构图:
主系统由32位多层AHB总线矩阵构成。总线矩阵用于主控总线之间的访问仲裁管理。仲裁采
取循环调度算法。总线矩阵可实现以下部分互联:
八条主控总线是:
Cortex-M4内核I总线,D总线和S总线;
DMA1存储器总线,DMA2存储器总线;
DMA2外设总线;
以太网DMA总线;
USBOTGHSDMA总线;
七条被控总线:
内部FLASHICode总线;
内部FLASHDCode总线;
主要内部SRAM1(112KB)
辅助内部SRAM2(16KB);
辅助内部SRAM3(64KB)(仅适用STM32F42xx和STM32F43xx系列器件);
101:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
AHB1外设和AHB2外设;
FSMC
下面我们具体讲解一下图中几个总线的知识。
①I总线(S0):此总线用于将Cortex-M4内核的指令总线连接到总线矩阵。内核通过此总
线获取指令。此总线访问的对象是包括代码的存储器。
②D总线(S1):此总线用于将Cortex-M4数据总线和64KBCCM数据RAM连接到总线矩
阵。内核通过此总线进行立即数加载和调试访问。
③S总线(S2):此总线用于将Cortex-M4内核的系统总线连接到总线矩阵。此总线用于访
问位于外设或SRAM中的数据。
④DMA存储器总线(S3,S4):此总线用于将DMA存储器总线主接口连接到总线矩阵。
DMA通过此总线来执行存储器数据的传入和传出。
⑤DMA外设总线:此总线用于将DMA外设主总线接口连接到总线矩阵。DMA通过此
总线访问AHB外设或执行存储器之间的数据传输。
⑥以太网DMA总线:此总线用于将以太网DMA主接口连接到总线矩阵。以太网DMA
通过此总线向存储器存取数据。
⑦USBOTGHSDMA总线(S7):此总线用于将USBOTGHSDMA主接口连接到总线矩
阵。USBOTGHSDMA通过此总线向存储器加载/存储数据。
对于系统架构的知识,在刚开始学****STM32的时候只需要一个大概的了解,大致知道是个
什么情况即可。对于寻址之类的知识,这里就不做深入的讲解,中文参考手册都有很详细的讲
解。
STM32F4时钟系统的知识在《STM32F4中文参考手册》第六章复位和时钟控制章节有非
常详细的讲解,网上关于时钟系统的讲解也基本都是参考的这里,讲不出啥特色,不过作为一
个完整的参考手册,我们必然要提到时钟系统的知识。这些知识也不是什么原创,纯粹根据官
方提供的中文参考手册和自己的应用心得来总结的,如有不合理之处望大家谅解。
这部分内容我们分3个小节来讲解:
·
·
·
众所周知,时钟系统是CPU的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而
喻了。STM32F4的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。
于是有人要问,采用一个系统时钟不是很简单吗?为什么STM32要有多个时钟源呢?因为首
先STM32本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,
比如看门狗以及RTC只需要几十k的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁
干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。
首先让我们来看看STM32F4的时钟系统图:
102:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
在STM32F4中,有5个最重要的时钟源,为HSI、HSE、LSI、LSE、PLL。其中PLL实
际是分为两个时钟源,分别为主PLL和专用PLL。从时钟频率来分可以分为高速时钟源和低速
时钟源,在这5个中HSI,HSE以及PLL是高速时钟,LSI和LSE是低速时钟。从来源可分为
外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和
LSE是外部时钟源,其他的是内部时钟源。下面我们看看STM32F4的这5个时钟源,我们讲
解顺序是按图中红圈标示的顺序:
103:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
①、LSI是低速内部时钟,RC振荡器,频率为32kHz左右。供独立看门狗和自动唤醒单元使用。
②、LSE是低速外部时钟,。这个主要是RTC的时钟源。
③、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz。
我们的开发板接的是8M的晶振。HSE也可以直接做为系统时钟或者PLL输入。
④、HSI是高速内部时钟,RC振荡器,频率为16MHz。可以直接作为系统时钟或者用作PLL
输入。
⑤、PLL为锁相环倍频输出。STM32F4有两个PLL:
1)主PLL(PLL)由HSE或者HSI提供时钟信号,并具有两个不同的输出时钟。
第一个输出PLLP用于生成高速的系统时钟(最高168MHz)
第二个输出PLLQ用于生成USBOTGFS的时钟(48MHz),随机数发生器的时钟和SDIO
时钟。
2)专用PLL(PLLI2S)用于生成精确时钟,从而在I2S接口实现高品质音频性能。
这里我们着重看看主PLL时钟第一个高速时钟输出PLLP的计算方法。
时钟图。
。主PLL时钟的时钟源要先经过一个分频系数为M的分频器,然后经过
倍频系数为N的倍频器出来之后的时候还需要经过一个分频系数为P(第一个输出PLLP)或
者Q(第二个输出PLLQ)的分频器分频之后,最后才生成最终的主PLL时钟。
例如我们的外部晶振选择8MHz。同时我们设置相应的分频器M=8,倍频器倍频系数N=336,
分频器分频系数P=2,那么主PLL生成的第一个输出高速时钟PLLP为:
PLL=8MHz*N/(M*P)=8MHz*336/(8*2)=168MHz
如果我们选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为168MHz。
这对于我们后面的实验都是采用这样的配置。
上面我们简要概括了STM32的时钟源,那么这5个时钟源是怎么给各个外设以及系统提
供时钟的呢?这里我们选择一些比较常用的时钟知识来讲解。
~G标示我们要讲解的地方。
。从图中可以看出,看门狗时钟源只能是低速的LSI时钟。
,从图上可以看出,RTC的时钟源可以选择LSI,LSE,以及
HSE分频后的时钟,HSE分频系数为2~31。
。MCO1是向芯片的PA8引脚输出时
钟。它有四个时钟来源分别为:HSI,LSE,HSE和PLL时钟。MCO2是向芯片的
104:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
PC9输出时钟,它同样有四个时钟来源分别为:HSE,PLL,SYSCLK以及PLLI2S
时钟。MCO输出时钟频率最大不超过100MHz。
。,SYSCLK系统时钟来源有三个方面:
HSI,HSE和PLL。在我们实际应用中,因为对时钟速度要求都比较高我们才会选
用STM32F4这种级别的处理器,所以一般情况下,都是采用PLL作为SYSCLK
时钟源。根据前面的计算公式,大家就可以算出你的系统的SYSCLK是多少。
,AHB时钟,APB2高速时钟,APB1低速时钟。
这些时钟都是来源于SYSCLK系统时钟。其中以太网PTP时钟是使用系统时钟。
AHB,APB2和APB1时钟是经过SYSCLK时钟分频得来。这里大家记住,AHB
最大时钟为168MHz,APB2高速时钟最大频率为84MHz,而APB1低速时钟最大频
率为42MHz。
。,I2S的时钟源来源于PLLI2S或者映
射到I2S_CKIN引脚的外部时钟。I2S出于音质的考虑,对时钟精度要求很高。探
索者STM32F4开发板使用的是内部PLLI2SCLK。
。对于MII接口来说,必须向外部
PHY芯片提供25Mhz的时钟,这个时钟,可以由PHY芯片外接晶振,或者使用
STM32F4的MCO输出来提供。然后,PHY芯片再给STM32F4提供
ETH_MII_TX_CLK和ETH_MII_RX_CLK时钟。对于RMII接口来说,外部必须
提供50Mhz的时钟驱动PHY和STM32F4的ETH_RMII_REF_CLK,这个50Mhz
时钟可以来自PHY、有源晶振或者STM32F4的MCO。我们的开发板使用的是
RMII接口,使用PHY芯片提供50Mhz时钟驱动STM32F4的
ETH_RMII_REF_CLK。
(60MHZ)时钟。
这里还需要说明一下,Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或
HCLK的8分频。具体配置请参考Systick定时器配置,
夹代码的时候讲解。
在以上的时钟输出中,有很多是带使能控制的,例如AHB总线时钟、内核时钟、各种APB1
外设、APB2外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。后面我们讲解
实例的时候会讲解到时钟使能的方法。
上一小节我们对STM32F4时钟树进行了初步的讲解,接下来我们来讲解一下STM32F4的
系统时钟配置。
()函数中完成的。对于系
统时钟关键寄存器设置主要是在SystemInit函数中调用SetSysClock()函数来设置的。我们可以
先看看SystemInit()函数体:
voidSystemInit(void)
{
/*FPUsettings------------------------------------------------------------*/
#if(__FPU_PRESENT==1)&&(__FPU_USED==1)
SCB->CPACR|=((3UL<<10*2)|(3UL<<11*2));/*setCP10andCP11FullAccess*/
#endif
105:.
STM32F4开发指南(库函数版)
ALIENTEK探索者STM32F407开发板教程
/*ResettheRCCclockconfigurationtothedefaultresetstate------------*/
/*SetHSIONbit*/
RCC->CR|=(uint32_t)0x00000001;
/*ResetCFGRregister*/
RCC->CFGR=0x00000000;
/*ResetHSEON,CSSONandPLLONbits*/
RCC->CR&=(uint32_t)0xFEF6FFFF;
/*ResetPLLCFGRregister*/
RCC->PLLCFGR=0x24003010;
/*ResetHSEBYPbit*/
RCC->CR&=(uint32_t)0xFFFBFFFF;
/*Disableallinterrupts*/
RCC->CIR=0x00000000;
#ifdefined(DATA_IN_ExtSRAM)||defined(DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif/*DATA_IN_ExtSRAM||DATA_IN_ExtSDRAM*/
/*ConfiguretheSystemclocksource,PLLMultiplierandDividerfactors,
AHB/APBxpre