1 STM32基础
1、内存映射
CPU控制外设的流程
- 寄存器 - 存储单元
- 将寄存器看做是4个字节的存储区 - 给存储区起了一个名字,寄存器和寄存器之间是一个个的连续存储区
- 将寄存器看做是4个字节的存储区 - 给存储区起了一个名字,寄存器和寄存器之间是一个个的连续存储区
内存:
flash:0x8000000 ~ 0x807ffff 为 512KB
SRAM:0X20000000 ~ 0X2000FFFF 为 64KB
block2:0X40000000 ~ 0X5FFFFFFF 用于存放外设控制器
GPIOB控制器:0x40010c00 ~ 0x40010fff
控制器分配的内存空间存储的就是一个一个的寄存器
|-------------GPIOB控制器--------------------|
|xxxx|xxxx|xxxx|yyyy|xxxx|xxxx|xxxx|xxxx|xxxx|
0c00 04 08 0c 10 14 18 1c …
拿到控制器的首地址 + 寄存器对应的偏移量 == 拿到 对应的寄存器的首地址
地址:*(unsigned int *)(0x40010c00 + 0x0c)
三条总线:
- 目的 - 通过总线找到对应控制器首地址
- APB1 起始地址 - 0x40000000
- APB2 起始地址 - 0x40010000
- AHB 起始地址 - 0x40020000
在实际编码的过程中,可以使用以下宏定义
#define APB2_BASE (0X40010000)
#define GPIOB_BASE (APB2_BASE + 0X0C00)
#define USART1_BASE (APB2_BASE + 0X3800)
// 根据总线找到对应的控制器,然后找到寄存器,得到地址
手册:
STM32F103ZET6.pdf 看内存映射
STM32F1xx中文参考手册.pdf 看控制器组成、寄存器、存储区映像、时钟树…
2、GPIO
-
地址划分
-
GPIO结构
- GPIO就是指CPU主板上的一组引脚
- 这些引脚可以发送或者接收电信号, 但是其不会任何特定目的而设置,称之为通用I/0
- 使用分组的方式管理所有的GPIO引脚
- STM32F103ZET6 - 分为了7组 A B C D E F G 给每一组引脚都分配了一个控制器:GPIOA GPIOB GPIOC GPIODGPIOE GPIOF GPIOG
- 引脚 - PA5 - GPIOA组的第5个引脚
- PB5 - GPIOB组的第5个引脚;
-
GPIO的输入输出模式
- 输入模式
- 输入浮空
3.3V/0V电压电信号—>PB5 I/O端口—>TTL施密特触发器转换逻辑1/0—>输入数据寄存器(地址)—>CPU核指针获取高低 如果不操作,此I/O端口的电平是不确定的,具体的电平状态完全有外设来决定! - 输入上拉
3.3V/0V电压电信号—>PB5 I/O端口—>TTL施密特触发器转换逻辑1/0—>输入数据寄存器(地址)—>CPU核指针获取高低 上拉输入的优点就是输入的电平不会上下浮动而导致输入信号不稳定,在没有信号输入的情况下可以稳定在高电平 - 输入下拉
3.3V/0V电压电信号—>PB5 I/O端口—>TTL施密特触发器转换逻辑1/0—>输入数据寄存器(地址)—>CPU核指针获取高低 下拉输入的优点就是输入的电平不会上下浮动而导致输入信号不稳定,在没有信号输入的情况下可以稳定在低电平 - 模拟输入
模拟电压信号—>关闭上下拉电阻—>关闭TTL施密特触发器—>输入到ADC控制器(电压模拟信号转数字信号1/0)
- 输入浮空
- 输出模式
- 开漏输出
只能输出低电平,输出低电平,N-MOS导通,如果要输出高电平,则需要外接上拉电阻
GPIO控制器—>位设置/位清除寄存器—>输出数据寄存器—>输出0—>N-MOS导通—>I/O端口输出低电平
GPIO控制器—>位设置/位清除寄存器—>输出数据寄存器—>输出1—>N-MOS关闭—>外接上拉电阻拉高,输出高电平
与此同时:I/O端口高低电平—>TTL施密特触发器->输入数据寄存器—>CPU核获取I/O电平状态 - 开漏复用功能
只能输出低电平,输出低电平,N-MOS导通,如果要输出高电平,则需要外接上拉电阻
I2C控制器—>其内部寄存器—>输出0—>N-MOS导通—>I/O端口输出低电平
I2C控制器—>其内部寄存器—>输出1—>N-MOS关闭—>外接上拉电阻拉高,输出高电平
与此同时:I/O端口高低电平—>TTL施密特触发器->输入数据寄存器—>CPU核获取I/O电平状态 - 推挽输出
GPIO控制器—>位设置/位清除寄存器—>输出数据寄存器—>输出0—>N-MOS导通—>I/O端口输出低电平
GPIO控制器—>位设置/位清除寄存器—>输出数据寄存器—>输出1—>P-MOS导通—>I/O端口输出高电平
输出高电平时,电流输出到负载,叫灌电流,可以理解成推,输出低电平时,负载电流流向芯片,叫拉电流,即挽。
与此同时:I/O端口高低电平—>TTL施密特触发器->输入数据寄存器—>CPU核获取I/O电平状态 - 推挽复用输出
UART控制器—>其内部寄存器—>输出0—>N-MOS导通—>I/O端口输出低电平
UART控制器—>其内部寄存器—>输出1—>P-MOS导通—>I/O端口输出高电平
与此同时:I/O端口高低电平—>TTL施密特触发器->输入数据寄存器—>CPU核获取I/O电平状态
- 开漏输出
- 输入模式
3、使用寄存器配置PB5
新建keil工,创建stm32f10x.h
#ifndef __STM32F10X_H_
#define __STM32F10X_H_
// 获取总线的地址 所有总线地址 + APB2 + AHB
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
// 获取GPIOB控制器 + RCC总线地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
// rcc寄存器
#define RCC_APB2ENR (*(unsigned int*)(RCC_BASE+0x18))
// 获取对应的寄存器的存储区
#define GPIOB_CRL (*(unsigned int*)(GPIOB_BASE+0x00))
#define GPIOB_CRH (*(unsigned int*)(GPIOB_BASE+0x04))
#define GPIOB_IDR (*(unsigned int*)(GPIOB_BASE+0x08))
#define GPIOB_ODR (*(unsigned int*)(GPIOB_BASE+0x0C))
#define GPIOB_BSRR (*(unsigned int*)(GPIOB_BASE+0x10))
#define GPIOB_BRR (*(unsigned int*)(GPIOB_BASE+0x14))
#define GPIOB_LCKR (*(unsigned int*)(GPIOB_BASE+0x18))
#endif
创建main.c函数
#include "stm32f10x.h"
void SystemInit(void){ }
// 延时函数
void deley(unsigned int x){
while(x--);
}
int main(){
//实现LED灯的初始化
// 1.1 打开GPIO时钟
RCC_APB2ENR |= (1<<3); // [3]==1
// 1.2 配置GPIOB为推挽输出 50MHz
GPIOB_CRL &=~(0xF<<20); // [23:20] ==0000
GPIOB_CRL |= (3<<20); // [23:20] == 0011
// 2. GPIOB5输出高电平
GPIOB_ODR |=(1<<5);
while(1){
// 循环开关灯
// 开灯
GPIOB_ODR&=~(1<<5);
// 延时
deley(0xfffff);
// 关灯
GPIOB_ODR|=(1<<5);
// 延时
deley(0xfffff);
}
}
4、库函数
针对寄存器复杂的操作,使得代码的可读性差,开发工作量大和代码的移植性差,制定了CMSIS标准库。
因为基于Cortex内核的芯片生产厂商有很多,不只是ST公司。为了解决不同厂家生产的Crotex芯片软件兼容问题,ARM公司和其他芯片厂商制定了这个CMSIS标准即:ARM Cortex微控制器软件接口标准
作用:屏蔽硬件操作细节,将复杂的寄存器统统封装起来,咱们直接调用库函数操作各个控制器和寄存器
文档:
1.查看寄存器, 控制器功能
STM32F1xx中文参考手册.pdf
2.看库函数如何使用
STM32固件库使用手册(中文翻译版).pdf
GPIO寄存器:查看库文件得出
// GPIO 定义 是GPIOB控制器的首地址
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
// 在GPIOB控制器的首地址开始, 存储了一个GPIO_TypeDef类型的数据
typedef struct
{
u32 CRL;
u32 CRH;
u32 IDR;
u32 ODR;
u32 BSRR;
u32 BRR;
u32 LCKR;
} GPIO_TypeDef;
// GPIO初始化信息
typedef struct
{
uint16_t GPIO_Pin; // 管脚
GPIOSpeed_TypeDef GPIO_Speed; // 速度
GPIOMode_TypeDef GPIO_Mode; // 模式
}GPIO_InitTypeDef;
具体的GPIO库函数,请参考STM32固件库使用手册(中文翻译版).pdf 的第10章GPIO
5、使用库函数配置PB5
在项目下新建system目录,在system中创建LED目录,打开keil项目,新建led.c和led.h文件,然后将led.c加入工程组,在魔术棒中添加LED的路径
编辑led.h
#ifndef __LED_H_
#define __LED_H_
// 包含总头文件
#include "stm32f10x.h"
// 初始化函数
void Led_Init(void);
// 开灯
void Led_On(void);
// 关灯
void Led_Off(void);
// 延时
void Delay(unsigned int n);
#endif
编辑led.c
#include "led.h"
// 初始化
void Led_Init(void){
// 1 打开GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_Config;
// 2 配置GPIOB5为推挽输出 50MHz
GPIO_Config.GPIO_Mode=GPIO_Mode_Out_PP; // 推挽输出
GPIO_Config.GPIO_Speed=GPIO_Speed_50MHz;// 50MHz
GPIO_Config.GPIO_Pin = GPIO_Pin_5; // 第5个引脚
// GPIO 初始化
GPIO_Init(GPIOB,&GPIO_Config);
// 3 GPIOB5 输出高电平
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
// 开灯
void Led_On(void){
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
// 关灯
void Led_Off(void){
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}
// 延时
void Delay(unsigned int n){
while(n--);
}
编辑main.c
#include "led.h"
int main(){
Led_Init();
while(1){
Led_On();
Delay(0xffffff);
Led_Off();
Delay(0xffffff);
}
}
6、位带操作
6.1 概念
- 给寄存器的每个BIT位都指定一个唯一的地址,以后访问这个地址就是在访问这个BIT位,比方说BSRR寄存器有32个位,那么可以映射到32个地址上,当我们去访问这32个地址就达到访问32个比特的目的
例如:将地址0x40002000的寄存器的[12]设置为0, 就可以关闭蓝牙
方式一:寄存器
#define REG (*(unsigned int *)0x40020000)
REG &= ~(1 << 12);
方式2:
BIT12 = 0; // 将0赋值给BIT12, 此时就会将0x40002000的[12]设置为0, 关闭蓝牙
如何实现方式2呢,将0x40002000寄存器的[12]和一个4字节的存储区建立映射关系,建立映射关系后, 操作该4字节存储区就是在操作该寄存器的[12]
解释:
位带区 映射关系 位带别名区
|----z-----| <----------------> |xxxxxxxxxxxxxxxxxxxx|
0x40002000 0x40003000 (BIT12)
z为该寄存器[12] 4字节存储区
- 将数字0放到BIT12这个4字节存储区中, 实际上就是将0放到了0x40002000的[12]中, 此时该寄存器的[12]为0:BIT12 = 0;
- 将数字1放到BIT12这个4字节存储区中, 实际上就是将1放到了0x40002000的[12]中, 此时该寄存器的[12]为1:BIT12 = 1;
- 读取BIT12的值给val,就是将0x40002000寄存器的[12]的值给val:unsigned int val = BIT12;
位带区与位带别名区
位带区和位带别名区地址空间划分
位带区与位带别名区地址转换
AliasAddr = ((A & 0xF0000000)+0x02000000+((A &0x000FFFFF)<<5)+(n<<2))
A: 表示我们要操作的那个位所在的寄存器的地址 n: 位序号
例如 将0x40002000寄存器的[12]建立映射关系, 拿到对应位带别名区首地址
AliasAddr = ((0x40002000&0xF0000000)+0x02000000)+((0x40002000 & 0x000FFFFF)<<5)+(12<<2))
6.2 位带
在keil工程中在system文件夹下添加system.h文件,编辑
#ifndef __SYSTEM_H_
#define __SYSTEM_H_
#include "stm32f10x.h"
// IO口操作宏定义
// 获取寄存器地址位addr的第bitnum位的位带别名区的首地址
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
// 根据某个地址,获取该地址上的无符号4字节存储区
#define MEM_ADDR(addr) (*(volatile unsigned int *)(addr))
// 得到寄存器地址位addr
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
// 获取输出寄存器的首地址
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
// 获取输入寄存器的首地址
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
// IO口操作
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) // 输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) // 输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) // 输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) // 输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) // 输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) // 输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) // 输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) // 输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) // 输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) // 输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) // 输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) // 输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) // 输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) // 输入
#endif
在led.h中加入system.h头文件,并添加位带操作
#include "system.h"
...
// 位带
#define LED PBout(5)
...
在main.c中就可以直接使用位带操作了
#include "led.h"
int main(){
Led_Init();
while(1){
LED=0;
Delay(0xffffff);
LED=1;
Delay(0xffffff);
}
}
7、时钟树
7.1 概念
-
时钟是由电路产生的具有周期性的脉冲信号,相当于单片机的心脏,要想使用单片机的外设必须开启相应的时钟,驱动外设的本质是操作寄存器,而寄存器是由D触发器构成,而触发器需要时钟才能改写值,所以要想操作寄存器必须开启对应外设的时钟。
-
STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟,主要也是为了降低整个芯片的功耗,所有外设时钟默认都是关闭状态(disable)当我们使用某个外设就要开启这个外设的时钟(enable),不同外设需要的时钟频率不同,没必要所有外设都用高速时钟造成浪费,而且有些外设也接受不了这么高的频率,这也是为什么STM32有五个时钟源的原因,就是兼容不同速度的外设,STM32的五个时钟源分别为:HSE、LSE、HSI、LSI、PLL
解释:
HSE:高速外部时钟,一般为8M
HSI:高速内部时钟,8M,不稳定一般不用
LSE:低速外部时钟,一般为实时时钟提供信号
LSI:低速内部时钟,给看门狗提供时钟信号
PLLXTPRE:分频
PLLSRC:锁相环,用于将一个参考时钟信号转换为更加精准的输出时钟信号
PLLMUL:倍频器 将时钟放大
SW:时钟选择器,选择时钟
CSS:检测HSE是否正常
AHB预分频器:分频器 将时钟缩小
7.2 库函数中时钟函数解析
// 查看SystemInit
RCC->CR |= (uint32_t)0x00000001; // 打开内部HSI时钟
RCC->CFGR &= (uint32_t)0xF8FF0000; // 重置系统时钟
// 将CR的16、18、19、24位设置为0,将CFGR的22到16位设置为0
RCC->CR &= (uint32_t)0xFEF6FFFF;
RCC->CR &= (uint32_t)0xFFFBFFFF;
RCC->CFGR &= (uint32_t)0xFF80FFFF;
RCC->CR &= (uint32_t)0xEBFFFFFF;
RCC->CIR = 0x009F0000; // 关闭中断
RCC->CR |= ((uint32_t)RCC_CR_HSEON); // 启用HSE时钟
// 判断HSE是否起振
do{
// CR的第17位为0表示HSE未起振,为1时,表示HSE正常起振
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; // HCLK=72MHz
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; // APB2=72MHz
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; // APB1=36MHz
RCC->CR |= RCC_CR_PLLON; // 启动PLL
while((RCC->CR & RCC_CR_PLLRDY) == 0){} // 判断PLL是否准备好,否则就死等
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); // 重置
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; // 选择将PLLCLK为系统时钟
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){}// 判断PLLCLK是否准备好,如果没有则死等
SYSCLK(系统时钟) = 72MHz
AHB 总线时钟(HCLK=SYSCLK) = 72MHz
APB1 总线时钟(PCLK1=SYSCLK/2) = 36MHz
APB2 总线时钟(PCLK2=SYSCLK/1) = 72MHz
PLL 主时钟 = 72MHz
使用库函数来初始化时钟
void RCC_HSE_Config(void){
// 先设置为默认值
RCC_DeInit();
// 打开HSE时钟
RCC_HSEConfig(RCC_HSE_ON);
// 判断HSE时钟是否使能
if(RCC_WaitForHSEStartUp() == SUCCESS){
// 配置AHB时钟 1分频
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// 配置APB1时钟 2分频
RCC_PCLK1Config(RCC_SYSCLK_Div2);
// 配置APB2时钟 1分频
RCC_PCLK2Config(RCC_SYSCLK_Div1);
// 配置PLL时钟源HSE,9倍频
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
// 使能PLL
RCC_PLLCmd(ENABLE);
// 判断PLL是否使能
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);
// 配置PLL为SYSCLK的时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 判断PLLCLK确定为SYSCLK的时钟源
while(RCC_GetSYSCLKSource() != 0x08);
}
}
8、Systick定时器
8.1 基本概念
- SysTick定时器(又名系统滴答定时器) 只要是ARM Cotex-M系列内核的处理器都包含这个定时器。使用内核的SysTick定时器来实现延时,可以不占用其他定时器,节约资源。
- SysTick定时器是一个24位递减计数器,每个时钟周期减1,当减到0时,会自动重载定时初值开始新一轮计数。通过设置这个定时初值,就可以实现得到指定时间。如下图所示,y为定时器初值,然后随着时间增加,值逐渐减小,直至为0,再重新加载初值,如此往复,x1、x2、x3这些时间段,就是我们需要的延时时间。
假设STM32F103的CPU核工作在72MHz(FCLK),即72000000Hz
- 1s则计数72000000次
- 1ms则计数72000000/1000=72000次
- 1us则计数72000/1000=72次
相关寄存器
[0] : ENABLE 使能位 0=关闭systick功能,1=开启systick功能
[1] : TICKINT 中断使能位 0=关闭systick中断,1=开启systick中断
[2] : CLKSOURCE 时钟源选择位 0=使用HCLK/8时钟源 1=使用HCLK时钟源
[16]: COUNTFLAG 计数比较标志,如果计数器达到0,则读入为1;当读取或清除当前计数器值时,将自动清除为0;
Systick定时器的操作步骤四步骤:
- 设置systick定时器的时钟源
SysTick->CTRL &= ~(1 << 2); // 选择外部时钟源9MHz - 设置systick定时器的重载初始值
SysTick->LOAD = 延时时间; - 清零systick定时器当前计数器的值
SysTick->VAL=0x00; - 打开systick定时器
SysTick->CTRL|=1; //bit[0] = 1
8.2 库函数实现
在system目录下新建SYSTICK目录,打开keil,新建systick.h和systick,c文件
编辑systick.h文件
#ifndef __SYSTICK_H_
#define __SYSTICK_H_
#include "stm32f10x.h"
void SysTick_Init(void); // 初始化函数
void delay_ms(u32 nms); // 延时nms毫秒
void delay_us(u32 nus); // 延时nus微秒
#endif
编辑systick.c文件
#include "systick.h"
static u32 fac_us = 0;// 延迟1微秒的次数
static u32 fac_ms = 0;// 延迟1毫秒的次数
void SysTick_Init(void){// 初始化函数
SysTick->CTRL &= ~(1<<2); // 配置时钟源为9MHz
fac_us = 9; // 配置1us计数
fac_ms = 9000; // 配置1ms计数
}
/* 初始值 - nms*fac_ms
对于9M的定时器而言,最大的毫秒数最大为1864ms
*/
void delay_ms(u32 nms){
SysTick->LOAD = nms*fac_ms;// 延时nms毫秒的初始值
SysTick->VAL =0; // 清空计数器
SysTick ->CTRL|=1; // 启动定时器,每个时钟周期自减1
u32 temp;
do{
temp = SysTick->CTRL;
}while(((temp&0x01)==0x01)&&(((temp>>16)&0x01) != 0x01));
SysTick->CTRL&=~1; // 关闭定时器
SysTick->VAL =0; // 清空计数器
}
// 初始值 - nus*fac_us
void delay_us(u32 nus){ // 延时nus微秒
SysTick->LOAD = nus*fac_us;
SysTick->VAL =0; // 清空计数器
SysTick ->CTRL|=1;// 启动定时器,每个时钟周期自减1
u32 temp;
do{
temp = SysTick->CTRL;
}while(((temp&0x01)==0x01)&&(((temp>>16)&0x01) != 0x01));
SysTick->CTRL&=~1;// 关闭定时器
SysTick->VAL =0;// 清空计数器
}