【国产MCU系列】-GD32F470-内部集成电路总线接口(I2C)

内部集成电路总线接口(I2C)


I2C总线是PHILIPS公司推出的一种双线式半双工串行总线,是具备多主机系统所需的总线裁决和高低速器件同步功能的性能串行总线。用于连接微控制器及外围设备。I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。

1、GD32F4的I2C模块介绍

I2C(内部集成电路总线)模块提供了符合工业标准的两线串行制接口,可用于 MCU和外部I2C设备的通讯。 I2C总线使用两条串行线:串行数据线 SDA和串行时钟线 SCL。

I2C接口模块实现了 I2C协议的标速模式和快速模式,具备 CRC计算和校验功能、支持 SMBus(系统管理总线)、 PMBus(电源管理总线)和 SAM_V(验证安全控制模块)模式,此外还支持多主机 I2C总线架构。 I2C接口模块也支持 DMA模式,可有效减轻 CPU的负担。

GD32F4单片机的I2C模块具有如下特性:

■ \blacksquare 并行总线至I2C总线协议的转换及接口。

<think>我们正在寻找GD32F470驱动W25Q128的代码示例和配置方法。根据引用[1]中的信息,GD32的驱动适配相关文件放置在`//drivers/adapter/platform`中,并且通过HDF机制加载。但这里我们更关注的是裸机驱动,因为W25Q128是SPIFlash,所以我们需要配置SPI外设。另外,引用[3]提到FlashDB的移植建立在Fal(flash抽象层)的基础上,而Fal又需要底层flash驱动。因此,我们可以参考Fal的移植方法,其中就包括W25Q128的驱动。步骤:1.初始化SPI外设(配置GPIO、SPI参数等)2.实现W25Q128的读写函数(包括读写数据、读写状态寄存器、擦除等)3.将W25Q128的驱动挂载到Fal(如果使用Fal)或者直接使用由于用户没有明确是否使用Fal,我们先提供裸机驱动代码,然后简要说明如何与Fal结合。###一、硬件连接假设使用GD32F470的SPI0,连接方式如下:-W25Q128的`CS`接PA4(SPI0_NSS)-`SCK`接PA5(SPI0_SCK)-`MISO`接PA6(SPI0_MISO)-`MOSI`接PA7(SPI0_MOSI)###二、SPI初始化首先配置SPI0的GPIO和SPI外设。```c#include"gd32f4xx.h"voidspi_config(void){/*配置SPI0的GPIO:PA4,PA5,PA6,PA7*/rcu_periph_clock_enable(RCU_GPIOA);rcu_periph_clock_enable(RCU_SPI0);/*配置SPI0引脚:PA4(SPI0_NSS)推挽输出PA5(SPI0_SCK)复用推挽输出PA6(SPI0_MISO)浮空输入PA7(SPI0_MOSI)复用推挽输出*/gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_4);gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_4);gpio_mode_set(GPIOA,GPIO_MODE_AF,GPIO_PUPD_NONE,GPIO_PIN_5|GPIO_PIN_7);gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_5|GPIO_PIN_7);gpio_mode_set(GPIOA,GPIO_MODE_AF,GPIO_PUPD_NONE,GPIO_PIN_6);gpio_af_set(GPIOA,GPIO_AF_5,GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);/*设置SPI0参数*/spi_parameter_structspi_init_struct;spi_init_struct.trans_mode=SPI_TRANSMODE_FULLDUPLEX;//全双工spi_init_struct.device_mode=SPI_MASTER;//主机spi_init_struct.frame_size=SPI_FRAMESIZE_8BIT;//8位数据spi_init_struct.clock_polarity_phase=SPI_CK_PL_HIGH_PH_2EDGE;//模式3(CPOL=1,CPHA=1)spi_init_struct.nss=SPI_NSS_SOFT;//软件控制NSSspi_init_struct.prescale=SPI_PSC_8;//预分频8(主频120MHz时,SPI时钟15MHz)spi_init_struct.endian=SPI_ENDIAN_MSB;//高位在前spi_init(SPI0,&spi_init_struct);/*使能SPI0*/spi_enable(SPI0);}```###三、W25Q128驱动函数实现基本的读写函数,包括写使能、读状态寄存器、扇区擦除、页编程、读数据等。```c/*W25Q128指令定义*/#defineW25Q_CMD_WRITE_ENABLE0x06#defineW25Q_CMD_WRITE_DISABLE0x04#defineW25Q_CMD_READ_STATUS_REG10x05#defineW25Q_CMD_READ_STATUS_REG20x35#defineW25Q_CMD_WRITE_STATUS_REG0x01#defineW25Q_CMD_PAGE_PROGRAM0x02#defineW25Q_CMD_SECTOR_ERASE0x20#defineW25Q_CMD_BLOCK_ERASE0x52#defineW25Q_CMD_CHIP_ERASE0xC7#defineW25Q_CMD_READ_DATA0x03#defineW25Q_CMD_READ_UID0x4B#defineW25Q_CMD_JEDEC_ID0x9F/*片选控制*/#defineW25Q_CS_LOW()gpio_bit_reset(GPIOA,GPIO_PIN_4)#defineW25Q_CS_HIGH()gpio_bit_set(GPIOA,GPIO_PIN_4)/*等待SPI传输完成*/staticvoidspi_wait(void){while(RESET==spi_i2s_flag_get(SPI0,SPI_FLAG_TBE));while(RESET==spi_i2s_flag_get(SPI0,SPI_FLAG_RBNE));while(SET==spi_i2s_flag_get(SPI0,SPI_FLAG_TRANS));}/*发送一个字节*/staticvoidspi_send_byte(uint8_tdata){spi_wait();spi_i2s_data_transmit(SPI0,data);spi_wait();spi_i2s_data_receive(SPI0);//丢弃接收到的数据(全双工)}/*接收一个字节*/staticuint8_tspi_recv_byte(void){spi_wait();spi_i2s_data_transmit(SPI0,0xFF);//发送dummy字节spi_wait();return(uint8_t)spi_i2s_data_receive(SPI0);}/*写使能*/voidw25q_write_enable(void){W25Q_CS_LOW();spi_send_byte(W25Q_CMD_WRITE_ENABLE);W25Q_CS_HIGH();}/*读状态寄存器1*/uint8_tw25q_read_status_reg1(void){uint8_tstatus;W25Q_CS_LOW();spi_send_byte(W25Q_CMD_READ_STATUS_REG1);status=spi_recv_byte();W25Q_CS_HIGH();returnstatus;}/*等待忙状态结束*/voidw25q_wait_busy(void){while(w25q_read_status_reg1()&0x01);//检查BUSY位}/*读取芯片ID*/uint32_tw25q_read_id(void){uint32_tid=0;W25Q_CS_LOW();spi_send_byte(W25Q_CMD_JEDEC_ID);id=spi_recv_byte()<<16;id|=spi_recv_byte()<<8;id|=spi_recv_byte();W25Q_CS_HIGH();returnid;//正确情况下应返回0xEF4018(W25Q128)}/*扇区擦除(4KB)*/voidw25q_sector_erase(uint32_taddr){w25q_write_enable();w25q_wait_busy();W25Q_CS_LOW();spi_send_byte(W25Q_CMD_SECTOR_ERASE);spi_send_byte((addr>>16)&0xFF);//地址高8位spi_send_byte((addr>>8)&0xFF);//地址中8位spi_send_byte(addr&0xFF);//地址低8位W25Q_CS_HIGH();w25q_wait_busy();//等待擦除完成}/*页编程(最大256字节)*/voidw25q_page_program(uint32_taddr,uint8_t*data,uint16_tlen){if(len>256)len=256;//一页最多256字节w25q_write_enable();w25q_wait_busy();W25Q_CS_LOW();spi_send_byte(W25Q_CMD_PAGE_PROGRAM);spi_send_byte((addr>>16)&0xFF);spi_send_byte((addr>>8)&0xFF);spi_send_byte(addr&0xFF);for(uint16_ti=0;i<len;i++){spi_send_byte(data[i]);}W25Q_CS_HIGH();w25q_wait_busy();}/*读取数据*/voidw25q_read_data(uint32_taddr,uint8_t*data,uint32_tlen){W25Q_CS_LOW();spi_send_byte(W25Q_CMD_READ_DATA);spi_send_byte((addr>>16)&0xFF);spi_send_byte((addr>>8)&0xFF);spi_send_byte(addr&0xFF);for(uint32_ti=0;i<len;i++){data[i]=spi_recv_byte();}W25Q_CS_HIGH();}```###四、与Fal结合(可选)如果使用Fal(Flash抽象层),需要实现`structfal_flash_dev`的操作函数,并注册设备。1.实现操作函数:```c#include<fal.h>staticintinit(void){return0;}//初始化,这里已经初始化过SPI,所以直接返回0staticintread(longoffset,uint8_t*buf,size_tsize){w25q_read_data(offset,buf,size);returnsize;}staticintwrite(longoffset,constuint8_t*buf,size_tsize){//注意:写入前需要确保该扇区已被擦除,且按页写入uint32_taddr=offset;while(size>0){uint16_twrite_size=(size>256)?256:size;w25q_page_program(addr,(uint8_t*)buf,write_size);addr+=write_size;buf+=write_size;size-=write_size;}returnsize;}staticinterase(longoffset,size_tsize){//擦除必须按扇区(4KB)对齐uint32_taddr=offset;while(size>0){w25q_sector_erase(addr);addr+=4096;//一个扇区4KBsize=(size>4096)?(size-4096):0;}return0;}//定义W25Q128的设备结构structfal_flash_devw25q128={.name="W25Q128",.capacity=16*1024*1024,//16MB.block_size=4096,//擦除块大小4KB.write_gran=1,//写粒度1字节.ops={init,read,write,erase},};```2.在Fal初始化时注册设备:```c#include<fal.h>intfal_init(void){/*注册设备*/fal_flash_register(&w25q128);/*创建分区表*/fal_partition_init();return0;}```###五、注意事项1.**SPI时钟**:W25Q128支持最高104MHz(在快速读取模式下),但通常SPI时钟不要超过50MHz。本例中使用15MHz。2.**擦除和写入**:必须先擦除(变为0xFF)才能写入,且写入只能将1改为0,不能将0改为1。3.**多任务环境**:如果在RTOS中使用,需要添加互斥锁保护SPI总线。###六、测试代码```cintmain(void){spi_config();//初始化SPI//测试读取IDuint32_tid=w25q_read_id();if(id!=0xEF4018){//读取ID失败,处理错误}//测试读写uint8_twrite_buf[256];uint8_tread_buf[256];for(inti=0;i<256;i++){write_buf[i]=i;}w25q_sector_erase(0x000000);//擦除0地址扇区w25q_page_program(0x000000,write_buf,256);w25q_read_data(0x000000,read_buf,256);//比较read_buf和write_buf是否一致while(1);}```以上代码为GD32F470驱动W25Q128的基本实现。根据实际需求,可以进一步优化(如使用DMA传输)。§§相关问题§§1.如何提高W25Q128的读写速度?2.如何实现W25Q128的磨损均衡?3.在RTOS中如何安全使用SPIFlash?4.W25Q128的块擦除和扇区擦除有什么区别?</think>### GD32F470驱动W25Q128 SPI Flash完整实现指南 #### 一、硬件连接配置 ```mermaid graph LR GD32F470-->|SPI_SCK|W25Q128[W25Q128] GD32F470-->|SPI_MISO|W25Q128 GD32F470-->|SPI_MOSI|W25Q128 GD32F470-->|SPI_CS|W25Q128 GD32F470-->|3.3V电源|W25Q128 GD32F470-->|GND|W25Q128 ``` **引脚配置建议**: - SPI_SCK: PA5 (SPI0_CLK) - SPI_MISO: PA6 (SPI0_MISO) - SPI_MOSI: PA7 (SPI0_MOSO) - SPI_CS: PA4 (GPIO控制) - 工作电压: 3.3V - 注意:WP和HOLD引脚需上拉至3.3V #### 二、SPI外设初始化代码 ```c #include "gd32f4xx.h" void spi_gpio_config(void) { // 启用GPIO和SPI时钟 rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_SPI0); // 配置SPI引脚 gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7); gpio_af_set(GPIOA, GPIO_AF_5, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); // SPI0复用功能 // 配置CS引脚(PA4) gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_4); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4); GPIO_BC(GPIOA) = GPIO_PIN_4; // CS高电平(不选中) } void spi_config(void) { spi_parameter_struct spi_init_struct; // SPI参数配置 spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; spi_init_struct.device_mode = SPI_MASTER; spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; // MODE3 spi_init_struct.nss = SPI_NSS_SOFT; // 软件控制CS spi_init_struct.prescale = SPI_PSC_8; // 120MHz/8=15MHz spi_init_struct.endian = SPI_ENDIAN_MSB; spi_init(SPI0, &spi_init_struct); spi_enable(SPI0); } ``` #### 三、W25Q128驱动核心函数 ```c // W25Q128指令集 #define W25Q_CMD_WRITE_ENABLE 0x06 #define W25Q_CMD_READ_DATA 0x03 #define W25Q_CMD_PAGE_PROGRAM 0x02 #define W25Q_CMD_SECTOR_ERASE 0x20 #define W25Q_CMD_READ_STATUS_REG1 0x05 #define W25Q_CMD_JEDEC_ID 0x9F // 片选控制宏 #define W25Q_CS_LOW() gpio_bit_reset(GPIOA, GPIO_PIN_4) #define W25Q_CS_HIGH() gpio_bit_set(GPIOA, GPIO_PIN_4) // 等待传输完成 static void spi_wait(void) { while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE)); } // 发送一个字节 static void spi_send_byte(uint8_t data) { spi_wait(); spi_i2s_data_transmit(SPI0, data); spi_wait(); spi_i2s_data_receive(SPI0); // 丢弃接收数据 } // 接收一个字节 static uint8_t spi_recv_byte(void) { spi_wait(); spi_i2s_data_transmit(SPI0, 0xFF); // 发送dummy spi_wait(); return (uint8_t)spi_i2s_data_receive(SPI0); } // 读取状态寄存器 uint8_t w25q_read_status(void) { uint8_t status; W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_READ_STATUS_REG1); status = spi_recv_byte(); W25Q_CS_HIGH(); return status; } // 等待操作完成 void w25q_wait_busy(void) { while (w25q_read_status() & 0x01); // 检查BUSY位 } // 读取芯片ID uint32_t w25q_read_id(void) { uint32_t id = 0; W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_JEDEC_ID); id = spi_recv_byte() << 16; id |= spi_recv_byte() << 8; id |= spi_recv_byte(); W25Q_CS_HIGH(); return id; // 正确ID应为0xEF4018 } // 扇区擦除(4KB) void w25q_sector_erase(uint32_t addr) { W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_WRITE_ENABLE); // 写使能 W25Q_CS_HIGH(); W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_SECTOR_ERASE); spi_send_byte((addr >> 16) & 0xFF); // 地址高8位 spi_send_byte((addr >> 8) & 0xFF); // 地址中8位 spi_send_byte(addr & 0xFF); // 地址低8位 W25Q_CS_HIGH(); w25q_wait_busy(); // 等待擦除完成 } // 页编程(最大256字节) void w25q_page_program(uint32_t addr, uint8_t *data, uint16_t len) { if (len > 256) len = 256; // 限制长度 W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_WRITE_ENABLE); // 写使能 W25Q_CS_HIGH(); W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_PAGE_PROGRAM); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte(addr & 0xFF); for (uint16_t i = 0; i < len; i++) { spi_send_byte(data[i]); } W25Q_CS_HIGH(); w25q_wait_busy(); // 等待写入完成 } // 读取数据 void w25q_read_data(uint32_t addr, uint8_t *buffer, uint32_t len) { W25Q_CS_LOW(); spi_send_byte(W25Q_CMD_READ_DATA); spi_send_byte((addr >> 16) & 0xFF); spi_send_byte((addr >> 8) & 0xFF); spi_send_byte(addr & 0xFF); for (uint32_t i = 0; i < len; i++) { buffer[i] = spi_recv_byte(); } W25Q_CS_HIGH(); } ``` #### 四、文件系统集成(使用Fal+FlashDB) 参考引用[3]中的FlashDB移植方法: 1. **移植Fal(Flash抽象层)**: ```c // fal_cfg.h #define FAL_PART_HAS_TABLE_CFG // 定义Flash设备 extern const struct fal_flash_dev w25q128; #define FAL_FLASH_DEV_TABLE \ { \ &w25q128, \ } // 定义分区表 #define FAL_PART_TABLE \ { \ {FAL_PART_MAGIC_WORD, "fdb_kvdb", "W25Q128", 0, 16 * 1024, 0}, /* 16KB KV存储 */ \ {FAL_PART_MAGIC_WORD, "download", "W25Q128", 16*1024, 512*1024, 0}, /* 固件存储 */\ } ``` 2. **实现Fal操作函数**: ```c #include <fal.h> static int init(void) { return 0; } static int read(long offset, uint8_t *buf, size_t size) { w25q_read_data(offset, buf, size); return size; } static int write(long offset, const uint8_t *buf, size_t size) { // 注意:需要先擦除再写入 w25q_page_program(offset, (uint8_t*)buf, size); return size; } static int erase(long offset, size_t size) { // 按扇区擦除 uint32_t start_sector = offset / 4096; uint32_t end_sector = (offset + size - 1) / 4096; for (uint32_t i = start_sector; i <= end_sector; i++) { w25q_sector_erase(i * 4096); } return 0; } const struct fal_flash_dev w25q128 = { .name = "W25Q128", .capacity = 16 * 1024 * 1024, // 16MB .block_size = 4096, // 扇区大小 .write_gran = 1, // 写粒度 .ops = {init, read, write, erase} }; ``` 3. **初始化FlashDB**: ```c #include <flashdb.h> static struct fdb_kvdb kvdb = {0}; int main(void) { // 初始化硬件 spi_gpio_config(); spi_config(); // 初始化Fal fal_init(); // 初始化FlashDB fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_SEC_SIZE, (void*)4096); if (fdb_kvdb_init(&kvdb, "fdb_kvdb", "fdb_kv", NULL, NULL) != FDB_NO_ERR) { // 错误处理 } // 使用示例 int boot_count = 0; fdb_kv_get(&kvdb, "boot_count", &boot_count, sizeof(int)); boot_count++; fdb_kv_set(&kvdb, "boot_count", &boot_count, sizeof(int)); } ``` #### 五、性能优化技巧 1. **启用QSPI模式**(需要硬件支持): ```c // 配置QSPI模式 spi_init_struct.trans_mode = SPI_TRANSMODE_QSPI; spi_init_struct.qspi_frame_size = QSPI_FRAMESIZE_32BIT; spi_init_struct.qspi_data_mode = QSPI_DATA_MODE_4_LINES; ``` 2. **DMA传输优化**: ```c // 配置SPI DMA dma_parameter_struct dma_init_struct; dma_deinit(DMA0, DMA_CH0); dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init_struct.number = len; dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(SPI0); dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_HIGH; dma_init(DMA0, DMA_CH0, &dma_init_struct); ``` 3. **磨损均衡策略**: ```c // FlashDB中启用磨损均衡 fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_WEAR_LEVELING, (void*)true); ``` #### 六、常见问题解决 1. **读取ID返回0xFFFFFF**: - 检查硬件连接(特别是CS引脚) - 确认SPI模式(W25Q128需要MODE3) - 降低SPI时钟频率测试 2. **写入后读取数据错误**: - 确保执行了擦除操作(Flash必须先擦除再写入) - 检查地址对齐(页编程必须256字节对齐) - 添加写保护检查:`if(w25q_read_status() & 0x20) // 检查WPEN位` 3. **擦除时间过长**: - 典型扇区擦除时间3-400ms,芯片擦除时间30-60秒 - 使用`w25q_wait_busy()`避免阻塞系统,可结合中断处理 > 完整工程参考:[GD32F4xx_Firmware_Library](https://github.com/GD32MCU/GD32F4xx_Firmware_Library) 中的SPI示例[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

视觉与物联智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值