实现stm32H7的IAP过程,没有想象中的顺利。
需要解决串口DMA和MPU配置管理。
查看正点原子的MPU管理例程,想自己用串口下发指令,实现MPU打开,读取和写入指令。
中间遇到很多坑,比如串口DMA方式下发指令,没反应,debug模式,发现数据读不进去。才知道需要配置串口DMA的MPU为nobuffer,实现 Cache 模式下不使用缓冲。
MCU型号:STM32H743ZIT6
Stm32cubemx版本:6.12.1
用到的知识点梳理:
一、MPU配置管理。
1、MPU:内存保护单元。
其实作用有两个:①加速访问CPU。CPU的速率能达到480MHz,但是除了TCM和Cache以480MHz工作,其它AXI SRAM,SRAM1,SRAM2等都是以240MHz工作。M7内核芯片基本都做了一级Cache支持,Cache又分数据缓存D-Cache和指令缓冲I-Cache,STM32H7的数据缓存和指令缓存大小都是16KB。对于指令缓冲,用户不用管,这里主要说的是数据缓存D-Cache。数据缓存D-Cache就是解决CPU加速访问SRAM。如果每次CPU要读写SRAM区的数据,都能够在Cache里面进行,自然是最好的,实现了240MHz到480MHz的飞跃,实际是做不到的,因为数据Cache只有16KB大小,总有用完的时候。
②内存保护,防止不受信任的应用程序访问受保护的内存区域。 防止用户应用程序破坏操作系统使用的数据。通过阻止任务访问其它任务的数据区。 允许将内存区域定义为只读,以便保护重要数据。检测意外的内存访问内存保护、外设保护和代码访问保护。
- 配置管理规则
结果HAL库MPU的结构体成员来说,
typedef struct
{
uint8_tEnable;
uint8_tNumber;
uint32_tBaseAddress;
uint8_t Size;
uint8_tSubRegionDisable;
uint8_tTypeExtField;
uint8_t AccessPermission;
uint8_tDisableExec;
uint8_tIsShareable;
uint8_tIsCacheable;
uint8_tIsBufferable;
}MPU_Region_InitTypeDef
前面的5个好理解,后面几个有些抽象。
- Enable 使能MPU内存区
- Number MPU内存区编号,从0到15。序号越大,优先级越高。
- BaseAddress MPU内存区的起始地址
- Size MPU内存区的大小
- SubRegionDisable 使能子区域,配置为0使能。配置为1,不使能
- TypeExtField 类型扩展域三种,和后面的c、s、b配合使用。常见的是两种level0和level1看下图。最常用的是level0。
- AccessPermission 有六种访问规则。无权限,全部权限,有读写权限,只读权限等。
- DisableExec =0使能指令提取, 即这块内存区可以执行程序代码;=1 表示禁止指令提取, 即这块内存区禁止执行程序代码
- IsShareable 多总线或者多核访问的共享
- IsCacheable 使能Cache。
- IsBufferable 配合 C 位。实现 Cache 模式下是否使用缓冲。
2、 配置Non-cacheable
这个最好理解,就是正常的读写操作,无Cache。
对应四种MPU配置如下:
·TEX = 000 C=0 B=0 S=忽略此位,强制为共享
·TEX = 000 C=0 B=1 S=忽略此位,强制为共享
·TEX = 001 C=0 B=0 S=0
·TEX = 001 C=0 B=0 S=1
由于H7内核达到480Mhz, CPU访问RAM都需要透过cache才能发挥性能. 所以H7芯片做了MPU这个部分来配置内存的访问策略. CPU访问SRAM 中间有CACHE的作用. 而DMA是直接操作SRAM空间. 所以要进行Cache策略配置.
不使用Cache。
3、 配置Write through(透写),read allocate,no write allocate
注意,M7内核只要开启了Cache,read allocate就是开启的。
· 使能了此配置的SRAM缓冲区写操作
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域,那么会同时写到Cache里面和SRAM里面;如果没有,就用到配置no write allocate了,意思就是CPU会直接往SRAM里面写数据,而不再需要在Cache里面开辟空间了。
在写Cache命中的情况下,这个方式的优点是Cache和SRAM的数据同步更新了,没有多总线访问造成的数据一致性问题。缺点也明显,Cache在写操作上无法有效发挥性能。
· 使能了此配置的SRAM缓冲区读操作
如果CPU要读取的SRAM区数据在Cache中已经加载好,就可以直接从Cache里面读取。如果没有,就用到配置read allocate了,意思就是在Cache里面开辟区域,将SRAM区数据加载进来,后续的操作,CPU可以直接从Cache里面读取,从而时间加速。
安全隐患,如果Cache命中的情况下,DMA写操作也更新了SRAM区的数据,CPU直接从Cache里面读取的数据就是错误的。
· 对应的两种MPU配置如下:
TEX = 000 C=1 B=0 S=1
TEX = 000 C=1 B=0 S=0
4、 配置Write back(回写),read allocate,no write allocate
注意,M7内核只要开启了Cache,read allocate就是开启的。
· 使能了此配置的SRAM缓冲区写操作
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域,那么会写到Cache里面,而不会立即更新SRAM;如果没有,就用到配置no write allocate了,意思就是CPU会直接往SRAM里面写数据,而不再需要在Cache里面开辟空间了。
安全隐患,如果Cache命中的情况下,此时仅Cache更新了,而SRAM没有更新,那么DMA直接从SRAM里面读出来的就是错误的。
· 使能了此配置的SRAM缓冲区读操作
如果CPU要读取的SRAM区数据在Cache中已经加载好,就可以直接从Cache里面读取。如果没有,就用到配置read allocate了,意思就是在Cache里面开辟区域,将SRAM区数据加载进来,后续的操作,CPU可以直接从Cache里面读取,从而时间加速。
安全隐患,如果Cache命中的情况下,DMA写操作也更新了SRAM区的数据,CPU直接从Cache里面读取的数据就是错误的。
· 对应两种MPU配置如下:
TEX = 000 C=1 B=1 S=1
TEX = 000 C=1 B=1 S=0
5、串口DMA的MPU配置:
MPU_InitStruct.Enable = MPU_REGION_ENABLE;//使能MPU内存区
MPU_InitStruct.Number = MPU_REGION_NUMBER0;//编号0
MPU_InitStruct.BaseAddress = 0x24000000;//对应的内存区起始地址
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;//大小
MPU_InitStruct.SubRegionDisable = 0x0;//不使能子区域
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;//
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;//支持全部访问
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;//=0使能指令提取, 即这块内存区可以执行程序代码, XN=1 表示禁止指令提取, 即这块内存区禁止执行程序代码。
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;//多总线或者多核访问的共享
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;//使能 Cache
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;//配合 C 位实现 Cache 模式下是否使用缓冲,这里需要设置不使能。
选择下图的模式。能避免Cache和SRAM中数据不一致的风险。
二、Stm32cubemx初始化
①MPU配置
②RCC配置
③UART配置
④烧写配置
⑤时钟配置
三、代码部分
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "memorymap.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h> //第一步包含头文件
#include "string.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define open_CMD "openmpu" //打开MPU
#define read_CMD"readmpu" //读取MPU
#define write_CMD"writempu" //写入MPU
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t t = 0;
uint16_t times = 0;
uint8_t mpudata[128] __attribute__((at(0X20002000)));//定义一个数组
uint8_t rx_flag = 0; //输入结束的标志位
uint8_t uart_rxbuf[100]; //输入数据的缓存区
uint8_t uart_rx[1];//输入字符缓存区
uint16_t len; //输入字符的个数
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MPU_Config(void);
static void MX_NVIC_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int c, FILE *stream) //重写fputc函数
{
/*
huart1是工具生成代码定义的UART1结构体,
如果以后要使用其他串口打印,只需要把这个结构体改成其他UART结构体。
*/
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
uint8_t mpu_set_protection(uint32_t baseaddr, uint32_t size, uint32_t rnum, uint8_t de, uint8_t ap, uint8_t sen, uint8_t cen, uint8_t ben)
{
MPU_Region_InitTypeDef mpu_region_init_handle;
HAL_MPU_Disable();/* 配置MPU之前先关闭MPU,配置完成以后在使能MPU */
mpu_region_init_handle.Enable = MPU_REGION_ENABLE; /* 使能该保护区域 */
mpu_region_init_handle.Number = rnum; /* 设置保护区域 */
mpu_region_init_handle.BaseAddress = baseaddr; /* 设置基址 */
mpu_region_init_handle.DisableExec = de; /* 是否允许指令访问 */
mpu_region_init_handle.Size = size; /* 设置保护区域大小 */
mpu_region_init_handle.SubRegionDisable = 0X00; /* 禁止子区域 */
mpu_region_init_handle.TypeExtField = MPU_TEX_LEVEL0; /* 设置类型扩展域为level0 */
mpu_region_init_handle.AccessPermission = (uint8_t)ap; /* 设置访问权限, */
mpu_region_init_handle.IsShareable = sen; /* 是否共用? */
mpu_region_init_handle.IsCacheable = cen; /* 是否cache? */
mpu_region_init_handle.IsBufferable = ben; /* 是否缓冲? */
HAL_MPU_ConfigRegion(&mpu_region_init_handle); /* 配置MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); /* 开启MPU */
return 0;
}
/* USER CODE END 0 */
/**
* @briefThe application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* Enable the CPU Cache */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
//HAL_MPU_Disable(); //关闭MPU
printf("\r\n<<<<<<<<<<<<<STM32H7 MPU TEST打印>>>>>>>>>>>>>>\r\n");
printf("input \"openmpu\"去打开MPU\t\"writempu\"去写入MPU\t \"readmpu\"去读取MPU\t需要在20s内输入\r\n");//提示输入指令
HAL_Delay(1000);
HAL_UART_Receive_DMA(&huart1, (uint8_t *)uart_rx, 1);//打开串口DMA接收输入
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
while (rx_flag == 1) //对输入命令执行判断
{
printf("字符数目是%d,输入内容如下:\r\n", len);//输入字符数
uart_rxbuf[len] = 0x0d;
uart_rxbuf[len + 1] = 0x0a;
// uart_rxbuf[len+2]=0x0d;
// uart_rxbuf[len+3]=0x0a;
HAL_UART_Transmit(&huart1, (uint8_t *)uart_rxbuf, len + 2, 0xffff);//输出输入的命令
{
if (strstr((const char *)uart_rxbuf, open_CMD) != NULL) //
{
// 打开MPU
mpu_set_protection(0X20002000, /* 只读,禁止共用,禁止catch,允许缓冲 */
MPU_REGION_SIZE_128B,
MPU_REGION_NUMBER1, 0,
MPU_REGION_PRIV_RO_URO,
MPU_ACCESS_NOT_SHAREABLE,
MPU_ACCESS_NOT_CACHEABLE,
MPU_ACCESS_BUFFERABLE);
printf("接收到指令openmpu\r\n");
printf("打开MPU 完成\r\n\r\n");
}
else if (strstr((const char *)uart_rxbuf, write_CMD) != NULL)
{
// 写入MPU
printf("接收到指令writempu\r\n");
printf("Start Writing data...\r\n");
sprintf((char *)mpudata, "MPU test array %d", t);//把t写入MPU
printf("Data Write finshed!\r\n\r\n");
}
// 读取MPU
else if (strstr((const char *)uart_rxbuf, read_CMD) != NULL) /* 从数组中读取数据,不管有没有开启MPU保护都不会进入内存访问错误! */
{
printf("接收到指令readmpu\r\n");
printf("Array data is:%s\r\n\r\n", mpudata);
}
else
{
HAL_Delay(1);
printf("不是有效的指令,请重新输入\r\n\r\n");
}
t++;
len = 0;//字符长度清零
rx_flag = 0;//输入状态标志位置零
}
}
HAL_Delay(10);
times++;
if (times % 10000 == 0)//每隔100s
{
printf("\r\ninput \"openmpu\"去打开MPU\t\"writempu\"去写入MPU\t \"readmpu\"去读取MPU\t需要在20s内输入\r\n");//提示输出指令“update”,10s内没接收到指令,开始启动APP
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Supply configuration update enable
*/
HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 120;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
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_CLOCKTYPE_D3PCLK1 | RCC_CLOCKTYPE_D1PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief NVIC Configuration.
* @retval None
*/
static void MX_NVIC_Init(void)
{
/* USART1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* DMA1_Stream1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
/* DMA1_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);
}
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//uart接收的回调函数
{
if (huart->Instance == USART1)
{
//结束符是换行
if (uart_rx[0] != 0x0A) //统计输入字符长度
{
len ++ ;
uart_rxbuf[len - 1] = uart_rx[0];//写入接收缓冲区
if (len > 255) //字符长度超出最大长度。重新输入
{
len = 0;
}
}
else//接收到换行符,把len的长度减去1,因为"\r"回车占用一个字节。
{
len--;
rx_flag = 1;
}
}
HAL_UART_Receive_DMA(&huart1, (uint8_t *)uart_rx, 1);//再次打开uart接口的DMA函数,循环接收
}
void MemManage_Handler(void)//触发mpu写入保护
{
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
printf("Mem Access Error!!\r\n");//写入失败
printf("Soft Reset……\r\n");
NVIC_SystemReset();//mcu软复位
/* USER CODE END MemoryManagement_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
/* USER CODE END W1_MemoryManagement_IRQn 0 */
}
}
/* USER CODE END 4 */
/* MPU Configuration */
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
/* Disables the MPU */
HAL_MPU_Disable();
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;//使能MPU内存区
MPU_InitStruct.Number = MPU_REGION_NUMBER0;//编号0
MPU_InitStruct.BaseAddress = 0x24000000;//对应的内存起始地址
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;//大小
MPU_InitStruct.SubRegionDisable = 0x0;//不使能子区域
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;//
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;//支持全部访问
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;//MPU_INSTRUCTION_ACCESS_ENABLE;//=0使能指令提取, 即这块内存区可以执行程序代码, XN=1 表示禁止指令提取, 即这块内存区禁止执行程序代码。
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;//多总线或者多核访问的共享
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;//使能 Cache
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;//配合 C 位实现 Cache 模式下是否使用缓冲,这里需要设置不使能
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
/**
* @briefThis function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @briefReports the name of the source file and the source line number
*where the assert_param error has occurred.
* @paramfile: pointer to the source file name
* @paramline: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
四、运行结果
输入writempu(见下图)
提示写入成功
输入readmpu
提示读取成功,打印出来
输入writempu
提示写入成功
再输入readmpu
提示读取成功,打印出来。读出的数据发生变化
输入openmpu
提示开启MPU
再输入readmpu
读取成功,打印出来。读出的数据不变。
再输入writempu
提示写入失败,系统软复位