滴答计时器

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

1. systick介绍
Systick 就是一个定时器而已,只是它放在了NVIC 中,主要的目的是为了
给操作系统提供一个硬件上的中断(号称滴答中断)。

没有学过操作系统的同学,
可能会很郁闷,啥叫滴答中断?这里来简单地解释一下。

操作系统进行运转的时
候,也会有“心跳”。

它会根据“心跳”的节拍来工作,把整个时间段分成很多
小小的时间片,每个任务每次只能运行一个“时间片”的时间长度就得退出给别
的任务运行,这样可以确保任何一个任务都不会霸占整个系统不放。

这个心跳,
可以通过定时器来周期性触发,而这个定时器就是systick。

很明显,这个“心跳”
是不允许任何人来随意地访问和修改的。

只要不把它在SysTick 控制及状态寄
存器中的使能位清除,就永不停息。

它有四个寄存器:
STK_CSR, 0xE000E010 -- 控制寄存器
STK_LOAD, 0xE000E014 -- 重载寄存器
STK_VAL, 0xE000E018 -- 当前值寄存器
STK_CALRB, 0xE000E01C -- 校准值寄存器
1、STK_CSR控制寄存器:寄存器内有4 个位t 具有意义
第0 位:ENABLE,Systick 使能位(0:关闭Systick 功能;1:开启Systick
功能)
第1 位:TICKINT,Systick 中断使能位(0:关闭Systick 中断;1:开启
Systick 中断)
第2 位:CLKSOURCE,Systick 时钟源选择(0:使用HCLK/8 作为Systick
时钟;1:使用HCLK 作为Systick 时钟)
第3 位:COUNTFLAG,Systick 计数比较标志,如果在上次读取本寄存器后,
SysTick 已经数到了0,则该位为1。

如果读取该位,该位将自动清零。

2、STK_LOAD 重载寄存器:
Systick 是一个递减的定时器,当定时器递减至0 时,重载寄存器中的值就
会被重装载,继续开始递减。

STK_LOAD 重载寄存器是个24 位的寄存器最大
计数0xFFFFFF。

3、STK_VAL当前值寄存器:
也是个24 位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同
时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG 标志。

4、STK_CALRB 校准值寄存器:
位31 NOREF :1=没有外部参考时钟(STCLK 不可用)0=外部参考时钟可用
位30 SKEW:1=校准值不是准确的1ms 0=校准值是准确的1ms 位[23:0] :Calibration value
Indicates the calibration value when the SysTick co unter runs on HCLK
max/8 as external clock. The value is product depen dent, please refer to
the Product Reference Manual, SysTick Calibration V alue section. When
HCLK is programmed at the maximum frequency, the Sy sTick period is 1ms.
If calibration information is not known, calculate the calibration value
required from the frequency of the processor clock
or external clock.
2. systick编程
现在我们想通过Systick 定时器做一个精确的延迟函数,比如让LED 精确延
迟1 秒钟闪亮一次。

思路:利用systick 定时器为递减计数器,设定初值并使能它后,它会每个
系统时钟周期计数器减1,计数到0 时,SysTick 计数器自动重装初值并继续
计数,同时触发中断。

那么每次计数器减到0,时间经过了:系统时钟周期* 计数器初值。

我们
使用72M 作为系统时钟,那么每次计数器减1 所用的时间是1/72M,计数器
的初值如果是72000,那么每次计数器减到0,时间经过(1/72M) * 72000 =
0.001,即1ms。

Q:什么是SYSTick定时器?
SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中
自动重装载定时初值。

只要不把它在SysTick 控制及状态寄存器中的使能位清
除,就永不停息。

Q:为什么要设置SysTick定时器?
(1)产生操作系统的时钟节拍
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。

在以前,大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作
为整个系统的时基。

因此,需要一个定时器来产生周期性的中断,而且最好还让
用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。

(2)便于不同处理器之间程序移植。

Cortex‐M3 处理器内部包含了一个简单的定时器。

因为所有的CM3 芯片都带
有这个定时器,软件在不同CM3 器件间的移植工作得以化简。

该定时器的时钟
源可以是内部时钟(FCLK,CM3 上的自由运行时钟),或者是外部时钟( C
M3 处理器上的STCLK 信号)。

不过,STCLK 的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率
可能会大不相同,你需要检视芯片的器件手册来决定选择什
么作为时钟源。

Sys
Tick 定时器能产生中断,CM3 为它专门开出一个异常类型,并且在向量表中有
它的一席之地。

它使操作系统和其它系统软件在CM3 器件间的移植变得简单多
了,因为在所有CM3 产品间对其处理都是相同的。

(3)作为一个闹铃测量时间。

SysTick 定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹
铃,用于测量时间等。

要注意的是,当处理器在调试期间被喊停(halt)时,则
SysTick 定时器亦将暂停运作。

Q:Systick如何运行?
首先设置计数器时钟源,CTRL->CLKSOURCE(控制寄存器)。

设置重载值(R
ELOAD 寄存器),清空计数寄存器VAL(就是下图的CURRENT)。

置CTRL
->ENABLE 位开始计时。

如果是中断则允许Systick 中断,在中断例程中处理。

如采用查询模式则不断读
取控制寄存器的COUNTFLAG 标志位,判断是否计时至零。

或者采取下列一种
方法
当SysTick 定时器从1 计到0 时,它将把COUNTFLAG 位置位;而下述方法
可以清零之:
1. 读取SysTick 控制及状态寄存器(STCSR)
2. 往SysTick 当前值寄存器(STCVR)中写任何数据
只有当VAL 值为0 时,计数器自动重载RELOAD。

Q:如何使用SysTicks作为系统时钟?
SysTick 的最大使命,就是定期地产生异常请求,作为系统的时基。

OS 都需要
这种“滴答”来推动任务和时间的管理。

如欲使能SysTick 异常,则把STCSR.TI
CKINT 置位。

另外,如果向量表被重定位到SRAM 中,还需要为SysTick 异
常建立向量,提供其服务例程的入口地址.
STM32的时钟树
对于广大初次接触STM32的读者朋友(甚至是初次接触ARM器件的读者朋友)来说,在熟悉了开发环境的使用之后,往往“栽倒”在同一个问题上。

这问题有个关键字叫:时钟树。

众所周知,微控制器(处理器)的运行必须要依赖周期性的时钟脉冲来驱动——往往由一个外部晶体振荡器提供时钟输入为始,最终转换为多个外部设备的周期性运作为末,这种时钟“能量”扩散流动的路径,犹如大树的养分通过主干流向各个分支,因此常称之为“时钟树”。

在一些传统的低端8位单片机诸如51,AVR,PIC等单片机,其也具备自身的一个时钟树系统,但其中的绝大部分是不受用户控制的,亦即在单片机上电后,时钟树就固定在某种不可更改的状态(假设单片机处于正常工作的状态)。

比如51单片机使用典型的12MHz晶振作为时钟源,则外设如IO口、定时器、串口等设备的驱动时钟速率便已经是固定的,用户无法将此时钟速率更改,除非更换晶振。

而STM32微控制器的时钟树则是可配置的,其时钟输入源与最终达到外设处的时钟速率不再有固定的关系,本文将来详细解析STM32微控制器的时钟树。

图1是STM32微控制器的时钟树,表1是图中各个标号所表示的部件。

标号图1标号释义
1 内部低速振荡器(LSI,40Khz)
2 外部低速振荡器(LSE,32.768Khz)
3 外部高速振荡器(HSE,3-25MHz)
4 内部高速振荡器(HIS,8MHz)
5 PLL输入选择位
6 RTC时钟选择位
7 PLL1分频数寄存器
8 PLL1倍频寄存器
9 系统时钟选择位
10 USB分频寄存器
11 AHB分频寄存器
12 APB1分频寄存器
13 AHB总线
14 APB1外设总线
15 APB2分频寄存器
16 APB2外设总线
17 ADC预分频寄存器
18 ADC外设
19 PLL2分频数寄存器
20 PLL2倍频寄存器
21 PLL时钟源选择寄存器
22 独立看门狗设备
23 RTC设备
图1 STM32的时钟树
在认识这颗时钟树之前,首先要明确“主干”和最终的“分支”。

假设使用外部8MHz晶振作为STM32的时钟输入源(这也是最常见的一种做法),则这个8MHz便是
“主干”,而“分支”很显然是最终的外部设备比如通用输入输出设备(GPIO)。

这样可以轻易找出第一条时钟的“脉络”:
3——5——7——21——8——9——11——13
对此条时钟路径做如下解析:
对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;对于5,通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);
对于7,设置外部晶振的分频数(假设1分频);
对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);
对于8,设置PLL倍频数(假设9倍频);
对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);
对于11,设置AHB总线分频数(假设1分频);
对于13,时钟到达AHB总线;
在上一章节中所介绍的GPIO外设属于APB2设备,即GPIO 的时钟来源于APB2总线,同样在图1中也可以寻获GPIO外设的时钟轨迹:3——5——7——21——8——9——11——15——16
对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;对于5,通过PLL选择位预先选择后续PLL分支的输入时钟
(假设选择外部晶振);
对于7,设置外部晶振的分频数(假设1分频);
对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);
对于8,设置PLL倍频数(假设9倍频);
对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);
对于11,设置AHB总线分频数(假设1分频);
对于15,设置APB2总线分频数(假设1分频);
对于16,时钟到达APB2总线;
现在来计算一下GPIO设备的最大驱动时钟速率(各个条件已在上述要点中假设):
1) 由3所知晶振输入为8MHz,由5——21知PLL的时钟源为经过分频后的外部晶振时钟,并且此分频数为1分频,因此首先得出PLL的时钟源为:8MHz / 1 = 8MHz。

2) 由8、9知PLL倍频数为9,且将PLL倍频后的时钟输出选择为系统时钟,则得出系统时钟为 8MHz * 9 = 72MHz。

3) 时钟到达AHB预分频器,由11知时钟经过AHB预分频器之后的速率仍为72MHz。

4) 时钟到达APB2预分频器,由15经过APB2预分频器后速率仍为72MHz。

5) 时钟到达APB2总线外设。

因此STM32的APB2总线外设,所能达到的最大速率为72MHz。

依据以上方法读者可以搜寻出APB1总线外设时钟、RTC外设时钟、独立看门狗等外设时钟的来龙去脉。

接下来从程序的角度分析时钟树的设置,程序清单如下:
void RCC_Configuration(void)
{
ErrorStatus
HSEStartUpStatus;
(1)
RCC_DeInit();
(2)RCC_HSEConfig(RCC_HSE_ON);
(3)
HSEStartUpStatus = RCC_WaitForHSEStartUp();
(4)
if(HSEStartUpStatus == SUCCESS)
(5)
{
RCC_HCLKConfig(RCC_SYSCLK_Div1);
(6)
RCC_PCLK2Config(RCC_HCLK_Div1);
(7)
RCC_PCLK1Config(RCC_HCLK_Div2);
(8)
FLASH_SetLatency(FLASH_Latency_2);
(9)
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_En able); (10)
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,
RCC_PLLMul_9);
(11)
RCC_PLLCmd(ENABLE);
(12)
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);
(13)
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
(14)
while(RCC_GetSYSCLKSource()!=0x08);
(15)
}
}
以上是ST官方所提供的STM32时钟树配置函数,读者首先要知道3点
1、ST所提供的库函数在函数和变量命名上有非常良好的规范性和易读性(虽然有点冗长),即便没有注释,也可从函数名和变量名来大致判断该函数或变量所包含的意义。

2、其次,读者应从上图区分出各个总线和对应的时钟:其中PLLCLK表示PLL锁相环的输出时钟,SYSCLK表示系统时钟,HCLK表示AHB总线的时钟,PCLK1表示APB1总线的时钟,PCLK2则表示APB2总线的时钟。

3、9、10两句代码的作用是设置STM32内部FLASH的等待周期。

做如下解释:STM32的内部用户FLASH用以存储代码指
令供CPU存取以执行,STM32的CPU的最大速率已知为72MHz,但FLASH无法达到这么高的速度,因此要在CPU存取FLASH 的过程中插入所谓的“等待周期”,显然CPU速度越快,所要插入的等待周期个数越多,原则是
1)当CPU速率为0 ~ 24MHz时,不需要插入等待周期,即等到周期个数为0;
2)当CPU速率为24 ~ 48MHz时,插入1个等待周期;
3)当CPU速率为48MHz ~ 72MHz时,插入2个等待周期;有以上三点准备之后,开始解析这段程序:
(1)定义一个ErrorStatus类型的变量HSEStartUpStatus;(2)将时钟树复位至默认设置;
(3)开启HSE晶振;
(4)等待HSE晶振起振稳定,并将起振结果保存至HSEStartUpStatus变量中;
(5)判断HSE晶振是否起振成功(假设成功了,进入if内部);
(6)设置HCLK时钟为SYSCLK的1分频;
(7)设置PLCK2时钟为SYSCLK的1分频;
(8)设置PLCK1时钟为SYSCLK的2分频;
(11)选择PLL输入源为HSE时钟经过1分频,并进行9倍频;
(12)使能PLL输出;
(13)等待PLL输出稳定;
(14)选择系统时钟源为PLL输出;
(15)等待系统时钟稳定;
上述代码中对时钟树的配置顺序为(对应图中标号): 3——11——14——15——7——21——8——9
通过对比发现,程序中对时钟树的配置顺序并不是依次从图中由左到右、由上到下配置的,这是为什么呢?事实上这个问题相信大部分读者都可以自己解释:电子设计世界的思维和操作方式,其顺序往往和日常生活是不一样的,比如人们经常先给电视机连接电源,再打开电视机开关;先把大水管的总闸打开,再打开小水龙头;总而言之是一种由“主”到“次”的顺序。

转移到最常见的51单片机的开发平台,开发人员往往先把定时器的分频数,重载值等参数配置好,最后才启动定时器计数;先把各个外设的中断打开,最后再打开总中断;这和人们的生活习惯其实恰好相反,是一种先“次”后“主”的顺序。

至此,理解STM32的时钟树就是轻而易举的事情了。

STM32的IO口可以由软件配置成8种模式:
1、输入浮空
2、输入上拉
3、输入下拉
4、模拟输入
5、开漏输出
6、推挽输出
7、推挽式复用功能
8、开漏复用功能
每个IO口可以自由编程,单IO口寄存器必须要按32位字被访问。

STM32的很多IO口都是5V兼容的,这些IO口在与5V电平的外设连接的时候很有优势,具体哪些IO口是5V兼容的,可以从该芯片的数据手册管脚描述章节查到(I/O Level标FT的就是5V电平兼容的)。

STM32的每个IO端口都有7个寄存器来控制。

他们分别是:配置模式的2个32位的端口配置寄存器CRL和CRH;2个32位的数据寄存器IDR和ODR;1个32位的置位/复位寄存器BSRR;一个16位的复位寄存器BRR;1个32位的所存寄存器LCKR;这里我们仅介绍常用的几个寄存器,我们常用的IO端口寄存器只有4个:CRL、CRH、IDR、ODR。

CRL和CRH控制着每个IO口的模式及输出速率。

STM32的IO口位配置表如表3.1.1.1所示:
表3.1.1.1 STM32的IO口位配置表
STM32输出模式配置如表3.1.1.2所示:
表3.1.1.2 STM32输出模式配置表
接下来我们看看端口低配置寄存器CRL的描述,如下图所示:
图3.1.1.1 端口低配置寄存器CRL各位描述
该寄存器的复位值为0X4444 4444,从上图可以看到,复位值其实就是配置端口为浮空输入模式。

从上图还可以得出:STM32的CRL控制着每个IO端口(A~G)的低8位的模式。

每个IO端口的位占用CRL的4个位,高两位为CNF,低两位为MODE。

这里我们可以记住几个常用的配置,比如0X4表示模拟输入模式(ADC用)、0X3表示推挽输出模式(做输出口用,50M速率)、0X8表示上/下拉输入模式(做输入口用)、0XB表示复用输出(使用IO口的第二功能,50M速率)。

CRH的作用和CRL完全一样,只是CRL控制的是低8位输出口,而CRH控制的是高8位输出口。

这里我们对CRH就不做详细介绍了。

给个实例,比如我们要设置PORTC的11位为上拉输入,12位为推挽输出。

代码如下:
GPIOC->CRH&=0XFFF00FFF;//清掉这2个位原来的设置,同时也不影响其他位的设置
GPIOC->CRH|=0X00038000; //PC11输入,PC12输出GPIOC->ODR=1<<11;//PC11上拉
通过这3句话的配置,我们就设置了PC11为上拉输入,PC12为推挽输出。

IDR是一个端口输入数据寄存器,只用了低16位。

该寄存器为只读寄存器,并且只能以16位的形式读出。

该寄存器各位的描述如下图所示:
图3.1.1.2 端口输入数据寄存器IDR各位描述
要想知道某个IO口的状态,你只要读这个寄存器,再看某个位的状态就可以了。

使用起来是比较简单的。

ODR是一个端口输出数据寄存器,也只用了低16位。

该寄存器虽然为可读写,但是从该寄存器读出来的数据都是0。

只有写是有效的。

其作用就是控制端口的输出。

该寄存器的各位描述如下图所示:
图3.1.1.3 端口输出数据寄存器ODR各位描述
#include <stm32f10x_lib.h>
#include "led.h"
//Mini STM32开发板
//LED驱动代码
//正点原子@ALIENTEK
//2010/5/27
// V1.0
//初始化PA8和PD2为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
RCC->APB2ENR|=1<<2; //使能PORTA时钟
RCC->APB2ENR|=1<<5; //使能PORTD时钟
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;//PA8 推挽输出
GPIOA->ODR|=1<<8; //PA8 输出高
GPIOD->CRL&=0XFFFFF0FF;
GPIOD->CRL|=0X00000300;//PD.2推挽输出
GPIOD->ODR|=1<<2; //PD.2输出高
}
该代码里面就包含了一个函数void LED_Init(void),该函
数的功能就是用来实现配置PA8和PD2为推挽输出。

在配置STM32外设的时候,任何时候都要先使能该外设的时钟!APB2ENR是APB2总线上的外设时钟使能寄存器,其各位的描述如下:
图3.1.3.2 寄存器APB2ENR各位描述
我们要使能的PORTA和PORTD的时钟使能位,分别在bit2和bit5,只要将这两位置1就可以使能PORTA和PORTD的时钟了。

该寄存器还包括了很多其他外设的时钟使能。

大家在以后会慢慢使用到的。

关于这个寄存器的详细说明在《STM32参考手册》的第61页。

在设置完时钟之后就是配置完时钟之后,LED_Init配置了PA8和PD2的模式为推挽输出,并且默认输出1。

这样就完成了对这两个IO口的初始化。

保存led.c代码,然后我们按同样的方法,新建一个led.h 文件,也保存在LED文件夹下面。

在led.h中输入如下代码:#ifndef __LED_H
#define __LED_H
#include "sys.h"
//Mini STM32开发板
//LED驱动代码
//正点原子@ALIENTEK
//2010/5/27
//LED端口定义
#define LED0 PAout(8)// PA8
#define LED1 PDout(2)// PD2
void LED_Init(void);//初始化
#endif
这段代码里面最关键就是2个宏定义:
#define LED0 PAout(8)// PA8
#define LED1 PDout(2)// PD2
这里使用的是位带操作来实现操作某个IO口的1个位的,关于位带操作前面已经有介绍,这里不再多说。

需要说明的是,这里可以使用另外一种操作方式实现。

如下:
#define LED0 (1<<8) //led0 PA8
#define LED1 (1<<2) //led1 PD2
#define LED0_SET(x) GPIOA->ODR=(GPIOA->ODR&~LED0)|(x ? LED0:0)
#define LED1_SET(x) GPIOD->ODR=(GPIOD->ODR&~LED1)|(x ? LED1:0)
后者通过LED0_SET(0)和LED0_SET(1)来控制PA8的输出0和1。

而前者的类似操作为:LED0=0和LED0=1。

显然前者简
单很多,从而可以看出位带操作带来的好处。

以后像这样的IO口操作,我们都使用位带操作来实现,而不使用第二种方法。

STM32的串口是相当丰富的,功能也很强劲。

最多可提供5路串口(MiniSTM32使用的是STM32F103RBT6,具有3个串口),有分数波特率发生器、支持单线光通信和半双工单线通讯、支持LIN、智能卡协议和IrDASIR ENDEC规范(仅串口3支持)、具有DMA等。

串口最基本的设置,就是波特率的设置。

STM32的串口使用起来还是蛮简单的,只要你开启了串口时钟,并设置相应IO口的模式,然后配置一下波特率,数据位长度,奇偶校验位等信息,就可以使用了。

下面,我们就简单介绍下这几个与串口基本配置直接相关的寄存器。

1,串口时钟使能。

串口作为STM32的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口1是在APB2ENR寄存器的第14位。

APB2ENR寄存器在之前已经介绍过了,这里不再介绍。

只是说明一点,就是除了串口1的时钟使能在APB2ENR寄存器,其他串口的时钟使能位都在APB1ENR。

2,串口复位。

当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。

一般在系统刚开始配置外设
的时候,都会先执行复位该外设的操作。

串口1的复位是通过配置APB2RSTR寄存器的第14位来实现的。

APB2RSTR寄存器的各位描述如下:
图3.3.1.1寄存器APB2RSTR各位描述
从上图可知串口1的复位设置位在APB2RSTR的第14位。

通过向该位写1复位串口1,写0结束复位。

其他串口的复位位在APB1RSTR里面。

3,串口波特率设置。

每个串口都有一个自己独立的波特率寄存器USART_BRR,通过设置该寄存器达到配置不同波特率的目的。

该寄存器的各位描述如下:
图3.3.1.2寄存器USART_BRR各位描述
前面提到STM32的分数波特率概念,其实就是在这个寄存器里面体现的。

最低4位用来存放小数部分DIV_Fraction,[15:4]这12位用来存放整数部分DIV_Mantissa。

高16位未使用。

这里波特率的计算通过如下公式计算:
这里的fpclkx(x=1、2)是给外设的时钟(PCLK1用于串口2、3、4、5,PCLK2用于串口1),USARTDIV是一个无符号的定点数,它的值可以有串口的BRR寄存器值得到。

而我
们更关心的是如何从USARTDIV的值得到USART_BRR的值,因为一般我们知道的是波特率,和PCLKx的时钟,要求的就是USART_BRR的值。

下面我们来介绍如何通过USARTDIV得到串口USART_BRR寄存器的值,假设我们的串口1要设置为9600的波特率,而PCLK2的时钟为72M。

这样,我们根据上面的公式有:USARTDIV=72000000/9600*16=468.75
那么得到:DIV_Fraction=16*0.75=12=0X0C; DIV_Ma ntissa= 468=0X1D4;
这样,我们就得到了USART1->BRR的值为0X1D4C。

只要设置串口1的BRR寄存器值为0X1D4C就可以得到9600的波特率。

4,串口控制。

STM32的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。

这里我们只要用到USART_CR1就可以实现我们的功能了。

BIT 13: 串口功能;
BIT 12: MODE,字长。

0: 1个开始位,8个数据位,1位停止位(默认);1: 1个开始位,9位数据位,1位停止位(默认);
*注意:停止位的长度可在USART_CR2寄存器中设置。

BIT 11: WAKE 唤醒功能
BIT 10: 校检使能位,当激活奇偶校验功能时,置位该位将自动往要传输数据的高位字节处插入就校验位。

BIT 09: Parity Selection,0:偶校验;1:奇校验。

BIT 08: PE Interrupt Enable
BIT 07: 发送缓冲区空中断使能位
BIT 06: 发送完成中断使能位
BIT 05: 接收缓冲区非空中断使能位
BIT 04: Idle Interrupt Enable
BIT 03: Transfer Enable
BIT 02: Receive Enable
BIT 01: Receiver Wakeup
BIT 00: Send Break
5,数据发送与接收。

STM32的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR。

当向该寄存器写数据的时候,串口就会自动发送,当收到收据的时候,也是存在该寄存器内。

该寄存器的各位描述如下:
图3.3.1.3寄存器USART_DR各位描述
可以看出,虽然是一个32位寄存器,但是只用了低9位(DR[8:0]),其他都是保留。

DR[8:0]为串口数据,包含了发送或接收的数据。

由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能。

TDR寄存器提供了内部总线和输出移位寄存器之间的并行接口。

RDR寄存器提供了输入移位寄存器和内部总线之间的并行接口。

当使能校验位(USART_CR1种PCE位被置位)进行发送时,写到MSB的值(根据数据的长度不同,MSB是第7位或者第8位)会被后来的校验位该取代。

当使能校验位进行接收时,读到的MSB位是接收到的校验位。

6,串口状态。

串口的状态可以通过状态寄存器USART_SR读取。

USART_SR的各位描述如下:
图3.3.1.4寄存器USART_SR各位描述
这里我们关注一下两个位,第5、6位RXNE和TC。

RXNE(读数据寄存器非空),当该位被置1的时候,就是提示已经有数据被接收到了,并且可以读出来了。

这时候我们要做的就是尽快去读取USART_DR,通过读USART_DR可以将该位清零,也可以向该位写0,直接清除。

TC(发送完成),当该位被职位的时候,表示USART_DR内的数据已经被发送完成了。

如果设置了这个位的中断,则会
产生中断。

该位也有两种清零方式:1)读USART_SR,写USART_DR。

2)直接向该位写0。

//初始化IO 串口1
//pclk2 CLK2时钟频率(Mhz)
//bound:波特率
void uart_init(u32 pclk2,u32bound)
{
floattemp;
u16mantissa;
u16fraction;
temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV
mantissa=temp;
//得到整数部分
fraction=(temp-mantissa)*16;//得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2;
//使能PORTA口时钟
RCC->APB2ENR|=1<<14;
//使能串口时钟
GPIOA->CRH=0X444444B4;//IO状态设置
RCC->APB2RSTR|=1<<14;
//复位串口1
RCC->APB2RSTR&=~(1<<14);//停止复位
//波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C;
//1位停止,无校验位.
#ifdef EN_USART1_RX
//如果使能了接收
//使能接收中断
USART1->CR1|=1<<8;
//PE中断使能
USART1->CR1|=1<<5;
//接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART1_IRQChannel,2);//组2,最低优先级
#endif
}
从该代码可以看出,其初始化串口的过程,和我们前面介绍的一致先计算得到USART1->BRR的内容。

然后开始初始化串口引脚,接着把USART1复位,然之后设置波特率和奇偶校验等。

这里需要注意一点,因为我们使用到了串口的中断接收,必须在usart.h里面定义 EN_USART1_RX 。

该函数才会配置中断使能,以及开启串口1的NVIC中断。

这里我们把串口1中断放在组2,优先级设置为组2里面的最低。

再介绍一下串口1的中断服务函数USART1_IRQHandler,该函数的名字不能自己定义了,MDK已经给每个中断都分配了一个固定的函数名,我们直接用就可以了。

具体这些函数的名字是什么,我们可以在MDK提供的例子里面,找到stm32f10x_it.c,该文件里面包含了STM32所有的中断服务函数。

USART1_IRQHandler的代码如下:
void USART1_IRQHandler(void)
{
u8res;
if(USART1->SR&(1<<5))//接收到数据
{
res=USART1->DR;
if((USART_RX_STA&0x80)==0)//接收未完成
{
if(USART_RX_STA&0x40)//接收到了0x0d
{
if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始elseUSART_RX_STA|=0x80;
//接收完成了
}else //还没收到0X0D
{
if(res==0x0d)USART_RX_STA|=0x40;
else
{
USART_RX_BUF[USART_RX_STA&0X3F]=res;
USART_RX_STA++;
if(USART_RX_STA>63)USART_RX_STA=0;//接收数据错误,重新开始接收
}}}}}
该函数的重点就是判断接收是否完成,通过检测是否收到0X0D、0X0A的连续2个字节(回车键)来检测是否结束。

当检测到这个结束序列之后,就会置位USART_RX_STA的最高为来标记已经收到了一次数据。

之后等待外部函数清空该位之后才开始第二次接收。

所接收的数据全部存放在USART_RX_BUF里面,一次接收数据不能超过64个字节,否则被丢弃。

介绍完了这两个函数,我们回到test.c,在test.c里面编写如下代码:
#include <stm32f10x_lib.h>
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
//Mini STM32开发板范例代码3
//串口实验
//正点原子@ALIENTEK
//2010.5.28
int main(void)
{
u8t;
u8len;
u16times=0;
Stm32_Clock_Init(9);//系统时钟设置delay_init(72);
//延时初始化
uart_init(72,9600);
//串口初始化为9600
LED_Init();
//初始化与LED连接的硬件接口
while(1)
{。

相关文档
最新文档