首页 > 其他分享 >SPI协议——结合百问网STM32入门 STM32 HAL快速入门与项目实战视频

SPI协议——结合百问网STM32入门 STM32 HAL快速入门与项目实战视频

时间:2024-07-24 15:57:28浏览次数:12  
标签:SPIFlash 入门 int void uint8 STM32 SPI HAL

目录

1、SPI协议的概念

2、SPI 的传输模式

2.1 SPI工作模式

2.2 SPI传输模式

2.3 SPI操作方法

3、时序图

4、代码实现

4.1 SPI HAL库编程

4.2 中断方式

4.3 DMA方式函数说明

5、总结

5.1 SPI协议的优点

5.2 SPI协议的缺点


1、SPI协议的概念

SPI(Serial Peripheral Interface,串行外设接口)是由摩托罗拉(Motorola)在 1980 前后提出的一种全双工同步串行通信接口,它用于 MCU 与各种外围设备以串行方式进 行通信以交换信息,通信速度最高可达 25MHz 以上。 SPI 接口主要应用在 EEPROM、FLASH、实时时钟、网络控制器、OLED 显示驱动器、AD 转换器,数字信号处理器、数字信号解码器等设备之间。 SPI 通常由四条线组成,一条主设备输出与从设备输入(Master Output Slave Input, MOSI),一条主设备输入与从设备输出(Master Input Slave Output,MISO),一条时钟信号(Serial Clock,SCLK),一条从设备使能选择(Chip Select,CS)。 SPI 可以一个主机连接单个或多个从机,每个从机都使用一个引脚进行片选,物理连接示意图如图所示:

2、SPI 的传输模式

2.1 SPI工作模式

SPI通讯需要使用4条线,3条总线和1条片选信号线,在 SCK 时钟周期的驱动下,主机把数据驱动到 MOSI 上传给从机,从机把数据驱动到 MISO 上传给主机,物理连接如下图:

主机发送 N 字节给从机时,必定能接收到 N 字节,至于接收到的 N 字节是否有意义由 从机决定。如果主机只想对从机进行写操作,主机只需忽略接收的从机数据即可。如果主 机只想读取从机数据,它也要发送数据给从机(发送的数据可以是空数据)。

2.2 SPI传输模式

SPI 有四种传输模式,如下表所示,主要差别在于 CPOL 和 CPHA 的不同。

  • CPOL(Clock Polarity,时钟极性)表示 SCK 在空闲时为高电平还是低电平。当 CPOL=0,SCK 空闲时为低电平,当 CPOL=1,SCK 空闲时为高电平。

  • CPHA(Clock Phase,时钟相位)表示 SCK 在第几个时钟边缘采样数据。当 CPHA=0, 在 SCK 第一个边沿采样数据,当 CPHA=1,在 SCK 第二个边沿采样数据。

2.3 SPI操作方法

在 SPI 传输里,有 2 个角色:MasterSlave。我们常用 Master,它主动发起传输、 产生时钟信号。 对于 STM32,如何使用 SPI Master? 先初始化:

① 配置 SPI_CR1 寄存器(Control Register):配置波特率 ;

② 配置 SPI_CR1 寄存器(Control Register):配置 CPOL、CPHA ;

③ 配置 SPI_CR1 寄存器(Control Register):配置 LSBFIRT 以决定先传输最高位还是最低位 ;

④ 配置 SPI_CR1 寄存器(Control Register):配置 NSS 使用软件控制,一般使用 GPIO 来 选择其他 Slave 设备,不是 NSS;

⑤ 配置 SPI_CR1 寄存器(Control Register):选择 Master 模式、使能 SPI。

如何发送数据呢?把数据写入“SPI_DR”即可,这个数据会被放入放入“Tx Buffer”, 进而放入“shift register”,它就会一位一位地出现在 MOSI 信号线上,发送给 Slave 设 备。当发送完毕后,TXE 标记位被设置。可以使用查询方式或中断方式监测到 TXE 被置位。 如何接收数据呢?即使我们只想接收数据,也要把一个数组写入“SPI_DR”,在发送数 据的同时就会接收到数据:MISO 信号线上的数据被一位一位地移入“shift register”, 然后被放入“Rx Buffer”并且 RXNE 标记位被置一。可以使用查询方式或中断方式监测到 RXNE 被置位,然后读取“SPI_DR”得到数据。

3、时序图

如下图所示,CPHA=0 时,表示在时钟第一个时钟边沿采样数据。当 CPOL=1,即空闲时为高电平,从高电平变为低电平,第一个时钟边沿(下降沿)即进行采样。当 CPOL=0,即 空闲时为低电平,从低电平变为高电平,第一个时钟边沿(上升沿)即进行采样。

4、代码实现

4.1 SPI HAL库编程

使用查询方式读取SPI设备的函数如下:

/* 发送同时接收数据 */ 
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t 
*pRxData, uint16_t Size, uint32_t Timeout); 
 
/* 发送数据 */ 
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, 
uint32_t Timeout); 
 
/* 接收数据 */ 
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, 
uint32_t Timeout); 

函数中的参数说明:

4.2 中断方式

通过编写spi中断回调函数代码,实现对flash的数据写入以及读取的控制和等待,在main文件中调用函数,从而在oled屏幕上显示出字符串“www.100ask.com”的值。

spi.c

static volatile int g_spi1_tx_complete = 0;
static volatile int g_spi1_rx_complete = 0;
static volatile int g_spi1_txrx_complete = 0;
​
/*
 *SPI发送完成中断回调函数,当SPI1发送完成,将全局变量g_spi1_tx_complete设置成1
*/
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if(hspi == &hspi1)
    {
        g_spi1_tx_complete = 1;
    }
}
/*这是一个等待SPI1发送完成的函数,它会不断检查g_spi1_tx_complete的值,直到它变为1或者超时。
超时时间由参数timeout指定,
每次循环都会延时1毫秒
*/
void Wait_SPI1_TxCplt(int timeout)
{
    while(g_spi1_tx_complete == 0 && timeout--)
    {
        HAL_Delay(1);
    }
    g_spi1_tx_complete = 0;
}
/*
 *SPI接收完成中断回调函数,当SPI1接收完成,将全局变量g_spi1_rx_complete设置成1
*/
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if(hspi == &hspi1)
    {
        g_spi1_rx_complete = 1;
    }
}
/*
这是一个等待SPI1接收完成的函数,它会不断检查g_spi1_rx_complete的值,直到它变为1或者超时。
超时时间由参数timeout指定,每次循环都会延时1毫秒。
*/
void Wait_SPI1_RxCplt(int timeout)
{
    while(g_spi1_rx_complete == 0 && timeout--)
    {
        HAL_Delay(1);
    }
    g_spi1_rx_complete = 0;
}
/*这是一个SPI发送和接收都完成的中断回调函数,当SPI1发送和接收都完成后,
将全局变量g_spi1_txrx_complete设置为1。
*/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if(hspi == &hspi1)
    {
        g_spi1_txrx_complete = 1;
    }
}
​
void Wait_SPI1_TxRxCplt(int timeout)
{
    while(g_spi1_txrx_complete == 0 && timeout--)
    {
        HAL_Delay(1);
    }
    g_spi1_txrx_complete = 0;
}

**driver_spi_flash.c**

#include "driver_spi_flash.h"
​
#include "stm32f1xx_hal.h"
​
#define SPI_FLASH_TIMEOUT 1000
​
void Wait_SPI1_TxCplt(int timeout);
void Wait_SPI1_TxRxCplt(int timeout);
void Wait_SPI1_RxCplt(int timeout);
​
​
extern SPI_HandleTypeDef hspi1;
​
/* 内部函数  */
static void SPIFlash_Select(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
}
​
static void SPIFlash_DeSelect(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
}
​
static int SPIFlash_WriteEnable(void)
{
    uint8_t buf[1] = {0x06};
    SPIFlash_Select();
    HAL_SPI_Transmit_IT(&hspi1, buf, 1);
    Wait_SPI1_TxCplt(SPI_FLASH_TIMEOUT);
    SPIFlash_DeSelect();
}
​
static int SPIFlash_ReadStatus(void)
{
    uint8_t txbuf[2] = {0x05, 0xff};
    uint8_t rxbuf[2] = {0, 0};
    
    SPIFlash_Select();
    HAL_SPI_TransmitReceive_IT(&hspi1, txbuf, rxbuf, 2);
    Wait_SPI1_TxRxCplt(SPI_FLASH_TIMEOUT);
    SPIFlash_DeSelect();
    return rxbuf[1];
}
​
static int SPIFlash_WaitReady(void)
{
    while (SPIFlash_ReadStatus() & 1 == 1);
}
​
/* 对外的函数 */
​
int SPIFlash_ReadID(void)
{
    uint8_t txbuf[2] = {0x9F, 0xff};
    uint8_t rxbuf[2] = {0, 0};
    
    SPIFlash_Select();
    HAL_SPI_TransmitReceive_IT(&hspi1, txbuf, rxbuf, 2);
    Wait_SPI1_TxRxCplt(SPI_FLASH_TIMEOUT);
    SPIFlash_DeSelect();
    return rxbuf[1];
}
​
int SPIFlash_EraseSector(uint32_t addr)
{
    uint8_t txbuf[4] = {0x20};
    
    /* write enable */
    SPIFlash_WriteEnable();
    
    /* erase */
    txbuf[1]= (addr>>16) & 0xff;
    txbuf[2]= (addr>>8) & 0xff;
    txbuf[3]= (addr>>0) & 0xff;
​
    SPIFlash_Select();
    HAL_SPI_Transmit_IT(&hspi1, txbuf, 4);
    Wait_SPI1_TxCplt(SPI_FLASH_TIMEOUT);
    SPIFlash_DeSelect();
    
    /* wait ready */
    SPIFlash_WaitReady();
    
    return 0;
}
​
int SPIFlash_Write(uint32_t addr, uint8_t *datas, uint32_t len)
{
    uint8_t txbuf[4] = {0x02};
    
    /* write enable */
    SPIFlash_WriteEnable();
    
    /* program */
    txbuf[1]= (addr>>16) & 0xff;
    txbuf[2]= (addr>>8) & 0xff;
    txbuf[3]= (addr>>0) & 0xff;
​
    SPIFlash_Select();
    
    /* 发送命令和地址 */
    HAL_SPI_Transmit_IT(&hspi1, txbuf, 4);
    Wait_SPI1_TxCplt(SPI_FLASH_TIMEOUT);
    
    /* 发送数据 */
    HAL_SPI_Transmit_IT(&hspi1, datas, len);
    Wait_SPI1_TxCplt(SPI_FLASH_TIMEOUT);
    
    SPIFlash_DeSelect();
    
    /* wait ready */
    SPIFlash_WaitReady();
    
    return 0;
}
​
int SPIFlash_Read(uint32_t addr, uint8_t *datas, uint32_t len)
{
    uint8_t txbuf[4] = {0x03};
        
    /* read */
    txbuf[1]= (addr>>16) & 0xff;
    txbuf[2]= (addr>>8) & 0xff;
    txbuf[3]= (addr>>0) & 0xff;
​
    SPIFlash_Select();
    
    /* 发送命令和地址 */
    HAL_SPI_Transmit_IT(&hspi1, txbuf, 4);
    Wait_SPI1_TxCplt(SPI_FLASH_TIMEOUT);
    
    /* 读取数据 */
    HAL_SPI_Receive_IT(&hspi1, datas, len);
    Wait_SPI1_RxCplt(SPI_FLASH_TIMEOUT);
    
    SPIFlash_DeSelect();
        
    return 0;
}
​

**driver_spi_flash.h**

#ifndef __DRIVER_SPI_FLASH_H
#define __DRIVER_SPI_FLASH_H
​
#include <stdint.h>
​
int SPIFlash_ReadID(void);
int SPIFlash_EraseSector(uint32_t addr);
int SPIFlash_Write(uint32_t addr, uint8_t *datas, uint32_t len);
int SPIFlash_Read(uint32_t addr, uint8_t *datas, uint32_t len);
​
​
#endif /* __DRIVER_SPI_FLASH_H */

**main.c**

    char *str = "www.100ask.net";
    char flash_buf[20];
    
    SPIFlash_EraseSector(4096);
    SPIFlash_Write(4096, str, strlen(str)+1);
    SPIFlash_Read(4096, flash_buf, 20);
    OLED_PrintString(0, 2, flash_buf);

实现现象

4.3 DMA方式函数说明

使用DMA方式读取SPI设备的函数如下:

/* 发送同时接收数据 */ 
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, 
uint8_t *pRxData, uint16_t Size); 
 
/* 发送数据 */ 
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t 
Size); 
​
/* 接收数据 */ 
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t 
Size); 

函数中参数说明

这几个函数只是启动 SPI 传输,以后完全由 DMA 来传输后续数据。这两个函数返回后, 并不表示传输已经完成,需要在回调函数里判断。 要使用 DMA 方式,还需要使用 STM32CubeMX 配置 DMA,如下:

数据读写完成或者出错调用以下函数:

/* 发送、接收完成回调函数 */ 
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi); 
 
/* 发送完成回调函数 */ 
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi); 
 
/* 接收完成回调函数 */ 
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) 
 
/* 出错回调函数 */ 
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi); 
​

5、总结

5.1 SPI协议的优点

  • SPI通信协议易于理解和实现,硬件接口仅涉及几根信号线,包括时钟线(SCLK)、主设备输出/从设备输入线(MOSI)、主设备输入/从设备输入线(MISO)、片选信号线(SS)。

  • SPI支持全双工通信,允许主设备和从设备同时发送和接收数据。

  • 能够实现高速传输、支持多种工作模式。

5.2 SPI协议的缺点

  • 缺乏流控制:SPI不提供内置的流量控制机制,如RTS(请求发送)和CTS(清除发送),在通信过程中,发送方无法知道接收方是否准备好接受数据,可能会导致数据溢出或丢失。

  • 调试困难:由于SPI是同步通信协议,并且缺乏内置的错误检测和纠错机制,调试SPI通信问题可能比较困难。

  • 共享总线限制:在SPI通信中,所有从设备共享相同的总线,即MISO、MOSI和SCLK线。当多个从设备同时通信时,总线上负载增加,可能会导致信号失真和传输延迟。这种共享总线架构限制了系统的扩展和性能。

标签:SPIFlash,入门,int,void,uint8,STM32,SPI,HAL
From: https://blog.csdn.net/weixin_62897522/article/details/140654755

相关文章

  • 逆向分析学习入门教程(非常详细)零基础入门到精通,看这一篇就够了!_逆向都要学啥
    前沿从本篇起,逆向工厂带大家从程序起源讲起,领略计算机程序逆向技术,了解程序的运行机制,逆向通用技术手段和软件保护技术,更加深入地去探索逆向的魅力。一、程序如何诞生?1951年4月开始在英国牛津郡哈维尔原子能研究基地正式投入使用的英国数字计算机“哈维尔·德卡特伦”,是......
  • vue-快速入门
    Vue前端体系、前后端分离1、概述1.1、简介Vue(发音为/vjuː/,类似view)是一款用于构建用户界面的JavaScript框架。它基于标准HTML、CSS和JavaScript构建,并提供了一套声明式的、组件化的编程模型,可以高效地开发用户界面。无论是简单还是复杂的界面,Vue都可以胜......
  • 学习STM32的SPI总线通信
    学习STM32的SPI总线通信需要了解SPI的基本原理和STM32的库函数使用方法。SPI(SerialPeripheralInterface)是一种全双工的同步串行通信总线,用于在微处理器或微控制器与外围设备之间传输数据。在STM32中,SPI总线通信需要使用SPI外设和相关的库函数。SPI外设包括多个SPI控制器,每个......
  • STM32入门教程:LED闪烁
    STM32是一款流行的微控制器系列,具有广泛的应用领域。在本教程中,我们将介绍如何使用STM32来控制LED灯的闪烁。第一步:准备工作在开始编写代码之前,我们需要准备一些必要的工具和材料。首先,我们需要一款能够编程的STM32微控制器开发板,例如ST-LinkV2。其次,我们需要一个集成开发......
  • 使用STM32实现简单的网络通信
    概述在本文中,我们将介绍如何使用STM32微控制器实现简单的网络通信。我们将使用STM32Cube软件来配置和编程STM32微控制器。我们将使用TCP/IP协议栈,以便在STM32微控制器与计算机之间进行通信。我们将通过创建一个简单的服务器端和一个客户端来演示网络通信的实现。准备工作在......
  • STM32入门教程:智能健康监测
    STM32是一种嵌入式微控制器,是STMicroelectronics公司开发的一款产品。它具有高性能、低功耗、丰富的外设接口等特点,非常适合用于智能健康监测等应用领域。本教程将以智能健康监测为例,详细介绍如何使用STM32进行开发。主要包括以下内容:硬件准备开发环境搭建传感器使用......
  • STM32F103C8T6与LD3320进行串口通讯控制LED灯的亮灭
    目录概要整体架构流程技术名词解释技术细节LD3320部分LD3320串口测试32单片机代码部分main.c(要与LD3320保持一致)串口部分概要STM32微控制器与LD3320语言模块通过串口进行数据交互,实现语音识别和控制功能。整体架构流程通过STC-ISP将LD3320部分的代码烧录进LD......
  • Samtec技术科普小课堂 | 一文入门射频连接器~
    【摘要/前言】在本文中,我们将回到基础知识,了解一下什么是射频连接器。如果您是信号完整性专家,请点击阅读原文访问我们的网站视频,通过我们的网络研讨会视频了解教科书上可能找不到的知识。如果您是电气工程领域的新手,或者只是需要更好地了解这种产品类型,请继续阅读,本文将通......
  • Python入门知识点 7--散列类型与字符编码
    1、初识散列类型(无序序列)数据类型分为3种:   前面已经学过了两种类型   1.数值类型:int/float/bool只能存储单个数据      2.序列类型:str/list/tuple,有序的存储多个数据--有序类型,有下标,可以进行索引切片步长操作          3.散列类型......
  • Python入门知识点 6--序列类型的方法
    1、初识序列类型方法序列类型的概念:数据的集合,在序列类型里面可以存放任意的数据也可以对数据进行更方便的操作这个操作就是叫增删改查(crud)(增加(Creat),读取查询(Retrieve),更新(Update),删除(Delete)几个单词的首字母简写)增删改查是操作数据最底层的操作(从本质......