我继续在低压电源电路管理领域中有关自行车构造的系列文章。 这次,我将讨论一种防止各种二次消费者使汽车电池深度放电的设备。
排放不受控制的可能后果之一。购买第一辆汽车或摩托车是每个人(尤其是工程师)生活中的重要里程碑。 毕竟,除了他的新铁马的明显优势之外,还有谁立即注意到其非显而易见的劣势呢? 谁立即开始考虑对标准进行任何改进和增加? 当然,如果这是高端汽车,甚至是“时尚”品牌的汽车,那么乍一看似乎绝对拥有一切。 但是,正如实践所示,这种情况下,时间反驳了第一印象。 如果您购买的是经济舱汽车,那么头一天就开始发痒!
用各种辅助电子设备“塞满”汽车的愿望是很自然的。 然而,在所有这些计划实施之后不久,生活就面临着车主严酷的现实。 事实证明,即使是建立在最新元素基础上的最现代的设备也仍然非常渴望用电。 而且看起来如此巨大的汽车电池根本不是核反应堆,并且可以在几天之内轻松地“坐下”所有这些看似无害的消费者的重量。
为了不进一步陷入抽象和假设的情况,我将直接讲我的故事。 买车后,首先是要在其中添加注册服务商的愿望。 这是在最短的时间内完成的,几乎完全由速卖通交付包裹的速度决定。 很明显,点烟器的常规电源极为不便,记录仪通过12 / 5v脉冲转换器迅速固定连接到车载网络的最近线路。 由于 坦率地说,不是今天,这个转换器甚至还不是现代的,因为它有自己的需要,后来证明,它消耗的电流高达21 mA。 现在,让我们估算一下该转换器仅能为一个容量为60 Ah的充满电的新电池供电。 算术非常简单且令人失望。
因此,在不到四个月的时间里,没有装载任何东西的转换器将使电池真正“降为零”。 如果我们考虑到并非完全新鲜的电池很容易成为寡妇,并且城市pokatushki之后的充电量远非100%,那么一个月之内的雨天就很容易就开始了。
我再说一遍,仅此而已就是一个电压转换器。 是的,今天您可以购买仅需半毫安的转换器即可满足自己的需求,但我举这个例子只是为了说明
水如何缓慢而自信地
磨出一块石头,甚至是微不足道的,但不断地作用,消费者从看似巨大的东西中汲取能量电池。
我们走得更远,在FHD @ 30fps记录模式下,记录仪本身从+ 5v电源消耗几乎300 mA的电流,考虑到效率,转换后从车载网络提供了约150 mA的电流。 假设用现代转换器代替了转换器,而我们仅用该电流来计算放电时间。
刚过两周,但实际上是十天。 现在,在下一个假期或商务旅行之后,照明(可能还要更换电池)的前景越来越隐约。
事情就这样发生在我身上:当我去短暂的强迫休假时,我不认为在一周左右的时间内即使是中央门锁也无法为我打开门。
许多人会说这是他们自己的错,所有东西都必须断电,或者至少停止录制,他们会是对的。 但是生命就是生命,记忆也不尽相同,琐碎的病假不能持续多久总是可以事先知道的。 因此,断路器的想法立刻出现。
当然,有一个从点火开关为记录器供电的选项,使其只能在旅途中使用,但是该选项也不是很实用,因为 如果汽车撞到停车场,我想有机会见到肇事者。 另外,在安装记录仪后的短时间后,汽车上的人员配备不足,包括隐藏的GPS跟踪器,这些设备应该可以正常工作(如果没有尽头的话),直到至少“几乎所有东西”都已经存在为止。
通常,经过数周的被动反射后,这种设备即应控制车载网络的电压,并根据这些数据来控制为两组用户供电:辅助设备(记录器,USB插座)和基本设备(GPS跟踪器等)什么)。
这怎么办
该设备的第一个虚拟原型是在LM393N模拟比较器的基础上“构建”的,能够处理最初计划从该设备接收的所有内容。 抽象方案就是这样。
这里有两个比较器用于切换负载。 一个公共参考电压发生器,两个确定工作阈值的分压器,捆绑比较器,两个电源开关。 成品设备的外部绑定计划如下。
主钥匙的开启时间比次要钥匙的开启时间长,因此降压转换器本身通过它供电。 初级负载直接连接到转换器。 次级开关对逆变器输出处已经存在于+ 5v电路中的次级负载进行整流。
到底发生了什么
这似乎是所有需要的东西,但是,经常发生的事情,随着细节的考虑,出现了替代实现的想法。 首先,模拟电路包含大量可提供比较器工作模式的分立元件,其次,应该使用微调电阻器来设置跳闸阈值,这会使设置复杂化,并有可能摆脱抖动和时间。 因此,最终决定采用数字实现,事实证明,数字实现在原理图和设置上都更加简单,同时为改进控制算法提供了巨大的机会,最重要的是,在这种情况下,就电流消耗而言,它要经济得多。
ATtiny13A控制器只是询问设备的心脏,该设备除了易于使用和便宜之外,仍然可以在温暖,温暖的DIP外壳中使用,以用于oldfag。 最初,即使是这么小的控制器,从输入/输出数量到程序和RAM的数量,各个方面的功能似乎都是多余的,但是,正如您所知道的,胃口是一餐。 结果,展望未来,我要说的是,该案的最终版本是所有微电路的结论,而自由软件内存只剩下不超过两个字节。
为了测量车载网络的电压,微控制器仅需要一个输入,该输入连接到ADC。 另外两个合乎逻辑的输出是管理消费者。 首先,在向“数字”的最终思想过渡之后,人们希望将两个免费的GPIO适应业务需求,不久就做出了决定。 当再次在寒冷中,起动机以难以掩饰的泪水转动发动机时,电路和算法中存在温度传感器似乎非常有用。 结果,第二个ADC用于测量温度。 为了使热敏电阻仅在需要时才消耗电流,因此决定从最后剩余的逻辑输出为它供电。
结果,设备图已经获得了这种最终形式。
在这里,我们看到的细节很少,其中没有任何东西受到任何“扭曲”。 让我们简要介绍一下要点。
对于电源,控制器需要1.8到5.5 V的稳定电压,这意味着电路中必须有一个稳定器,它将车载网络的电压降至所需的水平。 从节能的角度看,似乎有一个专门用于脉冲降压转换器的地方,但这只是乍一看。 事实是,即使在最耗能的工作模式(频率为8 MHz,执行代码)下,ATtiny13A的功耗也不超过6 mA。 在该方案中,控制器有99%的时间处于深度睡眠模式,并且还以1.2 MHz的频率工作,因此平均功耗约为15 µA以下。 另外,对于控制晶体管的基极电流,大约80 µA(如果两个负载均打开)。 好吧,在不到一秒钟的时间内,热敏电阻的功率被激活,这将使平均电流增加约25微安。 问题的答案就在这里:“为消耗不超过120 µA的负载而加载脉冲转换器是否值得?” 似乎不是那么简单。 而且,如果我们认为我们正在处理模拟测量,那么绝对不值得。 因此,使用了LP2950线性稳定器,它是流行的78L05的功能类似物,但更经济。 该转换器可在输出端提供高达100 mA的电流,而所爱的转换器消耗的电流不超过75 µA。
车载网络的分压器由齐纳二极管和电容器保护,可测量高达15 V的电压。
我知道现在批评浪潮将使我做出这样的决定,但是我们将是客观的。 首先,我没有在开发卫星,其次,没有导致灾难的单一因素。 肩电阻很高,即使在最悲观的情况下,齐纳二极管也能够转移比流过分压器的电流大得多的电流。 在齐纳二极管没有足够的速度时,高频脉冲会保护电容器C2(使用电阻器R7会创建一个截止频率仅为7 Hz的低通滤波器)。 D1和R6在某种程度上确保了该方案不会相互冲突。 而且不要忘了线性度,在这样的位置上进行电流隔离的任何方法都将使理论上的数量计算完全不现实,我们至少必须校准原型,但是我们不需要它。
分压器的输出电阻比ADC信号源推荐的10 kOhm高出十倍,但是由于使用了电容器C2,因此没有测量问题。
通常,根据数据手册,AVR控制器的ADC电路的输入阻抗声明为至少100兆欧。 但是,尽管如此,同一数据表仍建议使用内部电阻高达10 kOhm的信号源。 为什么这样 关键是该ADC本身的工作原理。 转换器以逐次逼近原理工作,其输入电路是电阻器和电容器的低通滤波器。 迭代获取10位样本是必要的,并且有必要在整个测量时间内将电容器充电至全部测量电压。 如果电源的输出阻抗太大,则在转换过程中电容器将继续直接充电,结果将不准确。 在我们的案例中,电容C2大于ADC滤波器的电容的七千倍,这意味着当在测量时打开这些电容器时,在这些电容器之间重新分配电荷时,输入电压的降幅不会超过1/7000,是七倍。低于10位ADC的极限精度。 的确,您需要记住,此技巧仅适用于单次测量之间有明显的暂停的情况,因此,您不应通过为多次连续测量的控制程序添加一个周期并平均其结果来“改善”控制程序。
由于存在受控电源,带有热敏电阻的分压器采用推荐的额定值构建。 NTCLE100E3用作传感器,但没有限制,可以使用任何额定值近似相同的热敏电阻,主要是根据源代码常数中的特性进行校正,以便将分压器的电压转换为正确的温度值。
作为控制键,任何类型的功率P沟道MOSFET都应使用可接受的明渠电阻和最大漏极-源极电压至少为30伏。 上面的电路使用了不同的晶体管。 这样做是因为它们必须切换不同的电压,并且为特定的工作条件选择了每种电压的类型。 较高的晶体管应具有较高的电压,如果可能的话,较低的晶体管应具有最小的开路电阻。 但是,我再说一遍,这个决定是由器件开关电路(见上文)决定的,另外一个因素是对下晶体管的要求可能有所不同。
为了控制电源开关,使用了一对相同的双极晶体管。 起初,这些晶体管似乎是多余的,但在这里并不是那么简单。 具有绝缘栅的场效应晶体管不会从栅上所需极性的任何电压开始打开,而是仅在达到某个阈值水平后才打开,该阈值水平在数据表中以“栅源阈值电压”的名义出现,通常等于2..4V。现在让我们开始只是数数。 控制器的输出电路可以形成两个逻辑电平:电压趋于零的逻辑“ 0”; 逻辑“ 1”,倾向于提供电压。 当由5伏特供电时,这些电压分别约为0和5V。 结果,当切换12伏电源时,栅极上的逻辑“ 0”将产生源栅极12-0 = 12伏的电压差,功率晶体管断开。 一切似乎都是正常的,但是电压为5 V的逻辑“ 1”将在源极和栅极之间产生12-5 = 7伏之间的电压,并且功率晶体管仍将保持断开状态。 因此,五伏控制信号无法控制按键,该按键会将7..9伏以上的电压换向。 因此,控制双极型晶体管实际上并没有像信号放大器那样工作,而是将控制电压从5伏提高到车载网络的电压的放大器。
每个控制晶体管的基本电路中的电阻器仅将控制器输出的电流限制在足以对其进行控制的水平。 它们的额定值可以降低两到三倍,而不会影响电路的运行。
不难发现,控制晶体管不在基于LM393N的模拟电路中。 关键是所选比较器的输出级是根据集电极开路构建的,也就是说,其输出只是终端晶体管集电极的输出。 这种构造原理要求将额外的部件挂在芯片上以产生输出级的负载,但另一方面使芯片非常灵活。 集电极开路允许比较器控制任何可接受的电流源,而不仅仅是与向比较器本身供电的兼容。
我必须说,限制功率MOSFET的阈值电压不仅适用于如上所述的高电压,而且适用于低电压。 毕竟,如果晶体管的最小开路电压为4伏,那么在切换3.3 V电源时,即使将栅极接地也不会在源极和栅极之间产生所需的电压差,并且晶体管将保持闭合。 因此,5伏特可能是所选晶体管可以可靠切换的最小电压。
客制化
设置设备是一个单独的对话。 一方面,电路中没有单个调谐元件,但另一方面,我们正在处理精度不低于0.1 V的测量电压。如何连接所有这些? 有两种方法。 第一种是使用电阻R6,R7和R8,其电阻至少为1%(或更好为0.1%)。 第二个涉及使用常规电阻器测量其实际电阻并校正程序源代码中的系数。
第一种方法适合批量生产,但是对我们来说更具吸引力的是,它不必费心寻找必要的高精度值,因此让我们继续第二种方法。 电阻可以用普通的万用表测量,这里的精度已经足够了。 测量的另一个目标是为电路供电的稳定器的电压。 控制器的ADC可以在不同的模式下工作,但是由于多种原因,对于我们而言,使用相对于电源电压对数字转换结果进行计数的模式更为方便。 这就是为什么尽可能准确地了解它很重要。
计算非常简单,包括在模数转换期间计算电阻分压器的分频系数以及结果在LSB中的转换比例。
Ux是分压器的输入电压;
Ru是分压器上臂的电阻(提供Ux)。
Rd是分压器下臂(接地)的电阻;
Uref-ADC的参考电压(即控制器电源电压);
1024-10位ADC输出的离散值的数量;
LSB是程序从ADC获得的数值。
让我们从分压器R6-R7开始。 . 5.0 . 13.5 :
, , , .
, , , Ru, Ux Uref. :
R8 , R9 NTCLE100E3 0⁰C:
, R8 R9 , , , . . , R9 , 0.5 m, . , , 0.01 .
, , , . , . - , .
, , , .
韧体
AtmelStudio ( gcc-avr 5.4.0)
,
hex . , .
//#define F_CPU 1200000UL //
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <util/delay.h>
//#define DBG
#define TEMPERATURE_OVERHEAT 753 // LSB- +50⁰C
#define TEMPERATURE_GIST 8 // ( LSB)
#define VOLTAGE_GIST 3 // ( LSB)
#define INTERVAL WDTO_1S // (1 )
#ifndef DBG
#define CELL_CHANGE_TIMEOUT 90 // ( INTERVAL, 254)
#define OVERHEAT_TIMEOUT 300 // "" ( INTERVAL)
#else
#define CELL_CHANGE_TIMEOUT 2
#define OVERHEAT_TIMEOUT 3
#endif
typedef unsigned char bool; //
#define true 0 == 0 //
#define false 0 != 0 //
typedef enum {st_none = 0b00, st_primary = 0b01, st_secondary = 0b10, st_both = 0b11} t_states; //
// ,
typedef enum {adc_temperature, adc_voltage} t_measure; //
typedef enum {move_null, move_up, move_down} t_movement; //
//
struct t_coordidates {
signed char row, col;
};
//
struct t_correction {
t_movement voltage, temperature;
};
#define CELLS_ROWS 3 // ( )
#define CELLS_COLS 5 // ( )
//
const t_states CELLS[CELLS_ROWS][CELLS_COLS] = {
{st_both, st_both, st_both, st_primary, st_none},
{st_both, st_both, st_primary, st_none, st_none},
{st_both, st_primary, st_none, st_none, st_none}
};
// LSB- ,
const unsigned int ROWS_EDGES[CELLS_ROWS - 1] = {
241, // 0⁰C
157 // -10⁰C
};
// LSB- ,
const unsigned int COLS_EDGES[CELLS_COLS - 1] = {
864, // 13.5V
800, // 12.5V
787, // 12.3V
768 // 12.0V
};
unsigned int overheat_rest_time = 0; // ""
unsigned char cell_change_time = 0; //
unsigned char no_cur_cell_time = 0; // ,
#define NULL_CELL (struct t_coordidates){.col = -1, .row = -1} // ,
#define NULL_CORRECTION (struct t_correction){.voltage = move_null, .temperature = move_null} // ,
struct t_correction moved_from = NULL_CORRECTION; //
struct t_coordidates cur_cell = NULL_CELL, //
next_cell = NULL_CELL; // -
//
static void init_pins() {
DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB3); // 2 (PB3), 5 (PB0) 6 (PB1)
PORTB &= ~(1 << PB0) & ~(1 << PB1) & ~(1 << PB3); // 2 (PB3), 5 (PB0) 6 (PB1)
}
// /
static void toggle_thermal_sensor(bool state) {
if(state) {
PORTB |= (1 << PB1); // state , 6 (PB1)
_delay_ms(5); //
} else {
PORTB &= ~(1 << PB1); // state , 6 (PB1)
}
}
//
static unsigned int measure_adc(t_measure measure) {
if(measure == adc_temperature) {
toggle_thermal_sensor(true); // ,
ADMUX = 0b10; // - 3 (PB4)
} else {
ADMUX = 0b01; // - 7 (PB2)
}
ADCSRA = (1 << ADPS2) | // = 16 (75 )
(1 << ADIE) | //
(1 << ADEN); //
set_sleep_mode(SLEEP_MODE_ADC); // ""
do {
sleep_cpu(); // , ,
} while(ADCSRA & (1 << ADSC)); // ,
ADCSRA = 0; //
toggle_thermal_sensor(false); //
return ADC; // 10-
}
// watchdog
static void init_interrupts(void) {
sleep_enable(); //
WDTCR = (1 << WDCE) | (1 << WDE); // watchdog
WDTCR = (1 << WDTIE) | INTERVAL; // watchdog , 1
sei(); //
}
//
static void toggle_loads(t_states states) {
unsigned char port = PORTB & ~((1 << PB3) | (1 << PB0)), // ,
bits = (((states & st_primary) >> 0) << PB3) | //
(((states & st_secondary) >> 1) << PB0);
PORTB = port | bits; //
}
// t_coordidates
static bool cells_equal(struct t_coordidates cell1, struct t_coordidates cell2) {
return cell1.row == cell2.row && cell1.col == cell2.col;
}
// LSB-
static signed char get_cell_row(unsigned int temperature) {
signed char row = 0;
while(row < CELLS_ROWS - 1) { //
if(temperature >= ROWS_EDGES[row]) { // temperature ,
return row;
} else {
++row;
}
}
return CELLS_ROWS - 1; // temperature ,
}
// LSB-
static signed char get_cell_col(unsigned int voltage) {
signed char col = 0;
while(col < CELLS_COLS - 1) { //
if(voltage >= COLS_EDGES[col]) { // voltage ,
return col;
} else {
++col;
}
}
return CELLS_COLS - 1; // voltage ,
}
// ,
static void get_row_edges(signed char row, unsigned int *upper, unsigned int *lower) {
*upper = row > 0 ? ROWS_EDGES[row - 1] : 0xffff - TEMPERATURE_GIST; // ,
*lower = row < CELLS_ROWS - 1 ? ROWS_EDGES[row] : TEMPERATURE_GIST; // ,
}
// ,
static void get_col_edges(signed char col, unsigned int *upper, unsigned int *lower) {
*upper = col > 0 ? COLS_EDGES[col - 1] : 0xffff - VOLTAGE_GIST; // ( ) ,
*lower = col < CELLS_COLS - 1 ? COLS_EDGES[col] : VOLTAGE_GIST; // ( ) ,
}
// -
static void gisteresis_correction(struct t_coordidates* new_cell, unsigned int temperature, unsigned int voltage) {
unsigned int upper_edge, lower_edge;
get_row_edges(cur_cell.row, &upper_edge, &lower_edge); //
if(new_cell->row > cur_cell.row && moved_from.temperature == move_up && temperature >= lower_edge - TEMPERATURE_GIST) {
--new_cell->row; // - , , ,
}
if(new_cell->row < cur_cell.row && moved_from.temperature == move_down && temperature <= upper_edge + TEMPERATURE_GIST) {
++new_cell->row; // - , , ,
}
get_col_edges(cur_cell.col, &upper_edge, &lower_edge); //
if(new_cell->col > cur_cell.col && moved_from.voltage == move_up && voltage >= lower_edge - VOLTAGE_GIST) {
--new_cell->col; // - , ( ), ,
}
if(new_cell->col < cur_cell.col && moved_from.voltage == move_down && voltage <= upper_edge + VOLTAGE_GIST) {
++new_cell->col; // - , ( ), ,
}
}
// stdlib::abs()
static unsigned char absolute(signed char value) {
return value >= 0 ? value : -value;
}
// -
static void calc_movement(struct t_coordidates new_cell) {
moved_from = NULL_CORRECTION; // -
if(!cells_equal(new_cell, NULL_CELL) && !cells_equal(cur_cell, NULL_CELL)) { // , -
if(absolute(new_cell.row - cur_cell.row) == 1) { //
moved_from.temperature = new_cell.row < cur_cell.row ? move_up : move_down; //
}
if(absolute(new_cell.col - cur_cell.col) == 1) { //
moved_from.voltage = new_cell.col < cur_cell.col ? move_up : move_down; //
}
}
}
// -
static void set_next_cell(struct t_coordidates cell) {
next_cell = cell;
cell_change_time = 0; //
}
//
static void set_cur_cell(struct t_coordidates cell) {
cur_cell = cell;
no_cur_cell_time = 0; //
set_next_cell(NULL_CELL); // -
}
// ,
static void change_cell(struct t_coordidates new_cell) {
if(cells_equal(new_cell, NULL_CELL)) { //
toggle_loads(st_none);
} else {
toggle_loads(CELLS[new_cell.row][new_cell.col]); //
}
calc_movement(new_cell); //
set_cur_cell(new_cell); //
}
//
static void main_proc(void) {
unsigned int temperature, voltage; // 10- LSB-
struct t_coordidates cell; // -
if(overheat_rest_time) { // "" ,
--overheat_rest_time;
} else {
temperature = measure_adc(adc_temperature); //
if(temperature >= TEMPERATURE_OVERHEAT) { // +50C, :
change_cell(NULL_CELL); // ( )
overheat_rest_time = OVERHEAT_TIMEOUT; //
} else {
voltage = measure_adc(adc_voltage); //
cell.col = get_cell_col(voltage); // -
cell.row = get_cell_row(temperature); // -
if(cells_equal(cur_cell, NULL_CELL)) { // ,
change_cell(cell);
} else {
gisteresis_correction(&cell, temperature, voltage); //
if(cells_equal(cell, cur_cell)) { // - ,
set_next_cell(NULL_CELL);
no_cur_cell_time = 0; // ,
} else {
if(no_cur_cell_time++ > CELL_CHANGE_TIMEOUT) { // CELL_CHANGE_TIMEOUT+1 cur_cell,
change_cell(cell); // ,
} else {
if(cells_equal(next_cell, NULL_CELL) || !cells_equal(next_cell, cell)) { // - ,
set_next_cell(cell);
} else {
if(++cell_change_time >= CELL_CHANGE_TIMEOUT) { // , , ,
change_cell(cell);
}
}
}
}
}
}
}
}
// watchdog
ISR(WDT_vect) {
WDTCR |= (1 << WDTIE); // watchdog ""
}
// , ADSC measure_adc()
EMPTY_INTERRUPT(ADC_vect);
//
int main(void) {
init_pins(); //
init_interrupts(); // watchdog
while(true) { // ,
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //
sleep_cpu(); // watchdog
main_proc(); //
}
}
: L:0x6A, H:0xFF.
. , – , – . , . :
, .
, . , . , , .
, - , . , , . , - , , , , . .. - , , . . . , , .
, . , , , 12.5 , , 12.4 . . , .
, , , . , . «» 8-9 .
, . «» , . , , «» , - (, , , , , - ).
, +50⁰C , . , , , . .
«», , (watchdog). .
, – . . Watchdog , , , . , , , watchdog. , , .
. 1006 , - .
, , . , O2, , Os , 1024 . -, .
.