使用SPI DMA在STM32上读写外部Flash
.为什么要使用DMA?
STM32硬件spi其实速度也算快,对于非大数据的读写外部flash操作,使用spi查询方式,基本就能处理很多问题,但是对于ota项目中,需要将固件写入外部flash,使用flash+dma能在减少cpu的负担。
最重要就是下面的代码 ,这是STM32F407标准库代码,HAL大家可以参考安富莱。
第一步:配置DMA通道数据流!
打开我们的stm32f407数据手册,找到DMA这一章节,
我们找到请求映射这一块,我们可以看到,通道三有SPI1rx SPI1tx,那有的人就要问了,为啥通道三上面有两对spirx,spitx呀,其实用那一对都可以,因为同一通道同一时刻只能用一个数据流,所以,我们选通道三数据流的0和3即可。
接下来就开始配置我们的数据流了
由于spi是全双工通信,那么全双通信就需要注意,你发的时候同时在在收数据,你接受数据的时候也是在发数据,那么如何保证我收的时候,不发送数据呢,我们来看这段代码。
isread 表示你是读操作还是写操作,是1表示读操作,读操作就是从SPI->DR读取数据,到内部存储器,那么我就需要把写的数据流 的DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递。
梳理一下流程:
读的时候,第一步:我需要设置好接收数据流buffer地址,设置存储器地址自增,
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer; //读方式下,把接收的数据传到buffer里
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
第二步:配置发送数据流中的DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
写的时候:第一步:先配置接收数据流中的DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
第二步:配置写数据流中的发送buffer 并且地址自增
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
总结:读数据时候禁止写数据流的buffer(&0x0xFFFFFFFF,不自增),写数据的时候禁止读数据流buffer(&0x0xFFFFFFFF,不自增)。
if两次判断就是这么来的:
void flash_start_dma(uint8_t *buffer, uint32_t length, uint8_t isread)
{
uint32_t temp = 0xffffffff;
DMA_InitTypeDef DMA_InitStructure;
DMA_StructInit(&DMA_InitStructure);//先初始化DMA的结构体
DMA_DeInit(DMA2_Stream0);//Rx 读 设置数据流初始化
DMA_DeInit(DMA2_Stream3);//TX 写 设置数据流初始化
while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE);
while(DMA_GetCmdStatus(DMA2_Stream3) != DISABLE);
/* DMA1 channel 0 stream0 --- 配置 SPI3-RX DMA */
DMA_InitStructure.DMA_Channel = DMA_Channel_3;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
if (isread == 1)
{
/* 读方式下回读数据放到 buffer 中*/
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer; //读方式下,把接收的数据传到buffer里
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存自增
}
else
{
/* 写方式下回读数据丢弃
代码健壮性:我这个通道配置的是读,如果不是读,那么我就设置我的地址不自增,不使能
第一个if判断:重点:配置DMA2_Stream0,这是用于接收数据的DMA流(SPI的RX,即接收数据)。
当isread为1时,
表示执行读操作,DMA应该将数据从SPI数据寄存器(SPI1->DR)传输到内存中的buffer。因此,
设置DMA_Memory0BaseAddr为buffer的地址,并启用内存地址递增(DMA_MemoryInc_Enable)。
如果不是读操作(即写操作),则不需要从SPI读取数据到内存,
此时DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递
*/
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
}
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = length;
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_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
/* DMA1 channel 0 stream7 --- 配置 SPI3-TX DMA */
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_Channel = DMA_Channel_3;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
if (isread == 1)
{
/* 读方式下发送 0xff
我现在是配置写,那么你读到的数据
此时DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递
*/
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
}
else
{
/* 写方式下发送 buffer 数据*/
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
}
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = length;
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_Low;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream3, &DMA_InitStructure);
/* 1.开启 DMA 数据流传输完成中断 */
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE);
/* 2.使能 DMA 数据流 */
DMA_Cmd(DMA2_Stream0, ENABLE);
DMA_Cmd(DMA2_Stream3, ENABLE);
/* 3.使能 SPI DMA 请求 */
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx|SPI_I2S_DMAReq_Tx, ENABLE);
/* 5.等待 DMA 传输完成 互锁机制 那我怎么知道你写 或者读完了呢 肯定在中断里面进行的*/
spi_wait_tx();
spi_wait_rx();
}
第二步:DMA中断都做了啥?
开启两个中断服务函数
void DMA2_Stream3_IRQHandler(void);(SPI1TX)
void DMA2_Stream0_IRQHandler(void);(SPI1RX)
主要任务:1.清除中断标志位
2.关闭中断;
3.禁止SPI请求;
4.禁止DMA数据流;
5.指标志位;
低功耗模式:在某些低功耗应用中,
一旦DMA传输完成,系统可能希望尽快进入低功耗状态。
禁用中断和DMA流有助于减少系统的活动,从而降低功耗。
void DMA2_Stream3_IRQHandler(void) //写中断
{
if (DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_TCIF3) != RESET)
{
/* 1.清中断标志 */
DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_TCIF3);
/* 2.禁能 DMA 传输完成中断 */
DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, DISABLE);//写完马上关掉中断
/* 3.禁能 SPI TX DMA 请求 */
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
/* 4.禁能 DMA Stream */
DMA_Cmd(DMA2_Stream3, DISABLE);
/* 5.通知应用程序 TX 传输完成 */
spi_post_tx();
}
}
void DMA2_Stream0_IRQHandler(void)
{
if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0) != RESET)
{
/* 1.清中断标志 */
DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);
/* 2.禁能 DMA 传输完成中断 */
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, DISABLE);
/* 3.禁能 SPI TX DMA 请求 */
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);
/* 4.禁能 DMA Stream */
DMA_Cmd(DMA2_Stream0, DISABLE);
/* 6.通知应用程序 RX 传输完成 */
spi_post_rx();
}
}
第三步:调用flash_start_dma,启动DMA
步骤:先使用轮询方式发送指令,在通过DMA进行搬运数据,
void flash_write_page(uint8_t* buffer, uint32_t addr)
{
// u8 i;
flash_enable_write();
spi_chip_cs(1);
spi_byte_transfer(0x02);
spi_byte_transfer((addr >> 16) & 0xFF);
spi_byte_transfer((addr >> 8) & 0xFF);
spi_byte_transfer((addr >> 0) & 0xFF);
flash_start_dma(buffer, FLASH_PAGE_SIZE, 0);
spi_chip_cs(0);
// for( i= 0;i<strlen(buffer);i++)
// {
// addr = addr+1;
// }
flash_wait_write();
}
总结:
以上就是SPI+DMA读取flash的内容
作者:苏锦编程