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

相关文章

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

实例:如何统计当前主机的连接状态和连接数

统计当前主机的连接状态和连接数 在 Linux 中,可使用 ss 命令来查看主机的网络连接状态。以下是统计当前主机连接状态和连接主机数量的具体操作。 1. 统计当前主机的连接状态 使用 ss 命令结合 grep、cut、sort 和 uniq 命令来统计当前主机的 TCP 连接状态。 ss -nta | grep -v '^State' | cut -d " " -f 1 | sort |

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme