0、GPIO回顾
GPIO,通用型输入输出,控制stm32输入输出的引脚,统称GPIO。
主功能是默认的功能
复用的功能在芯片里都是由连线的,有联系才能复用。所以GPIO引脚能复用的功能只能是它默认复用功能和重定义功能。一般都使用默认功能,重定义往往不会打开,一旦要用重定义功能需要用到重映射。


想象成多路开关,内部的配置决定输入输出模式。

端口配置寄存器 低位CRL (0-7) 高位CRH(8-15) 4位配一个引脚,CNF和MODE各配2个。
一、改进GPIO流水灯
(1)复制案例,修改名称
03_led_flow_pro_register

(2)删减
删除除user和start以外的文件夹和文件,保留.uvprojx文件
(3)创建文件



像这样的基础配置,都可以放在一起称为初始化。也就是将外围的LED灯,写一个驱动程序。LED灯是属于硬件外设,所以新建文件夹Hardware,新建两个文件,如下:

(4)打开keil文件项目

(5)打开项目管理,添加User文件夹下delay.c文件

(6)在Hardware文件下创建LED文件夹,将led.c和led.h文件放进去。







(7)配完关掉keil,通过vscode打开。
首先补充delay.c,第一步肯定是引入.h文件。

在delay.h中如此定义

前一个文件在main中定义的函数可以直接挪移。

led.c也是同理。引入led.h

之后把所有定义函数的操作放进.c文件里。
cpp
#include "led.h"
//初始化
void LED_Init(void)
{
//1.时钟配置,开启GPIOA时钟
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
//2.工作模式配置,PA0 PA1 PA8通用推挽输出 CNF=00,MODE=11
GPIOA->CRL&=~GPIO_CRL_CNF0;
GPIOA->CRL|=GPIO_CRL_MODE0;
GPIOA->CRL&=~GPIO_CRL_CNF1;
GPIOA->CRL|=GPIO_CRL_MODE1;
GPIOA->CRH&=~GPIO_CRH_CNF8;
GPIOA->CRH|=GPIO_CRH_MODE8;
//3.初始全高电平,都置1, 全关灯
/*GPIOA->ODR|=GPIO_ODR_ODR0; LED_Off(LED_1);
GPIOA->ODR|=GPIO_ODR_ODR1; LED_Off(LED_2)
GPIOA->ODR|=GPIO_ODR_ODR8; LED_Off(LED_3);*/
uint16_t leds[]={LED_1,LED_2,LED_3};
LED_OffAll(leds,3);
}
//开关LED灯,参数就定为led,要开哪个灯就传哪个灯
void LED_On(uint16_t led)
{
GPIOA->ODR&=~led;
}
void LED_Off(uint16_t led)
{
GPIOA->ODR|=led;
}
//反转LED灯状态
void LED_Toggle(uint16_t led)
{
//根据IDR对应位的值,判断当前LED状态. IDR和ODR对应的位是一样的
if((GPIOA->IDR&led)==0)
{
LED_Off(led);
}
else
{
LED_On(led);
}
}
//控制所有灯的开关
void LED_OnAll(uint16_t leds[],uint8_t size)//全开
{
for (uint8_t i = 0; i < size; i++)
{
LED_On(leds[i]);
}
}
void LED_OffAll(uint16_t leds[],uint8_t size)//全关
{
for (uint8_t i = 0; i < size; i++)
{
LED_Off(leds[i]);
}
}
为了看起来方便,将开启数据端口的寄存器写法宏定义为我们的白话。
合理规范代码:
delay.c
cpp
#include "delay.h"
//延时函数
void Delay_us(uint16_t us)
{
SysTick->LOAD=72*us;
SysTick->CTRL=0x05;
while(!(SysTick->CTRL&SysTick_CTRL_COUNTFLAG))
{}
SysTick->CTRL&=~SysTick_CTRL_ENABLE;
}
void Delay_ms(uint16_t ms)
{
while(ms--)
{
Delay_us(1000);
}
}
void Delay_s(uint16_t s)
{
while(s--)
{
Delay_ms(1000);
}
}
delay.h
cpp
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
//定义延时函数
void Delay_us(uint16_t us);
void Delay_ms(uint16_t ms);
void Delay_s(uint16_t s);
#endif
led.c
cpp
#include "led.h"
void LED_Init(void)
{
// 时钟配置,打开时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 工作模式配置,PA0 PA1 PA8通过推挽输出。CNF=00,MODE=11
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF0;
GPIOA->CRL |= GPIO_CRL_MODE1;
GPIOA->CRL &= ~GPIO_CRL_CNF1;
GPIOA->CRH |= GPIO_CRH_MODE8;
GPIOA->CRH &= ~GPIO_CRH_CNF8;
// 初始化引脚输出高电平,关灯
LED_Off(LED_1);
LED_Off(LED_2);
LED_Off(LED_3);
}
void LED_On(uint16_t led)
{
GPIOA->ODR &= ~led;
}
void LED_Off(uint16_t led)
{
GPIOA->ODR |= led;
}
void LED_Toggle(uint16_t led)
{
// 根据IDR对应位的值,判断当前LED状态
if ((GPIOA->IDR & led) == 0)
{
LED_Off(led);
}
else
{
LED_On(led);
}
}
void LED_OnAll(uint16_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
LED_On(leds[i]);
}
}
void LED_OffAll(uint16_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
LED_Off(leds[i]);
}
}
led.h
cpp
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
//定义LED灯
#define LED_1 GPIO_ODR_ODR0
#define LED_2 GPIO_ODR_ODR1
#define LED_3 GPIO_ODR_ODR8
//初始化
void LED_Init(void);
//开关LED灯
void LED_On(uint16_t led);
void LED_Off(uint16_t led);
//翻转LED灯状态
void LED_Toggle(uint16_t led);
//控制所有LED灯的开关
void LED_OnAll(uint16_t leds[],uint8_t size);
void LED_OffAll(uint16_t leds[],uint8_t size);
#endif
main.c
cpp
#include <stdint.h>
#include "delay.h"
#include "led.h"
int main(void)
{
//初始化
LED_Init();
uint16_t leds[]={LED_1,LED_2,LED_3};
uint8_t n=3;
while(1)
{
for(uint8_t i=0;i<n;i++)
{
LED_On(leds[i]);
Delay_ms(500);
LED_Off(leds[i]);
}
}
}
二、总体架构和时钟系统
1.总体架构stm32

(1)3个被动单元
内部SRAM
存储程序执行时用到的变量
在嵌入式环境中,SRAM相当于运行内存
内部闪存存储器
存储下载的程序 程序执行时用到的常量
flash严格来讲划分为ROM(随机访问存储器,掉电就丢)类型
在嵌入式环境中,flash相当于存储内存
AHB到APB的桥(AHB to APBx)
B:Bus 总线的意思 AHB:高速系统总线 是总线的核心


桥1,通过APB2总线连接到APB2上的外设。属于高速外设,最高72MHz.
桥2,通过APB1总线连接APB1的外设。低速外设,最高36MHz。时钟配置时需要二分频。
(2)四个驱动(主动)单元

(3)其他单元
内核Code总线
通过外部的ICode总线连接Flash,实现指令的读取
FSMC


2.时钟系统
51不需要开启时钟,因为简单,只有一个时钟,不用配。
32有不同时钟来源,高速设备接高速时钟,低速设备接低速时钟。这样效率最高。
时钟源

内部的低速时钟 40k 外部的低速时钟 32.768k
SYSCLK 系统时钟
RTCCLK 实时时钟

(1)HSE时钟
高速外部时钟是由外部时钟源提供。做过STM32项目的同学都知道,几乎所有的单片机都会外部接一个8Mhz的晶振,经过PLL九倍频得到72MHZ的系统时钟,这是系统默认的时钟。


(2)HSI时钟
HSI时钟是内部的8MHZ的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。不需要任何外部器件就能提供系统时钟。启用时间比HSE晶体振荡器短。缺点是就算校准,时钟频率精度较差。

(3)PLL时钟
内部PLL用来倍频HSI RC的输出时钟或HSE晶体输出时钟。PLL设置必须在其被激活前完成,一旦激活,参数不能被改动。如果PLL中断在时钟中断寄存器里被允许,当PLL准备就绪时,可产生中断申请。
PLL时钟对外部8MHZ时钟信号9倍频,得到72MHZ时钟频率,这是STM32F1系列允许的最高时钟频率。

(4)LSE时钟
LSE晶体是一个32.768khz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时功能他提供一个低功耗且精确的时钟源。LSE不能驱动系统时钟。

(5)LSI时钟
低功耗时钟源,在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。时钟频率大约40kHZ(30-60之间)。
不能驱动系统时钟。

(6)stm32时钟配置源码分析
关于时钟配置的源代码,往往在启动文件中已经做完了。




CR是时钟的控制器

CFGR 时钟配置寄存器

三、HAL库开发
1.简介
寄存器效率虽高,但是开发效率低,对开发者来说不太友好。

2.环境与安装
Java8很稳定,我们就安装这个。
win+r cmd
输入 java -version 检验是否安装java8

能够显示版本号,就说明电脑安装着Java的jdk.
然后就是傻瓜式安装。cubmx 图形化界面,自动生成ST的hal库文件,让我们的配置变成点点点。






之后就是芯片支持包了。双击打开cubeMX.



这是在线安装,可能时间费得有些多。
下面是离线安装步骤:

文章结尾我会给大家资料包,里面就有离线芯片支持包


里面有两个,一个是基础的,一个是升级的1-8-5,比较尴尬的是只能安装基础包,但我们可以李代桃僵。
我们安装好CUBEMX之后,就去找它的仓库,之前安装的时候,不改路径的话,应该在C盘用户


我们在CUBEmx上先装基础包,之后将高版本的解压到仓库文件夹替换掉它

必须解压到这里哦。
解压之后替换掉基础包。

3.流水灯案例(HAL库)
(1)配置


联网的话,会出现更新提示,直接关掉即可。
之后让我开始点点点。

这里面都是我们所要配的引脚,找准需求,定位普通或要复用的引脚吧。
系统核心配置:



SYS是必配的。选择模式和系统时钟,单线模式SW,一根数据线,一根时钟线。JTAG有四根线。

如果是标准库寄存器写法,时钟需要自己配。

前边我们说过,PLL时钟会选择外部晶振提供时钟。这一点主要在下图体现:

之后配置GPIO,点亮LED灯主要是靠GPIO的通用推挽输出。


剩下两个引脚都一样。
之后创建工程文件


之后跟随窗口就打开KEIL。
(2)代码实现

话不多说,开始写代码。将我们之前写的硬件外设LED灯的两个文件导入工程。


led.c
cpp
#include "led.h"
//开关LED灯,参数就定为led,要开哪个灯就传哪个灯
void LED_On(uint16_t led)
{
HAL_GPIO_WritePin(LED_1_GPIO_Port,led,GPIO_PIN_RESET);
}
void LED_Off(uint16_t led)
{
HAL_GPIO_WritePin(LED_1_GPIO_Port,led,GPIO_PIN_SET);
}
//反转LED灯状态
void LED_Toggle(uint16_t led)
{
HAL_GPIO_TogglePin(LED_1_GPIO_Port,led);
}
//控制所有灯的开关
uint16_t leds[]={LED_1_Pin,LED_2_Pin,LED_3_Pin};
void LED_OnAll(uint16_t leds[],uint8_t size)//全开
{
for (uint8_t i = 0; i < size; i++)
{
LED_On(leds[i]);
}
}
void LED_OffAll(uint16_t leds[],uint8_t size)//全关
{
for (uint8_t i = 0; i < size; i++)
{
LED_Off(leds[i]);
}
}
main.c
cpp
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "led.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();//初始化,所有引脚高电平,等全灭
uint8_t n=3;
uint16_t leds[]={LED_1_Pin,LED_2_Pin,LED_3_Pin};
while (1)
{
for (uint8_t i = 0; i < n; i++)
{
LED_On(leds[i]);
HAL_Delay(1000);
LED_Off(leds[i]);
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */