打印
[方案相关]

HC32F4A0 SPI读写国产128kB EEPROM 上海贝岭BL25CMIA

[复制链接]
4168|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
coshi|  楼主 | 2021-9-1 09:47 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、配置SPI引脚

本例中SPI只连接了一个设备,即国产128kB EEPROM上海贝岭BL25CMIA。

NSS脚采用软件控制。
其它引脚通过查看手册可知其连接的SPI外设为SPI1。


/* SPI port definition for master */
#define SPI_NSS_PORT                    (GPIO_PORT_B)
#define SPI_NSS_PIN                     (GPIO_PIN_12)

#define SPI_SCK_PORT                    (GPIO_PORT_B)
#define SPI_SCK_PIN                     (GPIO_PIN_13)
#define SPI_SCK_FUNC                    (GPIO_FUNC_40_SPI1_SCK)

#define SPI_MOSI_PORT                   (GPIO_PORT_B)
#define SPI_MOSI_PIN                    (GPIO_PIN_15)
#define SPI_MOSI_FUNC                   (GPIO_FUNC_41_SPI1_MOSI)

#define SPI_MISO_PORT                   (GPIO_PORT_B)
#define SPI_MISO_PIN                    (GPIO_PIN_14)
#define SPI_MISO_FUNC                   (GPIO_FUNC_42_SPI1_MISO)



        stc_gpio_init_t stcGpioInit;
       
                    /* Port configurate */
    (void)GPIO_StructInit(&stcGpioInit);

    /* High driving capacity for output pin. */
    stcGpioInit.u16PinDir = PIN_DIR_OUT;
    stcGpioInit.u16PinDrv = PIN_DRV_HIGH;
    stcGpioInit.u16PinState = PIN_STATE_SET;
    (void)GPIO_Init(SPI_NSS_PORT,  SPI_NSS_PIN, &stcGpioInit);

    (void)GPIO_StructInit(&stcGpioInit);
    stcGpioInit.u16PinDrv = PIN_DRV_HIGH;
    (void)GPIO_Init(SPI_SCK_PORT,  SPI_SCK_PIN, &stcGpioInit);
    (void)GPIO_Init(SPI_MOSI_PORT, SPI_MOSI_PIN, &stcGpioInit);

    /* CMOS input for input pin */
    stcGpioInit.u16PinDrv = PIN_DRV_LOW;
    stcGpioInit.u16PinIType = PIN_ITYPE_CMOS;
    (void)GPIO_Init(SPI_MISO_PORT, SPI_MISO_PIN, &stcGpioInit);

    /* Configure SPI Port function for master */
    GPIO_SetFunc(SPI_SCK_PORT,  SPI_SCK_PIN,  SPI_SCK_FUNC, PIN_SUBFUNC_DISABLE);
    GPIO_SetFunc(SPI_MOSI_PORT, SPI_MOSI_PIN, SPI_MOSI_FUNC, PIN_SUBFUNC_DISABLE);
    GPIO_SetFunc(SPI_MISO_PORT, SPI_MISO_PIN, SPI_MISO_FUNC, PIN_SUBFUNC_DISABLE);



使用特权

评论回复
沙发
coshi|  楼主 | 2021-9-1 09:49 | 只看该作者
二、初始化SPI1

开启SPI1时钟,给其上电。

#define SPI_UNIT_CLOCK                  (PWC_FCG1_SPI1)


    PWC_Fcg1PeriphClockCmd(SPI_UNIT_CLOCK, Enable);


根据EEPROM芯片特性,配置SPI1:

/* SPI unit and clock definition */
#define SPI_UNIT                        (M4_SPI1)



    stc_spi_init_t stcSpiInit;
    stc_spi_delay_t stcSpiDelayCfg;

    /* Clear initialize structure */
    (void)SPI_StructInit(&stcSpiInit);
    (void)SPI_DelayStructInit(&stcSpiDelayCfg);

    /* Configure peripheral clock */
    PWC_Fcg1PeriphClockCmd(SPI_UNIT_CLOCK, Enable);

    /* SPI De-initialize */
    SPI_DeInit(SPI_UNIT);

    /* Configuration SPI structure */
    stcSpiInit.u32WireMode          = SPI_WIRE_3;                                //SPI只接了一个芯片,无需切换片选
    stcSpiInit.u32TransMode         = SPI_FULL_DUPLEX;                        //全双工
    stcSpiInit.u32MasterSlave       = SPI_MASTER;                                //SPI主机
    stcSpiInit.u32SuspMode          = SPI_COM_SUSP_FUNC_OFF;        //不自动挂起
    stcSpiInit.u32Modfe             = SPI_MODFE_DISABLE;                //请勿开启该位
    stcSpiInit.u32Parity            = SPI_PARITY_INVALID;                //不开奇偶校验
    stcSpiInit.u32SpiMode           = SPI_MODE_0;                                //空闲低电平,奇数边沿采样
    stcSpiInit.u32BaudRatePrescaler = SPI_BR_PCLK1_DIV32;                //32分频,PCLK1为100M,32分频为3.1M,EEPROM芯片上限5M
    stcSpiInit.u32DataBits          = SPI_DATA_SIZE_8BIT;                //一次传输8位
    stcSpiInit.u32FirstBit          = SPI_FIRST_MSB;                        //高位在前
    (void)SPI_Init(SPI_UNIT, &stcSpiInit);

    stcSpiDelayCfg.u32IntervalDelay = SPI_INTERVAL_TIME_1SCK_2PCLK1;//SPI_INTERVAL_TIME_8SCK_2PCLK1;        //t3:下次存取延时
    stcSpiDelayCfg.u32ReleaseDelay = SPI_RELEASE_TIME_1SCK;//SPI_RELEASE_TIME_8SCK;                                //t2:振荡停止到片选无效时间
    stcSpiDelayCfg.u32SetupDelay = SPI_SETUP_TIME_1SCK;//SPI_SETUP_TIME_8SCK;                                        //t1:片选有效到开始振荡时间
    (void)SPI_DelayTimeCfg(SPI_UNIT, &stcSpiDelayCfg);

    SPI_FunctionCmd(SPI_UNIT, Enable);


SPI,配置比较简单,需要说明的是:

  • stcSpiInit.u32WireMode = SPI_WIRE_3; //SPI只接了一个芯片,无需切换片选这个选3线4线都可以。注意这里的3线是指不包含NSS片选线,我们通过软件来片选。
  • stcSpiInit.u32BaudRatePrescaler = SPI_BR_PCLK1_DIV32; //32分频,PCLK1为100M,32分频为3.1M,EEPROM芯片上限5M分频时候注意对照EEPROM的芯片手册
  • t1,t2,t3的设置手册没有提及,这里设到最低。



使用特权

评论回复
板凳
coshi|  楼主 | 2021-9-1 09:53 | 只看该作者
本帖最后由 coshi 于 2021-9-1 09:57 编辑

三、读一个字节

上海贝岭BL25CMIA是一个128kB的EEPROM,其容量超过了16位寻址的上限(65536字节)。该芯片采用24位寻址,所以SPI发送地址时需要从低到高发送三个字节,其中高字节中只有第0位有效,该字节的其它位芯片does not care。


  • 开片选
  • 读EEPROM 的状态寄存器

#define E2PROM_READ_STATUS_REGESITER    (0x05U)


/**
* @brief SPI EEPROM Read Status
*
* @param [in] None
*
* @retval uint8_t
*/
static uint8_t E2PROM_Read_Status(void)
{
  uint8_t EE_Status;

  SPI_NSS_LOW();
        
  /* send "Read Status Register" instruction */
  Spi_E2PROM_WriteReadByte(E2PROM_READ_STATUS_REGESITER);
               
  /* send a dummy byte to generate the clock needed by the EEPROM
  and put the value of the status register in EE_Status variable */
  EE_Status = Spi_E2PROM_WriteReadByte(0xff);

  /* deselect the EEPROM */
  SPI_NSS_HIGH();        

  /* return the status register value */               
  return EE_Status;   
}



第一个字节发指令,第二个字节随便发一个用于都回状态。
用于发送字节的函数为:

/**
* @brief SPI flash write byte function
*
* @param [in] u8Data                      SPI write data to EE
*
* @retval uint8_t                         SPI receive data from EE
*/
static uint8_t Spi_E2PROM_WriteReadByte(uint8_t u8Data)
{
    uint8_t u8Byte;

    /* Wait tx buffer empty */
    while (Reset == SPI_GetStatus(SPI_UNIT, SPI_FLAG_TX_BUFFER_EMPTY))
    {
    }
    /* Send data */
    SPI_WriteDataReg(SPI_UNIT, (uint32_t)u8Data);

    /* Wait rx buffer full */
    while (Reset == SPI_GetStatus(SPI_UNIT, SPI_FLAG_RX_BUFFER_FULL))
    {
    }
    /* Receive data */
    u8Byte = (uint8_t)SPI_ReadDataReg(SPI_UNIT);

    return u8Byte;
}


读取存储空间中指定地址内的存储值。最后关闭片选
#define E2PROM_READ_MEMORY                                (0x03U)



/**
* @brief SPI EEPROM Read a Byte
*
* @param [in] 1.unsigned int
*
* @retval uint8_t
*/
uint8_t E2PROM_Read_Byte(unsigned int address, uint8_t high_or_low64)
{
    uint8_t data;
    while((E2PROM_Read_Status()&0x01) == 0x01);  //091119

        SPI_NSS_LOW();
        uint8_t addr0_7;
        uint8_t addr8_15;
        uint8_t addr16_23 = high_or_low64;
        addr0_7 = address & 0xff;
        addr8_15 = (address & 0xff00)>>8;
        
        /* send "Read Status Register" instruction */
        Spi_E2PROM_WriteReadByte(E2PROM_READ_MEMORY);
        
        Spi_E2PROM_WriteReadByte(addr16_23);

        Spi_E2PROM_WriteReadByte(addr8_15);

        Spi_E2PROM_WriteReadByte(addr0_7);

        data = Spi_E2PROM_WriteReadByte(0xff);

        /* deselect the EEPROM */
        SPI_NSS_HIGH();

        /* return the status register value */               
    return data;

}


  • 读取存储空间中指定地址内的存储值。最后关闭片选
#define E2PROM_READ_MEMORY                              (0x03U)


/**
* @brief SPI EEPROM Read a Byte
*
* @param [in] 1.unsigned int
*
* @retval uint8_t
*/
uint8_t E2PROM_Read_Byte(unsigned int address, uint8_t high_or_low64)
{
    uint8_t data;
    while((E2PROM_Read_Status()&0x01) == 0x01);  //091119

SPI_NSS_LOW();
uint8_t addr0_7;
uint8_t addr8_15;
uint8_t addr16_23 = high_or_low64;
addr0_7 = address & 0xff;
addr8_15 = (address & 0xff00)>>8;
  
/* send "Read Status Register" instruction */
Spi_E2PROM_WriteReadByte(E2PROM_READ_MEMORY);
   
Spi_E2PROM_WriteReadByte(addr16_23);

Spi_E2PROM_WriteReadByte(addr8_15);

Spi_E2PROM_WriteReadByte(addr0_7);

data = Spi_E2PROM_WriteReadByte(0xff);

/* deselect the EEPROM */
SPI_NSS_HIGH();

/* return the status register value */     
    return data;

}


如果要读地址连续的多个字节,只需要继续发dummy字节就可以了。



  

使用特权

评论回复
地板
单片小菜| | 2021-9-1 09:56 | 只看该作者
这个还是比较好的,很不错的应用案例。

使用特权

评论回复
5
coshi|  楼主 | 2021-9-1 09:58 | 只看该作者
四、写一个字节

开片选
读EEPROM 的状态寄存器
开启写允许
#define E2PROM_WRITE_ENABLE                                (0x06U)


/**
* @brief SPI EE write enable function
*
* @param [in] None
*
* @retval None
*/
static void Spi_E2PROM_WriteEnable(void)
{
    SPI_NSS_LOW();
    (void)Spi_E2PROM_WriteReadByte(E2PROM_WRITE_ENABLE);
    SPI_NSS_HIGH();
}


读EEPROM 的状态寄存器
写一个字节,最后关片选
#define E2PROM_WRITE_MEMORY                                (0x02U)
1
/**
* @brief SPI EEPROM Write a Byte
*
* @param [in] 1.unsigned int    2.uint8_t
*
* @retval None
*/
void E2PROM_Write_Byte(unsigned int address, uint8_t high_or_low64, uint8_t data)
{
    while((E2PROM_Read_Status()&0x01) == 0x01);  //20130528

    Spi_E2PROM_WriteEnable();     //091119

    while((E2PROM_Read_Status()&0x02) != 0x02);    //091119
       
        SPI_NSS_LOW();
        uint8_t addr0_7;
        uint8_t addr8_15;
        uint8_t addr16_23 = high_or_low64;
        addr0_7 = address & 0xff;
        addr8_15 = (address & 0xff00)>>8;
        /* send "Read Status Register" instruction */
        Spi_E2PROM_WriteReadByte(E2PROM_WRITE_MEMORY);
       
        Spi_E2PROM_WriteReadByte(addr16_23);

        Spi_E2PROM_WriteReadByte(addr8_15);

        Spi_E2PROM_WriteReadByte(addr0_7);

        Spi_E2PROM_WriteReadByte(data);

        /* deselect the EEPROM */
        SPI_NSS_HIGH();
}


写也可以写地址连续的多个字节,继续发字节就行了,最多256个,而且应从256字节(一页)的整数倍地址开始写。


最后请注意,两次写(字节或页)间隔应大于6ms,本例为8ms。



                E2PROM_Write_Byte(address, E2PROM_LOW64KB, data);
                SysTick_Delay(8 * SYSTICK_MINISECOND);
               
                E2PROM_Write_Byte(address + 0x8000, E2PROM_LOW64KB, data ^ CHECK_XOR1);
                SysTick_Delay(8 * SYSTICK_MINISECOND);


使用特权

评论回复
6
coshi|  楼主 | 2021-9-1 09:59 | 只看该作者
五、嵌入式的EEPROM的可靠性读写

如果应用中需要存储的数据不多,EEPROM的容量数倍于数据量,且对读写EEPROM的准确性可靠性要求比较高。应当将同一份数据及其备份均匀放在EEPROM中的多处。本例中原始数据存放在第0~127页,即0x000000到0x008000中。另外三份备份分别放在第128到255页,256到383页,384到511页。

  • 原始数据原值直接存储进去
  • 备份一经与0x3c异或后存进去
  • 备份二经与0x96异或后存进去
  • 备份三经与0x5a异或后存进去

写完以后应该立即读出来,检查是否和写入值一样。如果不一样,程序应该进行异常处理。

读取时将4个数据均读取出来。三个备份的数据读出后,再分别用0x3c,0x96,和0x5a异或(任意一个数n,与另一个数m,连续两次按位异或后等于n自己,(n ^ m) ^ m == n ),四个数中至少三个数相等才可信。如果不到三个数相等,程序应进行异常处理,比如加载默认值。

如果是整页的读写,应该使用CRC8进行校验。

同样的,整页数据也应该存入多份作为备份。本例中同样是4份,在地址中均匀分布。备份的数据同样经过上述的异或处理。并且,每一份256个字节存完以后,需要计算其CRC8,并把它也存入EEPROM中。由于数据量较大,写完以后可以不用立刻读取进行验证。

读取时,先读取原始数据页,读取后,对原始256个数据进行CRC8计算。再从EEPROM中读取原始数据的CRC8并与刚才计算的结果相比。如果结果一致,则直接使用。如果不一致,用相同的方法读取并验证备份一的,如果结果相同,则异或回来使用。如果仍然不相同,继续使用备份2……备份3的。如果所有的CRC8都不对,则应进行异常处理,比如加载默认值。


使用特权

评论回复
7
coshi|  楼主 | 2021-9-1 10:00 | 只看该作者
六、测试

    for (;;)
    {               
                if (E2PROM_Enhanced_Write_Wave(20, t_e2dataArr) == Ok)
                {
                        if(E2PROM_Enhanced_Read_Wave(20, r_e2dataArr) == Ok)
                        {
                                if (t_e2dataArr[0] == 0)
                                {
                                        for (int i=0;i<256;i++)
                                        {
                                                t_e2dataArr[i] = 255-i;
                                        }
                                }
                                else
                                {
                                        for (int i=0;i<256;i++)
                                        {
                                                t_e2dataArr[i] = i;
                                        }
                                }
                        }
                        else
                        {
                                GPIO_ResetPins(LED_GREEN_PORT, LED_GREEN_PIN);
                        }
                }
                else
                {
                        GPIO_ResetPins(LED_GREEN_PORT, LED_GREEN_PIN);
                }
        }


在main函数死循环内进行连续不间断读写测试,这里如果发生读写错误就点亮LED。测试3个小时,未发生错误。


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

95

主题

3308

帖子

4

粉丝