Bare-Metal STM32: I2C Veri Yolunun Ana Alıcı-Verici Modunda Kullanılması


Sistemler içinde yerleşik ve yerleşik iletişim için günümüzün en popüler veri yollarından biri olarak, onu gömülü bir sistemle kullanma ihtimaliniz yüksek. I2C, yalnızca iki kablo (saat ve veri) gerektirirken çeşitli hızlar sunar; bu, SPI gibi alternatiflere göre kullanımı önemli ölçüde kolaylaştırır. STM32 MCU ailesinde, her cihazda en az bir I2C çevre birimi bulacaksınız.

Paylaşılan, yarı çift yönlü bir ortam olarak I2C, bir cihazın saati kontrol ettiği ve diğer cihazların I2C veriyolunda sabit adresleri gönderilene kadar bekleyip dinlediği oldukça basit bir çağrı ve yanıt tasarımı kullanır. Bir STM32 I2C çevre birimini yapılandırmak birkaç adım gerektirirken, bu makalede göreceğimiz gibi sonrasında kullanımı oldukça acısızdır.

Basit adımlar

Sensörler gibi alıcı cihazların, gerekli çekme dirençleri ile düzgün şekilde kablolandığını varsayarsak, MCU’nun I2C çevre birimini yapılandırmaya başlayabiliriz. STM32F042’yi hedef MCU olarak kullanacağız, ancak diğer STM32 aileleri I2C perspektifinden oldukça benzer. Ayrıca CMSIS tarzı çevre birimini kullanacağız ve referansları kaydedeceğiz.

İlk olarak, uygun alternatif fonksiyon (AF) modunu etkinleştirerek I2C çevre birimi için kullanmak istediğimiz GPIO pinlerini ayarlıyoruz. Bu, hedef MCU’nun veri sayfasında belgelenmiştir. STM23F042 MCU için standart SCL (saat) pini AF 5 ile PA11 üzerindedir. SDA (veri) aynı AF ile PA12 üzerinde bulunur. Bunun için GPIO_AFRH (alternatif fonksiyon kaydı yüksek) kaydında uygun bitleri ayarlamamız gerekiyor:

AF değerleri ile STM32F042 üzerinde GPIO_AFRH.

11 ve 12 (AFSEL11 ve AFSEL12) pinleri için AF 5 seçildiğinde, bu pinler daha sonra dahili olarak ilk I2C çevre birimine (I2C1) bağlanır. Bu, UART ile ilgili önceki bir makalede yaptığımıza benzer. Ayrıca GPIO_MODER’daki pin için AF modunu etkinleştirmemiz gerekiyor:

STM32F0x2 GPIO_MODER düzeni (RM0091, 8.4.4).

Bütün bunlar aşağıdaki kod kullanılarak yapılır:



uint8_t pin = 11;                // Repeat for pin 12
uint8_t pin2 = pin * 2;
GPIOA->MODER &= ~(0x3 << pin2); 
GPIOA->MODER |= (0x2 << pin2);   // Set AF mode.

// Set AF mode in appropriate (high/low) register.
if (pin < 8) { 
    uint8_t pin4 = pin * 4; 
    GPIOA->AFR[0] &= ~(0xF << pin4); 
    GPIOA->AFR[0] |= (af << pin4); 
} 
else { 
    uint8_t pin4 = (pin - 8) * 4; 
    GPIOA->AFR[1] &= ~(0xF << pin4); 
    GPIOA->AFR[1] |= (af << pin4);
}


SCL ve SDA pinlerinin her ikisinin de GPIO kayıtlarında, pullup veya pulldown olmadan kayan bir durumda ve açık tahliye konfigürasyonunda yapılandırılmasını istediğimizi unutmayın. Bu, açık tahliye olacak şekilde tasarlanmış I2C veri yolunun özellikleriyle eşleşir. Etkili bir şekilde bu, veri yolu üzerindeki bir ana veya bağımlı cihaz tarafından aşağı çekilmedikçe, veri yolu hatlarındaki çekmelerin sinyali yüksek tuttuğu anlamına gelir.

İlk I2C çevre biriminin saati şurada etkinleştirilir: RCC_APB1ENR (kayıt etkinleştir) ile:

RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;

Bazı STM32F0 MCU’larda yalnızca tek bir I2C çevre birimi (STM32F03x ve F04x) bulunurken, diğerlerinde iki tane bulunur. Ne olursa olsun, I2C çevre birimi varsa, bu kayıtta saat etkinleştirme bitini ayarladıktan sonra, şimdi I2C çevre biriminin kendisini bir ana olarak yapılandırmaya geçebiliriz.

Saat Yapılandırması

I2C çevre birimiyle başka bir şey yapmadan önce, devre dışı durumda olduğundan emin olmalıyız:

I2C1->CR1 &= ~I2C_CR1_PE;

Saat ayarları yapılır I2C_ TIMINGR:

I2C_TIMINGR düzeni, RM0091 (26.7.5) uyarınca
I2C_TIMINGR düzeni, RM0091 (26.7.5) uyarınca

Başvuru kılavuzu, I2C saatine bağlı olarak, örneğin STM32F0’da 8 MHz I2C saat hızı için zamanlama ayarlarına sahip bir dizi tabloyu listeler:

IC2_TIMINGR yapılandırma örneği tablosu.  Kaynak: RM0091, 26.4.10.
IC2_TIMINGR yapılandırma örneği tablosu. Kaynak: RM0091, 26.4.10.

Bu tablo, bu değerleri I2C_TIMINGR’a eklemek için doğru sıraya koyarak, örneğin STM32F0 için, I2C çevre birimini yapılandırmak için kullanıma hazır bir değerler dizisine dönüştürülebilir:



uint32_t i2c_timings_4[4];
uint32_t i2c_timings_8[4];
uint32_t i2c_timings_16[4];
uint32_t i2c_timings_48[4];
uint32_t i2c_timings_54[4];

i2c_timings_4[0] = 0x004091F3;
i2c_timings_4[1] = 0x00400D10;
i2c_timings_4[2] = 0x00100002;
i2c_timings_4[3] = 0x00000001;
i2c_timings_8[0] = 0x1042C3C7;
i2c_timings_8[1] = 0x10420F13;
i2c_timings_8[2] = 0x00310309;
i2c_timings_8[3] = 0x00100306;
i2c_timings_16[0] = 0x3042C3C7;
i2c_timings_16[1] = 0x30420F13;
i2c_timings_16[2] = 0x10320309;
i2c_timings_16[3] = 0x00200204;
i2c_timings_48[0] = 0xB042C3C7;
i2c_timings_48[1] = 0xB0420F13;
i2c_timings_48[2] = 0x50330309;
i2c_timings_48[3] = 0x50100103;
i2c_timings_54[0] = 0xD0417BFF;
i2c_timings_54[1] = 0x40D32A31;
i2c_timings_54[2] = 0x10A60D20;
i2c_timings_54[3] = 0x00900916;


Burada mevcut olan diğer seçenekler, STMicroelectronic tarafından sağlanan araçların (örn. CubeMX) değerleri sizin için hesaplamasına izin vermek veya kendiniz hesaplamak için referans kılavuzdaki bilgileri kullanmaktır. Bu noktada, Nodate çerçevesi I2C uygulaması STM32 için, STM32F0 için yukarıdaki gibi önceden tanımlanmış değerlerle ve diğer aileler için dinamik olarak hesaplanan değerlerle her ikisini de kullanır.

Zamanlama değerlerini dinamik olarak hesaplamanın avantajı, önceden tanımlanmış I2C saat hızlarına bağlı olmamasıdır. Bir dezavantaj olarak, bu değerlerin doğrudan bir tablodan okunması yerine hesaplanmasında ek gecikme vardır. Hangi yaklaşım en iyi sonucu verirse versin, projenin gereksinimlerine bağlıdır.

İle I2C_TIMINGR bu şekilde yapılandırılmış kayıt yapın, çevre birimini etkinleştirebiliriz:

I2C1->CR1 |= I2C_CR1_PE;

Veri Yazma

I2C çevre birimi hazır ve beklemedeyken veri göndermeye başlayabiliriz. Bir USART’ta olduğu gibi, bu bir iletim (TX) kaydına yazıp iletimin tamamlanmasını bekleyerek yapılır. Burada izlenecek adımlar, başvuru kılavuzunda sağlanan yardımcı akış şemasında ele alınmıştır:

RM0091'den yeniden üretilen ana verici akış şeması.
RM0091’den yeniden üretilen ana verici akış şeması.

Burada dikkate alınması gereken nokta, I2C_ISR_TC (aktarım tamamlandı) gibi bazı kontrollerde, fikrin bir kez kontrol edip yapılması değil, bir zaman aşımı ile beklemek olduğudur.

1 baytlık basit bir aktarım için I2C_CR2’yi şu şekilde ayarlardık:



I2C1->CR2 |= (slaveID << 1) | I2C_CR2_AUTOEND | (uint32_t) (1 << 16) | I2C_CR2_START;


Bu, 7 biti hedefleyen toplam 1 baytlık (I2C_CR2 kaydındaki NBYTES konumuna sola kaydırılmış) aktarımı başlatır. slaveID, otomatik olarak oluşturulan I2C durdurma koşuluyla. Aktarım tamamlandıktan sonra (NBYTES aktarıldı), STOP oluşturulur ve bu, I2C_ISR’de STOPF olarak adlandırılan bir bayrak ayarlar.

Veri aktarımının bittiğini bildiğimizde, bu bayrağın ayarlanmasını beklemeliyiz, ardından I2C_ICR’deki bayrağı ve I2C_CR2 kaydının temizlenmesini izlemeliyiz:



instance.regs->ICR |= I2C_ICR_STOPCF;
instance.regs->CR2 = 0x0;


Bu, temel bir veri aktarımını tamamlar. Tek bir bayttan fazlasını aktarmak için, aynı prosedürü döngüye alarak tek bir bayt yazmanız yeterlidir. I2C_TXDR her döngü ve beklemek I2C_ISR_TXIS ayarlanacak (gerekli zaman aşımı ile). 255 bayttan fazlasını aktarmak için, ayar I2C_CR2_RELOAD yerine I2C_CR2_AUTOEND I2C_CR2’de 255 bayt veya daha az yeni bir partinin aktarılmasına izin verecektir.

Veri Okuma

Bir cihazdan veri okurken, kesmelerin devre dışı bırakıldığından emin olun (kullanarak NVIC_DisableIRQ). Genel olarak, mikrodenetleyici tarafından cihaza bir okuma talebi gönderilir ve cihaz, istenen kayıt içeriğini cevap olarak göndererek yanıt verir. Örneğin, bir BME280 MEMS sensörü gönderilirse 0xd0 sadece yük olarak, fabrikada bu register’a programlandığı gibi (sabit) ID’sini geri göndererek yanıt verecektir.

Bir cihazdan alım için temel akış şeması aşağıdaki gibidir:

STM32F0 için ana alıcı akış şeması.  Kaynak: RM0091.
STM32F0 için ana alıcı akış şeması. Kaynak: RM0091.

Buradaki temel fikir, veri iletmekle aynıdır. I2C_CR2’yi öncekiyle aynı şekilde yapılandırıyoruz. Buradaki temel farklar, I2C_ISR_RXNE bayrağının ayarsız hale gelmesini beklememiz ve ardından I2C_RXDR’nin tek baytlık içeriğini arabelleğimize okuyabilmemizdir.

Tıpkı veri yazarken olduğu gibi, NBYTES’i okuduktan sonra, I2C_ISR_STOPF bayrağının ayarlanmasını beklememiz, ardından I2C_ICR kaydı aracılığıyla ve I2C_CR2 kaydının temizlenmesini beklememiz gerekiyor.

Kesintiye Dayalı Okumalar

I2C ile kesmeleri ayarlamak, söz konusu I2C çevre birimi için kesmeleri etkinleştirmemizi gerektirir. Bu, çevre birimi devre dışı durumdayken yapılmalıdır. Bundan sonra kesmeyi etkinleştirebiliriz:



NVIC_SetPriority(I2C1_IRQn, 0);
NVIC_EnableIRQ(I2C1_IRQn);


Daha sonra, yapılandırma biti ayarlanarak çevre biriminde kesintiler etkinleştirilir:



I2C1->CR1 |= I2C_CR1_RXIE;


Uygun şekilde adlandırılmış kesme işleyicisinin (ISR), başlatma kodunda belirtilen adla uygulandığından emin olun:



volatile uint8_t i2c_rxb = 0;

void I2C1_IRQHandler(void) {
    // Verify interrupt status.
    if ((I2C->ISR & I2C_ISR_RXNE) == I2C_ISR_RXNE) {
        // Read byte (which clears RXNE flag).
        i2c_rxb = instance.regs->RXDR;
    }
}


eklemeyi unutmayın extern "C" { } C dışında bir dil kullanılıyorsa, işlev adının karışmasını önlemek için işleyicinin etrafını engelleyin.

Bu kod yerindeyken, okuma arabelleği her bayt aldığında, ISR çağrılır ve onu bir arabelleğe veya başka bir yere kopyalayabiliriz.

Çoklu Cihaz Kullanımı

Bu noktada tahmin edilebileceği gibi, tek bir mikro denetleyici alıcı-vericiden birden fazla cihaz kullanmak, herhangi bir yükten önce yalnızca doğru cihaz tanımlayıcısının gönderilmesini gerektirir. Bu ayrıca, cihaz tanımlayıcılarında herhangi bir karışıklığı önlemek için I2C_CR2 kaydının temizlenmesi ve bir sonraki gönderme veya alma döngüsünde doğru şekilde ayarlanmasının gerekli olduğu yerdir.

Kod uygulamasına bağlı olarak (örneğin, çok iş parçacıklı bir RTOS ile), çakışan okuma ve yazmaların gerçekleşmesi olasıdır. Bu durumda, I2C yazma ve okuma işlemlerinin koordineli olması, hiçbir veri veya komutun kaybolmaması veya yanlış cihaza gönderilmemesi önemlidir.

toparlamak

STM32’de I2C’yi kullanmak, saat konfigürasyonunu ayarlamanın önündeki engeli kaldırdıktan sonra çok karmaşık değildir. Bu, saat germe ve gürültü filtreleme gibi I2C ile ilgili ileri düzey konularla birlikte kendi makalesine layık olabilecek bir konudur. Varsayılan olarak, STM32 MCU’lardaki I2C çevre birimi, I2C girişlerinde etkinleştirilmiş bir gürültü filtresine sahiptir, ancak bunlar ayrıca yapılandırılabilir.

I2C ile temel okuma ve yazma kadar kolay olsa da, STM32’de kendi I2C cihazınızı uygulamaya gelince de keşfedilecek koca bir tavşan deliği var. Bu konulardaki diğer makaleler için bizi izlemeye devam edin.


Kaynak : https://hackaday.com/2022/05/11/bare-metal-stm32-using-the-i2c-bus-in-master-transceiver-mode/

Yorum yapın