一、配置SPI引脚
本例中SPI只连接了一个设备,即国产128kB EEPROM上海贝岭BL25CMIA。
NSS脚采用软件控制。
其它引脚通过查看手册可知其连接的SPI外设为SPI1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26stc_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);
二、初始化SPI1
开启SPI1时钟,给其上电。
1
2#define SPI_UNIT_CLOCK (PWC_FCG1_SPI1)
1
2PWC_Fcg1PeriphClockCmd(SPI_UNIT_CLOCK, Enable);
根据EEPROM芯片特性,配置SPI1:
1
2
3
4/* SPI unit and clock definition */ #define SPI_UNIT (M4_SPI1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33stc_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的设置手册没有提及,这里设到最低。
三、读一个字节
上海贝岭BL25CMIA是一个128kB的EEPROM,其容量超过了16位寻址的上限(65536字节)。该芯片采用24位寻址,所以SPI发送地址时需要从低到高发送三个字节,其中高字节中只有第0位有效,该字节的其它位芯片does not care。
- 开片选
- 读EEPROM 的状态寄存器
1
2#define E2PROM_READ_STATUS_REGESITER (0x05U)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/** * @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; }
第一个字节发指令,第二个字节随便发一个用于都回状态。
用于发送字节的函数为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/** * @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; }
- 读取存储空间中指定地址内的存储值。最后关闭片选
1
2#define E2PROM_READ_MEMORY (0x03U)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38/** * @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字节就可以了。
四、写一个字节
- 开片选
- 读EEPROM 的状态寄存器
- 开启写允许
1
2#define E2PROM_WRITE_ENABLE (0x06U)
1
2
3
4
5
6
7
8
9
10
11
12
13
14/** * @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 的状态寄存器
- 写一个字节,最后关片选
1
2#define E2PROM_WRITE_MEMORY (0x02U)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36/** * @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。
1
2
3
4
5
6E2PROM_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);
五、嵌入式的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都不对,则应进行异常处理,比如加载默认值。
六、测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32for (;;) { 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个小时,未发生错误。
最后
以上就是虚拟樱桃最近收集整理的关于华大半导体HC32F4A0笔记(四),SPI读写国产128kB EEPROM 上海贝岭BL25CMIA的全部内容,更多相关华大半导体HC32F4A0笔记(四),SPI读写国产128kB内容请搜索靠谱客的其他文章。
发表评论 取消回复