DevBoy: عمل مولد إشارة

مرحبا اصدقاء!

في المقالات السابقة ، تحدثت عن مشروعي وجزء البرنامج الخاص به . في هذه المقالة سأخبرك بكيفية عمل مولد إشارة بسيط لأربع قنوات - قناتين تمثيليتين وقناتين PWM.



القنوات التناظرية


يشتمل متحكم STM32F415RG على محول DAC 12-بت (رقمي إلى تناظري) في قناتين مستقلتين ، مما يسمح بتوليد إشارات مختلفة. يمكنك تحميل البيانات مباشرة في سجلات المحول ، ولكن هذا ليس مناسبًا جدًا لتوليد الإشارات. الحل الأفضل هو استخدام مصفوفة لتوليد موجة واحدة من الإشارة ، ثم تشغيل DAC باستخدام مشغل من المؤقت و DMA. من خلال تغيير تردد المؤقت ، يمكنك تغيير تردد الإشارة التي تم إنشاؤها.

تتضمن الأشكال الموجية " الكلاسيكية ": الجيوب الأنفية ، والتعرج ، والمثلث ، والسنّاد.

الصورة

وظيفة توليد هذه الموجات في المنطقة العازلة هي كما يلي
// ***************************************************************************** // *** 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; } 

في الوظيفة ، تحتاج إلى تمرير مؤشر إلى بداية الصفيف ، حجم الصفيف ، القيمة القصوى وشكل الموجة المطلوب. بعد المكالمة ، سيتم تعبئة الصفيف بعينات لموجة واحدة من الشكل المطلوب ويمكنك بدء المؤقت لتحميل القيمة الجديدة بشكل دوري في DAC.

لدى DAC في وحدة التحكم الدقيقة هذه قيود: وقت الاستقرار النموذجي ( الوقت من تحميل قيمة جديدة إلى DAC وعندما تظهر على الإخراج ) هو 3 مللي ثانية. ولكن ليس كل شيء بسيطًا جدًا - هذه المرة هي الحد الأقصى ، أي التغيير من الحد الأدنى إلى الحد الأقصى والعكس صحيح. عند محاولة سحب المتعرج ، تكون هذه الجبهات المليئة بالرسومات واضحة جدًا:



إذا كانت الموجة الجيبية ناتجة ، فإن انسداد الجبهات لم يعد ملحوظًا بسبب شكل الموجة. ومع ذلك ، إذا تم زيادة التردد ، تتحول الإشارة الجيبية إلى إشارة مثلثة ، ومع زيادة أخرى ، ينخفض ​​اتساع الإشارة.

التوليد عند 1 كيلوهرتز ( سعة 90٪ ):



التوليد بسرعة 10 كيلوهرتز ( سعة 90٪ ):



التوليد عند 100 كيلو هرتز ( سعة 90٪ ):



الخطوات مرئية بالفعل - لأنه يتم تحميل البيانات الجديدة في DAC بتردد 4 ميجاهرتز.

بالإضافة إلى ذلك ، فإن الحافة الخلفية لإشارة سن المنشار تشوش ، ومن أسفل الإشارة لا تصل إلى القيمة التي يجب أن تكون عليها. وذلك لأن الإشارة ليس لديها الوقت للوصول إلى المستوى المنخفض المحدد ، ويقوم البرنامج بتحميل قيم جديدة

التوليد عند 200 كيلوهرتز ( سعة 90٪ ):



هنا يمكنك أن ترى بالفعل كيف تحولت جميع الموجات إلى مثلث.

القنوات الرقمية


مع القنوات الرقمية ، كل شيء أبسط بكثير - في أي متحكم تقريبًا هناك مؤقتات تسمح لك بإخراج إشارة PWM إلى مخرجات وحدة التحكم الدقيقة. من الأفضل استخدام مؤقت 32 بت - في هذه الحالة ، لا تحتاج إلى حساب المؤقت المسبق للمؤقت ، فقط قم بتحميل الفترة في سجل واحد وتحميل دورة العمل المطلوبة في سجل آخر.

واجهة المستخدم


تقرر تنظيم واجهة المستخدم في أربعة مستطيلات ، لكل منها صورة لإشارة الخرج والتردد والسعة / دورة العمل. للقناة المحددة حاليًا ، يتم عرض بيانات النص باللون الأبيض ، والباقي باللون الرمادي.



تقرر التحكم في برامج التشفير: الجهة اليسرى مسؤولة عن التردد والقناة المحددة الحالية ( تتغير عند الضغط على الزر ) ، والجهة اليمنى مسؤولة عن دورة السعة / العمل وشكل الموجة ( تتغير عند الضغط على الزر ).

بالإضافة إلى ذلك ، يتم تنفيذ دعم شاشة اللمس - عندما تنقر على قناة غير نشطة ، فإنها تصبح نشطة ، عندما تنقر على قناة نشطة ، يتغير شكل الموجة.

بالطبع ، يتم استخدام DevCore للقيام بكل هذا. يبدو رمز تهيئة واجهة المستخدم وتحديث البيانات على الشاشة كما يلي:

هيكل يحتوي على جميع كائنات واجهة المستخدم
  // ************************************************************************* // *** 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]; 
كود تهيئة واجهة المستخدم
  // 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); } 
رمز تحديث الشاشة
  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(); 

يتم تنفيذ تطبيق مثير للاهتمام للنقرة على الزر ( وهو مستطيل يتم رسم العناصر المتبقية عليه ). إذا نظرت إلى الرمز ، فيجب أن تلاحظ مثل هذا الشيء: ch_dsc [i] .box.SetCallback (& ​​Callback، this، nullptr، i)؛ دعا في حلقة. هذه هي وظيفة وظيفة رد الاتصال التي سيتم استدعاؤها عند الضغط على الزر. يتم نقل ما يلي إلى الوظيفة: عنوان الوظيفة الثابتة للدالة الثابتة للفئة ، هذا المؤشر ، ومعلمتين للمستخدم سيتم تمريرهما إلى وظيفة رد الاتصال - مؤشر ( لا يستخدم في هذه الحالة - يتم تمرير nullptr ) ورقم ( يتم إرسال رقم القناة ).

من مقاعد الجامعة ، أتذكر الافتراض: " الوظائف الثابتة لا يمكنها الوصول إلى أعضاء الفصل غير الثابت ". لذلك هذا ليس صحيحا . نظرًا لأن الوظيفة الثابتة هي عضو في فئة ، فإنها يمكنها الوصول إلى جميع أعضاء الفصل إذا كان لديها ارتباط / مؤشر لهذه الفئة. الآن نلقي نظرة على وظيفة رد الاتصال:

 // ***************************************************************************** // *** 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; } 

في السطر الأول من هذه الوظيفة ، يحدث " السحر " ، وبعد ذلك يمكنك الوصول إلى أي أعضاء في الفصل ، بما في ذلك الأفراد.

بالمناسبة ، يتم استدعاء هذه الوظيفة في مهمة أخرى ( عرض الشاشة ) ، لذلك تحتاج داخل هذه الوظيفة إلى الاهتمام بالمزامنة. في مشروع " الأمسيات " البسيط هذا ، لم أفعل ذلك ، لأنه في هذه الحالة بالذات ليس ضروريًا.

تم تحميل رمز مصدر المولد إلى GitHub: https://github.com/nickshl/WaveformGenerator
يتم تخصيص DevCore الآن إلى مستودع منفصل ويتم إدراجه كوحدة فرعية.

حسنًا ، لماذا أحتاج إلى مولد إشارة ، فسيكون في المقالة التالية ( أو إحدى المقالات التالية ).

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


All Articles