• SPI通信实验


    本实验将使用 STM32F4 的 SPI 来读取外部 SPI FLASH 芯片(W25Q128),实现类似上次实验的功能。

    1)配置相关引脚的复用功能,使能 SPI1 时钟。

    使能 SPI1 时钟的方法为:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能 SPI1 时钟

    复用 PB3,PB4,PB5 为 SPI1 引脚的方法为:

    GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3 复用为 SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4 复用为 SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5 复用为 SPI1

    同时我们要设置相应的引脚模式为复用功能模式:

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

    2)初始化 SPI1,设置 SPI1 工作模式等。

    这一步全部是通过 SPI1_CR1 来设置,我们设置 SPI1 为主机模式,设置数据格式为 8 位, 然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 37.5Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。

    在库函数中初始化 SPI 的函数为:

    void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

     SPI_InitTypeDef 的定义:

    typedef struct

    {

    uint16_t SPI_Direction;

    uint16_t SPI_Mode;

    uint16_t SPI_DataSize;

    uint16_t SPI_CPOL;

    uint16_t SPI_CPHA;

    uint16_t SPI_NSS;

    uint16_t SPI_BaudRatePrescaler;

    uint16_t SPI_FirstBit;

    uint16_t SPI_CRCPolynomial;

    }SPI_InitTypeDef;

    第一个参数 SPI_Direction 是用来设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行 发和串行收方式,这里我们选择全双工模式 SPI_Direction_2Lines_FullDuplex。

    第二个参数 SPI_Mode 用来设置 SPI 的主从模式,这里我们设置为主机模式 SPI_Mode_Master, 

    第三个参数 SPI_DataSiz 为 8 位还是 16 位帧格式选择项,这里我们是 8 位传输,选择 SPI_DataSize_8b。

    第四个参数 SPI_CPOL 用来设置时钟极性,我们设置串行同步时钟的空闲状态为高电平所以我 们选择 SPI_CPOL_High。

    第五个参数 SPI_CPHA 用来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升 或下降)数据被采样,可以为第一个或者第二个条边沿采集,这里我们选择第二个跳变沿,所以选择 SPI_CPHA_2Edge

    第六个参数 SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制,这里我们通过软件控 制 NSS 关键,而不是硬件自动控制,所以选择 SPI_NSS_Soft。

    第七个参数 SPI_BaudRatePrescaler 很关键,就是设置 SPI 波特率预分频值也就是决定 SPI 的时 钟的参数,从 2 分频到 256 分频 8 个可选值,初始化的时候我们选择 256 分频值 SPI_BaudRatePrescaler_256, 传输速度为 84M/256=328.125KHz。

    第八个参数 SPI_FirstBit 设置数据传输顺序是 MSB 位在前还是 LSB 位在前,这里我们选择SPI_FirstBit_MSB 高位在前。

    第九个参数 SPI_CRCPolynomial 是用来设置 CRC 校验多项式,提高通信可靠性,大于 1 即可。

    3)使能 SPI1。

    这一步通过 SPI1_CR1 的 bit6 来设置,以启动 SPI1,在启动之后,我们就可以开始 SPI 通讯了。库函数使能 SPI1 的方法为:

    SPI_Cmd(SPI1, ENABLE); //使能 SPI1外设

    4)SPI 传输数据

    通信接口当然需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:

    void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

    固件库提供的接受数据函数原型为:

    uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;

    5)查看 SPI 传输状态

    在 SPI 传输过程中,我们经常要判断数据是否传输完成,发送区是否为空等等状态,这是通过函数 SPI_I2S_GetFlagStatus 实现的,判断发送是否完成的 方法是:

    SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);

    W25QXX_Read 函数,该函数用于从 W25Q128 的指定地址读 出指定长度的数据。

    1. //读取 SPI FLASH
    2. //在指定地址开始读取指定长度的数据
    3. //pBuffer:数据存储区
    4. //ReadAddr:开始读取的地址(24bit)
    5. //NumByteToRead:要读取的字节数(最大 65535)
    6. void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
    7. {
    8. u16 i;
    9. W25QXX_CS=0; //使能器件
    10. SPI1_ReadWriteByte(W25X_ReadData); //发送读取命令
    11. SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址
    12. SPI1_ReadWriteByte((u8)((ReadAddr)>>8));
    13. SPI1_ReadWriteByte((u8)ReadAddr);
    14. for(i=0;i
    15. {
    16. pBuffer[i]=SPI1_ReadWriteByte(0XFF); //循环读数
    17. }
    18. W25QXX_CS=1;
    19. }

    由于 W25Q128 支持以任意地址(但是不能超过 W25Q128 的地址范围)开始读取数据,所以,在发送 24 位地址之后,程序就可以开始循环读数据了, 其地址会自动增加的,不过要注意,不能读的数据超过了 W25Q128 的地址范围哦!否则读出来的数据,就不是想要的数据了。

    W25QXX_Write函数,这个函数比较长就不再展示,里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内 的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是 否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定 长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长 度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此 循环,直到写入结束。这里还定义了一个 W25QXX_BUFFER 的全局变量,用于擦除时缓 存扇区内的数据。

    main函数

    1. int main(void)
    2. {
    3. u8 key;
    4. u16 i=0;
    5. u8 datatemp[SIZE];
    6. u32 FLASH_SIZE;
    7. u16 id = 0;
    8. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    9. delay_init(168); //初始化延时函数
    10. uart_init(115200); //初始化串口波特率为115200
    11. LED_Init(); //初始化LED
    12. KEY_Init(); //按键初始化
    13. W25QXX_Init(); //W25QXX初始化
    14. while(1)
    15. {
    16. id = W25QXX_ReadID();
    17. if (id == W25Q128 || id == NM25Q128)
    18. break;
    19. printf("W25Q128 Check Failed!\r\n");
    20. delay_ms(500);
    21. printf("Please Check!\r\n");
    22. delay_ms(500);
    23. LED0=!LED0; //DS0闪烁
    24. }
    25. printf("W25Q128 Ready!\r\n");
    26. FLASH_SIZE=16*1024*1024; //FLASH 大小为16字节
    27. while(1)
    28. {
    29. key=KEY_Scan(0);
    30. if(key==KEY1_PRES)//KEY1按下,写入W25Q128
    31. {
    32. printf("Start Write W25Q128....\r\n");
    33. W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE); //从倒数第100个地址处开始,写入SIZE长度的数据
    34. printf("W25Q128 Write Finished!\r\n"); //提示传送完成
    35. }
    36. if(key==KEY0_PRES)//KEY0按下,读取字符串并显示
    37. {
    38. printf("Start Read W25Q128.... \r\n");
    39. W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE); //从倒数第100个地址处开始,读出SIZE个字节
    40. printf("The Data Readed Is: "); //提示传送完成
    41. printf("%s\r\n",datatemp); //显示读到的字符串
    42. }
    43. i++;
    44. delay_ms(10);
    45. if(i==20)
    46. {
    47. LED0=!LED0;//提示系统正在运行
    48. i=0;
    49. }
    50. }
    51. }

    运行视频

    SPI

  • 相关阅读:
    手写嵌入式操作系统(基于stm8单片机)
    PyTorch nn.RNN 参数全解析
    力扣代码学习日记八
    docker搭建waline评论系统
    【数据结构】栈
    【计算机基础知识7】垃圾回收机制与内存泄漏
    文件的目录
    最短路径问题
    JAVA计算机毕业设计在线云音乐系统Mybatis+源码+数据库+lw文档+系统+调试部署
    视频号挂公众号链接引流到公众号还能加,好消息来了
  • 原文地址:https://blog.csdn.net/qq_64531765/article/details/126674241