eric2013 发表于 2019-1-8 01:12:51

加快SPI连续读写速度的配置方式

一般都是下面这种,比较啰嗦 /* 等待发送缓冲区空 */
      while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);

      /* 发送一个字节 */
      SPI_I2S_SendData(SPI1, _ucByte);

      /* 等待数据接收完毕 */
      while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);

      /* 读取接收到的数据 */
      SPI_I2S_ReceiveData(SPI1);
下面这种可以有效加速,特别是连续读写的时候
下面的函数SendBuf 隐藏了两个关键的知识点,看看有没有坛友能看出来的(答案已经揭晓到下面)

/* SPI_SR位定义 */
#define RXNE    0x01
#define TXE   0x02
#define BSY   0x80
#define FPCLK   (__FPCLK/1000)

/* 串行Flsh的片选GPIO端口*/
#define SPI_SelectHard            SPI1
#define SF_RCC_CS                         RCC_AHB1Periph_GPIOD
#define SF_PORT_CS                        GPIOD
#define SF_PIN_CS                        GPIO_Pin_13
#define SF_CS_0()                        SF_PORT_CS->BSRRH = SF_PIN_CS
#define SF_CS_1()                        SF_PORT_CS->BSRRL = SF_PIN_CS

/*
*********************************************************************************************************
*      函 数 名: Send
*      功能说明: 发送一个字节数据
*      形    参: 无
*      返 回 值: SPI接口返回值
*********************************************************************************************************
*/
static U8 Send (U8 outb)
{
      /* 通过SPI接口读写一个字节 */
      SPI_SelectHard->DR = outb;

      /* 等待数据接收完毕 */
      while (!(SPI_SelectHard->SR & RXNE));
      return (SPI_SelectHard->DR);
}

/*
*********************************************************************************************************
*      函 数 名: SendBuf
*      功能说明: 通过SPI接口发送多个字节
*      形    参: buf 数据地址
*             sz发送数据大小
*      返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL SendBuf (U8 *buf, U32 sz)
{
      U32 i;

      for (i = 0; i < sz; i++)
      {
                SPI_SelectHard->DR = buf;
                /* 等待发送完成 */
                while (!(SPI_SelectHard->SR & TXE));
                SPI_SelectHard->DR;
      }
      
      /* 等待接收完成 */
      while (SPI_SelectHard->SR & (BSY | RXNE))
      {
                SPI_SelectHard->DR;
      }
      return (__TRUE);
}
static/image/hrline/4.gif
由于SPI是全双工通信,发送完还要等带接收完成,这里做一个巧妙的处理。

发送的时候仅判断TEX发送空中断,发送完毕后,再等待RXNE,这样就不需要每次TEX都等待RXNE。

另外就是我们要清除TEX和RXNE标志,清除方法也比较简单,仅需读取DR寄存器就可以清除了,比较方便。

leiyitan 发表于 2019-1-8 08:26:52

用寄存器写,加速。*((u32 *)0x12345678)=0x12345678;有些时候我会尝试把代码全部写成这种风格,在加速的同时,还起到了不可读的效果!

廷润 发表于 2019-1-8 08:32:26

我抓一个关键词 “buffer”:)

roguebear 发表于 2019-1-8 08:55:47

V7年前出点干货啊。。。过年放假闲着也是闲着,抓紧时间学一下。。。

eric2013 发表于 2019-1-8 09:06:05

leiyitan 发表于 2019-1-8 08:26
用寄存器写,加速。*((u32 *)0x12345678)=0x12345678;有些时候我会尝试把代码全部写成这种风格,在加速的 ...

没说到点上,寄存器操作不是关键^_^

eric2013 发表于 2019-1-8 09:06:12

廷润 发表于 2019-1-8 08:32
我抓一个关键词 “buffer”
没说到点上,BUF不是关键^_^

xdj0818 发表于 2019-1-8 09:21:37

BUF方式,只要发送缓冲空TXE,就写入发送数据,不用等实际硬件是否完全发送完,这样数据发送是连续的。最后再检查RXNE标志判断实际硬件是否发送完成。

xieyang__ 发表于 2019-1-8 09:32:32

1、SPI读写过程是同步的,所以发送的时候只需等待TXE,不必每次都对接收完毕进行判断。

byccc 发表于 2019-1-8 09:50:30

函数SPI_SelectHard->DR;   用的巧妙

byccc 发表于 2019-1-8 10:11:04

可以方便的清除标志。

zl1215 发表于 2019-1-8 10:40:42

用DMA更好吧

byccc 发表于 2019-1-8 10:49:50

zl1215 发表于 2019-1-8 10:40
用DMA更好吧

DMA不灵活,特别是控制一些ADC,DAC芯片的时候,不方便。

suozhang 发表于 2019-1-8 13:08:16

没看出来……

Oenomaus 发表于 2019-1-8 15:55:37

在发送指定长度数据时,只写数据,不执行读。到写完时,判断一些读寄存器并处理。

avita 发表于 2019-1-8 16:09:49

Oenomaus 发表于 2019-1-8 15:55
在发送指定长度数据时,只写数据,不执行读。到写完时,判断一些读寄存器并处理。

执行了读的,我感觉时发送定长数据时,TXE为空就继续写入,不等待发送完成,同时读一下DR,以便清除可能的RXNE标志位而不实现检查RXNE标志位

roguebear 发表于 2019-1-8 16:48:51

难道SPI返回的数据又放在了buf里面?

eric2013 发表于 2019-1-9 00:24:22

suozhang 发表于 2019-1-8 13:08
没看出来……

楼主位已经更新答案。

roguebear 发表于 2019-1-9 08:23:13

不知道能节约多少? 可能某些场合需要这样的优化,一般项目感觉还是用HAL,程序的健壮性,可读性和可维护性才是第一的。

kinsno 发表于 2019-1-9 09:08:46

eric2013 发表于 2019-1-8 09:06
没说到点上,BUF不是关键^_^

硬汉,请教一个 RTX的疑惑啊,我看到user文件夹内有一个文件 RTX_lib.c , 它不是个头文件,只放在文件夹内,我在工程里没看到你有添加它,有什么作用呢? 能否删除它啊。

廷润 发表于 2019-1-9 09:50:30

这巧妙的答案,刚好给我一个灵感。去规避量产产品,个别产品发生随机通信异常现象。

roguebear 发表于 2019-1-9 11:14:03

廷润 发表于 2019-1-9 09:50
这巧妙的答案,刚好给我一个灵感。去规避量产产品,个别产品发生随机通信异常现象。

? 这个灵感好。晚上来我房聊聊。

廷润 发表于 2019-1-10 08:42:16

roguebear 发表于 2019-1-9 11:14
? 这个灵感好。晚上来我房聊聊。

我不是用贴中的功能。我是 “望文生义” ,激发了自己某款产品的某个 随机缺陷的优化思路。

huohua1991 发表于 2019-1-10 09:45:55

这方法的确巧妙,虽然SPI可以全双工,但实际应用中却没能实现真正意义上的全双工,官方的方法可能是对全双工的支持。

最近我发现BSP驱动的中的SPI FLASH驱动的sf_AutoWritePage()函数可以有优化的地方,就是在判别FLASH中的数据没有变化之前,
只做一次读FLASH操作,后续不用再做一次读FLASH操作。因为我看到sf_AutoWritePage()函数是在某个特定扇区内操作,如果写数据
的长度小于FLASH扇区的长度就要做两次读FLASH操作(前提是新数据与FLASH中的数据不一样);既然是在某个特定扇区内操作,何不首先
读出整个扇区数据,根据地址偏移对新数据与FLASH中的数据进行比较和判断需要擦除扇区。这样无论写数据的长度小于或等于FLASH扇区
的长度都只做一次读FLASH操作,还请大神鉴别。

eric2013 发表于 2019-1-10 12:19:11

huohua1991 发表于 2019-1-10 09:45
这方法的确巧妙,虽然SPI可以全双工,但实际应用中却没能实现真正意义上的全双工,官方的方法可能是对全双 ...

谢谢,后面研究下。

Tresordie 发表于 2019-1-12 10:22:10

确实比起之前为了接收一个数据,还要发送一个dummy数据快!

roguebear 发表于 2019-1-12 22:36:02

Tresordie 发表于 2019-1-12 10:22
确实比起之前为了接收一个数据,还要发送一个dummy数据快!

大概快多少?

cortex 发表于 2019-6-18 09:03:13

串口也可以用类似的方法,原理就是等地啊发送完成标志,只有发送完成,就可以往DR寄存器写数据。不需要等待发送完成后,再执行其他操作。也即是说,硬件在传输数据的时候,单片机不需要死等,而是可以做其他的事情,比如运算之类的。但是我发现F0系列的TXE表示的并不是发送完成,而是发送缓冲区是否为空,发送缓冲区为空不代表数据发送完成了,只是可以再次往发送缓冲区里写入新的数据。

eric2013 发表于 2019-6-18 10:51:09

cortex 发表于 2019-6-18 09:03
串口也可以用类似的方法,原理就是等地啊发送完成标志,只有发送完成,就可以往DR寄存器写数据。不需要等待 ...
正常来说,所有STM32系列,TXE都是表示发送空,而发送完成是TC。
F1,F4和H7都是这样的。

cortex 发表于 2019-6-18 11:15:06

eric2013 发表于 2019-6-18 10:51
正常来说,所有STM32系列,TXE都是表示发送空,而发送完成是TC。
F1,F4和H7都是这样的。

好像是哦,如果只是检测TXE,然后控制SPI的CS可能会导致数据还没发送完,就把CS拉高了。现在我的做法是等所有的SPI的数据发送完成后,最后检测检测一次Busy位。官方手册不建议检测Busy位,我估计是不建议在发完一个字节后检测Busy位。我是发送所有的字节后,检测Busy位,然后控制CS片选。

eric2013 发表于 2019-6-18 11:25:25

cortex 发表于 2019-6-18 11:15
好像是哦,如果只是检测TXE,然后控制SPI的CS可能会导致数据还没发送完,就把CS拉高了。现在我的做法是等 ...

SPI这里有个特殊的地方,发送结束务必是要检测RXNE的。

cortex 发表于 2019-6-18 11:36:08

eric2013 发表于 2019-6-18 11:25
SPI这里有个特殊的地方,发送结束务必是要检测RXNE的。

意思是不是这样的,还没发送完成,接收缓冲区也是为空的,只有等到发送完成了,接收缓冲区才不是为空,也就是传输完成了。

eric2013 发表于 2019-6-19 00:17:15

cortex 发表于 2019-6-18 11:36
意思是不是这样的,还没发送完成,接收缓冲区也是为空的,只有等到发送完成了,接收缓冲区才不是为空,也 ...
由于SPI是全双工,发送时,必须要保证收到RXNE信号才是整个传输完成了。

tianqi911 发表于 2020-7-10 09:29:26

SPI发送技巧。

eric2013 发表于 2021-5-16 00:05:33

标记下这个帖子,每次回复问题,都要查找一番这个帖子。

qlslxf 发表于 2021-6-16 16:41:08

使用后发现卡在了 while (!(SPI_SelectHard->SR & RXNE));这个里面

eric2013 发表于 2021-6-16 16:59:07

qlslxf 发表于 2021-6-16 16:41
使用后发现卡在了 while (!(SPI_SelectHard->SR & RXNE));这个里面

说明没有正常工作。

qlslxf 发表于 2021-6-16 17:18:14

eric2013 发表于 2021-6-16 16:59
说明没有正常工作。

大佬想问一下,这个spi加速改写如何替换HAL_SPI_Receive,试了一天了 感觉总是卡住了:'(

eric2013 发表于 2021-8-30 17:15:05

/*
*********************************************************************************************************
*
*        模块名称 : SPI驱动模块
*        文件名称 : SPI_STM32F103.c
*        版    本 : V1.0
*        说    明 : 本驱动是基于bsp_spi_bus.c文件实现。
*            (1)使用FlashFS的SPI FLASH文件系统前务必先调用初始化函数bsp_InitSPIBus(在函数bsp.c中初始化)。
*            (2)本文件使用的是官方模板,用户只需添加函数功能即可,故不对变量定义和函数名做重新规范。
*            
*        修改记录 :
*                版本号日期      作者   说明
*                V1.0    2015-09-09 Eric2013正式发布
*
*        Copyright (C), 2015-2020, 安富莱电子 www.armfly.com
*
*********************************************************************************************************
*/
#include <File_Config.h>
#include "bsp.h"


/*
**********************************************************************************************************
                                                                                        宏定义
**********************************************************************************************************
*/
/*
   SPI 驱动定义
   spi0_drv: 第一个SPI设备驱动
   spi1_drv: 第二个SPI设备驱动
*/

#define __DRV_IDspi0_drv
#define __FPCLK   72000000

/* SPI_SR位定义 */
#define RXNE    0x01
#define TXE   0x02
#define BSY   0x80
#define FPCLK   (__FPCLK/1000)

/* 串行Flsh的片选GPIO端口*/
#define SPI_SelectHard          SPI1
#define SF_RCC_CS                         RCC_APB2Periph_GPIOF
#define SF_PORT_CS                        GPIOF
#define SF_PIN_CS                        GPIO_Pin_11
#define SF_CS_0()                        SF_PORT_CS->BRR = SF_PIN_CS
#define SF_CS_1()                        SF_PORT_CS->BSRR = SF_PIN_CS

/*
**********************************************************************************************************
                                                                                        宏定义
**********************************************************************************************************
*/

/* SPI驱动接口函数 */
static BOOL Init (void);
static BOOL UnInit (void);
static U8   Send (U8 outb);
static BOOL SendBuf (U8 *buf, U32 sz);
static BOOL RecBuf (U8 *buf, U32 sz);
static BOOL BusSpeed (U32 kbaud);
static BOOL SetSS (U32 ss);

/* SPI 设备驱动控制块 */
SPI_DRV __DRV_ID = {
Init,
UnInit,
Send,
SendBuf,
RecBuf,
BusSpeed,
SetSS,
NULL
};


/*
*********************************************************************************************************
*        函 数 名: Init
*        功能说明: 初始化SPI设备,这里仅初始化了SPI Flash的片选,SPI初始化调用函数bsp_InitSPIBus()实现
*        形    参: 无
*        返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL Init (void)
{
        /*
          安富莱STM32-V4 开发板口线分配:串行Flash型号为 W25Q128 (104MHz)
          片选 PF8/SF_CS
        */
        GPIO_InitTypeDef GPIO_InitStructure;

        /* 使能GPIO 时钟 */
        RCC_APB2PeriphClockCmd(SF_RCC_CS, ENABLE);

        /* 配置片选口线为推挽输出模式 */
        SF_CS_1();                /* 片选置高,不选中 */
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Pin = SF_PIN_CS;
        GPIO_Init(SF_PORT_CS, &GPIO_InitStructure);
        return (__TRUE);
}

/*
*********************************************************************************************************
*        函 数 名: UnInit
*        功能说明: 卸载SPI设备,正常情况下此函数实现卸载需要将SPI接口设置为初始化状态,即未开启状态。
*             由于多个设备使用SPI接口,这里仅是将片选至高。
*        形    参: 无
*        返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL UnInit (void)
{
SF_CS_1();

return (__TRUE);
}

/*
*********************************************************************************************************
*        函 数 名: Send
*        功能说明: 发送一个字节数据
*        形    参: 无
*        返 回 值: SPI接口返回值
*********************************************************************************************************
*/
static U8 Send (U8 outb)
{
        /* 通过SPI接口读写一个字节 */
        SPI_SelectHard->DR = outb;

        /* 等待数据接收完毕 */
        while (!(SPI_SelectHard->SR & RXNE));
        return (SPI_SelectHard->DR);
}

/*
*********************************************************************************************************
*        函 数 名: SendBuf
*        功能说明: 通过SPI接口发送多个字节
*        形    参: buf 数据地址
*             sz发送数据大小
*        返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL SendBuf (U8 *buf, U32 sz)
{
        U32 i;

        for (i = 0; i < sz; i++)
        {
                SPI_SelectHard->DR = buf;
                /* 等待发送完成 */
                while (!(SPI_SelectHard->SR & TXE));
                SPI_SelectHard->DR;
        }
       
        /* 等待接收完成 */
        while (SPI_SelectHard->SR & (BSY | RXNE))
        {
                SPI_SelectHard->DR;
        }
        return (__TRUE);
}

/*
*********************************************************************************************************
*        函 数 名: RecBuf
*        功能说明: 通过SPI接口接收多个字节
*        形    参: buf 数据地址
*             sz接收数据大小
*        返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL RecBuf (U8 *buf, U32 sz)
{
        U32 i;

        for (i = 0; i < sz; i++)
        {
                SPI_SelectHard->DR = 0xFF;
                /* 等待接收完成 */
                while (!(SPI_SelectHard->SR & RXNE));
                buf = SPI_SelectHard->DR;
        }
        return (__TRUE);
}

/*
*********************************************************************************************************
*        函 数 名: BusSpeed
*        功能说明: 设置SPI速度,驱动SPI Flash使用的是18MHz
*        形    参: buf 数据地址
*             sz接收数据大小
*        返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL BusSpeed (U32 kbaud)
{
        U8 br;

        if      (kbaud >= FPCLK / 2)   br = 0;                     /* FPCLK/2    */
        else if (kbaud >= FPCLK / 4)   br = 1;                     /* FPCLK/4    */
        else if (kbaud >= FPCLK / 8)   br = 2;                     /* FPCLK/8    */
        else if (kbaud >= FPCLK / 16)br = 3;                     /* FPCLK/16   */
        else if (kbaud >= FPCLK / 32)br = 4;                     /* FPCLK/32   */
        else if (kbaud >= FPCLK / 64)br = 5;                     /* FPCLK/64   */
        else if (kbaud >= FPCLK / 128) br = 6;                     /* FPCLK/128*/
        else                           br = 7;                     /* FPCLK/256*/

        SPI_SelectHard->CR1 = (SPI_SelectHard->CR1 & ~(7 << 3)) | (br << 3);
        return (__TRUE);
}

/*
*********************************************************************************************************
*        函 数 名: BusSpeed
*        功能说明: 设置SPI速度,驱动SPI Flash使用的是18MHz
*        形    参: ss
*             0 表示选中设备
*             1 表示取消选中
*        返 回 值: __TRUE
*********************************************************************************************************
*/
static BOOL SetSS (U32 ss) {
        if(ss == 0)
        {
                bsp_SpiBusEnter();        /* 占用SPI总线, 用于总线共享 */
               
                bsp_SPI_Init(SPI_Direction_2Lines_FullDuplex | SPI_Mode_Master | SPI_DataSize_8b
                        | SPI_CPOL_High | SPI_CPHA_2Edge | SPI_NSS_Soft | SPI_BaudRatePrescaler_4 | SPI_FirstBit_MSB);
                SF_CS_0();
               
        }
        else
        {
                SF_CS_1();
                bsp_SpiBusExit();        /* 释放SPI总线, 用于总线共享 */
        }
       
return (__TRUE);
}

/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/


yuanzhongda 发表于 2021-9-2 19:52:04

硬汉哥,全双工如果只有一个主不接从,发送会显示失败吗,就是例程里这个        while (w_2TransferState == TRANSFER_WAIT),因为有上拉,我觉得就算不接从机,就相当于一直接收高电平,不应该提示错误啊,但是我这反馈是错误

eric2013 发表于 2021-9-3 09:22:52

yuanzhongda 发表于 2021-9-2 19:52
硬汉哥,全双工如果只有一个主不接从,发送会显示失败吗,就是例程里这个        while (w_2TransferState == TRAN ...

1、你貌似搞错了,这个例子里面没有while (w_2TransferState == TRANSFER_WAIT),这个是我们给bsp_spi_bus.c的HAL库驱动整理的。

2、可以参考我们论坛置顶帖V7网盘的DAC8563的DMA方式例子,就是仅发送。
SPI_DIRECTION_2LINES_TXONLY
页: [1] 2
查看完整版本: 加快SPI连续读写速度的配置方式