中级会员
 
- 积分
- 268
- 金钱
- 268
- 注册时间
- 2021-10-7
- 在线时间
- 44 小时
|
1金钱
本帖最后由 cyradg 于 2021-11-22 11:13 编辑
测试开发板为迷你开发板,SPI为SPI1口,SPI时钟设置为2M频率(好像是,不太记得了),不用DMA,读取256个字节大概需要1.2ms,加上DMA方式,读取256个字节大概为960多us,多少有些提升。用逻辑分析仪发现,不用DMA方式,每个字节的读取之间,时间间隔比较大,大约1us左右,如果用DMA方式,每个字节的读取之间,时间间隔比较小,大约200ns左右。
DMA刚学,不是太清楚,因为没有CPU介入,可能每个字节的读取之间的时间间隔会缩小,一定程度上提高了SPI传输速率。但是我目前出现了一个问题,就是通过DMA方式发送数据时,总是会漏掉最后两个字节不发送,例如,我要求发送4个字节,实际是发送了两个,要求发送6个,实际是发送4个,目前不知道原因,下面的代码很多是参考本网站别人写的代码:
//----------------------SPI_user.h-----------------
#ifndef __SPI__USER__
#define __SPI__USER__
#include <stm32f10x.h>
//SPI片选函数
typedef void (*SPI_CSFUN)(void);
//SPI时钟类型
typedef enum{
SPI_SCK_FCPU2=0,
SPI_SCK_FCPU4=1,
SPI_SCK_FCPU8=2,
SPI_SCK_FCPU16=3,
SPI_SCK_FCPU32=4,
SPI_SCK_FCPU64=5,
SPI_SCK_FCPU128=6,
SPI_SCK_FCPU256=7
}SPI_CLOCK;
typedef struct
{
SPI_TypeDef *SPIx; //SPI口
SPI_CLOCK SCK; //SPI时钟频率
u16 TimeOut; //超时设置
SPI_CSFUN CSLow; //CS片选,信号拉低函数
SPI_CSFUN CSHigh; //CS片选,信号拉高函数
}SPIx_TypeDef;
void SPIx_DMA_Init(SPIx_TypeDef *SPI);
u8 SPIx_DMA_ReadWriteBytes(SPIx_TypeDef *SPI,u8 *RxData,u8 *TxData,u16 DataLen );
u8 SPIx_DMA_ReadBytes(SPIx_TypeDef *SPI,u8* RxData,u16 DataLen);
u8 SPIx_DMA_WriteBytes(SPIx_TypeDef *SPI,u8* TxData,u16 DataLen);
#endif
//-------------------SPI_user.c-----------------------
#include "SPI_user.h"
typedef struct
{
DMA_Channel_TypeDef *DMARx;
DMA_Channel_TypeDef *DMATx;
}SPIx_DMA_TypeDef;
/*****************************************************************
**SPIx_GetDMA:获取DMA通道
**参数:
** SPI:SPI从设备
** DMARx:接收DMA通道
** DMATx:发送DMA通道
**返回值:0获取失败,1获取失败
******************************************************************/
u8 SPIx_GetDMA(SPIx_TypeDef *SPI,SPIx_DMA_TypeDef *DMA)
{
DMA->DMARx=0;
DMA->DMATx=0;
if (SPI1==SPI->SPIx)
{
DMA->DMARx=DMA1_Channel2; //SPI1的接收对应DMA1的通道2
DMA->DMATx=DMA1_Channel3; //SPI1的发送对应DMA1的通道3
}
else if (SPI2==SPI->SPIx)
{
DMA->DMARx=DMA1_Channel4; //SPI2的接收对应DMA1的通道4
DMA->DMATx=DMA1_Channel5; //SPI2的发送对应DMA1的通道5
}
else if (SPI3==SPI->SPIx)
{
DMA->DMARx=DMA2_Channel1; //SPI3的接收对应DMA2的通道1
DMA->DMATx=DMA2_Channel2; //SPI3的发送对应DMA2的通道2
}
else return 0;
return 1;
}
/*****************************************************************
**__SPIx_Init:初始化SPI
**参数:
** SPI:SPI从设备
******************************************************************/
void __SPIx_Init(SPIx_TypeDef *SPI)
{
if ((0==SPI->CSLow) || (0==SPI->CSHigh)) return;
if (0==SPI->SPIx) SPI->SPIx=SPI1;
if ((0!=SPI->SCK) && (0==(SPI->SCK&0x7))) SPI->SCK=SPI_SCK_FCPU256;
if (0==SPI->TimeOut) SPI->TimeOut=6000;
if (SPI1==SPI->SPIx)
{
RCC->APB2ENR|=1<<2; //PORTA时钟使能
RCC->APB2ENR|=1<<12; //SPI1时钟使能
//这里只针对SPI口初始化
GPIOA->CRL&=0X000FFFFF;
GPIOA->CRL|=0XBBB00000;//PA5.6.7复用
GPIOA->ODR|=0X7<<5; //PA5.6.7上拉
}
else if (SPI2==SPI->SPIx)
{
RCC->APB2ENR|=1<<3; //PORTB时钟使能
RCC->APB1ENR|=1<<14; //SPI2时钟使能
//这里只针对SPI口初始化
GPIOB->CRH&=0X000FFFFF;
GPIOB->CRH|=0XBBB00000;//PB13,14,15复用
GPIOB->ODR|=0X7<<13; //PB13,14,15上拉
}
else if (SPI3==SPI->SPIx)
{
RCC->APB2ENR|=1<<3; //PORTB时钟使能
RCC->APB1ENR|=1<<15; //SPI3时钟使能
//这里只针对SPI口初始化
GPIOB->CRL&=0xFF000FFF;
GPIOB->CRL|=0X00BBB000;//PB3,4,5复用
GPIOB->ODR|=0X7<<3; //PB3,4,5上拉
}
SPI->SPIx->CR1|=0<<10;//全双工模式
SPI->SPIx->CR1|=1<<9; //软件nss管理
SPI->SPIx->CR1|=1<<8;
SPI->SPIx->CR1|=1<<2; //SPI主机
SPI->SPIx->CR1|=0<<11;//8bit数据格式
SPI->SPIx->CR1|=1<<1; //空闲模式下SCK为1 CPOL=1
SPI->SPIx->CR1|=1<<0; //数据采样从第二个时间边沿开始,CPHA=1
SPI->SPIx->CR1|=SPI->SCK<<3; //Fsck=Fcpu/256
SPI->SPIx->CR1|=0<<7; //MSBfirst
}
/*****************************************************************
**SPIx_DMA_Config:SPI设备DMA配置
**参数:
** DMARx:SPI设备的接收DMA通道
** DMATx:SPI设备的发送DMA通道
******************************************************************/
void SPIx_DMA_Config(SPIx_TypeDef *SPI)
{
SPIx_DMA_TypeDef DMA;
if (!SPIx_GetDMA(SPI,&DMA)) return;
DMA_Channel_TypeDef *DMARx=DMA.DMARx;
DMA_Channel_TypeDef *DMATx=DMA.DMATx;
if ((DMA1_Channel2==DMARx) || (DMA1_Channel4==DMARx))
RCC->AHBENR |= 1<<0; //DMA1时钟使能
else
RCC->AHBENR |=1<<1; //DMA2时钟使能
/*------------------配置SPI1_RX_DMA通道---------------------*/
DMARx->CCR &= ~( 1<<14 ) ; //非存储器到存储器模式
DMARx->CCR |= 1<<12 ; //通道优先级高
DMARx->CCR &= ~( 3<<10 ) ; //存储器数据宽度8bit
DMARx->CCR &= ~( 3<<8 ) ; //外设数据宽度8bit
DMARx->CCR |= 1<<7 ; //存储器地址增量模式
DMARx->CCR &= ~( 1<<6 ) ; //不执行外设地址增量模式
DMARx->CCR &= ~( 1<<5 ) ; //不执行循环操作
DMARx->CCR &= ~( 1<<4 ) ; //从外设读
DMARx->CNDTR &= 0x0000 ; //传输数量寄存器清零
DMARx->CNDTR = 0 ; //传输数量设置为buffersize个
DMARx->CPAR = (u32)&SPI->SPIx->DR ; //设置外设地址,注意PSIZE
DMARx->CMAR = 0 ; //设置DMA存储器地址,注意MSIZE
/*------------------配置SPI1_TX_DMA通道Channel3---------------------*/
DMATx->CCR &= ~( 1<<14 ) ; //非存储器到存储器模式
DMATx->CCR |= 1<<12 ; //通道优先级中等
DMATx->CCR &= ~( 3<<10 ) ; //存储器数据宽度8bit
DMATx->CCR &= ~( 3<<8 ) ; //外设数据宽度8bit
DMATx->CCR |= 1<<7 ; //存储器地址增量模式
DMATx->CCR &= ~( 1<<6 ) ; //不执行外设地址增量模式
DMATx->CCR &= ~( 1<<5 ) ; //不执行循环操作
DMATx->CCR |= 1<<4 ; //从存储器读
DMATx->CNDTR &= 0x0000 ; //传输数量寄存器清零
DMATx->CNDTR = 0 ; //传输数量设置为buffersize个
DMATx->CPAR = (u32)&SPI->SPIx->DR ; //设置外设地址,注意PSIZE
DMATx->CMAR = 0 ; //设置DMA存储器地址,注意MSIZE
}
/*****************************************************************
**SPIx_DMA_Init:初始化SPI
**参数:
** SPI:SPI从设备
******************************************************************/
void SPIx_DMA_Init(SPIx_TypeDef *SPI)
{
__SPIx_Init(SPI);
SPI->SPIx->CR2 |= 1<<1 ; //发送缓冲区DMA使能
SPI->SPIx->CR2 |= 1<<0 ; //接收缓冲区DMA使能
SPI->SPIx->CR1|=1<<6; //SPI设备使能
SPIx_DMA_Config(SPI);
}
/*****************************************************************
**SPI1_DMA_ReadWriteBytes:发送接收多个字节
**参数:
** SPI:SPI从设备
** RxData:收到的缓存
**
**返回值:0接收失败,1:接收成功
******************************************************************/
u8 SPIx_DMA_ReadWriteBytes(SPIx_TypeDef *SPI,
u8 *RxData,u8 *TxData,u16 DataLen )
{
SPIx_DMA_TypeDef DMARTx;
if (!SPIx_GetDMA(SPI,&DMARTx)) return 0;
DMA_Channel_TypeDef *DMARx=DMARTx.DMARx;
DMA_Channel_TypeDef *DMATx=DMARTx.DMATx;
DMARx->CNDTR = 0x0000 ; //传输数量寄存器清零
DMARx->CNDTR = DataLen ; //传输数量设置为buffersize个
DMARx->CCR &= ~( 1<<5 ) ; //不执行循环操作
DMARx->CCR |= 1<<7 ; //存储器地址增量模式
DMARx->CMAR=(u32)RxData;
DMATx->CNDTR = 0x0000 ; //传输数量寄存器清零
DMATx->CNDTR = DataLen ; //传输数量设置为buffersize个
DMATx->CCR &= ~( 1<<5 ) ; //不执行循环操作
DMATx->CCR |= 1<<7 ; //存储器地址增量模式
DMATx->CMAR=(u32)TxData;
DMA_TypeDef *DMA;
u32 rx_ifcr,tx_ifcr;
if (DMARx==DMA1_Channel2)
{
rx_ifcr=0xF<<4;
tx_ifcr=0xF<<8;
DMA=DMA1;
}
else if (DMARx==DMA1_Channel4)
{
rx_ifcr=0xF<<12;
tx_ifcr=0xF<<16;
DMA=DMA1;
}
else
{
rx_ifcr=0xF;
tx_ifcr=0xF<<4;
DMA=DMA2;
}
DMA->IFCR = rx_ifcr ; //清除Rx通道的标志位
DMA->IFCR = tx_ifcr ; //清除Tx通道的标志位
SPI->SPIx->DR ; //接送前读一次SPI1->DR,保证接收缓冲区为空
u32 t=SPI->TimeOut;
while( ( SPI->SPIx->SR & 0x02 ) == 0 )
{
if ((t--)==0) return 0;
}
DMATx->CCR |= 1 << 0 ; //开启DMA通道3
DMARx->CCR |= 1 << 0 ; //开启DMA通道2
t=SPI->TimeOut*0xFFFF;
u8 res=1;
u32 isr=0;
if (DMA1_Channel2==DMARx) isr=1<<5;//DMA1通道2
else if (DMA1_Channel4==DMARx) isr=1<<13;//DMA1通道4
else isr=1<<0;//DMA2通道1
while( ( DMA->ISR & isr ) == 0 )
{
if (0==(t--))
{
res=0;
break;
}
}
DMATx->CCR &= ~( 1 << 0 ) ; //关闭DMA通道3
DMARx->CCR &= ~( 1 << 0 ) ; //关闭DMA通道2
return res;
}
/*****************************************************************
**SPIx_DMA_ReadBytes:发送一个字节
**参数:
** SPI:SPI从设备
** RxData:收到的字节
**返回值:0接收失败,1:接收成功
******************************************************************/
u8 SPIx_DMA_ReadBytes(SPIx_TypeDef *SPI,u8* RxData,u16 DataLen)
{
u8 b=0xFF;
SPIx_DMA_TypeDef DMARTx;
if (!SPIx_GetDMA(SPI,&DMARTx)) return 0;
DMA_Channel_TypeDef *DMARx=DMARTx.DMARx;
DMA_Channel_TypeDef *DMATx=DMARTx.DMATx;
DMARx->CNDTR = 0x0000 ; //传输数量寄存器清零
DMARx->CNDTR = DataLen ; //传输数量设置为buffersize个
DMARx->CCR &= ~( 1<<5 ) ; //不执行循环操作
DMARx->CMAR=(u32)RxData;
DMATx->CNDTR = 0x0000 ; //传输数量寄存器清零
DMATx->CNDTR = 1 ; //传输数量设置为buffersize个
DMATx->CCR &=~(1<<7) ; //存储器地址不增量模式
DMATx->CCR |= ( 1<<5 ) ; //执行循环操作
DMATx->CMAR=(u32)&b;
DMA_TypeDef *DMA;
u32 rx_ifcr,tx_ifcr;
if (DMARx==DMA1_Channel2)
{
rx_ifcr=0xF<<4;
tx_ifcr=0xF<<8;
DMA=DMA1;
}
else if (DMARx==DMA1_Channel4)
{
rx_ifcr=0xF<<12;
tx_ifcr=0xF<<16;
DMA=DMA1;
}
else
{
rx_ifcr=0xF;
tx_ifcr=0xF<<4;
DMA=DMA2;
}
DMA->IFCR = rx_ifcr ; //清除Rx通道的标志位
DMA->IFCR = tx_ifcr ; //清除Tx通道的标志位
SPI->SPIx->DR ; //接送前读一次SPI1->DR,保证接收缓冲区为空
u32 t=SPI->TimeOut;
while( ( SPI->SPIx->SR & 0x02 ) == 0 )
{
if ((t--)==0) return 0;
}
DMATx->CCR |= 1 << 0 ; //开启DMA通道3
DMARx->CCR |= 1 << 0 ; //开启DMA通道2
t=SPI->TimeOut*0xFFFF;
u8 res=1;
u32 isr=0;
if (DMA1_Channel2==DMARx) isr=1<<5;//DMA1通道2
else if (DMA1_Channel4==DMARx) isr=1<<13;//DMA1通道4
else isr=1<<0;//DMA2通道1
while( ( DMA->ISR & isr ) == 0 )
{
if (0==(t--))
{
res=0;
break;
}
}
DMATx->CCR &= ~( 1 << 0 ) ; //关闭DMA通道3
DMARx->CCR &= ~( 1 << 0 ) ; //关闭DMA通道2
return res;
}
/*****************************************************************
**SPIx_DMA_WriteBytes:发送多个字节
**参数:
** SPI:SPI从设备
** TxData:收到的字节
**返回值:0接收失败,1:接收成功
******************************************************************/
u8 SPIx_DMA_WriteBytes(SPIx_TypeDef *SPI,u8* TxData,u16 DataLen)
{
u32 b=0xFF;
SPIx_DMA_TypeDef DMARTx;
if (!SPIx_GetDMA(SPI,&DMARTx)) return 0;
DMA_Channel_TypeDef *DMARx=DMARTx.DMARx;
DMA_Channel_TypeDef *DMATx=DMARTx.DMATx;
DMARx->CNDTR = 0x0000 ; //传输数量寄存器清零
DMARx->CNDTR = 1 ; //传输数量设置为buffersize个
DMARx->CCR &=~(1<<7) ; //存储器地址不增量模式
DMARx->CCR |= ( 1<<5 ) ; //执行循环操作
DMARx->CMAR=(u32)&b;
DMATx->CNDTR = 0x0000 ; //传输数量寄存器清零
DMATx->CNDTR = DataLen ; //传输数量设置为buffersize个
DMATx->CCR |= 1<<7 ; //存储器地址增量模式
DMATx->CCR &= ~( 1<<5 ) ; //不执行循环操作
DMATx->CMAR=(u32)TxData;
DMA_TypeDef *DMA;
u32 rx_ifcr,tx_ifcr;
if (DMARx==DMA1_Channel2)
{
rx_ifcr=0xF<<4;
tx_ifcr=0xF<<8;
DMA=DMA1;
}
else if (DMARx==DMA1_Channel4)
{
rx_ifcr=0xF<<12;
tx_ifcr=0xF<<16;
DMA=DMA1;
}
else
{
rx_ifcr=0xF;
tx_ifcr=0xF<<4;
DMA=DMA2;
}
u32 isr=0;
if (DMA1_Channel3==DMATx) isr=1<<9;//DMA1通道3
else if (DMA1_Channel5==DMATx) isr=1<<17;//DMA1通道5
else isr=1<<5;//DMA2通道2
DMA->IFCR = rx_ifcr ; //清除Rx通道的标志位
DMA->IFCR = tx_ifcr ; //清除Tx通道的标志位
SPI->SPIx->DR ; //接送前读一次SPI1->DR,保证接收缓冲区为空
u32 t=SPI->TimeOut;
while( ( SPI->SPIx->SR & 0x02 ) == 0 )
{
if ((t--)==0) return 0;
}
DMATx->CCR |= 1 << 0 ; //开启DMA通道3
DMARx->CCR |= 1 << 0 ; //开启DMA通道2
t=SPI->TimeOut*0xFFFF;
u8 res=1;
while( ( DMA->ISR & isr ) == 0 )
{
// if (0==(t--))
// {
// res=0;
// break;
// }
}
DMATx->CCR &= ~( 1 << 0 ) ; //关闭DMA通道3
DMARx->CCR &= ~( 1 << 0 ) ; //关闭DMA通道2
return res;
}
//---------------main.c------------------------
#include "SPI_user.h"
u8 SPI1_TX_Buff[400];
void CS_LOW(void)
{
GPIOA->ODR &=~(0X1<<2); //PA2 下拉
}
void CS_High(void)
{
GPIOA->ODR |=0X1<<2; //PA2 上拉
}
int main(void)
{
//初始化LED灯,按键
RCC->APB2ENR |=1<<2;
RCC->APB2ENR |=1<<4;
GPIOA->CRH &=0xFFFFFFF0;
GPIOA->CRH |=3;
GPIOA->BRR=1<<8;
GPIOC->CRL &=0xFF0FFFFF;
GPIOC->CRL |=8<<20;
GPIOC->BSRR=1<<5;
//////////////////////////////////////
//W25Q64的片选脚配置
GPIOA->CRL&=0XFFFFF0FF;
GPIOA->CRL|=3<<(2<<2); //PA2.3.4 推挽
GPIOA->ODR|=0X1<<2; //PA2.3.4上拉
/////////////////////////////////////
u8 key=1;
SPIx_TypeDef SPI;
SPI.SPIx=SPI1;
SPI.SCK=SPI_SCK_FCPU32;
SPI.TimeOut=6800;
SPI.CSLow=CS_LOW;
SPI.CSHigh=CS_High;
SPI1_TX_Buff[0]=W25X_ReadData; //W25Q64的读数据命令
SPI1_TX_Buff[1]=0; //W25Q64的读取内存地址0
SPI1_TX_Buff[2]=0; //W25Q64的读取内存地址0
SPI1_TX_Buff[3]=0; //W25Q64的读取内存地址0
for (u16 i=0;i<256;i++)
{
SPI1_TX_Buff[4+i]=0xFF;//发送0xFF读数据
}
while (1)
{
if ((key==1) && (!(GPIOC->IDR & (1<<5))))
{
key=0;
GPIOA->BSRR=1<<8;
SPIx_DMA_Init(&SPI);
SPI.CSLow();
SPIx_DMA_WriteBytes(&SPI,SPI1_TX_Buff,4); //<-------------这里实际只发送了2个数据,原因未知,从逻辑分析仪来看,时钟还在,CS提前拉高了,说明提前结束了。
SPI.CSHigh();
}
}
}
SPIx_DMA_ReadWriteBytes,SPIx_DMA_ReadBytes都测试了256个字节,都正常,但是测试SPIx_DMA_WriteBytes,发现总是会漏掉最后两个字节没发送,不知道什么原因。DMA目前不是太明白,架构代码是抄来自己改的。为什么会有SPIx_DMA_WriteBytes这个方式,是因为发送时,只关注发送了多少,并不关注接收了多少,使用SPIx_DMA_ReadWriteBytes函数,需要事先分配一个匹配的接收数据缓存,但是这个缓存数据我并不关心,除了占内存没多大意义,所以SPIx_DMA_WriteBytes的思路如下:
1、SPI的DMA接收通道设置为循环模式,地址不增量模式,内存地址就是指向一个32位的变量,外设地址就是SPI的DR寄存器。
2、SPI的DMA发送通道设置为不循环模式,地址增量模式,内存地址指向要发送数据的地址,外设地址就是SPI的DR寄存器。
类似这种方式SPIx_DMA_ReadBytes函数,测试256个字节都是正常的,但是SPIx_DMA_WriteBytes函数,测试发送4个字节,实际只发送了2个,不知何故。
|
|