DevBoy: membuat generator sinyal

Halo teman-teman!

Dalam artikel sebelumnya, saya berbicara tentang proyek saya dan bagian perangkat lunaknya . Pada artikel ini saya akan memberi tahu Anda cara membuat generator sinyal sederhana untuk 4 saluran - dua saluran analog dan dua saluran PWM.



Saluran analog


Mikrokontroler STM32F415RG menggabungkan 12-bit DAC (digital-to-analog) converter menjadi dua saluran independen, yang memungkinkan menghasilkan sinyal yang berbeda. Anda dapat langsung memuat data ke register konverter, tetapi ini tidak terlalu cocok untuk menghasilkan sinyal. Solusi terbaik adalah menggunakan array yang digunakan untuk menghasilkan satu gelombang sinyal, dan kemudian jalankan DAC dengan pemicu dari timer dan DMA. Dengan mengubah frekuensi timer, Anda dapat mengubah frekuensi sinyal yang dihasilkan.

Bentuk gelombang " Klasik " meliputi: sinusoidal, berliku-liku, segitiga dan gigi gergaji.

gambar

Fungsi menghasilkan gelombang ini di buffer adalah sebagai berikut
// ***************************************************************************** // *** GenerateWave ******************************************************** // ***************************************************************************** Result Application::GenerateWave(uint16_t* dac_data, uint32_t dac_data_cnt, uint8_t duty, WaveformType waveform) { Result result; uint32_t max_val = (DAC_MAX_VAL * duty) / 100U; uint32_t shift = (DAC_MAX_VAL - max_val) / 2U; switch(waveform) { case WAVEFORM_SINE: for(uint32_t i = 0U; i < dac_data_cnt; i++) { dac_data[i] = (uint16_t)((sin((2.0F * i * PI) / (dac_data_cnt + 1)) + 1.0F) * max_val) >> 1U; dac_data[i] += shift; } break; case WAVEFORM_TRIANGLE: for(uint32_t i = 0U; i < dac_data_cnt; i++) { if(i <= dac_data_cnt / 2U) { dac_data[i] = (max_val * i) / (dac_data_cnt / 2U); } else { dac_data[i] = (max_val * (dac_data_cnt - i)) / (dac_data_cnt / 2U); } dac_data[i] += shift; } break; case WAVEFORM_SAWTOOTH: for(uint32_t i = 0U; i < dac_data_cnt; i++) { dac_data[i] = (max_val * i) / (dac_data_cnt - 1U); dac_data[i] += shift; } break; case WAVEFORM_SQUARE: for(uint32_t i = 0U; i < dac_data_cnt; i++) { dac_data[i] = (i < dac_data_cnt / 2U) ? max_val : 0x000; dac_data[i] += shift; } break; default: result = Result::ERR_BAD_PARAMETER; break; } return result; } 

Dalam fungsinya, Anda perlu meneruskan pointer ke awal array, ukuran array, nilai maksimum dan bentuk gelombang yang diinginkan. Setelah panggilan, array akan diisi dengan sampel untuk satu gelombang dari bentuk yang diperlukan dan Anda dapat memulai timer untuk secara berkala memuat nilai baru ke dalam DAC.

DAC dalam mikrokontroler ini memiliki batasan: waktu penyelesaian yang khas ( waktu dari memuat nilai baru ke DAC dan ketika muncul pada output ) adalah 3 ms. Tapi tidak semuanya begitu sederhana - kali ini maksimum, mis. berubah dari minimum ke maksimum dan sebaliknya. Ketika mencoba menarik keluar berliku-liku, bidang-bidang yang berserakan ini sangat jelas terlihat:



Jika gelombang sinusoidal dihasilkan, maka obstruksi front tidak lagi terlihat karena bentuk gelombang. Namun, jika frekuensinya meningkat, sinyal sinusoidal berubah menjadi segitiga, dan dengan peningkatan lebih lanjut, amplitudo sinyal berkurang.

Generasi 1 KHz ( amplitudo 90% ):



Generasi 10 KHz ( amplitudo 90% ):



Generasi pada 100 KHz ( amplitudo 90% ):



Langkah-langkahnya sudah terlihat - karena data baru dimuat ke DAC pada frekuensi 4 MHz.

Selain itu, tepi jejak sinyal gigi gergaji berantakan dan dari bawah sinyal tidak mencapai nilai yang seharusnya. Ini karena sinyal tidak punya waktu untuk mencapai level rendah yang ditentukan, dan perangkat lunak memuat nilai-nilai baru

Generasi pada 200 KHz ( amplitudo 90% ):



Di sini Anda sudah bisa melihat bagaimana semua gelombang berubah menjadi segitiga.

Saluran digital


Dengan saluran digital, semuanya jauh lebih sederhana - di hampir semua mikrokontroler ada timer yang memungkinkan Anda untuk mengeluarkan sinyal PWM ke output mikrokontroler. Cara terbaik adalah menggunakan timer 32-bit - dalam hal ini, Anda tidak perlu menghitung timer pra-timer, cukup memuat periode dalam satu register dan memuat siklus tugas yang diperlukan dalam register lain.

Antarmuka pengguna


Diputuskan untuk mengatur antarmuka pengguna menjadi empat persegi panjang, masing-masing dengan gambar sinyal output, frekuensi dan siklus amplitudo / tugas. Untuk saluran yang saat ini dipilih, data teks ditampilkan dalam warna putih, sisanya berwarna abu-abu.



Diputuskan untuk melakukan kontrol terhadap enkoder: yang kiri bertanggung jawab atas frekuensi dan saluran yang dipilih saat ini ( berubah ketika tombol ditekan ), yang kanan bertanggung jawab atas siklus amplitudo / tugas dan bentuk gelombang ( perubahan ketika tombol ditekan ).

Selain itu, dukungan untuk layar sentuh diterapkan - ketika Anda mengklik pada saluran yang tidak aktif, itu menjadi aktif, ketika Anda mengklik pada saluran yang aktif, bentuk gelombang berubah.

Tentu saja, DevCore terbiasa melakukan semua ini. Kode untuk menginisialisasi antarmuka pengguna dan memperbarui data di layar terlihat seperti ini:

Struktur yang berisi semua objek UI
  // ************************************************************************* // *** Structure for describes all visual elements for the channel ***** // ************************************************************************* struct ChannelDescriptionType { // UI data UiButton box; Image img; String freq_str; String duty_str; char freq_str_data[64] = {0}; char duty_str_data[64] = {0}; // Generator data ... }; // Visual channel descriptions ChannelDescriptionType ch_dsc[CHANNEL_CNT]; 
Kode inisialisasi antarmuka pengguna
  // Create and show UI int32_t half_scr_w = display_drv.GetScreenW() / 2; int32_t half_scr_h = display_drv.GetScreenH() / 2; for(uint32_t i = 0U; i < CHANNEL_CNT; i++) { // Generator data ... // UI data int32_t start_pos_x = half_scr_w * (i%2); int32_t start_pos_y = half_scr_h * (i/2); ch_dsc[i].box.SetParams(nullptr, start_pos_x, start_pos_y, half_scr_w, half_scr_h, true); ch_dsc[i].box.SetCallback(&Callback, this, nullptr, i); ch_dsc[i].freq_str.SetParams(ch_dsc[i].freq_str_data, start_pos_x + 4, start_pos_y + 64, COLOR_LIGHTGREY, String::FONT_8x12); ch_dsc[i].duty_str.SetParams(ch_dsc[i].duty_str_data, start_pos_x + 4, start_pos_y + 64 + 12, COLOR_LIGHTGREY, String::FONT_8x12); ch_dsc[i].img.SetImage(waveforms[ch_dsc[i].waveform]); ch_dsc[i].img.Move(start_pos_x + 4, start_pos_y + 4); ch_dsc[i].box.Show(1); ch_dsc[i].img.Show(2); ch_dsc[i].freq_str.Show(3); ch_dsc[i].duty_str.Show(3); } 
Kode pembaruan layar
  for(uint32_t i = 0U; i < CHANNEL_CNT; i++) { ch_dsc[i].img.SetImage(waveforms[ch_dsc[i].waveform]); snprintf(ch_dsc[i].freq_str_data, NumberOf(ch_dsc[i].freq_str_data), "Freq: %7lu Hz", ch_dsc[i].frequency); if(IsAnalogChannel(i)) snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Ampl: %7d %%", ch_dsc[i].duty); else snprintf(ch_dsc[i].duty_str_data, NumberOf(ch_dsc[i].duty_str_data), "Duty: %7d %%", ch_dsc[i].duty); // Set gray color to all channels ch_dsc[i].freq_str.SetColor(COLOR_LIGHTGREY); ch_dsc[i].duty_str.SetColor(COLOR_LIGHTGREY); } // Set white color to selected channel ch_dsc[channel].freq_str.SetColor(COLOR_WHITE); ch_dsc[channel].duty_str.SetColor(COLOR_WHITE); // Update display display_drv.UpdateDisplay(); 

Implementasi yang menarik dari klik tombol diimplementasikan (itu adalah persegi panjang di mana elemen yang tersisa diambil ). Jika Anda melihat kode, Anda seharusnya memperhatikan hal seperti itu: ch_dsc [i] .box.SetCallback (& ​​Callback, this, nullptr, i); disebut dalam satu lingkaran. Ini adalah tugas fungsi panggilan balik yang akan dipanggil saat tombol ditekan. Berikut ini ditransfer ke fungsi: alamat fungsi statis dari fungsi statis kelas, pointer ini, dan dua parameter pengguna yang akan diteruskan ke fungsi callback - pointer ( tidak digunakan dalam kasus ini - nullptr dilewatkan ) dan nomor ( nomor saluran ditransmisikan ).

Dari bangku universitas, saya ingat postulat: " Fungsi statis tidak memiliki akses ke anggota kelas yang tidak statis ." Jadi ini tidak benar . Karena fungsi statis adalah anggota kelas, ia memiliki akses ke semua anggota kelas jika memiliki tautan / penunjuk ke kelas ini. Sekarang lihat fungsi callback:

 // ***************************************************************************** // *** Callback for the buttons ********************************************* // ***************************************************************************** void Application::Callback(void* ptr, void* param_ptr, uint32_t param) { Application& app = *((Application*)ptr); ChannelType channel = app.channel; if(channel == param) { // Second click - change wave type ... } else { app.channel = (ChannelType)param; } app.update = true; } 

Di baris pertama dari fungsi ini, " sihir " terjadi, setelah itu Anda dapat mengakses anggota kelas mana pun, termasuk yang pribadi.

Omong-omong, fungsi ini dipanggil dalam tugas lain ( menampilkan layar ), jadi di dalam fungsi ini Anda perlu menjaga sinkronisasi. Dalam proyek " pasangan malam " sederhana ini, saya tidak melakukan ini, karena dalam kasus khusus ini tidak penting.

Kode sumber generator yang diunggah ke GitHub: https://github.com/nickshl/WaveformGenerator
DevCore sekarang dialokasikan ke repositori terpisah dan dimasukkan sebagai submodule.

Nah, mengapa saya membutuhkan generator sinyal, itu akan ada di artikel berikutnya ( atau salah satu dari berikut ).

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


All Articles