MCU架构
ARM Cortex-M系列
各型号对比
| 特性 | Cortex-M0 | Cortex-M3 | Cortex-M4 | Cortex-M7 |
|---|---|---|---|---|
| 架构 | ARMv6-M | ARMv7-M | ARMv7E-M | ARMv7E-M |
| 主频 | 50MHz | 120MHz | 180MHz | 400MHz+ |
| 流水线 | 3级 | 3级 | 3级 | 6级双发射 |
| 中断 | 32 | 240 | 240 | 240 |
| FPU | 无 | 无 | 可选 | 可选(DP) |
| DSP | 无 | 无 | 有 | 增强 |
| Cache | 无 | 无 | 无 | I/D Cache |
| MPU | 可选 | 可选 | 可选 | 标配 |
| 性能 | 0.9 DMIPS/MHz | 1.25 | 1.25 | 2.14 |
| 应用 | 简单控制 | 通用 | 音频/电机 | 高性能 |
指令集差异
// Cortex-M0:仅支持Thumb子集(56条指令)
uint32_t divide(uint32_t a, uint32_t b) {
// 无硬件除法,编译器生成库调用
return a / b; // 调用__aeabi_uidiv
}
// Cortex-M3+:支持完整Thumb-2(含除法指令)
uint32_t divide_m3(uint32_t a, uint32_t b) {
// UDIV指令,1个周期
return a / b;
}
// Cortex-M4:支持DSP指令
int16_t saturate_add(int16_t a, int16_t b) {
// QADD指令,饱和加法
int32_t result = a + b;
if (result > 32767) return 32767;
if (result < -32768) return -32768;
return result;
}
FPU浮点运算单元
// 使能FPU(Cortex-M4/M7)
void FPU_Enable(void) {
// CPACR: Coprocessor Access Control Register
SCB->CPACR |= (3UL << 20) | (3UL << 22); // CP10, CP11 full access
}
// 浮点运算性能对比
void benchmark(void) {
float a = 1.5f, b = 2.5f, c;
// 无FPU:软件模拟,约100-200个周期
// 有FPU:硬件执行,约3-10个周期
c = a * b + sqrtf(a);
}
// FPU寄存器
// S0-S31: 单精度浮点寄存器(32bit)
// D0-D15: 双精度浮点寄存器(64bit, M7支持)
// FPSCR: 浮点状态和控制寄存器
寄存器架构
通用寄存器
R0-R12: 通用寄存器
R13(SP): 堆栈指针
- MSP: 主堆栈指针(中断/特权模式)
- PSP: 进程堆栈指针(任务模式)
R14(LR): 链接寄存器(保存返回地址)
R15(PC): 程序计数器
// 读取特殊寄存器
uint32_t get_MSP(void) {
uint32_t result;
__asm volatile ("MRS %0, MSP" : "=r" (result));
return result;
}
uint32_t get_PSP(void) {
uint32_t result;
__asm volatile ("MRS %0, PSP" : "=r" (result));
return result;
}
// 获取PC值
uint32_t get_PC(void) {
uint32_t result;
__asm volatile ("MOV %0, PC" : "=r" (result));
return result;
}
程序状态寄存器(PSR)
typedef struct {
uint32_t N : 1; // Negative flag
uint32_t Z : 1; // Zero flag
uint32_t C : 1; // Carry flag
uint32_t V : 1; // Overflow flag
uint32_t Q : 1; // Saturation flag
// ...
} PSR_Flags;
// 读取APSR(应用程序状态寄存器)
uint32_t get_APSR(void) {
uint32_t result;
__asm volatile ("MRS %0, APSR" : "=r" (result));
return result;
}
外设寄存器映射
// STM32外设基地址
#define PERIPH_BASE 0x40000000UL
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
// GPIO寄存器结构
typedef struct {
volatile uint32_t MODER;
volatile uint32_t OTYPER;
volatile uint32_t OSPEEDR;
volatile uint32_t PUPDR;
volatile uint32_t IDR;
volatile uint32_t ODR;
volatile uint32_t BSRR;
volatile uint32_t LCKR;
volatile uint32_t AFR[2];
} GPIO_TypeDef;
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000UL)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
// 使用示例
void set_PA5_high(void) {
GPIOA->BSRR = (1 << 5); // 原子操作
}
时钟树
STM32时钟系统
[PLL] ×9
HSE(8MHz) ─┬─────────────┴──────→ SYSCLK(72MHz)
│ │
HSI(8MHz) ─┘ ├→ AHB Prescaler(/1) → HCLK(72MHz)
│ ├→ Cortex系统定时器
│ ├→ DMA
│ └→ GPIO
│
├→ APB1 Prescaler(/2) → PCLK1(36MHz)
│ ├→ TIM2-7
│ ├→ USART2-3
│ └→ I2C1-2
│
└→ APB2 Prescaler(/1) → PCLK2(72MHz)
├→ TIM1,8
├→ USART1
├→ ADC1-2
└→ SPI1
时钟配置代码
void SystemClock_Config(void) {
// 1. 使能HSE
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE就绪
// 2. 配置Flash延迟(72MHz需要2等待周期)
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
// 3. 配置PLL:HSE×9 = 72MHz
RCC->CFGR = RCC_CFGR_PLLSRC | // PLL源=HSE
RCC_CFGR_PLLMULL9 | // PLL倍频×9
RCC_CFGR_HPRE_DIV1 | // AHB不分频
RCC_CFGR_PPRE1_DIV2 | // APB1÷2
RCC_CFGR_PPRE2_DIV1; // APB2不分频
// 4. 使能PLL
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
// 5. 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
// 获取系统时钟频率
uint32_t SystemCoreClock = 72000000;
void SystemCoreClockUpdate(void) {
uint32_t pll_mul, pll_src;
switch (RCC->CFGR & RCC_CFGR_SWS) {
case RCC_CFGR_SWS_HSI: // HSI
SystemCoreClock = HSI_VALUE;
break;
case RCC_CFGR_SWS_HSE: // HSE
SystemCoreClock = HSE_VALUE;
break;
case RCC_CFGR_SWS_PLL: // PLL
pll_mul = ((RCC->CFGR & RCC_CFGR_PLLMULL) >> 18) + 2;
pll_src = (RCC->CFGR & RCC_CFGR_PLLSRC) ? HSE_VALUE : HSI_VALUE/2;
SystemCoreClock = pll_src * pll_mul;
break;
}
}
电源管理
低功耗模式
| 模式 | 功耗 | 唤醒时间 | 保留内容 | 唤醒源 |
|---|---|---|---|---|
| Run | 正常 | - | 全部 | - |
| Sleep | 低 | 快(us) | 全部 | 任何中断 |
| Stop | 很低 | 中(us) | SRAM/寄存器 | EXTI/RTC/IWDG |
| Standby | 极低 | 慢(ms) | 仅备份寄存器 | WKUP/RTC/IWDG |
// Sleep模式
void enter_sleep_mode(void) {
// WFI: Wait For Interrupt
__WFI();
// 任何中断唤醒,继续执行
}
// Stop模式
void enter_stop_mode(void) {
// 清除唤醒标志
PWR->CR |= PWR_CR_CWUF;
// 进入Stop模式
PWR->CR |= PWR_CR_LPDS; // 低功耗深睡眠
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
// 唤醒后需重新配置时钟
SystemClock_Config();
}
// Standby模式
void enter_standby_mode(void) {
// 使能电源时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 清除唤醒标志
PWR->CR |= PWR_CR_CWUF;
// 使能WKUP引脚唤醒
PWR->CSR |= PWR_CSR_EWUP;
// 进入Standby模式
PWR->CR |= PWR_CR_PDDS; // Power Down Deep Sleep
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
// 唤醒后从Reset Handler重新启动
}
// RTC唤醒
void RTC_WakeUp_Config(uint16_t seconds) {
// 配置RTC闹钟
// ...
// 进入低功耗模式,RTC闹钟唤醒
}
低功耗设计技巧
// 1. 关闭未使用的外设时钟
void disable_unused_peripherals(void) {
RCC->APB1ENR &= ~(RCC_APB1ENR_TIM2EN |
RCC_APB1ENR_TIM3EN |
RCC_APB1ENR_TIM4EN);
}
// 2. 降低系统时钟频率
void reduce_clock_speed(void) {
// 切换到HSI 8MHz
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_HSI;
}
// 3. 使用DMA减少CPU唤醒次数
void setup_dma_uart(void) {
// DMA自动传输,CPU可保持睡眠
}
// 4. 配置GPIO为模拟输入减少漏电流
void configure_unused_gpio(void) {
GPIOC->MODER = 0xFFFFFFFF; // 所有引脚为模拟输入
}
DMA直接内存访问
DMA传输模式
// DMA传输方向
typedef enum {
DMA_PERIPH_TO_MEMORY = 0, // 外设到内存
DMA_MEMORY_TO_PERIPH = 1, // 内存到外设
DMA_MEMORY_TO_MEMORY = 2 // 内存到内存
} DMA_Direction;
// DMA配置结构
typedef struct {
uint32_t PeriphAddr; // 外设地址
uint32_t MemoryAddr; // 内存地址
uint32_t Direction; // 传输方向
uint32_t BufferSize; // 缓冲区大小
uint32_t PeriphInc; // 外设地址递增
uint32_t MemoryInc; // 内存地址递增
uint32_t PeriphDataSize; // 外设数据宽度
uint32_t MemoryDataSize; // 内存数据宽度
uint32_t Mode; // 正常/循环模式
uint32_t Priority; // 优先级
} DMA_InitTypeDef;
// ADC+DMA连续采集
#define ADC_BUFFER_SIZE 100
uint16_t adc_buffer[ADC_BUFFER_SIZE];
void ADC_DMA_Init(void) {
// 配置DMA通道1
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR; // 外设地址
DMA1_Channel1->CMAR = (uint32_t)adc_buffer; // 内存地址
DMA1_Channel1->CNDTR = ADC_BUFFER_SIZE; // 传输数量
DMA1_Channel1->CCR = DMA_CCR_MINC | // 内存地址递增
DMA_CCR_CIRC | // 循环模式
DMA_CCR_TCIE | // 传输完成中断
DMA_CCR_EN; // 使能DMA
// 使能ADC DMA请求
ADC1->CR2 |= ADC_CR2_DMA;
}
// DMA传输完成中断
void DMA1_Channel1_IRQHandler(void) {
if (DMA1->ISR & DMA_ISR_TCIF1) {
DMA1->IFCR = DMA_IFCR_CTCIF1; // 清除标志
// 处理采集到的数据
process_adc_data(adc_buffer, ADC_BUFFER_SIZE);
}
}
内存到内存传输
void DMA_MemCopy(uint32_t *src, uint32_t *dst, uint32_t len) {
// 禁用DMA通道
DMA2_Channel1->CCR &= ~DMA_CCR_EN;
// 配置源和目标
DMA2_Channel1->CPAR = (uint32_t)src;
DMA2_Channel1->CMAR = (uint32_t)dst;
DMA2_Channel1->CNDTR = len;
// 配置传输参数
DMA2_Channel1->CCR = DMA_CCR_MEM2MEM | // 内存到内存
DMA_CCR_MINC | // 内存地址递增
DMA_CCR_PINC | // 外设地址递增
DMA_CCR_PSIZE_1 | // 32位
DMA_CCR_MSIZE_1 | // 32位
DMA_CCR_EN;
// 等待传输完成
while (!(DMA2->ISR & DMA_ISR_TCIF1));
DMA2->IFCR = DMA_IFCR_CTCIF1;
}
Flash与EEPROM
Flash操作
// Flash编程步骤
void Flash_Write(uint32_t addr, uint16_t data) {
// 1. 解锁Flash
if (FLASH->CR & FLASH_CR_LOCK) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
// 2. 等待上一次操作完成
while (FLASH->SR & FLASH_SR_BSY);
// 3. 使能编程
FLASH->CR |= FLASH_CR_PG;
// 4. 写入半字(16bit)
*((volatile uint16_t*)addr) = data;
// 5. 等待完成
while (FLASH->SR & FLASH_SR_BSY);
// 6. 检查错误
if (FLASH->SR & FLASH_SR_EOP) {
FLASH->SR = FLASH_SR_EOP; // 清除标志
}
// 7. 禁用编程
FLASH->CR &= ~FLASH_CR_PG;
}
// Flash擦除(页擦除)
void Flash_ErasePage(uint32_t page_addr) {
FLASH->CR |= FLASH_CR_PER; // 页擦除模式
FLASH->AR = page_addr; // 页地址
FLASH->CR |= FLASH_CR_STRT; // 开始擦除
while (FLASH->SR & FLASH_SR_BSY);
FLASH->CR &= ~FLASH_CR_PER;
}
// 保存配置参数
#define CONFIG_ADDR 0x0801F000 // 最后一页
typedef struct {
uint16_t magic; // 0xA55A
uint16_t brightness;
uint16_t volume;
uint16_t checksum;
} Config_t;
void save_config(Config_t *config) {
config->magic = 0xA55A;
config->checksum = calculate_checksum(config);
Flash_ErasePage(CONFIG_ADDR);
uint16_t *p = (uint16_t*)config;
for (int i = 0; i < sizeof(Config_t)/2; i++) {
Flash_Write(CONFIG_ADDR + i*2, p[i]);
}
}
void load_config(Config_t *config) {
memcpy(config, (void*)CONFIG_ADDR, sizeof(Config_t));
if (config->magic != 0xA55A) {
// 使用默认配置
config->brightness = 50;
config->volume = 80;
}
}
IAP在线升级
// 内存分区
// 0x08000000 - 0x08003FFF: Bootloader (16KB)
// 0x08004000 - 0x0801FFFF: Application (112KB)
#define APP_START_ADDR 0x08004000
// Bootloader跳转到应用程序
void jump_to_app(void) {
uint32_t app_stack = *((uint32_t*)APP_START_ADDR);
uint32_t app_entry = *((uint32_t*)(APP_START_ADDR + 4));
// 检查栈指针有效性
if ((app_stack & 0x2FFE0000) != 0x20000000) {
return; // 无效的应用程序
}
// 关闭全局中断
__disable_irq();
// 关闭SysTick
SysTick->CTRL = 0;
// 设置向量表偏移
SCB->VTOR = APP_START_ADDR;
// 设置栈指针
__set_MSP(app_stack);
// 跳转到应用程序
void (*app_reset_handler)(void) = (void*)app_entry;
app_reset_handler();
}
启动流程
// 1. 复位向量表(startup_stm32.s)
__Vectors:
DCD __initial_sp // 栈顶地址
DCD Reset_Handler // 复位处理函数
DCD NMI_Handler
DCD HardFault_Handler
// ...
// 2. Reset Handler
void Reset_Handler(void) {
// 复制.data段从Flash到RAM
extern uint32_t _sdata, _edata, _sidata;
uint32_t *src = &_sidata;
uint32_t *dst = &_sdata;
while (dst < &_edata) {
*dst++ = *src++;
}
// 清零.bss段
extern uint32_t _sbss, _ebss;
dst = &_sbss;
while (dst < &_ebss) {
*dst++ = 0;
}
// 调用SystemInit
SystemInit();
// 调用C++全局对象构造函数
__libc_init_array();
// 调用main函数
main();
// main不应返回
while (1);
}
// 3. SystemInit(初始化时钟等)
void SystemInit(void) {
// 配置FPU
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= (3UL << 20) | (3UL << 22);
#endif
// 设置向量表位置
SCB->VTOR = FLASH_BASE;
// 配置系统时钟
SystemClock_Config();
}
高频面试题
1. Cortex-M3和M4的主要区别是什么?
答案:
核心区别在于DSP和FPU:
- M3: 无DSP指令,无FPU
- M4: 有DSP指令集(饱和运算、SIMD、MAC),可选FPU
应用场景:
- M3: 通用控制(工控、智能家居)
- M4: 音频处理、电机控制、数字滤波
性能对比:
// FFT运算:M4(带FPU)比M3快10倍以上
// 浮点乘加:M4只需3周期,M3需要软件模拟(约100周期)
2. 如何降低MCU的功耗?
答案:
综合策略:
- 时钟优化: 降低主频,关闭未用外设时钟
- 睡眠模式: 空闲时进入Sleep/Stop模式
- GPIO配置: 未使用引脚配置为模拟输入
- 外设优化: 使用DMA减少CPU唤醒,使用RTC定时唤醒
- 电源域: 关闭未用电源域
- 硬件设计: 外部上拉/下拉改用内部,选择低功耗器件
实测数据(STM32L系列):
- Run(32MHz): 10mA
- Sleep: 3mA
- Stop: 10uA
- Standby: 1uA
3. DMA和CPU搬运数据的优缺点?
答案:
| 特性 | CPU | DMA |
|---|---|---|
| CPU占用 | 100% | 0% |
| 速度 | 受限于指令执行 | 直接访问总线 |
| 灵活性 | 高(可处理数据) | 低(仅搬运) |
| 功耗 | 高 | 低 |
| 适用场景 | 需要处理的数据 | 大块数据传输 |
使用原则:
- 大量数据(>64字节)使用DMA
- 需要处理/转换数据使用CPU
- 低功耗场景优先DMA
4. Flash为什么要先擦除后编程?
答案:
Flash存储原理:
- 编程: 只能将1变为0(电子注入浮栅)
- 擦除: 将0变为1(电子移出浮栅)
- 无法直接将0变1,必须整块擦除
擦除单位:
- NOR Flash: 扇区擦除(4KB-64KB)
- NAND Flash: 块擦除(128KB-256KB)
编程注意事项:
- 擦除次数有限(1万-10万次)
- 擦除耗时长(毫秒级)
- 编程时需禁用中断
- 掉电保护(使用双备份+校验)
5. 如何实现Bootloader跳转?
答案:
关键步骤:
void jump_to_app(uint32_t app_addr) {
// 1. 检查栈顶地址有效性
uint32_t sp = *(uint32_t*)app_addr;
if ((sp & 0x2FFE0000) != 0x20000000) {
return; // 无效
}
// 2. 关闭所有外设和中断
HAL_DeInit();
__disable_irq();
SysTick->CTRL = 0;
// 3. 重置向量表
SCB->VTOR = app_addr;
// 4. 设置栈指针
__set_MSP(sp);
// 5. 跳转
uint32_t pc = *(uint32_t*)(app_addr + 4);
void (*app_entry)(void) = (void*)pc;
app_entry();
}
注意事项:
- App链接脚本需设置正确的起始地址
- App启动代码需设置SCB->VTOR
- 可通过按键/超时/固件标志触发跳转