尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32 底层原理详解:从硅片到代码的完整旅程

STM32 底层原理详解:从硅片到代码的完整旅程 这是一篇面向嵌入式开发者的深度技术博客我们将从晶体管级别一路走到你的main()函数彻底搞懂 STM32 是怎么活过来的。前些天发现了一个巨牛的人工智能学习网站通俗易懂风趣幽默忍不住分享一下给大家。点击跳转到网站人工智能学习网站一、硬件层面STM32 的身体结构1.1 核心架构Cortex-M 内核 外设矩阵STM32 的本质是ARM Cortex-M 内核 ST 设计的外设矩阵封装在一块硅片上。┌─────────────────────────────────────────┐ │ STM32 芯片内部架构 │ ├─────────────────────────────────────────┤ │ ┌─────────────┐ ┌───────────────┐ │ │ │ Cortex-M │◄──►│ NVIC (中断) │ │ │ │ 内核 (CPU) │ │ 控制器 │ │ │ │ │ └───────┬───────┘ │ │ │ • R0-R15 │ │ │ │ │ • xPSR │◄───────────┘ │ │ │ • 流水线 │ │ │ └──────┬──────┘ ┌───────────────┐ │ │ │ │ SysTick │ │ │ └──────────►│ 系统滴答定时器 │ │ │ └───────────────┘ │ ├─────────────────────────────────────────┤ │ 总线矩阵 (Bus Matrix) │ │ ┌─────────┬─────────┬────────────────┐ │ │ │ I-Code │ D-Code │ System │ │ │ │ 总线 │ 总线 │ 总线 │ │ │ │(取指令) │(取数据) │ (外设/内存) │ │ │ └────┬────┴────┬────┴────────┬───────┘ │ │ └─────────┴─────────────┘ │ ├─────────────────────────────────────────┤ │ 外设区域 │ │ ┌────────┐ ┌────────┐ ┌────────────┐ │ │ │ RCC │ │ GPIO │ │ USART │ │ │ │时钟控制 │ │ 端口 │ │ 串口 │ │ │ └────────┘ └────────┘ └────────────┘ │ │ ┌────────┐ ┌────────┐ ┌────────────┐ │ │ │ TIM │ │ ADC │ │ DMA │ │ │ │ 定时器 │ │ 模数转换│ │ 直接存储访问 │ │ │ └────────┘ └────────┘ └────────────┘ │ ├─────────────────────────────────────────┤ │ 存储器 │ │ ┌────────────┐ ┌──────────────────┐ │ │ │ Flash │ │ SRAM │ │ │ │ (程序) │ │ (变量/堆栈) │ │ │ │ 0x0800_0000│ │ 0x2000_0000 │ │ │ └────────────┘ └──────────────────┘ │ └─────────────────────────────────────────┘关键总线说明I-Code 总线内核专门用来从 Flash 取指令的 32 位总线永远只读。D-Code 总线内核专门用来从 Flash 取常量数据如const变量、查找表。System 总线访问 SRAM、外设寄存器、以及通过 AHB/APB 桥访问所有外设。AHBAdvanced High-performance Bus高速总线连接 DMA、Flash、SRAM、RCC。APBAdvanced Peripheral Bus低速外设总线GPIO、USART、TIM、ADC 等都挂在这里。为什么分三条总线这是哈佛架构的优化取指令和取数据可以并行CPU 不用等待。System 总线则负责外设通信互不干扰。1.2 时钟树芯片的心跳系统STM32 的时钟系统极其复杂这是理解底层的关键。┌─────────────┐ HSE ─────►│ PLL │────┐ (8MHz晶振) │ 锁相环倍频 │ │ └─────────────┘ │ ▼ ┌────────────────────────────────┐ │ SYSCLK (系统时钟) │◄── 最高 72/168/480MHz │ (如 168MHz) │ └───────────────┬────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌────────┐ ┌────────┐ ┌────────┐ │ AHB │ │ AHB │ │ AHB │ │ 预分频 │ │ 预分频 │ │ 预分频 │ │ (/1) │ │ (/2) │ │ (/4) │ └───┬────┘ └───┬────┘ └───┬────┘ │ │ │ ▼ ▼ ▼ HCLK168MHz HCLK/284MHz HCLK/442MHz │ │ │ ┌────┴────┐ ┌───┴────┐ ┌───┴────┐ │ AHB │ │ APB1 │ │ APB2 │ │ 总线 │ │ 总线 │ │ 总线 │ │ │ │(低速) │ │(高速) │ └────┬────┘ └───┬────┘ └───┬────┘ │ │ │ ┌────┴────┐ ┌───┴────┐ ┌───┴────┐ │ 内存 │ │ TIM2-7 │ │ TIM1/8 │ │ DMA │ │ USART2 │ │ USART1 │ │ GPIO │ │ I2C │ │ SPI1 │ └─────────┘ └────────┘ └────────┘时钟源层级时钟源名称频率用途HSI高速内部 RC8/16 MHz默认启动时钟精度差±1%HSE高速外部晶振4-26 MHz高精度通常接 8MHz 晶振LSI低速内部 RC32 kHz看门狗、RTCLSE低速外部晶振32.768 kHzRTC 精确计时PLL锁相环倍频输出系统主时钟来源为什么需要这么复杂的时钟树功耗管理不同外设跑不同速度不用的高速外设可以关时钟。时序匹配APB1 最高 42MHzAPB2 最高 84MHz以 F4 为例必须分频。外设同步USART 需要精确波特率TIM 需要精确计数都来自同一时钟树。1.3 启动过程从复位到 main()当你按下复位键发生了一系列硬件级别的操作┌─────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────┐ │ 复位 │────►│ 从 0x0000_0000 │────►│ 读取 SP 初值 │────►│ 设置 SP │ │ 释放 │ │ 取向量表首地址 │ │ (栈顶地址) │ │ (0x2000_xxxx)│ └─────────┘ └─────────────┘ └──────────────┘ └────┬─────┘ │ ┌─────────┐ ┌─────────────┐ ┌──────────────┐ │ │ 执行 │◄────│ 跳转到 │◄────│ 读取 Reset │◄────────┘ │ Reset │ │ Reset_Handler│ │ Handler 地址 │ │ Handler │ │ │ │ (0x0000_0004)│ └────┬────┘ └─────────────┘ └──────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Reset_Handler 做的事情汇编代码在 startup_stm32xxxx.s 中 │ │ 1. 复制 .data 段从 Flash 到 SRAM初始化全局变量 │ │ 2. 清零 .bss 段未初始化全局变量置 0 │ │ 3. 调用 SystemInit()配置时钟、Flash 等待周期 │ │ 4. 调用 __libc_init_array()C 构造函数等 │ │ 5. 调用 main() │ └─────────────────────────────────────────────────────────────┘内存映射关键点地址空间 (32位4GB) ├─ 0x0000_0000 ─┤ 代码区可重映射 │ │ • 0x0000_0000: 初始 SP │ │ • 0x0000_0004: Reset_Handler │ │ 启动时这里映射到 Flash 或 System Memory ├─ 0x0800_0000 ─┤ Flash主存储区 │ │ 存放程序代码和常量 ├─ 0x1FFF_0000 ─┤ System MemoryST 出厂 Bootloader │ │ 串口/I2C/SPI 下载程序用 ├─ 0x2000_0000 ─┤ SRAM运行时变量、堆栈 │ │ 大小从 20KB 到 512KB 不等 ├─ 0x4000_0000 ─┤ 外设寄存器区 │ │ GPIO、TIM、USART 等所有外设 ├─ 0xE000_0000 ─┤ Cortex-M 内部外设 │ │ NVIC、SysTick、MPU、调试组件 └─ 0xFFFF_FFFF ─┘二、软件层面代码是怎么跑起来的2.1 编译链接的幕后你写的 C 代码要变成芯片能懂的机器码经历以下过程main.c stm32f4xx_hal.c startup.s │ ▼ ┌─────────┐ │ 预编译 │ 处理 #include, #define └────┬────┘ ▼ ┌─────────┐ │ 编译器 │ 生成 .o 目标文件ARM Thumb 指令 │ (gcc) │ 每条指令 16/32 位 └────┬────┘ ▼ ┌─────────┐ │ 链接器 │ 合并所有 .o分配地址 │ (ld) │ 生成 .elf → .hex → .bin └────┬────┘ ▼ ┌─────────┐ │ .bin 文件│ 纯二进制直接烧录到 0x0800_0000 └─────────┘链接脚本.ld的关键作用链接器根据脚本决定.text段代码放在 Flash 的哪个地址.data段已初始化全局变量在 Flash 存初值运行时在 SRAM 使用.bss段未初始化变量在 SRAM 中预留空间启动时清零stack_top栈顶位置通常在 SRAM 最高地址2.2 中断与 NVICCortex-M 内核内置了NVICNested Vectored Interrupt Controller这是理解实时响应的关键。中断发生时的硬件自动操作仅需 12 个时钟周期 1. 当前指令执行完毕 2. 硬件自动压栈xPSR, PC, LR, R12, R3, R2, R1, R0共 32 字节 3. 从向量表取中断处理函数地址 4. 更新 LR 为特殊值 EXC_RETURN标识这是中断返回 5. 跳转到中断服务函数 (ISR) 6. ISR 执行完毕遇到 BX LR 7. 硬件自动出栈恢复原现场 8. 继续执行主程序中断优先级以 4 位优先级为例优先级寄存器 8 位只用高 4 位 抢占优先级 │ 子优先级 │ 说明 ───────────┼─────────┼───────────────── 0 │ 0-15 │ 最高抢占级可打断任何 1 │ 0-15 │ 次高抢占级 ... │ ... │ ... 15 │ 0-15 │ 最低抢占级 • 抢占优先级高的可以打断低的嵌套中断 • 子优先级仅在同时发生时决定谁先执行不能嵌套三、代码编写三大核心部分现在进入实战。STM32 的代码无论多复杂骨架都是这三部分第一部分系统时钟配置System Clock Configuration这是最先执行的配置决定整个芯片的心跳频率。// 以 STM32F407 为例使用 HAL 库voidSystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct{0};RCC_ClkInitTypeDef RCC_ClkInitStruct{0};// 1. 启用 HSE外部高速晶振 8MHzRCC_OscInitStruct.OscillatorTypeRCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEStateRCC_HSE_ON;// 开启 HSERCC_OscInitStruct.PLL.PLLStateRCC_PLL_ON;// 启用 PLLRCC_OscInitStruct.PLL.PLLSourceRCC_PLLSOURCE_HSE;// PLL 输入源选 HSE// PLL 配置8MHz / 8(M) * 336(N) / 2(P) 168MHzRCC_OscInitStruct.PLL.PLLM8;// 分频系数 MRCC_OscInitStruct.PLL.PLLN336;// 倍频系数 NRCC_OscInitStruct.PLL.PLLPRCC_PLLP_DIV2;// 系统时钟分频 PRCC_OscInitStruct.PLL.PLLQ7;// USB/SDIO 时钟分频 Q (168/748MHz)if(HAL_RCC_OscConfig(RCC_OscInitStruct)!HAL_OK){Error_Handler();}// 2. 配置总线分频RCC_ClkInitStruct.ClockTypeRCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSourceRCC_SYSCLKSOURCE_PLLCLK;// SYSCLK PLLRCC_ClkInitStruct.AHBCLKDividerRCC_SYSCLK_DIV1;// HCLK 168MHzRCC_ClkInitStruct.APB1CLKDividerRCC_HCLK_DIV4;// APB1 42MHz (TIM2-7)RCC_ClkInitStruct.APB2CLKDividerRCC_HCLK_DIV2;// APB2 84MHz (TIM1, USART1)if(HAL_RCC_ClockConfig(RCC_ClkInitStruct,FLASH_LATENCY_5)!HAL_OK){Error_Handler();}// 3. 配置 Flash 等待周期168MHz 时需要 5 个等待周期// 因为 Flash 本身速度慢CPU 需要等待 Flash 取指令__HAL_RCC_SYSCFG_CLK_ENABLE();}时钟配置的关键原理参数含义计算PLLMPLL 输入分频8MHz / 8 1MHz进入 VCO 的必须 1-2MHzPLLNVCO 倍频1MHz * 336 336MHzVCO 输出PLLP系统时钟分频336 / 2 168MHzSYSCLKPLLQUSB/SDIO 时钟336 / 7 48MHzUSB 必须严格 48MHz⚠️Flash Latency当 CPU 跑 168MHz 时Flash 访问跟不上通常最高 30MHz所以必须插入等待周期。CPU 取指令时会停顿几个周期这是硬件自动处理的。第二部分外设模式配置与初始化Peripheral Mode Initialization这是代码量最大、最体现对芯片理解的部分。每个外设使用前必须做三件事开时钟 → 配引脚 → 初始化外设。2.1 GPIO 配置最基础voidMX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct{0};// 1. 开 GPIO 时钟必须没时钟外设不工作__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();// 2. 配置 PD12-PD15 为输出通常连接开发板的 LEDGPIO_InitStruct.PinGPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;GPIO_InitStruct.ModeGPIO_MODE_OUTPUT_PP;// 推挽输出GPIO_InitStruct.PullGPIO_NOPULL;// 无上下拉GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_LOW;// 低速省电HAL_GPIO_Init(GPIOD,GPIO_InitStruct);// 3. 配置 PA0 为外部中断输入按键GPIO_InitStruct.PinGPIO_PIN_0;GPIO_InitStruct.ModeGPIO_MODE_IT_RISING;// 上升沿触发中断GPIO_InitStruct.PullGPIO_PULLDOWN;// 下拉默认低电平HAL_GPIO_Init(GPIOA,GPIO_InitStruct);// 4. 配置 NVIC中断优先级HAL_NVIC_SetPriority(EXTI0_IRQn,0,0);// 抢占优先级 0子优先级 0HAL_NVIC_EnableIRQ(EXTI0_IRQn);// 使能 EXTI0 中断线}GPIO 模式详解模式电路行为应用场景推挽输出 (PP)可主动输出高/低电平驱动能力强LED、片选信号开漏输出 (OD)只能拉低或释放需外部上拉I2C 总线、电平转换输入浮空高阻态读取引脚电平外部已确定电平的信号输入上拉/下拉内部电阻拉到 VCC/GND按键、未连接时的默认状态模拟输入直接连到 ADC数字功能关闭ADC 采样复用推挽/开漏引脚控制权交给外设USART/TIM/SPI串口 TX、PWM 输出2.2 USART 配置串口通信UART_HandleTypeDef huart1;voidMX_USART1_UART_Init(void){huart1.InstanceUSART1;// 使用 USART1挂在 APB2最高 84MHzhuart1.Init.BaudRate115200;// 波特率huart1.Init.WordLengthUART_WORDLENGTH_8B;// 8 位数据huart1.Init.StopBitsUART_STOPBITS_1;// 1 位停止huart1.Init.ParityUART_PARITY_NONE;// 无校验huart1.Init.ModeUART_MODE_TX_RX;// 全双工huart1.Init.HwFlowCtlUART_HWCONTROL_NONE;// 无硬件流控huart1.Init.OverSamplingUART_OVERSAMPLING_16;// 16 倍过采样if(HAL_UART_Init(huart1)!HAL_OK){Error_Handler();}// HAL_UART_Init() 内部会自动// 1. 开启 USART1 时钟// 2. 配置 PA9(TX) 和 PA10(RX) 为复用推挽模式// 3. 根据 APB2 时钟84MHz计算波特率分频系数84MHz/16/115200 ≈ 45.57// 4. 写入 BRR 寄存器启动 USART}2.3 TIM 定时器配置PWM 输出TIM_HandleTypeDef htim2;voidMX_TIM2_Init(void){TIM_ClockConfigTypeDef sClockSourceConfig{0};TIM_MasterConfigTypeDef sMasterConfig{0};TIM_OC_InitTypeDef sConfigOC{0};htim2.InstanceTIM2;// TIM2 在 APB1时钟 42MHzhtim2.Init.Prescaler42-1;// 预分频42MHz / 42 1MHzhtim2.Init.CounterModeTIM_COUNTERMODE_UP;// 向上计数htim2.Init.Period1000-1;// 周期1MHz / 1000 1kHz PWM 频率htim2.Init.ClockDivisionTIM_CLOCKDIVISION_DIV1;if(HAL_TIM_Base_Init(htim2)!HAL_OK){Error_Handler();}sClockSourceConfig.ClockSourceTIM_CLOCKSOURCE_INTERNAL;HAL_TIM_ConfigClockSource(htim2,sClockSourceConfig);HAL_TIM_PWM_Init(htim2);sConfigOC.OCModeTIM_OCMODE_PWM1;// PWM 模式 1sConfigOC.Pulse500;// 占空比 50% (500/1000)sConfigOC.OCPolarityTIM_OCPOLARITY_HIGH;sConfigOC.OCFastModeTIM_OCFAST_DISABLE;HAL_TIM_PWM_ConfigChannel(htim2,sConfigOC,TIM_CHANNEL_1);// 启动 PWMHAL_TIM_PWM_Start(htim2,TIM_CHANNEL_1);}第三部分主程序逻辑与中断处理Main Loop ISR这是代码的灵魂分为两个运行域3.1 主循环轮询方式intmain(void){// 1. HAL 库初始化HAL_Init();// 2. 系统时钟配置第一部分SystemClock_Config();// 3. 外设初始化第二部分MX_GPIO_Init();MX_USART1_UART_Init();MX_TIM2_Init();uint8_ttx_buf[]Hello STM32!\r\n;uint8_trx_buf[64];// 4. 主循环第三部分核心while(1){// 方式 1轮询发送阻塞发送完才返回HAL_UART_Transmit(huart1,tx_buf,sizeof(tx_buf),1000);HAL_Delay(500);// 阻塞延时 500ms// 方式 2轮询读取按键效率低CPU 一直忙等if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)GPIO_PIN_SET){HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12);// 翻转 LED}// 实际项目中主循环通常做// • 状态机处理// • 非关键任务调度// • 看门狗喂狗}}3.2 中断方式高效非阻塞// 中断回调函数弱定义需要用户重写voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_PinGPIO_PIN_0){// 按键中断翻转 LEDHAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12);}}// 串口接收中断回调voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){// 收到一字节数据处理...// 重新启动接收中断接收是一次性的HAL_UART_Receive_IT(huart1,rx_buf,1);}}intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// 启动中断接收非阻塞HAL_UART_Receive_IT(huart1,rx_buf,1);while(1){// CPU 可以做其他事数据来了自动进中断处理// 低功耗模式可以在这里进入 Sleep 模式中断自动唤醒HAL_SuspendTick();HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);HAL_ResumeTick();}}3.3 DMA 方式最高效零 CPU 干预DMA_HandleTypeDef hdma_usart1_tx;voidMX_DMA_Init(void){__HAL_RCC_DMA2_CLK_ENABLE();// DMA2 时钟hdma_usart1_tx.InstanceDMA2_Stream7;// USART1_TX 在 DMA2 Stream7hdma_usart1_tx.Init.ChannelDMA_CHANNEL_4;// 通道 4hdma_usart1_tx.Init.DirectionDMA_MEMORY_TO_PERIPH;// 内存到外设hdma_usart1_tx.Init.PeriphIncDMA_PINC_DISABLE;// 外设地址不递增hdma_usart1_tx.Init.MemIncDMA_MINC_ENABLE;// 内存地址递增hdma_usart1_tx.Init.PeriphDataAlignmentDMA_PDATAALIGN_BYTE;hdma_usart1_tx.Init.MemDataAlignmentDMA_MDATAALIGN_BYTE;hdma_usart1_tx.Init.ModeDMA_NORMAL;// 正常模式非循环hdma_usart1_tx.Init.PriorityDMA_PRIORITY_LOW;HAL_DMA_Init(hdma_usart1_tx);__HAL_LINKDMA(huart1,hdmatx,hdma_usart1_tx);// 关联到 UART}// 使用发送 1024 字节CPU 只发起一次DMA 自动搬运HAL_UART_Transmit_DMA(huart1,big_buffer,1024);// 发送完成触发中断回调voidHAL_UART_TxCpltCallback(UART_HandleTypeDef*huart){// 发送完成可以发送下一包}四、深入底层寄存器级别的真相HAL 库虽然方便但理解寄存器才能真正掌握 STM32。以 GPIO 为例// HAL 库写法HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,GPIO_PIN_SET);// 对应的寄存器操作完全等价GPIOD-BSRRGPIO_PIN_12;// 置位寄存器写 1 置高电平// 更底层的地址直接操作*(volatileuint32_t*)(0x40020C18)0x00001000;// GPIOD_BSRR 地址GPIO 寄存器映射以 GPIOD 为例基地址 0x4002 0C00寄存器偏移功能MODER0x00模式寄存器00输入, 01输出, 10复用, 11模拟OTYPER0x04输出类型0推挽, 1开漏OSPEEDR0x08输出速度002MHz, 0125MHz, 1050MHz, 11100MHzPUPDR0x0C上下拉00浮空, 01上拉, 10下拉IDR0x10输入数据寄存器只读ODR0x14输出数据寄存器BSRR0x18置位/复位寄存器高 16 位写 1 复位低 16 位写 1 置位LCKR0x1C配置锁定寄存器AFR[2]0x20复用功能选择AFR[0]低 8 位引脚AFR[1]高 8 位为什么用 BSRR 而不是直接写 ODR因为BSRR是原子操作一条指令完成而ODR需要读-改-写三条指令。如果在读 ODR 和写 ODR 之间发生了中断修改了同一端口的其他引脚就会造成脏写。BSRR 避免了这个问题。五、总结STM32 代码的三层架构┌─────────────────────────────────────────┐ │ 第三层应用层 │ │ • main() 主循环 │ │ • 中断服务函数 (ISR) │ │ • 业务逻辑、状态机、协议解析 │ │ • DMA 回调处理 │ ├─────────────────────────────────────────┤ │ 第二层外设配置层你忘了的部分 │ │ • GPIO_Mode/Pull/Speed 配置 │ │ • USART_BaudRate/Parity 配置 │ │ • TIM_Prescaler/Period/Pulse 配置 │ │ • NVIC_Priority 配置 │ │ • DMA_Channel/Mode 配置 │ ├─────────────────────────────────────────┤ │ 第一层系统层 │ │ • RCC 时钟树配置PLL/HSE/分频 │ │ • Flash Latency 配置 │ │ • 电源管理Voltage Scale │ │ • 系统滴答定时器SysTick │ ├─────────────────────────────────────────┤ │ 第零层启动层隐藏但关键 │ │ • startup.s 汇编启动文件 │ │ • SystemInit() 系统初始化 │ │ • 向量表Vector Table │ │ • .data/.bss 段初始化 │ └─────────────────────────────────────────┘六、写给初学者的建议先理解时钟如果外设不工作90% 是因为没开时钟或时钟配错。学会看参考手册Reference Manual比教程权威一万倍每个寄存器的每一位都有说明。从寄存器开始再用 HAL先写几个寄存器版本的 GPIO 点灯再回来看 HAL 库你会恍然大悟。重视中断优先级项目做大了中断嵌套和优先级反转是调试的噩梦。善用 DMA只要涉及批量数据传输优先考虑 DMA解放 CPU 去做更重要的事。博客结语STM32 的底层并不神秘它只是一堆精心设计的数字电路通过总线连接通过寄存器控制。当你理解了时钟树怎么让芯片心跳、总线矩阵怎么搬运数据、NVIC 怎么响应外界事件你就掌握了打开任何一款 ARM 芯片的钥匙。从 F1 到 F4从 F4 到 H7万变不离其宗。本文基于 STM32F4 系列撰写原理适用于所有 Cortex-M 内核的 STM32 芯片。如有错误欢迎指正交流。
返回列表