第一次写博客,哪里有错误可以一起讨论,但不要爆粗口
硬件电路是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的指针,把参数强制转换类型就可以,像下面这样