• STM32——SPI通信实验


    程序配置过程:

    1、使能SPIx和IO口时钟:

    RCC_AHBxPeriphClockCmd()/RCC_APBxPeriphClockCmd();

    2、初始化IO口为复用功能:

    void GPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct);

    3、设置引脚复用映射:

    GPIO_PinAFConfig();

    4、初始化SPIx,设置SPIx工作模式:

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

    5、使能SPIx:

    void SPI_Cmd(SPI_TypeDef* SPIx,FunctionalState NewState);

    6、SPI传输数据:

    void SPI_I2C_SendData(SPI_TypeDef* SPIx,uint16_t Data);

    uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

    7、查看SPI传输数据:

    SPI_I2C_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE);

    使能SPIx和IO口时钟

    1. void SPI_Init(void)
    2. {
    3. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
    4. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
    5. }

    初始化IO口为复用功能

    1. void SPI_Init(void)
    2. {
    3. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
    4. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
    5. GPIO_InitTypeDef GPIO_InitABC;
    6. GPIO_InitABC.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5复用功能输出
    7. GPIO_InitABC.GPIO_Mode=GPIO_Mode_AF;//复用功能
    8. GPIO_InitABC.GPIO_OType=GPIO_OType_PP;//推挽输出
    9. GPIO_InitABC.GPIO_Speed=GPIO_Speed_100MHz;//100MHz
    10. GPIO_InitABC.GPIO_PuPd=GPIO_PuPd_UP;//上拉
    11. GPIO_Init(GPIOB,&GPIO_InitABC);
    12. }

    设置引脚复用映射

    1. void SPI_Init(void)
    2. {
    3. GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3复用为SPI1
    4. GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为SPI1
    5. GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为SPI1
    6. RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1
    7. RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1
    8. }

     初始化SPIx,设置SPIx工作模式

    1. void SPI_Init(void)
    2. {
    3. SPI_InitTypeDef SPI_InitABC;
    4. SPI_InitABC.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//设置SPI单向或者双向的数据模式
    5. SPI_InitABC.SPI_Mode=SPI_Mode_Master;//设置SPI工作模式:设置为主SPI
    6. SPI_InitABC.SPI_DataSize=SPI_DataSize_8b;//设置SPI的数据大小:SPI发送接收8为帧结构
    7. SPI_InitABC.SPI_CPOL=SPI_CPOL_High;//串行同步时钟的空闲状态为高电平
    8. SPI_InitABC.SPI_CPHA=SPI_CPHA_2Edge;//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    9. SPI_InitABC.SPI_NSS=SPI_NSS_Soft;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理
    10. SPI_InitABC.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;//定义波特率预分频的值
    11. SPI_InitABC.SPI_FirstBit=SPI_FirstBit_MSB;//指定数据传输从MSB位开始
    12. SPI_InitABC.SPI_CRCPolynomial=7;//CRC值计算的多项式
    13. SPI_Init(SPI1,&SPI_InitABC);
    14. }

    使能SPIx

    1. void SPI_Init(void)
    2. {
    3. SPI_Cmd(SPI1,ENABLE);
    4. SPI1_ReadWriteByte(0xff);
    5. }
    6. //设置SPI1速度
    7. void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
    8. {
    9. assert_param(IS_SPI_BAUDRATEPRESCALER(SPI_BauudRatePrescaler));//判断有效性
    10. SPI1->CR1&=0XFFC7;//位3~5清零,用来设置波特率
    11. SPI1->CR1|=SPI_BaudRatePrescaler;//设置SPI1速度
    12. SPI_Cmd(SPI1,ENABLE);//使能SPI1
    13. }

    SPI传输数据 

    1. u8 SPI1_ReadEriteByte(u8 TxData)
    2. {
    3. while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET){}//等待发送区空
    4. SPI_I2S_SendData(SPI1,TxData);//通过外设SPIx发送一个byte数据
    5. while(SPI_I2S_GetFlagStatus(SPI1_I2S_FLAG_RXNE)==RESET){}//等待接收完一个byte
    6. return SPI_I2S_ReceiveData(SPI1);//返回通过SPIx最近接收的数据
    7. }

    程序思路:

    写一个地址之前需要先判断是不是被擦除了(比如擦除之后的值为100,则需要将内容与100相比较从而判断是否擦除了),若未擦除则需要擦除,FLASH的最小单位是4K。

    W25QXX_Write函数思路:每个sector是4K(等于4096个地址),在写任何一个地址前,如果该地址的值不是0xFF,必须先擦除对应的sector然后再写。

    擦除的原理:将整个sector读出来放到一个4k的Buffer中,然后将sector擦除,在Buffer中更新数据,然后将Buffer中的数据写到sector中。(写的数据未超越一个sector)如果写的数据超过一个sector,处理方式相同。

    相关代码:

    1. void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
    2. {
    3. u32 secpos;
    4. u16 secoff;
    5. u16 i;
    6. u16 secremain;
    7. u8* W25QXX_BUF;
    8. W25QXX_BUF=W25QXX_BUFFER;
    9. secpos=WriteAddr/4096;//确定扇区地址(也就是sector的地址)
    10. secoff=WriteAddr%4096;//判断在扇区的偏移(判断sector中数据的位置,是否跨越一个sector)
    11. secremain=4096-secoff;//扇区剩余空间(sector剩余空间)
    12. if(NmByteToWrite<=secremain) secremain=NumByteToWrite;//如果写入的数据小于剩余的空间,就把secremain设置为写入的数据大小,这样只要写到了secremain就会终止。
    13. while(1)//先把扇区的数据读取出来
    14. {
    15. W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
    16. for(i=1;i//校验数据,是否擦除
    17. {
    18. if(W25QXX_BUF[secoff+i]!=oXFF) break;//需要擦除
    19. }
    20. if(i//
    21. {
    22. W25QXX_Erase_Sector(secpos);//擦除扇区
    23. for(i=0;i
    24. {
    25. W25QXX_BUF[i+secoff]=pBuffer[i];
    26. }
    27. W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
    28. }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间。
    29. if(NumByteToWrite==secremain) break;//写入结束了
    30. else
    31. {
    32. secpos++;//扇区地址递增1
    33. secoff=0;//偏移位置为0
    34. pBuffer+=secremain;//指针偏移
    35. WriteAddr+=secremain;//写地址偏移
    36. NumByteToWrite-=secremain;//字节数递减
    37. if(NumByteToWrite>4096) secremain=4096;//下一个扇区还是没写完
    38. else secremain=NumByteToWrite;//下一个扇区可以写完了
    39. }
    40. }
    41. }
  • 相关阅读:
    Spring Cloud Gateway夺命连环10问,带你彻底了解gateway
    【视觉SLAM入门】8. 回环检测,词袋模型,字典,感知,召回,机器学习
    AODNet复现: 用gpu批量处理图片
    【Leetcode每日一刷】贪心算法01:455.分发饼干、376. 摆动序列、53. 最大子序和
    腾讯音乐:说说Redis脑裂问题?
    k8s 亲和、反亲和、污点、容忍
    使用JMeter创建数据库测试
    2022年最新java之异常
    Swan学院社团招新
    tensorflow 与 cuda和cuDNN的版本对应表
  • 原文地址:https://blog.csdn.net/weixin_62584795/article/details/126668706