加快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寄存器就可以清除了,比较方便。
用寄存器写,加速。*((u32 *)0x12345678)=0x12345678;有些时候我会尝试把代码全部写成这种风格,在加速的同时,还起到了不可读的效果! 我抓一个关键词 “buffer”:) V7年前出点干货啊。。。过年放假闲着也是闲着,抓紧时间学一下。。。 leiyitan 发表于 2019-1-8 08:26
用寄存器写,加速。*((u32 *)0x12345678)=0x12345678;有些时候我会尝试把代码全部写成这种风格,在加速的 ...
没说到点上,寄存器操作不是关键^_^ 廷润 发表于 2019-1-8 08:32
我抓一个关键词 “buffer”
没说到点上,BUF不是关键^_^ BUF方式,只要发送缓冲空TXE,就写入发送数据,不用等实际硬件是否完全发送完,这样数据发送是连续的。最后再检查RXNE标志判断实际硬件是否发送完成。
1、SPI读写过程是同步的,所以发送的时候只需等待TXE,不必每次都对接收完毕进行判断。 函数SPI_SelectHard->DR; 用的巧妙 可以方便的清除标志。 用DMA更好吧 zl1215 发表于 2019-1-8 10:40
用DMA更好吧
DMA不灵活,特别是控制一些ADC,DAC芯片的时候,不方便。 没看出来…… 在发送指定长度数据时,只写数据,不执行读。到写完时,判断一些读寄存器并处理。 Oenomaus 发表于 2019-1-8 15:55
在发送指定长度数据时,只写数据,不执行读。到写完时,判断一些读寄存器并处理。
执行了读的,我感觉时发送定长数据时,TXE为空就继续写入,不等待发送完成,同时读一下DR,以便清除可能的RXNE标志位而不实现检查RXNE标志位 难道SPI返回的数据又放在了buf里面? suozhang 发表于 2019-1-8 13:08
没看出来……
楼主位已经更新答案。 不知道能节约多少? 可能某些场合需要这样的优化,一般项目感觉还是用HAL,程序的健壮性,可读性和可维护性才是第一的。 eric2013 发表于 2019-1-8 09:06
没说到点上,BUF不是关键^_^
硬汉,请教一个 RTX的疑惑啊,我看到user文件夹内有一个文件 RTX_lib.c , 它不是个头文件,只放在文件夹内,我在工程里没看到你有添加它,有什么作用呢? 能否删除它啊。 这巧妙的答案,刚好给我一个灵感。去规避量产产品,个别产品发生随机通信异常现象。
廷润 发表于 2019-1-9 09:50
这巧妙的答案,刚好给我一个灵感。去规避量产产品,个别产品发生随机通信异常现象。
? 这个灵感好。晚上来我房聊聊。 roguebear 发表于 2019-1-9 11:14
? 这个灵感好。晚上来我房聊聊。
我不是用贴中的功能。我是 “望文生义” ,激发了自己某款产品的某个 随机缺陷的优化思路。 这方法的确巧妙,虽然SPI可以全双工,但实际应用中却没能实现真正意义上的全双工,官方的方法可能是对全双工的支持。
最近我发现BSP驱动的中的SPI FLASH驱动的sf_AutoWritePage()函数可以有优化的地方,就是在判别FLASH中的数据没有变化之前,
只做一次读FLASH操作,后续不用再做一次读FLASH操作。因为我看到sf_AutoWritePage()函数是在某个特定扇区内操作,如果写数据
的长度小于FLASH扇区的长度就要做两次读FLASH操作(前提是新数据与FLASH中的数据不一样);既然是在某个特定扇区内操作,何不首先
读出整个扇区数据,根据地址偏移对新数据与FLASH中的数据进行比较和判断需要擦除扇区。这样无论写数据的长度小于或等于FLASH扇区
的长度都只做一次读FLASH操作,还请大神鉴别。 huohua1991 发表于 2019-1-10 09:45
这方法的确巧妙,虽然SPI可以全双工,但实际应用中却没能实现真正意义上的全双工,官方的方法可能是对全双 ...
谢谢,后面研究下。 确实比起之前为了接收一个数据,还要发送一个dummy数据快! Tresordie 发表于 2019-1-12 10:22
确实比起之前为了接收一个数据,还要发送一个dummy数据快!
大概快多少? 串口也可以用类似的方法,原理就是等地啊发送完成标志,只有发送完成,就可以往DR寄存器写数据。不需要等待发送完成后,再执行其他操作。也即是说,硬件在传输数据的时候,单片机不需要死等,而是可以做其他的事情,比如运算之类的。但是我发现F0系列的TXE表示的并不是发送完成,而是发送缓冲区是否为空,发送缓冲区为空不代表数据发送完成了,只是可以再次往发送缓冲区里写入新的数据。 cortex 发表于 2019-6-18 09:03
串口也可以用类似的方法,原理就是等地啊发送完成标志,只有发送完成,就可以往DR寄存器写数据。不需要等待 ...
正常来说,所有STM32系列,TXE都是表示发送空,而发送完成是TC。
F1,F4和H7都是这样的。
eric2013 发表于 2019-6-18 10:51
正常来说,所有STM32系列,TXE都是表示发送空,而发送完成是TC。
F1,F4和H7都是这样的。
好像是哦,如果只是检测TXE,然后控制SPI的CS可能会导致数据还没发送完,就把CS拉高了。现在我的做法是等所有的SPI的数据发送完成后,最后检测检测一次Busy位。官方手册不建议检测Busy位,我估计是不建议在发完一个字节后检测Busy位。我是发送所有的字节后,检测Busy位,然后控制CS片选。 cortex 发表于 2019-6-18 11:15
好像是哦,如果只是检测TXE,然后控制SPI的CS可能会导致数据还没发送完,就把CS拉高了。现在我的做法是等 ...
SPI这里有个特殊的地方,发送结束务必是要检测RXNE的。 eric2013 发表于 2019-6-18 11:25
SPI这里有个特殊的地方,发送结束务必是要检测RXNE的。
意思是不是这样的,还没发送完成,接收缓冲区也是为空的,只有等到发送完成了,接收缓冲区才不是为空,也就是传输完成了。 cortex 发表于 2019-6-18 11:36
意思是不是这样的,还没发送完成,接收缓冲区也是为空的,只有等到发送完成了,接收缓冲区才不是为空,也 ...
由于SPI是全双工,发送时,必须要保证收到RXNE信号才是整个传输完成了。
SPI发送技巧。 标记下这个帖子,每次回复问题,都要查找一番这个帖子。 使用后发现卡在了 while (!(SPI_SelectHard->SR & RXNE));这个里面 qlslxf 发表于 2021-6-16 16:41
使用后发现卡在了 while (!(SPI_SelectHard->SR & RXNE));这个里面
说明没有正常工作。 eric2013 发表于 2021-6-16 16:59
说明没有正常工作。
大佬想问一下,这个spi加速改写如何替换HAL_SPI_Receive,试了一天了 感觉总是卡住了:'( /*
*********************************************************************************************************
*
* 模块名称 : 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) *********************************/
硬汉哥,全双工如果只有一个主不接从,发送会显示失败吗,就是例程里这个 while (w_2TransferState == TRANSFER_WAIT),因为有上拉,我觉得就算不接从机,就相当于一直接收高电平,不应该提示错误啊,但是我这反馈是错误
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