好记性不如铅笔头

ARM, STM32, 操作系统

STM32CubeMX简单使用笔记:工具生成代码简单分析

因为项目需要,最近重新拾起来STM32,正好趁着这个机会好好的梳理下遇到的知识细节。

使用STM32CubeMX自动化生成代码,可以快速搭建可以正常工作的开发环境。为了能够更加了解生成的代码,这里我们简单的分析下 启用CSS  这个工程。

首先看下生成的文件目录:

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会先后调用SystemInitmain两个函数。

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 */
  }

}

 

Leave a Reply

15 − 7 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据