Mengukur 0 - 1MHz (resolusi 0,25Hz) Squarewave menggunakan MCU


8

Saya perlu mengukur frekuensi gelombang persegi yang dapat bervariasi antara 0 dan 1MHz, dan memiliki resolusi 0,25Hz.

Saya belum memutuskan controller yang mana tetapi itu kemungkinan besar akan menjadi salah satu Attiny 20pin.

Biasanya bagaimana saya akan mengukur sinyal frekuensi yang lebih rendah adalah dengan menggunakan dua timer yang dikonfigurasi dalam mode penangkapan waktu untuk menyela mengatakan naiknya tepi sinyal eksternal dan timer lain yang diatur untuk menyela setiap detik oleh karena itu nilai timer counter register pertama setelah 1 detik akan sama dengan frekuensi sinyal.

Namun metode ini jelas tidak akan berfungsi untuk menangkap sinyal yang berkisar antara 0 dan 1MHz dengan resolusi 0,25Hz untuk ini saya akan membutuhkan penghitung 22Bit (AFAIK 8bit micros hanya memiliki penghitung 8/16bit).

Satu ide yang saya miliki adalah untuk membagi sinyal sebelum menerapkannya ke mikro tetapi ini akan tidak rasional karena sinyal harus dibagi dengan 61 oleh karena itu frekuensinya hanya dapat diperbarui setiap 61 detik di mana saya ingin setiap beberapa detik .

Apakah ada metode lain yang akan memungkinkan frekuensi diperbarui katakan setiap 4 detik?


Memperbarui:

Solusi paling sederhana adalah dengan menggunakan interupsi eksternal atau pengatur waktu untuk menginterupsi tepi sinyal yang naik dan isrmenambahkan variabel tipe long int. Baca variabel setiap 4 detik (untuk memungkinkan frekuensi turun ke 0,25Hz untuk diukur).


Pembaruan 2:

Seperti yang ditunjukkan oleh JustJeff, MCU 8-bit tidak akan mampu mengimbangi sinyal 1MHz sehingga mengesampingkan gangguan pada setiap sisi yang meningkat dan meningkatkan long int...

Saya telah memilih metode yang disarankan oleh timororr. Setelah saya berkeliling untuk mengimplementasikannya, saya akan mengirim kembali dan membagikan hasilnya. Terima kasih untuk semua saran Anda.


Laporan perkembangan:

Iv'e mulai menguji beberapa ide yang disajikan di sini. Pertama saya mencoba kode vicatcu. Ada masalah nyata dari TCNT1 yang belum dihapus setelah frekuensi dihitung - bukan masalah besar ...

Kemudian saya perhatikan ketika men-debug kode yang setiap 2 sampai 7 kali frekuensi dihitung timer 1's (timer dikonfigurasi untuk menghitung peristiwa eksternal) jumlah overflow akan pendek dua. Saya meletakkan ini ke latensi Timer 0 ISR dan memutuskan untuk memindahkan blok pernyataan if dari ISR ​​ke utama (lihat cuplikan di bawah) dan hanya mengatur bendera di ISR. Beberapa debugging menunjukkan bahwa pengukuran pertama akan baik-baik saja tetapi dengan setiap pembacaan berikutnya, jumlah limpahan Timer 1 akan berakhir pada 2. yang tidak dapat saya jelaskan -Saya harapkan tidak kurang dari ...

int main()
{
    while(1)
    {
        if(global_task_timer_ms > 0 && (T0_overflow == 1))
        {
            global_task_timer_ms--;
            T0_overflow = 0;
        }

        .....
    }
}

Selanjutnya saya memutuskan untuk mencoba menerapkan saran timrorrs. Untuk menghasilkan interval yang diperlukan (sekitar 15ms antara masing-masing interupsi timer_isr), saya harus mengalirkan dua timer 8-bit karena timer 16-bit pada Atmega16 digunakan untuk menangkap peningkatan tepi sinyal eksternal.

Saya pikir solusi ini akan bekerja dan jauh lebih efisien karena sebagian besar overhead dipindahkan ke timer dan hanya satu isr pendek yang tersisa untuk cpu untuk menangani. Namun itu tidak seakurat yang saya harapkan, pengukuran bergeser bolak-balik oleh kira-kira 70Hz yang saya tidak akan keberatan pada frekuensi tinggi tetapi jelas tidak dapat diterima pada frekuensi yang lebih rendah. Saya tidak menghabiskan dua banyak waktu menganalisis masalah tetapi saya menduga pengaturan cascading timer tidak begitu akurat karena saya telah menerapkan pengaturan yang mirip dengan saran timrorrs pada pengontrol 8051 yang jauh lebih lambat yang memiliki 2 timer 16-bit dan hasilnya cukup akurat.

Saya sekarang telah kembali ke saran vicatcu, tetapi saya telah memindahkan perhitungan frekuensi ke Timer 0 isr (lihat cuplikan di bawah ), kode ini telah menghasilkan pengukuran yang konsisten dan cukup akurat. Dengan sedikit kalibrasi, akurasi seharusnya kira-kira +/- 10Hz.

ISR(TIMER0_OVF_vect)
{            

    TCNT0 = TIMER0_PRELOAD;         //Reload timer for 1KHz overflow rate

    if(task_timer_ms > 0)
    {
        task_timer_ms--;
    }
    else
    {     
        frequency_hz = 1.0 * TCNT1;
        TCNT1 = 0;
        frequency_hz += global_num_overflows * 65536.0;
        global_num_overflows  = 0;
        frequency_hz /= (TASK_PERIOD_MS / 1000.0);
        task_timer_ms = TASK_PERIOD_MS;
    }                                                 
}       

Jika ada yang punya saran lain saya terbuka untuk mereka walaupun tetapi saya lebih suka tidak harus menggunakan rentang ... Saya juga tidak lagi bermaksud mendapatkan resolusi 0,25%, sepertinya tidak ada gunanya dengan tingkat akurasi yang saya miliki saat ini .


Ada cara yang relatif mudah untuk melakukan ini menggunakan interupsi penangkapan pada PIC dan Timer 1 berjalan pada kecepatan yang sangat tinggi. Jika Anda masih tertarik dengan metode lain, beri tahu saya dan saya bisa menguraikannya dalam jawaban.
Kortuk

Saya belum mulai mengerjakan ini, jadi ya saya masih tertarik.
volting

Untuk beberapa alasan itu tidak pernah memberi tahu saya bahwa Anda telah mengomentari komentar saya.
Kortuk

@Kortuk: Perangkat lunak ini hanya memberi tahu Anda jika saya meninggalkan komentar pada salah satu jawaban atau pertanyaan Anda. Mungkin juga memberi tahu Anda tentang komentar ini, karena saya meletakkan @Kortuk di depannya. Tapi itu adalah perubahan perangkat lunak StackOverflow, dan saya tidak tahu apakah itu masuk ke dalam basis kode StackExchange atau tidak.
Robert Harvey

tidak, itu tidak memberi tahu saya bahwa Anda telah merespons, bahkan dengan @kortuk. Jangan khawatir. Sepertinya jawaban telah ditemukan.
Kortuk

Jawaban:


4

Jika mungkin saya sarankan memilih mikrokontroler yang mendukung operasi penghitung menggunakan input timer; daripada secara manual menambah penghitung di dalam ISR (yang pada frekuensi tinggi dengan cepat berakhir menjenuhkan aktivitas mikrokontroler) Anda mengizinkan perangkat keras untuk menangani penghitungan. Pada titik ini kode Anda hanya menjadi masalah menunggu interupsi berkala Anda kemudian menghitung frekuensi.

Untuk memperluas jangkauan dan membuat penghitung frekuensi lebih digeneralisasi (menghilangkan kebutuhan untuk beberapa rentang dengan mengorbankan sedikit lebih banyak pekerjaan untuk MCU), Anda dapat menggunakan teknik berikut.

Pilih tingkat interupsi berkala yang memungkinkan akurasi pengukuran pada frekuensi input tertinggi; ini harus memperhitungkan ukuran penghitung Anda (Anda perlu memilih periode waktu sehingga penghitung waktu tidak akan meluap pada frekuensi input maksimum). Untuk contoh ini saya akan menganggap bahwa nilai penghitung input dapat dibaca dari variabel "timer_input_ctr".

Sertakan variabel untuk menghitung interupsi berkala (harus diinisialisasi ke 0 pada saat startup); untuk contoh ini saya akan merujuk ke variabel ini sebagai "isr_count". Periode interupsi terkandung dalam konstanta "isr_ period".

Interupsi berkala Anda harus diimplementasikan sebagai (C pseudo-code):

void timer_isr()
{
  isr_count++;
  if (timer_input_ctr > 0)
  {
    frequency = timer_input_ctr / (isr_count * isr_period).
    timer_input_ctr = 0;
    isr_count = 0;
  }
}

Jelas contoh kasar ini bergantung pada beberapa matematika floating point yang mungkin tidak kompatibel untuk mikrokontroler low-end, ada teknik untuk mengatasi ini tetapi mereka berada di luar ruang lingkup jawaban ini.


1
Timororr yang luar biasa, yang akan melakukan apa yang saya inginkan tanpa biaya IC ekstra yang selalu baik, saya pikir saya terlalu cepat untuk mengabaikan kemungkinan pemecahan masalah dalam perangkat lunak. Terima kasih
volting

@timrorr, saya tertarik dengan pemikiran Anda tentang jawaban saya di bawah ini jika Anda ingin membacanya
vicatcu

7

Anda mungkin ingin mempertimbangkan memiliki dua (atau lebih) rentang. Masalah dengan menangkap frekuensi yang sangat rendah agak berbeda dari masalah dengan yang lebih tinggi. Seperti yang telah Anda catat, di ujung rentang Anda, Anda memiliki masalah counter overflow.

Tetapi pertimbangkan pada batas bawah kisaran Anda, akurasi Anda akan menderita karena tidak memiliki jumlah yang cukup dalam register. Tidak yakin apakah Anda benar-benar ingin membedakan antara 0,25Hz dan 0,5Hz, tetapi jika Anda melakukannya, maka Anda benar-benar harus menghitung selama empat detik untuk melakukan itu.

Juga, menentukan resolusi datar 0,25 Hz, diartikan secara ketat, berarti Anda akan dapat membedakan 500.000,00 Hz dari 500.000,25 Hz, yang merupakan tingkat presisi yang agak tinggi.

Karena alasan itu, mendesain untuk rentang yang berbeda dapat mengurangi masalah ukuran penghitung. Menarik angka secara acak demi contoh, untuk low-end, katakan 0 hingga 100Hz, hitung untuk interval 10 detik, dan Anda mendapatkan resolusi 0,1 Hz, dan Anda menghitung hanya perlu naik hingga 1000, bahkan 10 bit. Kemudian dari 100Hz ke 10kHz, hitung untuk interval 1 detik; Anda hanya mendapatkan resolusi 1Hz, tetapi penghitung Anda hanya perlu menjalankan hingga 10.000 masih lebih kecil dari 16 bit. Kisaran atas 10kHz hingga 1MHz bisa dihitung hanya 0,01 detik, dan jumlah maks masih hanya 10.000 dan meskipun resolusi Anda akan 100Hz, ini akan menjadi presisi yang masuk akal.


Ya saya sebutkan bahwa dalam pembaruan pertanyaan saya (sebelumnya) bahwa saya harus menghitung hingga 4 detik untuk ... dan saya ya saya ingin dapat membedakan antara katakan 500.000,00Hz dan 500.000,25Hz. Saya telah berpikir untuk menggunakan rentang yang berbeda, saya dapat dengan mudah mengikat ini dengan sisa perangkat keras karena sinyalnya memiliki 6 rentang yang dapat dipilih sehingga saya mungkin dapat merancang enkoder 6 hingga 3 sederhana untuk menunjukkan rentang mana ... tapi saya tidak yakin apakah akan diperlukan jika saya menggunakan penghitung perangkat keras ditambah dengan waktu pembaruan 4 detik, ini harus mengatasi masalah di kedua ujung spektrum
volting

5

Anda dapat mencampur penghitung perangkat keras dan perangkat lunak dengan menghitung luapan penghitung perangkat keras dalam ISR.

Menghitung setiap tepi sinyal dalam ISR akan terlalu lambat untuk sinyal 1 MHz. Saya pikir Anda bisa melakukan hingga 50kHz dengan cara itu.


Ya, Anda mungkin benar - itu akan terlalu lambat untuk 1MHz tetapi Id bayangkan prosesor 20MIPS RISC bisa melakukan lebih baik dari 50KHz. Lagi pula saya juga mempertimbangkan clocking counter biner 8bit dengan sinyal dan menghubungkan carry keluar counter ke pin interupsi eksternal MCU, kemudian membaca frekuensi sinyal sebagai jumlah dari bit carry interupsi ditambah dengan jumlah o / p Nilai penghitung setiap n detik, saya rasa itulah yang Anda maksud ketika Anda mengatakan kombinasi perangkat keras dan penghitung perangkat lunak.
terbang

Saya pikir OP mengacu pada penghitung perangkat keras bawaan. Mereka semua memiliki gangguan overflow yang dapat digunakan untuk meningkatkan rentang penghitungan.
jpc

@ starblue, apakah kode yang saya tulis di bawah ini sesuai dengan jawaban Anda?
vicatcu

2

Alih-alih melakukan penghitung 1 detik, jadikan penghitung 0,1 detik dan kalikan dengan 10?

Jika itu hanya masalah menyimpan nomor penghitung, tidak bisakah Anda menggunakan kode tambahan untuk melacak kapan penghitung akan meluap dan menulis ke lokasi memori lain untuk menjaga penghitungan?


2
Saya pikir saya pasti mengalami pembekuan otak .. solusi paling sederhana yang saya pikir adalah hanya dengan menambah variabel tipe long int setiap kali kenaikan edge terdeteksi. Baca nilai itu setiap detik dan kemudian atur ulang ke nol.
terbang

2
Sebenarnya saya perlu membaca nilai setiap 4 detik untuk mengukur ke 0.25Hz
volting

2

Tidak bisakah Anda menggunakan input capture dan limpahan timer 16-bit timer (ditambah variabel) untuk melakukan pengukuran? Inilah cara saya akan melakukannya dengan ATTiny24A dengan AVR-GCC (tentu saja belum diuji dan berpotensi buggy):

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define TIMER1_BITS           16    // 16 bit timer
#define TIMER1_HZ             8.0e6 // 8MHz crystal
#define TIMER1_OVF_PERIOD_SEC (1.0 * (1 << TIMER1_BITS) / TIMER1_HZ)
#define TIMER1_SEC_PER_TICK   (1.0 / TIMER1_HZ)

//global variables for time keeping
double total_period_sec = 0.0;
uint16_t  num_overflows = 0;

void setup_timer1_capture(){
   // set the ICP (input caputure pin) to a floating input
   DDRA  &= ~_BV(7); // it's A7 on the ATTiny24A...
   PORTA &= ~_BV(7);

   TIMSK1 =   _BV(ICIE1)  // enable input pin capture interrupt
            | _BV(TOIE1); // enable overflow interrupt

   TCCR1B =   _BV(ICNC1)  // activate the input noise canceller
            | _BV(ICES1)  // capture on rising edge of ICP
            | _BV(CS10);  // run the timer at full speed

}

ISR(TIM1_CAPT_vect, ISR_NOBLOCK){ //pin capture interrupt
  uint16_t capture_value_ticks = ICR1; // grab the captured value
  // do some floating point math
  total_period_sec =   1.0 * num_overflows * TIMER1_OVF_PERIOD_SEC
                     + 1.0 * capture_value_ticks / TIMER1_SEC_PER_TICK; 

  num_overflows = 0; // clear helper variable to be ready for next time
}

ISR(TIM1_OVF_vect){   //timer overflow interrupt
    num_overflows++;
}

int main(int argc, char *argv[]){
   setup_timer1_capture();

   sei(); // enable interrupts!

   for(;;){ //forever
      // do whatever you want...
      // the most recently calculated period is available in the 
      // total_period_sec variable 
      // (obviously 1.0 / total_period_sec is the frequency in Hz)
   }

   return 0;
} 

... Bagaimanapun, ia mengkompilasi :)


EDIT Saya melihat output file lss dari kode saya, dan kode yang dihasilkan memiliki terlalu banyak instruksi untuk tidak tersandung pada 1MHz dengan jam 8MHz ... bahkan kenaikan sederhana dengan satu baris dalam TIM1_OVF_vect menghasilkan 19 instruksi! Jadi untuk menangani peristiwa 1MHz, Anda pasti perlu mengoptimalkan, mungkin mendaftar mengalokasikan beberapa hal (mungkin num_overflows dan capture_value_ticks), menggunakan inline assembler (mencuri barang-barang penting dari file lss), dan memindahkan pemrosesan dari interupsi dan ke utama lengkung sedapat mungkin.


Mengukur frekuensi menggunakan periode bekerja dengan baik dengan bentuk gelombang lambat (Anda mengandalkan timer internal lebih cepat daripada sinyal eksternal) tetapi dengan cepat mencapai batas ketika frekuensi sinyal input meningkat. Pada dasarnya, seperti yang Anda temukan, waktu yang dihabiskan di dalam interupsi penangkapan waktu menjadi dominan; tidak ada waktu tersisa untuk menjalankan bagian kode lainnya. Sementara saya tidak begitu terbiasa dengan ATTiny, melihat sekilas pada datasheet menunjukkan bahwa timer / counter1 mendukung penghitungan acara eksternal, jadi biarkan perangkat keras menangani penghitungan.
timrorr

@timrorr, wow ya itu cara yang lebih cerdas untuk melakukannya :) Saya memposting kode AVR-GCC yang diperbarui dalam posting terpisah. Ingin melihat dan melihat apa yang Anda pikirkan?
vicatcu

2

Memposting kode ini sebagai alternatif per saran timrorr ke posting saya sebelumnya. Ini mengkompilasi untuk ATTiny24A menggunakan standar bahasa c99, tapi saya belum benar-benar mengujinya dengan cara apa pun selain itu.

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#define TIMER0_PRELOAD   0x83 // for 8MHz crystal and overflow @ 1kHz
#define TIMER0_PRESCALE 0x03  // divide by 64
#define TASK_PERIOD_MS 4000   // execute task every 4 seconds

//global variables for time keeping
volatile uint16_t  global_num_overflows = 0;
volatile uint16_t  global_task_timer_ms = TASK_PERIOD_MS;

void setup_timers(){
    // set the T1 pin (PA.4) to a floating input (external event)
    DDRA  &= ~_BV(4);
    PORTA &= ~_BV(4);

    // set Timer1 to count external events
    TIMSK1 = _BV(TOIE1);      // enable overflow interrupt
    TCCR1B =   _BV(CS10)      // clock on external positive edge of T1 pin
        | _BV(CS11)
        | _BV(CS12);

    // set Timer0 for task timing (overflow once per ms)
    TCCR0B = TIMER0_PRESCALE;
    TCNT0  = TIMER0_PRELOAD;  // setup appropriate timeout
    TIMSK0 = _BV(TOIE0);      // enable timer0 overflow interrupt
}


ISR(TIM1_OVF_vect){   //timer1 overflow interrupt
    global_num_overflows++;
}

ISR(TIM0_OVF_vect){            //timer0 overflow interrupt @ 1kHz
    TCNT0 = TIMER0_PRELOAD;   // preload timer for 1kHz overflow rate
    if(global_task_timer_ms > 0){
        global_task_timer_ms--;
    }
}

int main(int argc, char *argv[]){
    double frequency_hz = 0;
    uint16_t num_overflows = 0;
    uint16_t num_positive_edges  = 0;
    setup_timers();
    sei(); // enable interrupts!
    for(;;){ //forever
        if(global_task_timer_ms == 0){ // wait for task to be scheduled
            ATOMIC_BLOCK(ATOMIC_FORCEON){
                num_overflows        = global_num_overflows; // copy the volatile variable into a local variable
                global_num_overflows = 0;                    // clear it for next time
                num_positive_edges   = TCNT1;                // copy num positive edge events to local variable
            }

            // calculate the 'average' frequency during this task period
            frequency_hz  = 1.0 * num_positive_edges;  // num edges since last overflow
            frequency_hz += num_overflows * 65536.0;   // edges per overflow of 16 bit timer
            frequency_hz /= (TASK_PERIOD_MS / 1000.0); // over the task interval in seconds

            global_task_timer_ms = TASK_PERIOD_MS;     // reschedule task
        }

        // use frequency_hz for whatever other processing you want to do
    }
    return 0;
}

Ini adalah sedikit penggunaan yang bagus dari kemampuan perangkat keras dari Timer1 dan membebaskan satu ton siklus pemrosesan dibandingkan dengan posting asli saya.


2
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.