多语言展示
当前在线:1352今日阅读:82今日分享:48

STM32学习报告

二、启动代码分析1、启动模式的选择STM32自带的启动模式有3种:STM32的启动选择,通过设置BOOT1、BOOT0的引脚的高低电平即可选择。其中主闪存启动是将程序下载到内置的Flash进行启动(该flash可运行程序),该程序可以掉电保存,下次开机可自动启动;系统存储器启动是将程序写入到一快特定的区域,一般由厂家直接写入,不能被随意更改或擦除。内置SRAM启动,由于SRAM掉电丢失,不能保存程序,一般只用于程序的调试。2、启动文件分析启动文件,总的来说主要做了3个工作:分配和初始化栈、堆;定义复位向量并初始化;中断向量表及相应的异常处理程序,以107器件cl.s文件为例。1)定义栈、堆及其初始化定义堆、栈:Stack_Size EQU0x400Heap_Size EQU0x400 AREASTACK, NOINIT, READWRITE,Stack SPACEStack_Size AREAHEAP, NOINIT, READWRITE,Heap SPACEHeap_Size堆栈初始化定义:__user_initial_stackheap //进行栈、堆的赋值,在_main函数执行过程中调用 LDRR0, =Heap LDRR1, =(Stack + Stack_Size) LDRR2, =(Heap + Heap_Size) LDRR3, =Stack BXLR2)定义复位向量Boot引脚的设置不同,复位时,起始地址的位置不同,SRAM的起始地址为0x2000000, flash的起始地址为0x8000000。Cortex-M3内核规定,起始地址必须存放堆定指针,而第二个地址必须存放复位中断入口向量。在系统复位时,内核会自动从其实地址的下一个地址(即32位)空间取出复位中断入口向量,然后跳转到复位中断服务程序,该服务程序就会跳转到main()执行程序。中断向量表(部分向量):EXPORT __Vectors__Vectors DCDStack + Stack_Size ; Top of Stack DCD Reset_Handler DCD NMIException DCD HardFaultException DCD MemManageException DCD BusFaultException DCD UsageFaultException DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; ReservedDCD PendSV_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD OS_CPU_PendSVHandler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler复位中断服务程序: EXPORTReset_HandlerReset_Handler IMPORT__main //导入符号,_main为运行时库提供的函数;完成堆、栈的初始化 LDRR0, =__main //等工作,调用定义的__user_initial_stackheap BXR0 //跳到_main,进入C的世界3)其他中断向量及服务子程序在启动文件中,只定义了中断向量,它们相应的服务子程序跳转到空操作,为之后扩展中断服务程序做准备。4)拓展:_main()与main()区别注意:_main()与main()不是相同函数。_main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者是我们自己编写的main()主函数。对含有启动程序的系统来说,执行地址与加载地址相同不容易实现。因为启动程序是要烧到非易失存储器里,用来在上电执行的,而这个程序必定会有RW段,如果RW放在非易失存储器,如FLASH,那就不好实现RW功能了,因此要给RW移动到能够实现RW功能的存储器,如SRAM等.因此,对含有启动程序来说,'执行地址与加载地址相同'就不容易实现。程序的入口点在C库中的_main处,在该点,库代码执行以下操作:u把RO,RW从他们的加载域复制到他们的运行域中去u初始化ZI域u跳到__rt_entry而库函数__rt_entry()会完成以下工作:u调用__rt_stackheap_init()设置stack和heapu调用__rt_lib_init()初始化相应的库函数u调用main(),即是我们自己的应用程序了3、我司启动文件情况:因公司使用的stm32芯片规格不一,分stm32f103、stm32f107系列等等,在进行程序编程时,为保证程序的可移植性,在InterruptVector.c文件中,通过预定义的方式来切换103/107器件,同时代码在进行编译过程中,需手动选择启动文件类型,不然代码无法编译成功。图中编译使用的是cl.s文件。三、时钟资源分析1、时钟源在STM32中,有五个时钟源,为中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,在这5个中HIS,HSE以及PLL是高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源,其他的是内部时钟源。下面我们看看STM32的五个时钟源,对应上面图中标红的序号标示。①、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。②、HSI是高速内部时钟,RC振荡器,频率为8MHz。③、LSI是低速内部时钟,RC振荡器,频率为40kHz。独立看门狗的时钟源只能是LSI,同时LSI还可以作为RTC的时钟源。④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。这个主要是RTC的时钟源。⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。2、时钟源输出再看一下时钟源的输出,如何为外设及系统提供时钟,对应上方图中字母标示。A、MCO是STM32的一个时钟输出IO(PA8),它可以选择一个时钟信号输出,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。B、这里是RTC时钟源,从图上可以看出,RTC的时钟源可以选择LSI,LSE,以及HSE的128分频。C、USB的时钟是来自PLL时钟源。STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。D、是STM32的系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或者HSE。系统时钟最大频率为72MHz。E、指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是SYSCLK。SYSCLK通过AHB分频器分频后送给各模块使用。1)AHB总线、内核、内存和DMA使用的HCLK时钟。2)通过8分频后送给Cortex的系统定时器时钟,也就是systick了。3)直接送给Cortex的空闲运行时钟FCLK。4)送给APB1分频器。APB1分频器输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。5)送给APB2分频器。APB2分频器分频输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。注意:APB1上面的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3等等,APB2上面连接的是高速外设包括UART1、SPI1、Timer1、ADC1、ADC2、所有普通IO口(PA~PE)、第二功能IO口等。3、SystemInit函数分析首先,要芯片工作进行时钟配置时函数的调用关系:startup_stm32f10x_cl.s(启动文件)→main()→SystemInit()→SetSysClock()→SetSysClockTo72()。(系统默认使用72MHz)1)SystemInit()在进行时钟配置之前,对RCC寄存器CR、CIR、CFGR、CFGR2进行重置。然后调用函数SetSysClock()。/* Reset the RCC clockconfiguration to the default reset state(for debug purpose) *//* Set HSION bit */RCC->CR |= (uint32_t)0x;//内部8MHz时钟开启/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */#ifndef STM32F10X_CLRCC->CFGR &= (uint32_t)0xF8FF0000; //清零#elseRCC->CFGR &= (uint32_t)0xF0FF0000; //清零#endif /* STM32F10X_CL *//* Reset HSEON, CSSON and PLLON bits */RCC->CR &= (uint32_t)0xFEF6FFFF; //清零/* Reset HSEBYP bit */RCC->CR &= (uint32_t)0xFFFBFFFF; //清零/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */RCC->CFGR &= (uint32_t)0xFF80FFFF; //清零#ifdef STM32F10X_CL //互联型产品/* Reset PLL2ON and PLL3ON bits */RCC->CR &= (uint32_t)0xEBFFFFFF;/* Disable all interrupts and clear pending bits */RCC->CIR = 0x00FF0000; //中断使能关闭/* Reset CFGR2 register */RCC->CFGR2 = 0x;#elif defined (STM32F10X_LD_VL)|| defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)/* Disable all interrupts and clear pending bits */RCC->CIR = 0x009F0000;/* Reset CFGR2 register */RCC->CFGR2 = 0x;#else/* Disable all interrupts and clear pending bits */RCC->CIR = 0x009F0000;#endif /* STM32F10X_CL */SetSysClock();2)SetSysClock()#ifdef SYSCLK_FREQ_HSESetSysClockToHSE();#elif defined SYSCLK_FREQ_24MHzSetSysClockTo24();#elif defined SYSCLK_FREQ_36MHzSetSysClockTo36();#elif defined SYSCLK_FREQ_48MHzSetSysClockTo48();#elif defined SYSCLK_FREQ_56MHzSetSysClockTo56();#elif definedSYSCLK_FREQ_72MHz //定义为72MHzSetSysClockTo72();#endif定义为72MHz,便接着调用函数SetSysClockTo72()。3)SetSysClockTo72()先计算系统时钟SYSCLK频率(外部晶振为25MHz)。代码中:RCC->CFGR2|= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 | RCC_CFGR2_PREDIV1SRC_PLL2 |RCC_CFGR2_PREDIV1_DIV5);其中RCC_CFGR2_PREDIV2_DIV5:PREDIV2=5 //5分频也就是PREDIV2对输入的外部时钟5分频,那么PLL2和PLL3没有倍频前均为25/5=5MHz。RCC_CFGR2_PLL2MUL8:PLL2MUL=8 //8倍频8倍频后PLL2时钟=5*8=40MHz,因此PLL2CLK=40MHz。RCC_CFGR2_PREDIV1SRC_PLL2:RCC_CFGR2的第16位为1,选择PLL2CLK作为PREDIV1的时钟源。RCC_CFGR2_PREDIV1_DIV5:PREDIV1=5 //5分频PREDIV1CLK=PLL2CLK/5=8MHz。(以上是对RCC_CFGR2进行的配置)RCC->CFGR|= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLMULL9);RCC_CFGR_PLLXTPRE_PREDIV1:操作的是RCC_CFGR的第17位PLLXTPRE,操作这一位和操作RCC_CFGR2寄存器的位[3:0]中的最低位是相同的效果。RCC_CFGR_PLLSRC_PREDIV1:选择PREDIV1输出作为PLL输入时钟;PREDIV1CLK=8MHZ,所以输入给PLL倍频的时钟源是8MHz。RCC_CFGR_PLLMULL9:PLLMULL=9;//9倍频也就是对PLLCLK=PREDIV1CLK*9=72MHz。(以上是对RCC_CFGR进行的配置)/* Select PLL as system clock source */RCC->CFGR &=(uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR |=(uint32_t)RCC_CFGR_SW_PLL; //选择PLLCLK作为系统时钟源故系统时钟SYSCLK=72MHz。代码中:/* HCLK = SYSCLK */ RCC->CFGR |=(uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK2 = HCLK */ RCC->CFGR |=(uint32_t)RCC_CFGR_PPRE2_DIV1; /* PCLK1 = HCLK */ RCC->CFGR |=(uint32_t)RCC_CFGR_PPRE1_DIV2;故HCLK(AHB总线时钟)=PLLCLK=SYSCLK=72MHz。PLCK2(APB2时钟)=HCLK=SYSCLK=PLLCLK=72MHz。PLCK1(APB1时钟)=HCLK/2=36MHz。总结一下SystemInit()函数中设置的系统时钟大小:SYSCLK(系统时钟)=72MHZAHB总线时钟=72MHZAPB1总线时钟=36MHZAPB2总线时钟=72MHZPLL时钟=72MHZPLL2时钟=40MHZ以上属基于个人理解编写的学习总结,由于个人表达能力及专业能力有限,难免会有遗漏及描述不清晰的地方,若有疏忽的地方请指导一下
推荐信息