首页 > 其他分享 >stm32之硬件SPI读写W25Q64存储器应用案例

stm32之硬件SPI读写W25Q64存储器应用案例

时间:2024-09-13 08:52:30浏览次数:14  
标签:void MySPI W25Q64 stm32 SPI InitStructure GPIO

系列文章目录

1. stm32之SPI通信协议
2. stm32之软件SPI读写W25Q64存储器应用案例
3. stm32之SPI通信外设


文章目录


前言

提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者

本案例使用硬件SPI外设通信的方式实现了STM32与W25Q64 Flash存储器的通信,完成了常见的Flash存储器操作如读ID、页写、扇区擦除、读取数据等。


一、电路接线图

下图所示为W25Q64模块硬件接线图,左边是W25Q64模块作为从机,右边是stm32作为主机。本案例选用SPI1外设作为通信,经查阅引脚定义表可知,其中PA4对应主机的从机选择线SPI1_NSS连接到从机的CS引脚,PA5对应主机的时钟同步线SPI1_SCK连接到从机的CLK引脚,PA6对应主机的主机输入从机输出线SPI1_MISO连接到从机的DO引脚,PA7对应主机的主机输出从机输入线SPI1_MOSI连接到从机的DI引脚。最后,W25Q64模块的VCC和GND分别接到stm32的电源正负极进行供电。

在这里插入图片描述

二、应用案例代码

MySPI.h:

#ifndef __MYSPI_H
#define __MYSPI_H

void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);

#endif

MySPI.c:

#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
	SPI_InitStructure.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &SPI_InitStructure);
	
	SPI_Cmd(SPI1, ENABLE);
	
	MySPI_W_SS(1);
}

void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
	
	SPI_I2S_SendData(SPI1, ByteSend);
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
	
	return SPI_I2S_ReceiveData(SPI1);
}

W25Q64_Ins.h:

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

W25Q64.h:

#ifndef __W25Q64_H
#define __W25Q64_H

void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

#endif

W25Q64.c:

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

void W25Q64_Init(void)
{
	MySPI_Init();
}

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	Timeout = 100000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	W25Q64_Init();
	
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 5, MID, 2);
	OLED_ShowHexNum(1, 12, DID, 4);
	
	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);
	
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
		
	}
}

完整工程:stm32之硬件SPI读写W25Q64存储器

三、应用案例代码分析

有了上一章节软件SPI的基础,那么我们只需要在原有的基础上将软件SPI的接口直接改写成硬件实现就行了,具体来说就是只需要修改MySPI_Init以及MySPI_SwapByte函数的内部实现即可,这就是模块化封装的好处。我们先来看一下SPI的硬件基本结构图。

在这里插入图片描述

3.1 基本思路

  • 第一步,开启时钟,使能SPI和GPIO时钟。
  • 第二步,初始化GPIO口,配置相应GPIO口的引脚模式。
  • 第三步,配置SPI外设,调用SPI_Init函数完成初始化配置。
  • 第四步,SPI使能,调用SPI_Cmd函数开启SPI外设。

以上就是SPI初始化函数的基本思路了,接下来我们只需要在上一章软件SPI通信模块的基础上进行修改即可,把软件实现的时序用硬件SPI接口替换掉。

3.2 相关库函数介绍

老规矩,还是先来看一下操作SPI外设的相关函数。找到stm32f10x_spi.h然后拉到最后,可以发现这里很多的函数都带了个I2S,因为SPI和I2S共用同一套电路,我们不需要使用I2S,直接当它不存在就行了。

其实学完了这么多的外设可以发现,很多东西都是一个套路,像下面这些函数都是经常见的了,不过是换了个外设的名称,功能都是差不多的,这里就简单讲一下就好了。

void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
  • SPI_I2S_DeInit,恢复缺省配置。
  • SPI_Init,SPI外设初始化。
  • SPI_StructInit,结构体变量初始化。
  • SPI_Cmd,SPI外设使能。
  • SPI_I2S_ITConfig,中断使能。
  • SPI_I2S_DMACmd,DMA使能。

接下来是SPI比较重要的两个函数,发送与接收一个字节函数

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

最后就是一些获取标志位和清除标志位的相关函数,我们主要会用到SPI_I2S_GetFlagStatus来获取TXE和RXNE标志位的状态,再配合写DR和读DR的函数就能控制时序的产生了。

FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

ok,库函数的介绍就到这里,下面我们就进入正题吧!Let ’s go !

3.3 MySPI模块

3.3.1 模块初始化

1. 开启时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

注意:SPI1是APB2侧的外设

2. 初始化GPIO口

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
	
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
	
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

其中SCK(PA5)和MOSI(PA7)是由硬件外设控制的输出信号,所以配置为复用推挽输出。MISO(PA6)是硬件外设的输入信号,我们可以配置为上拉输入(因为输入设备可以有多个,所以不存在复用输入这种东西,直接配置为上拉输入即,普通GPIO口能输入,外设也能输入)。最后还有SS(PA4)引脚,SS引脚是软件控制的输出信号,所以配置为通用推挽输出即可。

3. 配置SPI外设

SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);

以下是关于SPI初始化配置各个参数的解析:

  • SPI_Mode:SPI的模式,这里选择SPI_Mode_Master,即SPI1 被配置为主设备。
  • SPI_Direction:这里选择SPI_Direction_2Lines_FullDuplex,SPI 工作在全双工模式。
  • SPI_DataSize:这里选择SPI_DataSize_8b,数据大小为 8 位。
  • SPI_FirstBit:这里选择为SPI_FirstBit_MSB,数据按最高有效位 (MSB) 先发送。
  • SPI_BaudRatePrescaler:这里选择为SPI_BaudRatePrescaler_128,SPI 时钟源频率被分频为 128。
  • SPI_CPOL:这里选择为SPI_CPOL_Low,空闲时钟极性SCK为低电平。
  • SPI_CPHA:这里选择为SPI_CPHA_1Edge,数据在第一个时钟边沿进行采样。
  • SPI_NSS:这里选择为SPI_NSS_Soft:NSS 信号通过软件控制,不使用硬件 NSS 管脚管理。
  • SPI_CRCPolynomial:这里选择为SPI_CRCPolynomial 设置为 7,虽然这里没有启用 CRC 校验,但必须指定一个多项式。

这里要注意一下的就是SPI_CPOL以及SPI_CPHA配置的是模式0。

4. SPI使能

SPI_Cmd(SPI1, ENABLE);

最后别忘了置SS为默认高电平状态,不选择从机。

MySPI_W_SS(1);

3.3.2 SPI基本时序单元模块

1. 起始与终止信号

void MySPI_Start(void)
{
	MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);
}

这个跟软件SPI一样,SS引脚我们采用软件控制GPIO口的方式来进行控制。

2. 交换一个字节

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
	
	SPI_I2S_SendData(SPI1, ByteSend);
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
	
	return SPI_I2S_ReceiveData(SPI1);
}

在这里我们使用的是SPI主模式全双工非连续传输的方式,这种传输方式的逻辑大概就是:

  • 第一步,等待TXE标志位为1,发送寄存器为空。
  • 第二步,写入DR,将数据写入TDR。
  • 第三步,等待RXNE标志位为1,接受寄存器非空。
  • 第四步,读取DR,从RDR中读取数据。

ok,到这里关于硬件SPI读写W25Q64存储器的分析就到这里了,剩下的还有W25Q64模块以及主程序代码逻辑的分析在软件SPI那一章已经详细地分析过了,这里就不再累述了。

标签:void,MySPI,W25Q64,stm32,SPI,InitStructure,GPIO
From: https://blog.csdn.net/Mr_Jaychong/article/details/141999750

相关文章

  • stm32 SPI通信协议&W25Q64(软件SPI读写W25Q64)
    理论SPI1.SPI通信SPI(SerialPeripheralInterface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(SerialClock)、MOSI(MasterOutputSlaveInput)、MISO(MasterInputSlaveOutput)、SS(SlaveSelect)同步,全双工支持总线挂载多设备(一主多从)SCK:时钟线MOSI:主机输出,从......
  • [20240912]记录使用tnsping遇到的问题.txt
    [20240912]记录使用tnsping遇到的问题.txt--//tnsping用来检测数据库是否连接存在许多局限性,记录自己在使用tnsping遇到的问题.1.环境:--//关闭数据库开启监听.SYS@book>shutdownimmediate;Databaseclosed.Databasedismounted.ORACLEinstanceshutdown.--//服务端监听配置......
  • STM32 之 SDRAM 详解
     目录 前言一、SDRAM简介二、SDRAM的组成原理 2.1存储单元阵列2.1.1地址译码2.1.2存储电容2.2控制逻辑2.2.1时钟同步2.2.2命令解码2.2.3模式寄存器2.3数据输入/输出缓冲2.3.1数据总线2.3.2数据锁存2.4刷新电路2.4.1自动刷新2.4.2自刷新三、STM32与S......
  • SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024
    VisionTransformer(ViT)架构传统上采用基于网格的方法进行标记化,而不考虑图像的语义内容。论文提出了一种模块化的超像素非规则标记化策略,该策略将标记化和特征提取解耦,与当前将两者视为不可分割整体的方法形成了对比。通过使用在线内容感知标记化以及尺度和形状不变的位置嵌入......
  • 较好的SPI 接口的 OLED入门文章
    参考文章:1.基于STM32+0.96寸OLED--7脚SPI接线显示+代码解析链接:https://blog.csdn.net/qq_59527512/article/details/139471311推荐理由:很好地讲解了OLED的基础认知:7脚OLED模块的引脚和5种接线方式(DS0/1/2)   我们的OLED屏幕选择的是SSD1306Z的芯片,是一个128*64大小......
  • 基于STM32C8T6的CubeMX:HAL库点亮LED
    三个可能的问题和解决方法:大家完成之后回来看,每一种改错误都是一种成长,不要畏惧,要快乐,积极面对,要耐心对待STMCuBeMX新建项目的两种匪夷所思的问题https://mp.csdn.net/mp_blog/creation/editor/142151511STMCubeMX文件下载后会出现其他项目无法下载的问题https://mp.csdn.ne......
  • 五、STM32标准库硬件SPI驱动OLED(基于SSD1106)
    1、驱动芯片及MCU介绍        SSD1106是一款常用于嵌入式设备中的小型屏幕OLED(有机发光二极管)显示器驱动芯片,被广泛应用于各种嵌入式设备中,本示例程序基于SSD1106驱动芯片的OLED显示屏。        本次示例采用STM32F103系列MCU,使用标准库硬件SPI驱动OLED......
  • stm32使用cubumx配置串口不定长接收
    前言此方法利用stm32的uart+dma来实现不定长接收,利用dma中的空闲中断。设置USART1为异步通信方式使能串口1全部中断设置USART1_RX的DMA中断接收 速度设置最大(其实用115200没有影响但为保证工程严谨性)检查中断优先级这两个中断是否开启  生成代码后......
  • stm32单片机遥控美的空调
    一、硬件清单1.STM32F103核心板2.红外发射传感器(38Khz)二、空调遥控原理及应用空调主机内设有红外接收管,通过红外发射管按照特定协议向空调主机发射信号,即可实现对空调温度、风速、模式等的控制。普通遥控器便是采用这样的方式进行操控。而若将此模块集成于我们的项目当中,......
  • STM32学习笔记——中断
    中断:在主程序运行过程中,出现了特定事件(例如发生已经预知的一些情况),从而转入中断程序中,处理完成后再回到主程序中继续执行。(频繁的中断函数会影响主程序的运行,所以中断函数一边不处理特别复杂的逻辑)EXTI(ExternInterrupt)外部中断支持的触发方式:上升沿/下降沿/双边沿/软件触发支......