SPI配合DMA转运数据

第一次写博客,哪里有错误可以一起讨论,但不要爆粗口

硬件电路是STM32F103C8T6,W25Q64

软件是基于江科大STM32驱动W25Q64的代码上进行DMA转运数据

关于W25Q64的讲解可以去看别人的博客

实验目的:基于W25Q64的硬件平台来将SPI收到的数据转运到数组里.说明白一点就是STM32用SPI外设将一组数据写进W25Q64中,然后再把这组数据读出,读到数组1里,在读数据的同时再用DMA把这组数据转运到数组2里.

注明:SPI写W25Q64这部分代码是江科大的

版权:资料下载

第一次写博客什么都不知道

下面这部分代码是SPI控制W25Q64的代码

/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)
{
    MySPI_Start();                                                             //SPI起始
    MySPI_SwapByte(W25Q64_WRITE_ENABLE);        //交换发送写使能的指令
    MySPI_Stop();                                                            //SPI终止
}

/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void)
{
    uint32_t Timeout;
    MySPI_Start();                                                                                //SPI起始
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);    //交换发送读状态寄存器1的指令
    Timeout = 100000;                                                                        //给定超时计数时间
    while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)    //循环等待忙标志位
    {
        Timeout --;                                //等待时,计数值自减
        if (Timeout == 0)                        //自减到0后,等待超时
        {
            /*超时的错误处理代码,可以添加到此处*/
            break;                                //跳出等待,不等了
        }
    }
    MySPI_Stop();                                //SPI终止
}

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray    用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
    uint16_t i;
    
    W25Q64_WriteEnable();                        //写使能
    
    MySPI_Start();                                //SPI起始
    MySPI_SwapByte(W25Q64_PAGE_PROGRAM);        //交换发送页编程的指令
    MySPI_SwapByte(Address >> 16);                //交换发送地址23~16位
    MySPI_SwapByte(Address >> 8);                //交换发送地址15~8位
    MySPI_SwapByte(Address);                    //交换发送地址7~0位
    for (i = 0; i < Count; i ++)                //循环Count次
    {
        MySPI_SwapByte(DataArray[i]);            //依次在起始地址后写入数据
    }
    MySPI_Stop();                                //SPI终止
    
    W25Q64_WaitBusy();                            //等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
    W25Q64_WriteEnable();                        //写使能
    
    MySPI_Start();                                //SPI起始
    MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);    //交换发送扇区擦除的指令
    MySPI_SwapByte(Address >> 16);                //交换发送地址23~16位
    MySPI_SwapByte(Address >> 8);                //交换发送地址15~8位
    MySPI_SwapByte(Address);                    //交换发送地址7~0位
    MySPI_Stop();                                //SPI终止
    
    W25Q64_WaitBusy();                            //等待忙
}

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
    void SPI_DmaState(uint8_t State);
    uint32_t i;
    MySPI_Start();                                //SPI起始
    MySPI_SwapByte(W25Q64_READ_DATA);            //交换发送读取数据的指令
    MySPI_SwapByte(Address >> 16);                //交换发送地址23~16位
    MySPI_SwapByte(Address >> 8);                //交换发送地址15~8位
    MySPI_SwapByte(Address);                    //交换发送地址7~0位
    SPI_DmaState(ENABLE);                        //使能SPI的DMA,因为这时候SPI与W25Q64交换的数据才是我们想要的数据
    for (i = 0; i < Count; i ++)                //循环Count次
    {
        DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);    //依次在起始地址后读取数据
    }
    MySPI_Stop();                                //SPI终止
}


注意上面这行代码,等下有大用

底下是引脚的定义

#define DataSize 10

#define SPI1_NSS  GPIO_Pin_4
#define SPI1_SCK  GPIO_Pin_5
#define SPI1_MISO GPIO_Pin_6
#define SPI1_MOSI GPIO_Pin_7
#define SPI1_GPIO GPIOA

底下这部分代码是,定义各个SPI引脚,初始化各个SPI引脚,初始化SPI的DMA,初始化SPI1对应的DMA通道,配置DMA的传输完成中断以及配置中断优先级

void W25Q64_SPI_DMA_Init(uint32_t Periph_Addr, uint32_t Memory_Addr, uint16_t Size)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    //开启GPIOA的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);    //开启SPI1的时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        //开启DMA1的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode        =GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin            =SPI1_NSS;
    GPIO_InitStructure.GPIO_Speed        =GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                    
    
    GPIO_InitStructure.GPIO_Mode        =GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin            =SPI1_SCK|SPI1_MOSI;
    GPIO_InitStructure.GPIO_Speed        =GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                    
    
    GPIO_InitStructure.GPIO_Mode        =GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin            =SPI1_MISO;
    GPIO_InitStructure.GPIO_Speed        =GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);    
    
    /*SPI初始化*/
    SPI_InitTypeDef SPI_InitStructure;    
    SPI_InitStructure.SPI_Mode                    =SPI_Mode_Master;                    //模式,选择为SPI主模式
    SPI_InitStructure.SPI_Direction                =SPI_Direction_2Lines_FullDuplex;    //方向,选择2线全双工
    SPI_InitStructure.SPI_DataSize                =SPI_DataSize_8b;                    //数据宽度,选择为8位
    SPI_InitStructure.SPI_FirstBit                =SPI_FirstBit_MSB;                    //先行位,选择高位先行
    SPI_InitStructure.SPI_BaudRatePrescaler        =SPI_BaudRatePrescaler_128;            //波特率分频,选择128分频
    SPI_InitStructure.SPI_CPOL                    =SPI_CPOL_Low;                        //SPI极性,选择低极性
    SPI_InitStructure.SPI_CPHA                    =SPI_CPHA_1Edge;                    //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
    SPI_InitStructure.SPI_NSS                    =SPI_NSS_Soft;                        //NSS,选择由软件控制
    SPI_InitStructure.SPI_CRCPolynomial            =7;                                    //CRC多项式,暂时用不到,给默认值7
    SPI_Init(SPI1, &SPI_InitStructure);

    //DMA1的通道2初始化,因为SPI1的RX寄存器在DMA1的通道2
    DMA_InitTypeDef DMA_InitStructure;                                        
    DMA_InitStructure.DMA_PeripheralBaseAddr =Periph_Addr;                    //外设基地址
    DMA_InitStructure.DMA_MemoryBaseAddr     =Memory_Addr;                    //存储器基地址
    DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte;    //外设数据宽度
    DMA_InitStructure.DMA_MemoryDataSize      =DMA_MemoryDataSize_Byte;        //存储器数据宽度
    DMA_InitStructure.DMA_PeripheralInc      =DMA_PeripheralInc_Disable;    //外设地址自增,选择失能    
    DMA_InitStructure.DMA_MemoryInc          =DMA_MemoryInc_Enable;            //存储器地址自增,选择使能
    DMA_InitStructure.DMA_DIR                  =DMA_DIR_PeripheralSRC;        //数据传输方向,选择由外设到存储器
    DMA_InitStructure.DMA_BufferSize          =Size;                            //转运次数
    DMA_InitStructure.DMA_Mode                 =DMA_Mode_Normal;                //模式,选择正常模式
    DMA_InitStructure.DMA_M2M                 =DMA_M2M_Disable;                //存储器到存储器,选择失能
    DMA_InitStructure.DMA_Priority           =DMA_Priority_Medium;            //优先级,选择中等
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);                    
    
    //DMA的传输完成中断开启
    DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
    //配置抢占优先级和子优先级的数量
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //配置DMA通道
    NVIC_InitTypeDef NVIC_Str;
    NVIC_Str.NVIC_IRQChannel                    =DMA1_Channel2_IRQn;
    NVIC_Str.NVIC_IRQChannelPreemptionPriority    =1;
    NVIC_Str.NVIC_IRQChannelSubPriority            =0;
    NVIC_Str.NVIC_IRQChannelCmd                    =ENABLE;    
    NVIC_Init(&NVIC_Str);    
    //DMA的通道2使能
    DMA_Cmd(DMA1_Channel2, ENABLE);
    //SPI1的DMA使能
    SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Rx,ENABLE);
    //SPI1使能
    SPI_Cmd(SPI1, ENABLE);
    //SS默认高电平,片选未选中
    MySPI_W_SS(1);                                            
}

底下这个代码是开启DMA和DMA的中断
void SPI_DmaState(uint8_t State)
{    
    DMA_Cmd(DMA1_Channel2, State);    
        
    DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, State);
    
}
然后底下是主函数以及DMA中断函数

int main(void)
{    
    //初始化SPI的各个引脚,SPI的DMA,DMA的中断,以及中断的优先级
    W25Q64_SPI_DMA_Init((uint32_t)&SPI1->DR,(uint32_t)DMA_ArrayRead,DataSize);
    //先失能SPI的DMA,因为SPI还未将数据传入W25Q64中
    SPI_DmaState(DISABLE);
    //扇区擦除,参数为擦除区域的起始地址
    W25Q64_SectorErase(0x000000);
    //SPI把数组ArrayWrite的数据写入W25Q64具体的位置
    W25Q64_PageProgram(0x000000, ArrayWrite, DataSize);
    //SPI将刚刚写入的数据读出来,放到ArrayRead数组中
    W25Q64_ReadData(0x000000,ArrayRead,DataSize);
    while (1)
    {    
    }
}

//DMA传输完成后就将DMA传输完成标志位清除
void DMA1_Channel2_IRQHandler()
{
    DMA_ClearITPendingBit(DMA1_IT_TC2);
}

大体的思路:先配置好GPIO,SPI,DMA.主函数里,擦除W25Q64的扇区.此时失能DMA,因为我们要读的数据是在SPI对W25Q64发送读取数据的指令之后,把我们要读的那批数据用SPI交换出来后的那批数据.反例是:如果你在主函数的一开始没有失能DMA,那你得到的数据是SPI在发送交换发送

扇区擦除的指令
交换发送地址23~16位指令
交换发送地址15~8位指令
交换发送地址7~0位指令

这四个指令返回的值,因为SPI在与从机(W25Q64)发送指令时也会返回从从机(W25Q64)中读取的值对吧,此时你又没有把数据写入W25Q64中,所以你现在读取的值并不是你想要的值.因此我在我画红线的那个地方开启DMA,这样MDA转运的数据就是我们想要的数据.

代码部分就说完了,最终的结果

在调试的过程中遇到的问题:

1.放错DMA使能的地方了,导致读错了数据,把数据读成这样了

2.

User\main.c(211): error:  #167: argument of type "uint8_t *" is incompatible with parameter of type "uint32_t"就是把函数的参数写成了下面这个样子

这个错误的地方是你原来的函数参数是uint32_t的类型,而你传入的这个参数是uint8_t的指针,把参数强制转换类型就可以,像下面这样

  • 43
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值