购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

任务二
单灯闪烁控制

为了验证某个端口的输出电平是不是由你编写的程序输出的电平,可以采用一个非常简单有效的办法,就是在你想验证的端口位接一个发光二极管。当你输出低电平时,发光二极管亮;输出高电平时,发光二极管灭。电路图如图2.2所示。

在本任务中,使用PC13来控制发光二极管以1Hz的频率不断闪烁。

图2.2 发光二极管电路图

例程:Led_Blink.c

● 接通板上的电源;

● 输入、保存、下载并运行程序Led_Blink.c(整个过程请参考第1章);

● 观察与PC13连接的LED是否周期性的闪烁。

Led_Blink.c是如何工作的?

先看while(1)逻辑块中的语句,两次调用了延时函数,让单片机微控制器在给PC13引脚端口输出高电平和低电平之间都延时500ms,即输出的高电平和低电平都保持500ms,从而达到发光二极管LED以1Hz的频率不断闪烁的效果。

头文件HelloRobot.h中定义了两个延时函数:void delay_nms(unsigned int i)与void delay_nus(unsigned int i)。

无符号长整型数据unsigned long

与长整型数据long相比,无符号长整型数据unsigned long只有一个区别:数据的取值范围从-2147483648~+2147483647变为0~4294967295,也就是说它只能取非负整数。基于ARM内核的微处理器(S3C2410/2440)或者单片机(STM32系列)是32位的,所以Keil MDK开发环境中整型int数据与长整型long数据相同,占用4字节;若在Keil uVision3中开发8位的51单片机程序,则整型int数据占用2字节,与短整型short数据相同。注意它们的范围有所不同。

delay_nus()是微秒级的延时,而delay_nms()是毫秒级的延时。如果你想延时1s,可以使用语句delay_nms(1000);1ms的延时则用delay_nus(1000)来完成。

注意: 上述的延时函数是在外部晶振为8MHz,内部锁相环(Phase Lock Loop,PLL)设置为9倍频的情况下设计的,这两个函数所产生的延时都经过示波器测试过。如果外部晶振频率不是8MHz,调用这两个函数所产生的真正延时就会发生变化。晶振电路如图2.3所示。图2.3(a)是系统晶振电路。

图2.3 晶振电路

晶振的作用

单片机要能工作,就必须有一个标准时钟信号,而晶振就是为单片机提供标准时钟信号。晶振的作用类似人的心跳,只有晶振起振了,嵌入式系统中的处理器才能工作、执行代码、实现特定功能,完成应用程序任务。因此,如果系统不工作应注意查看晶振是否起振了。可以用示波器测量晶振引脚处是否有信号。

如果将晶振比喻为人的心跳,那么电源输出电流就类似于流经人全身的血液。因此晶振和电源在嵌入式系统中的作用,就相当于心脏和血液对于人的作用,你说重不重要!晶振不稳定就相当于心率不齐。没有电源,电源不能输出电流,就相当于没有血液,或血液不流动。在后面的实时时钟章节中,将会更详细的介绍晶振。

注意: STM32上电默认是使用内部高速RC时钟(HIS),因此,判断STM32单片机最小系统是否工作用示波器检查OSC引脚是否有时钟信号是错误的。

如何选择晶振

对于一个高可靠性的系统设计,晶振的选择非常重要,尤其设计带有睡眠唤醒(往往用低电压以求低功耗)的系统。这是因为低供电电压使提供给晶振的激励功率减少,造成晶振起振很慢或根本就不能起振。这一现象在上电复位时并不特别明显,原因是上电时电路有足够的扰动,很容易建立振荡。在睡眠唤醒时,电路的扰动要比上电时小得多,起振变得很不容易。在振荡回路中,晶振既不能过激励(容易振到高次谐波上)也不能欠激励(不容易起振)。晶振的选择至少必须考虑谐振频点、负载电容、激励功率、温度特性、长期稳定性。

如何选择晶振电容

(1)因为每一种晶振都有各自的特性,所以最好按芯片制造厂商所提供的数值选择外部元器件。

(2)电容值小,容易起振;但过小,振荡器容易不稳定。电容值大,有利于振荡器的稳定;但过大,将会增加起振时间,不容易起振。一般选择合适的中间值。 ytIJecP4Qk4uPCGRIrw/HxKA/mdVArVz/b4Bk+Ct3obB8KbsQzvvNmfy8F47shLk



2.2 STM32单片机的时钟配置

一般而言,嵌入式系统在正式工作前,都要进行一些初始化工作,我们常把这个阶段写成一个子函数的形式,叫BSP_Init函数(Board Support Package,BSP,板级支持包)。

开发板初始化函数BSP_Init会调用3个函数:RCC_Configuration(复位和时钟设置),GPIO_Configuration(IO口设置),NVIC_Configuration(中断设置)。这里先介绍了前两个函数,第3个函数在后面讲解中断时再介绍。

我们先认识一下开发板初始化函数中的复位和时钟配置函数RCC_Configuration(Reset and Clock Configuration,RCC),它与STM32系列微控制器中的时钟有关。

STM32系列微控制器中的五个时钟源:HSI、HSE、LSI、LSE、PLL

① HSI(High Speed Internal)是高速内部时钟,RC振荡器,频率为8MHz(精度较差)。

② HSE(High Speed External)是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4~16MHz(精度高)。

③ LSI(Low Speed Internal)是低速内部时钟,RC振荡器,频率为30~60kHz。

④ LSE(Low Speed External)是低速外部时钟,接频率为32.768kHz的石英晶体,供实时时钟RTC使用。电路如图2.3(b)所示。

⑤ PLL(Phase Lock Loop)为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

锁相环的基本组成

PLL(Phase Locked Loop):锁相回路或叫锁相环。PLL用于振荡器中的反馈技术。许多电子设备要正常工作,通常需要外部的输入信号与内部的振荡信号同步,利用锁相环路就可以实现这个目的。锁相环是一种反馈控制电路,特点是:利用外部输入的参考信号控制环路内部振荡信号的频率和相位。因锁相环可以实现输出信号频率对输入信号频率的自动跟踪,所以锁相环通常用于闭环跟踪电路。

锁相环在工作的过程中,当输出信号的频率与输入信号的频率相等时,输出电压与输入电压保持固定的相位差值,即输出电压与输入电压的相位被锁住,这就是锁相环名称的由来。锁相环通常由鉴相器(Phase Detector,PD)、环路滤波器(Loop Filter,LF)和压控振荡器(Voltage Controlled Oscillator,VCO)三部分组成,锁相环组成的原理框图如图2.4所示。图中的鉴相器又称为相位比较器,它的作用是检测输入信号和输出信号的相位差,并将检测出的相位差信号转换成uD(t)电压信号输出,该信号经低通滤波器滤波后形成压控振荡器的控制电压uC(t),对振荡器输出信号的频率实施控制。输出频率f out 与输入参考频率f r 的关系为:f out =M*f r

图2.4 锁相环组成的原理框图

STM32单片机的将时钟信号(常是HSE)经过分频或倍频(PLL)后,得到系统时钟,系统时钟经过分频,产生外设所使用的时钟。图2.5是STM32时钟系统结构图。

其中,40kHz(典型值)的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。实时时钟RTC的时钟源也可以选择LSE,或者是HSE的128分频。RTC的时钟源通过备份域控制寄存器(RCC_BDCR)的RTCSEL[1:0]来选择。

STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。

另外,STM32还可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。

图2.5 STM32时钟系统结构图

系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或者HSE。系统时钟最大频率为72MHz,它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给8大模块使用。

① 送给SDIO使用的SDIOCLK时钟。

② 送给FSMC使用的FSMCCLK时钟。

③ 送给AHB总线、内核、内存和DMA使用的HCLK时钟。

④ 通过8分频后送给Cortex的系统定时器时钟(SysTick)。

⑤ 直接送给Cortex的空闲运行时钟FCLK。

⑥ 送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。

⑦ 送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器1倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器1使用。另外,APB2分频器还有一路输出供ADC分频器使用(可选择为2、4、6、8分频),分频后得到ADCCLK时钟,送给ADC模块使用。

⑧ 2分频后送给SDIO AHB接口使用(HCLK/2)。

AMBA片上总线

片上总线标准种类繁多,而由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC(System on Chip)片上系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外设总线。二者分别适用于高速与相对低速设备的连接。

时钟输出的使能控制

在以上的时钟输出中,有很多是带使能控制的,如AHB总线时钟、内核时钟、各种APB1外设、APB2外设等。 当需要使用某个外设模块时,记得一定要先使能对应的时钟,否则这个外设不能工作。 因此,使用任何一个外设都必须打开相应的时钟。这样的好处就是,如果不使用一个外设时,就把它的时钟关掉,从而可以降低系统的功耗,达到节能,实现低功耗的效果。当STM32单片机系统时钟为72MHz时,在运行模式下,打开全部外设时的功耗电流为36mA,关闭全部外设时的功耗电流为27mA。

需要注意的是定时器2、3、4的倍频器,当APB1的分频为1时,它的倍频值为1(且只能为1,因为不能高于AHB频率),此时,定时器的时钟频率等于APB1的频率;当APB1的预分频系数为其他数值(即预分频系数为2、4、8或16)时,它的倍频值就为2。连接在APB1(低速外设)上的设备有电源接口、备份接口、CAN、USB、I 2 C1、I 2 C2、UART2、UART3、SPI2、窗口看门狗、Timer2、Timer3、Timer4。注意USB模块虽然需要一个单独的48MHz时钟信号,但它应该不是供USB模块工作的时钟,而只是提供给串行接口引擎(SIE)使用的时钟。USB模块工作的时钟是由APB1提供的。连接在APB2(高速外设)上的设备有:UART1、SPI1、Timer1、ADC1、ADC2、所有普通I/O口(PA~PE)、第二功能I/O口。

为什么ARM时钟这么复杂?

大家可能看到了,基于ARM Cortex-M3的STM32单片机时钟很复杂,与51单片机相差太大。标准51单片机很简单,外部晶振12分频就是机器频率,即51单片机工作的基准频率,增强型51(如C8051)也不过是外部晶振频率直接是机器频率而已。

其实,随着芯片工艺的发展,台式机和嵌入式系统的处理器频率越来越快,而处理器除了中央处理单元(CPU)外,还有一些外设接口,如串口,它们的时钟并没有那么快。如果中央处理单元与外设接口公用一样的时钟,那么中央处理单元在同一时间内要做很多事情,外设接口才能做一件事情,如数据存取。设想一下,当中央处理单元等待外设接口传来一个数据时,岂不是要等到很久。这样中央处理单元的性能就不能发挥出来,而且外设接口也没必要提供太高的时钟。另一个原因就是时钟分开有助于实现低功耗。

因此,现在的嵌入式系统处理器常常将时钟分开,有供中央处理单元使用的,也有供外设接口使用的。不仅仅是基于ARM内核的芯片,很多其他32位嵌入式处理器也是这样。

复位和时钟配置函数RCC_Configuration

时钟的具体配置是从RCC配置寄存器开始。定义RCC配置寄存器的是结构体RCC_TypeDef,在文件“stm32f10x_map.h”中定义如下:

其中,vu32代表一个32位的无符号长整形数,在文件“stm32f10x_type.h”中定义了:

typedef signed long s32;

typedef signed short s16;

typedef signed char s8;

typedef volatile signed long vs32;

typedef volatile signed short vs16;

typedef volatile signed char vs8;

typedef unsigned long u32;

typedef unsigned short u16;

typedef unsigned char u8;

typedef unsigned long const uc32;/*Read Only*/

typedef unsigned short const uc16;/*Read Only*/

typedef unsigned char const uc8;/*Read Only*/

typedef volatile unsigned long vu32;

typedef volatile unsigned short vu16;

typedef volatile unsigned char vu8;

typedef volatile unsigned long const vuc32;/*Read Only*/

typedef volatile unsigned short const vuc16;/*Read Only*/

typedef volatile unsigned char const vuc8;/*Read Only*/

typedef enum{FALSE=0,TRUE=!FALSE}bool;

typedef enum{RESET=0,SET=!RESET}FlagStatus,ITStatus;

typedef enum{DISABLE=0,ENABLE=!DISABLE}FunctionalState;

#define IS_FUNCTIONAL_STATE(STATE)((STATE==DISABLE)||(STATE==ENABLE))

typedef enum{ERROR=0,SUCCESS=!ERROR}ErrorStatus;

从上面的几个宏定义可以看出,在程序中所有写RCC的地方,编译器的预处理程序将它替换成((RCC_TypeDef*)0x40021000)。其实,这个地址是RCC寄存器组的首地址,RCC寄存器映像和复位值如表2.1所示,这些寄存器的具体定义和使用方式参见芯片数据手册。关于STM32处理器的存储映射参见附录,这里不再赘述。

表2.1 RCC寄存器映像和复位值表

续表

volatile关键字的含义

一个定义为volatile的变量是说这变量是易变的,可能会被意想不到地改变,它们的值可能由于程序控制之外的事件而被潜在改变。这样,编译器就不会去假设这个变量的值了。准确地说就是,编译器优化时,在用到这个变量时必须每次都重新读取这个变量的值,即每次读写都必须访问实际地址存储器的内容,而不是使用保存在寄存器中的副本。

在嵌入式系统中,volatile大量地用来描述一个对应于内存映射的输入/输出端口,或者硬件寄存器(如状态寄存器)。

进一步讲解volatile

那为什么编译器会将没有被volatile修饰的变量在寄存器里保存个备份呢?这往往是基于程序运行效率的考虑,因为从寄存器里取数据要更快些,寄存器是在嵌入式处理器内核中,而从实际的存储器地址(往往是外设)访问会慢些。

这个问题往往是区分C程序员和嵌入式系统程序员的最基本问题。嵌入式工程师经常同硬件、中断、RTOS等打交道,所有这些都要求用到volatile变量。不懂得volatile的含义就将会给嵌入式系统软件带来缺陷,发生不可预料甚至灾难性的后果。

其次,中断服务例程中使用的非自动变量或者多线程应用程序中多个任务共享的变量也必须使用volatile进行限定。例如代码:

如果没有使用volatile限定flag变量,编译器看到在f()函数中并没有修改flag,可能只执行一次flag读操作并将flag的值缓存在寄存器中,以后每次访问flag(读操作)都使用寄存器中的缓存值而不进行存储器绝对地址访问,导致some_action函数永远无法执行,即使中断函数isr_f()执行了将flag置1。

下面,我们看看复位和时钟配置函数RCC_Configuration。

在初始化阶段,RCC_Configuration函数完成系统的复位和时钟设置。这些函数的具体实现在库文件“stm32f10x_rcc.c”中(\library\src目录下)。复位和时钟设置函数RCC_Configuration中的第一条语句是RCC_DeInit(),其作用是复位定义在结构体RCC_TypeDef中的各个RCC配置寄存器。教学开发板上有一个8MHz的晶振,将PLL设置为9倍频,这样系统时钟为72MHz(STM32F103增强型单片机最高工作频率为72MHz),高速总线和低速总线2都为72MHz,低速总线1为36MHz。这里应注意:PLL的设定需要在使能之前,一旦PLL使能后参数不可更改。

由上述程序可以看出,系统时钟的设定是比较复杂的,外设越多,需要考虑的因素就越多。这种设定是有规律可循的,设定参数也很规范。

例如,加入RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA,ENABLE);语句,则使能DMA外设时钟。如果你想给模数转换器(ADC)设置时钟,可以在if(HSEStartUpStatus==SUCCESS)逻辑块中加入:

RCC_ADCCLKConfig(RCC_PCLK2_Div6);

这样,ADC的时钟就设为12MHz,即系统时钟的6分频。

注意: 由于USB时钟的数据传输标准为48MHz,因此你需经过1.5分频设置才可实现。

时钟设置

一般的,时钟设置需要先考虑系统时钟的来源,是内部RC、外部晶振、还是外部的振荡器,是否需要PLL。然后再考虑内部总线和外部总线,最后考虑外设的时钟信号。遵从先倍频作为CPU时钟,然后再由内向外分频的原则。

要注意的是,STM32处理器因为低功耗的需要,各模块需要分别独立开启时钟,所以,一定不要忘记给用到的模块和引脚使能时钟。

系统复位后,HSI振荡器被选为系统时钟。当时钟源被直接或通过PLL间接作为系统时钟时,它将不能被停止。只有当目标时钟源准备就绪了(经过启动稳定阶段的延迟或PLL稳定),从一个时钟源到另一个时钟源的切换才会发生。在被选择时钟源没有就绪前,系统时钟的切换不会发生;直至目标时钟源就绪,才发生切换。时钟控制寄存器(RCC_CR)中的状态位指示了哪个时钟已经准备好了,哪个时钟目前被用做系统时钟。

STM32单片机的时钟安全系统

时钟安全系统(CSS)可以通过软件被激活。一旦其被激活,时钟监测器将在HSE振荡器启动延迟后被使能,并在HSE时钟关闭后关闭。如果HSE时钟发生故障,HSE振荡器被自动关闭,时钟失效事件将被送到高级定时器(TIM1和TIM8)的刹车输入端,并产生时钟安全中断CSSI,允许软件进行紧急处理操作。此CSSI中断连接到Cortex-M3的NMI中断(不可屏蔽中断)。关于STM32单片机的中断,在后面的章节再做介绍。

注意: 一旦CSS被激活,并且HSE时钟出现故障,CSS中断就产生,并且NMI也自动产生。NMI将被不断执行,直到CSS中断挂起位被清除。因此,在NMI的处理程序中必须通过设置时钟中断寄存器(RCC_CIR)里的CSSC位来清除CSS中断。如果HSE振荡器被直接或间接(经PLL倍频)地作为系统时钟,时钟故障将导致系统时钟自动切换到HSI振荡器,同时外部HSE振荡器被关闭。在时钟失效时,如果HSE振荡器时钟是用做系统时钟的PLL的输入时钟,PLL也将被关闭。因此,STM32单片机的时钟系统具有很高的安全型。 ytIJecP4Qk4uPCGRIrw/HxKA/mdVArVz/b4Bk+Ct3obB8KbsQzvvNmfy8F47shLk

点击中间区域
呼出菜单
上一章
目录
下一章
×