必备的API知识

第 1 步:系统上电复位,进入启动文件 startup_stm32h743xx.s,在这个文件里面执行复位中断服务程序。

  • 在复位中断服务程序里面执行函数 SystemInit,在system_stm32h7xx.c 里面。*
  • 之后是调用编译器封装好的函数,比如用于 MDK 的启动文件是调__main,最终进入到 main函数*

第 2 步:进入到 main 函数就可以开始用户应用程序编程了。在这个函数里面要做几个重要的初始化,依次是:

  • MPU 初始化,需要用到库文件
    stm32h7xx_hal_cortex.c 和stm32h7xx_hal_cortex.h。
  • Cache 初始化,需要用到 core_cm7.h 文件。
  • HAL 库初始化函数 HAL_Init,需要用到文件 stm32h7xx_hal.c。
  • 系统时钟初始化,需要用到库文件 stm32h7xx_hal_rcc.c。

函数 HAL_Init()

HAL_StatusTypeDef HAL_Init(void)
{
/* 设置中断优先级分组 */
 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
 /* 使用滴答定时器做为默认时基,配置为 1ms 滴答,另外系统上电后默认使用的 HIS 时钟 */
 if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
 {
 return HAL_ERROR;
 }
  /* 初始化底层硬件 */
 HAL_MspInit();
 /* 返回函数状态 */
 return HAL_OK;
}

此函数用于初始化 HAL 库,此函数主要实现如下功能:

  • 设置 NVIC 优先级分组是 4。
  • 设置滴答定时器的每 1ms 中断一次。
  • HAL 库不像之前的标准库,在系统启动函数 SystemInit 里面做了 RCC 初始化,HAL 库是没有做的,
    所以进入到 main 函数后,系统还在用内部高速时钟 HSI,对于 H7 来说,HSI 主频是 64MHz。
  • 函数 HAL_Init 里面调用的 HAL_MspInit 一般在文件 stm32h7xx_hal_msp.c 里面做具体实现,主要
    用于底层初始化。当前此函数也在文件 stm32h7xx_hal.c 里面,只是做了弱定义。

源文件 sttm32h7xx_hal_rcc.c

这个文件主要是实现内部和外部时钟(HSE、HSI、LSE、CSI、LSI、HSI48、PLL、CSS、MCO)以
及总线时钟(SYSCLK、AHB3、 AHB1、AHB2、AHB4、APB3、APB1L、APB1H、APB2、 APB4)的配置。
系统上电复位后,用户需要完成以下工作:
⚫选择用于驱动系统时钟的时钟源。
⚫ 配置系统时钟频率和 Flash 设置。
⚫ 配置分频器。
⚫ 使能外设时钟。
⚫ 配置外设时钟源,部分外设的时钟可以不来自系统时钟,此时通过配置寄存器 RCC_D1CCIPR、
RCC_D2CCIP1R、RCC_D2CCIP2R 和 RCC_D3CCIPR 实现

函数 HALRCCClockConfig()

RCC_ClkInitTypeDef RCC_ClkInitStruct;
HAL_StatusTypeDef ret = HAL_OK;

/* 
 选择 PLL 的输出作为系统时钟
 配置 RCC_CLOCKTYPE_SYSCLK 系统时钟
 配置 RCC_CLOCKTYPE_HCLK 时钟,对应 AHB1,AHB2,AHB3 和 AHB4 总线
 配置 RCC_CLOCKTYPE_PCLK1 时钟,对应 APB1 总线
 配置 RCC_CLOCKTYPE_PCLK2 时钟,对应 APB2 总线
 配置 RCC_CLOCKTYPE_D1PCLK1 时钟,对应 APB3 总线
 配置 RCC_CLOCKTYPE_D3PCLK1 时钟,对应 APB4 总线 
 */
RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 |
RCC_CLOCKTYPE_PCLK1 | \
RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1);
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; 
/* 此函数会更新 SystemCoreClock,并重新配置 HAL_InitTick */
ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
if(ret != HAL_OK)
{
 Error_Handler(__FILE__, __LINE__);
}

源文件 sttm32h7xx_hal_cortex.c

这个库文件主要功能是 NVIC,MPU 和 Systick 的配置。此文件有个臃肿的地方,里面的 API 其实就
是将 ARM 的 CMSIS 库各种 API 重新封装了一遍。这么做的好处是保证了 HAL 的 API 都是以字母 HAL
开头。

MPU 单元

MPU 可以将 memory map 内存映射区分为多个具有一定访问规则的区域,通过这些规则可以实现
如下功能:
◆ 防止不受信任的应用程序访问受保护的内存区域。
◆ 防止用户应用程序破坏操作系统使用的数据。
◆ 通过阻止任务访问其它任务的数据区。
◆ 允许将内存区域定义为只读,以便保护重要数据。
◆ 检测意外的内存访问。
简单的说就是内存保护、外设保护和代码访问保护

MPU 可以配置保护 16 个内存区域(这 16 个内存域是独立配置的),每个区域最小要求 256 字节,
每个区域还可以配置为 8 个子区域。由于子区域一般都相同大小,这样每个子区域的大小就是 32 字节,正好跟 Cache 的 Cache Line 大小一样。
MPU 可以配置的 16 个内存区的序号范围是 0 到 15,还有默认区 default region,也叫作背景区,序号-1。由于这些内存区可以嵌套和重叠,所以这些区域在嵌套或者重叠的时候有个优先级的问题。序号15 的优先级最高,以此递减,序号-1,即背景区的优先级最低。这些优先级是固定的。下面通过一个具体的实例帮助大家理解。如下所示共有 7 个区,背景区和序号 0-5 的区。内存区 4 跟内存区 0 和 1 有重叠部分,那么重叠部分将按照内存区 4 的配置规则执行;内存区 5 被完全包含在内存区3 里面,那么这部分内存区将按照内存区 5 的配置规则执行。

函数 HAL_MPU_ConfigRegion()

== 函数原型:==

void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init)
{
 /* 部分省略未写 */
 /* Set the Region number */
 MPU->RNR = MPU_Init->Number;
 if ((MPU_Init->Enable) != RESET)
 {
 /* 部分省略未写 */
 MPU->RBAR = MPU_Init->BaseAddress;
 MPU->RASR = ((uint32_t)MPU_Init->DisableExec << MPU_RASR_XN_Pos) |
 ((uint32_t)MPU_Init->AccessPermission << MPU_RASR_AP_Pos) |
 ((uint32_t)MPU_Init->TypeExtField << MPU_RASR_TEX_Pos) |
 ((uint32_t)MPU_Init->IsShareable << MPU_RASR_S_Pos) |
 ((uint32_t)MPU_Init->IsCacheable << MPU_RASR_C_Pos) |
 ((uint32_t)MPU_Init->IsBufferable << MPU_RASR_B_Pos) |
 ((uint32_t)MPU_Init->SubRegionDisable << MPU_RASR_SRD_Pos) |
 ((uint32_t)MPU_Init->Size << MPU_RASR_SIZE_Pos) |
 ((uint32_t)MPU_Init->Enable << MPU_RASR_ENABLE_Pos);
 }
 else
 {
 MPU->RBAR = 0x00;
 MPU->RASR = 0x00;
 }
}

函数参数
此函数的形参是一个 MPU_Region_InitTypeDef 类型的结构体变量,定义如下:

typedef struct
{
 uint8_t Enable; 
 uint8_t Number; 
 uint32_t BaseAddress; 
 uint8_t Size; 
 uint8_t SubRegionDisable; 
 uint8_t TypeExtField; 
 uint8_t AccessPermission; 
 uint8_t DisableExec; 
 uint8_t IsShareable; 
 uint8_t IsCacheable; 
 uint8_t IsBufferable; 
}MPU_Region_InitTypeDef;

STM32H7 的 Cache

当前芯片厂商出的 M7 内核芯片基本都做了一级 Cache 支持,Cache 又分数据缓存 D-Cache 和指令
缓冲 I-Cache,STM32H7 的数据缓存和指令缓存大小都是 16KB。对于指令缓冲,用户不用管,这里主
要说的是数据缓存 D-Cache。以 STM32H7 为例,主频是 400MHz,除了 TCM 和 Cache 以 400MHz
工作,其它 AXI SRAM,SRAM1,SRAM2 等都是以 200MHz 工作。数据缓存 D-Cache 就是解决 CPU
加速访问 SRAM。
STM32 H7系列学习笔记-LMLPHP

如果每次 CPU 要读写 SRAM 区的数据,都能够在 Cache 里面进行,自然是最好的,实现了 200MHz
到 400MHz 的飞跃,实际是做不到的,因为数据 Cache 只有 16KB 大小,总有用完的时候。
对于使能了 Cache 的 SRAM 区,要分读写两种情况考虑。

读操作:

如果 CPU 要读取的 SRAM 区数据在 Cache 中已经加载好,这就叫读命中(Cache hit),如果 Cache
里面没有怎么办,这就是所谓的读 Cache Miss。

写操作:

如果 CPU 要写的 SRAM 区数据在 Cache 中已经开辟了对应的区域(专业词汇叫 Cache Line,以 32
字节为单位),这就叫写命中(Cache hit),如果 Cache 里面没有开辟对应的区域怎么办,这就是所谓的写 Cache Miss。

总结:

  1. Cortex-M7 内核的 L1 Cache 由多行内存区组成,每行有 32 字节,每行都配有一个地址标签。数据
    缓冲 DCache 是每 4 行为一组,称为 4-way set associative。而指令缓冲区 ICache 是 2 行为一组,
    这样节省地址标签,不用每个行都标记一个地址。
  2. 对于读操作,只有在第 1 次访问指定地址时才会加载到 Cache,而写操作的话,可以直接写到内存中
    (write-through 模式)或者放到 Cache 里面,后面再写入(write-back 模式)。
  3. 如果采用的是 Write back,Cache line 会被标为 dirty,等到此行被 evicted 时,才会执行实际的写
    操作,将 Cache Line 里面的数据写入到相应的存储区。
  4. Cache 命中是访问的地址落在了给定的 Cache Line 里面,所以硬件需要做少量的地址比较工作,以
    检查此地址是否被缓存。如果命中了,将用于缓存读操作或者写操作。如果没有命中,则分配和标记
    新行,填充新的读写操作。如果所有行都分配完毕了,Cache 控制器将支持 eviction 操作。根据 Cache
    Line 替换算法,一行将被清除 Clean,无效化 Invalid 或者重新配置。数据缓存和指令缓存是采用的
    伪随机替换算法。
  5. Cache 支持的 4 种基本操作,使能,禁止,清空和无效化。Clean 清空操作是将 Cache Line 中标记
    为 dirty 的数据写入到内存里面,而无效化 Invalid 是将 Cache Line 标记为无效,即删除操作。

面对繁冗复杂的 Cache 配置,推荐方式和安全隐患解决办法

◆ 推荐使用 128KB 的 TCM 作为主 RAM 区,其它的专门用于大缓冲和 DMA 操作等。
◆ Cache 问题主要是 CPU 和 DMA 都操作这个缓冲区时容易出现,使用时要注意。
◆ Cache 配置的选择,优先考虑的是 WB,然后是 WT 和关闭 Cache,其中 WB 和 WT 的使用中可以
配合 ARM 提供的函数解决上面说到的隐患问题(见本章 24.6 小节)。但不是万能的,在不起作用的
时候,直接暴力选择函数 SCB_CleanInvlaidateDCache 解决。关于这个问题,在分别配置以太网
MAC 的描述符缓冲区,发送缓冲区和接收缓冲区时尤其突出。

Cache 的相关函数

CMSIS 软件包的 core_cm7.h 文件为 Cache 的配置提供了 11 个函数:
◆ SCB_EnableICache
◆ SCB_DisableICache
◆ SCB_InvalidateICache
◆ SCB_EnableDCache
◆ SCB_DisableDCache
◆ SCB_InvalidateDCache
◆ SCB_CleanDCache
◆ SCB_CleanInvalidateDCache
◆ SCB_InvalidateDCache_by_Addr
◆ SCB_CleanDCache_by_Addr
◆ SCB_CleanInvalidateDCache_by_Addr
其中前三个函数是指令 Cache,比较容易掌握。重点是后面几个数据 Cache 函数。由于函数 SCB_CleanInvalidateDCache,SCB_CleanDCache 和 SCB_InvalidateDCache是对整个 Cache 的操作,所以比最后的三个函数 SCB_InvalidateDCache_by_Addr,SCB_CleanDCache_by_Addr 和 SCB_CleanInvalidateDCache_by_Addr 要耗时,当然,如果用户操作的存储器超过了数据 Cache 的大小,即 16KB,那么就跟前三个函数没有区别了。

STM32H7 的 TCM,SRAM 等五块内存基础知识

◆ TCM : Tightly-Coupled Memory 紧密耦合内存 。ITCM 用于指令,DTCM 用于数据,特点是跟内
核速度一样,而片上 RAM 的速度基本都达不到这个速度。
STM32 H7系列学习笔记-LMLPHP
◆ ITCM 和 DTCM
这两个是直连 CPU 的。
◆ D1 Domain
D1 域中的各个外设是挂在 64 位 AXI 总线组成 67 的矩阵上。
⚫ 6 个从接口端 ASIB1 到 ASIB6
外接的主控是 LTDC,DMA2D,MDMA,SDMMC1,AXIM 和 D2-to-D1 AHB 总线。
⚫ 7 个主接口端 AMIB1 到 AMIB7
外接的从设备是 AHB3 总线,Flash A,Flash B,FMC 总线,QSPI 和 AXI SRAM。另外 AHB3
也是由 AXI 总线分支出来的,然后再由 AHB3 分支出 APB3 总线。
◆ D2 Domain
D2 域的各个外设是挂在 32 位 AHB 总线组成 10
9 的矩阵上。
⚫ 10 个从接口
外接的主控是 D1-to-D2 AHB 总线,AHBP 总线,DMA1,DMA2,Ethernet MAC,SDMMC2,
USB HS1 和 USB HS2。
⚫ 9 个主接口
外接的从设备是 SRAM1,SRMA2,SRAM3,AHB1,AHB2,APB1,APB2,D2-to-D1 AHB
总线和 D2-to-D3 AHB 总线。
◆ D3 Domain
D3 域的各个外设是挂在 32 位 AHB 总线组成 3*2 的矩阵上。
⚫ 3 个从接口
外接的主控 D1-to-D3 AHB 总线,D2-to-D3 AHB 总线和 BDMA。
⚫ 2 个主接口
外接的从设备是 AHB4,SRAM4 和 Bckp SRAM。另外 AHB4 也是这个总线矩阵分支出来的,
然后再由 AHB4 分支出 APB4 总线

各块 RAM 特性

TCM 区

TCM : Tightly-Coupled Memory 紧密耦合内存 。ITCM 用于运行指令,也就是程序代码,DTCM
用于数据存取,特点是跟内核速度一样,而片上 RAM 的速度基本都达不到这个速度,所以有降频处
理。
速度:400MHz。
DTCM 地址:0x2000 0000,大小 128KB。
ITCM 地址:0x0000 0000,大小 64KB。

AXI SRAM 区

位于 D1 域,数据带宽是 64bit,挂在 AXI 总线上。除了 D3 域中的 BDMB 主控不能访问,其它都可
以访问此 RAM 区。
速度:200MHz。
地址:0x2400 0000,大小 512KB。
用途:用途不限,可以用于用户应用数据存储或者 LCD 显存。

SRAM1,SRAM2 和 SRAM3 区

位于 D2 域,数据带宽是 32bit,挂在AHB 总线上。除了 D3域中的 BDMB主控不能访问这三块SRAM,
其它都可以访问这几个 RAM 区。
速度:200MHz。
SRAM1:地址 0x3000 0000,大小 128KB,用途不限,可用于 D2 域中的 DMA 缓冲,也可以当
D1 域断电后用于运行程序代码。
SRAM2:地址 0x3002 0000,大小 128KB,用途不限,可用于 D2 域中的 DMA 缓冲,也可以用于
用户数据存取。
SRAM3:地址 0x3004 0000,大小 32KB,用途不限,主要用于以太网和 USB 的缓冲。

SRAM4 区

位于 D3 域,数据带宽是 32bit,挂在 AHB 总线上,大部分主控都能访这块 SRAM 区。
速度:200MHz。
地址:0x3800 0000,大小 64KB。
用途:用途不限,可以用于 D3 域中的 DMA 缓冲,也可以当 D1 和 D2 域进入 DStandby 待机方式
后,继续保存用户数据。

Backup SRAM 区

备份 RAM 区,位于 D3 域,数据带宽是 32bit,挂在 AHB 总线上,大部分主控都能访问这块 SRAM
区。
速度:200MHz。
地址:0x3880 0000,大小 4KB。
用途:用途不限,主要用于系统进入低功耗模式后,继续保存数据(Vbat 引脚外接电池)。

串口的 HAL 库用法

串口的 HAL 库用法其实就是几个结构体变量成员的配置和使用,然后配置 GPIO、时钟,并根据需要
配置 NVIC、中断和 DMA。
HAL 库在 USART_TypeDef 的基础上封装了一个结构体 UART_HandleTypeDef,定义如下:

typedef struct
{
 USART_TypeDef *Instance; /*!< UART registers base address */
 UART_InitTypeDef Init; /*!< UART communication parameters */
 UART_AdvFeatureInitTypeDef AdvancedInit; /*!< UART Advanced Features initialization parameters */
 uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
 uint16_t TxXferSize; /*!< UART Tx Transfer size */
 __IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
 uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
 uint16_t RxXferSize; /*!< UART Rx Transfer size */
 __IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
 uint16_t Mask; /*!< UART Rx RDR register mask */
 DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
 DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
 HAL_LockTypeDef Lock; /*!< Locking object */
 __IO HAL_UART_StateTypeDef gState; /*!< UART state information related to global Handle management
 and also related to Tx operations.
 This parameter can be a value of @ref HAL_UART_StateTypeDef */
 __IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations.
 This parameter can be a value of @ref HAL_UART_StateTypeDef */
 __IO uint32_t ErrorCode; /*!< UART Error code */
}UART_HandleTypeDef;

UART_InitTypeDef 结构体的定义如下:

typedef struct
{
 uint32_t BaudRate; /* 波特率 */
 uint32_t WordLength; /* 数据位长度 */
 uint32_t StopBits; /* 停止位 */ 
 uint32_t Parity; /* 奇偶校验位 */ 
 uint32_t Mode; /* 发送模式和接收模式使能 */ 
 uint32_t HwFlowCtl; /* 硬件流控制 */ 
 uint32_t OverSampling; /* 过采样,可以选择 8 倍和 16 倍过采样 */ 
 uint32_t Prescaler; /* 串口分频 */ 
 uint32_t FIFOMode; /* 串口 FIFO 使能 */ 
 uint32_t TXFIFOThreshold; /* 发送 FIFO 的阀值 */ 
 uint32_t RXFIFOThreshold; /* 接收 FIFO 的阀值 */ 
}UART_InitTypeDef;

UART_AdvFeatureInitTypeDef AdvancedInit(这个参数用于配置串口的高级特性)具体支持的功能参数如下:

typedef struct
{
 uint32_t AdvFeatureInit; /* 初始化的高级特性类别 */
 uint32_t TxPinLevelInvert; /* Tx 引脚电平翻转 */
 uint32_t RxPinLevelInvert; /* Rx 引脚电平翻转 */
 uint32_t DataInvert; /* 数据逻辑电平翻转 */
 uint32_t Swap; /* Tx 和 Rx 引脚交换 */
 uint32_t OverrunDisable; /* 接收超时检测禁止 */
 uint32_t DMADisableonRxError; /* 接收出错,禁止 DMA */
 uint32_t AutoBaudRateEnable; /* 自适应波特率使能 */
 uint32_t AutoBaudRateMode; /* 自适应波特率的四种检测模式选择 */
 uint32_t MSBFirst; /* 发送或者接收数据时,高位在前 */
} UART_AdvFeatureInitTypeDef;

示例

配置串口参数,其实就是配置结构体 UART_HandleTypeDef 的成员。比如下面配置为波特率 115200,8个数据位,无奇偶校验,1 个停止位。

UART_HandleTypeDef UartHandle;
/* USART3 工作在 UART 模式 */
/* 配置如下:
 - 数据位 = 8 Bits
 - 停止位 = 1 bit
 - 奇偶校验位 = 无
 - 波特率 = 115200bsp
 - 硬件流控制 (RTS 和 CTS 信号) */
UartHandle.Instance = USART3;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if(HAL_UART_Init(&UartHandle) != HAL_OK)
{
Error_Handler();
}

串口外设的基本参数配置完毕后还不能使用,还需要配置 GPIO、时钟、中断等参数,比如下面配置
串口 1,使用引脚 PA9 和 PA10。

/* 串口 1 的 GPIO PA9, PA10 */
#define USART1_CLK_ENABLE()			 __HAL_RCC_USART1_CLK_ENABLE()
#define USART1_TX_GPIO_CLK_ENABLE()	 __HAL_RCC_GPIOA_CLK_ENABLE()
#define USART1_TX_GPIO_PORT 	GPIOA
#define USART1_TX_PIN 			GPIO_PIN_9
#define USART1_TX_AF 			GPIO_AF7_USART1
/*
*********************************************************************************************************
* 函 数 名: InitHardUart
* 功能说明: 配置串口的硬件参数和底层
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void InitHardUart(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
#if UART1_FIFO_EN == 1 /* 串口 1 */
/* 使能 GPIO TX/RX 时钟 */
USART1_TX_GPIO_CLK_ENABLE();
USART1_RX_GPIO_CLK_ENABLE();
/* 使能 USARTx 时钟 */
USART1_CLK_ENABLE();
/* 配置 TX 引脚 */
GPIO_InitStruct.Pin = USART1_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = USART1_TX_AF;
HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct);
/* 配置 RX 引脚 */
GPIO_InitStruct.Pin = USART1_RX_PIN;
GPIO_InitStruct.Alternate = USART1_RX_AF;
HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);
/* 配置 NVIC the NVIC for UART */ 
HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USART1_IRQn);
 
/* 配置波特率、奇偶校验 */
bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);
SET_BIT(USART1->ICR, USART_ICR_TCCF); /* 清除 TC 发送完成标志 */
SET_BIT(USART1->RQR, USART_RQR_RXFRQ); /* 清除 RXNE 接收标志 */
SET_BIT(USART1->CR1, USART_CR1_RXNEIE);/* 使能 PE. RX 接受中断 */
#endif
}

串口的状态标志清除问题

__HAL_USART_GET_FLAG 函数。这个函数用来检查 USART 标志位是否被设置。

/** @brief Check whether the specified USART flag is set or not.
 * @param __HANDLE__: specifies the USART Handle
 * @param __FLAG__: specifies the flag to check.
 * This parameter can be one of the following values:
 * @arg USART_FLAG_TXFT: TXFIFO threshold flag
 * @arg USART_FLAG_RXFT: RXFIFO threshold flag
 * @arg USART_FLAG_RXFF: RXFIFO Full flag
 * @arg USART_FLAG_TXFE: TXFIFO Empty flag
 * @arg USART_FLAG_REACK: Receive enable ackowledge flag
* @arg USART_FLAG_TEACK: Transmit enable ackowledge flag
 * @arg USART_FLAG_BUSY: Busy flag
 * @arg USART_FLAG_TXE: Transmit data register empty flag
 * @arg USART_FLAG_TC: Transmission Complete flag
 * @arg USART_FLAG_RXNE: Receive data register not empty flag
 * @arg USART_FLAG_IDLE: Idle Line detection flag
 * @arg USART_FLAG_ORE: OverRun Error flag
 * @arg USART_FLAG_UDR: UnderRun Error flag
 * @arg USART_FLAG_NE: Noise Error flag
 * @arg USART_FLAG_FE: Framing Error flag
 * @arg USART_FLAG_PE: Parity Error flag
 * @retval The new state of __FLAG__ (TRUE or FALSE).
 */
#define __HAL_USART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->ISR & (__FLAG__)) == (__FLAG__))

USART 标志是需要软件主动清零的。清零有两种方式:一种是调用__HAL_USART_CLEAR_FLAG 函数,另一种是操作相关寄存器后自动清零。

/** @brief Clear the specified USART pending flag.
 * @param __HANDLE__: specifies the USART Handle.
 * @param __FLAG__: specifies the flag to check.
 * This parameter can be any combination of the following values:
 * @arg USART_FLAG_TXFT: TXFIFO threshold flag
 * @arg USART_FLAG_RXFT: RXFIFO threshold flag
 * @arg USART_FLAG_RXFF: RXFIFO Full flag
 * @arg USART_FLAG_TXFE: TXFIFO Empty flag
 * @arg USART_FLAG_REACK: Receive enable ackowledge flag
 * @arg USART_FLAG_TEACK: Transmit enable ackowledge flag
 * @arg USART_FLAG_WUF: Wake up from stop mode flag
 * @arg USART_FLAG_RWU: Receiver wake up flag (is the USART in mute mode)
 * @arg USART_FLAG_SBKF: Send Break flag
 * @arg USART_FLAG_CMF: Character match flag
 * @arg USART_FLAG_BUSY: Busy flag
 * @arg USART_FLAG_ABRF: Auto Baud rate detection flag
 * @arg USART_FLAG_ABRE: Auto Baud rate detection error flag
 * @arg USART_FLAG_RTOF: Receiver timeout flag
 * @arg USART_FLAG_LBD: LIN Break detection flag
 * @arg USART_FLAG_TXE: Transmit data register empty flag
 * @arg USART_FLAG_TC: Transmission Complete flag
 * * @arg USART_FLAG_RXNE: Receive data register not empty flag
 * @arg USART_FLAG_IDLE: Idle Line detection flag
 * @arg USART_FLAG_ORE: OverRun Error flag
 * @arg USART_FLAG_NE: Noise Error flag
 * @arg USART_FLAG_FE: Framing Error flag
 * @arg USART_FLAG_PE: Parity Error flag
 * @retval The new state of __FLAG__ (TRUE or FALSE).
 */
#define __HAL_USART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->ICR = (__FLAG__))

RS485 的基础知识

关于 RS485 的逻辑状态,不同厂家的芯片的定义可能不同,但不影响正常的数据收发,这里以 TI 的
为例做个说明,TI 的定义方式如下:
A 表示非反向输出 non-inverting output,B 表示反向输出 inverting output。
当 VA > VB 的时候表示逻辑状态 0,被称为 ON。
当 VA < VB 的时候表示逻辑状态 1,被称为 OFF。
STM32 H7系列学习笔记-LMLPHP
对应到实际芯片框图上就是下面这样(DE 发送使能,D 是发送数据端,RE 是接收使能,R 是接收数据端)
STM32 H7系列学习笔记-LMLPHP
当用户在 D(Driver)引脚输入逻辑高电平时,将在 485 总线上实现逻辑状态 0,即 ON 状态。接收端 R
(Receiver)将收到逻辑高电平。
当用户在 D(Driver)引脚输入逻辑低电平时,将在 485 总线上实现逻辑状态 1,即 OFF 状态。接收端 R
(Receiver)将收到逻辑低电平。

FMC 基础知识

FMC 的几个关键知识点放在开头说:
◆ STM32H7 的 FMC 总线是挂载 64 位带宽的 AXI 总线上,F1,F4 和 F7 是挂在 32 位总线上。
◆ 使用 FMC,可以用来外挂 NOR/PSRAM 型存储器,SRAM 型存储器,NAND 型存储器,SDRAM
存储器等,从而可以用来驱动 AD7606,OLED,DM9000 等并行控制设备。
◆ 支持 8 位,16 位和 32 位总线带宽控制。
◆ 每个片选下的存储器空间配置都是独立的,有专门的寄存器,互不影响。

FMC 时钟选择

STM32 H7系列学习笔记-LMLPHP

FMC地址区域

FMC 总线可操作的地址范围 0x60000000 到 0xDFFFFFFF。
STM32 H7系列学习笔记-LMLPHP
与 F1 和 F4 不同,H7 系列的 FMC 总线接口支持重映射,也就是可以设置这几块存储器的位置。
STM32 H7系列学习笔记-LMLPHP
◆ 对于 NOR/PSRAM/SRAM 块区。
这个块区用到的地方最多,像 NAND 和 SDRAM 块区基本只能接 NAND 和 SDRAM,而
NOR/PSRAM/SRAM 区就不同了,除了能接这几种类型的存储器,还可以外接 DM9000,SDRAM,OLED,AD7606 等总线外设。这个块区有 4 路片选,分别是 FMC_NE1,FMC_NE2,FMC_NE3 和 FMC_NE4,这几个片选在芯片上都有对应的引脚,每个片选可以管理 64MB 的访问空间,这个是由 FMC 引出的 26路地址线 FMC_A[0:25]决定的,2^26 = 64MB。
⚫ FMC_NE1:首地址 0x6000 0000,可以管理的地址范围 0x6000 0000 到 0x63FF FFFF。
⚫ FMC_NE2:首地址 0x6400 0000,可以管理的地址范围 0x6400 0000 到 0x67FF FFFF。
⚫ FMC_NE3:首地址 0x6800 0000,可以管理的地址范围 0x6800 0000 到 0x6BFF FFFF。
⚫ FMC_NE4:首地址 0x6C00 0000,可以管理的地址范围 0x6C00 0000 到 0x6FFF FFFF。

NOR/PSRAM/SRAM 时序控制

F103 和 F407 仅支持 16 位总线访问,等到 F429,H7 已经支持 32 位总线访问。以驱动 SRAM 为例,
需要用到下面的数据,地址和控制引脚。配置完毕后,就可以像使用内部 SRAM 一样进行读写了,使用
比较方便。
STM32 H7系列学习笔记-LMLPHP

NOR/PSARM/SRAM 时序配置结构体

FMC_NORSRAM_TimingTypeDef

typedef struct
{
 uint32_t AddressSetupTime; //此参数用于设置地址建立时间,单位 FMC 时钟周期个数,范围 0 -15。同步 NOR Flash 用不到此参
数。
 uint32_t AddressHoldTime; //此参数用于设置地址持续时间,单位 FMC 时钟周期个数,范围 1 -15。同步 NOR Flash 用不到此参
数。
 uint32_t DataSetupTime; //此参数用于设置数据建立时间,单位 FMC 时钟周期个数,范围 1 -255。用于 SRAM,异步多路复用
 uint32_t BusTurnAroundDuration; //此参数用于设置总线 TurnAround(总线周转阶段)持续时间,单位 FMC 时钟周期个数,范围 0 -15。仅用于多路复用 NOR Flash。
 uint32_t CLKDivision; //此参数用于设置时钟分频,范围 2 -16,仅用于同步器件。
 uint32_t DataLatency; //对于使能了读/写突发模式的同步访问,此参数定义了读写首个数据前要发送给存储器的时钟周期个数。
 	//操作 CRAM,此参数必须为 0。
	//异步 NOR/PSRAM/SRAM 器件用不到此参数。
	//使能了同步突发模式的 NOR Flash,此参数的范围是 2 – 17,单位 FMC 时钟周期个数。 
 uint32_t AccessMode; //用于设置 FMC 的访问模式
}FMC_NORSRAM_TimingTypeDef;
04-11 07:23