找回密码
 立即注册
楼主: wuzhengmin

25.SPI读写W25X40CL - 按k1

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 11:17 | 显示全部楼层
这段串口处理函数,要慢慢读,我加了注释:



先大致看懂,细节慢慢理解:

  1.           else if((RX2_Buffer[0] == 'W') && (RX2_Cnt >= 12) && (RX2_Buffer[10] == ' '))   //写入N个字节
  2.             {
  3.                
  4.                                 //检测要写入的空间是否为空
  5.                                
  6.                                 j = RX2_Cnt - 11;  //要写入的字节数
  7.                 for(i=0; i<j; i++)  tmp[i] = 0xff;      //循环初始化,典型的数组遍历或缓冲区填充操作
  8.                                
  9. /*将临时数组 tmp 的第 i 个元素赋值为 0xFF(即二进制 11111111)。
  10. 常见用途:
  11. 初始化:将缓冲区填充为固定值(如全1),可能用于后续校验或清空旧数据。
  12. 测试写入:模拟写入操作,检测硬件(如Flash、EEPROM)是否正常响应。
  13. 协议填充:某些通信协议要求空闲位或填充位为高电平(对应 0xFF)。存储设备(如Flash)的擦除或写入前准备。
  14. 而函数u8 SPI_Read_Compare(u32 addr, u8 *buffer, u16 size)的功能是
  15. 读出n个字节,跟指定的数据进行比较, 错误返回1,正确返回0
  16.                        
  17. */
  18.                                
  19.                 i = SPI_Read_Compare(Flash_addr,tmp,j);
  20.                
  21.                                
  22.                 if(i > 0) //SPI_Read_Compare和全部是FF的数组对比,全部是FF返回0
  23.                 {
  24.                     PrintString2("要写入的地址为非空,不能写入,请先擦掉!\r\n");
  25.                 }
  26.                 else
  27.                 {
  28.                     SPI_Write_Nbytes(Flash_addr,&RX2_Buffer[11],j);     //写N个字节
  29.                                         //在C语言中,数组前面加&表示取整个数组的地址
  30.                                         //&数组名的类型是指向整个数组的指针,而不是一个指向指针常量的指针
  31.                                        
  32.                     i = SPI_Read_Compare(Flash_addr,&RX2_Buffer[11],j); //比较写入的数据
  33.                     if(i == 0)
  34.                     {
  35.                         PrintString2("已写入");
  36.                         if(j >= 100)    {UART2_TxByte((u8)(j/100+'0'));   j = j % 100;}
  37.                         if(j >= 10)     {UART2_TxByte((u8)(j/10+'0'));    j = j % 10;}
  38.                         UART2_TxByte((u8)(j%10+'0'));
  39.                         PrintString2("字节内容!\r\n");
  40.                     }
  41.                     else        PrintString2("写入错误!\r\n");
  42.                 }
  43.                 F0 = 1;
  44.             }
  45.             else if((RX2_Buffer[0] == 'R') && (RX2_Cnt >= 12) && (RX2_Buffer[10] == ' '))   //读出N个字节
  46.             {
  47.                 j = GetDataLength();
  48.                 if((j > 0) && (j < EE_BUF_LENGTH))
  49.                 {
  50.                     SPI_Read_Nbytes(Flash_addr,tmp,j);
  51.                     PrintString2("读出");
  52.                     if(j>=100)  UART2_TxByte((u8)(j/100+'0'));
  53.                     UART2_TxByte((u8)(j%100/10+'0'));
  54.                     UART2_TxByte((u8)(j%10+'0'));
  55.                     PrintString2("个字节内容如下:\r\n");
  56.                     for(i=0; i<j; i++)  UART2_TxByte(tmp[i]);
  57.                     UART2_TxByte(0x0d);
  58.                     UART2_TxByte(0x0a);
  59.                     F0 = 1;
  60.                 }
  61.             }
  62.         }
  63.     }
  64.     if(!F0) PrintString2("命令错误!\r\n");
复制代码


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 11:24 | 显示全部楼层
主要注意细节:

我们先给F0 软件置0 ,等我们执行一段语句命令后,F0还能成功保持是0,就 执行if(!F0) PrintString2("命令错误!\r\n"); 打印“命令错误!”,不成功保持0,就说明我们这段语句正常执行了。本身这段语句读、写的时候不断检查有没有写错,读错,有错的话就让F0等于0   ----- 如果正确顺利的写入和读出了就让F0 = 1;
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 11:39 | 显示全部楼层
函数u8 SPI_Read_Compare(u32 addr, u8 *buffer, u16 size)比较重要:


我们仔细看:

我已经详细标了注释:


/************************************************************************
读出n个字节,跟指定的数据进行比较, 错误返回1,正确返回0
buffer先装填好要比较数组的值,长度也是u16 size
************************************************************************/
u8 SPI_Read_Compare(u32 addr, u8 *buffer, u16 size)
{
    u8  j;
    if(size == 0)   return 2;
    if(!B_FlashOK)  return 2;
    while(CheckFlashBusy() > 0);            //Flash忙检测

    j = 0;
    SPI_CE_Low();                           //enable device
    SPI_WriteByte(SFC_READ);                //read command
    SPI_WriteByte(((u8 *)&addr)[1]);        //设置起始地址
    SPI_WriteByte(((u8 *)&addr)[2]);
    SPI_WriteByte(((u8 *)&addr)[3]);
    do
    {
        if(*buffer != SPI_ReadByte())       //receive byte and store at buffer
                                                                                        //接收字节并和存储在缓冲区中的buffer每个组员相比较
                                                                                        //如果按位读出来的和buffer相应组员相等,什么都不干
                                                                                        //不相等,j=1;跳出循环,返回 1,否则返回 0
        {
            j = 1;
            break;
        }
        buffer++;
    }while(--size);         //read until no_bytes is reached
                                                        //读取直到没有更多的字节可以读取”或“读取直到达到文件末尾”。
    SPI_CE_High();          //disable device
    return j;
}


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 12:08 | 显示全部楼层
接着看 ---除扇区, 一个扇区4KB



我们也在函数上注释:


/************************************************
擦除扇区, 一个扇区4KB
入口参数: 无
出口参数: 无
************************************************/
void FlashSectorErase(u32 addr)
{
    if(B_FlashOK)
    {
        FlashWriteEnable();             //使能Flash写命令
        SPI_CE_Low();
        if(B_FlashOK == 1)
        {
            SPI_WriteByte(SFC_SECTORER1);    //发送#define SFC_SECTORER1   0xD7  即PM25LV040 扇区擦除指令
        }
        else
        {
            SPI_WriteByte(SFC_SECTORER2);    //发送#define SFC_SECTORER2   0x20 即W25Xxx 扇区擦除指令
        }
               
                SPI_WriteByte(((u8 *)&addr)[1]);           //设置起始地址
        SPI_WriteByte(((u8 *)&addr)[2]);
        SPI_WriteByte(((u8 *)&addr)[3]);
               
//        (u8 *)&addr的意思是将addr的地址转换为unsigned char类型的指针。在C语言中,(type *)&variable是一种类型
//转换语法,它将variable的地址转换为type类型的指针。因此,(u8 *)&addr将addr的地址转换为unsigned char类型的指针
               
        SPI_CE_High();
    }
}


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 12:15 | 显示全部楼层


SPI 写一个字节



/************************************************************************/
void SPI_WriteByte(u8 out)
{
    SPDAT = out;
    while(SPIF == 0) ;
    SPIF = 1;   //清SPIF标志
    WCOL = 1;   //清WCOL标志
}


硬件写,程序很简单:写 等 清标志
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 14:46 | 显示全部楼层
好了,轮到SPI初始化:



/************************************************************************/
void SPI_init(void)
{
    SSIG = 1; //忽略 SS 引脚功能,使用 MSTR 确定器件是主机还是从机
    SPEN = 1; //使能 SPI 功能
    DORD = 0; //先发送/接收数据的高位( MSB)
    MSTR = 1; //设置主机模式
    CPOL = 1; //SCLK 空闲时为高电平,SCLK 的前时钟沿为下降沿,后时钟沿为上升沿
    CPHA = 1; //数据在 SCLK 前时钟沿驱动,后时钟沿采样
    //采用漏极开路+上拉电阻方式驱动,需要调低SPI频率才能正常与Flash通信
    SPCTL = (SPCTL & ~3) | 2;   //SPI 时钟频率选择, 0: 4T, 1: 8T,  2: 16T,  3: 2T
                                                                // spctl 的最低两位设置为 10,即选择1/16倍时钟频率(1/16T)
/*
1.        位操作解释:
(SPCTL & ~3):这一步是清除 spctl 变量的最低两位。~3 是对3取反,即二进制的 11111111111111111111111111111100(假设是32位整数)。与 spctl 进行按位与操作,会清除最低两位。
| 2:这一步是将结果与2进行按位或操作。2的二进制是 10,所以这一步是将 spctl 的最低两位设置为 10。
2.        时钟频率选择:
0:1/4倍时钟频率(1/4T)
1:1/8倍时钟频率(1/8T)
2:1/16倍时钟频率(1/16T)
3:1/2倍时钟频率(1/2T)
这行代码将 spctl 的最低两位设置为 10,即选择1/16倍时钟频率(1/16T)。
通过位操作可以高效地配置硬件寄存器。

*/       
               
    P_SW1 = (P_SW1 & ~(3<<2)) | (1<<2);     //IO口切换. 0: P1.2 /P5.4 P1.3 P1.4 P1.5, 1: P2.2 P2.3 P2.4 P2.5, 2: P5.4 P4.0 P4.1 P4.3, 3: P3.5 P3.4 P3.3 P3.2

/*
        代码解释
位操作:

3<<2:将数字3左移2位,得到二进制值1100,即十进制的12。
~(3<<2):对1100取反,得到0011(假设是4位寄存器)。

p_sw1 & ~(3<<2):将p_sw1寄存器的第3位和第2位清零。

(1<<2):将数字1左移2位,得到二进制值0100,即十进制的4。
(p_sw1 & ~(3<<2)) | (1<<2):将p_sw1寄存器的第3位和第2位设置为01。

实际是 1 对应的 SPI功能脚切换为 P2.2 P2.3 P2.4 P2.5


*/       
       
    HSCLKDIV = 0x01;        //高速时钟1分频,默认2分频
   
    P_PM25LV040_SCK = 0;    // set clock to low initial state
    P_PM25LV040_SI = 1;
    SPIF = 1;   //清SPIF标志
    WCOL = 1;   //清WCOL标志
}


已经注释的很清楚了
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 14:49 | 显示全部楼层
/************************************************************************/
void SPI_WriteByte(u8 out)
{
    SPDAT = out;
    while(SPIF == 0) ;
    SPIF = 1;   //清SPIF标志
    WCOL = 1;   //清WCOL标志
}

/************************************************************************/
u8 SPI_ReadByte(void)
{
    SPDAT = 0xff;
    while(SPIF == 0) ;
    SPIF = 1;   //清SPIF标志
    WCOL = 1;   //清WCOL标志
    return (SPDAT);
}



这个SPI写一个字节,读一个字节,因为是硬件读写,程序很简单

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 14:56 | 显示全部楼层


检测Flash是否准备就绪,B_FlashOK = 0就出错,B_FlashOK = 1 为PM25LVxx   B_FlashOK = 2  W25X4x系列  B_FlashOK = 3 W25X8x系列


/************************************************
检测Flash是否准备就绪
入口参数: 无
出口参数:
    0 : 没有检测到正确的Flash
    1 : Flash准备就绪
************************************************/
void FlashCheckID(void)
{
    SPI_CE_Low();
    SPI_WriteByte(SFC_RDID);        //发送读取ID命令
    SPI_WriteByte(0x00);            //空读3个字节
    SPI_WriteByte(0x00);
    SPI_WriteByte(0x00);
    PM25LV040_ID1 = SPI_ReadByte();         //读取制造商ID1
    PM25LV040_ID  = SPI_ReadByte();         //读取设备ID
    PM25LV040_ID2 = SPI_ReadByte();         //读取制造商ID2
    SPI_CE_High();

//    UART2_TxByte(PM25LV040_ID1);
//    UART2_TxByte(PM25LV040_ID);
//    UART2_TxByte(PM25LV040_ID2);
       
    if((PM25LV040_ID1 == 0x9d) && (PM25LV040_ID2 == 0x7f))  B_FlashOK = 1;  //检测是否为PM25LVxx系列的Flash
    else if(PM25LV040_ID == 0x12)  B_FlashOK = 2;                           //检测是否为W25X4x系列的Flash
    else if(PM25LV040_ID == 0x13)  B_FlashOK = 3;                           //检测是否为W25X8x系列的Flash
    else                                                    B_FlashOK = 0;
}


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 15:11 | 显示全部楼层

下面这2个函数,最好理解:


/************************************************
检测Flash的忙状态
入口参数: 无
出口参数:
    0 : Flash处于空闲状态
    1 : Flash处于忙状态
************************************************/
u8 CheckFlashBusy(void)
{
    u8  dat;

    SPI_CE_Low();
    SPI_
WriteByte(SFC_RDSR);        //发送读取状态命令
    dat = SPI_ReadByte();           //读取状态
    SPI_CE_High();

    return (dat);                   //状态值的Bit0即为忙标志
}

/************************************************
使能Flash写命令
入口参数: 无
出口参数: 无
************************************************/
void FlashWriteEnable(void)
{
    while(CheckFlashBusy() > 0);    //Flash忙检测
    SPI_CE_Low();
    SPI_WriteByte(SFC_WREN);        //发送写使能命令
    SPI_CE_High();
}


到此为止,所有的子函数都准备后了,下面看看主函数:




回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:47
  • 最近打卡:2025-10-21 08:57:14
已绑定手机

4

主题

356

回帖

627

积分

高级会员

积分
627
发表于 昨天 15:21 | 显示全部楼层
/********************* 主函数 *************************/
void main(void)
{
    WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXFR = 1; //扩展寄存器(XFR)访问使能
    CKCON = 0; //提高访问XRAM速度

    P0M1 = 0x30;   P0M0 = 0x30;   //设置P0.4、P0.5为漏极开路(实验箱加了上拉电阻到3.3V)
    P1M1 = 0x32;   P1M0 = 0x32;   //设置P1.1、P1.4、P1.5为漏极开路(实验箱加了上拉电阻到3.3V), P1.1在PWM当DAC电路通过电阻串联到P2.3
    P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
    P3M1 = 0x50;   P3M0 = 0x50;   //设置P3.4、P3.6为漏极开路(实验箱加了上拉电阻到3.3V)
    P4M1 = 0x3c;   P4M0 = 0x3c;   //设置P4.2~P4.5为漏极开路(实验箱加了上拉电阻到3.3V)
    P5M1 = 0x0c;   P5M0 = 0x0c;   //设置P5.2、P5.3为漏极开路(实验箱加了上拉电阻到3.3V)
    P6M1 = 0xff;   P6M0 = 0xff;   //设置为漏极开路(实验箱加了上拉电阻到3.3V)
    P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口

    delay_ms(10);
    UART2_config(2);    // 选择波特率, 2: 使用Timer2做波特率, 其它值: 无效.

    EA = 1;                     //打开总中断

    PrintString2("命令设置:\r\n");
    PrintString2("E 0x001234            --> 扇区擦掉  十六进制地址\r\n");
    PrintString2("W 0x001234 1234567890 --> 写入操作  十六进制地址  写入内容\r\n");
    PrintString2("R 0x001234 10         --> 读出操作  十六进制地址  读出字节\r\n");
    PrintString2("C                     --> 如果检测不到PM25LV040/W25X40CL/W25Q80BV, 发送C强制允许操作.\r\n\r\n");

    SPI_init();
    FlashCheckID();
    FlashCheckID();

    if(!B_FlashOK)  PrintString2("未检测到PM25LV040/W25X40CL/W25Q80BV!\r\n");
    else
    {
        if(B_FlashOK == 1)
        {
            PrintString2("检测到PM25LV040!\r\n");
        }
        else if(B_FlashOK == 2)
        {
            PrintString2("检测到W25X40CL!\r\n");
        }
        else if(B_FlashOK == 3)
        {
            PrintString2("检测到W25Q80BV!\r\n");
        }
        PrintString2("制造商ID1 = 0x");
        UART2_TxByte(Hex2Ascii(PM25LV040_ID1 >> 4));
        UART2_TxByte(Hex2Ascii(PM25LV040_ID1));
        PrintString2("\r\n      ID2 = 0x");
        UART2_TxByte(Hex2Ascii(PM25LV040_ID2 >> 4));
        UART2_TxByte(Hex2Ascii(PM25LV040_ID2));
        PrintString2("\r\n   设备ID = 0x");
        UART2_TxByte(Hex2Ascii(PM25LV040_ID >> 4));
        UART2_TxByte(Hex2Ascii(PM25LV040_ID));
        PrintString2("\r\n");
    }

    while(1)
    {
        delay_ms(1);

        if(RX2_TimeOut > 0)
        {
            if(--RX2_TimeOut == 0)  //超时,则串口接收结束
            {
                if(RX2_Cnt > 0)
                {
                    RX2_Check();    //串口2处理数据
                }
                RX2_Cnt = 0;
            }
        }
    }
}


还有3个子函数没有研究:




回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-10-22 06:55 , Processed in 0.130426 second(s), 91 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表