Baru-baru ini, seorang teman meminta saya untuk membantu dengan satu tugas: untuk mengontrol komputer dengan pemutar audio yang diinstal pada laptop Windows menggunakan remote perangkat keras kecil. Saya meminta segala macam remote IR untuk tidak menawarkan. Dan untuk membuat AVR-e, di mana ia memiliki sejumlah besar yang tersisa, perlu untuk melampirkan perlahan.
Pernyataan masalah
Tugas, jelas, dibagi menjadi dua bagian:
- Perangkat keras mikrokontroler, dan
- Perangkat lunak yang berjalan di komputer dan mengontrol apa yang ada di dalamnya.
Karena kami bekerja dengan AVR, lalu mengapa tidak Arduino?
Kami menimbulkan masalah.
Platform perangkat keras:
HW1. Manajemen dilakukan oleh tombol tanpa memperbaiki;
HW2. Kami melayani 3 tombol (secara umum, berapa banyak yang tidak keberatan);
HW3. Menekan dianggap menahan tombol setidaknya 100 milidetik;
HW4. Penekanan yang lebih lama diabaikan. Memproses lebih dari 1 tombol pada satu waktu tidak dilakukan;
HW5. Saat tombol ditekan, tindakan tertentu diluncurkan pada komputer;
HW6. Menyediakan antarmuka komunikasi dengan komputer melalui Serial / USB-converter bawaan;
Platform perangkat lunak:
SW1. Menyediakan antarmuka komunikasi dengan komputer melalui port serial yang dapat dipilih;
SW2. Ubah perintah yang datang melalui antarmuka komunikasi ke acara-acara sistem operasi yang dikirimkan ke pemutar audio yang diinginkan.
SW3. Jeda pemrosesan perintah. Termasuk perintah dari remote control.
Nah, ada persyaratan tambahan: jika ini tidak menimbulkan investasi waktu yang serius, buat solusinya senyaman mungkin.
Desain dan Solusi
Hw1
Tombol-tombol tombol tetap pada posisi "ditekan" untuk waktu yang singkat. Selain itu, tombol dapat berdetak (yaitu, menghasilkan banyak pemicu dalam waktu singkat karena kontak yang tidak stabil).
Tidak masuk akal untuk menghubungkan mereka ke interupsi - waktu respons yang salah diperlukan untuk mengganggunya. Kami akan membaca status mereka dari pin digital. Untuk memastikan pembacaan yang stabil dari tombol dalam kondisi tidak tertekan, perlu menghubungkan pin input ke ground (pull-down) atau ke power (pull-up) melalui resistor pull-up. Menggunakan resistor pull-up bawaan, kami tidak akan membuat elemen diskrit tambahan di sirkuit. Di satu sisi kami menghubungkan tombol ke input kami, yang lain - ke tanah. Inilah hasilnya:

Dan - untuk setiap tombol.
Hw2
Ada beberapa tombol, jadi kita perlu sejumlah catatan seragam tentang cara polling tombol dan apa yang harus dilakukan jika ditekan. Kami melihat ke arah enkapsulasi dan membuat kelas Button
dari Button
, yang berisi jumlah pin dari mana survei sedang dilakukan (dan itu sendiri menginisialisasi itu), dan perintah yang harus dikirim ke port. Kami akan membahas seperti apa tim nanti.
Kelas tombol akan terlihat seperti ini:
Kode Kelas Tombol class Button { public: Button(uint8_t pin, ::Command command) : pin(pin), command(command) {} void Begin() { pinMode(pin, INPUT); digitalWrite(pin, 1); } bool IsPressed() { return !digitalRead(pin); } ::Command Command() const { return command; } private: uint8_t pin; ::Command command; };
Setelah langkah ini, tombol kami menjadi universal dan tanpa wajah, tetapi Anda dapat bekerja dengan mereka dengan cara yang sama.
Letakkan tombol bersama dan berikan pin:
Button buttons[] = { Button(A0, Command::Previous), Button(A1, Command::PauseResume), Button(A2, Command::Next), };
Inisialisasi semua tombol dilakukan dengan memanggil metode Begin()
untuk setiap tombol:
for (auto &button : buttons) { button.Begin(); }
Untuk menentukan tombol mana yang ditekan, kami akan beralih di atas tombol dan memeriksa apakah ada yang ditekan. Kami mengembalikan indeks tombol, atau salah satu nilai khusus: "tidak ada yang ditekan" dan "lebih dari satu tombol ditekan." Nilai khusus, tentu saja, tidak bisa tumpang tindih dengan nomor tombol yang valid.
GetPressed () int GetPressed() { int index = PressedNothing; for (byte i = 0; i < ButtonsCount; ++i) { if (buttons[i].IsPressed()) { if (index == PressedNothing) { index = i; } else { return PressedMultiple; } } } return index; }
Hw3
Tombol-tombol akan disurvei dengan periode tertentu (katakanlah, 10 ms), dan kami akan menganggap bahwa penekanan terjadi jika tombol yang sama (dan tepat satu) ditahan untuk sejumlah siklus polling tertentu. Membagi waktu fiksasi (100 ms) dengan periode pemungutan suara (10 ms), kita dapatkan 10.
Kami akan memulai penghitung penurunan, di mana kami menulis 10 pada fiksasi pertama penekanan, dan pengurangan pada setiap periode. Begitu mulai dari 1 hingga 0, kami mulai memproses (lihat HW5)
Hw4
Jika penghitung sudah 0, tidak ada tindakan yang diambil.
Hw5
Seperti disebutkan di atas, perintah yang dapat dieksekusi dikaitkan dengan setiap tombol. Itu harus ditransmisikan melalui antarmuka komunikasi.
Pada tahap ini, Anda dapat menerapkan strategi keyboard.
Implementasi loop utama void HandleButtons() { static int CurrentButton = PressedNothing; static byte counter; int button = GetPressed(); if (button == PressedMultiple || button == PressedNothing) { CurrentButton = button; counter = -1; return; } if (button == CurrentButton) { if (counter > 0) { if (--counter == 0) { InvokeCommand(buttons[button]); return; } } } else { CurrentButton = button; counter = PressInterval / TickPeriod; } } void loop() { HandleButtons(); delay(TickPeriod); }
Hw6
Antarmuka komunikasi harus jelas untuk pengirim dan penerima. Karena antarmuka serial memiliki unit transfer data 1 byte dan sinkronisasi byte, tidak masuk akal untuk memagari sesuatu yang rumit dan membatasi diri untuk mentransmisikan satu byte per perintah. Untuk kenyamanan debugging, kami akan mentransfer satu karakter ASCII per perintah.
Implementasi Arduino
Sekarang kita kumpulkan. Kode implementasi lengkap ditunjukkan di bawah ini di bawah spoiler. Untuk meluaskannya, cukup menentukan kode ASCII dari perintah baru dan melampirkan sebuah tombol padanya.
Tentu saja, akan mungkin hanya untuk secara eksplisit menunjukkan kode simbol untuk setiap tombol, tetapi kami tidak akan melakukan ini: penamaan perintah akan berguna bagi kami ketika mengimplementasikan klien untuk PC.
Implementasi penuh const int TickPeriod = 10;
Dan ya, saya membuat tombol lain untuk dapat menghentikan sementara transfer perintah ke klien.
Klien untuk PC
Kami lolos ke bagian kedua.
Karena kami tidak memerlukan antarmuka yang kompleks, dan mengikat ke Windows, kami dapat pergi dengan cara yang berbeda, seperti yang Anda suka: WinAPI, MFC, Delphi, .NET (Windows Forms, WPF, dll.), Atau konsol pada platform yang sama ( baik, kecuali untuk MFC).
SW1
Persyaratan ini ditutup melalui komunikasi dengan port serial pada platform perangkat lunak yang dipilih: sambungkan ke port, baca byte, byte proses.
SW2
Mungkin semua orang melihat keyboard dengan tombol multimedia. Setiap tombol pada keyboard, termasuk yang multimedia, memiliki kode sendiri. Solusi paling sederhana untuk masalah kami adalah mensimulasikan penekanan tombol multimedia pada keyboard. Kode kunci dapat ditemukan di sumber asli - MSDN . Masih mempelajari cara mengirim mereka ke sistem. Ini juga tidak sulit: ada fungsi SendInput di WinAPI.
Setiap keystroke adalah dua peristiwa: menekan dan melepaskan.
Jika kita menggunakan C / C ++, maka kita cukup memasukkan file header. Dalam bahasa lain, penerusan panggilan harus dilakukan. Jadi, misalnya, ketika mengembangkan .NET Anda harus mengimpor fungsi yang ditentukan dan menjelaskan argumen. Saya memilih .NET untuk kenyamanan mengembangkan antarmuka.
Saya memilih dari proyek hanya bagian substantif, yang bermuara pada satu kelas: Internals
.
Ini kodenya:
Kode internal kelas internal class Internals { [StructLayout(LayoutKind.Sequential)] [DebuggerDisplay("{Type} {Data}")] private struct INPUT { public uint Type; public KEYBDINPUT Data; public const uint Keyboard = 1; public static readonly int Size = Marshal.SizeOf(typeof(INPUT)); } [StructLayout(LayoutKind.Sequential)] [DebuggerDisplay("Vk={Vk} Scan={Scan} Flags={Flags} Time={Time} ExtraInfo={ExtraInfo}")] private struct KEYBDINPUT { public ushort Vk; public ushort Scan; public uint Flags; public uint Time; public IntPtr ExtraInfo; private long spare; } [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure); private static INPUT[] inputs = { new INPUT { Type = INPUT.Keyboard, Data = { Flags = 0
Pertama, ini menggambarkan struktur data (hanya yang terkait dengan input keyboard terputus, karena kami mensimulasikannya), dan SendInput
mengimpor SendInput
.
Bidang inputs
adalah array dari dua elemen yang akan digunakan untuk menghasilkan acara keyboard. Tidak masuk akal untuk mengalokasikannya secara dinamis jika arsitektur aplikasi berasumsi bahwa SendKey
tidak akan SendKey
dalam banyak utas.
Sebenarnya, masalah teknis lebih lanjut: kita mengisi bidang struktur yang sesuai dengan kode kunci virtual dan mengirimkannya ke antrian masukan sistem operasi.
SW3
Persyaratan ditutup sangat sederhana. Bendera dinaikkan dan perintah lain diproses dengan cara khusus: bendera beralih ke keadaan logis yang berlawanan. Jika diatur, maka perintah yang tersisa diabaikan.
Alih-alih sebuah kesimpulan
Peningkatan bisa dilakukan tanpa akhir, tapi itu cerita lain. Saya tidak menyajikan proyek klien Windows di sini, karena ini memberikan imajinasi yang luas.
Untuk mengontrol pemutar media, kami mengirim satu set โpenekanan tombolโ tombol, jika Anda perlu mengelola presentasi, yang lain. Anda dapat membuat modul kontrol, merakitnya baik secara statis atau sebagai plug-in. Secara umum, banyak hal yang mungkin. Yang utama adalah keinginan.
Terima kasih atas perhatian anda