QEMU固件模拟技术-stm32仿真分析及IRQ仿真实践
字数 912 2025-08-10 00:23:58
QEMU固件模拟技术:STM32仿真分析及IRQ仿真实践
概述
本文基于STM32F205-SOC的实现,详细介绍QEMU中断仿真技术,包括QEMU中断模型、STM32外设仿真实现以及如何开发裸板程序测试中断仿真。主要内容包括:
- STM32F205-SOC的QEMU实现分析
- QEMU中断模型原理
- 裸板程序开发与中断测试实践
- NVIC中断控制器配置要点
STM32F205-SOC实现分析
内存映射初始化
STM32F205-SOC在QEMU中实现了三个关键内存区域:
MemoryRegion *system_memory = get_system_memory();
MemoryRegion *sram = g_new(MemoryRegion, 1);
MemoryRegion *flash = g_new(MemoryRegion, 1);
MemoryRegion *flash_alias = g_new(MemoryRegion, 1);
// 初始化Flash区域 (0x8000000)
memory_region_init_ram(flash, NULL, "STM32F205.flash", FLASH_SIZE, &error_fatal);
// 初始化Flash别名区域 (0x0)
memory_region_init_alias(flash_alias, NULL, "STM32F205.flash.alias", flash, 0, FLASH_SIZE);
// 设置只读属性
memory_region_set_readonly(flash, true);
memory_region_set_readonly(flash_alias, true);
// 映射到系统内存
memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, flash);
memory_region_add_subregion(system_memory, 0, flash_alias);
// 初始化SRAM区域 (0x20000000)
memory_region_init_ram(sram, NULL, "STM32F205.sram", SRAM_SIZE, &error_fatal);
memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram);
外设初始化 - 以UART为例
UART设备初始化流程:
- 创建UART设备对象
- 设置字符设备属性
- 实现设备
- 映射MMIO区域
- 连接中断
for (i = 0; i < STM_NUM_USARTS; i++) {
// 创建设备
dev = DEVICE(&(s->usart[i]));
qdev_prop_set_chr(dev, "chardev", serial_hd(i));
// 实现设备
object_property_set_bool(OBJECT(&s->usart[i]), true, "realized", &err);
// 映射MMIO
busdev = SYS_BUS_DEVICE(dev);
sysbus_mmio_map(busdev, 0, usart_addr[i]);
// 连接中断
sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
}
UART设备MMIO地址:
static const uint32_t usart_addr[STM_NUM_USARTS] = {
0x40011000, 0x40004400, 0x40004800,
0x40004C00, 0x40005000, 0x40011400
};
UART中断号:
static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39, 52, 53, 71};
QEMU中断模型详解
中断模型架构
QEMU使用GPIO实现中断系统,架构如下:
Device.[GPIO_OUT] ->[GPIO_IN].GIC.[GPIO_OUT]->[GPIO_IN].core
关键数据结构:
struct IRQState {
Object parent_obj;
qemu_irq_handler handler; // irq处理函数
void *opaque;
int n; // irq的编号
};
typedef struct IRQState *qemu_irq;
中断相关API
- 触发中断:
void qemu_set_irq(qemu_irq irq, int level) {
if (!irq) return;
irq->handler(irq->opaque, irq->n, level);
}
- 初始化GPIO输入:
void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n)
- 初始化GPIO输出:
void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p)
- 连接GPIO:
void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq)
中断号与异常向量表
在STM32F205中:
IRQ号 = 异常向量表中的序号 - CPU内置异常数目(16)
例如:
- 异常向量表第17号中断 → IRQ号 = 17 - 16 = 1
裸板程序开发与中断测试
STM32F205-SOC修改
添加自定义中断测试设备:
// 全局中断处理函数指针
qemu_irq stm32f2xx_irq_demo_handler = NULL;
#define IRQ_DEMO_BASE 0x88990000
// MMIO写回调
static void stm32f2xx_irq_demo_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size) {
qemu_set_irq(stm32f2xx_irq_demo_handler, 1);
return;
}
// MMIO操作集
static const MemoryRegionOps stm32f2xx_irq_demo_ops = {
.write = stm32f2xx_irq_demo_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
// 在realize函数中添加
memory_region_init_io(demo_mem, NULL, &stm32f2xx_irq_demo_ops, s,
"irq-demo-mmio", 0x1000);
memory_region_add_subregion(system_memory, IRQ_DEMO_BASE, demo_mem);
// 获取IRQ20的处理函数
stm32f2xx_irq_demo_handler = qdev_get_gpio_in(armv7m, 20);
裸板程序实现
异常向量表
.section .isr_vector, "a"
g_pfnVectors:
.word stack_top
.word Reset_Handler
.word Default_Handler // NMI
.word Default_Handler // HardFault
.word Default_Handler // MemManage
.word Default_Handler // BusFault
.word Default_Handler // UsageFault
.word 0
.word 0
.word 0
.word 0
.word Default_Handler // SVC
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word demo_irq_handler // IRQ20处理函数
中断处理程序
.thumb_func
Reset_Handler:
bl main_func
b .
.thumb_func
demo_irq_handler:
bl demo_irq_func
b .
C语言实现
#define USART1_BASE_ADDR 0x40011000
#define USART_DR 0x04
#define IRQ_DEMO_BASE 0x88990000
#define NVIC_MEM_BASE 0xe000e000
// 串口输出函数
void print_func(unsigned char* s) {
while (*s != 0) {
*(volatile unsigned char*)(USART1_BASE_ADDR + USART_DR) = *s;
s++;
}
}
// 启用IRQ20中断
void enable_demo_irq() {
unsigned int irq = 20 + 16;
unsigned int offset = (irq - 16) / 8;
offset += 0x180;
offset -= 0x80;
*(volatile unsigned char*)(NVIC_MEM_BASE + offset) = 1 << 4;
}
// 主函数
void main_func() {
print_func("main_func!\n");
enable_demo_irq(); // 启用IRQ20
// 触发中断
*(volatile unsigned int*)(IRQ_DEMO_BASE + 4) = 33;
print_func("end main_func!\n");
return;
}
// 中断处理函数
void demo_irq_func() {
print_func("demo_irq_func!\n");
return;
}
测试运行
使用QEMU加载固件:
qemu-system-arm -M netduino2 -kernel startup.bin -nographic
预期输出:
main_func!
demo_irq_func!
NVIC中断控制器关键点
- 中断启用:系统启动时NVIC中所有中断默认禁用,必须手动启用:
void enable_demo_irq() {
unsigned int irq = 20 + 16;
unsigned int offset = (irq - 16) / 8;
offset += 0x180;
offset -= 0x80;
*(volatile unsigned char*)(NVIC_MEM_BASE + offset) = 1 << 4;
}
-
NVIC寄存器布局:
- 启用中断寄存器位于
0xE000E100-0xE000E11F - 禁用中断寄存器位于
0xE000E180-0xE000E19F
- 启用中断寄存器位于
-
中断优先级:可通过NVIC的优先级寄存器设置中断优先级
总结
本文详细分析了:
- QEMU中STM32F205-SOC的实现原理
- QEMU中断模型的工作机制
- 如何开发裸板程序测试中断仿真
- NVIC中断控制器的关键配置
关键要点:
- QEMU使用GPIO模型实现中断系统
- 必须正确配置NVIC才能处理外设中断
- 中断号与异常向量表位置有固定换算关系
- 裸板程序需要正确实现异常向量表和处理函数