STM32 HAL库开发: 存储器和总线架构
Posted Michael_chemic
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32 HAL库开发: 存储器和总线架构相关的知识,希望对你有一定的参考价值。
系统结构
驱动单元
ICode
Instruction指令。我们写好的程序经过编译之后都是一条条指令,存放在FLASH 中,内核要读取这些指令来执行程序就必须通过ICode 总线,它几乎每时每刻都需要被使用,它是专门用来取指的。该总线将Cortex™-M3内核的指令总线与闪存Flash指令接口相连接。指令预取在此总线上完成。
DCode
DCode 总线是用来取数
的。
-
常量就是固定不变的,用
C 语言中的const 关键字修饰
,是放到内部的FLASH
当中的 -
变量是可变的,不管是全局变量还是局部变量都放在
内部的SRAM
。因为数据可以被Dcode 总线和DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵
来仲裁
,决定哪个总线在取数。该总线将Cortex™-M3内核的DCode总线与闪存存储器的数据接口相连接(常量加载和调试访问)。
System bus
系统总线主要是访问外设的寄存器(register)
,我们通常说的寄存器编程,即读写寄存器都是通过这根系统总线来完成的。此总线连接Cortex™-M3内核的系统总线(外设总线)到总线矩阵,总线矩阵协调着内核和DMA间的访问。
DMA(外设) bus
Direct Memory Access
即直接存储器访问.
使用DMA,cpu就不用兜圈子,所以DMA不占cpu资源.
DMA 总线也主要是用来传输数据,这个数据可以是在某个外设的数据寄存器,可以在SRAM,可以在内部的FLASH。因为数据可以被Dcode 总线和DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过总线矩阵
来仲裁,决定哪个总线在取数. 此总线将DMA的AHB主控接口与总线矩阵相联,总线矩阵协调着CPU的DCode和DMA到 SRAM、闪存和外设的访问。
总线矩阵
总线矩阵
用于主控总线之间的访问仲裁管理,仲裁采用循环调度算法
。 有了总线矩阵,就可以让主设备和从设备进行并行访问,提升了访问效率,同时也降低了功耗。 需要注意的是,虽然总线矩阵使得多个主设备可以并行访问不同的从设备,但在一个定义的时间段内,只有一个主设备拥有总线矩阵的控制权,如果有多个主设备同时出现总线请求时就得进行仲裁。
总线矩阵协调内核系统总线和DMA主控总线之间的访问仲裁,仲裁利用轮换算法。在互联型产品中,总线矩阵包含5个驱动部件(CPU的DCode、系统总线、以太网DMA、DMA1总线和DMA2总线)和3个从部件(闪存存储器接口(FLITF)、SRAM和AHB2APB桥)。在其它产品中总线矩阵包含4个驱动部件(CPU的DCode、系统总线、DMA1总线和DMA2总线)和4个被动部件(闪存存储器接口(FLITF)、SRAM、FSMC和AHB2APB桥)。 AHB外设通过总线矩阵与系统总线相连,允许DMA访问。
参考cortex-M3手册
被动单元
AHB/APB桥(APB)
从AHB 总线延伸出来的两条APB2 和APB1 总线
,上面挂载着STM32 的外设。我们经常说的GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习STM32 的重点,就是要学会编程这些外设去驱动外部的各种设备。
AHB/APB桥(APB) 两个AHB/APB桥在AHB和2个APB总线间提供同步连接。
APB1操作速度限于36MHz
,APB2操作于全速(最高72MHz)。
在每一次复位以后,所有除SRAM和FLITF以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR来打开该外设的时钟。
注意: 当对APB寄存器进行8位或者16位访问时,该访问会被自动转换成32位的访问:桥会自动将8位或者32位的数据扩展以配合32位的向量。
内部的闪存存储器
内部的闪存存储器即FLASH,我们编写好的程序就放在这个地方。内核通过ICode 总线来取里面的指令。
内部的SRAM
内部的SRAM,即我们通常说的RAM,程序的变量,堆栈等的开销都是基于内部的SRAM。内核通过DCode 总线来访问它。
FSMC
FSMC 的英文全称是Flexible static memory controller
,叫灵活的静态的存储器控制器
,是STM32F10xx 中一个很有特色的外设,通过FSMC,我们可以扩展内存,如外部的SRAM,
NANDFLASH(与非门闪存)和NORFLASH(非门闪存)。
注意:FSMC 只能扩展静态的内存,即名称里面的
S:static,不能是动态的内存,比如SDRAM 就不能扩展。
存储器映射 Memory map
存储器本身没有地址,给存储器分配地址的过程叫存储器映射
被控单元的FLASH,RAM,FSMC 和AHB 到APB 的桥(即片上外设),这些功能部件共同排列在一个4GB 的地址空间内。操作时通过C语言访问地址.
存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。
如图,是Cortex-M3存储器映射结构图。
Cortex-M3是32位的内核,因此其PC指针可以指向2^32=4G的地址空间,从0x0000_0000到0xFFFF_FFFF空间。
对比一下Cortex-M3存储器结构:
图中可以很清晰的看到,STM32的存储器结构和Cortex-M3的很相似,stm32还添加了flash和SDRAM.
STM32的存储器地址空间被划分为大小相等的8块区域,每块区域大小为512MB。
对STM32存储器知识的掌握,实际上就是对Flash和SRAM这两个区域知识的掌握。因此,下面将重点描述Flash和SRAM的知识。
寄存器映射
存储器本身没有地址,给存储器分配地址的过程叫存储器映射.
在存储器Block2 这块区域为片上外设
,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器
,这个给已经分配好地址的有特定功能的内存单元
取别名的过程就叫寄存器映射
。
寄存器映射
基地址
偏移地址
寄存器地址访问
//常规写法
#define GPIOB_ODR (unsignedint*)(0x40010c0c)
* GPIOB_ODR = 0xFF;
//类似封装的写法
#define GPIOB_ODR *(unsigned int *)(GPIOB_BASE+0x0C)
//指针套一个指针 基地址+偏移地址 = 0xFFFF
GPIOB_ODR = 0xFF;
ODR:output data register
这样编程效率低,所以诞生了库函数,然后又有基于cmsis的HAL库编程.
STM32的SRAM
以下是STM32参考手册RM0008中的一段原话:
不同类型的STM32单片机的SRAM大小是不一样的,但是他们的起始地址都是0x2000 0000,终止地址都是0x2000 0000+其固定的容量大小。
SRAM的理解比较简单,其作用是:
用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据。
STM32的Flash
STM32的Flash,严格说,应该是Flash模块。
对闪存编程参考STM32F10xxx闪存编程参考手册
.
该Flash模块包括:
Flash主存储块(Main memory)
Flash信息块(Information block)
Flash存储接口寄存器块(Flash memory interface)
三个组成部分分别在0x0000 0000到0xFFFF FFFF不同的区域,如图(小容量的STM32)所示:
图中完全可以看出Flash模块中的三个组成部分在整个存储器中的位置。
具体的内部区域的意义及功能请参见编程手册PM0042,里面很详细。
STM32存储器结构总结
Peripherals:外设的存储器映射,对该区域操作,就是对相应的外设进行操作;
SRAM:运行时临时存放代码的地方;
Flash:存放代码的地方;
System Memory:STM32出厂时自带的你只能使用,不能写或擦除;
Option Bytes:可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现);
后面就可以编写代码、程序运行、寄存器设置、ICP、IAP.
STM32F429开发板用户手册第31章 STM32F429的SPI总线基础知识和HAL库API
最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255
第31章 STM32F429的SPI总线基础知识和HAL库API
本章节为大家讲解SPI(Serial peripheral interface)总线的基础知识和对应的HAL库API。
31.1 初学者重要提示
31.2 SPI总线基础知识
31.3 SPI总线的HAL库用法
31.4 源文件stm32f4xx_hal_spi.c
31.5 总结
31.1 初学者重要提示
- STM32H7的SPI支持4到32bit数据传输,而STM32F1和F4系列仅支持8bit或者16bit。
- STM3F429的主频168MHz时,SPI1、4、5、6最高通信时钟是42MHz,而SPI2和SPI3是21MHz。
- SPI总线的片选引脚SS在单一的主从器件配置下是可选的,一般情况下可以不使用。
- 搜集了几篇质量比较高的SPI总线介绍帖:http://www.armbbs.cn/forum.php?mod=viewthread&tid=96788。
31.2 SPI总线基础知识
31.2.1 SPI总线的硬件框图
认识一个外设,最好的方式就是看它的框图,方便我们快速的了解SPI的基本功能,然后再看手册了解细节。
通过这个框图,我们可以得到如下信息:
- SCK(CK),Serial Clock
此引脚在主机模式下用于时钟输出,从机模式下用于时钟输入。
- MISO(SDI),Master In / Slave Out data
此引脚在从机模式下用于发送数据,主机模式下接收数据。
- MOSI(SDO), Master Out / Slave In data
此引脚在从机模式下用于数据接收,主机模式下发送数据。
- SS(WS), Slave select pin
根据SPI和SS设置,此引脚可用于:
(1) 选择从器件进行通信。
(2) 允许多主模式(可以禁止NSS引脚输出)。
31.2.2 SPI接口的区别和时钟源(SPI1到SPI6)
这个知识点在初学的时候容易忽视,所以我们这里整理下。
- SPI1到SPI6的所在的总线
SPI1,SPI4,SPI5,SPI6在APB2总线,SPI2,SPI3在APB1总线。SPI的最高时钟由这些总线决定的。
- SPI1到SPI6的支持的最高时钟
STM32F429主频在168MHz下,SPI1,SPI4,SPI5,SPI6的最高时钟是84MHz,而SPI2和SPI3是42MHz。这里特别注意一点,SPI工作时最少选择二分频,也就是说SPI1,4,5,6实际通信时钟是42MHz,而SPI2,3是21MHz。
31.2.3 SPI总线全双工,单工和半双工通信
片选信号SS在单一的主从器件配置下是可选的,一般情况下可以不使用。
全双工通信(F4只有一个移位寄存器)
全双工就是主从器件之间同时互传数据,SPI总线的全双工模式接线方式如下:
关于这个接线图要认识到以下几点:
- 注意接线方式,对于主器件来说MISO引脚就是输入端,从器件的MISO是输出端,即Master In / Slave Out data。MOSI也是同样道理。
- 每个时钟信号SCK的作用了,主器件的MISO引脚接收1个bit数据,MOSI引脚输出1个bit数据。
- 这种单一的主从接线模式下,SS引脚可以不使用。
半双工通信
半双工就是同一个时刻只能为一个方向传输数据,SPI总线的半工模式接线方式如下:
关于这个接线图要认识到以下几点:
- 更改通信方式时,要先禁止SPI。
- 主器件的MISO和从器件的MISO不使用,可以继续用作标准GPIO。
- 1KΩ的接线电阻很有必要,因为当主器件和从器件的通信方向不是同步变化时,容易出现其中一个输出低电平,另一个输出高电平,造成短路。
- 这种单一的主从接线模式下,SS引脚可以不使用
单工模式
单工就是只有一种通信方向,即发送或者接收,SPI总线的单工模式接线方式如下:
关于这个接线图要认识到以下几点:
- 未用到的MOSI或者MISO可以用作标准GPIO。
- 这种单一的主从接线模式下,SS引脚可以不使用。
31.2.4 SPI总线星型拓扑
SPI总线星型拓扑用到的地方比较多,V6开发板就是用的星型拓扑外接多种SPI器件:
关于这个接线图,有以下几点需要大家了解:
- 主器件的SS引脚不使用,使用通用GPIO控制。为每个器件配一个SS引脚,方便单独片选控制。
- 从器件的MISO引脚要配置为复用开漏输出(很多外部芯片在未片选时,数据引脚是呈现高阻态)。
31.2.5 SPI总线通信格式
SPI总线主要有四种通信格式,由CPOL时钟极性和CPHA时钟相位控制:
四种通信格式如下:
- 当CPOL = 1, CPHA = 1时
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
- 当CPOL = 0, CPHA = 1时
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
- 当CPOL = 1, CPHA = 0时
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
- 当CPOL = 0, CPHA = 0时
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
31.3 SPI总线的HAL库用法
31.3.1 SPI总线结构体SPI_TypeDef
SPI总线相关的寄存器是通过HAL库中的结构体SPI_TypeDef定义的,在stm32f429xx.h中可以找到这个类型定义:
typedef struct { __IO uint32_t CR1; /*!< SPI control register 1 (not used in I2S mode), Address offset: 0x00 */ __IO uint32_t CR2; /*!< SPI control register 2, Address offset: 0x04 */ __IO uint32_t SR; /*!< SPI status register, Address offset: 0x08 */ __IO uint32_t DR; /*!< SPI data register, Address offset: 0x0C */ __IO uint32_t CRCPR; /*!< SPI CRC polynomial register (not used in I2S mode), Address offset: 0x10 */ __IO uint32_t RXCRCR; /*!< SPI RX CRC register (not used in I2S mode), Address offset: 0x14 */ __IO uint32_t TXCRCR; /*!< SPI TX CRC register (not used in I2S mode), Address offset: 0x18 */ __IO uint32_t I2SCFGR; /*!< SPI_I2S configuration register, Address offset: 0x1C */ __IO uint32_t I2SPR; /*!< SPI_I2S prescaler register, Address offset: 0x20 */ } SPI_TypeDef;
这个结构体的成员名称和排列次序和CPU的寄存器是一 一对应的。
__IO表示volatile, 这是标准C语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化掉。core_m4.h 文件定义了这个宏:
#define __O volatile /*!< Defines \'write only\' permissions */ #define __IO volatile /*!< Defines \'read / write\' permissions */
下面我们看下SPI的定义,在stm32f429xx.h文件。
#define PERIPH_BASE 0x40000000UL #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL) #define SPI1_BASE (APB2PERIPH_BASE + 0x3000UL) #define SPI2_BASE (APB1PERIPH_BASE + 0x3800UL) #define SPI3_BASE (APB1PERIPH_BASE + 0x3C00UL) #define SPI4_BASE (APB2PERIPH_BASE + 0x3400UL) #define SPI5_BASE (APB2PERIPH_BASE + 0x5000UL) #define SPI6_BASE (APB2PERIPH_BASE + 0x5400UL) #define SPI1 ((SPI_TypeDef *) SPI1_BASE) #define SPI2 ((SPI_TypeDef *) SPI2_BASE) #define SPI3 ((SPI_TypeDef *) SPI3_BASE) #define SPI4 ((SPI_TypeDef *) SPI4_BASE) #define SPI5 ((SPI_TypeDef *) SPI5_BASE) #define SPI6 ((SPI_TypeDef *) SPI6_BASE) <----- 展开这个宏,(FLASH_TypeDef *)0x40015400
我们访问SPI的CR1寄存器可以采用这种形式:SPI->CR1 = 0。
31.3.2 SPI总线初始化结构体SPI_InitTypeDef
下面是SPI总线的初始化结构体,用到的地方比较多:
typedef struct { uint32_t Mode; uint32_t Direction; uint32_t DataSize; uint32_t CLKPolarity; uint32_t CLKPhase; uint32_t NSS; uint32_t BaudRatePrescaler; uint32_t FirstBit; uint32_t TIMode; uint32_t CRCCalculation; uint32_t CRCPolynomial; } SPI_InitTypeDef;
下面将结构体成员逐一做个说明:
- Mode
用于设置工作在主机模式还是从机模式。
#define SPI_MODE_SLAVE (0x00000000U) #define SPI_MODE_MASTER (SPI_CR1_MSTR | SPI_CR1_SSI)
- Direction
用于设置SPI工作在全双工,单工,还是半双工模式。
#define SPI_DIRECTION_2LINES (0x00000000U) #define SPI_DIRECTION_2LINES_RXONLY SPI_CR1_RXONLY #define SPI_DIRECTION_1LINE SPI_CR1_BIDIMODE
- DataSize
用于设置SPI总线数据收发的位宽,支持8bit或者16bit。
#define SPI_DATASIZE_8BIT (0x00000000U) #define SPI_DATASIZE_16BIT SPI_CR1_DFF
- CLKPolarity
用于设置空闲状态时,CLK是高电平还是低电平。
#define SPI_POLARITY_LOW (0x00000000U) #define SPI_POLARITY_HIGH SPI_CR1_CPOL
- NSS
用于设置NSS信号由硬件NSS引脚管理或者软件SSI位管理。
#define SPI_NSS_SOFT SPI_CR1_SSM #define SPI_NSS_HARD_INPUT (0x00000000U) #define SPI_NSS_HARD_OUTPUT (SPI_CR2_SSOE << 16U)
- BaudRatePrescaler
用于设置SPI时钟分频,仅SPI工作在主控模式下起作用,对SPI从机模式不起作用。
#define SPI_BAUDRATEPRESCALER_2 (0x00000000U) #define SPI_BAUDRATEPRESCALER_4 (SPI_CR1_BR_0) #define SPI_BAUDRATEPRESCALER_8 (SPI_CR1_BR_1) #define SPI_BAUDRATEPRESCALER_16 (SPI_CR1_BR_1 | SPI_CR1_BR_0) #define SPI_BAUDRATEPRESCALER_32 (SPI_CR1_BR_2) #define SPI_BAUDRATEPRESCALER_64 (SPI_CR1_BR_2 | SPI_CR1_BR_0) #define SPI_BAUDRATEPRESCALER_128 (SPI_CR1_BR_2 | SPI_CR1_BR_1) #define SPI_BAUDRATEPRESCALER_256 (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
- FirstBit
用于设置数据传输从最高bit开始还是从最低bit开始。
#define SPI_FIRSTBIT_MSB (0x00000000U) #define SPI_FIRSTBIT_LSB SPI_CR1_LSBFIRST
- TIMode
用于设置是否使能SPI总线的TI模式。
#define SPI_TIMODE_DISABLE (0x00000000U) #define SPI_TIMODE_ENABLE SPI_CR2_FRF
- CRCCalculation
用于设置是否使能CRC计算。
#define SPI_CRCCALCULATION_DISABLE (0x00000000U) #define SPI_CRCCALCULATION_ENABLE SPI_CR1_CRCEN
- CRCPolynomial
用于设置CRC计算使用的多项式,必须是奇数,范围0到65535。
31.3.3 SPI总线句柄结构体SPI_HandleTypeDef
下面是SPI总线的初始化结构体,用到的地方比较多:
typedef struct __SPI_HandleTypeDef { SPI_TypeDef *Instance; SPI_InitTypeDef Init; uint8_t *pTxBuffPtr; uint16_t TxXferSize; __IO uint16_t TxXferCount; uint8_t *pRxBuffPtr; uint16_t RxXferSize; __IO uint16_t RxXferCount; void (*RxISR)(struct __SPI_HandleTypeDef *hspi); void (*TxISR)(struct __SPI_HandleTypeDef *hspi); DMA_HandleTypeDef *hdmatx; DMA_HandleTypeDef *hdmarx; HAL_LockTypeDef Lock; __IO HAL_SPI_StateTypeDef State; __IO uint32_t ErrorCode; #if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U) void (* TxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* RxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* ErrorCallback)(struct __SPI_HandleTypeDef *hspi); void (* AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* MspInitCallback)(struct __SPI_HandleTypeDef *hspi); void (* MspDeInitCallback)(struct __SPI_HandleTypeDef *hspi); #endif } SPI_HandleTypeDef;
注意事项:
条件编译USE_HAL_SPI_REGISTER_CALLBACKS用来设置使用自定义回调还是使用默认回调,此定义一般放在stm32f4xx_hal_conf.h文件里面设置:
#define USE_HAL_SPI_REGISTER_CALLBACKS 1
通过函数HAL_SPI_RegisterCallback注册回调,取消注册使用函数HAL_SPI_UnRegisterCallback。
这里重点介绍下面几个参数,其它参数主要是HAL库内部使用和自定义回调函数。
- SPI_TypeDef *Instance
这个参数是寄存器的例化,方便操作寄存器,比如使能SPI1。
SET_BIT(SPI1 ->CR1, SPI_CR1_SPE)。
- SPI_InitTypeDef Init
这个参数是用户接触最多的,在本章节3.2小节已经进行了详细说明。
- DMA_HandleTypeDef *hdmatx
- DMA_HandleTypeDef *hdmarx
用于SPI句柄关联DMA句柄,方便操作调用。
31.4 SPI总线源文件stm32f4xx_hal_spi.c
此文件涉及到的函数较多,这里把几个常用的函数做个说明:
- HAL_SPI_Init
- HAL_SPI_DeInit
- HAL_SPI_TransmitReceive
- HAL_SPI_TransmitReceive_IT
- HAL_SPI_TransmitReceive_DMA
31.4.1 函数HAL_SPI_Init
函数原型:
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi) { /* 检测句柄是否有效 */ if (hspi == NULL) { return HAL_ERROR; } /* 检查参数 */ assert_param(IS_SPI_ALL_INSTANCE(hspi->Instance)); assert_param(IS_SPI_MODE(hspi->Init.Mode)); assert_param(IS_SPI_DIRECTION(hspi->Init.Direction)); assert_param(IS_SPI_DATASIZE(hspi->Init.DataSize)); assert_param(IS_SPI_NSS(hspi->Init.NSS)); assert_param(IS_SPI_BAUDRATE_PRESCALER(hspi->Init.BaudRatePrescaler)); assert_param(IS_SPI_FIRST_BIT(hspi->Init.FirstBit)); assert_param(IS_SPI_TIMODE(hspi->Init.TIMode)); if (hspi->Init.TIMode == SPI_TIMODE_DISABLE) { assert_param(IS_SPI_CPOL(hspi->Init.CLKPolarity)); assert_param(IS_SPI_CPHA(hspi->Init.CLKPhase)); } #if (USE_SPI_CRC != 0U) assert_param(IS_SPI_CRC_CALCULATION(hspi->Init.CRCCalculation)); if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) { assert_param(IS_SPI_CRC_POLYNOMIAL(hspi->Init.CRCPolynomial)); } #else hspi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; #endif if (hspi->State == HAL_SPI_STATE_RESET) { /* 解锁 */ hspi->Lock = HAL_UNLOCKED; #if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U) /* 自定义回调函数 */ hspi->TxCpltCallback = HAL_SPI_TxCpltCallback; /* Legacy weak TxCpltCallback */ hspi->RxCpltCallback = HAL_SPI_RxCpltCallback; /* Legacy weak RxCpltCallback */ hspi->TxRxCpltCallback = HAL_SPI_TxRxCpltCallback; /* Legacy weak TxRxCpltCallback */ hspi->TxHalfCpltCallback = HAL_SPI_TxHalfCpltCallback; /* Legacy weak TxHalfCpltCallback */ hspi->RxHalfCpltCallback = HAL_SPI_RxHalfCpltCallback; /* Legacy weak RxHalfCpltCallback */ hspi->TxRxHalfCpltCallback = HAL_SPI_TxRxHalfCpltCallback; /* Legacy weak TxRxHalfCpltCallback */ hspi->ErrorCallback = HAL_SPI_ErrorCallback; /* Legacy weak ErrorCallback */ hspi->AbortCpltCallback = HAL_SPI_AbortCpltCallback; /* Legacy weak AbortCpltCallback */ if (hspi->MspInitCallback == NULL) { hspi->MspInitCallback = HAL_SPI_MspInit; /* Legacy weak MspInit */ } /* 初始化底层硬件: GPIO, CLOCK, NVIC... */ hspi->MspInitCallback(hspi); #else /* 初始化底层硬件: GPIO, CLOCK, NVIC... */ HAL_SPI_MspInit(hspi); #endif } hspi->State = HAL_SPI_STATE_BUSY; /* 关闭SPI外设 */ __HAL_SPI_DISABLE(hspi); /*----------------------- SPIx CR1 & CR2 配置 ---------------------*/ /* 配置的各种SPI参数 */ WRITE_REG(hspi->Instance->CR1, (hspi->Init.Mode | hspi->Init.Direction | hspi->Init.DataSize | hspi->Init.CLKPolarity | hspi->Init.CLKPhase | (hspi->Init.NSS & SPI_CR1_SSM) |hspi->Init.BaudRatePrescaler | hspi->Init.FirstBit | hspi->Init.CRCCalculation)); /* 配置NSS和TI模式位 */ WRITE_REG(hspi->Instance->CR2, (((hspi->Init.NSS >> 16U) & SPI_CR2_SSOE) | hspi->Init.TIMode)); #if (USE_SPI_CRC != 0U) /*---------------------------- SPIx CRCPOLY 配置 ------------------*/ /* 配置 : CRC 多项式 */ if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) { WRITE_REG(hspi->Instance->CRCPR, hspi->Init.CRCPolynomial); } #endif #if defined(SPI_I2SCFGR_I2SMOD) CLEAR_BIT(hspi->Instance->I2SCFGR, SPI_I2SCFGR_I2SMOD); #endif hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->State = HAL_SPI_STATE_READY; return HAL_OK; }
函数描述:
此函数用于初始化SPI。
函数参数:
- 第1个参数是SPI_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
注意事项:
- 函数HAL_SPI_MspInit用于初始化SPI的底层时钟、引脚等功能。需要用户自己在此函数里面实现具体的功能。由于这个函数是弱定义的,允许用户在工程其它源文件里面重新实现此函数。当然,不限制一定要在此函数里面实现,也可以像早期的标准库那样,用户自己初始化即可,更灵活些。
- 如果形参hspi的结构体成员State没有做初始状态,这个地方就是个坑。特别是用户搞了一个局部变量SPI_HandleTypeDef SpiHandle。
对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个 HAL_SPI_STATE_RESET = 0x00U。
解决办法有三
方法1:用户自己初始串口和涉及到的GPIO等。
方法2:定义SPI_HandleTypeDef SpiHandle为全局变量。
方法3:下面的方法
if(HAL_SPI_DeInit(&SpiHandle) != HAL_OK) { Error_Handler(); } if(HAL_SPI_Init(&SpiHandle) != HAL_OK) { Error_Handler(); }
使用举例:
SPI_HandleTypeDef hspi = {0}; /* 设置SPI参数 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 设置波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全双工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置时钟相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置时钟极性 */ hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置数据宽度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 数据传输先传高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位无效 */ hspi.Init.NSS = SPI_NSS_SOFT; /* 使用软件方式管理片选引脚 */ hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */ /* 初始化SPI */ if (HAL_SPI_Init(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
31.4.2 函数HAL_SPI_DeInit
函数原型:
HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef *hspi) { /* 判断SPI句柄 */ if (hspi == NULL) { return HAL_ERROR; } /* 检查参数 */ assert_param(IS_SPI_ALL_INSTANCE(hspi->Instance)); hspi->State = HAL_SPI_STATE_BUSY; /* 关闭SPI外设时钟 */ __HAL_SPI_DISABLE(hspi); #if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U) if (hspi->MspDeInitCallback == NULL) { hspi->MspDeInitCallback = HAL_SPI_MspDeInit; /* Legacy weak MspDeInit */ } /* 复位底层硬件: GPIO, CLOCK, NVIC... */ hspi->MspDeInitCallback(hspi); #else /* 复位底层硬件: GPIO, CLOCK, NVIC... */ HAL_SPI_MspDeInit(hspi); #endif hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->State = HAL_SPI_STATE_RESET; /* 解锁 */ __HAL_UNLOCK(hspi); return HAL_OK; }
函数描述:
用于复位SPI总线初始化。
函数参数:
- 第1个参数是SPI_HandleTypeDef类型结构体指针变量。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中
31.4.3 函数HAL_SPI_TransmitReceive
函数原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,uint32_t Timeout) { /* 省略未写 */ /* 16bit数据传输 */ if (hspi->Init.DataSize == SPI_DATASIZE_16BIT) { /* 省略未写 */ } /* 8bit数据传输 */ else { /* 省略未写 */ } /* 省略未写 */ }
函数描述:
此函数主要用于SPI数据收发,全双工查询方式。
函数参数:
- 第1个参数是SPI_HandleTypeDef类型结构体指针变量。
- 第2个参数是发送数据缓冲地址。
- 第3个参数是接收数据缓冲地址。
- 第4个参数是传输的数据大小,单位字节个数。
- 第5个参数是传输过程的溢出时间,单位ms。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
使用举例:
SPI_HandleTypeDef hspi = {0}; if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
31.4.4 函数HAL_SPI_TransmitReceive_IT
函数原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size) { /* 省略未写 */ /* 设置传输参数 */ hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->pTxBuffPtr = (uint8_t *)pTxData; hspi->TxXferSize = Size; hspi->TxXferCount = Size; hspi->pRxBuffPtr = (uint8_t *)pRxData; hspi->RxXferSize = Size; hspi->RxXferCount = Size; /* 设置中断处理 */ if (hspi->Init.DataSize > SPI_DATASIZE_8BIT) { hspi->RxISR = SPI_2linesRxISR_16BIT; hspi->TxISR = SPI_2linesTxISR_16BIT; } else { hspi->RxISR = SPI_2linesRxISR_8BIT; hspi->TxISR = SPI_2linesTxISR_8BIT; } #if (USE_SPI_CRC != 0U) /* 复位CRC计算 */ if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) { SPI_RESET_CRC(hspi); } #endif /* 使能TXE, RXNE 和 ERR 中断 */ __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR)); /* 检测SPI是否已经使能 */ if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) { /* 使能SPI外设 */ __HAL_SPI_ENABLE(hspi); } error : /* 解锁 */ __HAL_UNLOCK(hspi); return errorcode; }
函数描述:
此函数主要用于SPI数据收发,全双工中断方式。
函数参数:
- 第1个参数是SPI_HandleTypeDef类型结构体指针变量。
- 第2个参数是发送数据缓冲地址。
- 第3个参数是接收数据缓冲地址。
- 第4个参数是传输的数据大小,单位字节个数。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
使用举例:
SPI_HandleTypeDef hspi = {0}; if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
31.4.5 函数HAL_SPI_TransmitReceive_DMA
函数原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size) { /* 省略未写 */ /* 使能RX DMA */ if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->DR, (uint32_t)hspi->pRxBuffPtr, hspi->RxXferCount)) { } /* 使能RX DMA */ if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR, hspi->TxXferCount)) { } /* 省略未写 */ }
函数描述:
此函数主要用于SPI数据收发,全双工DMA方式。
函数参数:
- 第1个参数是SPI_HandleTypeDef类型结构体指针变量。
- 第2个参数是发送数据缓冲地址。
- 第3个参数是接收数据缓冲地址。
- 第4个参数是传输的数据大小,单位字节个数。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示忙,正在使用中。
使用举例:
SPI_HandleTypeDef hspi = {0}; if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
31.5 总结
本章节就为大家讲解这么多,要熟练掌握SPI总线的查询,中断和DMA方式的实现,因为基于SPI接口的外设芯片很多,熟练后,可以方便的驱动各种SPI接口芯片,以便选择合适的驱动方式。
以上是关于STM32 HAL库开发: 存储器和总线架构的主要内容,如果未能解决你的问题,请参考以下文章
STM32H7教程第91章 STM32H7的FDCAN总线基础知识和HAL库API
STM32+cubemx0011 HAL库开发:I2C总线访问加速度传感器ADXL345