STM32单片机介绍1

本文详细介绍了STM32的中断系统、时钟配置和外设设置,包括命名规则、启动模式、总线AMBA、AHB和APB、NVIC中断控制器、TIM1输出互补波形编程、中断编程过程、AD使用DMA以及外设地址设置。内容涵盖了STM32的中断优先级分组、定时器配置、DMA初始化和STM32CubeMX工具的使用。
摘要由CSDN通过智能技术生成

目录:

一、命名规则

二、启动模式配置说明

1、STM32一共有三种启动模式,相关的配置说明如下

2、启动模式配置附录

三、总线AMBA、AHB、APB

1、AMBA

2、Bus Bridges

3、STM32上的总线结构

四、NVIC

1、简述

2中断优先级分组

3、举例说明

五、IM1输出互补波形程序

六、中断编程过程

1、串口中断编程过程

1)应用串口中断时涉及到的一些库文件   2)初始化   3)发送数据    4)接收数据   5)main函数    6在启动文件查找断函数

2、外中断编程过编程过程

1)通用I/O端口连接到16个外部中断/事件线   2)外部中断服务函数的编写   3)具体程序编写

七、AD使用DMA

1、DMA初始化

2、STM32关于使用定时器触发ADC转换的解决办法和详细说明

八、外设地址设置

九、时钟树

1、在STM32上如果不使用外部晶振,OSC_IN和OSC_OUT的接法

2、STM32时钟系统

1)简述      2)时钟输出的使能控制     3)使用HSE时钟,程序设置时钟参数流程

3、定时器对应的输出端口


或者使用“STM32CubeMX”工具软件,介绍STM32CubeMX之F1xx系列实用报告(图文代码齐全),软件下载STM32CubeMX软件


下续:STM32单片机介绍2

一、命名规则

从命名规则可得STM32F103VET6Flash大小为512K字节。

通过数据手册得到Flash大小,下面以STM32F103RBT6为例说明:

Page0~Page127 = 1KB(0000-03FF) * 128Page = 128KB total,该区域地址范围:0x08000000~0x0801FFFF,总大小为128K字节。该区域主要用途:存放STM32的代码段(用户程序)。

二、启动模式配置说明

1、STM32一共有三种启动模式

相关的配置说明如下:

所谓启动,一般来说就是指我们下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存。用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。

-----------------------

1)Main Flash memory

STM32内置的Flash,一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。正常工作就在这种模式下,STM32的FLASH可以擦出10万次,所以不用担心芯片哪天会被擦爆!STM32从Flash存储的第一条指令开始执行,即执行STM32的启动代码stm32f10x_vector.s(或stm32F10x_xxx_xxx.s或startup_xxx.s 根据STM32 Firmware library的不同而不同),执行启动代码后会跳到main函数,执行用户程序。

-----------------------

2)System memory

System memory is used to boot the device in System memory boot mode. The area is reserved for use by STMicroelectronics and contains the boot loader which is used to reprogram the Flash memory using the USART1 serial interface. It is programmed by ST when the device is manufactured, and protected against spurious write/erase operations. For further details please refer to AN2606.

从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。

系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader,也就是我们常说的ISP程序,这是一块ROM,出厂后无法修改。

一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件(比如mcuisp.exe),可以通过这个BootLoader将程序下载到系统的Flash中。但是这个下载方式需要以下步骤:

将BOOT0设置为1,BOOT1设置为0,然后按下复位键(或重启),这样才能从系统存储器启动BootLoader;

最后在BootLoader的帮助下,通过串口下载程序到Flash中;

程序下载完成后,又需要将BOOT0设置为GND(通过电阻连到地),手动复位(或重启),这样STM32才可以从Flash中启动。

关于DB9的针脚定义见“通信-RS232、RS485、RS4223、DB9针脚定义”。

-----------------------

3)Embedded SRAM

内置SRAM,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。

(1)故障的局部诊断,写一段小程序加载到SRAM中诊断板上的其他电路,或用此方法读写板上的Flash或EEPROM等。

(2)通过这种方法解除内部Flash的读写保护,当然解除读写保护的同时Flash的内容也被自动清除,以防止恶意的软件拷贝。

(3)只修改了代码中一个小小的地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码(也就是STM32的内存中),用于快速的程序调试,等程序调试完成后,在将程序下载到Flash中。

2、启动模式配置附录

先是拿到一块别人的板子和程序来修改,后来做了块板子,烧录程序后发现:烧录后通过烧录工具的“烧录后运行”选项可以正常跑起来,但一旦使程序从Flash开始运行,则跑不了。后发现别人板子的MCU烧录过Boot,而自己做的板子没有烧录Boot,前者程序从0x8003000开始运行,后者程序实际从0x8000000开始运行,把0x8003000处开始运行的程序烧到0x8000000的板子上的结果就是:复位向量地址不正确,导致芯片无法启动,因为0x8003000的程序把中断向量表搬到了0x8003000处。
转载正点原子的分析:

        STM32的内部闪存(Flash)地址起始于0x08000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004(0x8003000的程序中,中断向量表的地址0x8003000),当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。

         在图53.1.1中,STM32在复位后,先从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号所示;在复位中断服务程序执行完之后,会跳转到我们的main函数,如图标号所示;而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处,如图标号所示;然后,根据中断源进入相应的中断服务程序,如图标号所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图标号所示。

三、总线AMBA、AHB、APB

1、AMBA

AMBA(Advanced Microprocessor Bus Architecture)是ARM公司提出的一种开放性的SoC总线标准,现在已经广泛的应用于RISC的内核上了。 
AMBA定义了一种多总线系统(multilevel busing system),包括系统总线和等级稍低的外设总线。 
AMBA支持32位、64位、128位的数据总线,和32位的地址总线,同时支持byte和half-word设计。 
它定义了两种总线: 

AHB(Advanced High-performance Bus)先进的高性能总线,也叫做ASB(Advanced System Bus)。APB(Advanced peripheral Bus)先进的外设总线 。
AHB和ASB其实是一个东西,是高速总线,主要负责嵌入式处理器、DMA控制器、Memory等等的接口。 
APB是低速总线,主要负责外设接口。
AHB和APB之间是通过Bridge(桥接器)链接的。

2、Bus Bridges

总所周知,一个系统中的各个模块之间相互通信是通过总线,总线的作用,就是把数据和地址从设备A搬运到设备B上。如果说设备A和设备B具有一致性(原文是under discussion,这里我不知道怎么翻译比较好,暂且翻译为一致性),那么设备A和设备B可以直接挂在同一个总线上,并直接解读总线上的数据。 
但是,如果设备A和设备B不具有一致性,那么设备A和设备B就必须挂在两条不同的总线上,这时候我们就需要一个“翻译”,把设备A上的总线上的数据和地址转换成设备B可以解析的格式,然后放到设备B的总线上,这个“翻译”就是“Bus Bridge”,下面这幅图就形象的说明了Bus Bridge在AHB和APB之间的作用。

AHB链接的设备的数据传输速度是比APB设备传输的速度快很多的,也就是说,这里的这个Bus Bridge所起的作用就是缓冲”。这里可以看到AHB主要是链接在了系统的内核以及存储管理上面的,APB则主要分布给外设。 
下面这张图,更容易看出AHB和APB的作用: 
AHB链接的是系统总线、RAM等;
APB链接的是常用的外设:GPIO、UART等,如下:
APB1负责DA,USB,SPI,I2C,CAN,串口2345,普通TIM
APB2负责AD,I/O,高级TIM,串口1。

3、STM32上的总线结构

首先看一下F103系列的芯片的总线结构 

需要注意的是,这里有两个APB,它们链接的外设是不一样的,所以在STM32的库文件中会有关于APB1和APB2的定义: 

#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001) 
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008) 
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010) 
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040) 
#define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080) 
#define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100) 
#define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200) 
#define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400) 
#define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800) 
#define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000) 
#define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000) 
#define RCC_APB2Periph_USART1 ((uint32_t)0x00004000) 
#define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000) 
#define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000) 
#define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000) 
#define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000) 
#define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000) 
#define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000) 
#define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000) 
#define IS_RCC_APB2_PERIPH(PERIPH) ((((PERIPH) & 0xFFC00002) == 0x00) && ((PERIPH) != 0x00)) 

#define RCC_APB1Periph_TIM2 ((uint32_t)0x00000001) 
#define RCC_APB1Periph_TIM3 ((uint32_t)0x00000002) 
#define RCC_APB1Periph_TIM4 ((uint32_t)0x00000004) 
#define RCC_APB1Periph_TIM5 ((uint32_t)0x00000008) 
#define RCC_APB1Periph_TIM6 ((uint32_t)0x00000010) 
#define RCC_APB1Periph_TIM7 ((uint32_t)0x00000020) 
#define RCC_APB1Periph_TIM12 ((uint32_t)0x00000040) 
#define RCC_APB1Periph_TIM13 ((uint32_t)0x00000080) 
#define RCC_APB1Periph_TIM14 ((uint32_t)0x00000100) 
#define RCC_APB1Periph_WWDG ((uint32_t)0x00000800) 
#define RCC_APB1Periph_SPI2 ((uint32_t)0x00004000) 
#define RCC_APB1Periph_SPI3 ((uint32_t)0x00008000) 
#define RCC_APB1Periph_USART2 ((uint32_t)0x00020000) 
#define RCC_APB1Periph_USART3 ((uint32_t)0x00040000) 
#define RCC_APB1Periph_UART4 ((uint32_t)0x00080000) 
#define RCC_APB1Periph_UART5 ((uint32_t)0x00100000) 
#define RCC_APB1Periph_I2C1 ((uint32_t)0x00200000) 
#define RCC_APB1Periph_I2C2 ((uint32_t)0x00400000) 
#define RCC_APB1Periph_USB ((uint32_t)0x00800000) 
#define RCC_APB1Periph_CAN1 ((uint32_t)0x02000000) 
#define RCC_APB1Periph_CAN2 ((uint32_t)0x04000000) 
#define RCC_APB1Periph_BKP ((uint32_t)0x08000000) 
#define RCC_APB1Periph_PWR ((uint32_t)0x10000000) 
#define RCC_APB1Periph_DAC ((uint32_t)0x20000000) 
#define RCC_APB1Periph_CEC ((uint32_t)0x40000000) 
#define IS_RCC_APB1_PERIPH(PERIPH) ((((PERIPH) & 0x81013600) == 0x00) && ((PERIPH) != 0x00))
APB的速率见下面说明: APB的速率见下面说明:

APB1限制在了36MHz,APB2也可以达到全速72MHz 
下面是F105和F107的总线构架:

STM32上APB1和APB2的地址映射

四、NVIC

1、简述

   需要在STM32上移植RTOS,那么首先必须深入理解它的中断系统。什么是NVIC?即嵌套向量中断控制器(Nested Vectored Interrupt Controller)。STM32的中有一个强大而方便的NVIC,它是属于Cortex内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理,而SYSTICK不是由 NVIC来控制的。

特性:

  60个可屏蔽中断通道(不包含16个Cortex™-M3的中断线);

  16个可编程的优先等级(使用了4位中断优先级);

  低延迟的异常和中断处理;

  电源管理控制;

  系统控制寄存器的实现;

2、中断优先级分组

STM32(Cortex-M3)中有两个优先级的概念--抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。 

  具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。

  当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。

    Cortex内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception)和中断(interrupt),并把它们用一个表管理起来,编号为0~15的称为内核异常,而16以上的则称为外部中断,这个表就称为中断向量表。

    正是因为每个中断源都需要被指定这两种优先级,就需要有相应的寄存器位记录每个中断的优先级;在Cortex-M3中定义了8个比特位用于设置中断源的优先级,这8个比特位可以有8种分配方式,如下:

1)所有8位用于指定响应优先级 

2)最高1位用于指定抢占式优先级,最低7位用于指定响应优先级

3)最高2位用于指定抢占式优先级,最低6位用于指定响应优先级

4)最高3位用于指定抢占式优先级,最低5位用于指定响应优先级

5)最高4位用于指定抢占式优先级,最低4位用于指定响应优先级

6)最高5位用于指定抢占式优先级,最低3位用于指定响应优先级

7)最高6位用于指定抢占式优先级,最低2位用于指定响应优先级

8)最高7位用于指定抢占式优先级,最低1位用于指定响应优先级

以上便是优先级分组的概念,但是Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级。

    而STM32对这个表重新进行了编排,把编号从-3至6的中断向量定义为系统异常,编号为负的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号7开始的为外部中断,这些中断的优先级都是可以用户更改的。详细的 STM32中断向量号可以在startup_stm32f10x_XX.s中查找。

因此STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:

第0组:所有4位用于指定响应优先级(16种)

第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级(8种)

第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级(4种)

第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级(2种)

第4组:所有4位用于指定抢占式优先级

这里便对于于文章最前提到的固件库里相关的函数了——NVIC_PriorityGroupConfig(u32 NVIC_PriorityGroup),函数的参数共有5种:

这个函数的参数(NVIC_PriorityGroup值)有下列5种:

NVIC_PriorityGroup_0 => 选择第0组

NVIC_PriorityGroup_1 => 选择第1组

NVIC_PriorityGroup_2 => 选择第2组

NVIC_PriorityGroup_3 => 选择第3组

NVIC_PriorityGroup_4 => 选择第4组

     这其实也很好理解,比如选择NVIC_PriorityGroup_1,那么抢占式优先级便占一位,也就是说可以有2^1个级别,可以设置为0和1,而响应优先级则占3位,也就是说可以有2^3个选择,可以设置为0~7;总共来说就可以区别>16种优先级了。

 //NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

 //NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

3、举例说明

      假如现在有4个外部中断,还有一个EXTI9_5中断,那么如果选择优先级分组为第1组,那么抢占式优先级便只有两种,5个中断就至少有3个在抢占式优先级上是相同的优先级上,其他两个在令一优先级别。接着设置响应优先级可以有8种选择;假如现在同时有两个抢占式优先级别相同的中断发生,那么处理的顺序是谁的响应优先级高则谁优先进入中断,另外这点是需要注意的,如果此时进入这个中断之后又来了一个抢占式优先级相同但是响应优先级更高的中断,这时也是不会打断已有的中断的。

void NVIC_Config(void)
{
 NVIC_InitTypeDef NVIC_InitStructure;
 #ifdef  VECT_TAB_RAM
    //Set the Vector Table base location at 0x20000000 
    NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
 #else
    //Set the Vector Table base location at 0x08000000 
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); 
 #endif

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);  //中断优先级组 :1组(整个系统为同一组)
    // 设置先占优先级0~1,响应优先级0~7
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // Enable the TIM3 Interrupt
   NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;       // TIM3 全局中断
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 先占优先级 1
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;    // 从优先级 1
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;       // IRQ通道被使能
   NVIC_Init(&NVIC_InitStructure);
   NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;      //响应优先级0
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure); 
}

五、TIM1输出互补波形

使用官方的库函数的DEMO,如下图:
#include "stm32f10x.h"
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t TimerPeriod = 0;
uint16_t Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0;
void RCC_Configuration(void);
void GPIO_Configuration(void);

int main(void)
{
  RCC_Configuration();
  GPIO_Configuration();
  TimerPeriod = (SystemCoreClock / 17570 ) - 1;
  Channel1Pulse = (uint16_t) (((uint32_t) 5 * (TimerPeriod - 1)) / 10);
  Channel2Pulse = (uint16_t) (((uint32_t) 375 * (TimerPeriod - 1)) / 1000);
  Channel3Pulse = (uint16_t) (((uint32_t) 25 * (TimerPeriod - 1)) / 100);
  Channel4Pulse = (uint16_t) (((uint32_t) 125 * (TimerPeriod- 1)) / 1000);
//定时器初始化 函数 见库函数 P246页
  TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIM 时钟频率除数的预分频值
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式
  TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0; //时钟分割
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //设置 周期计数值
  TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
//TIM1 配置 见 库函数 P294 页
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //脉冲宽度调制模式2
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出比较状态
  TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //使能 互补 输出状态
  TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; //脉冲 值
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出比较极性低
  TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;//互补输出极性高
  TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; //MOE=0 设置 TIM1输出比较空闲状态
  TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;//MOE=0 重置 TIM1输出比较空闲状态
  TIM_OC1Init(TIM1, &TIM_OCInitStructure); //设定好的参数初始化TIM
  TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; //脉宽值
  TIM_OC2Init(TIM1, &TIM_OCInitStructure); //设定好的参数初始化TIM
  TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; //脉宽值
  TIM_OC3Init(TIM1, &TIM_OCInitStructure); //设定好的参数初始化TIM
  TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; //脉宽值
  TIM_OC4Init(TIM1, &TIM_OCInitStructure);//设定好的参数初始化TIM
  TIM_Cmd(TIM1, ENABLE); //使能 TIM1
  TIM_CtrlPWMOutputs(TIM1, ENABLE); //使能 TIM1 输出
  while (1)
  {}
}

void RCC_Configuration(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1
  RCC_APB2Periph_GPIOA
  RCC_APB2Periph_GPIOE
  RCC_APB2Periph_GPIOB
  RCC_APB2Periph_AFIO, ENABLE);
}

void GPIO_Configuration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
#ifdef STM32F10X_CL
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9
  GPIO_Pin_11
  GPIO_Pin_13
  GPIO_Pin_14
  GPIO_Pin_8
  GPIO_Pin_10
  GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOE, &GPIO_InitStructure);
  GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE);
#else
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8
  GPIO_Pin_9
  GPIO_Pin_10
  GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13
  GPIO_Pin_14
  GPIO_Pin_15;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
#endif
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
  while (1)
  {}
}
#endif

六、中断编程过程

1、串口中断编程过程

本文以USART1为例,叙述串口中断的编程过程。

1)应用串口中断时涉及到的一些库文件

  首先对于STM32外设库文件的应用编程,misc.c和stm32f10x_rcc.c是肯定要添加到。

  接下来就是我们要用到的相关外设了。毫无疑问,串口文件stm32f10x_usart.c是必须的。串口通信是对通用GPIO端口引脚的功能复用,所以还需要stm32f10x_gpio.c文件。另外,因为有中断的产生,所以中断文件stm32f10x_it.c也是必要的,当然这个文件一般和main.c放在一个文件夹下(一般习惯为User文件夹),因为我们的中断响应函数是要在里面自己编写的。

  当然还有其他的基本必须文件如系统配置文件等在这地方就不说了,这个是创建一个工程应该知道的。

-----------------------

2)初始化

对于串口通信的初始化,不仅仅只是对串口的初始化(这个地方是比较烦人的,不像别的芯片那样简洁明了)。

(1)首先时钟使能配置。STM32内部的时钟有很多,感兴趣的自己看看参考手册。此处以USART1为例说明。有USART1时钟、GPIOA时钟、GPIO复用(AFIO)时钟。由于此处USART1和GPIOA、AFIO均在APB2上,所以可以一次配置完成。如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1 ,ENABLE);

(2)其次中断配置。主要有优先级组设定、USART1中断使能、该中断的优先级,中断初始化。程序如下:

void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);//选择分组方式0

  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

(3)然后GPIO复用功能配置。一般情况下我们使用原始的外设和GPIO端口引脚的映射关系,如果要改变其映射的话,请另外查看参考手册上关于GPIO重映射部分。对于GPIO的复用,其引脚的输入与输出模式都有要求,在参考手册上有详细说明。

void GPIO_Configuration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(USARTy_GPIO, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(USARTy_GPIO, &GPIO_InitStructure);
}

(4)串口初始化配置。主要有串口基本参数配置(如波特率、数据位、工作方式等),串口中断使能,串口使能。

基本参数配置

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//校验
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //发送与接受两种方式
USART_Init(USART1, &USART_InitStructure);//用配置的参数激活串口初始化

串口中断使能

USART_ITConfig(USARTy, USART_IT_RXNE, ENABLE);//使能接受中断,在接受移位寄存器中有数据时产生
USART_ITConfig(USARTy, USART_IT_TXE, ENABLE);//使能发送中断,在发送完数据 后产生。

一般情况下,如果与PC通信的话,我们只用接受中断即可。串口使能:

USART_Cmd(USART1, ENABLE); //USART1使能

好了,经过以上步骤之后呢,我们就可以进行数据的收发了。

-----------------------

3)发送数据

//使用函数USART_SendData(USART1, char data),一次只能发送一个字符。当然我们可以用如下函数发送字符串。

void USART1_Puts(char * str) 
{ 
  while(*str)
  {
    USART_SendData(USART1, *str++);  //发送一个字符
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);  //等待发送完毕
  }
}

//当然我们也可以循环发送字符串数组

for(i = 0; TxBuf1 != '\0'; i++) // TxBuf1为定义好的字符串数组
{
  USART_SendData(USART2 , TxBuf1);
  while(USART_GetFlagStatus(USART2, USART_FLAG_TC)==RESET);
}

-----------------------

4)接收数据

由于我们使用的是接受中断,所以当有数据需要接收时,会执行相应的中断函数。此处我们USART1的中断函数在stm32f10x_it.c文件中。找到函数void USART1_IRQHandler(void),如果没有的话就自己加上吧,别忘了头文件中需要声明一下。当然你也可以在其他文件中写下该中断函数。当产生中断进入该函数之后,我们就可以进行自己的操作了。

void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USARTy, USART_IT_RXNE) != RESET)//如果寄存器中有数据
  {
    RxBuffer1[RxCounter1++] = USART_ReceiveData(USART1);
  }
}

别忘了在接受完数据进行别的操作之前为了防止数据被覆盖最好先禁止一下接受中断

USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); 
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);

-----------------------

5)main函数

int main(void) //这个地方有些特别,我们知道一般main函数是没有返回值的,但在STM32 的编程中其返回类型为int。
{
  RCC_Configuration();
  NVIC_Configuration();
  GPIO_Configuration();

  USART_InitStructure.USART_BaudRate = 9600;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

  USART_Init(USART1, &USART_InitStructure);
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
  //USART_ITConfig(USART1, USART_IT_TXE, ENABLE);

  USART_Cmd(USART1, ENABLE);

  while (1) //等待中断
  {

  }
}

当然你也可以在main()中添加一些发送指令之类的东西。

-----------------------
6)在启动文件查找中断函数

2、外中断编程过编程过程

1)通用I/O端口连接到16个外部中断/事件线

-----------------------
2)外部中断服务函数的编写
  EXPORT   EXTI0_IRQHandler
  EXPORT   EXTI1_IRQHandler
  EXPORT   EXTI2_IRQHandler
  EXPORT   EXTI3_IRQHandler
  EXPORT   EXTI4_IRQHandler
  EXPORT   EXTI9_5_IRQHandler
  EXPORT   EXTI15_10_IRQHandler
中断线 0~4 每个中断线对应一个中断函数
中断线 5~9 共用中断函数 EXTI9_5_IRQHandler
中断线 10~15 共用中断函数 EXTI15_10_IRQHandler
-----------------------
3)具体程序编写
void SurgeProtection_IO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 使能PortD的外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
}
void SurgeProtection_IO_EXTI_Init(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_ClearITPendingBit(EXTI_Line8);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource8);
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发
    EXTI_InitStructure.EXTI_Line=EXTI_Line8;
    EXTI_Init(&EXTI_InitStructure);
}
void SurgeProtection_IO_EXTI_Init1(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_ClearITPendingBit(EXTI_Line9);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource9);
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发
    EXTI_InitStructure.EXTI_Line=EXTI_Line9;
    EXTI_Init(&EXTI_InitStructure);
}
void SurgeProtection_NVIC_Init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
void SurgeProtectionInit(void)  注:两个中断EXIT_Init不能合二为一,不知何故。
{
    SurgeProtection_IO_Init();
    SurgeProtection_IO_EXTI_Init();
    SurgeProtection_IO_EXTI_Init1();
    SurgeProtection_NVIC_Init();
}

/边沿触发中断服务函数入口/

void EXTI9_5_IRQHandler(void)
{
    if((EXTI_GetITStatus(EXTI_Line8)!=RESET)||(EXTI_GetITStatus(EXTI_Line9)!=RESET))
    {
        if(!GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_8)) //PD8=0
        {//用户服务程序
            EXTI_ClearITPendingBit(EXTI_Line8);
        }
        if(!GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_9)) //PD9=0
        {//用户服务程序
            EXTI_ClearITPendingBit(EXTI_Line9);
        }
    }
}

七、AD使用DMA

1、DMA初始化

  DMA_DeInit(DMA1_Channel1);
  DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC1_DR_Address=(u32)(&ADC1->DR)
  DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_Result;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  DMA_InitStructure.DMA_BufferSize = 6;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
  
  //DMA_Cmd(DMA1_Channel1, ENABLE);//以前在这里启用DMA,出现通道错乱
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfChannel = 6;
  ADC_Init(ADC1, &ADC_InitStructure);

  ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 4, ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 6, ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 5, ADC_SampleTime_55Cycles5);

  ADC_DMACmd(ADC1, ENABLE);
  ADC_Cmd(ADC1, ENABLE);
  ADC_ResetCalibration(ADC1);
  while(ADC_GetResetCalibrationStatus(ADC1));
  ADC_StartCalibration(ADC1);
  while(ADC_GetCalibrationStatus(ADC1));

  DMA_Cmd(DMA1_Channel1, ENABLE);
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
下面的经验“珍情岁月”提供:
用DMA连续采样多路,自动触发,
请注意程序初始化顺序。 如果在初始化AD前使能了DMA就会出现数据错位现象。
要注意在初始化时将DMA_Cmd(DMA1_Channel1, ENABLE);放置到ADC_SoftwareStartConvCmd(ADC1, ENABLE);前面。
原因是:如果在初始化AD的时候DMA被触发了一次,但是此时并没有采样,但是DMA目的地址已经发生了自加,当你采样第一路的时候,数据却填充到了第二路。校准AD的时候会触发DMA导致通道错位,因此校准AD基准前不要启用DMA。

2、STM32关于使用定时器触发ADC转换的解决办法和详细说明

八、外设地址设置

写过AD采集,DA输出的人应该知道,经常要在代码的开始设定外设的地址,那么这个地址是怎么来的呢?
查看《STM32F10XX参考手册》中的存储器映像得知:每一个STM32F10XX的外设都有特定的起始地址,如下图:

那么该怎么用呢?先来看这样一个例子:用AD1的通道14采集数据,并通过DMA1通道1传输数据,显示在LCD12864上。在DMA外设地址设置中我们就需要知道ADC1_DR_ADDRESS的值了,从上表我们知道ADC1的地址范围为:0x40012400~0x400127FF

找到10.12.15节:ADC寄存器地址映像表:

在表的最后一栏ADC_DR,即ADC数据寄存器,我们就可以得知ADC_DR的地址偏移,再加上ADC1的起始地址0x40012400,就可以得到ADC_DR_ADDRESS = 0x4001244C

其他外设的地址也是相同的原理,步骤如下:

根据表1找到外设的起始地址;

根据外设的存储器映像表找到具体寄存器的偏移量;

起始地址+偏移地址 = 外设寄存器的实际地址

九、时钟树

RCC(Reset and Clock Control)复位和时钟控制,控制供给各模块时钟信号的通断。

1、在STM32上如果不使用外部晶振,OSC_INOSC_OUT的接法

 如果使用内部RC振荡器而不使用外部晶振,请按照下面方法处理:

1)对于100脚或144脚的产品,OSC_IN应接地,OSC_OUT应悬空。
2)对于少于100脚的产品,有2种接法:
2.1)OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。
2.2)分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出并输出'0'。此方法可以减小功耗并(相对上面2.1)节省2个外部电阻。

HSI内部8MHz的RC振荡器的误差在1%左右,内部RC振荡器的精度通常比用HSE(外部晶振)要差上十倍以上。STM32的ISP就是用(HSI)内部RC振荡器。

2、STM32时钟系统

1)简述

在STM32中,有五个时钟源,为HSIHSELSILSEPLL

HSI是高速内部时钟,RC振荡器,频率为8MHz。

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

LSI是低速内部时钟,RC振荡器,频率为40kHz。

LSE是低速外部时钟,接频率为32.768kHz的石英晶体。

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

      用户可通过多个预分频器配置AHB总线、高速APB2总线和低速APB1总线的频率。AHB和APB2域的最大频率是72MHZ。APB1域的最大允许频率是36MHZ。SDIO接口的时钟频率固定为HCLK/2。
      40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。
      STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。
     另外,STM32还可以选择一个PLL输出的2分频、HSI、HSE、或者系统时钟SYSCLK输出到MCO脚(PA8)上。系统时钟SYSCLK,是供STM32中绝大部分部件工作的时钟源,它可选择为PLL输出、HSI或者HSE,(一般程序中采用PLL倍频到72Mhz)在选择时钟源前注意要判断目标时钟源是否已经稳定振荡。Max=72MHz,它分为2路,1路送给I2S2、I2S3使用的I2S2CLK,I2S3CLK;另外1路通过AHB分频器分频(1/2/4/8/16/64/128/256/512)分频后送给以下8大模块使用:
送给SDIO使用的SDIOCLK时钟。
送给FSMC使用的FSMCCLK时钟。
送给AHB总线、内核、内存和DMA使用的HCLK时钟。
通过8分频后送给Cortex的系统定时器时钟(SysTick)。
直接送给Cortex的空闲运行时钟FCLK。
送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer2-7)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4、5、6、7使用。
送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer1、Timer8)1、2倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器1和定时器8使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后得到ADCCLK时钟送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。
2分频后送给SDIO AHB接口使用(HCLK/2)。

-----------------------

2)时钟输出的使能控制
  在以上的时钟输出中有很多是带使能控制的,如AHB总线时钟、内核时钟、各种APB1外设、APB2外设等。
当需要使用某模块时,必需先使能对应的时钟。需要注意的是定时器的倍频器,当APB的分频为1时,它的倍频值为1,否则它的倍频值就为2。
  连接在APB1(低速外设)上的设备有:电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3、SPI2、窗口看门狗、 Timer2、Timer3、Timer4。注意USB模块虽然需要一个单独的48MHz时钟信号,但它应该不是供USB模块工作的时钟,而只是提供给串行接口引擎(SIE)使用的时钟。USB模块工作的时钟应该是由APB1提供的。
连接在APB2(高速外设)上的设备有:GPIO_A-E、USART1、ADC1、ADC2、ADC3、TIM1、TIM8、SPI1、AFIO。

-----------------------

3)使用HSE时钟,程序设置时钟参数流程
(1)将RCC寄存器重新设置为默认值 RCC_DeInit;

(2)打开外部高速时钟晶振HSE   RCC_HSEConfig(RCC_HSE_ON);
(3)等待外部高速时钟晶振工作   HSEStartUpStatus = RCC_WaitForHSEStartUp();
(4)设置AHB时钟          RCC_HCLKConfig;
(5)设置高速AHB时钟     RCC_PCLK2Config;
(6)设置低速速AHB时钟    RCC_PCLK1Config;
(7)设置PLL         RCC_PLLConfig;
(8)打开PLL         RCC_PLLCmd(ENABLE);
(9)等待PLL工作       while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
(10)设置系统时钟      RCC_SYSCLKConfig;
(11)判断是否PLL是系统时钟  while(RCC_GetSYSCLKSource() != 0x08)
(12)打开要使用的外设时钟  RCC_APB2PeriphClockCmd()/RCC_APB1PeriphClockCmd()

下面是STM32软件固件库的程序中对RCC的配置函数(使用外部8MHz晶振)

void RCC_Configuration(void)
{
  RCC_DeInit();
  RCC_HSEConfig(RCC_HSE_ON);   //RCC_HSE_ON——HSE晶振打开(ON)
  HSEStartUpStatus = RCC_WaitForHSEStartUp();
  if(HSEStartUpStatus == SUCCESS)        //SUCCESS:HSE晶振稳定且就绪
  {   
    RCC_HCLKConfig(RCC_SYSCLK_Div1);  //RCC_SYSCLK_Div1——AHB时钟 = 系统时钟
    RCC_PCLK2Config(RCC_HCLK_Div1);   //RCC_HCLK_Div1——APB2时钟 = HCLK
    RCC_PCLK1Config(RCC_HCLK_Div2);   //RCC_HCLK_Div2——APB1时钟 = HCLK / 2
    FLASH_SetLatency(FLASH_Latency_2);    //FLASH_Latency_2  2延时周期
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);       // 预取指缓存使能
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);    

   // PLL的输入时钟 = HSE时钟频率;RCC_PLLMul_9——PLL输入时钟x 9
    RCC_PLLCmd(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) ;    
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
   //RCC_SYSCLKSource_PLLCLK——选择PLL作为系统时钟
    while(RCC_GetSYSCLKSource() != 0x08);        //0x08:PLL作为系统时钟
  }

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
  RCC_APB2Periph_GPIOC , ENABLE);
//RCC_APB2Periph_GPIOA    GPIOA时钟
//RCC_APB2Periph_GPIOB    GPIOB时钟
//RCC_APB2Periph_GPIOC    GPIOC时钟
//RCC_APB2Periph_GPIOD    GPIOD时钟
}

3、定时器对应的输出端口

查找其数据手册找到对应的端口


何必计较呢!在乌鸦的世界里,天鹅也是有罪的。觉得不错,动动发财的小手点个赞哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱上电路设计

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值