جدول المحتويات:

1024 عينة من محلل الطيف FFT باستخدام Atmega1284: 9 خطوات
1024 عينة من محلل الطيف FFT باستخدام Atmega1284: 9 خطوات

فيديو: 1024 عينة من محلل الطيف FFT باستخدام Atmega1284: 9 خطوات

فيديو: 1024 عينة من محلل الطيف FFT باستخدام Atmega1284: 9 خطوات
فيديو: [138 -1024] ما رأيكم في كتاب "إحياء علوم الدين للغزالي"، وهل للمبتدئين أن يشتروه ويقرءوه؟ 2024, شهر نوفمبر
Anonim
1024 عينة من محلل الطيف FFT باستخدام Atmega1284
1024 عينة من محلل الطيف FFT باستخدام Atmega1284
1024 عينة من محلل الطيف FFT باستخدام Atmega1284
1024 عينة من محلل الطيف FFT باستخدام Atmega1284

سيوضح لك هذا البرنامج التعليمي السهل نسبيًا (بالنظر إلى مدى تعقيد هذا الموضوع) كيف يمكنك إنشاء محلل طيف بسيط للغاية يبلغ 1024 عينة باستخدام لوحة من نوع Arduino (1284 Narrow) والرسام التسلسلي. أي نوع من اللوحات المتوافقة مع Arduino سيفي بالغرض ، ولكن كلما زادت ذاكرة الوصول العشوائي ، سيكون أفضل دقة تردد تحصل عليها. ستحتاج إلى أكثر من 8 كيلوبايت من ذاكرة الوصول العشوائي لحساب FFT مع 1024 عينة.

يستخدم تحليل الطيف لتحديد مكونات التردد الرئيسية للإشارة. تتكون العديد من الأصوات (مثل تلك التي تنتجها آلة موسيقية) من تردد أساسي وبعض التوافقيات التي لها تردد يمثل عددًا صحيحًا مضاعفًا للتردد الأساسي. سيُظهر لك محلل الطيف كل هذه المكونات الطيفية.

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

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

الخطوة الأولى: تثبيت المكتبة

سنستخدم مكتبة ArduinoFFT التي كتبها إنريكي كونديس. نظرًا لأننا نريد توفير ذاكرة الوصول العشوائي قدر الإمكان ، فسنستخدم فرع التطوير لهذا المستودع الذي يسمح باستخدام نوع البيانات العائمة (بدلاً من مزدوج) لتخزين البيانات التي تم أخذ عينات منها والمحسوبة. لذلك علينا تثبيته يدويًا. لا تقلق ، ما عليك سوى تنزيل الأرشيف وإلغاء ضغطه في مجلد مكتبة Arduino (على سبيل المثال في التكوين الافتراضي لنظام التشغيل Windows 10: C: / Users / _your_user_name_ / Documents / Arduino / libraries)

يمكنك التحقق من تثبيت المكتبة بشكل صحيح عن طريق تجميع أحد الأمثلة المتوفرة ، مثل "FFT_01.ino."

الخطوة 2: تحويل فورييه ومفاهيم FFT

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

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

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

أنت بالتأكيد على دراية بمفهوم التعادل ، مثل هذا المفهوم الذي يعود إلى الثمانينيات مع Graphic EQ. حسنًا ، سوف نحصل على نفس النوع من النتائج ولكن مع 1024 نطاقًا بدلاً من 16 ودقة أكثر كثافة. عندما يعطي المعادل رؤية شاملة للموسيقى ، يسمح التحليل الطيفي الدقيق بحساب شدة كل نطاق من النطاقات الـ 1024 بدقة.

مفهوم مثالي ، لكن:

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

في التمرين

1) تردد أخذ العينات (ملحوظ fs)

سنقوم بتجربة إشارة ، أي قياس اتساعها ، كل 1 / fs ثانية. fs هو تردد أخذ العينات. على سبيل المثال ، إذا أخذنا عينات عند 8 كيلو هرتز ، فإن ADC (المحول التناظري إلى الرقمي) الموجود على متن الشريحة سيوفر قياسًا كل 1/8000 ثانية.

2) عدد العينات (لاحظ N أو العينات في الكود)

نظرًا لأننا نحتاج إلى الحصول على جميع القيم قبل تشغيل FFT ، فسيتعين علينا تخزينها وبالتالي سنحد من عدد العينات. تحتاج خوارزمية FFT إلى عدد من العينات تبلغ قوتها 2. وكلما زاد عدد العينات التي لدينا كان ذلك أفضل ، ولكنها تتطلب الكثير من الذاكرة ، وكلما احتجنا أيضًا إلى تخزين البيانات المحولة ، وهي قيم معقدة. توفر مكتبة Arduino FFT بعض المساحة باستخدام ملفات

  • مصفوفة واحدة تسمى "vReal" لتخزين بيانات العينة ثم الجزء الحقيقي من البيانات المحولة
  • مصفوفة واحدة تسمى "vImag" لتخزين الجزء التخيلي من البيانات المحولة

الكمية المطلوبة من ذاكرة الوصول العشوائي تساوي 2 (صفائف) * 32 (بت) * N (عينات).

لذلك في Atmega1284 الذي يحتوي على 16 كيلوبايت من ذاكرة الوصول العشوائي ، سنخزن بحد أقصى N = 16000 * 8/64 = 2000 قيمة. نظرًا لأن عدد القيم يجب أن يكون بقوة 2 ، فسوف نقوم بتخزين 1024 قيمة كحد أقصى.

3) قرار التردد

سوف يحسب FFT القيم لعدد من نطاقات التردد مثل عدد العينات. ستمتد هذه النطاقات من 0 هرتز إلى تردد أخذ العينات (fs). ومن ثم ، فإن دقة التردد هي:

Fresolution = fs / N

الدقة أفضل عندما تكون أقل. لذلك من أجل دقة أفضل (أقل) نريد:

  • المزيد من العينات و / أو
  • أ أقل fs

لكن…

4) الحد الأدنى fs

نظرًا لأننا نريد أن نرى الكثير من الترددات ، فبعضها أعلى بكثير من "التردد الأساسي" ، لا يمكننا ضبط fs على مستوى منخفض جدًا. في الواقع ، هناك نظرية أخذ العينات Nyquist-Shannon التي تجبرنا على الحصول على تردد أخذ العينات أعلى بكثير من ضعف التردد الأقصى الذي نرغب في اختباره.

على سبيل المثال ، إذا كنا نرغب في تحليل كل الطيف من 0 هرتز لنفترض أن 15 كيلو هرتز ، وهو تقريبًا الحد الأقصى للتردد الذي يمكن أن يسمعه معظم البشر بوضوح ، فعلينا ضبط تردد أخذ العينات عند 30 كيلو هرتز. في الواقع ، غالبًا ما يقوم الإلكترونى بتعيينه على 2.5 (أو حتى 2.52) * الحد الأقصى للتردد. في هذا المثال سيكون 2.5 * 15 كيلو هرتز = 37.5 كيلو هرتز. الترددات المعتادة لأخذ العينات في الصوت الاحترافي هي 44.1 كيلوهرتز (تسجيل قرص مضغوط صوتي) و 48 كيلوهرتز وأكثر.

استنتاج:

تؤدي النقاط من 1 إلى 4 إلى: نريد استخدام أكبر عدد ممكن من العينات. في حالتنا مع 16 كيلوبايت من ذاكرة الوصول العشوائي ، سننظر في 1024 عينة. نريد أخذ العينات بأدنى تردد ممكن لأخذ العينات ، طالما أنه مرتفع بما يكفي لتحليل أعلى تردد نتوقعه في إشارتنا (2.5 * هذا التردد ، على الأقل).

الخطوة 3: محاكاة إشارة

محاكاة إشارة
محاكاة إشارة

في محاولتنا الأولى ، سنقوم بتعديل مثال TFT_01.ino الوارد في المكتبة قليلاً ، لتحليل إشارة مكونة من

  • التردد الأساسي مضبوط على 440 هرتز (موسيقي أ)
  • التوافقية الثالثة بنصف القوة الأساسية ("-3 ديسيبل")
  • التوافقي الخامس عند 1/4 من القوة الأساسية ("-6 ديسيبل)

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

الخطوة 4: تحليل إشارة محاكاة - تشفير

0) تضمين المكتبة

# تضمين "arduinoFFT.h"

1. تعاريف

في أقسام الإعلانات لدينا

const بايت adcPin = 0 ؛ // A0

عينات const uint16_t = 1024 ؛ // يجب أن تكون هذه القيمة دائمًا قوة 2 const uint16_t samplingFrequency = 8000 ؛ // سيؤثر على القيمة القصوى للمؤقت في timer_setup () يجب أن يكون SYSCLOCK / 8 / samplingFrequency عددًا صحيحًا

نظرًا لأن الإشارة لها التوافقيات الخامسة (تردد هذا التوافقي = 5 * 440 = 2200 هرتز) ، نحتاج إلى ضبط تردد أخذ العينات فوق 2.5 * 2200 = 5500 هرتز. اخترت هنا 8000 هرتز.

نعلن أيضًا عن المصفوفات حيث سنخزن البيانات الأولية والمحسوبة

تعويم vReal [عينات] ؛

تعويم vImag [عينات] ؛

2) التجسيد

نقوم بإنشاء كائن ArduinoFFT. يستخدم إصدار dev من ArduinoFFT نموذجًا حتى نتمكن من استخدام نوع البيانات العائمة أو نوع البيانات المزدوجة. يعد Float (32 بت) كافياً فيما يتعلق بالدقة الشاملة لبرنامجنا.

ArduinoFFT FFT = ArduinoFFT (vReal ، vImag ، عينات ، samplingFrequency) ؛

3) محاكاة الإشارة عن طريق ملء مصفوفة vReal ، بدلاً من ملؤها بقيم ADC.

في بداية الحلقة ، نملأ مصفوفة vReal بـ:

دورات الطفو = (((عينات) * تردد الإشارة) / samplingFrequency) ؛ // عدد دورات الإشارة التي ستقرأها العينة

لـ (uint16_t i = 0؛ i <sample؛ i ++) {vReal = float ((السعة * (sin ((i * (TWO_PI * cycles)) / العينات))) ؛ / * إنشاء البيانات بإيجابية و القيم السالبة * / vReal + = تعويم ((السعة * (الخطيئة ((3 * i * (TWO_PI * cycles)) / العينات))) / 2.0) ؛ / * إنشاء البيانات بقيم موجبة وسالبة * / vReal + = تعويم ((السعة * (الخطيئة ((5 * i * (TWO_PI * دورتان)) / عينات))) / 4.0) ؛ / * إنشاء بيانات ذات قيم موجبة وسالبة * / vImag = 0.0 ؛ // يجب أن يكون الجزء التخيلي صفريًا في حالة التكرار لتجنب الحسابات والتجاوزات الخاطئة}

نضيف رقمنة الموجة الأساسية والتوافقيين بسعة أقل. ثم نهيئ المصفوفة التخيلية بالأصفار. نظرًا لأن هذه المجموعة يتم ملؤها بواسطة خوارزمية FFT ، نحتاج إلى مسحها مرة أخرى قبل كل حساب جديد.

4) حوسبة FFT

ثم نحسب FFT والكثافة الطيفية

FFT.windowing (FFTWindow:: Hamming، FFTDirection:: Forward) ؛

FFT.compute (FFTDirection:: Forward) ؛ / * حساب FFT * / FFT.complexToMagnitude () ؛ / * حساب الأقدار * /

تعمل عملية FFT.windowing (…) على تعديل البيانات الأولية لأننا نقوم بتشغيل FFT على عدد محدود من العينات. تمثل العينات الأولى والأخيرة انقطاعًا (لا يوجد "شيء" من جانبهم). هذا مصدر للخطأ. تميل عملية "النوافذ" إلى تقليل هذا الخطأ.

FFT.compute (…) مع الاتجاه "Forward" يحسب التحويل من المجال الزمني إلى مجال التردد.

ثم نحسب قيم المقدار (أي الشدة) لكل نطاق من نطاقات التردد. تمتلئ صفيف vReal الآن بقيم المقادير.

5) رسم الراسمة التسلسلي

دعونا نطبع القيم على الراسمة التسلسلية عن طريق استدعاء الدالة printVector (…)

PrintVector (vReal ، (العينات >> 1) ، SCL_FREQUENCY) ؛

هذه وظيفة عامة تسمح بطباعة البيانات باستخدام محور زمني أو محور تردد.

نقوم أيضًا بطباعة تردد النطاق الذي يحتوي على أعلى قيمة من حيث الحجم

تعويم x = FFT.majorPeak () ،

Serial.print ("f0 =") ؛ Serial.print (x ، 6) ؛ Serial.println ("هرتز") ؛

الخطوة 5: تحليل إشارة محاكاة - النتائج

تحليل إشارة محاكاة - النتائج
تحليل إشارة محاكاة - النتائج

نرى 3 ارتفاعات تقابل التردد الأساسي (f0) ، التوافقيات الثالثة والخامسة ، بنصف و 1/4 من حجم f0 ، كما هو متوقع. يمكننا أن نقرأ في الجزء العلوي من النافذة f0 = 440.430114 هرتز. هذه القيمة ليست بالضبط 440 هرتز ، نظرًا لجميع الأسباب الموضحة أعلاه ، ولكنها قريبة جدًا من القيمة الحقيقية. لم يكن من الضروري حقًا إظهار الكثير من الكسور العشرية غير المهمة.

الخطوة 6: تحليل إشارة حقيقية - توصيل ADC

تحليل إشارة حقيقية - أسلاك ADC
تحليل إشارة حقيقية - أسلاك ADC

نظرًا لأننا نعرف كيفية المضي قدمًا من الناحية النظرية ، نود تحليل إشارة حقيقية.

الأسلاك بسيطة للغاية. قم بتوصيل الأسس معًا وخط الإشارة بالدبوس A0 في لوحك من خلال المقاوم المتسلسل بقيمة 1 KOhm إلى 10 KOhm.

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

في هذا العرض التوضيحي ، استخدمت مولدًا وظيفيًا لتغذية إشارة جيبية بتردد 440 هرتز وسعة حوالي 5 فولت (من الأفضل إذا كانت السعة بين 3 و 5 فولت ، لذلك يتم استخدام ADC بالقرب من النطاق الكامل) ، من خلال مقاوم 1.2 كيلو أوم.

الخطوة السابعة: تحليل الإشارة الحقيقية - الترميز

0) تضمين المكتبة

# تضمين "arduinoFFT.h"

1) الإقرارات والتعزيزات

في قسم الإعلان ، نحدد إدخال ADC (A0) ، وعدد العينات وتكرار أخذ العينات ، كما في المثال السابق.

const بايت adcPin = 0 ؛ // A0

عينات const uint16_t = 1024 ؛ // يجب أن تكون هذه القيمة دائمًا قوة 2 const uint16_t samplingFrequency = 8000 ؛ // سيؤثر على القيمة القصوى للمؤقت في timer_setup () يجب أن يكون SYSCLOCK / 8 / samplingFrequency عددًا صحيحًا

نقوم بإنشاء كائن ArduinoFFT

ArduinoFFT FFT = ArduinoFFT (vReal ، vImag ، عينات ، samplingFrequency) ؛

2) إعداد الموقت و ADC

قمنا بتعيين المؤقت 1 بحيث يقوم بالدوران عند تردد أخذ العينات (8 كيلو هرتز) ويثير مقاطعة عند مقارنة الإخراج.

timer_setup باطل () {

// إعادة تعيين Timer 1 TCCR1A = 0 ؛ TCCR1B = 0 ؛ TCNT1 = 0 ؛ TCCR1B = بت (CS11) | بت (WGM12) ؛ // CTC ، مقياس مسبق لـ 8 TIMSK1 = بت (OCIE1B) ؛ OCR1A = ((16000000/8) / samplingFrequency) -1 ؛ }

وتعيين ADC لذلك

  • يستخدم A0 كمدخل
  • المشغلات تلقائيًا على كل مؤقت إخراج 1 مقارنة مطابقة ب
  • يولد مقاطعة عند اكتمال التحويل

يتم ضبط ساعة ADC على 1 ميجاهرتز ، عن طريق القياس المسبق لساعة النظام (16 ميجاهرتز) في 16. نظرًا لأن كل تحويل يستغرق حوالي 13 ساعة على نطاق واسع ، يمكن تحقيق التحويلات عند تردد 1/13 = 0.076 ميجاهرتز = 76 كيلو هرتز. يجب أن يكون تردد أخذ العينات أقل بكثير من 76 كيلو هرتز للسماح لـ ADC بالوقت لأخذ عينات من البيانات. (اخترنا fs = 8 كيلو هرتز).

adc_setup باطل () {

ADCSRA = بت (عدن) | بت (ADIE) | بت (ADIF) ؛ // قم بتشغيل ADC ، وتريد المقاطعة عند الانتهاء ADCSRA | = بت (ADPS2) ؛ // Prescaler لـ 16 ADMUX = بت (REFS0) | (adcPin & 7) ؛ // ضبط إدخال ADC ADCSRB = بت (ADTS0) | بت (ADTS2) ؛ // Timer / Counter1 Compare Match B المشغل المصدر ADCSRA | = bit (ADATE) ؛ // تشغيل التشغيل التلقائي}

نعلن عن معالج المقاطعة الذي سيتم استدعاؤه بعد كل تحويل ADC لتخزين البيانات المحولة في صفيف vReal ، وتصفية المقاطعة

// ADC كاملة ISR

ISR (ADC_vect) {vReal [resultNumber ++] = ADC ، إذا (resultNumber == عينات) {ADCSRA = 0 ؛ // إيقاف تشغيل ADC}} EMPTY_INTERRUPT (TIMER1_COMPB_vect) ؛

يمكنك الحصول على شرح شامل حول تحويل ADC على Arduino (analogRead).

3) الإعداد

في وظيفة الإعداد ، نقوم بمسح جدول البيانات التخيلية واستدعاء وظائف إعداد المؤقت و ADC

صفرأنا () ، // وظيفة يتم تعيينها على 0 جميع البيانات التخيلية - تم شرحها في القسم السابق

timer_setup () ؛ adc_setup () ،

3) حلقة

FFT.dcRemoval () ، // قم بإزالة مكون DC لهذه الإشارة حيث تمت الإشارة إلى ADC على الأرض

FFT.windowing (FFTWindow:: Hamming، FFTDirection:: Forward) ؛ // وزن البيانات FFT.compute (FFTDirection:: Forward) ؛ // Compute FFT FFT.complexToMagnitude () ؛ // حساب المقادير // طباعة الطيف والتردد الأساسي f0 PrintVector (vReal ، (العينات >> 1) ، SCL_FREQUENCY) ؛ تعويم x = FFT.majorPeak () ، Serial.print ("f0 =") ؛ Serial.print (x ، 6) ؛ Serial.println ("هرتز") ؛

نقوم بإزالة مكون التيار المستمر لأن ADC يُشار إليه على الأرض وتتركز الإشارة حول 2.5 فولت تقريبًا.

ثم نقوم بحساب البيانات كما هو موضح في المثال السابق.

الخطوة 8: تحليل إشارة حقيقية - النتائج

تحليل الإشارة الحقيقية - النتائج
تحليل الإشارة الحقيقية - النتائج

في الواقع ، نرى ترددًا واحدًا فقط في هذه الإشارة البسيطة. التردد الأساسي المحسوب هو 440.118194 هرتز. هنا مرة أخرى ، القيمة هي تقريب قريب جدًا للتردد الحقيقي.

الخطوة 9: ماذا عن الإشارة الجيبية المقطوعة؟

ماذا عن الإشارة الجيبية المقطوعة؟
ماذا عن الإشارة الجيبية المقطوعة؟

الآن دعنا نزيد من سرعة ADC قليلاً عن طريق زيادة سعة الإشارة فوق 5 فولت ، لذلك يتم قصها. لا تضغط على الهريسة حتى لا تدمر مدخلات ADC!

يمكننا أن نرى ظهور بعض التوافقيات. يؤدي قص الإشارة إلى إنشاء مكونات عالية التردد.

لقد رأيت أساسيات تحليل FFT على لوحة Arduino. يمكنك الآن محاولة تغيير وتيرة أخذ العينات وعدد العينات ومعلمة النوافذ. تضيف المكتبة أيضًا بعض المعلمات لحساب FFT بشكل أسرع وبدقة أقل. ستلاحظ أنه إذا قمت بتعيين تردد أخذ العينات منخفضًا جدًا ، فستظهر المقادير المحسوبة خاطئة تمامًا بسبب الطي الطيفي.

موصى به: