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:
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:
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
:
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:
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:
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:
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/