上个帖子中介绍了PCB设计过程,接下来介绍PCB的调试过程,重点说一下MP2651的程序调试过程。
焊接完成后的PCBA如图1,首先进行常规检查,确保整个基板没有短路现象。确认无误后进行上电测试,使用5V USB供电测试,测量输出电压值为8.4V,MP2651默认配置是两节电池,所以默认输出电压是8.4V是OK的,如图2。图3是规格书截图,由于此时MP2651的输入电压是5V,所以MP2651工作在Boost模式。
我的设计是用单节电池供电,所以首先需要更改MP2651的电池数量配置。根据以往经验,ST官方自带的I2C驱动库不是很好用,尤其是针对私有协议,调试起来很麻烦,所以我直接使用IO口模拟I2C总线,这样自由度比较高。正好论坛里面已经有人发了MP2731的驱动程序代码,我就直接在这个代码基础上进行修改。
此部分代码如下:
//i2c.c
#include “i2c/bsp_i2c.h”
static void i2c_Delay(void)
{
uint8_t i;
for (i = 0; i < 4; i++);
}
//开始信号
void i2c_Start(void)
{
// 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号
I2C_SDA_1();
I2C_SCL_1();
i2c_Delay();
I2C_SDA_0();
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
}
//停止信号
void i2c_Stop(void)
{
// 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号
I2C_SDA_0();
I2C_SCL_1();
i2c_Delay();
I2C_SDA_1();
i2c_Delay();
}
//发送一个字节
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
// 先发送字节的高位bit7
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
I2C_SDA_1();
}
else
{
I2C_SDA_0();
}
i2c_Delay();
I2C_SCL_1();
i2c_Delay();
I2C_SCL_0();
if (i == 7)
{
I2C_SDA_1(); // 释放总线
}
_ucByte <<= 1; // 左移一个bit
i2c_Delay();
}
}
//读取一个字节
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
I2C_SCL_1();
i2c_Delay();
if (I2C_SDA_READ())
{
value++;
}
I2C_SCL_0();
i2c_Delay();
}
return value;
}
//等待应答信号
uint8_t i2c_WaitAck(void)
{
uint8_t re;
I2C_SDA_1(); /* CPU释放SDA总线 */
// i2c_Delay();
I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
if (I2C_SDA_READ()) /* CPU读取SDA口线状态 */
{
re = 1;
}
else
{
re = 0;
}
I2C_SCL_0();
i2c_Delay();
return re;
}
//应答信号
void i2c_Ack(void)
{
I2C_SDA_0(); /* CPU驱动SDA = 0 /
i2c_Delay();
I2C_SCL_1(); / CPU产生1个时钟 /
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
I2C_SDA_1(); / CPU释放SDA总线 /
}
void i2c_Nack(void)
{
I2C_SDA_1(); / CPU驱动SDA = 1 /
i2c_Delay();
I2C_SCL_1(); / CPU产生1个时钟 /
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
I2C_SDA_1(); / CPU释放SDA总线 */
}
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
if (I2C_SDA_READ() && I2C_SCL_READ())
{
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address | 0);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
return ucAck;
}
return 1; /* I2C总线异常 */
}
//MP2731
uint8_t Device_ReadData(uint8_t DeciveAddr,uint8_t RegAddr,uint8_t *Data,int size)
{
uint8_t ucAck;
int count=size;
uint8_t *pData=Data;
//起始信号
i2c_Start();
//发送器件地址
i2c_SendByte(DeciveAddr<<1);
//等待应答
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
i2c_SendByte(RegAddr);
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
i2c_Start();
i2c_SendByte((DeciveAddr<<1)+1);
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
while(count--)
{
//发送数据
*pData++ = i2c_ReadByte();
if(count)
{
i2c_Ack();
}
else
{
i2c_Nack(); //end nack
}
}
//结束信号
i2c_Stop();
return ucAck;
}
//MP2731
uint8_t Device_WriteData(uint8_t DeciveAddr,uint8_t RegAddr,uint8_t *Data,int size)
{
uint8_t ucAck;
int count=size;
uint8_t *pData=Data;
//起始信号
i2c_Start();
//发送器件地址
i2c_SendByte(DeciveAddr<<1); //
//等待应答
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
i2c_SendByte(RegAddr); //
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
while(count--)
{
//发送数据
i2c_SendByte(*pData++);
//等待应答
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
}
//结束信号
i2c_Stop();
return ucAck;
}
/////////////////////////////////////////////////////////////////
//MP2651
uint8_t Device_SingleWordRead(uint8_t DeciveAddr,uint8_t RegAddr,uint16_t *Data)
{
uint8_t ucAck;
int count=2;
uint8_t pData[2] = {0};
//起始信号
i2c_Start();
//发送器件地址
i2c_SendByte(DeciveAddr<<1);
//等待应答
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
i2c_SendByte(RegAddr);
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
i2c_Start();
i2c_SendByte((DeciveAddr<<1)+1);
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
while(count--)
{
//接收数据
pData[count] = i2c_ReadByte();
if(count)
{
i2c_Ack();
}
else
{
i2c_Nack(); //end nack
}
}
//结束信号
i2c_Stop();
*Data = pData[1] + pData[0]*256;
return ucAck;
}
//MP2651
uint8_t Device_SingeWordWrite(uint8_t DeciveAddr,uint8_t RegAddr,uint16_t *Data)
{
uint8_t ucAck;
int count=2;
uint8_t pData[2] = {0};
pData[1] = *Data % 256;
pData[0] = *Data / 256;
//起始信号
i2c_Start();
//发送器件地址
i2c_SendByte(DeciveAddr<<1); //
//等待应答
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
i2c_SendByte(RegAddr); //
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
while(count--)
{
//发送数据
i2c_SendByte(pData[count]);
//等待应答
ucAck = i2c_WaitAck();
if(ucAck) return ucAck;
}
//结束信号
i2c_Stop();
return ucAck;
}
经过反复尝试,成功实现了MP2651的读写操作。读取代码如下:
temp = Device_SingleWordRead(I2C_ADDRESS,0x10,&rbuf[0]);//读取电池数量设置
temp = Device_SingleWordRead(I2C_ADDRESS,0x15,&rbuf[1]);//读取电池限压设置
temp = Device_SingleWordRead(I2C_ADDRESS,0x16,&rbuf[2]);//读取状态寄存器0
开始不好使还一度怀疑是芯片没焊好,费了不少功夫,这里要吐槽一下,官方设计了很齐全的辅助工具,却偏偏没有MP2651 I2C Evaluation GUI软件。如果有的话就可以先用这个软件和USB-I2C调试器确认硬件好坏,免得还需要各方验证到底是哪里的问题。图4是读取的部分寄存器值,在调试的时候直接显示在watch窗口。
根据我自己的设计需求,通过以下代码将配置修改为与我设计相符:单节电池,充电限制电压4.2V。代码如下:
uint16_t sbuf[5]={0x0840,0x1A40};
temp = Device_SingeWordWrite(I2C_ADDRESS,0x10,&sbuf[0]);//写入电池数量设置
temp = Device_SingeWordWrite(I2C_ADDRESS,0x15,&sbuf[1]);//写入电池限压设置
写完后读取到的寄存器数值如图5。验证写入成功。
用万用表测量,输出电压为4.2V左右,芯片配置成功,如图6。
到此为止,MP2651的驱动部分就调试完成,不过我发现一个问题,MP2651掉电后设置的配置信息都复位了,下次上电还写重新写一遍,这个比较麻烦,不知道是不是必须出厂时用OTP方式固化好才行。
接下来按照预期需求把DS18B20、发光管、电加热元件焊上,简单调下程序就实现了相关功能。这部分都是以前做过的比较简单,不再赘述。
整个作品调试完如图7所示,基本功能在一个名片大小的板上都实现了,这个做的是单面布件的PCB,如果对体积有要求,还可以双面布件做的更小一些。
通过的本次创意实现,在了解了MP2651这个芯片的强大功能之后,可以看出,对于大多数基于锂电池的应用场景,一片MP2651加一片最简单配置的MCU就可以满足基本的需求。同时,MP2651自带的各种电压、电流、温度等监测功能,既能节省MCU资源,还高度集成,节省PCB空间,提高可靠性,是一个不可多得多功能充电IC。
最后感谢MPS和EEWORLD组织的这次活动,希望以后举办更多类似的活动,让广大工程师了解新技术,有更多方案可以选择。