使用官方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:要写入的值
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 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低电平。
测试结果是所有通讯都异常了,没法接收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:寄存器地址
*返回 : 读取的寄存器值
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 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:初始化失败
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 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
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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收发未实现完成。