一是想总结一下SPI总线的特点与注意点,二是总结一下SPI DMA的使用

SPI总线

SPI信号线说明

通常SPI通过4个引脚与外部器件相连:

  • MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
  • MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
  • SCK:串口时钟,作为主设备的输出,从设备的输入
  • NSS:从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

原理

MOSI脚相互连接,MISO脚相互连接。这样,数据在主和从之间串行地传输(MSB位在前)。

通信总是由主设备发起。主设备通过MOSI脚把数据发送给从设备,从设备通过MISO引脚回传数据。这意味全双工通信的数据输出和数据输入是用同一个时钟信号同步的;时钟信号由主设备通过SCK脚提供。

NSS说明与注意点

NSS分为内部引脚和外部引脚。

  NSS外部引脚可以作为输入信号或者输出信号,输入信号一般用作硬件方式从机的片选,而输出信号一般用于主SPI去片选与之相连的从SPI。

  NSS从设备选择有两种模式:

1、软件模式

  可以通过设置SPI_CR1寄存器的SSM位来使能这种模式,当它为1时,NSS引脚上的电平由SSI决定。在这种模式下NSS外部引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动。 

2、硬件模式

​ 两种方式:

(1)对于主SPI,NSS可以直接接高电平,对于从SPI,可以直接接低电平。  

(2)当STM32F10xxx工作为主SPI,并且NSS输出已经通过SPI_CR2寄存器的SSOE位使能,这时主机的NSS讲作为输出信号,引脚信号被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并配置为硬件NSS的SPI设备,将自动变成从SPI设备。

  此时两个的NSS信号线可以接个上拉电阻直连。

DMA说明

DMA是AMBA的先进高性能总线(AHB)上的设备,它有2个AHB端口:一个是从端口,用于配置DMA,另一个是主端口,使得DMA可以在不同的从设备之间传输数据。

  DMA的作用是在没有Cortex-M3核心的干预下,在后台完成数据传输。在传输数据的过程中,主处理器可以执行其它任务,只有在整个数据块传输结束后,需要处理这些数据时才会中断主处理器的操作。它可以在对系统性能产生较小影响的情况下,实现大量数据的传输。

DMA原理

1.CPU配置好DMA。

2.SPI发出DMA请求。(在DMA_Mode_Normal模式下,该请求实际上需要CPU命令SPI发出请求)

3.若该通道有多个请求,DMA控制器通过仲裁器判断,根据配置的优先级,选择先回应该通道高优先级的请求,再回应低优先级的请求。(此过程不需要CPU参与)

4.DMA控制器回应请求后,自动根据配置,进行数据传输。(此过程不需要CPU参与)

DMA通道资源分配

DMA1通道

DMA2通道

SPI_DMA的通信过程

  • 设置外设地址

  • 设置存储器地址

  • 设置传输数据量

  • 设置通道的配置信息

  • 使能DMA通道,启动传输

  • 发送时,在每次TXE被设置为’1’时发出DMA请求,DMA控制器则写数据至SPI_DR寄存器,TXE标志因此而被清除。

  • 接收时,在每次RXNE被设置为’1’时发出DMA请求,DMA控制器则从SPI_DR寄存器读出数据,RXNE标志因此而被清除。

相关配置代码(方法一)

这里使用的是SPI1

SPI_DMA配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*******************************************************************************
* Function Name : SPI1_DMA_Configuration
* Description : 配置SPI1_RX的DMA通道2,SPI1_TX的DMA通道3
* Input : None
* Output : None
* Return : None
* Attention :
*******************************************************************************/
void SPI1_DMA_Configuration( void )
{
DMA_InitTypeDef DMA_InitStructure;

/* DMA1 Channel2 (triggered by SPI1 Rx event) Config */
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_PeripheralBaseAddr = SPI1_DR_Addr; //设置 SPI1 发送外设(0x4001300C) 地址(目的地址)
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SPI1_RX_Buff; //设置 SRAM 存储地址(目的地址)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向 外设-内存
DMA_InitStructure.DMA_BufferSize = SPI1_ReciveBufferSize; //设置 SPI1 发送长度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);

DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
/* Enable SPI1 DMA RX request */
SPI1->CR2 |= 1<<0; //接收缓冲区DMA使能
DMA_Cmd(DMA1_Channel2, ENABLE);


/* DMA1 Channel3 (triggered by SPI1 Tx event) Config */
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = SPI1_DR_Addr; //设置 接收外设(0x4001300C) 地址(源地址)
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SPI1_TX_Buff; //设置 SRAM 存储地址(源地址)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //传输方向 内存-外设
DMA_InitStructure.DMA_BufferSize = SPI1_SendBufferSize; //设置 SPI1 接收长度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址增量(不变)
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址增量(变化)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设传输宽度(字节)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存传输宽度(字节)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //传输方式,一次传输完停止,不重新加载
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //中断方式-高(三级)
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //内存到内存方式禁止
DMA_Init(DMA1_Channel3, &DMA_InitStructure);

DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE); //开启 DMA1_Channel3 传输完成中断
DMA_ITConfig(DMA1_Channel3, DMA_IT_TE, ENABLE); //开启 DMA1_Channel3 传输错误中断
/* Enable SPI1 DMA TX request */
SPI1->CR2 |= 1<<1; //发送缓冲区DMA使能
DMA_Cmd(DMA1_Channel3, DISABLE); //开启 DMA 通道 DMA1_Channel3
}

SPI发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*******************************************************************************
* Function Name : SPI1_Send
* Description : SPI1的DMA方式发送
* Input : SPI1_TX_Buff[SPI1_SendBufferSize]
* Output : None
* Return : None
* Attention : 关闭DMA通道3之前必须等待TXE为1,等待忙标志为0
*******************************************************************************/
void SPI1_Send( u8 *buff, u32 len )
{
DMA1_Channel3->CPAR = SPI1_DR_Addr; //外设地址
DMA1_Channel3->CMAR = (u32) buff; //mem地址
DMA1_Channel3->CNDTR = len ; //传输长度
DMA1_Channel3->CCR = (0 << 14) | // 非存储器到存储器模式
(2 << 12) | // 通道优先级高
(0 << 11) | // 存储器数据宽度8bit
(0 << 10) | // 存储器数据宽度8bit
(0 << 9) | // 外设数据宽度8bit
(0 << 8) | // 外设数据宽度8bit
(1 << 7) | // 存储器地址增量模式
(0 << 6) | // 外设地址增量模式(不增)
(0 << 5) | // 非循环模式
(1 << 4) | // 从存储器读
(1 << 3) | // 允许传输错误中断
(0 << 2) | // 允许半传输中断
(1 << 1) | // 允许传输完成中断
(1); // 通道开启
}

SPI接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*******************************************************************************
* Function Name : SPI1_Recive
* Description : SPI1的DMA方式接收
* Input : None
* Output : SPI1_RX_Buff[SPI1_ReciveBufferSize]
* Return : None
* Attention : 必须要先关闭通道2,然后再配置通道2的参数
*******************************************************************************/
void SPI1_Recive( u8 *buff, u32 len )
{
DMA1_Channel2->CCR &= ~( 1 << 0 ); //关闭DMA通道2

DMA1_Channel2->CPAR = SPI1_DR_Addr; //外设地址
DMA1_Channel2->CMAR = (uint32_t)buff; //mem地址
DMA1_Channel2->CNDTR = len ; //传输长度
DMA1_Channel2->CCR = (0 << 14) | // 非存储器到存储器模式
(2 << 12) | // 通道优先级高
(0 << 11) | // 存储器数据宽度8bit
(0 << 10) | // 存储器数据宽度8bit
(0 << 9) | // 外设数据宽度8bit
(0 << 8) | // 外设数据宽度8bit
(1 << 7) | // 存储器地址增量模式
(0 << 6) | // 外设地址增量模式(不增)
(0 << 5) | // 非循环模式
(0 << 4) | // 传输方向 外设-内存
(0 << 3) | // 允许传输错误中断
(0 << 2) | // 允许半传输中断
(1 << 1) | // 允许传输完成中断
(1); // 通道开启
}

相关配置代码(方法二)

既然使用的是SPI+DMA,必定要使能SPI,详细请参考

SPI配置

配置完SPI后,配置DMA,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void dma_init(unsigned int SendBuff,unsigned int buffer_size)
{

DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel3);
/*DMA配置*/

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
//SPI数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
//内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
//方向(从内存到外设)
//DMA_DIR_PeripheralSRC为从外设到内存
DMA_InitStructure.DMA_BufferSize = buffer_size;
//传输内容的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte ;
//外设数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ;
//内存数据单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
//DMA模式:一次传输
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium ;
//优先级:高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//禁止内存到内存的传输

DMA_Init(DMA1_Channel3, &DMA_InitStructure);
//配置DMA1的3通道
DMA_Cmd(DMA1_Channel3, ENABLE); //使能DMA
}

注意:DMA_PeripheralBaseAddr不是SPI外设的地址,而是SPI1->DR即SPI数据寄存器的地址

因为DMA_Mode_Normal模式使用一次后DMA_BufferSize会清零,如下代码用于重置DMA_BufferSize

1
2
3
4
5
6
7
8
9
10
void DMA_Buffercounter_reset(unsigned int buffer_size)
{
DMA_Cmd(DMA1_Channel3, DISABLE );
//失能DMA,使得DMA_SetCurrDataCounter能够使用
DMA_SetCurrDataCounter(DMA1_Channel3,buffer_size);
//一次传输模式,DMA_BufferSize执行一次后会清零
//后续需要重复传输的时候,需要使用该函数再次设置DMA_BufferSize
DMA_Cmd(DMA1_Channel3, ENABLE);
//使能DMA
}

主函数编写如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define buff_size 256
unsigned char sendbuff[buff_size];

int mian(coid)
{
dma_init((unsigned int)sendbuff,buff_size);
while(1)
{
for(i=0 ;i<buff_size;i++)
{
sendbuff[i]=color;
}
for(i = 0 ; i<(32768/buff_size); i++)
{

SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE);
//SPI向DMA发出请求,DMA会通过仲裁器自动回应请求
DMA_Buffercounter_reset(buff_size);
//重置DMA_BufferSize,为下一次DMA传送做好准备

while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC3)!=RESET)//判断DMA是否传输完成
{
DMA_ClearFlag(DMA1_FLAG_TC3);//DMA传输完成,清除标志位
break;
}
}
}
}

}

相关链接(侵删)

  1. STM32 SPI DMA 的使用

=================我是分割线=================

欢迎到公众号来唠嗑: