上个帖子介绍了PCB设计过程,这个帖子介绍PCB焊接和程序调试过程。
PCB焊接前先进行检查,确保所使用的裸板是合格的,不能有断线缺焊盘等问题,否则等焊完再发现PCB问题,先前的工作前功尽弃了。裸板如下图。
图1、裸板
第一步先啃最难的骨头,就是MP2731。我在做封装的时候为了方便手工焊接,有意把引脚焊盘做的稍微长一点,这样对于QFN焊接更容易上锡。经过一番努力,终于把芯片焊上去了,用万用表测试没有短路。如下图。
图2、焊接MP2731
然后焊接其他部分芯片。其余的部分封装比较大,基本上没什么问题,相对容易焊好了,如下图。
图3、焊接完成
焊完的板子用万用表测试后没有短路现象。然后就是上电测试。接上电池,用万用表测试电压正常,如下图。
图4、电压正常
观测电源输出,在5.5V时,充电电路工作电流大约在468mA,基本和默认设置一致。如下图。
图5、电流正常
电压电流都正常,基本上说明板子焊接没有太大问题。然后接上单片机的调试工具ST-link调试程序,如下图。
图6、调试程序
第一个要操作的肯定是MP2731。我先是用ST官方自带的I2C驱动连接MP2731,但是不管我怎么更改I2C配置,反复尝试就是不能连接上。开始怀疑MP2731没焊好,各种补焊,从引脚上锡程度应该是没问题的。各种尝试不成功后,我想起开发板配的调试工具也可以操作MP2731,然后我赶紧把I2C的两个引脚和GND引出来连接到开发板调试器上,如下图。
图7、连接调试器
在PC端配置完成后一把通讯成功,如下图。这说明我焊接是没有问题的,应该是程序的问题,这样我就放心硬件了,开始仔细研究怎么搞定程序设置。
图8、MP2731通讯成功
网上查到ST官配的I2C代码适用性比较窄,大部分工程师建议自己写代码。因此我决定自己写MP2731的I2C驱动代码,不再考虑库程序了。经过几个晚上的奋战和反复测试,终于搞出完全好用的MP2731驱动代码,整个C文件如下。
代码1、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 = 0 */
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总线异常 */
}
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;
}
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;
}
搞定了MP2731,马上把LED灯、蜂鸣器、电池、按键、震动传感器等外设焊接好,开始弄整体的程序。整体电路如下图。
图9、整体电路
这些外设以前都搞过,比较简单,就是有点繁琐,大概弄了几个晚上基本都搞定了。下面是截取的部分代码,仅供参考。
代码2、部分代码
void LED_PWM_SET(uint8_t level)
{
if(0 == level)
{
/* Stop channel 1 */
if (HAL_TIM_PWM_Stop(&htim14, TIM_CHANNEL_1) != HAL_OK)
{
/* PWM Generation Error */
Error_Handler();
}
}
else
{
/* Stop channel 1 */
if (HAL_TIM_PWM_Stop(&htim14, TIM_CHANNEL_1) != HAL_OK)
{
/* PWM Generation Error */
Error_Handler();
}
if(1 == level)
{
sConfigOC.Pulse = PULSE4_VALUE;
}
else if(2 == level)
{
sConfigOC.Pulse = PULSE3_VALUE;
}
else if(3 == level)
{
sConfigOC.Pulse = PULSE2_VALUE;
}
else
{
sConfigOC.Pulse = PULSE1_VALUE;
}
if (HAL_TIM_PWM_ConfigChannel(&htim14, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* Start channel 1 */
if (HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1) != HAL_OK)
{
/* PWM Generation Error */
Error_Handler();
}
}
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I2C1;
PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_HSI;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief I2C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C1_Init(void)
{
/* USER CODE BEGIN I2C1_Init 0 */
/* USER CODE END I2C1_Init 0 */
/* USER CODE BEGIN I2C1_Init 1 */
/* USER CODE END I2C1_Init 1 */
hi2c1.Instance = I2C1;
hi2c1.Init.Timing = I2C_TIMING;
hi2c1.Init.OwnAddress1 = I2C_ADDRESS;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0xFF;
hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
/** Configure Analogue filter
*/
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
{
Error_Handler();
}
/** Configure Digital filter
*/
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I2C1_Init 2 */
/* USER CODE END I2C1_Init 2 */
}
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC2 = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = uhPrescalerValue;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = PERIOD_VALUE;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC2.OCMode = TIM_OCMODE_PWM1;
sConfigOC2.Pulse = PULSE1_VALUE;
sConfigOC2.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC2.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC2, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC2, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
/* Start channel 1 */
// if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2) != HAL_OK)
// {
// /* PWM Generation Error */
// Error_Handler();
// }
}
/**
* @brief TIM14 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM14_Init(void)
{
/* USER CODE BEGIN TIM14_Init 0 */
/* USER CODE END TIM14_Init 0 */
// TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM14_Init 1 */
/* USER CODE END TIM14_Init 1 */
htim14.Instance = TIM14;
htim14.Init.Prescaler = 0;
htim14.Init.CounterMode = TIM_COUNTERMODE_UP;
htim14.Init.Period = PERIOD_VALUE;
htim14.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim14.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim14) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim14) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = PULSE4_VALUE;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim14, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM14_Init 2 */
/* USER CODE END TIM14_Init 2 */
HAL_TIM_MspPostInit(&htim14);
/* Start channel 1 */
if (HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1) != HAL_OK)
{
/* PWM Generation Error */
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6, GPIO_PIN_SET);
/*Configure GPIO pins : PA0 PA1 PA2 PA15 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA3 PA4 PA5 PA6 */
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;//|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PB0 PB1 PB3 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : PA8 PA9 PA10 */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA11 PA12 */
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_1_IRQn);
HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);
HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
/*Configure GPIO pins : PB6 PB7*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
// GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
// GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7|GPIO_PIN_6, GPIO_PIN_SET);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM1 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY_PWR_Pin)
{
LEDx_StateSet(LED1,LED_TOGGLE);
}
if(GPIO_Pin == KEY_BEEP_Pin)
{
LEDx_StateSet(LED2,LED_TOGGLE);
}
if(GPIO_Pin == SHOCK_INT_Pin)
{
LEDx_StateSet(LED3,LED_TOGGLE);
}
if(GPIO_Pin == MP_INT_Pin)
{
LEDx_StateSet(LED4,LED_TOGGLE);
}
}
经过断断续续调试,基本上把预期的功能都调了一下,这个作品基本完成。
通过本次作品制作,熟悉了MP2731这个芯片的强大功能,对于大多数基于锂电池的应用,基本上一颗MP2731加一颗最简单的MCU就能满足需求。MP2731自带的各种电压、电流、温度等监测功能,既能节省MCU资源,还高度集成,节省PCB空间,提高可靠性,是一个不可多得多功能充电IC。
最后感谢MPS和EEWORLD组织的这次活动,希望以后举办更多类似的活动,让广大工程师了解新技术,有更多方案可以选择。