GD32F407之硬件IIC(主机模式)

2024-02-08 15:40

本文主要是介绍GD32F407之硬件IIC(主机模式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

IIC总线应用真的太广泛了,介绍什么的就不说了。

GD32F407真的太像STM32F407了,之前用STM32的时候网上都说硬件IIC不稳定容易死机,不过没有真正在项目中使用过,主要官方给的源码都是while结构的,如何敢用,简直了。

最近项目用到了GD32F407的三组IIC,而且还有一组作为从机模式,如果用模拟GPIO方式来做从机之前真没有玩过,网上看资料不多(主要看有没现成拿来用,哈哈),那就都用硬件方式,咨询过GD的工程师完全可以用硬件来做项目的。但是非常遗憾的事GD的源码也是while结构的,单独运行起来杠杠的,但是能直接放到项目嘛,当然不行,那就直接修改了。下面记录修改过程,及遇到问题

一、下面是硬件IIC作为主机模式发送接受软件流程图

Datasheet上的主机发送 接收模式的软件流程图,中文版太给力了,这是第一次认真去分析每一步如何发送数据该如何设置标志和清除标志

主机发送模式下的软件流程

主机接受模式,A和B都差不多,

非常详细介绍了每一步应该如何判断哪个标志位清除哪个标志位,我们照着来就好了。

二、首先看看官方硬件接收源码:

/*!\brief      write more than one byte to the EEPROM with a single write cycle\param[in]  p_buffer: pointer to the buffer containing the data to be written to the EEPROM\param[in]  write_address: EEPROM's internal address to write to\param[in]  number_of_byte: number of bytes to write to the EEPROM\param[out] none\retval     none
*/
void eeprom_page_write(uint8_t* p_buffer, uint8_t write_address, uint8_t number_of_byte)
{/* wait until I2C bus is idle */while(i2c_flag_get(I2C0, I2C_I2CBSY));/* send a start condition to I2C bus */i2c_start_on_bus(I2C0);/* wait until SBSEND bit is set */while(!i2c_flag_get(I2C0, I2C_SBSEND));/* send slave address to I2C bus */i2c_master_addressing(I2C0, eeprom_address, I2C_TRANSMITTER);/* wait until ADDSEND bit is set */while(!i2c_flag_get(I2C0, I2C_ADDSEND));/* clear the ADDSEND bit */i2c_flag_clear(I2C0,I2C_STAT0_ADDSEND);/* wait until the transmit data buffer is empty */while( SET != i2c_flag_get(I2C0, I2C_TBE));/* send the EEPROM's internal address to write to : only one byte address */i2c_transmit_data(I2C0, write_address);/* wait until BTC bit is set */while(!i2c_flag_get(I2C0, I2C_BTC));/* while there is data to be written */while(number_of_byte--){  i2c_transmit_data(I2C0, *p_buffer);/* point to the next byte to be written */p_buffer++; /* wait until BTC bit is set */while(!i2c_flag_get(I2C0, I2C_BTC));}/* send a stop condition to I2C bus */i2c_stop_on_bus(I2C0);/* wait until the stop condition is finished */while(I2C_CTL0(I2C0)&0x0200);
}

都是while的结构,如果有一个地方卡主那就死机了,所以下面将它修改成不用while模式。用硬件IIC最好用中断模式,即在硬件标志了就会产生中断这个时候可你以处理数据。

下面是一个主机读写开始的一个函数,不管读数据还是写数据都是主机发送起始信号,

void Mi2c_RW_Start(BYTE CountByte,uint32_t i2c_periph,BYTE Channel)
{//DEBUG(0x05);if((!i2c_flag_get(i2c_periph, I2C_I2CBSY)) && (Start_Flag[Channel] == 1)){i2c_interrupt_enable(i2c_periph, I2C_CTL1_ERRIE | I2C_CTL1_BUFIE | I2C_CTL1_EVIE);I2C_nBytes[Channel] = CountByte;if(2 == I2C_nBytes[Channel]){/* send ACK for the next byte */i2c_ackpos_config(i2c_periph, I2C_ACKPOS_NEXT);}/* send a start condition to I2C bus */i2c_start_on_bus(i2c_periph);Start_Flag[Channel] = 0;Start_Flag2[Channel] =0;} 
}

 下面是一个读写数据选择,这个函数应该放在中断处理函数里面

void Mi2c_RW_Data(BYTE Channel)
{//DEBUG(0x16);if(1 == (M_tran_buffer[Channel][0]>>7)){//receiverMi2c_read_data(Channel);}else{//transmitter//DEBUG(0x37);Mi2c_write_Data(Channel);}
}

中断处理函数

void I2C2_EV_IRQHandler(void)
{Mi2c_RW_Data(2);
}

中断错误函数,非常重要

void I2C2_ER_IRQHandler(void)
{    if(i2c_flag_get(I2C2, I2C_AERR)){/* no acknowledge received */i2c_flag_clear(I2C2, I2C_AERR);//ErrorFlag += 1;//ErrorDebug(2,ErrorFlag);          OEM_Cheack_Error(2);}   if(i2c_flag_get(I2C2, I2C_SMBALT)){/* SMBus alert */i2c_flag_clear(I2C2, I2C_SMBALT);}   if(i2c_flag_get(I2C2, I2C_SMBTO)){/* bus timeout in SMBus mode */i2c_flag_clear(I2C2, I2C_SMBTO);}  if(i2c_flag_get(I2C2, I2C_OUERR)){/* over-run or under-run when SCL stretch is disabled */i2c_flag_clear(I2C2, I2C_OUERR);}    if(i2c_flag_get(I2C2, I2C_LOSTARB)){/* arbitration lost */i2c_flag_clear(I2C2, I2C_LOSTARB);} if(i2c_flag_get(I2C2, I2C_BERR)){ /* bus error */i2c_flag_clear(I2C2, I2C_BERR);}   if(i2c_flag_get(I2C2, I2C_PECERR)){/* CRC value doesn't match */i2c_flag_clear(I2C2, I2C_PECERR);}/* disable the error interrupt */i2c_interrupt_disable(I2C2,I2C_CTL1_ERRIE | I2C_CTL1_BUFIE | I2C_CTL1_EVIE);
}

测试过程中发现会出现(I2C_AERR)NO ACK的报错,导致卡机,增加一个 NO ACk的处理函数,做一下清标志位

void OEM_Cheack_Error(BYTE Channel)
{//检测从机没有ACK回应后处理OEM_TRAN_Timer[Channel] = 0;i2c_stop_on_bus( Channel_buffer[Channel][0]);/* enable acknowledge */i2c_ack_config( Channel_buffer[Channel][0], I2C_ACK_ENABLE);i2c_ackpos_config( Channel_buffer[Channel][0], I2C_ACKPOS_CURRENT);Start_Flag[Channel] = 1;Start_Flag2[Channel] = 0
}

我们是将读写函数整合到一起了,所以上面的函数都是为读写函数做准备的。

写数据函数

void Mi2c_write_Data(BYTE Channel)
{//DEBUG(0X17);//第一部分发送从机地址和寄存器地址if(Start_Flag2[Channel] == 0){if(i2c_flag_get( Channel_buffer[Channel][0], I2C_SBSEND)){/* send slave address to I2C bus */i2c_master_addressing( Channel_buffer[Channel][0], M_tran_buffer[Channel][1], I2C_TRANSMITTER);            }else if(i2c_flag_get( Channel_buffer[Channel][0], I2C_ADDSEND)){i2c_flag_clear( Channel_buffer[Channel][0],I2C_STAT0_ADDSEND);}else if(i2c_flag_get(  Channel_buffer[Channel][0] , I2C_TBE )){/* send the register address to write to : only one byte address */i2c_transmit_data( Channel_buffer[Channel][0],M_tran_buffer[Channel][2]);Start_Flag2[Channel] = 1;               Write_count[Channel] = I2C_nBytes[Channel];}}//写入数据if(Start_Flag2[Channel] == 1){//DEBUG(0X19);if(i2c_flag_get( Channel_buffer[Channel][0], I2C_BTC)){if(I2C_nBytes[Channel]>0){i2c_transmit_data( Channel_buffer[Channel][0], M_i2c_Txbuffer[Channel][Write_count[Channel] - I2C_nBytes[Channel]]);I2C_nBytes[Channel]--;  }else if(0 == I2C_nBytes[Channel]){ OEM_TRAN_Timer[Channel] = 0;i2c_stop_on_bus( Channel_buffer[Channel][0]);/* enable acknowledge */i2c_ack_config( Channel_buffer[Channel][0], I2C_ACK_ENABLE);i2c_ackpos_config( Channel_buffer[Channel][0], I2C_ACKPOS_CURRENT);Start_Flag[Channel] = 1;Start_Flag2[Channel] = 0;//DEBUG(0x85);i2c_interrupt_disable( Channel_buffer[Channel][0], I2C_CTL1_ERRIE | I2C_CTL1_BUFIE | I2C_CTL1_EVIE);}           }}
}

可以看到还是相当的复杂,主要是将读写做成了统一架构的模式,所以很多都是为了更好地封装加上的,下面介绍读数据

读数据比写数据要复杂一点,

官方源代码

/*!\brief      read data from the EEPROM\param[in]  p_buffer: pointer to the buffer that receives the data read from the EEPROM\param[in]  read_address: EEPROM's internal address to start reading from\param[in]  number_of_byte: number of bytes to reads from the EEPROM\param[out] none\retval     none
*/
void eeprom_buffer_read(uint8_t* p_buffer, uint8_t read_address, uint16_t number_of_byte)
{  /* wait until I2C bus is idle */while(i2c_flag_get(I2C0, I2C_I2CBSY));if(2 == number_of_byte){i2c_ackpos_config(I2C0,I2C_ACKPOS_NEXT);}/* send a start condition to I2C bus */i2c_start_on_bus(I2C0);/* wait until SBSEND bit is set */while(!i2c_flag_get(I2C0, I2C_SBSEND));/* send slave address to I2C bus */i2c_master_addressing(I2C0, eeprom_address, I2C_TRANSMITTER);/* wait until ADDSEND bit is set */while(!i2c_flag_get(I2C0, I2C_ADDSEND));/* clear the ADDSEND bit */i2c_flag_clear(I2C0,I2C_STAT0_ADDSEND);/* wait until the transmit data buffer is empty */while(SET != i2c_flag_get( I2C0 , I2C_TBE ));/* enable I2C0*/i2c_enable(I2C0);/* send the EEPROM's internal address to write to */i2c_transmit_data(I2C0, read_address);  /* wait until BTC bit is set */while(!i2c_flag_get(I2C0, I2C_BTC));/* send a start condition to I2C bus */i2c_start_on_bus(I2C0);/* wait until SBSEND bit is set */while(!i2c_flag_get(I2C0, I2C_SBSEND));/* send slave address to I2C bus */i2c_master_addressing(I2C0, eeprom_address, I2C_RECEIVER);if(number_of_byte < 3){/* disable acknowledge */i2c_ack_config(I2C0,I2C_ACK_DISABLE);}/* wait until ADDSEND bit is set */while(!i2c_flag_get(I2C0, I2C_ADDSEND));/* clear the ADDSEND bit */i2c_flag_clear(I2C0,I2C_STAT0_ADDSEND);if(1 == number_of_byte){/* send a stop condition to I2C bus */i2c_stop_on_bus(I2C0);}/* while there is data to be read */while(number_of_byte){if(3 == number_of_byte){/* wait until BTC bit is set */while(!i2c_flag_get(I2C0, I2C_BTC));/* disable acknowledge */i2c_ack_config(I2C0,I2C_ACK_DISABLE);}if(2 == number_of_byte){/* wait until BTC bit is set */while(!i2c_flag_get(I2C0, I2C_BTC));/* send a stop condition to I2C bus */i2c_stop_on_bus(I2C0);}/* wait until the RBNE bit is set and clear it */if(i2c_flag_get(I2C0, I2C_RBNE)){/* read a byte from the EEPROM */*p_buffer = i2c_receive_data(I2C0);/* point to the next location where the byte read will be saved */p_buffer++; /* decrement the read bytes counter */number_of_byte--;} }/* wait until the stop condition is finished */while(I2C_CTL0(I2C0)&0x0200);/* enable acknowledge */i2c_ack_config(I2C0,I2C_ACK_ENABLE);i2c_ackpos_config(I2C0,I2C_ACKPOS_CURRENT);
}

比写数据起码多了一倍啊

修改后代码

void Mi2c_read_data(BYTE Channel)
{//ErrorDebug(Channel,0x12);//第一部分发送从机地址和寄存器地址if(Start_Flag2[Channel] == 0){if(i2c_flag_get(Channel_buffer[Channel][0], I2C_SBSEND)){/* send slave address to I2C bus */i2c_master_addressing(Channel_buffer[Channel][0], M_tran_buffer[Channel][1], I2C_TRANSMITTER);}else if(i2c_flag_get(Channel_buffer[Channel][0], I2C_ADDSEND)){/* clear the ADDSEND bit */i2c_flag_clear(Channel_buffer[Channel][0],I2C_STAT0_ADDSEND);}else if(i2c_flag_get( Channel_buffer[Channel][0] , I2C_TBE )){i2c_enable(Channel_buffer[Channel][0]);/* send the register address to write to : only one byte address */i2c_transmit_data(Channel_buffer[Channel][0], M_tran_buffer[Channel][2]);Start_Flag2[Channel] = 1;}}//需要转换模式,所以重新启动IIC到接收模式if(Start_Flag2[Channel] == 1){if(i2c_flag_get(Channel_buffer[Channel][0], I2C_BTC)){//DEBUG(0x74);i2c_start_on_bus(Channel_buffer[Channel][0]);Start_Flag2[Channel] = 2;}}//第二部分接收数据if(Start_Flag2[Channel] == 2){          if(i2c_flag_get(Channel_buffer[Channel][0], I2C_SBSEND)){/* the master sends slave address */i2c_master_addressing(Channel_buffer[Channel][0], M_tran_buffer[Channel][1], I2C_RECEIVER);}else if(i2c_flag_get(Channel_buffer[Channel][0], I2C_ADDSEND)){if(I2C_nBytes[Channel] < 3){/* clear the ACKEN before the ADDSEND is cleared */i2c_ack_config(Channel_buffer[Channel][0], I2C_ACK_DISABLE);/* clear the ADDSEND bit */i2c_flag_clear(Channel_buffer[Channel][0],I2C_STAT0_ADDSEND);}else{/* clear the ADDSEND bit */i2c_flag_clear(Channel_buffer[Channel][0],I2C_STAT0_ADDSEND);}if(1 == I2C_nBytes[Channel]){i2c_stop_on_bus(Channel_buffer[Channel][0]);}   Read_count[Channel] = I2C_nBytes[Channel];               }else if(i2c_flag_get(Channel_buffer[Channel][0], I2C_RBNE)){//DEBUG(0x83);if(I2C_nBytes[Channel]>0){// if(3 == I2C_nBytes[Channel]){//      i2c_ack_config(Channel_buffer[Channel][0],I2C_ACK_DISABLE);// }if(I2C_nBytes[Channel] == 2){   i2c_ack_config(Channel_buffer[Channel][0],I2C_ACK_DISABLE);i2c_stop_on_bus(Channel_buffer[Channel][0]);}/* read a data byte from I2C_DATA*/M_i2c_Rxbuffer[Channel][Read_count[Channel]-I2C_nBytes[Channel]] = i2c_receive_data(Channel_buffer[Channel][0]);I2C_nBytes[Channel]--;if(0 == I2C_nBytes[Channel]){   OEM_TRAN_Timer[Channel] = 0;i2c_stop_on_bus(Channel_buffer[Channel][0]);i2c_ack_config(Channel_buffer[Channel][0], I2C_ACK_ENABLE);i2c_ackpos_config(Channel_buffer[Channel][0], I2C_ACKPOS_CURRENT);Start_Flag[Channel] = 1;Start_Flag2[Channel] = 0;//DEBUG(0x85);i2c_interrupt_disable(Channel_buffer[Channel][0], I2C_CTL1_ERRIE | I2C_CTL1_BUFIE | I2C_CTL1_EVIE);}}}}
}

读数据分两步,

第一步先作为写(发送)数据模式。给从机写入从机地址和寄存器地址

i2c_master_addressing(Channel_buffer[Channel][0], M_tran_buffer[Channel][1], I2C_TRANSMITTER);

第二步,第一部完后重新启动IIC为接收数据模式kn

i2c_master_addressing(Channel_buffer[Channel][0], M_tran_buffer[Channel][1], I2C_RECEIVER);

一个写WORD数据的应用函数

void SMB_wrWORD(BYTE Channel, BYTE SMB_SLAVE, BYTE SMB_COMMAND, WORD wData,BYTE RETRY_CNT)
{/* SMB_SLAVE: Slave address.RETRY_CNT: Number of times to attempt an SMBus transfer.Must be >= 1 and <= 3.  The RETRY_CNT is notthe number of retries but is the number ofSMBus transfer attempts.FLAG == TRUE  -> SMBus write word is ok.== FALSE -> SMBus write word failed. */Channel_buffer[Channel][0] = Channel_Select(Channel);    M_tran_buffer[Channel][0]    = SMB_WRITE_WORD;   /* Protocol byte. */M_tran_buffer[Channel][1]    = SMB_SLAVE & ~0x01;  /* Address and read/write = 0 for write. */M_tran_buffer[Channel][2]    = SMB_COMMAND;      /* SMBus command. */M_i2c_Txbuffer[Channel][0]   = (BYTE) (wData & 0xFF);M_i2c_Txbuffer[Channel][1]   = (BYTE) ((wData >> 8) & 0xFF);//   /* Number of attempts for SMBus transfer. *///   attempts[Channel] = RETRY_CNT;I2C_SADDR0(Channel_buffer[Channel][0]) |= M_tran_buffer[Channel][1];/* Set the timer to start the SMBus transfer. */OEM_TRAN_Timer[Channel] = START;Mi2c_RW_Start((M_tran_buffer[Channel][0]&0x03),Channel_buffer[Channel][0],Channel);
}

一个读WORD数据的应用函数

void SMB_rdWORD(BYTE Channel, BYTE SMB_SLAVE, BYTE SMB_COMMAND, BYTE RETRY_CNT)
{//DEBUG(0x15);/* SMB_SLAVE: Slave address.RETRY_CNT: Number of times to attempt an SMBus transfer.Must be >= 1 and <= 3.  The RETRY_CNT is notthe number of retries but is the number ofSMBus transfer attempts. */Channel_buffer[Channel][0] = Channel_Select(Channel);    M_tran_buffer[Channel][0]    = SMB_READ_WORD; /* Protocol byte. */M_tran_buffer[Channel][1]    = SMB_SLAVE;   /* Address and read/write = 0 for write. */M_tran_buffer[Channel][2]    = SMB_COMMAND; /* SMBus command. *///   /* Number of attempts for SMBus transfer. *///   attempts[Channel] = RETRY_CNT;I2C_SADDR0(Channel_buffer[Channel][0]) |= M_tran_buffer[Channel][1];/* Set the timer to start the SMBus transfer. */OEM_TRAN_Timer[Channel] = START;Mi2c_RW_Start((M_tran_buffer[Channel][0]&0x03),Channel_buffer[Channel][0],Channel);
}

这两个读写WORD函数放到轮询函数里面即可

文章主要点是将while去除的读写数据的两个函数,其它都是根据自己的项目来搭建框架的封装函数

GD32F407资料下载--http://www.gd32mcu.com/cn/download

eive

这篇关于GD32F407之硬件IIC(主机模式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/691485

相关文章

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R

SQL Server身份验证模式步骤和示例代码

《SQLServer身份验证模式步骤和示例代码》SQLServer是一个广泛使用的关系数据库管理系统,通常使用两种身份验证模式:Windows身份验证和SQLServer身份验证,本文将详细介绍身份... 目录身份验证方式的概念更改身份验证方式的步骤方法一:使用SQL Server Management S

Redis高可用-主从复制、哨兵模式与集群模式详解

《Redis高可用-主从复制、哨兵模式与集群模式详解》:本文主要介绍Redis高可用-主从复制、哨兵模式与集群模式的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录Redis高可用-主从复制、哨兵模式与集群模式概要一、主从复制(Master-Slave Repli

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文