本章内容:LVR 与 LVD 介绍,使用方式,代码案例,注意事项,源码解释,LVD侦测多种电压实战
本次使用九齐NY8系列芯片,示例代码使用的芯片为NY8A051H,其他NY8系列芯片的LVD使用相差无几,但还请确认不同芯片的芯片手册之间是否有所区别。
一、LVR
LVR:低电压复位,当供电低于设定的低电压值时,它会自动将芯片复位,以防止异常运行。
(一)设置LVR电压
(二)使能LVR
void system_init(void)
{
//....
//其他初始化操作
PCON |= C_LVR_En; //LVR使能
//....
}
二、LVD
LVD:低电压检测,它用于检测芯片供电电压是否低于一定值,当电压低于这个值的时候,会将 LVDOUT 置 0
(一)代码案例
案例描述:芯片检测电压控制小灯,PB1接绿灯接地,PB2接红灯接地
1.当电压大于4.1V时,绿灯亮,红灯灭
2.当电压低于4.1V时,红灯亮,绿灯灭
#include <ny8.h>
#include "ny8_constant.h"
/*
* 案例代码
*/
void main()
{
DISI(); //禁中断
INTE = 0x00;
IOSTB = C_PB_Output; //设置全部PB引脚为输出模式
PORTB = 0x00; //全部PB脚输出低电平
PCON = C_WDT_En; //使能看门狗
//*****低电压检测初始化设置*****
CMPCR = 0x0A; //电压选择控制寄存器
PCONbits.LVDEN = 1; //使能LVD
PCON |= C_LVD_4P1V; //设置LVD检测电压为4.1V
//***************************
while(1)
{
//如果PCON1 & C_LVDOUT的值为1,说明当前输入电压大于4.1V
if(PCON1 & C_LVDOUT)
{
PORTBbits.PB1 = 1; //绿灯亮
PORTBbits.PB2 = 0; //红灯灭
}
//否则,输入电压小于4.1V
else
{
PORTBbits.PB1 = 0; //绿灯灭
PORTBbits.PB2 = 1; //红灯亮
}
CLRWDT(); // 清理看门狗
}
}
(二)注意事项
上述代码很接近官网示例,简单描述了 LVD 初始化以及使用,在第一次使用时,我便踩了不少坑,我先细细盘点我遇到的各种问题:
- LVD实际上就是一个比较器,不同芯片对其的封装便可能有区别,有的芯片在低于设定值时,读取LVDOUT会得到0;但是有的芯片在低于设定值时,读取LVDOUT会得到1。所以,确定LVDOUT的输出需要在官网查看官方文档的内部电路结构
- **LVD也可以侦测不同电压,每改变检测电压时,需要延时特定时间才能得到正确的状态值。**这个具体也会在芯片手册中的标题LVD下说明。
(三)源码解释
//;------------------------------------------------------------
//; PCON1 (0FH) --------- 电源控制寄存器1
//;------------------------------------------------------------
//bit7:为全部中断使能
#define C_All_INT_En 0x80 //启用所有未被屏蔽的中断
//bit6:低压检测器输出 低压为0,非低压为1
#define C_LVDOUT 0x40 //【只读】
//bit[5:2] : 选择LVD电压 (注:必须设置CMPCR为0001010b,即0x0A)
#define C_LVD_3P75V 0x3C //电压3.75V
#define C_LVD_3P45V 0x38 //电压3.45V
#define C_LVD_3P2V 0x34 //电压3.2V
#define C_LVD_4P15V 0x30 //电压4.15V
#define C_LVD_2P6V 0x2C //电压2.6V
#define C_LVD_4P05V 0x28 //电压4.05V
#define C_LVD_1P95V 0x24 //电压1.95V
#define C_LVD_3P9V 0x20 //电压3.9V
#define C_LVD_3P6V 0x1C //电压3.6V
#define C_LVD_3P3V 0x18 //电压3.3V
#define C_LVD_3P0V 0x14 //电压3.0V
#define C_LVD_2P9V 0x10 //电压2.9V
#define C_LVD_2P8V 0x0C //电压2.8V
#define C_LVD_2P4V 0x08 //电压2.4V
#define C_LVD_2P2V 0x04 //电压2.2V
#define C_LVD_2P0V 0x00 //电压2.0V
//bit0 : 使能定时器0
#define C_TMR0_En 0x01 // 使能定时器0
#define C_TMR0_Dis 0x00 // 禁用定时器0
(四)LVD 侦测多种电压
在注意事项中,我提到LVD也可以侦测不同电压,但相对来说还挺复杂,便以一个实战来讲述这个设置方式。
注:NY8A051H 在修改PCON[5:2]时,需要延时50us才能得到正确的状态值,这是通过芯片手册得知,不同芯片可能会有差别
芯片封装:051H合封
原理图:
案例描述:
之前用过九齐051H与4054的合封芯片,PB4在芯片内部与4054的CHRG脚连接(充电时CHRG脚低电平,充满或不充时高阻塞),外部有四个引脚(PB0,PB1,PB2,PB3),分别连接了前灯,侧灯,红色LED指示灯,按键
需求说明:
一、充电部分
1.充电时:红灯常亮
2.充满或未充电时:红灯熄灭
3.电池电压低于3V时,红灯闪烁
二、功能要求
1.一档:LED1以780mA亮灯;0-3min:780mA保持;3-6min:电流缓慢下降,6min后,保持500mA。
2.二档:LED1爆闪模式,频率8 Hz。
3.三档:LED2以300mA亮灯;0-3min:300mA保持;3-6min,电流缓慢下降,6min后,保持;
4.四档:关掉所有灯。
三、待机时进入休眠模式
四、其他要求,在充满电后,如果不开灯或者插拔电源,红灯不能再重新亮起
五、低压关断:2.7V
头疼的问题:
充满电后,过了十几分钟4054会重新充电,导致红灯亮起,这不被允许,所以只好加LVD检测4.1V左右电压,以阻止此事发生。
user_config.h
#ifndef user_config_H
#define user_config_H
#include <ny8.h>
#include "ny8_constant.h"
#define u8 unsigned char
#define u16 unsigned int
#define u32 unsigned long
#define ENABLE 1
#define DISABLE 0
#define TRUE 1
#define FALSE 0
#define SET 1
#define RESET 0
#define INC 1
#define DEC 0
#define ON 0
#define OFF 1
#define BIT0 0x01
#define BIT1 0x02
#define BIT2 0x04
#define BIT3 0x08
#define BIT4 0x10
#define BIT5 0x20
#define BIT6 0x40
#define BIT7 0x80
#define BIT8 0x100
#define BIT9 0x200
#define BIT10 0x400
#define BIT11 0x800
#define BIT12 0x1000
#define BIT13 0x2000
#define BIT14 0x4000
#define BIT15 0x8000
#define IO_CHRG PB4
#define IO_KEY PB3
#define IO_COB1 PB2
#define IO_COB2 PB1
#define IO_LEDR PB0
#define TMR0_T 170 //50us
#define TMR0_PS C_PS0_Div2 //64分频
#define SYSTEM_5MS BIT0
#define SYSTEM_100MS BIT1
#define CHARGE_OFF 0 //未充电
#define CHARGE_ON 1 //充电中
#define CHARGE_FULL 2 //充满
#define UPDATE_REG(x) __asm__("MOVR _" #x ",F")
#endif
main.c
#include "check.h"
#include "pub_config.h"
#define KEY_FILTER 6 //按键抖动滤波次数
#define COB_MAX 116
#define COB_MIN 64
#define COB_SEC_DELAY 180
#define COB_CNT_DELAY 200
extern u8 cnt;
u8 cob_cnt = 0; //档位1毫秒计数(每5ms加1)
u8 cob_sec = 0; //档位秒计数
u8 cob_step = 0; //档位1状况,0-3 78亮,3-6 78->50,6 50
u8 key_scan = 0; //按键状态
u8 key_last = 0; //上一次按键状态
u8 key_filter = 0; //按键抖动计数器
u8 cob_gear = 0; //档位
u8 cob1_duty = 0; //占空比
u8 cob2_duty = 0; //占空比
volatile u8 sleep_delay = 0;
volatile u8 system_tick = 0;
void System_Init(void);
void LED_Show(void);
void LED_Task(void);
void Key_Task(void);
void Sleep_Task(void);
/*
* 每5ms定时器触发中断
*/
void isr() __interrupt(0)
{
static u8 tick_5ms = 0;
static u8 tick_100ms = 0;
if(T0IF)//Timer0溢出中断标志
{
T0IF = 0;//清除中断标志
TMR0 = TMR0_T;
if(++tick_5ms > 100)
{
tick_5ms = 0;
system_tick |= SYSTEM_5MS;
if(++tick_100ms >= 20)
{
tick_100ms = 0;
system_tick |= SYSTEM_100MS;
}
}
}
if(PBIF)
{
PBIF = 0;
sleep_delay = 0;
}
}
void main(void)
{
System_Init();
voltage_reset();
CLRWDT();
while(1)
{
LED_Show();
if(system_tick & SYSTEM_5MS)
{
CLRWDT();
system_tick &= ~SYSTEM_5MS;
Key_Task();//检查按键
LED_Task();//检查灯
}
if(system_tick & SYSTEM_100MS)
{
CLRWDT();
system_tick &= ~SYSTEM_100MS;
Check_CHAG_Task(); //检测是否充满电
Sleep_Task(); //休眠
}
}
}
void System_Init(void)
{
DISI(); //关中断
//GPIO
PORTB = 0x00; //全部输出低电平
IOSTB = C_PB3_Input | C_PB4_Input; //PB3 PB4 输入,其他输出
BPHCON = (unsigned char)~(C_PB3_PHB | C_PB4_PHB);//PB3 PB4 上拉
//时钟
PCON1 = C_TMR0_Dis; //关闭时钟
TMR0 = TMR0_T; //初始化Timer0寄存器
T0MD = TMR0_PS; //分频率64
PCON = C_WDT_En | C_LVR_En | C_LVD_En; //使能看门狗
CMPCR = 0x0A;
PCONbits.LVDEN = 1;
PCON1 = C_TMR0_En | C_LVD_2P9V; //使能时钟
//充电和按键唤醒
BWUCON = C_PB3_Wakeup | C_PB4_Wakeup;//充电和按键唤醒
INTE = C_INT_TMR0 | C_INT_PBKey; //使能Timer0溢出中断和PB输入变化中断
UPDATE_REG(PORTB);
INTF = 0; //清除所有中断标志
ENI(); //开中断
}
//按键任务
void Key_Task(void)
{
//当前高电平,却被按下,清除
if (IO_KEY)
{
if (key_scan)
key_filter = 0;
key_scan = 0;
}
//当前低电平,按键状态为0,设置为1,记为按下
else
{
if (!key_scan)
key_filter = 0;
key_scan = 1;
}
//按键抖动计数
if (key_filter < KEY_FILTER)
{
key_filter++;
sleep_delay = 0;
}
//按键有效
else if (key_last != key_scan)
{
key_last = key_scan;
if (key_scan == 0)
return;
if (cob_gear == 0)
cob_gear = 1;
else if (cob_gear == 1)
cob_gear = 2;
else if (cob_gear == 2)
cob_gear = 3;
else
cob_gear = 0;
}
}
void LED_Show(void)
{
static u8 pwm_cnt = 0;
if(cob_gear)
{
pwm_cnt++;
IO_COB1 = pwm_cnt < cob1_duty ? 1 : 0;
IO_COB2 = pwm_cnt < cob2_duty ? 1 : 0;
if(pwm_cnt >= 200)
pwm_cnt = 0;
}
else
{
IO_COB1 = 0;
IO_COB2 = 0;
}
}
void LED_Task(void)
{
switch(cob_gear)
{
case 0:
cob1_duty = 0;
cob2_duty = 0;
cob_cnt = 0;
cob_sec = 0;
cob_step = 0;
break;
case 1:
cob2_duty = 0;
cob_cnt++;
if(cob_cnt >= COB_CNT_DELAY)
{
cob_cnt=0;
cob_sec++;
}
//每3分钟进入下一步
if(cob_sec >= COB_SEC_DELAY)
{
cob_sec = 0;
if(++cob_step > 2)
cob_step = 2;
}
//根据档位状况,调整占空比
switch(cob_step)
{
case 0:
cob1_duty = COB_MAX;
break;
case 1:
//每6秒调低1%占空比
if(++cob_sec % 6 == 0)
{
if(--cob1_duty <= COB_MIN)
cob_step++;
}
break;
default:
cob1_duty = COB_MIN;
break;
}
break;
case 2:
//清理档位1
cob2_duty = 0;
if(++cob_cnt <= 12)
cob1_duty = COB_MAX;
else if(++cob_cnt <= 25)
cob1_duty = 0;
else
cob_cnt = 0;
break;
case 3:
cob1_duty = 0;
cob2_duty = 61;
break;
default:
cob_gear = 0;
cob1_duty = 0;
cob2_duty = 0;
break;
}
}
//休眠
void Sleep_Task()
{
//如果开着灯或在充电或低压,不休眠
if(cob_gear || charge_state)
{
sleep_delay = 0;
return;
}
//休眠延时达到,进入休眠
if(++sleep_delay >= 10)
{
sleep_delay = 0;
PCON &= ~C_WDT_En;
PCON1 &= ~C_TMR0_En;
NOP();
cnt = 0;
IO_COB1 = 0;
IO_COB2 = 0;
IO_LEDR = 0;
NOP();
SLEEP();
NOP();
NOP();
PCON = C_WDT_En | C_LVR_En | C_LVD_En;
PCON1 = C_TMR0_En | C_LVD_2P9V;
}
}
check.h
#ifndef check_H
#define check_H
#include "user_config.h"
extern volatile u8 voltage_state;
extern volatile u8 charge_state;
extern volatile u8 charge_flag;
#define CHARGE_OFF 0 //未充电
#define CHARGE_ON 1 //充电中
#define CHARGE_FULL 2 //充满电
typedef enum
{
VOLTAGE_OFF = 0,
VOLTAGE_LOW ,
VOLTAGE_FULL,
}VOLTAGE_TypeDef;
void voltage_reset();
void Check_CHAG_Task(void);
#endif
check.c
#include "user_config.h"
#include "check.h"
#define LVD_CLEAR() PCON1 &= ~(BIT2 | BIT3 | BIT4 | BIT5)
#define LVD_SET_OFF() PCON1 |= C_LVD_2P8V
#define LVD_SET_LOW() PCON1 |= C_LVD_2P9V
#define LVD_SET_FULL() PCON1 |= C_LVD_4P15V
#define CHECK_FILTER 5 //充电抖动滤波次数
u8 cnt = 0;
volatile u8 voltage_state = 0; //电压状态
volatile u8 charge_state = 0; //充电状态
volatile u8 charge_flag = 0;
u8 charge_last_state = 0; //上一次充电状态
u8 check_filter = 0; //充电抖动计数
extern volatile u8 sleep_delay;
extern u8 cob_gear; //档位
void check_set(u8 state)
{
LVD_CLEAR();
switch(state)
{
case VOLTAGE_OFF: LVD_SET_OFF(); break;
case VOLTAGE_LOW: LVD_SET_LOW(); break;
case VOLTAGE_FULL: LVD_SET_FULL(); break;
default:break;
}
}
void voltage_reset()//1s
{
static u8 cnt = 0;
u8 check = 0;
u8 i = 0;
u8 j = 0;
voltage_state = VOLTAGE_FULL;
while(1)
{
check_set(voltage_state);
//NOP 200 times = delay 100us
for(j = 0; j <= 120; j++)
{
NOP();NOP();NOP();NOP();NOP();NOP();NOP();NOP();NOP();NOP();
}
CLRWDT();
if (PCON1 & C_LVDOUT)
{
return;
}
if (--voltage_state == VOLTAGE_OFF)
return;
}
}
void Check_CHAG_Task(void)//100
{
static u8 cnt = 0;
static u8 low = 0;
static u8 rise = 0;
static u16 time = 0;
if(PCON1 & C_LVDOUT)
{
low = 0;
if(++rise >= 200)
{
rise = 0;
voltage_reset();
}
}
else
{
rise = 0;
if(++low >= 20)
{
low = 0;
voltage_reset();
}
}
if(IO_CHRG)
{
//chrg拉高后,如果电池电压为满电
if(voltage_state == VOLTAGE_FULL)
{
charge_state = CHARGE_FULL;
}
else
{
charge_state = CHARGE_OFF;//如果电池未满电,则未充电
}
if(cob_gear)
{
charge_state = CHARGE_OFF;//如果开过灯,则状态为未充电
}
}
else
{
if(charge_state != CHARGE_FULL)//如果不是充满电状态,则状态改为充电
{
charge_state = CHARGE_ON;
}
}
if(charge_state == CHARGE_ON)
{
IO_LEDR = 1;
}
else if(voltage_state == VOLTAGE_OFF)
{
if(++cnt < 5)
{
IO_LEDR = 1;
}
else if(cnt < 10)
{
IO_LEDR = 0;
}
else
{
cnt = 0;
}
}
else
{
IO_LEDR = 0;
}
}