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)){//receiver
Mi2c_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 not
the number of retries but is the number of
SMBus 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 not
the number of retries but is the number of
SMBus 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去除的读写数据的两个函数,其它都是根据自己的项目来搭建框架的封装函数个
eive