Berbahaya bagi cahaya, atau bagaimana menjaga baterai mobil tetap kosong

Saya melanjutkan serangkaian artikel tentang konstruksi sepeda di bidang manajemen sirkuit listrik bertegangan rendah. Kali ini saya akan berbicara tentang perangkat yang mencegah pelepasan aki mobil yang dalam oleh berbagai konsumen sekunder.


Salah satu konsekuensi yang mungkin terjadi dari pemulangan yang tidak terkendali.

Membeli mobil atau motor pertama adalah tonggak penting dalam kehidupan setiap orang, dan terutama seorang insinyur. Lagi pula, siapa lagi selain keuntungan nyata dari kuda besi barunya yang segera memperhatikan kerugiannya yang tidak jelas? Siapa yang segera mulai berpikir tentang perbaikan dan penambahan standar? Tentu saja, jika ini adalah mobil dari segmen atas, dan bahkan merek "modis", maka pada awalnya mungkin tampak bahwa ia memiliki segalanya. Tetapi, seperti yang ditunjukkan oleh latihan, dalam hal ini, waktu membantah kesan pertama. Jika Anda membeli mobil kelas ekonomi, maka tangan Anda mulai terasa gatal pada hari pertama!

Keinginan untuk "mengisi" mobil Anda dengan berbagai perangkat elektronik tambahan sangat wajar. Namun, segera setelah implementasi semua rencana ini, hidup berhadapan dengan pemilik mobil dengan kenyataan pahit. Ternyata bahkan perangkat paling modern yang dibangun di atas dasar elemen terbaru masih cukup bersemangat untuk listrik. Dan aki mobil yang tampak sangat besar sama sekali bukan reaktor nuklir, dan dapat dengan mudah "duduk" di bawah beban semua konsumen yang tampaknya tidak berbahaya ini dalam beberapa hari.

Agar tidak mengalir lebih jauh ke dalam situasi abstrak dan hipotetis, saya akan langsung menuju cerita saya. Setelah membeli mobil, yang pertama adalah keinginan untuk memasukkan pendaftar di dalamnya. Ini dilakukan dalam waktu sesingkat mungkin, hampir sepenuhnya ditentukan oleh kecepatan pengiriman paket dari AliExpress. Jelas bahwa catu daya reguler dari pemantik rokok sangat tidak nyaman, dan perekam dengan cepat mendapatkan koneksi stasioner ke saluran terdekat dari jaringan on-board melalui konverter pulsa 12 / 5v. Dan sejak itu itu, secara sederhana, bukan kemarin, konverter ini bahkan tidak modern, untuk kebutuhannya sendiri, ternyata kemudian, ia makan sebanyak 21 mA saat ini. Sekarang mari kita perkirakan berapa banyak konverter ini hanya dapat memberi makan baterai baru dan terisi penuh dengan kapasitas 60 Ah. Aritmatika sangat sederhana dan mengecewakan.


Jadi, dalam waktu kurang dari empat bulan, konverter yang tidak dimuat dengan apa pun akan mendaratkan baterai secara harfiah "ke nol". Jika kita memperhitungkan bahwa baterai yang tidak sepenuhnya segar dapat dengan mudah berubah menjadi janda kurang, dan biaya setelah kota pokatushki jauh dari 100%, hari hujan dengan mudah dimulai dalam waktu sebulan dengan kail.

Dan itu saja, saya ulangi, hanya konverter tegangan. Ya, hari ini Anda dapat membeli konverter yang hanya membutuhkan setengah miliampere untuk kebutuhannya sendiri, tetapi saya memberikan contoh ini hanya untuk menunjukkan seberapa lambat dan percaya diri air menajamkan batu, bahkan remeh, tetapi terus-menerus bertindak, konsumen mengambil energi dari apa yang tampaknya sangat besar baterai.

Kita melangkah lebih jauh, perekam dalam mode perekaman FHD @ 30fps mengkonsumsi hampir 300 mA dari sumber + 5v, yang setelah konversi dengan pertimbangan efisiensinya memberikan arus sekitar 150 mA dari jaringan on-board. Misalkan konverter diganti dengan yang modern, dan kami menghitung waktu pengosongan hanya dengan arus ini.


Hanya lebih dari dua minggu, tetapi dalam praktiknya - sepuluh hari. Sekarang prospek pencahayaan (dan mungkin mengganti baterai) menjulang setelah liburan atau perjalanan bisnis berikutnya.

Dan begitulah yang terjadi pada saya: ketika saya pergi berlibur singkat dengan terpaksa, saya tidak berpikir bahwa dalam seminggu atau lebih bahkan kunci pusat tidak akan dapat membuka pintu untuk saya.

Banyak yang akan mengatakan bahwa itu adalah kesalahan mereka sendiri, bahwa segala sesuatu harus dihilangkan energi, atau setidaknya berhenti merekam, dan mereka akan benar. Tetapi hidup adalah hidup, dan ingatannya tidak sama, dan berapa lama cuti yang sakit itu berlangsung tidak selalu mungkin diketahui sebelumnya. Oleh karena itu, gagasan tentang pemutus sirkuit segera muncul.

Tentu saja ada opsi untuk menghidupkan perekam dari kunci kontak sehingga hanya berfungsi saat bepergian, tetapi opsi ini juga tidak terlalu, karena jika mobil menabrak di tempat parkir, saya ingin memiliki kesempatan untuk melihat pelakunya. Plus, setelah beberapa saat setelah memasang perekam, mobil kekurangan staf dengan beberapa perangkat lagi, termasuk pelacak GPS tersembunyi, yang seharusnya berfungsi, jika tidak sampai akhir, maka setidaknya sampai ketika "hampir semua" sudah ada.

Secara umum, selama beberapa minggu refleksi pasif, gagasan perangkat yang harus mengontrol tegangan jaringan on-board dan berdasarkan data ini untuk mengontrol pasokan daya ke dua kelompok konsumen akhirnya terbentuk: sekunder (perekam, soket USB) dan utama (GPS-tracker dan beberapa apa).

Bagaimana ini bisa dilakukan


Prototipe virtual pertama perangkat "dibangun" berdasarkan pembanding analog LM393N dan mampu melakukan segala sesuatu yang semula direncanakan akan diterima dari perangkat. Skema abstrak adalah sesuatu seperti ini.


Di sini dua pembanding digunakan untuk mengalihkan beban. Generator tegangan referensi umum, dua pembagi yang menentukan ambang batas untuk operasi, pengikat pembanding, dua sakelar daya. Ikatan eksternal perangkat jadi direncanakan sebagai berikut.


Kunci primer tetap hidup lebih lama daripada kunci sekunder, sehingga konverter step-down itu sendiri diaktifkan melalui itu. Muatan primer terhubung langsung ke konverter. Saklar sekunder mengubah beban sekunder yang sudah ada di sirkuit + 5v pada output inverter.

Apa yang keluar pada akhirnya


Tampaknya hanya itulah yang diperlukan, tetapi, seperti yang sering terjadi, dengan pemikiran rinciannya, muncul gagasan implementasi alternatif. Pertama, rangkaian analog berisi segunung elemen diskrit yang menyediakan mode operasi pembanding, dan kedua, ambang perjalanan seharusnya diatur menggunakan resistor trim, yang memperumit pengaturan dan menciptakan kemungkinan “lolos” dari goncangan dan waktu. Oleh karena itu, pada akhirnya, diputuskan untuk memikirkan implementasi digital, yang ternyata jauh lebih sederhana baik secara skematis dan dalam pengaturan, sambil membuka peluang besar untuk meningkatkan algoritma kontrol, dan, yang paling penting, dalam konteks ini, ternyata menjadi urutan besarnya lebih ekonomis dalam hal konsumsi saat ini.

Kontroler ATtiny13A hanya menanyakan inti dari perangkat, yang, selain kemudahan penggunaan dan murahnya, masih tersedia dalam case DIP-hangat tabung hangat untuk oldfag. Pada awalnya, kemampuan pengendali sekecil itu tampak mubazir di semua lini, mulai dari jumlah input / output hingga jumlah program dan RAM, namun, seperti yang Anda tahu, nafsu makan datang dengan hidangan. Sebagai hasilnya, melihat ke depan, saya akan mengatakan bahwa versi final dari kasus ini ternyata merupakan semua kesimpulan dari rangkaian mikro, dan memori perangkat lunak bebas tidak meninggalkan lebih dari dua lusin byte.

Untuk mengukur tegangan jaringan terpasang, mikrokontroler hanya membutuhkan satu input, yang terhubung ke ADC. Dua keluaran yang lebih logis adalah mengelola konsumen. Pertama-tama, setelah transisi mental terakhir ke "digital", ada keinginan untuk mengadaptasi dua GPIO gratis ke bisnis, dan keputusan itu tidak lama datang. Ketika, sekali lagi, dalam cuaca dingin, starter menghidupkan mesin dengan sobekan yang tersembunyi, keberadaan sensor suhu di sirkuit dan algoritma tampak sangat berguna. Akibatnya, ADC kedua digunakan untuk mengukur suhu. Dan agar termistor untuk mengkonsumsi arus hanya ketika diperlukan, diputuskan untuk menyalakannya dari keluaran logis terakhir yang tersisa.

Akibatnya, diagram perangkat telah memperoleh bentuk final seperti itu.


Di sini kita melihat detail yang sangat minimum, dan di antara mereka tidak ada yang tunduk pada jenis "memutar". Mari kita bahas secara singkat poin-poin utama.

Untuk catu daya, pengontrol memerlukan tegangan stabil dari 1,8 hingga 5,5 V, yang berarti bahwa harus ada stabilizer di sirkuit yang akan menurunkan tegangan jaringan on-board ke level yang diperlukan. Dari sudut pandang penghematan energi, mungkin terlihat bahwa ada tempat untuk konverter stepdown berdenyut secara eksklusif, tetapi ini hanya pada pandangan pertama. Faktanya adalah ATtiny13A bahkan dalam mode operasi paling intensif energi (frekuensi 8 MHz, eksekusi kode aktif) mengkonsumsi tidak lebih dari 6 mA. Dalam skema ini, pengontrol 99% waktu dalam mode tidur nyenyak dan juga beroperasi pada frekuensi 1,2 MHz, menghasilkan konsumsi rata-rata sekitar kurang dari 15 μA. Plus, sekitar 80 μA ke arus basis dari transistor kontrol (jika kedua beban aktif). Nah, untuk sepersekian detik, kekuatan termistor diaktifkan, yang menambahkan sekitar 25 microamp ke arus rata-rata. Dan di sini ada jawaban untuk pertanyaan “apakah layak untuk memuat konverter pulsa demi beban dengan konsumsi tidak lebih dari 120 μA? Sepertinya tidak begitu mudah. Dan jika kita menganggap bahwa kita berurusan dengan pengukuran analog, maka pasti tidak sepadan. Oleh karena itu, stabilisator linier LP2950 digunakan, analog fungsional 78L05 populer, tetapi jauh lebih ekonomis. Konverter ini dapat memberikan hingga 100 mA arus pada output, sambil mengkonsumsi tidak lebih dari 75 μA untuk orang yang dicintai.

Pembagi tegangan jaringan on-board, dilindungi oleh dioda zener dan kapasitor, memungkinkan Anda untuk mengukur tegangan hingga 15 V.

Saya tahu bahwa sekarang gelombang kritik akan menyerang saya untuk keputusan seperti itu, tetapi kami akan bersikap objektif. Pertama, saya tidak mengembangkan satelit, dan kedua, tidak ada faktor tunggal yang akan menyebabkan bencana. Hambatan bahu tinggi, dioda zener mampu mengalihkan lebih banyak arus daripada yang bisa mengalir melalui pembagi, bahkan dalam skenario yang paling pesimistis. Dari pulsa frekuensi tinggi, ketika dioda zener tidak memiliki kecepatan yang cukup, kapasitor C2 akan melindungi (dengan resistor R7 itu menciptakan filter low-pass dengan frekuensi cutoff hanya 7 Hz). D1 dan R6 sampai batas tertentu memastikan skema dari jatuh satu sama lain. Dan orang tidak boleh lupa tentang linearitas, metode isolasi galvanik di tempat seperti itu akan membuat perhitungan teoritis jumlah sepenuhnya tidak realistis, kita harus mengkalibrasi setidaknya prototipe, tetapi kita tidak membutuhkannya.

Resistansi keluaran pembagi sepuluh kali lebih tinggi dari yang direkomendasikan 10 kOhm untuk sumber sinyal ADC, tetapi berkat kapasitor C2 tidak ada masalah pengukuran.

Secara umum, impedansi input dari sirkuit ADC dari pengendali AVR menurut datasheet dinyatakan setidaknya 100 megohms. Namun demikian, lembar data yang sama merekomendasikan penggunaan sumber dengan resistansi internal hingga 10 kOhm. Kenapa begitu Intinya adalah prinsip operasi ADC ini sendiri. Konverter beroperasi berdasarkan prinsip perkiraan sekuensial , dan rangkaian inputnya adalah filter low-pass dari resistor dan kapasitor. Memperoleh sampel 10-bit adalah berulang, dan kapasitor perlu diisi ke tegangan terukur penuh selama seluruh waktu pengukuran. Jika impedansi keluaran sumber terlalu besar, kapasitor akan terus dibebankan secara langsung selama proses konversi dan hasilnya akan tidak akurat. Dalam kasus kami, kapasitansi C2 lebih dari tujuh ribu kali kapasitas filter ADC, yang berarti bahwa ketika muatan didistribusikan kembali antara kapasitor ini ketika diaktifkan pada saat pengukuran, tegangan input akan berkurang tidak lebih dari 1/7000, yang merupakan tujuh kali lipat kurang dari akurasi tertinggi dari 10-bit ADC. Benar, Anda harus ingat bahwa trik ini hanya berfungsi untuk pengukuran tunggal dengan jeda yang signifikan di antara mereka, jadi Anda tidak boleh "meningkatkan" program kontrol dengan menambahkan siklus padanya untuk beberapa pengukuran berturut-turut dengan rata-rata hasilnya.

Pembagi dengan termistor karena keberadaan sumber daya yang dikontrol dibangun menggunakan peringkat yang direkomendasikan. NTCLE100E3 digunakan sebagai sensor, tetapi tidak ada batasan, Anda dapat menggunakan termistor yang kira-kira memiliki peringkat yang sama, hal utama adalah membuat koreksi yang sesuai dengan karakteristiknya dalam konstanta kode sumber sehingga tegangan pembagi dikonversi ke nilai suhu yang benar.

Sebagai kunci kontrol, daya MOSFET saluran-P jenis apa pun digunakan dengan resistansi saluran terbuka yang dapat diterima dan tegangan sumber-saluran maksimum minimal 30 volt. Rangkaian di atas menggunakan transistor yang berbeda. Ini dilakukan karena mereka harus mengganti voltase yang berbeda dan tipe masing-masing dipilih untuk kondisi kerja tertentu. Transistor atas harus lebih bertegangan tinggi, dan semakin rendah, jika mungkin, memiliki resistansi saluran terbuka minimum. Tapi, saya ulangi, keputusan ini ditentukan oleh sirkuit switching perangkat (lihat di atas), dengan inklusi lain persyaratan untuk transistor yang lebih rendah mungkin berbeda.

Untuk mengontrol sakelar daya, sepasang transistor bipolar identik digunakan. Pada awalnya mungkin tampak bahwa transistor ini berlebihan, tetapi di sini tidak begitu sederhana. Transistor efek medan dengan gerbang berinsulasi mulai membuka bukan dari tegangan apa pun dari polaritas yang diperlukan di gerbang, tetapi hanya setelah mencapai tingkat ambang tertentu, yang muncul di lembar data dengan nama "tegangan ambang pintu-ke-sumber" dan biasanya sama dengan 2.,4 V. Sekarang mari kita lihat hitung saja. Sirkuit keluaran pengontrol dapat membentuk dua level logis: logis "0" dengan tegangan cenderung nol; dan logis "1" dengan tegangan cenderung menyuplai. Ketika diberdayakan oleh 5 volt, ini akan menjadi tegangan sekitar 0 dan 5 V, masing-masing. Akibatnya, ketika mengganti sumber 12-volt, "0" logis pada gerbang akan membuat perbedaan tegangan sumber-gerbang 12 - 0 = 12 volt, transistor daya terbuka. Semuanya tampak normal, tetapi logis "1" dengan tegangan 5 V akan menciptakan tegangan antara 12 - 5 = 7 volt antara sumber dan gerbang, dan transistor daya akan tetap terbuka. Dengan demikian, sinyal kontrol lima volt tidak dapat mengontrol kunci, yang mengubah tegangan di atas 7..9 volt. Oleh karena itu, transistor bipolar kontrol sebenarnya bekerja tidak begitu banyak dengan tombol sinyal sebagai amplifier yang menaikkan tegangan kontrol dari 5 volt ke tegangan jaringan on-board.

Resistor dalam rangkaian dasar dari masing-masing transistor kontrol hanya membatasi arus keluaran pengontrol ke tingkat yang cukup untuk mengendalikannya. Peringkat mereka dapat dikurangi dua hingga tiga kali tanpa konsekuensi untuk pengoperasian sirkuit.

Sangat mudah untuk melihat bahwa transistor kontrol tidak ada dalam rangkaian analog berdasarkan pada LM393N. Masalahnya adalah bahwa tahap output komparator yang dipilih dibangun sesuai dengan rangkaian kolektor terbuka, yaitu, outputnya hanyalah keluaran dari kolektor transistor terminal. Prinsip konstruksi ini membutuhkan bagian tambahan yang digantung pada chip untuk membuat beban tahap output, tetapi, di sisi lain, membuat chip sangat fleksibel. Kolektor terbuka memungkinkan komparator untuk mengontrol sumber arus apa pun yang dapat diterima, dan tidak hanya kompatibel dengan sumber yang menyediakan daya bagi komparator itu sendiri.

Saya harus mengatakan bahwa membatasi ambang tegangan MOSFET daya bekerja tidak hanya terhadap tegangan tinggi, seperti yang disebutkan di atas, tetapi juga terhadap yang rendah. Lagi pula, jika tegangan pembukaan minimum dari transistor adalah, katakanlah, 4 volt, maka ketika beralih sumber 3.3 V, bahkan menghubungkan gerbang ke tanah tidak akan membuat perbedaan tegangan yang diinginkan antara sumber dan gerbang dan transistor akan tetap ditutup. Jadi 5 volt adalah, mungkin, tegangan minimum yang dapat dengan andal dialihkan oleh transistor yang dipilih.

Kustomisasi


Menyiapkan perangkat adalah percakapan terpisah. Di satu sisi, tidak ada elemen tala tunggal di sirkuit, tetapi di sisi lain, kita berhadapan dengan mengukur tegangan dengan akurasi tidak lebih buruk dari 0,1 V. Bagaimana menghubungkan semua ini? Ada dua cara. Yang pertama adalah menggunakan resistor R6, R7 dan R8 dengan toleransi minimal 1% (atau lebih baik 0,1%). Yang kedua melibatkan penggunaan resistor konvensional dengan pengukuran resistensi nyata mereka dan koreksi koefisien dalam kode sumber program.

Metode pertama baik untuk produksi massal, tetapi jauh lebih menarik bagi kita untuk tidak repot-repot mencari nilai presisi tinggi yang diperlukan, jadi mari kita lanjutkan dengan cara kedua. Resistansi dapat diukur dengan multimeter biasa, akurasinya di sini cukup memadai. Objek pengukuran lain adalah tegangan stabilizer yang memasok rangkaian. ADC controller dapat bekerja dalam mode yang berbeda, tetapi karena beberapa alasan lebih mudah bagi kita untuk menggunakannya di mana hasil konversi digital dihitung relatif terhadap tegangan suplai. Itulah mengapa penting untuk mengetahuinya seakurat mungkin.

Perhitungannya sangat sederhana dan terdiri dari menghitung koefisien pembagian pembagi resistif dan proporsi terjemahan hasil dalam LSB selama konversi analog-ke-digital.


Ux adalah tegangan input pembagi;
Ru adalah resistansi lengan atas pembagi (yang disediakan Ux);
Rd adalah resistansi lengan bawah pembagi (yang terhubung ke tanah);
Uref - tegangan referensi ADC (yaitu tegangan suplai pengontrol);
1024 - jumlah nilai diskrit pada output 10-bit ADC;
LSB adalah nilai numerik yang diperoleh oleh program dari ADC.

Mari kita mulai dengan pembagi tegangan R6-R7. . 5.0 . 13.5 :


, , , .

, , , Ru, Ux Uref. :


R8 , R9 NTCLE100E3 0⁰C:


, R8 R9 , , , . . , R9 , 0.5 m, . , , 0.01 .

, , , . , . - , .

, , , .

Firmware


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 . -, .

.


Eagle .

Source: https://habr.com/ru/post/id446956/


All Articles