STM32库函数编程、Keli/MDK、stm32f103zet6
catalogue
0. Cortex-M3地址空间 1. 基于标准外设库的软件开发 2. 基于固件库实现串口输出(发送)程序 3. 红外接收实验 4. 深入分析流水灯例程 5. GPIO再举例之按键实验 6. 串口通信(USART) 7. 库函数开发通用流程小结 8. DMA传输方式 9. STM32 ADC 10. SysTick(系统滴答定时器) 11. STM32定时器
0. Cortex-M3地址空间
0x1: MDK中三种linker之间的区别
1. 采用Target对话框中的RAM和ROM地址
采用此方式,需在Linker选项卡中勾选Use Memory Layout from Target Dialog选项(选中这一项实际上是默认在Target中对Flash和RAM的地址配置,编译链接时会产生一个默认的脚本文件),并且在Target中设置好RAM、ROM地址。MDK会根据Target选项中设定的RAM和ROM地址自动加载生成一个加载文件。最后链接器会根据此文件中的信息对目标文件进行链接,生成axf镜像文件
STM32是通过同一个连续的地址空间来寻址片上ROM和片外RAM
2. 直接通过Linker选项卡中的R/O Base和R/W Base来设定链接信息
接器最后可根据此处指定的地址信息进行链接,链接的文件应该是顺序存放了,最多RO和RW分开。此时需要注意的是应将Use Memory Layout from Target Diaglog前的勾去掉,且保证Scatter File一栏中未包含分散加载文件,并且要在Misc controls中设定镜像文件的入口点
3. 直接采用分散加载文件
Relevant Link:
http://blog.csdn.net/mybelief321/article/details/8947424 http://www.openedv.com/thread-17087-1-1.html
1. 基于标准外设库的软件开发
0x1: STM32标准外设库概述
STM32标准外设库之前的版本也称固件函数库或简称固件库(即操作片外固件的代码集合),是一个固件函数包,它由程序、数据结构和宏组成,包括了微控制器所有外设的性能特征。该函数库还包括每一个外设的驱动描述和应用实例,为开发者访问底层硬件提供了一个中间API,通过使用固件函数库,无需深入掌握底层硬件细节,开发者就可以轻松应用每一个外设。因此,使用固态函数库可以大大减少用户的程序编写时间,进而降低开发成本。每个外设驱动都由一组函数组成,这组函数覆盖了该外设所有功能。每个器件的开发都由一个通用API (application programming interface 应用编程界面)驱动,API对该驱动程序的结构,函数和参数名称都进行了标准化,顺便提一句,arduino之所以入门容易开发简单就是因为我们很多时候是要"面向固件库编程",很多复杂的外设操作都通过简单的API调用就完成了
ST公司2007年10月发布了V1.0版本的固件库,MDK ARM3.22之前的版本均支持该库。2008年6月发布了V2.0版的固件库,从2008年9月推出的MDK ARM3.23版本至今均使用V2.0版本的固件库。V3.0以后的版本相对之前的版本改动较大
0x2: 使用标准外设库开发的优势
使用标准外设库进行开发最大的优势就在于可以使开发者不用深入了解底层硬件细节就可以灵活规范的使用每一个外设。标准外设库覆盖了从GPIO到定时器,再到CAN、I2C、SPI、UART和ADC等等的所有标准外设。对应的C源代码只是用了最基本的C编程的知识,所有代码经过严格测试,易于理解和使用,并且配有完整的文档,非常方便进行二次开发和应用
0x3: STM32F10XXX标准外设库结构与文件描述
表 5‑4 STM32F10XXX V3.4标准外设库文件夹描述
STM32F10x_StdPeriph_Lib_V3.4.0 |
_htmresc |
本文件夹包含了所有的html页面资源 |
Libraries |
CMSIS微控制器软件接口标准(CMSIS:Cortex Microcontroller Software Interface Standard) |
是 Cortex-M 处理器系列的与供应商无关的软件抽象层。 使用CMSIS,可以为处理器和外设实现一致且简单的软件接口,从而简化软件的重用 |
STM32F10x_StdPeriph_Driver |
inc |
标准外设库驱动头文件 |
src |
标准外设库驱动源文件 |
|
Project |
Examples |
标准外设库驱动的完整例程 |
Template |
MDK-ARM |
KEIL RVMDK的项目模板示例 |
RIDE |
Raisonance RIDE的项目模板示例 |
|
EWARM |
IAR EWARM的项目模板示例 |
|
Utilities |
STM3210-EVAL |
本文件夹包含了用于STM3210B-EVAL和STM3210E-EVAL评估板的专用驱动 |
标准外设库的第一部分是CMSIS 和STM32F10x_StdPeriph_Driver,CMSIS 是独立于供应商的Cortex-M 处理器系列硬件抽象层,为芯片厂商和中间件供应商提供了简单的处理器软件接口,简化了软件复用工作,降低了Cortex-M 上操作系统的移植难度,并减少了新入门的微控制器开发者的学习曲线和新产品的上市时间。STM32F10x_StdPeriph_Driver则包括了分别对应包括了所有外设对应驱动函数,这些驱动函数均使用C语言编写,并提供了统一的易于调用的函数接口,供开发者使用。Project文件夹中则包括了ST官方的所有例程和基于不同编译器的项目模板,这些例程是学习和使用STM32的重要参考。Utilities包含了相关评估板的示例程序和驱动函数,供使用官方评估板的开发者使用,很多驱动函数同样可以作为学习的重要参考
0x4: STM32F10xxx标准外设库体系结构
文件名 |
功能描述 |
具体功能说明 |
core_cm3.h core_cm3.c |
Cortex-M3内核及其设备文件 |
访问Cortex-M3内核及其设备:NVIC,SysTick等 访问Cortex-M3的CPU寄存器和内核外设的函数 |
stm32f10x.h |
微控制器专用头文件 |
这个文件包含了STM32F10x全系列所有外设寄存器的定义(寄存器的基地址和布局)、位定义、中断向量表、存储空间的地址映射等 |
system_stm32f10x.h system_stm32f10x.c |
微控制器专用系统文件 |
函数SystemInit,用来初始化微控制器 函数Sysem_ExtMemCtl,用来配置外部存储器控制器。它位于文件startup_stm32f10x_xx.s /.c,在跳转到main前调用 SystemFrequncy,该值代表系统时钟频率 |
startup_stm32f10x_Xd.s |
编译器启动代码 |
微控制器专用的中断处理程序列表(与头文件一致) 弱定义(Weak)的中断处理程序默认函数(可以被用户代码覆盖) 该文件是与编译器相关的 |
stm32f10x_conf.h |
固件库配置文件 |
通过更改包含的外设头文件来选择固件库所使用的外设,在新建程序和进行功能变更之前应当首先修改对应的配置。 |
stm32f10x_it.h stm32f10x_it.c |
外设中断函数文件 |
用户可以相应的加入自己的中断程序的代码,对于指向同一个中断向量的多个不同中断请求,用户可以通过判断外设的中断标志位来确定准确的中断源,执行相应的中断服务函数。 |
stm32f10x_ppp.h stm32f10x_ppp.c |
外设驱动函数文件 |
包括了相关外设的初始化配置和部分功能应用函数,这部分是进行编程功能实现的重要组成部分。 |
Application.c |
用户文件 |
用户程序文件,通过标准外设库提供的接口进行相应的外设配置和功能设计。 |
0x5: 基于CMSIS标准的软件架构
对于ARM公司来说,一个ARM内核往往会授权给多个厂家,生产种类繁多的产品,如果没有一个通用的软件接口标准,那么当开发者在使用不同厂家的芯片时将极大的增加了软件开发成本,因此,ARM与Atmel、IAR、Keil、hami-nary Micro、Micrium、NXP、SEGGER和ST等诸多芯片和软件厂商合作,将所有Cortex芯片厂商产品的软件接口标准化,制定了CMSIS标准。此举意在降低软件开发成本,尤其针对新设备项目开发,或者将已有软件移植到其他芯片厂商提供的基于Cortex处理器的微控制器的情况。有了该标准,芯片厂商就能够将他们的资源专注于产品外设特性的差异化,并且消除对微控制器进行编程时需要维持的不同的、互相不兼容的标准的需求,从而达到降低开发成本的目的
基于CMSIS标准的软件架构(或者叫固件库架构)主要分为以下4层
1. 用户应用层 2. 操作系统及中间件接口层 3. CMSIS层: CMSIS层起着承上启下的作用 1) 一方面该层对硬件寄存器层进行统一实现,屏蔽了不同厂商对Cortex-M系列微处理器核内外设寄存器的不同定义 2) 另一方面又向上层的操作系统及中间件接口层和应用层提供接口,简化了应用程序开发难度,使开发人员能够在完全透明的情况下进行应用程序开发。也正是如此,CMSIS层的实现相对复杂 4. 硬件寄存器层
0x6: 使用方式
在实际开发过程中,根据应用程序的需要,可以采取2种方法使用标准外设库(StdPeriph_Lib)
1. 使用外设驱动: 这时应用程序开发基于外设驱动的API(应用编程接口)。用户只需要配置文件"stm32f10x_conf.h",并使用相应的文件例如"stm32f10x_ppp.h/.c"即可 2. 不使用外设驱动: 这时应用程序开发基于外设的寄存器结构和位定义文件(需要了解单片机的大量硬件、引脚细节)
标准外设库(StdPeriph_Lib)支持STM32F10xxx系列全部成员:大容量,中容量和小容量产品,实际开发中根据使用的STM32产品具体型号,用户可以通过文件"stm32f10x.h"中的预处理define或者通过开发环境中的全局设置来配置标准外设库(StdPeriph_Lib),一个define对应一个产品系列
STM32F10x_LD:STM32小容量产品
STM32F10x_MD:STM32中容量产品
STM32F10x_HD:STM32大容量产品
在库文件中这些define的具体作用范围是
1. 文件"stm3210f.h"中的中断IRQ定义 2. 启动文件中的向量表,小容量,中容量,大容量产品各有一个启动文件 3. 外设存储器映像和寄存器物理地址 4. 产品设置: 外部晶振(HSE)的值等 5. 系统配置函数
因此通过宏定义这种方式,可以使标准外设库适用于不同系列的产品,同时也方便与不同产品之间的软件移植,极大的方便了软件的开发
0x7: 命名规范
标准外设库中的主要外设均采用了缩写的形式,通过这些缩写可以很容易的辨认对应的外设。
缩写 |
外设/单元 |
ADC |
模数转换器 |
BKP |
备份寄存器 |
CAN |
控制器局域网模块 |
CEC |
|
CRC |
CRC计算单元 |
DAC |
数模转换器 |
DBGMCU |
调试支持 |
DMA |
直接内存存取控制器 |
EXTI |
外部中断事件控制器 |
FLASH |
闪存存储器 |
FSMC |
灵活的静态存储器控制器 |
GPIO |
通用输入输出 |
I2C |
I2C接口 |
IWDG |
独立看门狗 |
PWR |
电源/功耗控制 |
RCC |
复位与时钟控制器 |
RTC |
实时时钟 |
SDIO |
SDIO接口 |
SPI |
串行外设接口 |
TIM |
定时器 |
USART |
通用同步/异步收发器 |
WWDG |
窗口看门狗 |
标准外设库遵从以下命名规则 PPP表示任一外设缩写,例如:ADC。源程序文件和头文件命名都以“stm32f10x_”作为开头,例如:stm32f10x_conf.h
外设函数的命名以该外设的缩写加下划线为开头。每个单词的第一个字母都由英文字母大写书写,例如:SPI_SendData。在函数名中,只允许存在一个下划线,用以分隔外设缩写和函数名的其它部分。对于函数命名
Relevant Link:
http://baike.baidu.com/link?url=X3kL65ER2yug2m-_XhgXTggAd7uH7VCnyhdaJ2ddxYt-Nj8oqB46tWDhNngqyrPnuAzzs8wJe56NzIJi-6zWWa http://www.cnblogs.com/emouse/archive/2011/11/29/2268441.html
2. 基于固件库实现串口输出(发送)程序
0x1: 关键库函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) 发送函数,它告诉我们很重要的一点,那就是串口是以"位"来传输的 FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG) 用它来得知串口的状态
0x2: 参考printf函数编写串口发送函数(自定义一个完全脱库标准C库的新函数)
#include <stdio.h> #include <stdarg.h> /************************************************* * Function Name : USART1_printf * Description : * Input : * Output : NONE * Return : NONE *************************************************/ void USART1_printf (char *fmt, ...) { char buffer[CMD_BUFFER_LEN+1]; u8 i = 0; va_list arg_ptr; va_start(arg_ptr, fmt); vsnprintf(buffer, CMD_BUFFER_LEN+1, fmt, arg_ptr); while ((i < CMD_BUFFER_LEN) && buffer[i]) { USART_SendData(USART1, (u8) buffer[i++]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); } va_end(arg_ptr); } /************************************************* * Function Name : USART1_SendData * Description : 串口1发送 * Input : char *Buffer * Output : NONE * Return : NONE *************************************************/ void USART1_SendData(char *Buffer) { u8 Counter = 0; while( (Counter == 0) || (Buffer[Counter] != 0) ) //条件... { USART_SendData(USART1, Buffer[Counter++]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } }
0x3: Code
/*---------------------------------------------------------------------------- * Name: Hello.c * Purpose: Hello World Example * Note(s): *---------------------------------------------------------------------------- * This file is part of the uVision/ARM development tools. * This software may only be used under the terms of a valid, current, * end user licence from KEIL for a compatible version of KEIL software * development tools. Nothing else gives you the right to use this software. * * This software is supplied "AS IS" without warranties of any kind. * * Copyright (c) 2012 Keil - An ARM Company. All rights reserved. *----------------------------------------------------------------------------*/ #include <stdio.h> /* prototype declarations for I/O functions */ #include <stm32f10x.h> /* STM32F10x definitions */ #include <stm32f10x_usart.h> #include <stdarg.h> #include <string.h> #define CMD_BUFFER_LEN 64u static void delayms(int cnt){ int i; while(cnt--) for (i=0; i<7333; i++); } void USART2_printf (char *fmt, ...) { char buffer[CMD_BUFFER_LEN+1] = {0}; u8 i = 0; int len = 0; va_list arg_ptr; memset(buffer, '\x00', CMD_BUFFER_LEN+1); len = strlen(fmt) + 1; va_start(arg_ptr, fmt); vsnprintf(buffer, len, fmt, arg_ptr); while ((i < len) && buffer[i] != '\x00') { USART_SendData(USART1, (u8) buffer[i++]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) ; } va_end(arg_ptr); } extern void SER_Init(void); /* see Serial.c */ /*---------------------------------------------------------------------------- main program *----------------------------------------------------------------------------*/ int main (void) { /* execution starts here */ SER_Init (); /* initialize the serial interface */ USART2_printf ("Hello World\n"); /* the 'printf' function call */ while (1) { /* An embedded program does not stop and */ USART2_printf ("Hello World\n"); /* ... */ /* never returns. We use an endless loop. */ delayms(3000); } /* Replace the dots (...) with your own code.*/ }
使用64字节的缓冲数组保存需要发送的数据,然后通过while循环逐byte发送往Terminal终端
为了显示方便,还可以加上sleep函数
static void delayms(int cnt){ int i; while(cnt--) for (i=0; i<7333; i++); }
需要明白的,我们从指令层面看while循环,由于指令周期、机器周期都是可时间量化的,因此可以用while来实现spin CPU空转,即sleep
Relevant Link:
http://www.cnblogs.com/mocet/p/stm32f10x_usart_InputOutout_Function_Design_1.html
3. 红外接收实验
二进制脉冲码的形式有多种,其中最为常见的是PWM码(脉冲宽度调制码)和PPM码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制),如果要开发红外接收设备,一定要知道红外发射器(例如遥控器)的编码方式和载波频率,我们才可以选取一体化红外接收头和制定解码方案
/********************************************************************************** * ***********************************************************************************/ #include "stm32f10x.h" #include "stm32f10x_exti.h" //#include "stm8s_beep.h" #include "stm32f10x_systick.h" #define LED1_0 GPIOD->BRR = 0x00000100 #define LED2_0 GPIOD->BRR = 0x00000200 #define LED3_0 GPIOD->BRR = 0x00000400 #define LED4_0 GPIOD->BRR = 0x00000800 #define LED1_1 GPIOD->BSRR = 0x00000100 #define LED2_1 GPIOD->BSRR = 0x00000200 #define LED3_1 GPIOD->BSRR = 0x00000400 #define LED4_1 GPIOD->BSRR = 0x00000800 #define IR_Hongwai_0 GPIOE->BRR = 0x00000004 //??????? #define IR_Hongwai_1 GPIOE->BSRR = 0x00000004 //??????? #define IR_Hongwai_x GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2) //???????? unsigned int TimeByte; volatile unsigned int IR_Tireafg[4] = {0, 0, 0, 0}; //unsigned int IR_xidwrit[8] = {0, 0, 0, 0, 0, 0 ,0, 0}; /* * GPIO??????? */ void GPIO_InitStructReadtempCmd(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2; //??GPIO?? GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //????????? GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //??????50MHZ GPIO_Init(GPIOE, &GPIO_InitStruct); //??????? GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //?????????? GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &GPIO_InitStruct); } /* * ????????? */ void RCC_APB2PeriphReadtempyCmd(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //??GPIOB???? RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); //??GPIOE???? RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); //??GPIOD???? RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE); //??AFIO???????? } /* * ?????????Count1 * 10us */ unsigned int IR_HongwaiRead_LSB_Cmd() { unsigned int Count1 = 0; //?????? IR_Hongwai_0; //??????? do //????? { Count1++; //?????1 Delay_10us(1); //??10us } while(IR_Hongwai_x == 0); //??????????????????????? return(Count1); //???????? } /* * ?????????Count2 * 10us */ unsigned int IR_HongwaiRead_MSB_Cmd() { unsigned int Count2 = 0; //?????? IR_Hongwai_1; //??????? do //????? { Count2++; //?????1 Delay_10us(1); //??10us } while(IR_Hongwai_x == 1); //??????????????????????? return(Count2); } /* * ???? */ int main(void) { SystemInit(); //?????????72M?? SYSTICK_InitStructReadTCmd(); //???SysTick?????? RCC_APB2PeriphReadtempyCmd(); //???????????? GPIO_InitStructReadtempCmd(); //???GPIO??????? EXTI_InitStructReadtempCmd(); //???EXTI??????? NVIC_InitStructReadtempCmd(); //???NVIC??????? while(1) { } } /* * EXTI????????? */ void EXTI2_IRQHandler(void) { unsigned char i = 0; unsigned int Countline2 = 0; IR_Hongwai_1; Countline2 = IR_HongwaiRead_LSB_Cmd(); //?????? 9ms?? if((Countline2 < 286) || (Countline2 > 305)) //??8694us ??9272us ???????? { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); //?????? 4.5ms?? if((Countline2 < 138) || (Countline2 > 155)) //??4195us ??4712us ???????? { return; } TimeByte = 0; for(i = 1; i < 14; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); //?????0.56 ?? if((Countline2 < 14) || (Countline2 > 22)) //??425us ??851us ???????? { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); //?????0.56?? if((Countline2 < 14) || (Countline2 > 65)) //??425us ??1793us ???????? { return; } if( Countline2 > 50) //???????1300us?1???0 { TimeByte |= 0x80; //?1 } } IR_Tireafg[0] = TimeByte; TimeByte = 0; for(i = 14; i < 27; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); if((Countline2 < 14) || (Countline2 > 22)) { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); if((Countline2 < 14) || (Countline2 > 65)) { return; } if( Countline2 > 50) { TimeByte |= 0x80; } } IR_Tireafg[1] = TimeByte; TimeByte = 0; for(i = 27; i < 35; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); if((Countline2 < 14) || (Countline2 > 22)) { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); if((Countline2 < 14) && (Countline2 > 65)) { return; } if( Countline2 > 50) { TimeByte |= 0x80; } } IR_Tireafg[2] = TimeByte; TimeByte = 0; for(i = 35; i < 43; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); if((Countline2 < 14) || (Countline2 > 22)) { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); if((Countline2 < 14) || (Countline2 > 65)) { return; } if( Countline2 > 52) { TimeByte |= 0x80; } } IR_Tireafg[3] = TimeByte; //************************?????????***************************************// /* if(IR_Tireafg[0]!= 0x11A) { return; } */ //************************?????????***************************************// /* do { if(IR_Tireafg[2] == ~IR_Tireafg[3]) { flag = 0; } } while(flag == 1); */ /* if(IR_Tireafg[2] != ~IR_Tireafg[3]) { flag = 0; } */ //************************??????LED??**************************************// switch (IR_Tireafg[2]) { case 0x00: //?? 0 LED1_1; LED2_0; LED3_0; LED4_0; break; case 0x01: //?? 1 LED1_0; LED2_1; LED3_0; LED4_0; break; case 0x02: //?? 2 LED1_0; LED2_0; LED3_1; LED4_0; break; case 0x03: //?? 3 LED1_0; LED2_0; LED3_0; LED4_1; break; case 0x04: //?? 4 LED1_0; LED2_0; LED3_1; LED4_0; break; case 0x05: //?? 5 LED1_0; LED2_1; LED3_0; LED4_0; break; case 0x06: //?? 6 LED1_1; LED2_0; LED3_0; LED4_0; break; case 0x07: //?? 7 LED1_1; LED2_0; LED3_1; LED4_0; break; case 0x08: //?? 8 LED1_1; LED2_0; LED3_0; LED4_1; break; case 0x09: //?? 9 LED1_0; LED2_1; LED3_0; LED4_1; break; case 0x15: //??? LED1_0; LED2_1; LED3_1; LED4_0; break; case 0x1C: //??? LED1_0; LED2_0; LED3_0; LED4_0; break; case 0x14: //OSD? LED1_1; LED2_1; LED3_0; LED4_0; break; case 0x0E: //RECALL? LED1_0; LED2_0; LED3_1; LED4_1; break; case 0x19: //SLEEP? LED1_1; LED2_1; LED3_1; LED4_0; break; case 0x0A: //A/C? LED1_0; LED2_1; LED3_1; LED4_1; break; case 0x0F: //TV/AV? LED1_1; LED2_1; LED3_1; LED4_1; break; case 0x13: //PP? LED1_1; LED2_0; LED3_1; LED4_0; break; case 0x0C: //GAME? LED1_0; LED2_1; LED3_1; LED4_1; break; case 0x1E: //V-? LED1_1; LED2_1; LED3_1; LED4_0; break; case 0x1F: //V+? LED1_0; LED2_0; LED3_1; LED4_0; break; case 0x1B: //P+? LED1_0; LED2_0; LED3_0; LED4_1; break; case 0x1A: //P-? LED1_1; LED2_0; LED3_0; LED4_0; break; case 0x10: //MENU? LED1_0; LED2_1; LED3_0; LED4_0; break; default : break; } // Beep_lookCmd(); //?????? EXTI_ClearITPendingBit(EXTI_Line2); //??EXTI2??????? }
Relevant Link:
http://www.iqiyi.com/w_19rrdz9g91.html#curid=5449021609_7b2174ee370808596288e2209eef0b75 http://bbs.21ic.com/icview-243059-1-1.html http://wenku.baidu.com/view/2d0b4636a32d7375a417802e.html http://blog.csdn.net/houqi02/article/details/51585551 http://bbs.21ic.com/icview-649262-1-1.html http://wenku.baidu.com/link?url=F4r-R2rp-cF8lw7zSxuLYVWRoLdXeCQYt2Kf4hO9Kb7JMe1n7eOxkY-5t4Ar3990U5EmoungBQCyGFJitjsFqSSId5joGVgND6gQntg0ipO
4. 深入分析流水灯例程
想要控制LED灯,自然是要通过控制STM32芯片的IO引脚电平的高低来实现(这种和arduino是一样的)(因为LED灯的本质就是高低电位是否导通),在STM32芯片上,IO引脚可以被软件设置成各种不同的功能,如输入输出,所以又被称为GPIO(general purpose IO),按照这种方式去思考,我们要做的事如下
1. GPIO端口引脚多:需要选定需要控制的的特定引脚 2. GPIO功能较丰富:配置需要的特定功能(配置寄存器) 3. 控制LED的亮和灭:设置GPIO输出电压的高低
STM32的功能实际上也是通过配置寄存器来实现的,STM32编程本质上就是面向寄存器的编程
对于GPIO端口,每个端口有16个引脚,每个引脚的模式由寄存器的4个位控制,每4位又分为两位控制引脚配置,另外两位控制引脚的模式以及最高速度
0x1: STM32的地址映射(stm32f10x.h)
我们来回顾下在51单片机上点亮LED是如何实现的(arduino也类似)
#include <reg52.h> int main(void){ P0 = 0; while(1); }
这背后的原理是就是地址映射,所谓地址映射,就是将芯片上的存储器甚至IO等资源与地址建立一一对应关系(即所有东西皆地址),这样如果某个地址对应着某个存储器,我们就可以运用C语言的指针来寻址并修改这个地址上的内容,从而实现修改该存储器的内容
Cortex-M3的地址映射也是类似的,Cortex-M3有32根地址线,所以它的寻址空间大小为4GB,ARM公司设计时,预先把这4GB的寻址空间大致地分配好了
stm32f10x.h这个文件的重要的内容就是把STM32的所有寄存器进行地址映射,它就像一个大表格,我们在使用的时候就是通过宏定义进行类似查表的操作
0x2: STM32的时钟系统(同步/驱动bit信号的传输)
STM32芯片为了实现低功耗,设计了一个功能完善但是却非常复杂的时钟系统,普通的MCU一般只要配置好GPIO的寄存器就可以使用了,但是STM32还有一个步骤,就是开启外设时钟
1. 时钟树 & 时钟源
上图说明了STM32的时钟走向,从左边开始从时钟源一步步分配到外设时钟,从时钟频率来说,又分为高速时钟和低速时钟
1. 高速时钟: 是提供给芯片主体的主时钟 2. 低速时钟: 提供给芯片中的RTC(实时时钟)及独立看门狗使用
从芯片角度来说,时钟源分为内部时钟与外部时钟源
1. 内部时钟: 是由芯片内部RC振荡器产生的,起振较快,所以时钟在芯片刚上电的时候,默认使用内部高速时钟 2. 外部时钟: 是由外部的晶振输入的,在精度和稳定性在都有较大优势,所以上电之后再通过软件配置,转而采用外部时钟信号
所以,STM32有以下4个时钟源
1. 高速外部时钟(HSE):以外部晶振作为时钟源,晶振频率可取范围为4 ~ 16MHZ,我们一般采用8MHZ的晶振 2. 高速内部时钟(HSI):由内部RC振荡器产生,频率为8MHZ,但不稳定 3. 低速外部时钟(LSE):以外部晶振作为时钟源,主要提供给实时时钟模块,所以一般采用32KHZ 4. 低速内部时钟(LSI):由内部RC振荡器产生,也主要提供给实时时钟模块,频率大约为40kHZ
2. HCLK、FCLK、PCLK1、PCLK2
从时钟树的分析,看到经过一系列的倍频、分频后得到了几个与我么开发密切相关的时钟
1. SYSCLK:系统时钟,是STM32大部分器件的时钟来源,主要由AHB预分频器分配到各个部件 2. HCLK:由AHB预分频器直接输出得到,它是高速总线AHB的时钟信号,提供给存储器、DMA、Cortex内核,是Cortex内核运行的时钟,CPU主频就是这个信号,它的大小与STM32运算速度、数据存取速度密切相关 3. FCLK:同样由AHB预分频器输出得到,是内核的"自由运行时钟"。"自由"表现在它不来自时钟"HCLK",因此在HCLK时钟停止时FCLK也能继续运行。它的存在可以保证,在处理器处于休眠时也能够采样到中断和跟踪休眠事件,它与HCLK互相同步 4. PCLK1:外设时钟,由APB1预分配器输出得到,最大频率为36MHZ,提供给挂载在APB1总线上的外设 5. PCLK2:外设时钟,由APB2预分频器输出得到,最大频率可达72MHZ,提供给挂载在APB2总线上的外设
STM32的时钟系统设计地如此复杂是出于以下几个原因考虑
1. 因为有分频、倍频以及一系列外设时钟的开关 2. 需要倍频是考虑到电磁兼容性,如果外部提供一个72MHZ的晶振,太高的振荡频率可能会给制作电路带来一定的难度 3. 分频是因为STM32既有高速外设也有低速外设,各种外设的工作频率不尽相同,如同PC上的南北桥,把高速和低速的设备分开来管理 4. 每个外设都配备了外设时钟的开关,每当我们不使用某个外设时,可以把这个外设时钟关闭,从而降低STM32的整体功耗,所以当我们使用外设时,一定要记得开启外设的时钟
5. GPIO再举例之按键实验
0x1: GPIO的8种工作模式
在初始化GPIO的时候,根据我们的使用要求,必须把GPIO设置为相应的模式
0x2: 循环扫描方式检测按键是否按下
我们可以采取中断响应、或者循环扫描方式去检测对应的GPIO输入端口(引脚)是否有指定的bit数据来实现按键的检测
#ifndef __KEY_H #define __KEY_H #include "stm32f10x.h" #define S1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) #define S2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) #define S3 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) #define S4 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) void KEY_Init(void); #endif #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" int main(void) { uint8_t j; //¶¨Òå±äÁ¿ LED_Init();//LED³õʼ»¯ KEY_Init();//°´¼ü³õʼ»¯ SysTick_Init();//ÑÓʱ³õʼ»¯ while (1) { if(!S1) { Delay_ms(10); if(!S1) { while(!S1);//µÈ´ý°´¼üÊÍ·Å LED2_REV; } } //////////////////////////////////////////// if(!S2) { Delay_ms(10); if(!S2) { while(!S2); LED3_REV; } } ////////////////////////////////////////// if(!S3) { Delay_ms(10); if(!S3) { while(!S3); LED2_REV; LED3_REV; } } /////////////////////////////////////////// if(S4) { Delay_ms(10); if(S4) { while(S4); for(j=0;j<10;j++) { LED2_REV; LED3_REV; Delay_ms(100); } } } } }
0x3: 外部中断触发方式检测按键是否按下
还可以使用EXIT(External Interrupt 外部中断)的方式,即通过GPIO检测输入脉冲,引起中断事件,打断原来的代码执行流程,进入到中断服务函数中进行处理,处理完后再返回到中断之前的代码中执行。我们知道,STM32的所有GPIO口可以用作外部中断源的输入端
1. STM32的中断和异常
Cortex内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception)和中断(interrupt),并把它们用一个表(中断向量表)管理起来
1. 编号为-3 ~ 6的称为内核异常: 不能被设置优先级,如复位(Reset)、不可屏蔽中断(NMI)、硬错误(HardFault) 2. 7以上的则称为外部中断: 这些中断的优先级是可以自行设置的
需要注意的是,不同型号的STM32芯片,中断向量表稍有区别,最好的方法的是从启动文件startup_stm32f10x_hd.s中查找,在启动文件中,已经有相应芯片可用的全部中断向量,而且在编写中断服务函数时,需要从启动文件中定义的中断向量表查找中断服务函数名
2. NVIC中断控制器(Nested Vectored Interrupt Controller)
NVIC是属于Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,但是SYSTICK不是由NVIC来控制的。嵌套向量中断控制器(NVIC)和处理器核的接口紧密相连,可以实现低延迟的中断处理和有效地处理晚到的中断。嵌套向量中断控制器管理着包括核异常等中断
3. NVIC结构体成员
ST库已经把NVIC封装了库函数的形式
uint8_t NVIC_IRQChannel
FunctionalState NVIC_IRQChannelCmd
uint8_t NVIC_IRQChannelPreemptionPriority
uint8_t NVIC_IRQChannelSubPriority
STM32的中断向量具有两个属性
1. 抢占属性: 抢占是指打断其他中断的属性,即因为有这个属性才会出现嵌套中断 2. 响应属性: 响应属性则是在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果连个中断同时到达,则优先处理响应优先级较高的中断
STM32单片机上的所有IO端口都可以配置为EXIT中断模式,用来捕捉外部信号,可以配置为下降沿中断、上升沿中断和上升下降沿中断这3种模式,通过这种方式,我们可以很方便地通过中断方式来接收外设接收到的信号数据
4. EXIT外部中断
STM32的所有GPIO都引入到EXIT外部中断线上,使得所有的GPIO都能作为外部中断的输入源
1. PA0 ~ PG0连接到EXIT0 2. PA1 ~ PG1连接到EXIT1 3. PA2 ~ PG0连接到EXIT2 ... 15. PA15 ~ PG15连接到EXIT15
PAx ~ PGx端口的中断事件都连接到EXITx,即同一时刻EXITx只能响应一个端口的事件触发,不能同时时间响应所有GPIO端口的事件,但可以分时复用。它可以配置为上升沿触发、下降沿触发、双边触发。EXIT最普通的应用就是接上一个按键,并设置为下降沿触发,用中断来检测按键
在编程时要注意,不同开发板的引脚定义都是不同的,不同开发板的代码直接移植过程会存在代码不可用的现象,需要根据实际情况做适当修改
6. 串口通信(USART)
STM32的串口非常强大,它不仅支持最基本的通用串口同步、异步通信,还具有LIN总线功能(局域互联网)、IRDA功能(红外通信)、SmartCard功能
0x1: 串口工作过程
从下至上,我们看到串口外设主要由三个部分组成,分别是波特率控制、收发控制、数据存储传输
1. 波特率控制
波特率,即每秒传输的二进制位数,用b/s(bps)表示,通过对时钟的控制可以改变波特率。在配置波特率时,我们向波特率寄存器USART_BRR写入参数,修改了串口时钟的分频值USARTDIV(USARTDIV = DIV_Mantissa + (DIV_Fraction / 16))。USARTDIV是对串口外设的时钟源进行分频的
1. USART1: 挂载在APB2总线上,时钟源为fpclk2 2. USART2: 挂载在APB1上,时钟源为fpclk1
串口的时钟源经过USARTDIV分频后分别输出为发送器时钟、接收器时钟,控制发送和接收
2. 收发控制
围绕着发送器和接收器控制部分,有很多寄存器
1. CR1 2. CR2 3. CR3 4. SR
3. 数据存储转移
收发控制器根据我们的寄存器配置,对数据存储转移部分的"移位寄存器"进行控制,串口收发就是通过移位寄存器实现的
1. 当我们需要发送数据时,内核或DMA外设把数据从内存(变量)写入到数据寄存器TDR后,发送控制器将适时地自动把数据从TDR转移到移位寄存器时,会产生发送寄存器TDR已空事件TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件TC,这些事件可以在状态寄存器中查询到 2. 接收数据是一个逆过程,数据从串口线Rx一位一位地输入到接收移位寄存器,然后自动地转移到接收数据寄存器RDR,最后用内核指令或DMA读取到内存(变量)中
0x2: 串口输出函数重定向
1. printf()函数重定向
默认情况下,STM32固件库的printf是输出到标准输出中,我们如果希望其输出到串口终端中,需要把printf()重定向到串口中,即需要自己重写C的库函数,当linker检查到用户编写了与C库函数相同名字的函数时,优先采用用户编写的函数。为了实现重定向printf()函数,我们需要重写fputc()这个C标准库函数,因为printf()在C标准库函数中实质是一个宏,最终是调用了fputc()这个函数
#ifdef __GNUC__ /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__ */ PUTCHAR_PROTOTYPE { /* Place your implementation of fputc here */ /* e.g. write a character to the USART */ USART_SendData(USART1, (uint8_t) ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} return ch; }
当使用printf()时,它先调用这个fputc()函数,然后使用ST库的串口发送函数USART_SendData(),把数据转移到发送数据寄存器TDR,触发我们的串口向PC发送一个相应的数据,调用完USART_SendData()后,要使用while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}不断检查串口发送是否完成的标志位TC,在这段while循环检测的延时中,串口外设已经由发送控制器以及根据我们的配置把数据从移位寄存器一位一位地通过串口线Tx发送出去了
2. USART1_printf()函数
7. 库函数开发通用流程小结
0x1: 初始化
1. GPIO_InitTypeDef GPIO_InitStructure: 用来配置GPIO 2. NVIC_InitTypeDef NVIC_InitStructure: 用来配置NVIC 3. EXIT_InitTypeDef EXIT_InitStructure: 用来配置EXIT 4. USART_InitTypeDef USART_InitStructure: 用来配置USART
使用ST库对外设进行初始化,一般有以下步骤
1. 定义一个xxx_InitTypeDef类型的初始化结构体 2. 根据使用需要,向这些初始化结构体的成员赋值特定的控制参数 3. 填充好结构体后,把这个结构体作为输入参数调用相应的外设库函数xxx_Init(),从而实现向寄存器写入控制参数,并配置好外设
对于需要时钟同步的外设,还需要开启(使能)对应总线上的时钟
RCC_AHBPeriphClockCmd(RCC_AHBPerip_DMA1, ENABLE);
对于需要中断回调的外设,还可以配置中断callback函数
DMA_Init(DMA1_Channal4, &DMA_InitStructure); DMA_Cmd(DMA1_Channal4, ENABLE); //配置DMA发送完成后产生中断 DMA_ITConfig(DMA1_Channal4, DMA_IT_TC, ENABLE);
0x2: 数据输入输出
对外设的使用,一般涉及其输入和输出数据,ST官方库中有一类函数专门为此而编写的
GPIO的输入输出函数
GPIO_ReadOutputDataBit()
GPIO_ReadInputData
GPIO_SetBits
USART的收发数据函数
USART_ReceiveData
USART_SendData
这些函数控制相应外设数据寄存器DR的内容,达到控制输入输出的目的,使用的方法也是类似的
1. 通过输入参数,向函数指定要使用的是什么外设,如用(GPIOA、GPIO_Pin_5)选定PA5进行控制,用USART1来指定使用串口1外设 2. 若向外输出数据,则调用Output或Send函数,把将要输出的数据变量作为函数的输入参数 3. 若为接收外部数据,则调用Read或Receive函数,读取函数的返回值来得到外部输入数据
0x3: 状态位、标志位
当我们需要知道外设的工作状态时,就涉及一系列标志检查的ST官方库函数了
1. 事件
当外设完成了某些工作或出现某些状态的时候,会触发一些事件,这些事件会在状态寄存器SR中,以不同的寄存器位来记录,这些寄存器位称为相应的事件标志位
2. 标志位的检查与清除
假如我们把串口的发送完成事件、接收寄存器非空事件(串口接收到数据)都配置为可触发中断,因为它们触发的都是串口中断,所以中断时都是进入到同一个串口中断服务函数中处理的,那么我们这个串口服务函数中,就要区分事件源类型的,例如USART_GetFlagStatus、EXIT_GetFlagStatus,检查的值来自外设的xxx_SR寄存器
3. 外设函数的分类
8. DMA传输方式
DMA(Direct Memory Access 直接存储器存取)是一种可以大大减轻CPU工作量的数据存取方式,因而被广泛使用,STM32的DMA则是以类似外设的形式添加到Cortex内核之外的。在传统硬件系统中,主要由CPU(内核)、外设、内存(SRAM)、总线等结构组成,数据经常要在内存与外设之间转移,或从外设A转移到外设B。在转移数据的过程中会占用CPU十分宝贵的资源,DMA正是为CPU分担了数据转移的工作,因为DMA的存在CPU能够被解放出来,它可以在DMA转移数据的过程中同时进行数据运算、响应中断,大大提高效率
0x1: DMA工作分析
从上图可以看到,STM32内核、存储器、外设、DMA的连接最终都通过各种各样的线连接到总线矩阵之中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设都能够和谐地使用总线来传输数据
1. 在不使用DMA的情况下,内核通过DCode经过总线矩阵协调,使用AHB把外设ADC采集的数据读取到内核,然后内核DCode再通过总线矩阵协调,把数据存放到内存SRAM中 2. DMA正好可以取代这样的工作,由DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC的数据经由DMA通道存放到内存SRAM,在这个数据传输的过程中,不需要内核的全称参与,所以内核可以同时进行数据运算。而且,DMA方式是点到点的数据转移
要使用DMA,需要确定一系列的控制参数,如外设数据的地址、内存地址、传输方向等,在开启DMA传输前还要先发出DMA请求
0x2: DMA实例之串口通信
我们知道,硬件外设本来就是一个异步工作方式,外设工作的时候,除了转移数据,实质上是不需要内核干预的,而数据转移的工作交给了DMA,所以在串口发送数据的时候,内核同时还可以进行其他操作,如点亮LED灯
9. STM32 ADC
ADC(Analog to Digital Converter 模/数转换器),在模拟信号需要以数字形式处理、存储或传输时,模数转换器几乎必不可少。配套STM32开发板用的是STM32F103VET6,属于增强型CPU,它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模拟执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中
0x1: STM32的ADC主要技术指标
1. 分辨率: 12位分辨率,不能直接测量负电压,所以没有符号位,即最小量化单位LSB = Vref/ 212 2. 转换时间: 转换时间是可编程的,采样一次至少要用14个ADC时钟周期,而ADC的时钟频率最高为14MHz,也就是说,它的采样时间最短为1us,足以胜任中、低频数字示波器的采样工作 3. ADC类型: STM32的ADC是逐次比较型ADC 4. 参考电压范围: 2.4V <= Vref+ <= 3.6V
0x2: ADC工作过程分析
所有的器件都是围绕中间的模拟至数字转换器部分(ADC部件)展开的
1. 它的左端为Vref+、Vref-参考电压 2. ADCx_IN0 ~ ADCx_IN15为ADC的输入信号通道,即某些GPIO引脚,输入信号经过这些通道被送到ADC部件,ADC部件需要受到触发信号才开始进行转换,如EXIT外部触发、定时器触发、也可以使用软件触发 3. ADC部件接收到触发信号后,在ADCCLK时钟的驱动下对输入通道的信号进行采样,并进行模数转换,其中ADCCLK是来自ADC预分频器的 4. ADC部件转换后的数值被保存到一个16位的规则通道数据存储器(或注入通道数据寄存器)之中,我们可以通过CPU指令或DMA把它读取到内存(变量) 5. 模数转换之后,可以触发DMA请求、或者触发ADC的转换结束回调事件 6. 如果配置了模拟看门狗,并且采集得的电压大于阈值,会触发看门狗中断
0x3: ADC数据采集实例(DMA方式)
使用ADC时常常需要不间断采集大量的数据,在一般的器件中会使用中断进行处理,但使用中断的效率还是不够高,在STM32中,使用ADC时往往采用DMA传输方式,由DMA把ADC外设转换的数据直接传输到SRAM中(跳过CPU的中转),再进行处理,甚至直接把ADC的数据转移到串口发送给上位机
10. SysTick(系统滴答定时器)
0x1: SysTick(操作系统的心跳)
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号15),滴答中断对操作系统尤其重要,例如
1. 操作系统可以为多个任务分配不同数目的时间片,确保没有一个任务能霸占系统 2. 或者将每个定时器周期的某个时间范围赐予特定的任务
操作系统提供的各种定时功能都与这个滴答定时器有关,因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统"心跳"的节律。Cortex-M3在内核部分包含了一个简单的定时器: SysTick。该定时器的时钟源可以是内部时钟(FCLK、CM3上的自由运行时钟),或者是外部时钟(CM3处理器上的STCLK信号)
SysTick定时器能产生中断,CM3为它专门预留了一个异常类型,并且在向量表中有专门的中断处理函数void SysTick_Handler(void),它使操作系统和其他系统软件在CM3器件间的移植变得简单多了,因为在所有CM3产品间,SysTick的处理方式都是相同的,SysTick定时器除了能服务于操作系统外,还能用于其他目的,如作为一个闹钟、用作测量时间等
0x2: SysTick工作分析
SysTick是一个24位的定时器,即一次最多可以计数2^24个时钟脉冲,这个脉冲计数值被保存到当前计数值寄存器STK_VAL(SysTick current register)中,只能向下计数,每接收到一个时钟脉冲STK_VAL的值就向下减1,直至0,当STK_VAL的值被减至0时,由硬件自动把重载寄存器STK_LOAD(SysTick reload value register)中保存的数据加载到STK_VAL,重新向下计数。
当STK_VAL的值被计数至0时,触发异常,就可以在中断服务函数中处理定时事件了。要使SysTick进行以上工作必须要进行SysTick配置,它的控制很简单,只有三个控制位和一个标志位,都位于寄存器STK_CTRL(SysTick control and status register)中
1. Bit0: ENABLE: 为SysTick的使能位,此位为1的时候使能SysTick定时器,此位为0的时候关闭SysTick定时器 2. Bit1: TICKINT: 为异常触发使能位,此位为1的时候并且STK_VAL计数至0时会触发SysTick异常,此位被配置为0的时候不触发异常 3. Bit2: CLKSOUCE: 为SysTick的时钟选择位,此位为1时SysTick的时钟为AHB时钟,此位为0时SysTick始终为AHB/8(AHB的8分频) 4. Bit16: COUNTFLAG: 为计数为0标志位,若STK_VAL计数至0,此标志位会被置1
0x3: 使用SysTick精确延时实验分析
比起使用while循环一定次数来大概估测延时时间,当我们需要精确延时时,就可以利用SysTick实现,理论上它的最小计时单位为AHB的时钟周期,即1/72000000秒,72分之一的微秒,足以满足大部分应用的需求
11. STM32定时器
区别于SysTick一般只用于系统时钟的计时,STM32的定时器外设功能更加强大,STM32一共有8个16位的定位是
1. TIM6/TIM7是基本定时器 2. TIM2/TIM3/TIM4/TIM5是通用定时器 3. TIM1/TIM8是高级定时器
这些定时器使STM32具有定时、信号的频率测量、信号的PWM测量、PWM输出、三相6步电机控制及编码接口等功能,都是专门为工控领域定做的
0x1: 基本定时器
基本定时器TIM6/TIM7只具备最基本的定时功能,就是累加的时钟脉冲数超过预定值时,能触发中断或触发DMA请求。由于在芯片内部与DAC外设相连,可通过触发输出驱动DAC,也可以作为其他通用定时器的时钟基准
0x2:通用定时器
相比之下,通用定时器TIM2 ~ TIM5就比基本定时器复杂的多了,除了基本的定时,它主要用在测量输入脉冲的频率、脉冲宽与输出PWM脉冲的场合,还具有编码器的接口
0x3: 高级定时器
TIM1和TIM8是两个高级定时器,它们具有基本、通用定时器的所有功能,还具有三相6步电机的接口、刹车的功能(break function)及用于PWM驱动电驴的死区时间控制,使得它非常适合于点击的控制
在进行STM32编程的时候,我们会发现一点,STM32需要你去了解很多底层的硬件细节、GPIO、中断callback的设置、中断向量例程的设置、外设时钟的设置和使能,它们往往是成对出现、并配置使用的,这涉及到了大量的细节
Copyright (c) 2016 LittleHann All rights reserved