因为项目需要,最近重新拾起来STM32,正好趁着这个机会好好的梳理下遇到的知识细节。
使用STM32CubeMX自动化生成代码,可以快速搭建可以正常工作的开发环境。为了能够更加了解生成的代码,这里我们简单的分析下 启用CSS 这个工程。
首先看下生成的文件目录:
CONTENTS
Core目录:
Core目录主要包含了程序启动配置和我们实现的上层业务代码。
Drivers/CMSIS目录:
CMSIS主要包含了ARM CortexM在不同的编译工具和不同的内核下,统一化的提供汇编函数格式(__ASM等),程序扩展语法(__PACKED等),统一化的提供寄存器访问和控制API(__enable_irq,NVIC_SetVector等)
主要入口为 cmsis_compiler.h 。
Drivers/STM32F4xx_HAL_Driver 目录:
HAL_Driver目录主要包含了STM32的官方HAL库。
链接文件:
生成文件:
程序启动流程简单分析:
首先从链接文件开始,我们打开IDE的项目属性,可以看到默认连接是以Flash运行,如下图:
我们的开发板BOOT配置默认也是从Flash开始执行,所以IDE编译链接的代码可以直接跑起来,无需再次配置。
我们看下STM32F407ZGTX_FLASH.ld这个文件,关键内容如下:
/* Entry Point */ 设置程序入口 ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _estack 表示栈顶指针 _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */ _Min_Heap_Size = 0x200 ; /* required amount of heap */ _Min_Stack_Size = 0x400 ; /* required amount of stack */ /* Memories definition */ MEMORY { CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K 设置Flash起始和长度,由于STM32Flash起始地址为0x8000000,因此该程序会直接执行。 FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K } /* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH 写入FLASH的第一个区段,内容为isr_vector分段。
然后我们看下 startup_stm32f407zgtx.s 这个文件,部分关键内容如下:
Reset_Handler: 。。。 。。。 /* Call the clock system intitialization function.*/ Reset_Handler 会首先调用 SystemInit bl SystemInit /* Call static constructors */ bl __libc_init_array /* Call the application's entry point.*/ Reset_Handler 会再次调用 main bl main bx lr .size Reset_Handler, .-Reset_Handler isr_vector分段定义如下,里面包含了g_pfnVectors数组 .section .isr_vector,"a",%progbits .type g_pfnVectors, %object .size g_pfnVectors, .-g_pfnVectors g_pfnVectors数组就是ARM CortexMx中断向量表 g_pfnVectors: .word _estack .word Reset_Handler .word NMI_Handler 。。。 。。。 .word SysTick_Handler 。。。 。。。 以SysTick_Handler为例,中断处理函数全部为弱引用,默认调用为Default_Handler .weak SysTick_Handler .thumb_set SysTick_Handler,Default_Handler
通过 STM32F407ZGTX_FLASH.ld 和 startup_stm32f407zgtx.s 这两个文件,我们可以简单分析出,在编译连接阶段,链接器会在0x8000000这个位置存放ARM CortexMx中断向量表,然后根据ARM CortexM的执行规则,_estack为栈顶位置,Reset_Handler为第一个函数执行点。Reset_Handler会先后调用SystemInit和main两个函数。
SystemInit定义在 system_stm32f4xx.c 中,主要用来设置中断向量表偏移:
void SystemInit(void) { 。。。 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ 。。。 }
main定义在main.c中,部分关键内容如下:
int main(void) { 。。。 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); 。。。 /* Configure the system clock */ SystemClock_Config(); 。。。 /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); 。。。 。。。 }
从这里开始,我们按照功能,分别笔记:
HAL时钟介绍:
STM32的官方HAL库需要一个时钟用来进行计时处理,默认情况下,STM32CubeMX使用ARM CortexM的SYSTICK作为时基,如下图所示:
对应的代码实现如下:
在HAL_Init中,STM32会调用HAL_InitTick函数,此函数会配置ARM CortexM的SYSTICK寄存器,并且启用SysTick中断,中断间隔为1ms(HAL_TICK_FREQ_1KHZ),中断响应函数在stm32f4xx_it.c中定义:
void SysTick_Handler(void) { HAL_IncTick(); }
可以看到,当Systick中断时,HAL层会更新tick,此时HAL时钟就运转起来了。
时钟树配置介绍:
首先看下时钟配置:
整个总线启用如下:
在HAL_Init中,STM32会调用HAL_MspInit函数,该函数实现在stm32f4xx_hal_msp.c中,主要功能为启用外部总线:
void HAL_MspInit(void) { __HAL_RCC_SYSCFG_CLK_ENABLE(); __HAL_RCC_PWR_CLK_ENABLE(); }
然后在main中,会根据我们设置好的时钟树配置RCC寄存器,代码如下:
void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; //时钟来自HSE RCC_OscInitStruct.HSEState = RCC_HSE_ON; //启用HSE RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //启用PLL RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //PLL来自HSE RCC_OscInitStruct.PLL.PLLM = 4; // PLL输入4分频 RCC_OscInitStruct.PLL.PLLN = 168; // PLL输出168倍频 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLLCLK输入2分频 RCC_OscInitStruct.PLL.PLLQ = 4; // OTG SDIO等4分频 //HAL_RCC_OscConfig中会等待HSE稳定后在返回 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; //外设时钟来自PLL RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; //AHB不分频输出,CPU挂在AHB上,也就是CPU主频 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; //APB1 4分频输出 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; //APB2 2分频输出 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } /** Enables the Clock Security System */ HAL_RCC_EnableCSS(); //启用了CSS }
我们启用了外部晶振,因此需要启用PH0和PH1,因此在MX_GPIO_Init中启用了GPIOH时钟。代码如下:
static void MX_GPIO_Init(void) { 。。。 __HAL_RCC_GPIOH_CLK_ENABLE(); }
CSS介绍:
如果我们启用了CSS,那么在SystemClock_Config中,会启用CSS,代码如下:
HAL_RCC_EnableCSS(); //启用了CSS
根据STM32的官方介绍,CSS会触发NMI中断,NMI中断响应函数在stm32f4xx_it.c中定义,如下:
void NMI_Handler(void) { HAL_RCC_NMI_IRQHandler(); }
UART介绍:
在本工程中,我们启用了UART1,对应启用的GPIO为PA9和AP10,因此需要启用GPIOA时钟,代码如下:
static void MX_GPIO_Init(void) { 。。。 __HAL_RCC_GPIOA_CLK_ENABLE(); }
然后在main中,我们会根据UART1配置UART寄存器,代码如下:
static void MX_USART1_UART_Init(void) { 。。。 huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } 。。。 }
在HAL_UART_Init中,STM32会调用HAL_UART_MspInit来绑定GPIO和UART1的功能,代码如下:
void HAL_UART_MspInit(UART_HandleTypeDef* huart) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(huart->Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } }
发表评论