嵌入式软件--STM32 SPI通信(上)

类似于串口、IIC,SPI也是一种通信协议。全称Serial Peripheral interface,串行外围设备接口。

一、SPI概念

1.1物理层

SPI:串行外围设备接口

Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。

SPI接口主要应用于EEPROM,FLASH,各种传感器,AD转换器等。扩展各种外设,传感器,转换器。

高速的,全双工,同步的串行通信总线。同时发同时收。

物理层有四条线,时钟 片选 输入 输出

SCK时钟线,用于通讯的数据同步。由主机产生决定通讯的通讯速率。STM32的SPI时钟频率最大为fpclk/2。两个设备通讯,速率受限于低速设备。

MOSI:主设备输出/从设备输入引脚

MISO:主设备输入/从设备输出

SS:片选线或者使能线。主机要连接多条从设备就需要多条片选线。因为SPI通信时,各设备没有地址,不需要地址码,就是费片选线,有几个从设备就要设置多少片选线。主机的片选线只需要任意决定状态的IO口,从设备有决定状态的片选线,被选定时是固定状态。

1.2协议层

1.2.1数据交换过程

物理层多线,协议层就方便了好多,不需要地址码,基本上就是主从机之间的数据交换。甚至连移位寄存器都可以共用。

主机寄存器左移一位发出去,低位会空出来,从机高位左移一位发出去,正好填补主机寄存器空出来的低位。

移位寄存器临时存储要交换的数据(来自发送缓冲区)。

接收缓冲区存储最终得到的值。

所谓交换,就是一个循环传递。时钟上升沿,主从机左移一位,将高位的值发出,一般高位先行。时钟下降沿,主从机将低位读取接收,存储到低位寄存器的低位。经过8次交换,就能完成一个字节交换,最后从接收缓冲区读取字节。

1.2.2时钟的极性与相位

数据采样和设置时钟的极性和相位有关。

通信的整个过程分为空闲时刻和通信时刻。CPOL:Clock Polarity

空闲状态SCK是低电平,CPOL=0;空闲状态SCK是高电平,CPOL=1

CPHA:clock phase,时钟的相位,直接决定SPI总线从哪个跳变沿开始采样数据。

CPHA=0:表示从第一个跳变沿开始采样;CPHA=1:表示从第2个跳变沿开始采样

通过极性和相位,SPI有四种工作模式。

mode0:空闲状态时为低电平,且在第一个跳变沿采样。就是第一个上升沿采样。

mode1:空闲状态为低电平,且在第二个跳变沿采样。第一个下降沿采样。

mode2:第一个下降沿采样。

mode3:第一个上升沿采样。

下面是模式0和模式2的时序图:

模式3和模式1

很多设备喜欢上升沿采样,也就是模式0和模式3.大多选择模式0.

二、SPI通信_W25Q32芯片

2.1介绍

SPI与IIC一样,可以拓展各种各样的外设。我们可以用SPI扩展一种名为W25Q32的存储器(NOR FLASH:基于或非门构建的flash)

DI:FLASH的输入,连接的是stm32的输出,主设备输出,从设备输入,也就是写入数据。

DO:flash的输出,连接的是stm32的输入,主设备输入,从设备输出,也就是主机在读取flash的数据。

SPI-NSS是从设备选择,主设备连接从设备时,不需要连接自己的片选线,随便用一个IO口连接从设备的片选线。

flash作为从设备,CS连接主机stm32的一个IO口。

flash芯片只支持模式0和模式3;写的时候必须是先擦除,擦除后再写入;移位是高位优先。

这个flash有32M bit,也就是4M字节.

2.2读写要求与存储结构

2.2.1状态寄存器

Status Register 状态寄存器表示当前能不能写入。他有一个关键的位叫Busy,置一时不允许写入和读写,只能查询。

和EEprom一样,也需要页写地址,字节地址。那么4M字节需要多少位来表示?一个字节8位,三个位可以表示其字节,像000 111 010 110 101 100......,三位地址可以表示8这个字节,而8是2的立方,所以可以三位表示。4M字节是2的22次方,所以需要22位来表示极限。22个1位写成十六进制就是0x3fffffh,这个地址线太大了。我们可以划分区块,一块64kB可以划分64块。64k=2^6*2^10=2^16.

block 0范围:0x000000h~0x00ffffh

block 1范围:0x010000h~0x01ffffh

22位地址:前六位块号 4位段号 4位页号 8位页内地址

2.2.2写入操作和读取操作

写入注意事项:

(1)写入操作前,必须先进行写使能。

(2)每个数据位只能由1改写为0,不能由0改写为1.

(3)写入数据前必须先擦除,擦除后,所有数据位变为1,擦除必须按最小擦除单元进行。

(4)连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。

(5)写入操作结束后,芯片进入忙状态(busy),不响应新的读写操作。

读取注意事项:

(1)直接调用读取时序,无需读使能,无需额外操作,没有页的限制。

(2)读取操作结束后不会进入忙状态,但不能在忙状态时读取。

三、W25Q32芯片读写指令

READ DATA:

四、通信案例1------软件模拟SPI读写flash_框架搭建和读取ID

4.1交换数据时序(核心)

一般使用模式0

ss低电平状态开始工作,时序结束后高电平结束工作。

4.2软件模拟SPI读写flash

4.2.1需求描述

基于寄存器操作,使用软件模拟SPI协议,并完成读写FLash。

4.2.2硬件电路设计

主机的PC13gpio口执行电平状态选择要通信的从设备flash.

GPIO口 PA5模拟时钟线SCK,PA6模拟MISO,PA7模拟MOSI.

4.2.3软件设计(寄存器)

因为时软件模拟,这个工程与IIC相似,所以可以复制之前的IIC软件模拟工程。

在硬件层文件夹里创建SPI文件夹,里面创建spi.c和spi.h文件。接口层里创建FLASH文件夹,里面创建w25q32.c和w25q32.h文件。

复制代码
#include "spi.h"

// 初始化
void SPI_Init(void)
{
    // 1.开启时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

    // 2.GPIO工作模式
    // 2.1cs-pc13,通用推挽输出,CNF=00,MODE=11
    GPIOC->CRH |= GPIO_CRH_MODE13;
    GPIOC->CRH &= ~GPIO_CRH_CNF13;

    // 2.2SCK-PA5,通用推挽输出,CNF=00,MODE=11
    GPIOA->CRL |= GPIO_CRL_MODE5;
    GPIOA->CRL &= ~GPIO_CRL_CNF5;

    // 2.3MOSI-PA7,通用推挽输出,CNF=00,MODE=11
    GPIOA->CRL |= GPIO_CRL_MODE7;
    GPIOA->CRL &= ~GPIO_CRL_CNF7;

    // 2.4MISO-PA6,浮空输入,CNF=01,MODE=00
    GPIOA->CRL &= ~GPIO_CRL_MODE6;
    GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
    GPIOA->CRL |= GPIO_CRL_CNF6_0;

    // 3.SCK 保持空闲状态,模式0-低电平空闲
    SCK_LOW;
    // 4.片选初始不选中
    CS_HIGH;
    // 5.延时
    SPI_DELAY;
}

// SPI通信的开启和关闭(通过片选信号控制)
void SPI_Start(void)
{
    CS_LOW;
}
void SPI_Stop(void)
{
    CS_HIGH;
}

// 一个时钟周期内,主从交换一个字节数据
uint8_t SPI_SwapByte(uint8_t byte)
{
    // 定义变量接收数据
    uint8_t rByte = 0x00;
    // 用循环依次读写每一位
    for (uint8_t i = 0; i < 8; i++)
    {
        // 1.判断当前最高位,将MOSI拉至对应电平
        if (byte & 0x80)
        {
            MOSI_HIGH;
        }
        else
        {
            MOSI_LOW;
        }
        //2.左移一位
        byte <<= 1;
        //3.拉高时钟,在第一个上升沿采样数据
        SCK_HIGH;
        SPI_DELAY;
        //4.先左移,空出最低位来存放读取的数据
        rByte <<= 1;
        // 2.在MISO上采样读取数据
        if (MISO_READ)
        {
rByte |= 0x01;//读到高电平,最后一位置1
        }
        
        //6.拉低时钟,为下次传输做准备
        SCK_LOW;
        SPI_DELAY;
    }
    return rByte;
}

#ifndef __SPI_H_
#define __SPI_H_
#include "stm32f10x.h"
#include "delay.h"
// 宏定义引脚线的操作
// CS -PC13
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)

//SCK -PA5
#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)

//MOSI -PA7
#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)

//MISO -PA6,读取数据线
#define MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6)

//定义标准延迟时间 100khz 10us
#define SPI_DELAY Delay_us(5)

//初始化
void SPI_Init(void);

//SPI通信的开启和关闭
void SPI_Start(void);
void SPI_Stop(void);

//一个时钟周期内,主从交换一个字节数据
uint8_t SPI_SwapByte(uint8_t byte);
//SPI
#endif

#include "w25q32.h"

//初始化
void W25Q32_Init(void)
{
    SPI_Init();
}

//读取JEDEC ID,进行测试
void W25Q32_ReadID(uint8_t *pMID,uint16_t *pDID)
{
//1.开启SPI通信,片选使能
SPI_Start();
//2.发送9fh指令
SPI_SwapByte(0x9f);
//3.依次读取三个字节的ID
//3.1第一个字节,生产商ID
*pMID = SPI_SwapByte(0xff);
//3.2第二个字节,设备ID
*pDID = 0;
*pDID |= SPI_SwapByte(0xff) << 8;
*pDID |= SPI_SwapByte(0xff)& 0xff;
//4.关闭SPI通信
SPI_Stop();
}

#ifndef __W25Q32_H
#define __W25Q32_H
#include "spi.h"

//初始化
void W25Q32_Init(void);

//读取JEDEC ID,进行测试
void W25Q32_ReadID(uint8_t *pMID,uint16_t *pDID);
//写使能和关闭使能
void W25Q32_WriteEnable(void);
void W25Q32_WriteDisable(void);

//等待直到状态不为Busy
void w25q32_wait4NotBusy(void);

//段擦除,传入块号和段号
void W25Q32_EraseSector(uint8_t block,uint8_t sector);

//页写,地址以块号-段号-页号 形式表示
void W25Q32_WritePage(uint8_t block,uint8_t sector,uint8_t page,uint8_t *data,uint16_t len);
//读取
void w25q32_Read(uint32_t addr,uint8_t *buffer,uint16_t len);
#endif

/*
 * @Author: wushengran
 * @Date: 2024-09-13 16:29:39
 * @Description:
 *
 * Copyright (c) 2024 by atguigu, All Rights Reserved.
 */

#include "usart.h"
#include "delay.h"
#include "w25q32.h"

int main(void)
{
	// 初始化
	USART_Init();
    W25Q32_Init();
	printf("软件模拟实验...\n");

	//读取JEDEC ID
	uint8_t mID=0;
	uint16_t dID=0;
	W25Q32_ReadID(&mID, &dID);
	printf("mID = %x, dID = %#x\n", mID, dID);



	while (1)
	{
		
	}
}

到时候用一个串口调试工具,查看输出的值。

相关推荐
芯岭5 小时前
内含32位MCU的无线收发芯片XL2422
单片机·嵌入式硬件·信息与通信·射频工程
宫瑾6 小时前
STM32USB学习
stm32·嵌入式硬件·学习
1379号监听员_6 小时前
嵌入式软件架构--按键消息队列3(测试)
开发语言·stm32·单片机·嵌入式硬件·架构
迎風吹頭髮7 小时前
Linux服务器编程实践57-功能强大的网络信息函数getaddrinfo:支持IPv4与IPv6
单片机·嵌入式硬件
GilgameshJSS9 小时前
STM32H743-ARM例程26-TCP_CLIENT
c语言·arm开发·stm32·单片机·tcp/ip
清风6666669 小时前
基于单片机的开尔文电路电阻测量WIFI上传设计
单片机·嵌入式硬件·毕业设计·课程设计
奋斗的牛马11 小时前
FPGA—ZYNQ学习Helloward(二)
单片机·嵌入式硬件·学习·fpga开发
我先去打把游戏先15 小时前
ESP32学习笔记(基于IDF):ESP32连接MQTT服务器
服务器·笔记·单片机·嵌入式硬件·学习·esp32
CiLerLinux20 小时前
第一章 FreeRTOS简介
单片机·嵌入式硬件·物联网·gnu