STM32 外设使用教程
STM32 外设使用教程
以F411CEU6为例
总架构图:
优化项
STM32 F4 系列具备指令缓存、数据缓存和预取缓冲区功能。
1
2
// 启用指令缓存、数据缓存、预取缓冲区
FLASH->ACR |= FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN;
FLASH->ACR:FLASH是一个指向闪存控制寄存器组的结构体指针,ACR即闪存访问控制寄存器(Access Control Register)。借助对该寄存器进行操作,能够对闪存的访问行为进行配置。FLASH_ACR_ICEN:这是一个宏定义,代表指令缓存使能位。当该位被置为 1 时,指令缓存功能会被启用。指令缓存的作用是把频繁访问的指令存储起来,这样在后续需要执行这些指令时,就无需再从闪存中读取,从而加快指令的执行速度。FLASH_ACR_DCEN:这同样是一个宏定义,代表数据缓存使能位。当该位被置为 1 时,数据缓存功能会被启用。数据缓存用于存储频繁访问的数据,减少对闪存的访问次数,进而提高数据访问的速度。FLASH_ACR_PRFTEN:此宏定义代表预取缓冲区使能位。当该位被置为 1 时,预取缓冲区功能会被启用。预取缓冲区能够提前从闪存中读取指令,在 CPU 需要执行指令时可以更快地提供指令,从而提升指令的执行效率。
在 STM32 F1 系列中,只有大容量产品(如 STM32F103xE 等)具备预取缓冲区(Prefetch Buffer)功能,不过并没有指令缓存(Instruction Cache)和数据缓存(Data Cache)功能。
配置时钟源
以下图中时钟配置为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 配置使用外部高速时钟 HSE 作为时钟源
void SystemClock_Config(void) {
ErrorStatus HSEStartUpStatus;
RCC_DeInit(); // 复位 RCC 时钟配置为默认值
RCC_HSEConfig(RCC_HSE_ON); // 使能 HSE
HSEStartUpStatus = RCC_WaitForHSEStartUp(); // 等待 HSE 启动
if (HSEStartUpStatus == SUCCESS) {
// 配置 PLL 锁相环
RCC_PLLConfig(RCC_PLLSource_HSE, 12, 96, 2, 4);
// 使能 PLL 锁相环
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
// 等待 PLL 启动
}
// 配置系统时钟
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// 配置 APB1 和 APB2 时钟
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
// 设置系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 等待系统时钟切换到 PLL
while (RCC_GetSYSCLKSource() != 0x08) {}
} else {
while (1) {}
// 配置失败,死循环
}
}
配置完毕之后可以通过一个函数获取到当前芯片的系统时钟频率:
1
2
3
4
5
6
7
8
9
10
// 获取当前系统时钟频率
void get_clock_info(void) {
RCC_ClocksTypeDef RccClocks;
RCC_GetClocksFreq(&RccClocks);
printf("SYSCLK Frequency: %lu Hz\r\n", RccClocks.SYSCLK_Frequency);
printf("HCLK Frequency: %lu Hz\r\n", RccClocks.HCLK_Frequency);
printf("PCLK1 Frequency: %lu Hz\r\n", RccClocks.PCLK1_Frequency);
printf("PCLK2 Frequency: %lu Hz\r\n", RccClocks.PCLK2_Frequency);
}
GPIO
作为输出
1
2
3
4
5
6
7
8
9
10
11
12
13
// LED硬件GPIO初始化
void led_init() {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitTypeDef blue_led_pin;
blue_led_pin.GPIO_Pin = GPIO_Pin_13;
blue_led_pin.GPIO_Mode = GPIO_Mode_OUT;
blue_led_pin.GPIO_Speed = GPIO_Speed_2MHz;
blue_led_pin.GPIO_OType = GPIO_OType_PP;
blue_led_pin.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &blue_led_pin);
}
读取外部电平信号
以按键电路为例
首先初始化GPIO口
1
2
3
4
5
6
7
8
9
10
11
void Key_Init(void) {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_Key_InitStructure;
GPIO_Key_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Key_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_Key_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Key_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Key_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_Init(GPIOA, &GPIO_Key_InitStructure);
}
UART/USART
使用串口,看芯片的引脚发现,UART1需要复用GPIOA的Pin9和Pin10。
所以总体流程是:
- 使能GPIO
- 使能UART
- 配置GPIO Pin脚属性及Pin复用(引脚复用这里要注意,F1系列中使用
GPIO_Init函数就完成复用功能了,但是F4系列引脚复用功能更强大,所以要使用单独的GPIO_PinAFConfig函数指定复用功能) - 配置Pin脚UART属性
- 使能UART接收中断
- 使能UART
- 配置UART接收中断的NVIC
- 重写UART接收中断服务程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "stm32f4xx.h"
#include "usart.h"
// Reuse GPIOA Pin9 and Pin10 as UART1 TX/RX pins
void usart1_gpio_init(void) {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // enable GPIOA clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // enable USART1 clock
// initialize USART1 TX pin
GPIO_InitTypeDef usart1_tx_pin;
usart1_tx_pin.GPIO_Pin = GPIO_Pin_9;
usart1_tx_pin.GPIO_Mode = GPIO_Mode_AF;
usart1_tx_pin.GPIO_Speed = GPIO_Speed_50MHz;
usart1_tx_pin.GPIO_OType = GPIO_OType_PP;
usart1_tx_pin.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &usart1_tx_pin);
// initialize USART1 RX pin
GPIO_InitTypeDef usart1_rx_pin;
usart1_rx_pin.GPIO_Pin = GPIO_Pin_10;
usart1_rx_pin.GPIO_Mode = GPIO_Mode_AF;
usart1_rx_pin.GPIO_Speed = GPIO_Speed_50MHz;
usart1_rx_pin.GPIO_OType = GPIO_OType_OD;
usart1_rx_pin.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &usart1_rx_pin);
// REMAP USART1 TX/RX pins
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
// initialize USART1
USART_InitTypeDef usart1_init;
usart1_init.USART_BaudRate = 115200;
usart1_init.USART_WordLength = USART_WordLength_8b;
usart1_init.USART_StopBits = USART_StopBits_1;
usart1_init.USART_Parity = USART_Parity_No;
usart1_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart1_init.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &usart1_init);
// enable USART1 RX interrupt
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// enable USART1
USART_Cmd(USART1, ENABLE);
// enable USART1 interrupt
NVIC_InitTypeDef nvic_init;
nvic_init.NVIC_IRQChannel = USART1_IRQn;
nvic_init.NVIC_IRQChannelPreemptionPriority = 6;
nvic_init.NVIC_IRQChannelSubPriority = 0;
nvic_init.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init);
}
// USART1中断服务程序
// USART1 interrupt service program
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char ch = USART_ReceiveData(USART1);
USART_SendData(USART1, ch);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
TIM(Timer)
TIM是一个计数器,当计数器的输入是一个准确可靠的基准时钟的时候,计数的过程就是计时的过程,所以也叫做定时器。
除了基本的定时中断功能,还有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。
TIM根据复杂度和应用场景可以分为高级定时器、通用定时器、基本定时器三种类型。
ADC
Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。
ADC的主要特征
- 12位逐次逼近型的模拟数字转换器;
- 最多带3个ADC控制器,可以单独使用,也可以使用双重模式提高采样率;
- 最多支持23个通道,可最多测量21个外部和2个内部信号源;
- 支持单次和连续转换模式;
- 转换结束,注入转换结束,和发生模拟看门狗事件时产生中断;
- 通道0到通道n的自动扫描模式;
- 自动校准;
- 采样间隔可以按通道编程;
- 规则通道和注入通道均有外部触发选项;
- 转换结果支持左对齐或右对齐方式存储在16位数据寄存器;
- ADC转换时间:最大转换速率 1us(最大转换速度为1MHz,在ADCCLK=14M,采样周期为1.5个ADC时钟
- 得到);
- ADC供电要求:2.4V-3.6V;
- ADC输入范围:VREF- ≤ VIN ≤ VREF+。
F411ceu6中的adc
该款芯片中只有一个12位的adc,挂载在APB2总线上,内部总线、外设联系:
F411xC/xE有1个ADC,16个外部输入通道,有single-shot mode和 scan mode两种采样模式,也交代了可以使用DMA, 并且在电压采样的时候会产生中断。
ADC的时钟可以通过PCLK2通过分频得到,不过ADCCLK存在一个最大值,在供电电压在2.4V~3.6V时,ADCCLK最大可以是36MHz,典型值是30MHz。
This post is licensed under CC BY 4.0 by the author.



