Girino - Oscilloscope Arduino Cepat

Saya seorang Fisikawan dan bagian terbaik dari bekerja di bidang ini adalah saya dapat membuat instrumen sendiri. Dengan cara berpikir seperti ini, saya memutuskan untuk membuat osiloskop Arduino buatan sendiri. Instruksi ini ditulis dengan tujuan untuk mengajarkan sedikit tentang mikrokontroler dan akuisisi data. Ini adalah proyek ekstrem karena saya ingin keluar dari Arduino secepat mungkin, saya belum melihat Oscilloscope Arduino lainnya secepat ini.

Beberapa waktu lalu saya sedang mengerjakan proyek Arduino dan saya perlu melihat apakah sinyal keluaran sesuai dengan spesifikasi. Jadi saya menghabiskan beberapa waktu di internet untuk mencari Arduino Oscilloscopes yang sudah diimplementasikan, tetapi saya tidak menyukai apa yang saya temukan. Proyek-proyek yang saya temukan sebagian besar terdiri dari Graphical User Interface untuk komputer yang ditulis dalam Processing dan sketsa arduino yang sangat sederhana. Sketsa itu seperti: void setup () {

Serial.begin (9600);

}

void loop () {

int val = analogRead (ANALOG_IN);

Serial.println (val);

} Pendekatan ini tidak salah dan saya tidak ingin menghina siapa pun, tetapi ini terlalu lambat bagi saya. Port serial lambat dan mengirimkan setiap hasil dari analogRead () melalui itu adalah hambatan.

Saya telah mempelajari Waveform Digitizers selama beberapa waktu dan saya tahu benar bagaimana cara kerjanya, jadi saya mendapat inspirasi dari mereka. Ini adalah titik awal dari osiloskop yang ingin saya buat:

  • sinyal yang masuk harus dipisahkan dari arduino untuk melestarikannya;
  • dengan offset sinyal, dimungkinkan untuk melihat sinyal negatif;
  • data harus di-buffer;
  • pemicu perangkat keras diperlukan untuk menangkap sinyal;
  • buffer melingkar dapat memberikan bentuk sinyal sebelum pelatuk (lebih banyak untuk mengikuti hal ini);
  • menggunakan fungsi tuas rendah yang standar membuat program berjalan lebih cepat.

Sketsa untuk Arduino melekat pada langkah ini, bersama dengan skema rangkaian yang saya buat.

Nama yang saya buat, Girino, adalah bahasa Italia yang sembrono. Giro berarti rotasi dan menambahkan akhiran -ino Anda mendapatkan rotasi kecil, tetapi Girino juga berarti kecebong . Dengan cara ini saya mendapat nama dan maskot.

Langkah 1: Penafian

PENULIS INSTRUKTIF INI TIDAK MEMBUAT JAMINAN VALIDITAS DAN TIDAK ADA JAMINAN APA PUN .

Elektronik bisa berbahaya jika Anda tidak tahu apa yang Anda lakukan dan penulis tidak dapat menjamin validitas informasi yang ditemukan di sini. Ini bukan saran profesional dan apa pun yang ditulis dalam instruksi ini dapat tidak akurat, menyesatkan, berbahaya, atau salah. Jangan mengandalkan informasi apa pun yang ditemukan di sini tanpa verifikasi independen.

Terserah Anda untuk memverifikasi informasi apa pun dan mengecek apakah Anda tidak memperlihatkan diri Anda, atau siapa pun, pada bahaya apa pun atau memaparkan apa pun terhadap kerusakan apa pun; Saya tidak bertanggung jawab. Anda harus mengikuti sendiri tindakan pencegahan keamanan yang tepat, jika Anda ingin mereproduksi proyek ini.

Gunakan panduan ini dengan risiko Anda sendiri!

Langkah 2: Apa yang Anda Butuhkan

Yang benar-benar kita butuhkan untuk proyek ini adalah papan Arduino dan lembar data ATMega328P.
Lembar data adalah yang memberi tahu kita cara kerja mikrokontroler dan sangat penting untuk menyimpannya jika kita menginginkan tuas kontrol yang lebih rendah.

Lembar data dapat ditemukan di sini: //www.atmel.com/Images/doc8271.pdf

Perangkat keras yang saya tambahkan ke Arduino sebagian diperlukan, tujuannya hanya untuk membentuk sinyal untuk ADC dan untuk memberikan level tegangan untuk pelatuk. Jika Anda mau, Anda bisa mengirim sinyal langsung ke Arduino dan menggunakan beberapa referensi tegangan yang ditentukan oleh pembagi tegangan, atau bahkan 3, 3 V yang diberikan oleh Arduino itu sendiri.

Langkah 3: Output Debug

Saya biasanya menaruh banyak hasil debug di program saya karena saya ingin melacak apa pun yang terjadi; masalah dengan Arduino adalah kita tidak memiliki stdout untuk menulis. Saya memutuskan untuk menggunakan port Serial sebagai stdout.

Namun, perlu diketahui bahwa pendekatan ini tidak berhasil setiap saat! Karena menulis ke port Serial memerlukan waktu untuk eksekusi dan dapat secara dramatis mengubah hal-hal selama beberapa rutinitas yang masuk akal.

Saya biasanya mendefinisikan output debugging di dalam makro preprocessor, jadi ketika debug dinonaktifkan mereka hanya menghilang dari program dan tidak memperlambat eksekusi:
  • dprint (x); - Menulis ke port serial, seperti: # x: 123
  • dshow ("Some string"); - Menulis string

Ini definisi:

#jika DEBUG == 1
#define dprint (ekspresi) Serial.print ("#"); Serial.print (#expression); Serial.print (":"); Serial.println (ekspresi)
#define dshow (ekspresi) Serial.println (ekspresi)
#lain
#define dprint (ekspresi)
#define dshow (ekspresi)
#berakhir jika

Langkah 4: Mengatur Bit Registrasi

Dengan tujuan menjadi cepat, perlu untuk memanipulasi fitur mikrokontroler dengan fungsi tuas yang lebih rendah daripada yang standar yang disediakan oleh Arduino IDE. Fungsi internal dikelola melalui beberapa register, yaitu kumpulan delapan bit di mana masing-masing mengatur sesuatu yang khusus. Setiap register berisi delapan bit karena ATMega328P memiliki arsitektur 8-bit.

Register memiliki beberapa nama yang ditentukan dalam datasheet tergantung pada artinya, seperti ADCSRA untuk Register Pengaturan ADC. Juga setiap bit bermakna dari register memiliki nama, seperti ADEN untuk ADC Enable Bit dalam register ADCSRA.

Untuk mengatur bit-bitnya, kita bisa menggunakan sintaks C yang biasa untuk aljabar biner, tetapi saya menemukan di internet beberapa makro yang sangat bagus dan bersih:

// Tetapkan untuk mengatur dan menghapus bit register
#ifndef cbi
#define cbi (sfr, bit) (_SFR_BYTE (sfr) & = ~ _BV (bit))
#berakhir jika
#ifndef sbi
#define sbi (sfr, bit) (_SFR_BYTE (sfr) | = _BV (bit))
#berakhir jika

Menggunakannya sangat sederhana, jika kita ingin menetapkan 1 Enable Bit dari ADC kita bisa menulis:

sbi (ADCSRA, ADEN);

Sementara jika kita ingin mengaturnya ke 0 ( id est clear it) kita bisa menulis:

cbi (ADCSRA, ADEN);

Langkah 5: Apa Interupsi

Seperti yang akan kita lihat pada langkah selanjutnya, penggunaan interupsi diperlukan dalam proyek ini. Interupsi adalah sinyal yang memberi tahu mikrokontroler untuk menghentikan eksekusi loop utama dan meneruskannya ke beberapa fungsi khusus. Gambar-gambar memberikan gambaran tentang alur program.

Fungsi-fungsi yang dieksekusi disebut Interrupt Service Routines (ISR) dan lebih atau kurang fungsi sederhana, tetapi itu tidak mengambil argumen.

Mari kita lihat contohnya, seperti menghitung beberapa pulsa. ATMega328P memiliki Analog Comparator yang memiliki interupsi terkait yang diaktifkan ketika sinyal melampaui tegangan referensi. Pertama-tama Anda harus mendefinisikan fungsi yang akan ditampilkan:

ISR (ANALOG_COMP_vect)
{
counter ++;
}

Ini sangat sederhana, instruksi ISR ​​() adalah makro yang memberi tahu kompiler bahwa fungsi berikut adalah Rutin Layanan Interupsi. Sementara ANALOG_COMP_vect disebut Interrupt Vector dan memberitahu kompiler yang interupsi terkait dengan rutin itu. Dalam hal ini, Analog Comparator Interrupt. Jadi, setiap kali komparator melihat sinyal lebih besar dari referensi, ia memberi tahu mikrokontroler untuk mengeksekusi kode itu, terutama dalam hal ini untuk menambah variabel itu.

Langkah selanjutnya adalah mengaktifkan interupsi yang terkait. Untuk mengaktifkannya kita harus mengatur bit ACIE (Analog Comparator Interrupt Enable) dari register ACSR (Analog Comparator Setting Register):

sbi (ACSR, ACIE);

Di situs berikut ini kita dapat melihat daftar semua Interrupt Vektor:
//www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Langkah 6: Terus Mengakuisisi Dengan Circular Buffer

Konsep menggunakan Circular Buffer cukup lurus ke depan:

Dapatkan terus menerus sampai sinyal ditemukan, kemudian kirim sinyal digital ke komputer.

Pendekatan ini memungkinkan untuk memiliki bentuk sinyal yang masuk juga sebelum peristiwa pemicu.


Saya menyiapkan beberapa diagram untuk membuat diri saya jelas. Poin-poin berikut mengacu pada gambar.
  • Pada gambar pertama kita bisa melihat apa yang saya maksud dengan akuisisi berkelanjutan . Kami mendefinisikan buffer yang akan menyimpan data, dalam kasus saya sebuah array dengan 1280 slot, maka kami mulai terus membaca ADC output register (ADCH) dan mengisi buffer dengan data. Ketika kita sampai ke ujung buffer, kita memulai kembali dari awal tanpa membersihkannya. Jika kita mengubah array yang diatur dalam cara melingkar mudah untuk melihat apa yang saya maksud.
  • Ketika sinyal melampaui ambang batas, Interupsi Komparator Analog diaktifkan. Kemudian kami memulai fase menunggu di mana kami terus mendapatkan sinyal tetapi menghitung siklus ADC yang melewati dari Analog Comparator Interrupt.
  • Ketika kami menunggu siklus N (dengan N <1280), kami membekukan situasi dan menghentikan siklus ADC. Jadi kita berakhir dengan buffer yang diisi dengan digitalisasi bentuk temporal sinyal. Bagian terbaik dari ini, adalah bahwa kita juga memiliki bentuk sebelum peristiwa pemicu, karena kita sudah mendapatkan sebelumnya.
  • Sekarang kita dapat mengirim seluruh buffer ke port serial dalam satu blok data biner, alih-alih mengirimkan ADC tunggal yang dibaca. Ini mengurangi biaya yang diperlukan untuk mengirim data dan hambatan sketsa yang saya temukan di internet.

Langkah 7: Pemicu Osiloskop

Sebuah osiloskop menunjukkan pada layarnya sebuah sinyal, yang kita semua setuju, tetapi bagaimana ia dapat menunjukkannya dengan mantap dan tidak menunjukkannya melompat-lompat di layar? Ini memiliki pemicu internal yang mampu menunjukkan sinyal selalu pada posisi layar yang sama (atau setidaknya sebagian besar kali), menciptakan ilusi plot yang stabil.

Pemicu dikaitkan dengan ambang yang mengaktifkan sapuan ketika sinyal melewatinya. Sapuan adalah fase di mana osiloskop merekam dan menampilkan sinyal. Setelah sapuan fase lain terjadi: penahanan, di mana osiloskop menolak sinyal masuk. Periode penahanan dapat terdiri dari bagian waktu mati, di mana osiloskop tidak dapat menerima sinyal apa pun, dan bagian yang dapat dipilih pengguna. Waktu mati dapat disebabkan oleh berbagai alasan seperti harus menggambar di layar atau harus menyimpan data di suatu tempat.

Melihat gambar itu kita bisa merasakan apa yang terjadi.
  1. Sinyal 1 melampaui ambang batas dan mengaktifkan sapuan;
  2. sinyal 2 ada di dalam waktu sapuan dan tertangkap dengan yang pertama;
  3. setelah penahanan, sinyal 3 mengaktifkan sapuan lagi;
  4. sebaliknya sinyal 4 ditolak karena jatuh di dalam wilayah penahanan.
The raison d'être dari fase penahanan adalah untuk mencegah beberapa sinyal yang tidak diinginkan untuk masuk ke wilayah sapuan. Agak lama untuk menjelaskan hal ini dan menghindari tujuan yang bisa diajari ini.

Moral dari kisah ini adalah bahwa kita membutuhkan:
  1. tingkat ambang batas yang dapat kita bandingkan dengan sinyal yang masuk;
  2. sinyal yang memberitahu mikrokontroler untuk memulai fase menunggu (lihat langkah sebelumnya).
Kami memiliki beberapa solusi untuk poin 1.:
  • menggunakan pemangkas kita dapat secara manual mengatur level tegangan;
  • menggunakan PWM dari Arduino kita dapat mengatur level dengan perangkat lunak;
  • menggunakan 3.3 V yang disediakan oleh Arduino sendiri;
  • menggunakan referensi bangap internal kita bisa menggunakan level tetap.
Untuk poin 2. kami memiliki solusi yang tepat: kita dapat menggunakan interupsi Comparator Analog internal mikrokontroler.

Langkah 8: Cara Kerja ADC

Mikrokontroler Arduino memiliki fitur ADC perkiraan 10-bit tunggal berurutan. Sebelum ADC ada multiplexer analog yang memungkinkan kami mengirim, ke ADC, sinyal dari berbagai pin dan sumber (tetapi hanya satu per satu).

Perkiraan ADC Berurutan berarti bahwa ADC membutuhkan 13 siklus clock untuk menyelesaikan konversi (dan 25 siklus clock untuk konversi pertama). Ada sinyal jam yang didedikasikan untuk ADC yang "dihitung" dari jam utama Arduino; ini karena ADC sedikit lambat dan tidak dapat mengimbangi kecepatan bagian lain dari mikrokontroler. Ini membutuhkan frekuensi input jam antara 50 kHz dan 200 kHz untuk mendapatkan resolusi maksimum. Jika diperlukan resolusi yang lebih rendah dari 10 bit, frekuensi clock input ke ADC bisa lebih tinggi dari 200 kHz untuk mendapatkan laju sampel yang lebih tinggi.

Tetapi berapa banyak tarif yang lebih tinggi yang bisa kita gunakan? Ada beberapa panduan bagus tentang ADC di Open Music Labs yang saya sarankan untuk dibaca:
  • //www.openmusiclabs.com/learning/digital/atmega-adc/
  • //www.openmusiclabs.com/learning/digital/atmega-adc/in-depth/
Karena tujuan saya adalah untuk mendapatkan osiloskop cepat, saya memutuskan untuk membatasi presisi hingga 8-bit. Ini memiliki beberapa bonus:
  1. buffer data dapat menyimpan lebih banyak data;
  2. Anda tidak menyia-nyiakan 6-bit RAM per datum;
  3. ADC dapat memperoleh lebih cepat.
Prescaler memungkinkan kita membagi frekuensi, dengan beberapa faktor, dengan mengatur bit ADPS0-1-2 dari register ADCSRA. Melihat plot presisi dari Artikel Open Music Labs, kita dapat melihat bahwa untuk presisi 8-bit frekuensinya bisa mencapai 1, 5 MHz, bagus! Tetapi karena kemampuan mengubah faktor prescaler memungkinkan kita mengubah laju akuisisi, kita dapat menggunakannya juga untuk mengubah skala waktu osiloskop.

Ada fitur bagus tentang register keluaran: kita dapat memutuskan penyesuaian bit konversi, dengan mengatur bit ADLAR dalam register ADMUX. Jika 0 mereka benar disesuaikan dan sebaliknya (lihat gambar). Karena saya menginginkan presisi 8-bit, saya mengaturnya ke 1 sehingga saya bisa membaca register ADCH saja dan mengabaikan ADCL.

Saya memutuskan untuk memiliki hanya satu saluran input untuk menghindari keharusan berpindah saluran bolak-balik di setiap konversi.

Satu hal terakhir tentang ADC, ia memiliki mode menjalankan berbeda masing-masing dengan sumber pemicu yang berbeda:
  • Mode Menjalankan Gratis
  • Pembanding Analog
  • Permintaan Interupsi Eksternal 0
  • Timer / Counter0 Bandingkan Pertandingan A
  • Timer / Counter0 Overflow
  • Timer / Counter1 Bandingkan Pertandingan B
  • Timer / Counter1 Overflow
  • Timer / Counter1 Capture Event
Saya tertarik pada mode berlari bebas yang merupakan mode di mana ADC terus-menerus mengubah input dan melemparkan Interupsi pada akhir setiap konversi (terkait vektor: ADC_vect).

Langkah 9: Buffer Input Digital

Pin input analog dari Arduino juga dapat digunakan sebagai pin I / O digital, oleh karena itu mereka memiliki buffer input untuk fungsi digital. Jika kami ingin menggunakannya sebagai pin analog, Anda harus menonaktifkan fitur ini.

Mengirim sinyal analog ke pin digital menginduksi untuk beralih antara status TINGGI dan RENDAH, terutama jika sinyal berada di dekat batas antara kedua negara; toggling ini menginduksi beberapa noise ke sirkuit dekat seperti ADC itu sendiri (dan menginduksi konsumsi energi yang lebih tinggi).

Untuk menonaktifkan buffer digital kita harus mengatur bit ADCnD dari register DIDR0:

sbi (DIDR0, ADC5D);
sbi (DIDR0, ADC4D);
sbi (DIDR0, ADC3D);
sbi (DIDR0, ADC2D);
sbi (DIDR0, ADC1D);
sbi (DIDR0, ADC0D);

Langkah 10: Menyiapkan ADC

Dalam sketsa, saya menulis fungsi inisialisasi yang mengatur semua parameter fungsi ADC. Karena saya cenderung menulis kode yang bersih dan berkomentar, saya hanya akan melewati fungsi di sini. Kami dapat merujuk pada langkah sebelumnya dan komentar untuk arti register. batal initADC (batal)
= (ADCPIN & 0x07);

// ------------------------------------------------ ---------------------
// pengaturan ADCSRA
// ------------------------------------------------ ---------------------
// Menulis bit ini ke satu memungkinkan ADC. Dengan menuliskannya ke nol,
// ADC dimatikan. Mematikan ADC saat konversi sedang berlangsung
// kemajuan, akan menghentikan konversi ini.
cbi (ADCSRA, ADEN);
// Dalam mode Konversi Tunggal, tulis bit ini menjadi satu untuk memulai masing-masing
// konversi. Dalam mode Free Running, tulis bit ini ke yang satu untuk memulai
// konversi pertama. Konversi pertama setelah ADSC telah ditulis
// setelah ADC diaktifkan, atau jika ADSC ditulis bersamaan
// waktu ketika ADC diaktifkan, alih-alih akan mengambil 25 siklus clock ADC
// normal 13. Konversi pertama ini melakukan inisialisasi dari
// ADC. ADSC akan membaca selama konversi sedang berlangsung.
// Ketika konversi selesai, ia kembali ke nol. Menulis nol hingga
// bit ini tidak berpengaruh.
cbi (ADCSRA, ADSC);
// Ketika bit ini ditulis ke salah satunya, Auto Triggering dari ADC adalah
// diaktifkan. ADC akan memulai konversi pada sisi positif
// sinyal pemicu yang dipilih. Sumber pemicu dipilih dengan menetapkan
// ADC Trigger Select bits, ADTS dalam ADCSRB.
sbi (ADCSRA, ADATE);
// Ketika bit ini ditulis ke satu dan I-bit di SREG diatur, the
// ADC Conversion Complete Interrupt diaktifkan.
sbi (ADCSRA, ADIE);
// Bit-bit ini menentukan faktor pembagian antara jam sistem
// frekuensi dan jam input ke ADC.
// ADPS2 ADPS1 ADPS0 Divisi Faktor
// 0 0 0 2
// 0 0 1 2
// 0 1 0 4
// 0 1 1 8
// 1 0 0 16
// 1 0 1 32
// 1 1 0 64
// 1 1 1 128
sbi (ADCSRA, ADPS2);
sbi (ADCSRA, ADPS1);
sbi (ADCSRA, ADPS0);

// ------------------------------------------------ ---------------------
// pengaturan ADCSRB
// ------------------------------------------------ ---------------------
// Ketika bit ini ditulis logika satu dan ADC dimatikan
// (ADEN dalam ADCSRA adalah nol), multiplekser ADC memilih yang negatif
// masukan ke Pembanding Analog. Ketika bit ini ditulis logika nol,
// AIN1 diterapkan pada input negatif dari Analog Comparator.
cbi (ADCSRB, ACME);
// Jika ADATE dalam ADCSRA ditulis ke satu, nilai bit ini
// pilih sumber mana yang akan memicu konversi ADC. Jika ADATE
// dihapus, pengaturan ADTS2: 0 tidak akan berpengaruh. Konversi akan
// dipicu oleh tepi naik Bendera Interrupt yang dipilih. Catatan
// bahwa beralih dari sumber pemicu yang dihapus ke pemicu
// sumber yang diatur, akan menghasilkan tepi positif pada pelatuk
// sinyal. Jika ADEN di ADCSRA diatur, ini akan memulai konversi.
// Beralih ke mode Free Running (ADTS [2: 0] = 0) tidak akan menyebabkan a
// memicu acara, bahkan jika Bendera Interupsi ADC diatur.
// ADTS2 ADTS1 ADTS0 Sumber pemicu
// 0 0 0 Mode Menjalankan Gratis
// 0 0 1 Pembanding Analog
// 0 1 0 Permintaan Interupsi Eksternal 0
// 0 1 1 Timer / Counter0 Bandingkan Pertandingan A
// 1 0 0 Timer / Counter0 Overflow
// 1 0 1 Timer / Counter1 Bandingkan Pertandingan B
// 1 1 0 Timer / Counter1 Overflow
// 1 1 1 Timer / Counter1 Acara Pengambilan
cbi (ADCSRB, ADTS2);
cbi (ADCSRB, ADTS1);
cbi (ADCSRB, ADTS0);

// ------------------------------------------------ ---------------------
// pengaturan DIDR0
// ------------------------------------------------ ---------------------
// Ketika bit ini dituliskan sebagai logika, buffer input digital pada
// pin ADC yang sesuai dinonaktifkan. Daftar PIN yang sesuai
// bit akan selalu dibaca sebagai nol ketika bit ini disetel. Ketika analog
// sinyal diterapkan pada pin ADC5..0 dan input digital dari ini
// pin tidak diperlukan, bit ini harus ditulis secara logis untuk mengurangi
// konsumsi daya dalam buffer input digital.
// Perhatikan bahwa pin ADC ADC7 dan ADC6 tidak memiliki buffer input digital,
// dan karenanya tidak memerlukan bit Input Digital Nonaktifkan.
sbi (DIDR0, ADC5D);
sbi (DIDR0, ADC4D);
sbi (DIDR0, ADC3D);
sbi (DIDR0, ADC2D);
sbi (DIDR0, ADC1D);
sbi (DIDR0, ADC0D);

Langkah 11: Bagaimana Pembanding Analog Bekerja

Analog Comparator adalah modul internal mikrokontroler dan membandingkan nilai input pada pin positif (Pin Digital 6) dan pin negatif (Pin Digital 7). Ketika tegangan pada pin positif lebih tinggi dari tegangan pada pin negatif AIN1, Analog Comparator menghasilkan 1 dalam bit ACO dari register ACSR.

Secara opsional, komparator dapat memicu interupsi, eksklusif untuk Pembanding Analog. Vektor terkait adalah ANALOG_COMP_vect.

Kita juga dapat mengatur interupsi untuk diluncurkan pada sisi naik, tepi jatuh atau pada sakelar negara.

Analog Comparator adalah apa yang kita butuhkan untuk memicu menghubungkan sinyal input ke pin 6, sekarang yang tersisa adalah level ambang batas pada pin 7.

Langkah 12: Menyiapkan Pembanding Analog

Dalam sketsa, saya menulis fungsi inisialisasi lain yang mengatur semua parameter fungsi Analog Comparator. Masalah yang sama tentang buffer digital ADC berlaku untuk Analog Comparator, seperti yang dapat kita lihat di bagian bawah rutin.

membatalkan initAnalogComparator (batal)
{
// ------------------------------------------------ ---------------------
// Pengaturan ACSR
// ------------------------------------------------ ---------------------
// Ketika bit ini ditulis logika satu, kekuatan untuk Analog
// Pembanding dimatikan. Bit ini dapat diset kapan saja untuk berbelok
// dari Analog Comparator. Ini akan mengurangi konsumsi daya dalam
// Mode aktif dan Idle. Saat mengubah bit ACD, Analog
// Comparator Interrupt harus dinonaktifkan dengan menghapus bit ACIE
// ACSR. Kalau tidak, interupsi dapat terjadi ketika bit diubah.
cbi (ACSR, ACD);
// Ketika bit ini diset, tegangan referensi celah pita tetap menggantikan
// input positif ke Pembanding Analog. Ketika bit ini dihapus,
// AIN0 diterapkan pada input positif dari Pembanding Analog. Kapan
// referensi bandgap digunakan sebagai input ke Analog Comparator, itu
// akan membutuhkan waktu tertentu untuk menstabilkan tegangan. Jika tidak
// distabilkan, konversi pertama mungkin memberikan nilai yang salah.
cbi (ACSR, ACBG);
// Ketika bit ACIE ditulis logika satu dan I-bit di Status
// Daftar diatur, interupsi Analog Comparator diaktifkan.
// Ketika logika nol ditulis, interupsi dinonaktifkan.
cbi (ACSR, ACIE);
// Ketika logika satu ditulis, bit ini mengaktifkan fungsi tangkap input
// di Timer / Counter1 yang akan dipicu oleh Analog Comparator. Itu
// output komparator dalam hal ini terhubung langsung ke input
// tangkap logika front-end, membuat komparator memanfaatkan noise
// fitur pembatal dan tepi pilih dari Timer / Counter1 Input
// Tangkap interupsi. Ketika logika ditulis nol, tidak ada koneksi antara
// Analog Comparator dan fungsi input capture ada. Untuk
// buat pembanding memicu Pencatat Input Timer / Counter1
// interupsi, ICIE1 sedikit di Timer Interrupt Mask Register
// (TIMSK1) harus disetel.
cbi (ACSR, ACIC);
// Bit-bit ini menentukan peristiwa komparator mana yang memicu Analog
// Gangguan komparator.
// Mode ACIS1 ACIS0
// 0 0 Beralih
// 0 1 Dicadangkan
// 1 0 Falling edge
// 1 1 Meningkat
sbi (ACSR, ACIS1);
sbi (ACSR, ACIS0);

// ------------------------------------------------ ---------------------
// pengaturan DIDR1
// ------------------------------------------------ ---------------------
// Ketika bit ini dituliskan sebagai logika, buffer input digital pada
// Pin AIN1 / 0 dinonaktifkan. Bit Register PIN yang sesuai akan
// selalu baca sebagai nol saat bit ini disetel. Ketika sinyal analog
// diterapkan pada pin AIN1 / 0 dan input digital dari pin ini tidak
// diperlukan, bit ini harus ditulis logika satu untuk mengurangi daya
// konsumsi dalam buffer input digital.
sbi (DIDR1, AIN1D);
sbi (DIDR1, AIN0D);
}

Langkah 13: Ambang

Mengingat apa yang kami katakan tentang pemicu, kami dapat menerapkan dua solusi ini untuk ambang:
  • menggunakan pemangkas kita dapat secara manual mengatur level tegangan;
  • menggunakan PWM dari Arduino kita dapat mengatur level dengan perangkat lunak.
Pada gambar kita dapat melihat implementasi perangkat keras dari ambang di kedua jalur.

Untuk pemilihan manual, potensiometer multi-putaran antara +5 V dan GND sudah cukup.

Sementara untuk pemilihan perangkat lunak kita membutuhkan filter low-pass yang menyaring sinyal PWM yang berasal dari Arduino. Sinyal PWM (lebih lanjut tentang ini untuk mengikuti) adalah sinyal kuadrat dengan frekuensi konstan tetapi lebar pulsa variabel. Keragaman ini membawa nilai rata-rata variabel dari sinyal yang dapat diekstraksi dengan filter low-pass. Frekuensi cutoff yang baik untuk filter adalah sekitar seperseratus dari frekuensi PWM dan saya memilih sekitar 560 Hz.

Setelah dua sumber ambang, saya menyisipkan beberapa pin yang memungkinkan untuk memilih, dengan jumper, sumber mana yang saya inginkan. Setelah pemilihan saya juga menambahkan pengikut emitor untuk memisahkan sumber dari pin Arduino.

Langkah 14: Bagaimana Modulasi Lebar Pulsa Bekerja

Seperti yang dinyatakan sebelumnya, sinyal Pulse Width Modulation (PWM) adalah sinyal persegi dengan frekuensi tetap tetapi lebar variabel. Pada gambar kita melihat contoh. Pada setiap baris ada salah satu dari sinyal tersebut dengan siklus tugas yang berbeda ( id adalah bagian periode di mana sinyal tinggi). Mengambil sinyal rata-rata selama suatu periode, kita mendapatkan garis merah yang sesuai dengan siklus tugas sehubungan dengan maksimum sinyal.

Secara elektronik "mengambil rata-rata sinyal" dapat diterjemahkan menjadi "meneruskannya ke filter low-pass", seperti yang terlihat pada langkah sebelumnya.

Bagaimana Arduino menghasilkan sinyal PWM? Ada tutorial yang sangat bagus tentang PWM di sini:
//arduino.cc/en/Tutorial/SecretsOfArduinoPWM
Kami akan melihat hanya poin-poin yang dibutuhkan untuk proyek ini.

Di ATMega328P ada tiga timer yang dapat digunakan untuk menghasilkan sinyal PWM, masing-masing memiliki karakteristik berbeda yang dapat Anda gunakan. Untuk masing-masing penghitung waktu berhubungan dengan dua register yang disebut Output Membandingkan Register A / B (OCRnx) yang digunakan untuk mengatur siklus tugas sinyal.

Adapun ADC ada prescaler (lihat gambar), yang memperlambat jam utama untuk memiliki kontrol yang tepat dari frekuensi PWM. Jam yang diperlambat diumpankan ke penghitung yang menambah Pengatur Waktu / Penghitung (TCNTn). Register ini secara terus menerus dibandingkan dengan OCRnx, ketika mereka sama dengan sinyal dikirim ke Generator Bentuk Gelombang yang menghasilkan pulsa pada pin output. Jadi triknya adalah mengatur register OCRnx ke beberapa nilai untuk mengubah nilai rata-rata sinyal.

Jika kita menginginkan sinyal 5 V (maksimum) kita harus menetapkan siklus tugas 100% atau 255 di OCRnx (maksimum untuk angka 8-bit), sedangkan jika kita menginginkan sinyal 0, 5 V kita harus menetapkan siklus tugas 10% atau 25 di OCRnx.

Karena jam harus mengisi register TCNTn sebelum memulai dari awal untuk pulsa baru, frekuensi output PWM adalah:

f = (Jam utama) / prescaler / (maksimum TCNTn)

exempli gratia untuk Timer 0 dan 2 (8-bit) tanpa prescaler akan menjadi: 16 MHz / 256 = 62, 5 KHz sedangkan untuk Timer 1 (16-bit) akan menjadi 16 MHz / 65536 = 244 Hz.

Saya memutuskan untuk menggunakan Timer nomor 2 karena
  • Timer 0 digunakan secara internal oleh Arduino IDE untuk fungsi seperti millis ();
  • Timer 1 memiliki frekuensi output terlalu lambat karena ini adalah timer 16-bit.

Di ATMega328P ada berbagai jenis mode operasi timer, tapi yang saya inginkan adalah Fast PWM yang tanpa prescaling untuk mendapatkan frekuensi output maksimum yang dimungkinkan.

Langkah 15: Menyiapkan PWM

Dalam sketsa, saya menulis fungsi inisialisasi lain yang mengatur semua parameter dari Timer berfungsi dan menginisialisasi beberapa pin. membatalkan initPins (batal)
{
// ------------------------------------------------ ---------------------
// pengaturan TCCR2A
// ------------------------------------------------ ---------------------
// Bit ini mengontrol perilaku Output Compare pin (OC2A). Jika satu atau
// kedua bit COM2A1: 0 diatur, output OC2A menimpa
// fungsionalitas port normal pin I / O yang terhubung dengannya.
// Namun, perhatikan bahwa Data Direction Register (DDR) sedikit
// sesuai dengan pin OC2A harus diatur untuk mengaktifkan
// driver output.
// Ketika OC2A terhubung ke pin, fungsi COM2A1: 0 bit
// tergantung pada pengaturan WGM22: 0 bit.
//
// Mode PWM Cepat
// COM2A1 COM2A0
// 0 0 Operasi port normal, OC2A terputus.
// 0 1 WGM22 = 0: Operasi Port Normal, OC0A Terputus.
// WGM22 = 1: Hidupkan OC2A di Match Match.
// 1 0 Bersihkan OC2A pada Bandingkan, atur OC2A di BOTTOM
// 1 1 Kosongkan OC2A pada Bandingkan, kosongkan OC2A di BOTTOM
cbi (TCCR2A, COM2A1);
cbi (TCCR2A, COM2A0);
sbi (TCCR2A, COM2B1);
cbi (TCCR2A, COM2B0);

// Dikombinasikan dengan bit WGM22 yang ditemukan di Register TCCR2B, bit-bit ini
// kontrol urutan penghitungan penghitung, sumber untuk maksimum
// (TOP) nilai penghitung, dan jenis pembangkit gelombang apa yang akan digunakan
// Mode operasi yang didukung oleh unit Timer / Counter adalah:
// - Mode normal (penghitung),
// - Hapus Timer pada mode Bandingkan Pertandingan (CTC),
// - dua jenis mode Pulse Width Modulation (PWM).
//
// Mode WGM22 WGM21 WGM20 Operasi TOP
// 0 0 0 0 0xFF normal
// 1 0 0 1 PWM 0xFF
// 2 0 1 0 CTC OCRA
// 3 0 1 1 PWM cepat 0xFF
// 4 1 0 0 Dicadangkan -
// 5 1 0 1 PWM OCRA
// 6 1 1 0 Dicadangkan -
// 7 1 1 1 PWM OCRA cepat
cbi (TCCR2B, WGM22);
sbi (TCCR2A, WGM21);
sbi (TCCR2A, WGM20);

// ------------------------------------------------ ---------------------
// pengaturan TCCR2B
// ------------------------------------------------ ---------------------
// Bit FOC2A hanya aktif ketika bit WGM menentukan non-PWM
// mode.
// Namun, untuk memastikan kompatibilitas dengan perangkat masa depan, ini sedikit
// harus disetel ke nol saat TCCR2B ditulis saat beroperasi di PWM
// mode. Saat menulis yang logis ke bit FOC2A, langsung
// Perbandingan Match dipaksa pada unit Waveform Generation. OC2A
// output diubah sesuai dengan pengaturan COM2A1: 0 bit-nya. Catat itu
// bit FOC2A diimplementasikan sebagai strobo. Karena itu nilainya
// hadir dalam COM2A1: 0 bit yang menentukan efek dari
// dipaksa membandingkan.
// Strobo FOC2A tidak akan menghasilkan interupsi, juga tidak akan jernih
// timer dalam mode CTC menggunakan OCR2A sebagai TOP.
// Bit FOC2A selalu dibaca sebagai nol.
cbi (TCCR2B, FOC2A);
cbi (TCCR2B, FOC2B);

// Tiga Clock Select bits pilih sumber jam yang akan digunakan
// Timer / Penghitung.
// CS22 CS21 CS20 Prescaler
// 0 0 0 Tidak ada sumber jam (Timer / Penghitung berhenti).
// 0 0 1 Tidak ada prescaling
// 0 1 0 8
// 0 1 1 32
// 1 0 0 64
// 1 0 1 128
// 1 1 0 256
// 1 1 1 1024
cbi (TCCR2B, CS22);
cbi (TCCR2B, CS21);
sbi (TCCR2B, CS20);

pinMode (errorPin, OUTPUT);
pinMode (thresholdPin, OUTPUT);

analogWrite (thresholdPin, 127);
}

Langkah 16: Variabel Volatile

Saya tidak ingat di mana, tetapi saya membaca bahwa variabel yang dimodifikasi di dalam ISR harus dinyatakan sebagai volatile .

Variabel volatil adalah variabel yang dapat berubah selama waktu, bahkan jika program yang sedang berjalan tidak memodifikasinya. Sama seperti register Arduino yang dapat mengubah nilai untuk beberapa intervensi eksternal.

Mengapa kompiler ingin tahu tentang variabel seperti itu? Itu karena kompiler selalu mencoba untuk mengoptimalkan kode yang kita tulis, membuatnya lebih cepat, dan memodifikasinya sedikit, berusaha untuk tidak mengubah artinya. Jika suatu variabel berubah dengan sendirinya, sepertinya kompiler itu tidak pernah dimodifikasi selama eksekusi, katakanlah, sebuah loop dan ia dapat mengabaikannya; sementara itu bisa menjadi penting bahwa variabel mengubah nilainya. Jadi mendeklarasikan variabel volatil itu mencegah kompiler untuk memodifikasi kode mengenai mereka.

Untuk informasi lebih lanjut, saya sarankan untuk membaca halaman Wikipedia: //en.wikipedia.org/wiki/Volatile_variable

Langkah 17: Menulis Kernel Sketsa

Akhirnya kita sampai pada inti program!

Seperti yang kita lihat sebelumnya, saya ingin akuisisi berkelanjutan dan saya menulis ADC Interrupt Service Routine untuk menyimpan dalam buffer melingkar data secara terus menerus. Itu berhenti setiap kali mencapai indeks yang sama dengan stopIndex. Buffer diimplementasikan sebagai bundar menggunakan operator modulo.

// ------------------------------------------------ -----------------------------
// Konversi Lengkap ADC
// ------------------------------------------------ -----------------------------
ISR (ADC_vect)
{
// Ketika ADCL dibaca, Register Data ADC tidak diperbarui hingga ADCH
// adalah membaca. Akibatnya, jika hasilnya dibiarkan disesuaikan dan tidak lebih
// Dibutuhkan presisi 8-bit, cukup untuk membaca ADCH.
// Kalau tidak, ADCL harus dibaca dulu, lalu ADCH.
ADCBuffer [ADCCounter] = ADCH;

ADCCounter = (ADCCounter + 1)% ADCBUFFERSIZE;

jika (tunggu)
{
if (stopIndex == ADCCounter)
{
// Situasi beku
// Nonaktifkan ADC dan hentikan Free Conversion Mode
cbi (ADCSRA, ADEN);

diam = benar;
}
}
}

Analog Comparator Interrupt Service Rutin (yang disebut ketika sinyal melewati ambang batas) menonaktifkan itu sendiri dan memberitahu ADC ISR untuk memulai fase menunggu dan mengatur stopIndex.

// ------------------------------------------------ -----------------------------
// Analog Comparator interrupt
// ------------------------------------------------ -----------------------------
ISR (ANALOG_COMP_vect)
{
// Nonaktifkan interupsi Analog Comparator
cbi (ACSR, ACIE);

// Nyalakan errorPin
// digitalWrite (errorPin, HIGH);
sbi (PORTB, PORTB5);

tunggu = benar;
stopIndex = (ADCCounter + waitDuration)% ADCBUFFERSIZE;
}


Ini benar-benar mudah setelah semua landasan itu!

Langkah 18: Membentuk Sinyal Masuk

Mari kita pergi ke perangkat keras sekarang. Rangkaian mungkin terlihat rumit tetapi sangat sederhana.
  • Ada resistor 1 MΩ pada input, untuk memberikan referensi massa pada sinyal dan memiliki input impedansi tinggi. Impedansi tinggi "mensimulasikan" sirkuit terbuka jika Anda menghubungkannya ke impedansi lebih rendah, sehingga keberadaan Girino tidak terlalu mengacaukan sirkuit yang ingin Anda ukur.
  • Setelah resistor ada pengikut emitor untuk memisahkan sinyal dan melindungi elektronik berikut.
  • Ada offset sederhana yang menghasilkan level 2, 5 V dengan pembagi tegangan. Itu terpasang ke kapasitor untuk menstabilkannya.
  • Ada penguat jumlah non-pembalik yang menjumlahkan sinyal masuk dan offset. Saya menggunakan teknik ini karena saya ingin dapat melihat juga sinyal negatif, karena Arduino ADC hanya dapat melihat sinyal antara 0 V hingga 5 V.
  • Setelah penjumlahan-amp ada pengikut emitor lain.
  • Jumper memungkinkan kita memutuskan apakah kita ingin memberi makan sinyal dengan offset atau tidak.
Penguat Operasional yang ingin saya gunakan adalah LM324 yang dapat bekerja antara 0 V hingga 5 V tetapi juga antara, katakanlah, -12 V hingga 12 V. Ini memberi kami lebih banyak kemungkinan dengan catu daya. Saya juga mencoba TL084 yang jauh lebih cepat daripada LM324 tetapi membutuhkan catu daya ganda. Keduanya sama-sama memiliki pinout yang sama sehingga dapat diubah tanpa modifikasi sirkuit.

Langkah 19: Bypass Capacitors

Bypass Capacitors adalah kapasitor yang digunakan untuk menyaring catu daya Integrated Circuits (IC) dan harus diletakkan sedekat mungkin ke pin alimentasi IC. Mereka biasanya digunakan dalam pasangan, satu keramik dan satu elektrolit karena mereka dapat menyaring frekuensi yang berbeda.

Langkah 20: Sumber Daya

Saya menggunakan catu daya ganda untuk TL084 yang dapat dikonversi ke catu daya tunggal untuk LM324.

Pada gambar kita dapat melihat bahwa saya menggunakan beberapa regulator tegangan 7812, untuk +12 V, dan 7912, untuk -12 V. Kapasitor, seperti biasa, digunakan untuk menstabilkan level dan nilainya seperti yang disarankan di lembar data.

Jelas untuk memiliki ± 12 V kita harus memiliki setidaknya sekitar 30 V pada input karena regulator tegangan memerlukan input yang lebih tinggi untuk memberikan output yang stabil. Karena saya tidak memiliki catu daya seperti itu, saya menggunakan trik menggunakan dua catu daya 15 V secara seri. Salah satu dari keduanya terhubung ke konektor daya Arduino (jadi itu memberi makan Arduino dan sirkuit saya) dan yang lainnya langsung ke sirkuit.

Ini bukan kesalahan untuk menghubungkan +15 V catu daya kedua ke GND yang pertama! Inilah cara kami mendapatkan -15 V dengan catu daya terisolasi .

Jika saya tidak ingin membawa sekitar Arduino dan dua catu daya saya masih bisa menggunakan +5 V yang disediakan oleh Arduino mengubah jumper-jumper tersebut (dan menggunakan LM324).

Langkah 21: Mempersiapkan Konektor Perisai

Saya selalu terganggu oleh konektor yang saya temukan untuk membuat perisai Arduino, karena mereka selalu memiliki pin yang terlalu pendek dan papan yang saya gunakan dapat disolder hanya di satu sisi. Jadi saya membuat sedikit trik untuk membuat pin lebih lama sehingga bisa disolder dan dimasukkan ke dalam Arduino.

Memasukkan pin strip ke papan, seperti pada gambar, kita bisa mendorong pin, untuk memilikinya hanya di satu sisi plastik hitam. Kemudian kita bisa menyoldernya di sisi yang sama di mana mereka akan dimasukkan ke dalam Arduino.

Langkah 22: Menyolder dan Menguji

Saya tidak dapat menunjukkan kepada Anda semua prosedur penyolderan sirkuit karena itu mengalami banyak percobaan dan kesalahan. Pada akhirnya menjadi sedikit berantakan tetapi tidak terlalu buruk, meskipun saya tidak akan menunjukkan bagian bawahnya karena itu benar-benar berantakan.

Pada tahap ini tidak banyak yang bisa dikatakan karena saya sudah menjelaskan secara rinci semua bagian sirkuit. Saya mengujinya dengan osiloskop, bahwa seorang teman meminjam saya, untuk melihat sinyal di setiap titik sirkuit. Tampaknya semuanya bekerja dengan baik dan saya cukup puas.

Konektor untuk sinyal yang masuk bisa terasa sedikit aneh bagi seseorang yang tidak berasal dari Fisika Energi Tinggi, itu adalah konektor LEMO. Ini adalah konektor standar untuk sinyal nuklir, setidaknya di Eropa seperti di Amerika Serikat saya telah melihat sebagian besar konektor BNC.

Langkah 23: Sinyal Uji

Untuk menguji sirkuit dan Data AcQuisition (DAQ) saya menggunakan Arduino kedua dengan sketsa sederhana yang menghasilkan pulsa persegi dengan panjang yang berbeda. Saya juga menulis skrip python yang berbicara dengan Girino dan menyuruhnya untuk mendapatkan beberapa seri data dan menyimpan salah satunya ke file.
Keduanya melekat pada langkah ini.

Lampiran

  • Unduh TaraturaTempi.ino
  • readgirino.py Unduh

Langkah 24: Kalibrasi Waktu

Dengan menggunakan sinyal uji, saya mengkalibrasi skala horizontal plot. Dengan mengukur lebar pulsa (yang dikenal karena dihasilkan) dan memplot lebar pulsa yang diukur terhadap nilai-nilai yang diketahui, kami mendapatkan plot linear yang diharapkan. Melakukan ini untuk setiap pengaturan prescaler kami memiliki kalibrasi waktu untuk semua tingkat akuisisi.

Pada gambar kita dapat melihat semua data yang saya ambil dianalisis. Plot "Fitted slopes" adalah yang paling menarik karena memberitahu kami tingkat akuisisi aktual sistem saya di setiap pengaturan prescaler. Kemiringan diukur sebagai angka [ch / ms] tetapi ini setara dengan [kHz], sehingga nilai kemiringan sebenarnya kHz atau juga kS / s (kilo Sampel per detik). Itu berarti bahwa dengan prescaler yang ditetapkan ke 8 kita mendapatkan tingkat akuisisi:

(154 ± 2) kS / s

Tidak buruk, eh?

Sedangkan dari plot "Fitted y-intercepts" kita mendapatkan wawasan tentang linearitas sistem. Semua intersep y harus nol karena pada sinyal dengan panjang nol harus sesuai dengan pulsa dengan panjang nol. Seperti yang dapat kita lihat pada grafik, mereka semua kompatibel dengan nol, tetapi bukan dataset 18-prescaler. Dataset ini, bagaimanapun, adalah yang terburuk karena hanya memiliki dua data dan kalibrasi tidak dapat dipercaya.

Berikut ini adalah tabel dengan tingkat akuisisi untuk setiap pengaturan prescaler.

PrescalerTingkat akuisisi [kS / s]
1289, 74 ± 0, 04
6419, 39 ± 0, 06
3237, 3 ± 0, 6
1675, 5 ± 0, 3
8153 ± 2
Kesalahan yang dikutip berasal dari mesin fit Gnuplot dan saya tidak yakin tentang mereka.

Saya juga mencoba angka yang tidak tertimbang karena Anda dapat melihat bahwa mereka kira-kira dua kali lipat ketika prescaling membagi dua, ini terlihat seperti hukum proporsionalitas terbalik. Jadi saya memasang tarif vs pengaturan prescaler dengan hukum sederhana

y = a / x

Saya mendapat nilai untuk dari

a = 1223

dengan χ² = 3, 14 dan 4 derajat kebebasan, ini berarti bahwa hukum tersebut diterima dengan tingkat kepercayaan 95%!

Langkah 25: Selesai! (Hampir)

Di akhir pengalaman panjang ini, saya merasa sangat puas karena
  • Saya belajar banyak tentang mikrokontroler secara umum;
  • Saya belajar lebih banyak tentang Arduino ATMega328P;
  • Saya memiliki pengalaman langsung tentang Akuisisi Data, bukan dengan menggunakan sesuatu yang sudah dilakukan tetapi dengan membuat sesuatu;
  • Saya menyadari osiloskop amatir yang tidak terlalu buruk.
Saya harap panduan ini bermanfaat bagi siapa saja yang membacanya. Saya ingin menulisnya dengan sangat rinci karena saya mempelajari semua itu dengan cara yang sulit (menjelajahi internet, membaca lembar data, dan melalui banyak trial and error) dan saya ingin menyisihkan siapa pun dari pengalaman itu.

Langkah 26: Bersambung ...

Proyek jika jauh dari selesai. Yang hilang adalah:
  1. Tes dengan sinyal analog yang berbeda (saya kehilangan generator sinyal analog);
  2. Antarmuka Pengguna Grafis untuk sisi komputer.
Sedangkan untuk poin 1. Saya tidak yakin kapan akan selesai, karena saya tidak berencana untuk membeli / membangunnya dalam waktu dekat.

Untuk poin 2. situasinya bisa lebih baik. Adakah yang mau membantu saya dengan itu? Saya menemukan Oscilloscope Python yang bagus di sini:
//www.phy.uct.ac.za/courses/python/examples/moreexamples.html#oscilloscope-and-spectrum-analyser
Saya ingin memodifikasinya agar sesuai untuk Girino, tetapi saya menerima saran.

Artikel Terkait