STM32单片机到底是如何实现软硬件结合?

发布者:Xiaoxue666最新更新时间:2024-09-25 来源: elecfans关键字:STM32  单片机  软硬件结合 手机看文章 扫描二维码
随时随地手机看文章

本文分析 STM32 单片机到底是如何实现软硬件结合的,接着分析单片机程序如何编译、运行。


软硬件结合

初学者,通常有一个困惑,就是为什么软件能控制硬件?就像当年的 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,用一个新芯片,就先看这个图。
3e53dc34-f36b-11ed-90ce-dac502259ad0.png

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

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

在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)

{

 /* Check the parameters */

 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地址,有点眉目了吧?在进一步看看

/*!< Peripheral memory map */

#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。

3eae0560-f36b-11ed-90ce-dac502259ad0.png


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

 

寄存器,其实应该是内存的统称,外设寄存器应该叫做特殊寄存器。慢慢的,所有人都把外设的叫做寄存器,其他的统称内存或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 handler

Reset_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 下有关于这个菜单的设置。


3f0ca3fe-f36b-11ed-90ce-dac502259ad0.png

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

3f3bf348-f36b-11ed-90ce-dac502259ad0.png


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

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

分析用户代码


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


int main(void){  GPIO_InitTypeDef GPIO_InitStructure; /*!< At this stage the microcontroller clock setting is already configured,  this is done through SystemInit() function which is called from startup   files before to branch to application main.   To reconfigure the default setting of SystemInit() function,   refer to system_stm32f4xx.c file */  /* SysTick end of count event each 10ms */  RCC_GetClocksFreq(&RCC_Clocks);  SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);  /* Add your application code here */  /* Insert 50 ms delay */  Delay(5);

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

/*初始化LED IO口*/

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);    



/* Infinite loop */

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函数,这个函数使用两个全局变量,两个局部变量。

/* Private functions ---------------------------------------------------------*/

u32 TestTmp1 = 5;//全局变量,初始化为5

u32 TestTmp2;//全局变量,未初始化



const u32 TestTmp3[10] = {6,7,8,9,10,11,12,13,12,13};



u8 TestFun(u32 x)//函数,带一个参数,并返回一个u8值

{

 u8 test_tmp1 = 4;//局部变量,初始化

 u8 test_tmp2;//局部变量,未初始化



 static u8 test_tmp3 = 0;//静态局部变量



 test_tmp3++;



 test_tmp2 = x;



 if(test_tmp2> TestTmp1)

  test_tmp1 = 10;

 else

  test_tmp1 = 5;



 TestTmp2 +=TestTmp3[test_tmp1];



 return test_tmp1;

}

然后程序就一直在 main 函数的 while 循环里面执行。中断呢?对,还有中断。中断中断,就是中断正常的程序执行流程。我们查看Delay函数,uwTimingDelay不等于0就死等?谁会将uwTimingDelay改为0?

/**

  * @brief  Inserts a delay time.

  * @param  nTime: specifies the delay time length, in milliseconds.

  * @retval None

  */

void Delay(__IO uint32_t nTime)

{

  uwTimingDelay = nTime;



  while(uwTimingDelay != 0);

}

搜索 uwTimingDelay 变量,函数 TimingDelay_Decrement 会将变量一直减到0。

/**

  * @brief  Decrements the TimingDelay variable.

  * @param  None

  * @retval None

  */

void TimingDelay_Decrement(void)

{

  if (uwTimingDelay != 0x00)

  {

    uwTimingDelay--;

  }

}

这个函数在哪里执行?经查找,在 SysTick_Handler 函数中运行。谁用这个函数?

/**

  * @brief  This function handles SysTick Handler.

  * @param  None

  * @retval None

  */

void SysTick_Handler(void)

{

  TimingDelay_Decrement();

}

经查找,在中断向量表中有这个函数,也即是说这个函数指针保存在中断向量表内。当发生中断时,就会执行这个函数。当然,在进出中断会有保存和恢复现场的操作。这个主要涉及到汇编,暂时不进行分析了。有兴趣自己研究研究。通常,现在我们开发程序不用关心上下文切换了。

__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 

            DCD     0                      ; Reserved      

            DCD     0                      ; Reserved     

            DCD     0                      ; Reserved    

[1] [2]
关键字:STM32  单片机  软硬件结合 引用地址:STM32单片机到底是如何实现软硬件结合?

上一篇:STM32F407 基本定时器配置输出PWM方波
下一篇:STM32G0开发笔记:定时器timer的基本使用方法

推荐阅读最新更新时间:2024-11-17 00:04

51单片机测量PWM脉冲宽度LCD1602显示当前PWM占空比频率
此程序通过两个定时器一个外部中断,测量PWM高电平时间以及周期,计算并在1602显示出占空比 周期 高电平时间,只需让脉冲在P3^2口输入即能测得数据。。 单片机源程序如下: #include reg52.h #include LCD1602.h typedef unsigned char uint8; typedef unsigned int uint16;//数据类型重定义 uint8 count = 0; //计进入外部中断次数 bit flag = 1; //捕获结束标志位 long Pwm_All; //PWM的周期 long Pwm_High; //PWM的高电平时间 占空比=Pwm_Hi
[单片机]
基于51单片机矩阵键盘的简易计算器制作
1. 运算过程、符号公式实时显示在显示屏上(I2C 1602)。 2. 自带三角函数、开根号、平方运算。 3. 计算得出的结果可设置保存并用以下一次计算。 4. 所有运算结果精确到至少小数点后两位。 5. 运算结果可通过串口发送给上位机。 6. 当断电重启时,能存储并显示断电前正在计算的任务。(AT24C02) 硬件连接图如下: 单片机源程序如下: main.c #include REGX52.H #include Calculate.H #include Martixkey.h #include AT24C02.h #include stdio.h #i
[单片机]
基于51<font color='red'>单片机</font>矩阵键盘的简易计算器制作
单片机是由哪几部分组成的?
单片机是由哪几部分组成的? 答:单片机是在一块集成电路芯片上装有CPU和程序存储器、数据存储器、输入/输出接口电路、定时/计数器、中断控制器、模/数转换器、数/模转换器、调制解调器以及其他部件等的系统。视其型号不同,其组成部分各异。
[单片机]
基于51单片机的等精度频率计设计
设计以51单片机为核心,显示采用1602液晶。频率测量方法采用等精度频率法测量,外部脉冲作为内部高速脉冲计数的启动信号,也是最后的计数的结束信号。保证外部脉冲计数的无误差,通过内部的高速计数保证测量精度。 等精度频率计ppt: 仿真原理图如下(proteus仿真工程文件可到本帖附件中下载): 单片机源程序如下: #include STC12C5A60S2.H #include Intrins.h #include 1602.h unsigned char t0_hh,t1_hh; unsigned char dis_buf ; //unsigned char code frequence ={ frequence:
[单片机]
基于51<font color='red'>单片机</font>的等精度频率计设计
基于8098单片机的脉冲测量仪的研制
  测量原理   8098单片机具有性能十分优良的高速输入输出通道,HSO0~HSO5为高速输出通道,能产 生输出宽度与周期均可调的脉冲波(PWM)。HSI0~HSI3为高速输入通道,CPU通过它们可 以同时接受来自外部的4个脉冲信号,并且随时记录脉冲信号中的高、低电平出现的时间, 非常适用于对脉冲参数的检测。   以高速输入通道HSI为例,HSI部件有自己的中断功能,当控制寄存器IOC1,7=1时,F IFO存储区装满事件后发出中断请求,如果IOC1,7=0,则保持寄存器在装入事件后发 出中断请求,两种原因引起的中断请求可通过查询状态寄存器IOS1而鉴别:如果FIFO 存储区满时,IOS1,6=1,反之,IOS1,6=0;如
[测试测量]
基于8098<font color='red'>单片机</font>的脉冲测量仪的研制
用8051单片机设计倒计时牌
;************* 电子定时器的设计******************; ;*MCU: AT892051 ; ;*MCU-crystal: 12M ; ;*Version: 01 ; ;*Last Updata: 2009-5-4 ; ;*Author: HaiZhiZi ; ;*Description: ; ;定时器T0、T1溢出周期为50MS,T0为秒计数用 ; ;S2为功能键、S3为方式选择键 ; ;P1口为字符输出口,采用共阳显示管 ; ;P3.2~P3.5为位选,P1.7为报警发音,P3.7为被控继电器 ; ;****************************************
[单片机]
MSP430单片机的加密熔断器设计
引 言   MSP430系列单片机是德州仪器( TI )公司推出的一款16位超低功耗单片机。它能够在1.8~3.6 V电压、1 MHz频率的条件下运行,耗电电流在0.1~400μA。在运算速度上,MSP430系列单片机能在8 MHz晶振的驱动下,实现125 ns的指令周期。16位的数据宽度、125 ns的指令周期以及多功能的硬件乘法器相配合,能实现数字信号处理的某些算法(如FFT等)。   在整合方面,MSP430系列单片机将大量的CPU外围模块集成在片内,有如下一些模块:看门狗(WDT)、模拟比较器、串口、硬件乘法器、液晶驱动器、10位/12位/14位ADC、端口0~6、基本定时器。其中定时器A、B均带有多个捕获/比较寄存器
[单片机]
MSP430<font color='red'>单片机</font>的加密熔断器设计
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved