单片机到底是如何软硬件结合的

时间:2025-04-28  作者:Diven  阅读:0

 

单片机到底是如何软硬件结合的

我们通过IO和串口的软件开发,已经体验了嵌入式软件开发。不知道大家有没有疑惑,为什么软件能控制硬件?反正当年我学习51的时候,有这个疑惑。今天我们就暂停软件开发,分析单片机到底是如何软硬件结合的。并通过一个基本的程序,分析单片机程序的编译,运行。

软硬件结合

初学者,通常有一个困惑,就是为什么软件能控制硬件?就像当年的51,为什么只要写P1=0X55,就可以在IO口输出高低电平?要理清这个问题,先要认识一个概念:地址空间。

寻址空间

什么是地址空间呢?所谓的地址空间,就是PC指针的寻址范围,因此也叫寻址空间。

大家应该都知道,我们的电脑有32位系统和64位系统之分,为什么呢?因为32位系统,PC指针就是一个32位的二进制数,也就是0xffffffff,范围只有4G寻址空间。现在内存越来越大,4G根本不够,所以需要扩展,为了能访问超出4G范围的内存,就有了64位系统。STM32是多少位的?是32位的,因此PC指针也是32位,寻址空间也就是4G。

我们来看看STM32的寻址空间是怎么样的。在数据手册《STM32F407_数据手册.pdf》中有一个图,这个图,就是STM32的寻址空间分配。所有的芯片,都会有这个图,名字基本上都是叫Memory map,用一个新芯片,就先看这个图。

  • 最左边,8个block,每个block 512M,总共就是4G,也就是芯片的寻址空间。

  • block 0 里面有一段叫做FLASH,也就是内部FLASH,我们的程序就是下载到这个地方,起始地址是0X800 0000,大家注意,这个只有1M空间。现在STM32已经有2M flash的芯片了,超出1M的FLASH放在哪里呢?请自行查看对应的芯片手册。

  • 3 在block 1 内,有两段SRAM,总共128K,这个空间,也就是我们前面说的内存,存放程序使用的变量。如果需要,也可以把程序放到SRAM中运行。407不是有196K吗?

  • 其实407有196K内存,但是有64k并不是普通的SRAM,而是放在block 0 内的CCM。这两段区域不连续,而且,CCM只能内核使用,外设不能使用,例如DMA就不能用CCM内存,否则就死机。

  • block 2,是Peripherals,也就是外设空间。我们看右边,主要就是APB1/APB2、AHB1/AHB2,什么东西呢?回头再说。

  • block 3、block4、block5,是FSMC的空间,FSMC可以外扩SRAM,NAND FALSH,LCD等外设。

好的,我们分析了寻址空间,我们回过头看看,软件是如何控制硬件的。在IO口输出的例程中,我们配置IO口是调用库函数,我们看看库函数是怎么做的。

例如:

GPIO_SetBits(GPIOG, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2| GPIO_Pin_3);

这个函数其实就是对一个变量赋值,对GPIOx这个结构体的成员BSRRL赋值。

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){  assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BSRRL = GPIO_Pin;}

assert_param:这个是断言,用于判断输入参数是否符合要求GPIOx是一个输入参数,是一个GPIO_TypeDef结构体指针,所以,要用->获取其成员

GPIOx是我们传入的参数GPIOG,具体是啥?在stm32f4xx.h中有定义。

#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)

GPIOG_BASE同样在文件中有定义,如下:

#define GPIOG_BASE           (AHB1PERIPH_BASE + 0x1800)AHB1PERIPH_BASE,AHB1地址,有点眉目了吧?在进一步看看#define APB1PERIPH_BASE       PERIPH_BASE#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000)#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x10000000)

再找找PERIPH_BASE的定义

#define PERIPH_BASE           ((uint32_t)0x40000000)    

 

到这里,我们可以看出,操作IO口G,其实就是操作0X40000000+0X1800这个地址上的一个结构体里面的成员。说白了,就是操作了这个地方的寄存器。实质跟我们操作普通变量一样,就像下面的两句代码,区别就是变量i是SRAM空间地址,0X40000000+0X1800是外设空间地址。

u32 i;i = 0x55aa55aa;

这个外设空间地址的寄存器是IO口硬件的一部分。如下图,左边的输出数据寄存器,就是我们操作的寄存器(内存、变量),地址就是0X40000000+0X1800+0x14.

控制其外设也类似,就是将数据写到外设寄存器上,跟操作内存一样,就可控制外设了。

寄存器,其实应该是内存的统称,外设寄存器应该叫做特殊寄存器。慢慢的,所有人都把外设的叫做寄存器,其统称内存或RAM。寄存器为什么能控制硬件外设呢?因为,略的说,一个寄存器的一个BIT,就是一个开关,开就是1,关就是0。通过这个电子开关去控制电路,从而控制外设硬件。

纯软件-包罗万象的小程序

我们已经完成了串口和IO口的控制,但是我们仅仅知道了怎么用,对其一无所知。程序怎么跑的?代码到底放在里?内存又是怎么保存的?下面,我们通过一个简单的程序,学习嵌入式软件的基本要素。

分析启动代码

  • 函数从哪里开始运行?

每个芯片都有复位功能,复位后,芯片的PC指针(一个寄存器,指示程序运行位置,对于多级流水线的芯片,PC可能跟真正执行的指令位置不一致,这里暂且认为一致)会复位到固定值,一般是0x00000000,在STM32中,复位到0X08000004。因此复位后运行的第一条代码就是0X08000004。前面我们不是拷贝了一个启动代码文件到工程吗?startup_stm32f40_41xxx.s,这个汇编文件为什么叫启动代码?因为里面的汇编程序,就是复位之后执行的程序。在文件中,有一段数据表,称为中断向量,里面保存了各个中断的执行地址。复位,也是一个中断。

芯片复位时,芯片从中断表中将Reset_Handler这个值(函数指针)加载到PC指针,芯片就会执行Reset_Handler函数了。(一个函数入口就是一个指针)

; Vector Table Mapped to Address 0 at Reset                AREA    RESET, DATA, READONLY                EXPORT  __Vectors                EXPORT  __Vectors_End                EXPORT  __Vectors_Size__Vectors       DCD     __initial_sp               ; Top of Stack                DCD     Reset_Handler              ; Reset Handler                DCD     NMI_Handler                ; NMI Handler                DCD     HardFault_Handler          ; Hard Fault Handler                DCD     MemManage_Handler          ; MPU Fault Handler                DCD     BusFault_Handler           ; Bus Fault Handler                DCD     UsageFault_Handler         ; Usage Fault Handler

Reset_Handler函数,先执行SystemInit函数,这个函数在标准库内,主要是初始芯片时钟。然后跳到__main执行,__main函数是什么函数?

是我们在main.c中定义的main函数吗?后面我们再说这个问题。

; Reset handlerReset_Handler    PROC                 EXPORT  Reset_Handler             [WEAK]        IMPORT  SystemInit        IMPORT  __main                 LDR     R0, =SystemInit                 BLX     R0                 LDR     R0, =__main                 BX      R0                 ENDP

芯片是怎么知道开始就执行启动代码的呢?或者说,我们如何把这个启动代码放到复位的位置?这就牵涉到一个一般情况下不关注的文件wujique.sct,这个文件在wujiqueprjObjects目录下,通常把这个文件叫做分散加载文件,编译工具在链接时,根据这个文件放置各个代码段和变量。

在MDK软件Options菜单Linker下有关于这个菜单的设置。

把Use Memory Layout from Target Dialog前面的勾去掉,之前不可设置的框都可以设置了。点击Edit进行编辑。

在代码编辑框出现了分散加载文件内容,当前文件只有基本的内容。

其实这个文件功能很强大,通过修改这个文件可以配置程序的很多功能,例如:1 指定FLASH跟RAM的大小于起始位置,当我们把程序分成BOOT、CORE、APP,甚至进行驱动分离的时候,就可以用上了。2 指定函数与变量的位置,例如把函数加载到RAM中运行。

从这个基本的分散加载文件我们可以看出:

  • 第6行 ER_IROM1 0x08000000 0x00080000定义了ER_IROM1,也就是我们说的内部FLASH,从0x08000000开始,大小0x00080000。

  • 第7行 .o (RESET, +First)从0x08000000开始,先放置一个.o文件, 并且用(RESET, +First)指定RESET块优先放置,RESET块是什么?请查看启动代码,中断向量就是一个AREA,名字叫RESET,属于READONLY。这样编译后,RESET块将放在0x08000000位置,也就是说,中断向量就放在这个地方。DCD是分配空间,4字节,第一个就是__initial_sp,第二个就是Reset_Handler函数指针。也就是说,最后编译后的程序,将Reset_Handler这个函数的指针(地址),放在0x800000+4的地方。所以芯片在复位的时候,就能找到复位函数Reset_Handler。

  • 第8行 *(InRoot$$Sections)什么鬼?GOOGLE啊!回头再说。

  • 第9行 .ANY (+RO)意思就是其所有RO,顺序往后放。就是说,其代码,跟着启动代码后面。

  • 第11行 RW_IRAM1 0x20000000 0x00020000定义了RAM大小。

  • 第12行 .ANY (+RW +ZI)所有的RW ZI,全部放到RAM里面。RW,ZI,也就是变量,这一行指定了变量保存到什么地址。

分析用户代码

到此,基本启动过程已经分析完。下一步开始分析用户代码,就从main函数开始。1 程序跳转到main函数后:RCC_GetClocksFreq获取RCC时钟频率;SysTICk_Config配置SysTICk,在这里打开了SysTick中断,10毫秒一次。

Delay(5);延时50毫秒。

int main(void){  GPIO_InitTypeDef GPIO_InitStructure;     RCC_GetClocksFreq(&RCC_Clocks);  SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);      Delay(5);

2 初始化IO就不说了,进入while(1),也就是一个死循环,嵌入式程序,都是一个死循环,否则就跑飞了。

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2| GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(GPIOG, &GPIO_InitStructure);    mcu_uart_open(3);while (1){  GPIO_ResetBits(GPIOG, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);  Delay(100);  GPIO_SetBits(GPIOG, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);  Delay(100);  mcu_uart_test();  TestFun(TestTmp2);}

3 在while(1)中调用TestFun函数,这个函数使用两个全局变量,两个局部变量。

u32 TestTmp1 = 5;//全局变量,初始化为5u32 TestTmp2;//全局变量,未初始化const u32 TestTmp3[10] = {6,7,8,9,10,11,12,13,12,13};u8 TestFun(u32 x)//函数,带一个参数,并返回一个u8值{ u8 test_tmp1 = 4; 
相关资料

猜您喜欢

现代工业和科技发展迅速的背景下,配件的重要性愈加凸显。本文将深入探讨“Accessories_12.35X4.3MM_SM”这一特定配件的特点、应用及其在各行各...
2025-04-23 17:00:45

肖特基二极管是特殊类型的二极管,低正向压降和快速开关特性而广受欢迎。在电子电路中发挥着重要作用,尤其是在电源管理和信号处理领域。本文将深入探讨肖特基二极管的作用...
2025-04-07 00:31:41

电磁阀是自动化控制系统中不可少的重要组成部分,其种类繁多,主要区别体现在结构、工作原理和应用领域等方面。电磁阀的结构可以分为直动式和先导式。直动式电磁阀适用于小...
2014-06-09 00:00:00

十字螺丝批是常见的手动工具,独特的十字形状设计,应用于多个领域。在家庭维修中,十字螺丝批常用于固定家具、安装电器和修理日常用品,方便快捷。在电子产品行业,十字螺...
2022-08-18 00:00:00

NTC型热敏电阻(Negative Temperature Coefficient)是一种电阻值随温度升高而降低的元件,应用于温度测量、温度补偿和过热保护等领域...
2025-03-17 17:31:06

LED驱动器,作为LED灯具的心脏,在LED照明系统中是很重要的配件。不仅为LED提供稳定的电流,还能延长LED的使用寿命。然而,面对市场上琳琅满目的LED驱动...
2024-07-03 00:00:00

家用工具套装是指一组专为家庭日常维护和修理而设计的工具集合。通常,这些工具包括螺丝刀、锤子、钳子、量尺、切割工具等,能够满足各种小型维修和 DIY 项目的需求。...
2020-08-22 00:00:00

贴片电阻1211并非表示具体的阻值,而是指它的尺寸大小。1211是英制表示法,指的是电阻的长和宽分别为12 mil 和11 mil。 mil是长度单位,1 mi...
2024-11-26 11:29:52

纸介质电容是利用纸作为介质材料的电容器。基本构造包括两个导电层,通常是金属薄膜,夹在一层绝缘的纸介质之间。纸介质电容良好的电气性能和稳定性,应用于电子设备中。纸...
2008-04-19 00:00:00

桌面书立是实用而美观的书籍整理工具,专为爱书之人设计。不仅能够有效地保持书籍的整齐,还能为您的办公或阅读空间增添一抹独特的风格。无论是经典的木质书立,还是现代的...
2008-11-04 00:00:00