WM8994软件IIC调试怪异问题-已经解决(2020-02-15)

 

使用官方demo,硬件IIC可以正常与WM8994通讯,而我这个使用了几年的软件IIC却无法正常通讯,表现为设备地址0x34能正常发送,能正确响应ACK,如果后续的16bit(2个字节)的地址与16bit(2个字节)的数据,BIT0,BIT8有一个1的话,就无法完成通讯。

比如发送0xFEFE是没问题的,如果是0xFFFF或者0x0001或者0x0100之类,只要BIT0或BIT8有1则不会收到ACK,时序确认了无数次,其它各种IIC芯片都能正常读写,唯独这个WM8994死活无法读取,已经折腾1个多星期了。

 

之前的软件IIC代码在这:https://blog.csdn.net/cp1300/article/details/75644988 已经用了好几年了。

然后我还重新编写了一个IIC的通讯接口,问题依旧。


//SDA-PH8  SCL-PH7
#define WM8994_SDA_OUT_MODE()		SYS_GPIOx_OneInit(GPIOH, 8, OUT_OD, SPEED_50M)			//SDA输出模式
#define WM8994_SDA_IN_MODE()		SYS_GPIOx_OneInit(GPIOH, 8, IN_IPU, IN_NONE)			//SDA输入模式
#define IIC_SDA_Set(x)				(PHout(8) =  x)
#define IIC_SCL_Set(x)				(PHout(7) = x)
#define IIC_SDA_Get()				(PHin(8))

//IIC启动序列(结果:SDA=0,SCL=0)
void IIC_Start(void)
{
	IIC_SDA_Set(1);
	IIC_SCL_Set(1);
	Delay_US(3);
	IIC_SDA_Set(0);
	Delay_US(3);
	IIC_SCL_Set(0);
}


//IIC结束序列(结果:SDA=1,SCL=1)
void IIC_Stop(void)
{
	Delay_US(1);
	IIC_SCL_Set(1);
	Delay_US(1);
	IIC_SDA_Set(0);
	Delay_US(1);
	IIC_SDA_Set(1);
	Delay_US(1);
}

//IIC结束序列(结果:SDA=x,SCL=0)
void IIC_SendByte(u8 data)
{
	u8 i;
	
	for(i = 0;i < 8;i ++)
	{
		IIC_SDA_Set((data & 0x80)?1:0);	//MSB
		data <<= 1;
		//产生时钟
		Delay_US(1);
		IIC_SCL_Set(1);		//上升沿
		Delay_US(2);
		IIC_SCL_Set(0);		//下降沿
		Delay_US(1);
	}
	
}

//等待ACK(结果:SDA=x,SCL=0)
bool IIC_WaitACK(void)
{
	u8 retry = 0;
	
	WM8994_SDA_IN_MODE();	//SDA设置为输入
	Delay_US(1);
	IIC_SCL_Set(1);			//上升沿
	Delay_US(2);
	while(IIC_SDA_Get())
	{
		retry++;
		if(retry>250)
		{
			IIC_SCL_Set(0);			//下降沿
			WM8994_SDA_OUT_MODE();	//SDA设置为输出
			IIC_Stop();
			return FALSE;
		}
	}
	Delay_US(1);
	IIC_SCL_Set(0);			//下降沿
	Delay_US(1);
	WM8994_SDA_OUT_MODE();	//SDA设置为输出
	return TRUE;
}

/*************************************************************************************************************************
*函数        	:	bool WM8994_WriteOneReg(WM8994_HANDLE *pHandle,u16 RegAddr,u8 data)
*功能        	:	WM8994写一个寄存器
*参数        	:	pHandle:句柄;RegAddr:寄存器地址;data:要写入的值
*返回        	:	无
*依赖			: 	底层宏定义
*作者       	:	[email protected]
*时间     		:	2019-02-13
*最后修改时间	:	2019-02-13
*说明        	:	
*************************************************************************************************************************/
bool WM8994_WriteOneReg(WM8994_HANDLE *pHandle,u16 RegAddr,u16 data)
{
	u8 SlaveAddr = pHandle->SlaveAddr;
	
	IIC_Start();				//发送起始信号
	IIC_SendByte(SlaveAddr);	//发送一字节
	if(IIC_WaitACK() == FALSE)
	{
		DEBUG("[IIC写错误]:1\r\n");
		return FALSE;
	}
	IIC_SendByte(RegAddr>>8);	//发送一字节
	if(IIC_WaitACK() == FALSE)
	{
		DEBUG("[IIC写错误]:2\r\n");
		return FALSE;
	}
	
	IIC_SendByte(RegAddr);	//发送一字节
	if(IIC_WaitACK() == FALSE)
	{
		DEBUG("[IIC写错误]:3\r\n");
		return FALSE;
	}
	
	IIC_SendByte(data>>8);	//发送一字节
	if(IIC_WaitACK() == FALSE)
	{
		DEBUG("[IIC写错误]:4\r\n");
		return FALSE;
	}
	
	IIC_SendByte(data);	//发送一字节
	if(IIC_WaitACK() == FALSE)
	{
		DEBUG("[IIC写错误]:5\r\n");
		return FALSE;
	}
	
	IIC_Stop();
	
	return TRUE;
}


//测试代码
while(1)
	{
		if(WM8994_WriteOneReg(pHandle, 0, BIT6) == TRUE)
		{
			uart_printf("WM8994写入成功\r\n");
		}
		SYS_DelayMS(1);
		LED1 = PDin(6);
		SYS_DelayMS(500);
		
		WM8994_WriteOneReg(pHandle, 0xFEFF, BIT0|BIT6);
		SYS_DelayMS(1);
		LED1 = PDin(6);
		SYS_DelayMS(500);
		
		//temp = WM8994_ReadOneReg(pHandle, 1792);
		//uart_printf("reg%02d=0x%X\r\n",1792, temp);
}

 

//结果,只要BIT0或BIT8不为1就能正常写入,并且有正确的ACK响应

依旧不甘心,测试时在8个bit发送完成后,在SCL低电平期间将SDA设置为高电平,所有通讯都会故障,这个就解释不通了,理论上IIC在SCL为低电平时,SDA是可以变动的,但是这个1貌似会影响到之后的ACK低电平。

扫描二维码关注公众号,回复: 9213910 查看本文章

测试结果是所有通讯都异常了,没法接收ACK(已经测试过,后面无论延时多久都不会收到ACK,所以可以排除是时间导致的ACK错过,只要接收ACK之前SDA输出了1,就不会再收到ACK了,而且SDA的变化时在SCL=0时进行的,是符合IIC标准的,理论上不会影响后续数据)

全部返回错误1,也就是第一个字节发送出去就没有ACK了

 

而且与时间无关,就算延时1秒,依旧无法读取

从WM8994的手册中,看到时钟只限制了最大速度400K,并没有最低限制,同步通讯,理论上这个地方的延时都不会有影响,而且在SCL为低电平期间,是允许SDA变化的

 

 

同样的代码,读取FT5336,一次就成功了

/*************************************************************************************************************************
*函数        	:	u8 FT5336_ReadOneReg(FT5336_HANDLE *pHandle,u8 RegAddr)
*功能        	:	FT5336读取一个8bit寄存器
*参数        	:	pHandle:句柄;RegAddr:寄存器地址
*返回        	:	读取的寄存器值
*依赖			: 	底层宏定义
*作者       	:	[email protected]
*时间     		:	2019-02-13
*最后修改时间	:	2019-02-13
*说明        	:	
*************************************************************************************************************************/
u8 FT5336_ReadOneReg(FT5336_HANDLE *pHandle,u8 RegAddr)
{
	u16 data;
	u8 SlaveAddr = pHandle->SlaveAddr;
	
	
	
	SIIC_Start(&pHandle->IIC_Handle);									//产生IIC起始信号
	if(SIIC_SendByte(&pHandle->IIC_Handle, SlaveAddr) == FALSE)			//发送设备地址+写信号
	{
		DEBUG("没有收到ACK-01\r\n");
	}
	if(SIIC_SendByte(&pHandle->IIC_Handle, RegAddr) == FALSE)			//发送寄存器地址
	{
		DEBUG("没有收到ACK-02\r\n");
	}
	SIIC_Start(&pHandle->IIC_Handle);									//产生IIC起始信号
	if(SIIC_SendByte(&pHandle->IIC_Handle, SlaveAddr|BIT0) == FALSE)	//发送设备地址+读信号
	{
		DEBUG("没有收到ACK-03\r\n");
	}
	
	data = SIIC_ReadByte(&pHandle->IIC_Handle, TRUE);					//SIIC读取一个字节-高位
	SIIC_Stop(&pHandle->IIC_Handle);									//产生IIC停止信号
	
	return data;
}



/*************************************************************************************************************************
*函数        	:	FT5336_Init(FT5336_HANDLE *pHandle, u8 SlaveAddr)
*功能        	:	FT5336初始化
*参数        	:	pHandle:句柄;SlaveAddr通讯地址;
*返回        	:	TRUE:初始化成功;FALSE:初始化失败
*依赖			: 	底层宏定义
*作者       	:	[email protected]
*时间     		:	2019-01-30
*最后修改时间	:	2019-01-30
*说明        	:	需要先提前初始化IIC接口
*************************************************************************************************************************/
bool FT5336_Init(FT5336_HANDLE *pHandle, u8 SlaveAddr)
{
	u8 id;
	
	if(pHandle == NULL) return FALSE;
	pHandle->SlaveAddr = SlaveAddr;							//通讯地址

	while(1)
	{
		id = FT5336_ReadOneReg(pHandle, 0xA8);
		
		uart_printf("FT5336 id = 0x%X\r\n", id);

		SYS_DelayMS(1000);
	}
	
	
	return TRUE;
}


//软件IIC读取数据测试
	uart_printf("FT5336测试开始\r\n");
	{	
		//软件IIC初始化
		if(SIIC_Init(&g_SysGlobal.mFT5336_Handle.IIC_Handle, GPIOH, GPIOH, 8, 7, 5) == FALSE)	
		{
			DEBUG("软件IIC初始化失败!\r\n");
		}	
		FT5336_Init(&g_SysGlobal.mFT5336_Handle, 0x70);
		//OSTimeDlyHMSM(1,0,0,0);
	}

 

2020-02-15 21:37 问题跟进:由于软件IIC一直无法读取WM8994寄存器,进而花了2天调试硬件IIC,一直出现从机地址发送瞬间就NACK,调试了很久,终于找到了问题,我的一个IO初始化函数,无法将IO初始化为开漏输出,以前从未遇到过非要开漏输出的情况,测试结果表明,WM8994无论是软件IIC还是硬件IIC,必须设置为开漏输出,目前还没有遇到别的芯片必须这个要求的,虽然IIC总线通常都是开漏输出,保持IO三态,但是输出时候影响到了输入还是头一次遇到,问题代码如下。

//这个是之前有bug的代码,IO初始化函数

/*************************************************************************************************************************
* 函数			:	void SYS_GPIOx_OneInit(GPIO_TypeDef *GPIOx, u8 io_num, SYS_GPIO_MODE mode, SYS_GPIO_SPEED speed)
* 功能			:	单个GPIO初始化
* 参数			:	GPIOx:GPIO选择,从GPIOA~GPIOI;io_num:0-15;mode:IO模式,见SYS_GPIO_MODE;speed:IO速度,见SYS_GPIO_SPEED
* 返回			:	无
* 依赖			:	底层宏定义
* 作者			:	[email protected]
* 时间			:	2016-03-10
* 最后修改时间 	: 	2016-03-10
* 说明			:	输出速度只针对输出模式有效
					只能设置单个IO,单个IO设置效率较多个IO同时设置高
					注意:必须先初始化IO时钟才能进行设置,否则设置无效
*************************************************************************************************************************/
void SYS_GPIOx_OneInit(GPIO_TypeDef *GPIOx, u8 io_num, SYS_GPIO_MODE mode, SYS_GPIO_SPEED speed)	
{
	u32 moder = (mode >> 16)&0x03;
	u32 otyper = (mode >> 8)&0x01;
	u32 pupdr = mode & 0x03;
	u32 speedr = speed & 0x03;
	
	if(io_num > 15) return;	//无效的IO
	GPIOx->MODER &= ~(0x03 << (io_num*2));	//先清除端口模式寄存器
	GPIOx->MODER |= moder << (io_num*2);	//设置端口模式寄存器
	GPIOx->OTYPER &= ~(1<<io_num);			//先清除输出类型寄存器
	GPIOx->OTYPER |= otyper;				//设置输出类型寄存器
	GPIOx->PUPDR &= ~(0x03 << (io_num*2));	//先清除端口上拉/下拉寄存器
	GPIOx->PUPDR |= pupdr << (io_num*2);	//设置端口上拉/下拉寄存器
	//设置端口速度寄存器,只针对输出端口有效
	GPIOx->OSPEEDR &= ~(0x03 << (io_num*2));//先清除端口输出速度寄存器
	GPIOx->OSPEEDR |= speedr << (io_num*2);	//设置端口输出速度寄存器
}

这个是刚刚修复的,就少了一个<<iio_num,导致无法正确的设置OTYPER寄存器,进而无法实现开漏输出。

//修复后的代码如下
void SYS_GPIOx_OneInit(GPIO_TypeDef *GPIOx, u8 io_num, SYS_GPIO_MODE mode, SYS_GPIO_SPEED speed)	
{
	u32 moder = (mode >> 16)&0x03;
	u32 otyper = (mode >> 8)&0x01;
	u32 pupdr = mode & 0x03;
	u32 speedr = speed & 0x03;
	
	if(io_num > 15) return;	//无效的IO
	GPIOx->MODER &= ~(0x03 << (io_num*2));	//先清除端口模式寄存器
	GPIOx->MODER |= moder << (io_num*2);	//设置端口模式寄存器
	GPIOx->OTYPER &= ~(1<<io_num);			//先清除输出类型寄存器
	GPIOx->OTYPER |= otyper<<io_num;		//设置输出类型寄存器-2020-02-15:修复OTYPER寄存器设置错误bug
	GPIOx->PUPDR &= ~(0x03 << (io_num*2));	//先清除端口上拉/下拉寄存器
	GPIOx->PUPDR |= pupdr << (io_num*2);	//设置端口上拉/下拉寄存器
	//设置端口速度寄存器,只针对输出端口有效
	GPIOx->OSPEEDR &= ~(0x03 << (io_num*2));//先清除端口输出速度寄存器
	GPIOx->OSPEEDR |= speedr << (io_num*2);	//设置端口输出速度寄存器
}

最后再来测试一下WM8994的读写,目前测试是将WM8994的一个IO口设置为输出,也就是GPIO1设置为输出,然后通过PD6接收,将接收到的信号传输到LED,通过控制GPIO1间断的输出0,1实现LED的闪烁(这样就可以确保数据是正确的写入到了WM8994,因为IO口能正确操作,目前测试正常,LED能闪烁)

WM8994的ID也读取出来了,没想到折腾这么多天就是一个开漏输出导致的,由于之前看到IIC总线是3.3V上拉,单片机IO也是3.3V的,所以以为推挽输出也行,没想到这个WM8994却不支持,最后硬件IIC也调试出来了,能正常读取,也折腾了不少时间,也是败在这个开漏输出的bug上。

硬件IIC能够正常的读取到触摸屏的ID,0x51,硬件IIC的代码后续再上传,还有DMA收发未实现完成。

发布了143 篇原创文章 · 获赞 370 · 访问量 81万+

猜你喜欢

转载自blog.csdn.net/cp1300/article/details/104310993