Birisi zamanlayıcıları ayarlamak için kullanılan bu garip görünümlü kodu açıklayabilir mi?


10

Diğer insanların yazdığı eskizlere bakarken, bazen şöyle görünen bir kodla karşılaşıyorum:

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

Tek bildiğim bunun zamanlama / zamanlayıcılarla ilgili olduğunu düşünüyorum (sanırım). Böyle bir kodu nasıl deşifre edebilir ve oluşturabilirim? Ne TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1, ve TOIE1?



1
Atmel web sitesinden cihazınız için "Tamamlandı" veri sayfasını indirin ve zamanlayıcılarla ilgili bölümleri okuyun. Bence veri sayfasını okumak şaşırtıcı derecede iyi.
jippie

Yanıtlar:


15

Bu tuhaf görünmüyor. Normal MCU kodu aslında böyle görünüyor.

Burada sahip olduğunuz, bellek eşlemeli çevre birimleri kavramına bir örnektir . Temel olarak, MCU donanımının kendisine atanan MCU'nun SRAM adres alanında özel konumları vardır. Bu adreslere yazarsanız, n adresine yazılan bayt bitleri , periferik m'nin davranışını kontrol eder .

Temel olarak, bazı bellek bankalarının kelimenin tam anlamıyla SRAM hücresinden donanıma giden çok az kablosu vardır. Bu bite bu bayta "1" yazarsanız, bu SRAM hücresini mantıksal bir yüksekliğe ayarlar ve bu da donanımın bir kısmını açar.

MCU'nun başlıklarına bakarsanız, <-> adres eşlemelerinin büyük büyük tabloları vardır. TCCR1BVb ... gibi şeyler derleme zamanında bu şekilde çözülür.

Bu bellek haritalama mekanizması MCU'larda son derece yaygın olarak kullanılmaktadır. Arduino'daki ATmega MCU, PIC, ARM, MSP430, STM32 ve STM8 MCU serilerinin yanı sıra hemen tanımadığım birçok MCU kullanıyor.


Arduino kodu, MCU kontrol kayıtlarına dolaylı olarak erişen işlevlerle garip şeylerdir. Bu biraz "daha hoş" görünmekle birlikte, aynı zamanda çok daha yavaştır ve daha fazla program alanı kullanır.

Gizemli sabitlerin hepsi ATmega328P veri sayfasında çok ayrıntılı olarak açıklanmıştır , bu da bir arduino üzerinde oklüzal olarak geçiş yapan pimlerden daha fazlasını yapmak istiyorsanız gerçekten okumalısınız.

Yukarıda bağlantı verilen veri sayfasından alıntılar seçin:

resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin

Yani, örneğin, TIMSK1 |= (1 << TOIE1);bit ayarlar TOIE1içinde TIMSK1. Bu, ikili dosya 1'i ( 0b00000001) TOIE1bitler tarafından sola kaydırarak TOIE1, bir başlık dosyasında 0 olarak tanımlanarak elde edilir. Bu, daha sonra bu değeri TIMSK1biraz yüksek bir değere ayarlayan geçerli değerine bit yönlü ORed olur .

Bit 0 belgelerine bakıldığında TIMSK1, bunun olarak tarif edilir görebilirsiniz

Bu bit bir taneye yazıldığında ve Durum Kaydındaki I bayrağı ayarlandığında (genel olarak kesmeler etkinleşir), Zamanlayıcı / Sayaç1 Taşma kesmesi etkinleştirilir. TIFR1'de bulunan TOV1 Bayrağı ayarlandığında karşılık gelen Kesme Vektörü (Bkz. ”Kesintiler” sayfa 57) yürütülür.

Diğer tüm satırlar aynı şekilde yorumlanmalıdır.


Bazı notlar:

Bunun gibi şeyler de görebilirsiniz TIMSK1 |= _BV(TOIE1);. _BV()Bir olduğu yaygın olarak kullanılan makro aslen uygulanması libc AVR . daha iyi okunabilirlik avantajıyla _BV(TOIE1)fonksiyonel olarak aynıdır (1 << TOIE1).

Ayrıca, gibi satırlar da görebilirsiniz: TIMSK1 &= ~(1 << TOIE1);veya TIMSK1 &= ~_BV(TOIE1);. Bu zıt işlevi vardır TIMSK1 |= _BV(TOIE1);o ki, getopts bit TOIE1içinde TIMSK1. Bu, tarafından üretilen bit maskesini alarak, _BV(TOIE1)üzerinde bitsel bir NOT işlemi gerçekleştirerek ( ~) ve daha sonra TIMSK1bu NOTed değeriyle (0b11111110) ANDing yaparak elde edilir.

Not olduğunu bu durumlarda Sonuç olarak, gibi şeylerin değeri (1 << TOIE1)veya _BV(TOIE1)tamamen çözümlenir derleme zamanında onlar işlevsel basit sabite azaltmak ve dolayısıyla zamanında bilgi işlem için hiçbir yürütme zaman alabilir bu yüzden.


Düzgün yazılmış kod genellikle , kayıtların ne yapmak için atandığını ayrıntılı olarak kodla birlikte satır içinde yorumlar içerir. İşte son zamanlarda yazdığım oldukça basit bir yumuşak SPI rutini:

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTCPORTCATmega328P içindeki çıkış pinlerinin değerini kontrol eden kayıttır. PINCyazmaçtır girdi değerleri PORTCmevcuttur. Temel olarak, digitalWriteya da digitalReadişlevlerini kullandığınızda bu gibi şeyler dahili olarak gerçekleşir . Bununla birlikte, arduino "pin numaralarını" 50 saat döngü alanında bir yere götüren gerçek donanım pin numaralarına dönüştüren bir arama işlemi vardır. Tahmin edebileceğiniz gibi, hızlı gitmeye çalışıyorsanız, sadece 1 gerektiren bir işlemde 50 saat döngüsünü boşa harcamak biraz saçma.

Yukarıdaki fonksiyon muhtemelen 8 biti transfer etmek için 100-200 saat döngüsü alanında bir yere sahiptir. Bu 24 pin yazma ve 8 okuma gerektirir. Bu, digital{stuff}fonksiyonları kullanmaktan çok, çok daha hızlıdır .


Bu kodun, ATmega328P'den daha fazla zamanlayıcı içerdiğinden Atmega32u4 (Leonardo'da kullanılır) ile de çalışması gerektiğini unutmayın.
jfpoilpret

1
@Ricardo - Küçük MCU gömülü kodun muhtemelen% 90'ı + doğrudan kayıt manipülasyonu kullanır. Dolaylı yardımcı işlevlerle bir şeyler yapmak, IO / çevre birimlerini manipüle etmenin ortak modu değildir. Donanım kontrolünü (örneğin Atmel ASF) soyutlamak için bazı araç takımları vardır, ancak bu genellikle çalışma zamanı yükünü azaltmak için mümkün olduğunca derlemek için yazılmıştır ve neredeyse her zaman veri sayfalarını okuyarak çevre birimlerini anlamayı gerektirir.
Connor Wolf

1
Temel olarak, arduino şeyler, gerçek belgelere veya donanımın yaptığı şeyleri nasıl yaptığını gerçekten zahmete girmeden "burada X yapan işlevler" diyerek çok normal değil. Bunun bir tanıtım aracı olarak değerini anlıyorum, ancak hızlı prototipleme haricinde, gerçek profesyonel ortamlarda gerçekten yapılmıyor.
Connor Wolf

1
Açıkçası, arduino kodunu gömülü MCU ürün yazılımı için alışılmadık yapan şey, arduino koduna özgü değildir , genel yaklaşımın bir işlevidir. Temel olarak, gerçek MCU hakkında iyi bir anlayışa sahip olduğunuzda, işleri düzgün bir şekilde yapmak (örneğin, doğrudan donanım kayıtlarını kullanmak) çok az zaman alır veya fazla zaman almaz. Bu nedenle, gerçek MCU geliştiricisini öğrenmek istiyorsanız, oturmak ve MCU'nuzun gerçekte ne yaptığını anlamak çok daha iyidir , daha sonra sızıntı yapma eğilimi olan başka birinin soyutlamasına güvenmek .
Connor Wolf

1
Burada biraz alaycı olabileceğimi, ancak arduino topluluğunda gördüğüm davranışların çoğunun anti-kalıpları programladığını unutmayın. Bir çok "kopyala-yapıştır" programlaması, kütüphanelerin kara kutu olarak ele alınmasını ve genel olarak toplumdaki genel kötü tasarım uygulamalarını görüyorum. Tabii ki, EE.stackexchange üzerinde oldukça aktifim, bu yüzden biraz moderatör araçlara sahip olduğum için ve biraz kapalı soruları gördüğüm için biraz eğimli bir görünüme sahip olabilirim. Orada gördüğüm arduino sorularında "bana neyi düzeltmek için C&P'ye ne söyleyeceğimi söyle" yönünde bir önyargı var, daha sonra "bu neden çalışmıyor".
Connor Wolf

3

TCCR1A zamanlayıcı / sayaç 1 kontrol yazmacı A

TCCR1B zamanlayıcı / sayaç 1 kontrol kaydı B

TCNT1 zamanlayıcı / sayaç 1'in sayaç değeri

CS12 zamanlayıcı / sayaç için 3. saat seçim bitidir 1

TIMSK1 zamanlayıcı / sayaç 1'in kesme maskesi kaydı

TOIE1 zamanlayıcı / sayaç 1 taşma kesme etkin

Böylece, kod 62.5 kHz'de zamanlayıcı / sayaç 1'i etkinleştirir ve değeri 34286'ya ayarlar. Sonra taşma kesmesini etkinleştirir, böylece 65535'e ulaştığında, büyük olasılıkla şu şekilde etiketlenmiş kesme işlevini tetikler ISR(timer0_overflow_vect)


1

TCCR1B kaydının bit 2'sini temsil ettiği için CS12, 2 değerine sahiptir.

(1 << CS12) 1 değerini alır (0b00000001) ve (0b00000100) almak için 2 kez sola kaydırır. İşlemlerin sırası () içindeki şeylerin önce gerçekleştiğini belirtir, bu nedenle "| =" değerlendirilmeden önce yapılır.

(1 << CS10) 1 değerini alır (0b00000001) ve (0b00000001) almak için 0 kez sola kaydırır. İşlemlerin sırası () içindeki şeylerin önce gerçekleştiğini belirtir, bu nedenle "| =" değerlendirilmeden önce yapılır.

Şimdi TCCR1B | = 0b00000101, TCCR1B = TCCR1B | 0b00000101.

"|" "VEYA" ise, TCCR1B'deki CS12 dışındaki tüm bitler etkilenmez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.