Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

В birinci bölüm Arduino pantolonuyla büyüyen hobi elektronik mühendislerine, mikrodenetleyicilerle ilgili veri sayfalarını ve diğer dokümanları nasıl ve neden okumaları gerektiğini anlatmaya çalıştım. Metnin büyük olduğu ortaya çıktı, bu yüzden pratik örnekleri ayrı bir makalede göstermeye söz verdim. Kendine süt mantarı adını verdi...

Bugün size STM32 (Mavi Hap) ve STM8 kontrolörlerindeki oldukça basit ancak birçok proje için gerekli görevleri çözmek için veri sayfalarını nasıl kullanacağınızı göstereceğim. Tüm demo projeleri en sevdiğim LED'lere adanmıştır, onları büyük miktarlarda aydınlatacağız ve bunun için her türlü ilginç çevre birimini kullanmamız gerekecek.

Metnin yine çok büyük olduğu ortaya çıktı, bu yüzden kolaylık sağlamak için içeriği yapıyorum:

STM32 Mavi Hap: DM16 sürücüsü ile 634 LED
STM8: Altı PWM pininin ayarlanması
STM8: Üç pin üzerinde 8 RGB LED, kesintiler

Yasal Uyarı: Ben mühendis değilim, elektronik konusunda derin bilgiye sahip olduğumu iddia etmiyorum, makale benim gibi amatörlere yöneliktir. Aslında iki yıl önce kendimi hedef kitle olarak görüyordum. Birisi bana tanıdık olmayan bir çip üzerindeki veri sayfalarını okumanın korkutucu olmadığını söyleseydi, internette bazı kod parçalarını aramak ve makas ve yapışkan bantlı koltuk değneği icat etmek için çok fazla zaman harcamazdım.

Bu makalenin odak noktası projeler değil veri sayfalarıdır, bu nedenle kod çok düzenli olmayabilir ve sıklıkla sıkışık olabilir. Yeni çiple ilk tanışma için uygun olmasına rağmen projelerin kendisi çok basit.

Umarım makalem benzer bir hobiye dalma aşamasında olan birine yardımcı olacaktır.

STM32

DM16 ve SPI'lı 634 LED

Mavi Hap (STM32F103C8T6) ve DM634 LED sürücüsünü kullanan küçük bir proje. Veri sayfalarını kullanarak sürücüyü, STM IO bağlantı noktalarını bulacağız ve SPI'yi yapılandıracağız.

DM634

16 adet 16 bit PWM çıkışlı Tayvan çipi zincirler halinde bağlanabilir. Düşük kaliteli 12 bitlik model yerli bir projeden biliniyor Hafif Paket. Bir zamanlar DM63x ile iyi bilinen TLC5940 arasında seçim yaparken, birkaç nedenden dolayı DM'yi seçtim: 1) Aliexpress'deki TLC kesinlikle sahte, ancak bu değil; 2) DM'nin kendi frekans üretecine sahip özerk bir PWM'si vardır; 3) Ali'den bir paket beklemek yerine Moskova'da ucuza satın alınabilir. Ve elbette, hazır bir kütüphane kullanmak yerine çipi kendi başınıza nasıl kontrol edeceğinizi öğrenmek ilginçti. Çipler artık çoğunlukla SSOP24 paketinde sunuluyor; adaptöre lehimlenmesi kolaydır.

Üretici Tayvanlı olduğundan veri Sayfası Çip Çince İngilizce yazılmış, bu da eğlenceli olacağı anlamına geliyor. İlk önce pin çıkışına bakıyoruz (Pin Bağlantısı) hangi bacağın neye bağlanacağını anlamak ve pinlerin açıklamasını (Pin Açıklaması). 16 pin:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
DC Lavabo Kaynakları (Açık Tahliye)

Lavabo / Açık tahliye çıkışı - boşaltmak; akan akımın kaynağı; çıkış aktif durumda toprağa bağlanır - LED'ler sürücüye katotlarla bağlanır. Elektriksel olarak bu elbette bir “açık drenaj” değildir (Açık drenaj), ancak veri sayfalarında drenaj modundaki pinler için bu atama sıklıkla bulunur.

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Çıkış akımı değerini ayarlamak için REXT ve GND arasındaki harici dirençler

REXT pimi ile toprak arasına, çıkışların iç direncini kontrol eden bir referans direnci takılıdır, veri sayfasının 9. sayfasındaki grafiğe bakın. DM634'te bu direnç, genel parlaklığı ayarlayarak yazılım tarafından da kontrol edilebilir (küresel parlaklık); Bu yazıda detaya girmeyeceğim, buraya sadece 2.2 - 3 kOhm'luk bir direnç koyacağım.

Çipin nasıl kontrol edileceğini anlamak için cihaz arayüzünün açıklamasına bakalım:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Evet, işte burada, tüm ihtişamıyla Çince İngilizce. Bunu tercüme etmek sorunludur, isterseniz anlayabilirsiniz, ancak başka bir yol daha var - işlevsel olarak benzer TLC5940 bağlantısının veri sayfasında nasıl tanımlandığına bakın:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
... Cihaza veri girmek için yalnızca üç pin gereklidir. SCLK sinyalinin yükselen kenarı, verileri SIN pininden dahili kayda kaydırır. Tüm veriler yüklendikten sonra, kısa bir yüksek XLAT sinyali sıralı olarak aktarılan verileri dahili kayıtlara kilitler. Dahili kayıtlar XLAT sinyal seviyesi tarafından tetiklenen kapılardır. Tüm veriler önce en anlamlı bit iletilir.

mandal – mandal/mandal/kilit.
Yükselen kenar – darbenin ön kenarı
İlk önce MSB – en anlamlı (en soldaki) bit ileri.
verileri saatlemek – verileri sırayla (bit-bit) iletin.

Kelime mandal genellikle cips belgelerinde bulunur ve çeşitli şekillerde tercüme edilir, bu yüzden anlaşılması adına kendime izin vereceğim

küçük eğitim programıLED sürücüsü aslında bir kaydırma yazmacıdır. "Vardiya" (çalışma) adında - cihazın içindeki verilerin bit bazında hareketi: içeri itilen her yeni bit, tüm zinciri önüne doğru iter. Hiç kimse vardiya sırasında LED'lerin kaotik yanıp sönmesini gözlemlemek istemediğinden, işlem, çalışan kayıtlardan bir sönümleyici () ile ayrılan tampon kayıt defterlerinde gerçekleşir.mandal), bitlerin istenilen sıraya göre düzenlendiği bir tür bekleme odasıdır. Her şey hazır olduğunda deklanşör açılır ve parçalar bir önceki partinin yerine geçerek çalışmaya başlar. Kelime mandal mikro devrelerin belgelerinde, hangi kombinasyonlarda kullanılırsa kullanılsın, neredeyse her zaman böyle bir damper anlamına gelir.

Böylece DM634'e veri aktarımı şu şekilde gerçekleştirilir: DAI girişini uzak LED'in en önemli bitinin değerine ayarlayın, DCK'yı yukarı ve aşağı çekin; DAI girişini bir sonraki bitin değerine ayarlayın, DCK'yi çekin; ve bu şekilde tüm bitler iletilene kadar devam eder (saatli), ardından LAT'ı çekeriz. Bu manuel olarak yapılabilir (ufak patlama), ancak STM32'mizde iki kopya halinde sunulduğu için bunun için özel olarak tasarlanmış bir SPI arayüzünü kullanmak daha iyidir.

Mavi Hap STM32F103

Giriş: STM32 denetleyicileri, Atmega328'den korkutucu göründüğünden çok daha karmaşıktır. Üstelik enerji tasarrufu nedeniyle hemen hemen tüm çevre birimleri başlangıçta kapatılır ve saat frekansı dahili kaynaktan 8 MHz'dir. Neyse ki, STM programcıları çipi "hesaplanan" 72 MHz'e çıkaran kod yazdılar ve tanıdığım tüm IDE'lerin yazarları bunu başlatma prosedürüne dahil etti, bu nedenle saat ayarlamamıza gerek yok (ancak eğer gerçekten istiyorsan yapabilirsin). Ancak çevre birimlerini açmanız gerekecek.

Belgeler: Blue Pill, popüler STM32F103C8T6 çipiyle donatılmıştır; bunun için iki yararlı belge vardır:

Veri sayfasında ilginizi çekebilir:

  • Pinout'lar – çip pinout'ları – panoları kendimiz yapmaya karar vermemiz durumunda;
  • Bellek Haritası – belirli bir çip için bellek haritası. Referans Kılavuzunda tüm hattın bir haritası vardır ve bizimkinin sahip olmadığı kayıtlardan bahseder.
  • Pin Tanımları tablosu – pinlerin ana ve alternatif işlevlerini listeler; “mavi hap” için internette pinlerin ve işlevlerinin bir listesini içeren daha uygun resimler bulabilirsiniz. Bu nedenle hemen Blue Pill pinout'unu Google'da aratıyoruz ve bu resmi elimizde tutuyoruz:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Not: İnternetten alınan resimde yorumlarda belirtilen bir hata vardı, bunun için teşekkür ederiz. Resim değiştirildi, ancak bu bir derstir; bilgileri veri sayfalarından kontrol etmek yerine kontrol etmek daha iyidir.

Veri sayfasını kaldırıyoruz, Referans Kılavuzunu açıyoruz ve artık sadece onu kullanıyoruz.
Prosedür: standart giriş/çıkışla ilgileniriz, SPI'yi yapılandırırız, gerekli çevre birimlerini açarız.

Giriş çıkış

Atmega328'de G/Ç son derece basit bir şekilde uygulanır, bu nedenle STM32 seçeneklerinin çokluğu kafa karıştırıcı olabilir. Şimdi sadece sonuçlara ihtiyacımız var, ancak bunların bile dört seçeneği var:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
açık drenaj, itme-çekme, alternatif itme-çekme, alternatif açık drenaj

"Çekme itme" (itme çekme) Arduino'nun olağan çıkışıdır, pin YÜKSEK veya DÜŞÜK değerini alabilir. Ancak “açık drenaj” ile karmaşa, aslında burada her şey basit olmasına rağmen:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Çıkış konfigürasyonu / port çıkışa atandığında: / çıkış tamponu etkin: / – açık boşaltma modu: çıkış kaydındaki “0” N-MOS'u etkinleştirir, çıkış kaydındaki “1” bağlantı noktasını Hi-Z modunda bırakır ( P-MOS etkinleştirilmez ) / – itme-çekme modu: Çıkış kaydındaki “0” N-MOS'u etkinleştirir, çıkış kaydındaki “1” P-MOS'u etkinleştirir.

Açık drenaj arasındaki tüm farklar (Açık drenaj) “itme-çekme”den (itme çekme) ilk pinin YÜKSEK durumu kabul edememesidir: çıkış yazmacına bir tane yazarken yüksek direnç moduna geçer (yüksek empedans, Merhaba-Z). Sıfır yazarken pin hem mantıksal hem de elektriksel olarak her iki modda da aynı şekilde davranır.

Normal çıkış modunda pin, çıkış yazmacının içeriğini basitçe yayınlar. "Alternatif"te ilgili çevre birimleri tarafından kontrol edilir (bkz. 9.1.4):

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Bir port biti alternatif fonksiyon pini olarak yapılandırılmışsa pin kaydı devre dışı bırakılır ve pin çevresel pin'e bağlanır.

Her pinin alternatif işlevselliği şurada açıklanmıştır: Pin Tanımları Veri sayfası indirilen görüntünün üzerindedir. Bir pinin birden fazla alternatif işlevi varsa ne yapılması gerektiği sorusuna veri sayfasındaki bir dipnotta yanıt veriliyor:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Birden fazla çevre birimi aynı pini kullanıyorsa, alternatif işlevler arasında çakışmayı önlemek için, aynı anda yalnızca bir çevre birimi kullanılmalı ve çevre birimi saat etkinleştirme biti (uygun RCC kaydında) kullanılarak değiştirilmelidir.

Son olarak çıkış modundaki pinlerin de saat hızı vardır. Bu da başka bir enerji tasarrufu özelliği; bizim durumumuzda bunu maksimuma ayarlayıp unutuyoruz.

Yani: SPI kullanıyoruz, bu da iki pinin (veri ve saat sinyali ile birlikte) “alternatif itme-çekme işlevi” olması gerektiği ve diğerinin (LAT) “normal itme-çekme” olması gerektiği anlamına gelir. Ancak bunları atamadan önce SPI ile ilgilenelim.

SPI

Başka bir küçük eğitim programı

SPI veya Seri Çevresel Arayüz (seri çevresel arayüz), bir MK'yi diğer MK'lere ve genel olarak dış dünyaya bağlamak için basit ve çok etkili bir arayüzdür. Çalışma prensibi yukarıda, Çin LED sürücüsü hakkında zaten açıklanmıştır (referans kılavuzunda, bölüm 25'e bakınız). SPI, ana ("ana") ve bağımlı ("bağımlı") modunda çalışabilir. SPI'nin tamamı kullanılamayan dört temel kanalı vardır:

  • MOSI, Ana Çıkış / Bağımlı Giriş: bu pin ana modda veri iletir ve bağımlı modda veri alır;
  • MISO, Master Giriş / Slave Çıkışı: Tam tersi, master'da alır ve Slave'de iletir;
  • SCK, Seri Saat: Master'daki veri aktarım sıklığını ayarlar veya Slave'de bir saat sinyali alır. Esasen vuruş vuruşları;
  • SS, Köle Seçimi: Bu kanal sayesinde köle kendisinden bir şey istendiğini bilir. STM32'de buna NSS denir, burada N = negatif, yani. bu kanalda topraklama varsa kontrol cihazı köle haline gelir. Açık Tahliye Çıkışı moduyla iyi bir şekilde birleşir, ancak bu başka bir hikaye.

Her şey gibi STM32'deki SPI da işlevsellik açısından zengindir, bu da anlaşılmasını biraz zorlaştırır. Örneğin sadece SPI ile değil, I2S arayüzü ile de çalışabilir ve dokümantasyonda açıklamaları karışıktır, fazlalığın zamanında kesilmesi gerekir. Görevimiz son derece basit: sadece MOSI ve SCK'yi kullanarak veri göndermemiz gerekiyor. Bölüm 25.3.4'e (yarı çift yönlü iletişim, yarı çift yönlü iletişim) gidiyoruz ve burada şunu buluyoruz: 1 saat ve 1 tek yönlü veri kablosu (1 saat sinyali ve 1 tek yönlü veri akışı):

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Bu modda uygulama SPI'yi yalnızca iletim veya yalnızca alma modunda kullanır. / Yalnızca iletim modu çift yönlü moda benzer: veriler iletim pini üzerinden iletilir (ana modda MOSI veya bağımlı modda MISO) ve alma pini (sırasıyla MISO veya MOSI) normal bir I/O pini olarak kullanılabilir . Bu durumda uygulamanın yalnızca Rx arabelleğini yok sayması gerekir (okunursa orada aktarılan veri olmayacaktır).

Harika, MISO pini boş, hadi LAT sinyalini ona bağlayalım. STM32'de programlı olarak kontrol edilebilen ve son derece kullanışlı olan Slave Select'e bakalım. 25.3.1 SPI Genel Açıklaması bölümünde aynı isimli paragrafı okuduk:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Yazılım kontrolü NSS (SSM = 1) / Slave seçim bilgisi SPI_CR1 kaydının SSI bitinde bulunur. Harici NSS pini diğer uygulama ihtiyaçları için boş kalır.

Kayıtlara yazmanın zamanı geldi. SPI2'yi kullanmaya karar verdim, temel adresini veri sayfasında - bölüm 3.3 Bellek Haritası'nda aramaya karar verdim:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Neyse başlayalım:

#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))

Bölüm 25.3.3'ü açıklayıcı "Ana Modda SPI'yi Yapılandırma" başlığıyla açın:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

1. Seri saat frekansını SPI_CR2 kaydındaki BR[0:1] bitleriyle ayarlayın.

Kayıtlar aynı isimli referans kılavuzu bölümünde toplanmıştır. Adres kaydırma (Adres ofseti) CR1 – 0x00 için, varsayılan olarak tüm bitler temizlenir (Değeri sıfırla 0x0000):

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

BR bitleri denetleyici saat bölücüsünü ayarlar, böylece SPI'nin çalışacağı frekansı belirler. STM32 frekansımız 72 MHz olacak, LED sürücüsü veri sayfasına göre 25 MHz'e kadar frekansta çalışıyor, bu yüzden dörde bölmemiz gerekiyor (BR[2:0] = 001).

#define _SPI_CR1 0x00

#define BR_0        0x0008
#define BR_1        0x0010
#define BR_2        0x0020

_SPI2_ (_SPI_CR1) |= BR_0;// pclk/4

2. Veri aktarımı ile seri saat zamanlaması arasındaki ilişkiyi tanımlamak için CPOL ve CPHA bitlerini ayarlayın (sayfa 240'taki şemaya bakın)

Burada bir veri sayfası okuduğumuz ve şemalara bakmadığımız için, 704. sayfadaki (SPI Genel Açıklaması) CPOL ve CPHA bitlerinin metin açıklamasına daha yakından bakalım:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Saat fazı ve polarite
SPI_CR1 kaydının CPOL ve CPHA bitlerini kullanarak programlı olarak dört zamanlama ilişkisini seçebilirsiniz. CPOL (saat polaritesi) biti, hiçbir veri iletilmediği zaman saat sinyalinin durumunu kontrol eder. Bu bit ana ve yardımcı modları kontrol eder. CPOL sıfırlanırsa SCK pini dinlenme modunda düşüktür. CPOL biti ayarlanmışsa, dinlenme modunda SCK pini yüksektir.
CPHA (saat fazı) biti ayarlandığında, yüksek bit tuzak flaşı SCK sinyalinin ikinci kenarıdır (CPOL temizse düşer, CPOL ayarlıysa yükselir). Veriler saat sinyalindeki ikinci değişiklikle yakalanır. CPHA biti temizse, yüksek bit tuzak flaşı SCK sinyalinin yükselen kenarıdır (CPOL ayarlıysa düşen kenar, CPOL temizlenmişse yükselen kenar). Veriler saat sinyalindeki ilk değişiklikte yakalanır.

Bu bilgiyi özümsedikten sonra her iki bitin de sıfır kalması gerektiği sonucuna varırız çünkü SCK sinyalinin kullanılmadığı zamanlarda düşük kalmasını ve verinin darbenin yükselen kenarında iletilmesini istiyoruz (bkz. Yükselen kenar DM634 veri sayfasında).

Bu arada, burada ilk kez ST veri sayfalarındaki kelime dağarcığının bir özelliğiyle karşılaştık: içlerinde "biti sıfıra sıfırla" ifadesi yazılıyor biraz sıfırlamak içinVe biraz temizlemekörneğin Atmega gibi.

3. Veri bloğunun 8 bit formatında mı yoksa 16 bit formatında mı olduğunu belirlemek için DFF bitini ayarlayın

DM16 gibi 634 bit PWM verilerini aktarma zahmetine girmemek için özellikle 12 bit DM633 aldım. DFF'yi bire ayarlamak mantıklıdır:

#define DFF         0x0800

_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode

4. Blok formatını belirlemek için SPI_CR1 kaydındaki LSBFIRST bitini yapılandırın

LSBFIRST, adından da anlaşılacağı gibi, iletimi en az anlamlı bit önce olacak şekilde yapılandırır. Ancak DM634 en anlamlı bitten başlayarak veri almak istiyor. Bu nedenle sıfırlamayı bırakıyoruz.

5. Donanım modunda, NSS pininden giriş gerekiyorsa, tüm bayt aktarım sırası boyunca NSS pinine yüksek bir sinyal uygulayın. NSS yazılım modunda, SPI_CR1 kaydındaki SSM ve SSI bitlerini ayarlayın. NSS pini çıkış olarak kullanılacaksa yalnızca SSOE bitinin ayarlanması gerekir.

NSS donanım modunu unutmak için SSM ve SSI'yi yükleyin:

#define SSI         0x0100
#define SSM         0x0200

_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high

6. MSTR ve SPE bitleri ayarlanmalıdır (yalnızca NSS sinyali yüksekse ayarlı kalırlar)

Aslında bu bitlerle SPI’mızı master olarak tanımlayıp açıyoruz:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI yapılandırıldı, hemen sürücüye bayt gönderen fonksiyonları yazalım. 25.3.3 "SPI'yi ana modda yapılandırma" bölümünü okumaya devam edin:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Veri aktarım sırası
İletim, Tx arabelleğine bir bayt yazıldığında başlar.
Veri baytı kaydırma yazmacına şu anda yüklenir: paralel ilk bitin iletimi sırasında mod (dahili veri yolundan), ardından iletilir ardışık MOSI pin modu, CPI_CR1 kaydındaki LSBFIRST bitinin ayarına bağlı olarak ilk veya son bit ileri. TXE bayrağı veri iletiminden sonra ayarlanır Tx tamponundan kaydırma yazmacınave ayrıca CPI_CR1 kaydındaki TXEIE biti ayarlıysa bir kesme oluşturur.

STM kontrolörlerindeki SPI uygulamasının bir özelliğine dikkat çekmek için çeviride birkaç kelimeyi vurguladım. Atmega'da TXE bayrağı (Tx Boş, Tx boş ve veri almaya hazır) yalnızca baytın tamamı gönderildikten sonra ayarlanır dışarı. Ve burada bu bayrak, bayt dahili kaydırma yazmacına eklendikten sonra ayarlanır. Oraya tüm bitlerle aynı anda (paralel olarak) itildiğinden ve ardından veriler sırayla iletildiğinden, TXE, bayt tamamen gönderilmeden önce ayarlanır. Bu önemli çünkü LED sürücümüzde ise gönderdikten sonra LAT pinini çekmemiz gerekiyor. Tüm veriler, yani TXE bayrağı tek başına bize yetmeyecektir.

Bu, başka bir bayrağa ihtiyacımız olduğu anlamına geliyor. 25.3.7 - “Durum Bayrakları”na bakalım:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
<…>
Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
MEŞGUL bayrağı
BSY bayrağı donanım tarafından ayarlanır ve temizlenir (ona yazmanın hiçbir etkisi yoktur). BSY bayrağı SPI iletişim katmanının durumunu gösterir.
Sıfırlanır:
aktarım tamamlandığında (aktarım sürekli ise ana mod hariç)
SPI devre dışı bırakıldığında
ana mod hatası oluştuğunda (MODF=1)
Aktarım sürekli değilse, her veri aktarımı arasında BSY bayrağı temizlenir

Tamam, bu işe yarayacak. Tx tamponunun nerede bulunduğunu bulalım. Bunu yapmak için “SPI Veri Kaydı”nı okuyun:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Bitler 15:0 DR[15:0] Veri Kaydı
Alınan veriler veya iletilecek veriler.
Veri kaydı iki arabelleğe bölünmüştür; biri yazma (iletim arabelleği) ve diğeri okuma (alma arabelleği) için. Veri kaydına yazmak, Tx arabelleğine yazar ve veri kaydından okumak, Rx arabelleğinde bulunan değeri döndürür.

Peki, TXE ve BSY bayraklarının bulunduğu durum kaydı:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Biz yazarız:

#define _SPI_DR  0x0C
#define _SPI_SR  0x08

#define BSY         0x0080
#define TXE         0x0002

void dm_shift16(uint16_t value)
{
    _SPI2_(_SPI_DR) = value; //send 2 bytes
    while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}

Peki, LED sürücü çıkışlarının sayısına göre 16 çarpı iki bayt iletmemiz gerektiğinden, şunun gibi bir şey:

void sendLEDdata()
{
    LAT_low();
    uint8_t k = 16;
    do
    {   k--;
        dm_shift16(leds[k]);
    } while (k);

    while (_SPI2_(_SPI_SR) & BSY); // finish transmission

    LAT_pulse();
}

Ancak LAT pinini nasıl çekeceğimizi henüz bilmiyoruz, bu yüzden G/Ç'ye geri döneceğiz.

Pimleri atama

STM32F1'de pinlerin durumundan sorumlu olan kayıtlar oldukça sıra dışıdır. Atmega'dan daha fazla sayıda olduğu açık ancak diğer STM çiplerinden de farklılar. Bölüm 9.1 GPIO'nun Genel Açıklaması:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Genel amaçlı G/Ç bağlantı noktalarının her biri (GPIO) iki adet 32 ​​bitlik konfigürasyon yazmacına (GPIOx_CRL ve GPIOx_CRH), iki adet 32 ​​bitlik veri yazmacına (GPIOx_IDR ve GPIOx_ODR), bir 32 bitlik ayarlama/sıfırlama yazmacına (GPIOx_BSRR), bir 16 bitlik sıfırlama yazmacına (GPIOx_BRR) ve bir 32-bitlik sıfırlama yazmacına (GPIOx_BRR) sahiptir. bit engelleme kaydı (GPIOx_LCKR).

İlk iki kayıt olağandışıdır ve aynı zamanda oldukça elverişsizdir çünkü 16 bağlantı noktası pini "kardeş başına dört bit" formatında bunların üzerine dağılmıştır. Onlar. sıfırdan yediye kadar olan pinler CRL'dedir ve geri kalanı CRH'dedir. Aynı zamanda, geri kalan kayıtlar, bağlantı noktasının tüm pinlerinin bitlerini başarıyla içerir - genellikle yarısı "ayrılmış" kalır.

Kolaylık sağlamak için listenin sonundan başlayalım.

Engelleme kaydına ihtiyacımız yok.

Ayarlama ve sıfırlama kayıtları birbirlerini kısmen kopyaladıkları için oldukça komiktir: her şeyi yalnızca BSRR'ye yazabilirsiniz, burada daha yüksek 16 bit pini sıfıra sıfırlar ve düşük olanlar 1'e ayarlanır veya ayrıca Alt 16 biti yalnızca pini sıfırlayan BRR'yi kullanın. İkinci seçeneği seviyorum. Bu kayıtlar önemlidir çünkü pinlere atomik erişim sağlarlar:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Atomik Ayarlama veya Sıfırlama
GPIOx_ODR'yi bit düzeyinde programlarken kesintileri devre dışı bırakmaya gerek yoktur: tek bir atomik yazma işlemi APB2 ile bir veya daha fazla bit değiştirilebilir. Bu, değiştirilmesi gereken bitin ayarlama/sıfırlama kaydına (GPIOx_BSRR veya yalnızca sıfırlama için GPIOx_BRR) bir "1" yazılarak gerçekleştirilir. Diğer bitler değişmeden kalacaktır.

Veri kayıtları oldukça açıklayıcı isimlere sahiptir - IDR = Giriş Yön Kaydı, giriş kaydı; ODR = Çıktı Yön Kaydı, çıkış kaydı. Mevcut projede bunlara ihtiyacımız olmayacak.

Ve son olarak kontrol kayıtları. İkinci SPI pinleri olan PB13, PB14 ve PB15 ile ilgilendiğimiz için hemen CRH'ye bakıyoruz:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Ve 20'den 31'e kadar bitler halinde bir şeyler yazmamız gerekeceğini görüyoruz.

Yukarıda pinlerden ne istediğimizi zaten anlamıştık, bu yüzden burada ekran görüntüsü olmadan yapacağım, sadece MODE'un yönü (her iki bit 0'a ayarlanmışsa giriş) ve pin hızını (50MHz'e ihtiyacımız var, yani) belirttiğini söyleyeceğim. her iki pin de “1”) ve CNF modu ayarlar: normal “itme-çekme” – 00, “alternatif” – 10. Varsayılan olarak, yukarıda gördüğümüz gibi, tüm pinler alttan üçüncü bit'e sahiptir (CNF0), onları moda ayarlar değişken giriş.

Bu çiple başka bir şey yapmayı planladığımdan, basitlik adına hem alt hem de üst kontrol kayıtları için olası tüm MODE ve CNF değerlerini tanımladım.

Peki, böyle bir şey

#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000

#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000

Pinlerimiz B portunda bulunur (temel adres – 0x40010C00), kod:

#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))

#define _BRR  0x14
#define _BSRR 0x10
#define _CRL  0x00
#define _CRH  0x04

//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14

//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);

//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;

//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;

Ve buna göre, BRR ve BSRR kayıtları tarafından değiştirilecek olan LAT için tanımları yazabilirsiniz:

/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)

#define LAT_low() _PORTB_(_BRR) = (1<<14)

(Ataletten dolayı LAT_low, hep böyleydi, kalsın)

Şimdi her şey harika ama işe yaramıyor. Bu STM32 olduğu için elektrikten tasarruf sağlarlar, bu da gerekli çevre birimlerinin saat ölçümünü etkinleştirmeniz gerektiği anlamına gelir.

Saat ölçümünü aç

Saat olarak da bilinen saat, saat ölçümünden sorumludur. Ve RCC kısaltmasını zaten fark edebiliyorduk. Bunu belgelerde arıyoruz: bu Sıfırlama ve Saat Kontrolü.

Neyse ki yukarıda da söylediğimiz gibi saat konusunun en zor kısmı bizim için STM çalışanları tarafından yapıldı, kendilerine çok teşekkür ediyoruz (bir kez daha link vereceğim) Di Halt'un web sitesi, ne kadar kafa karıştırıcı olduğunu açıkça belirtmek için). Yalnızca çevresel saatlamayı etkinleştirmekten sorumlu kayıtlara ihtiyacımız var (Çevresel Saat Etkinleştirme Kayıtları). Öncelikle RCC'nin temel adresini bulalım, “Hafıza Haritasının” en başında yer alıyor:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))

Ve sonra ya plakada bir şey bulmaya çalıştığınız bağlantıya tıklayın ya da daha iyisi, ilgili bölümlerdeki etkinleştirme kayıtlarının açıklamalarını inceleyin. kayıtları etkinleştir. RCC_APB1ENR ve RCC_APB2ENR'yi nerede bulacağız:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Ve buna göre SPI2, IOPB (G/Ç Bağlantı Noktası B) ve alternatif işlevlerin (AFIO) saat ölçümünü içeren bitler içerirler.

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001

//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;

//включаем  тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;

Son kodu bulabilirsiniz burada.

Test etme fırsatınız ve isteğiniz varsa DM634'ü şu şekilde bağlayın: DAI'yi PB15'e, DCK'yı PB13'e, LAT'ı PB14'e. Sürücüye 5 volttan güç sağlıyoruz, toprakları bağlamayı unutmayın.

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

STM8 PWM

STM8'de PWM

Bu makaleyi henüz planlarken, örnek olarak, alışılmadık bir çipin bazı işlevlerine yalnızca bir veri sayfası kullanarak hakim olmaya karar verdim, böylece çizmesiz bir ayakkabıcıyla karşılaşmayacağım. STM8 bu rol için idealdi: birincisi, STM8S103'lü birkaç Çin anakartım vardı ve ikincisi, pek popüler değil ve bu nedenle internette okuma ve çözüm bulma isteği bu çözümlerin eksikliğine dayanıyor.

Çip de var veri Sayfası и referans kılavuzu RM0016, ilkinde pin çıkışı ve kayıt adresleri var, ikincisinde - diğer her şey. STM8 berbat bir IDE'de C dilinde programlandı ST Görsel Geliştirme.

Saatleme ve G/Ç

STM8 varsayılan olarak 2 MHz frekansında çalışıyor, bu durumun acilen düzeltilmesi gerekiyor.

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
HSI (Yüksek Hızlı Dahili) Saat
HSI saat sinyali, programlanabilir bir bölücüye (16 ila 1) sahip dahili bir 8 MHz RC osilatöründen türetilir. Saat bölücü kaydında (CLK_CKDIVR) ayarlanır.
Not: Başlangıçta, saat sinyalinin ana kaynağı olarak bölücüsü 8 olan bir HSI RC osilatörü seçilir.

Kayıt adresini veri sayfasında, açıklamayı ise refman'de buluyoruz ve kaydın temizlenmesi gerektiğini görüyoruz:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

PWM'yi çalıştırıp LED'leri bağlayacağımız için pin çıkışına bakalım:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

Çip küçüktür ve birçok işlev aynı pin üzerinde asılıdır. Köşeli parantez içindekiler "alternatif işlevsellik"tir, "seçenek baytları" (seçenek baytları) – Atmega sigortaları gibi bir şey. Değerlerini programlı olarak değiştirebilirsiniz, ancak bu gerekli değildir çünkü Yeni işlevsellik yalnızca yeniden başlatmanın ardından etkinleştirilir. Bu baytları değiştirebilen ST Visual Programmer'ı (Visual Develop ile indirilen) kullanmak daha kolaydır. Pin çıkışı, ilk zamanlayıcının CH1 ve CH2 pinlerinin köşeli parantez içinde gizlendiğini gösterir; AFR1 ve AFR0 bitlerini STVP'de ayarlamak gerekir ve ikincisi ayrıca ikinci zamanlayıcının CH1 çıkışını PD4'ten PC5'e aktaracaktır.

Böylece, 6 pin LED'leri kontrol edecektir: ilk zamanlayıcı için PC6, PC7 ve PC3, ikinci zamanlayıcı için PC5, PD3 ve PA3.

G/Ç pinlerini STM8'de ayarlamak STM32'ye göre daha basit ve daha mantıklıdır:

  • Atmega DDR veri yönü kaydından tanıdık (Veri Yönü Kaydı): 1 = çıkış;
  • birinci kontrol yazmacı CR1, çıkış sırasında itme-çekme modunu (1) veya açık tahliyeyi (0) ayarlar; LED'leri çipe katotlarla bağladığımdan buraya sıfır bırakıyorum;
  • ikinci kontrol kaydı CR2, çıkışta saat hızını ayarlar: 1 = 10 MHz

#define PA_DDR     *(volatile uint8_t *)0x005002
#define PA_CR2     *(volatile uint8_t *)0x005004
#define PD_DDR     *(volatile uint8_t *)0x005011
#define PD_CR2     *(volatile uint8_t *)0x005013
#define PC_DDR     *(volatile uint8_t *)0x00500C
#define PC_CR2     *(volatile uint8_t *)0x00500E

PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast

PWM ayarı

Öncelikle terimleri tanımlayalım:

  • PWM Frekansı – zamanlayıcının çalışma sıklığı;
  • Otomatik yeniden yükleme, AR – zamanlayıcının sayılacağı otomatik olarak yüklenebilir değer (darbe periyodu);
  • Güncelleme Etkinliği, UEV – zamanlayıcı AR'ye kadar saydığında meydana gelen bir olay;
  • PWM Görev Döngüsü – Genellikle “görev faktörü” olarak adlandırılan PWM görev döngüsü;
  • Değeri Yakalayın/Karşılaştırın – zamanlayıcının saydığı yakalama/karşılaştırma değeri bir şey yapacak (PWM durumunda çıkış sinyalini tersine çevirir);
  • Ön Yükleme Değeri – önceden yüklenmiş değer. Değeri karşılaştır zamanlayıcı çalışırken değiştirilemez, aksi takdirde PWM döngüsü bozulur. Bu nedenle, iletilen yeni değerler bir ara belleğe yerleştirilir ve zamanlayıcı geri sayımının sonuna ulaştığında ve sıfırlandığında çıkarılır;
  • Kenar hizalı и Ortaya hizalanmış modlar – sınır boyunca ve merkezde hizalama, Atmel’inkiyle aynı Hızlı PWM и Faz doğru PWM.
  • OCiREF, Çıkış Karşılaştırma Referans Sinyali – referans çıkış sinyali, aslında PWM modunda ilgili pinde görünen şey.

Zaten pin şemasından da anlaşılacağı gibi, iki zamanlayıcının PWM yetenekleri vardır; birincisi ve ikincisi. Her ikisi de 16 bittir, ilkinin birçok ek özelliği vardır (özellikle hem yukarı hem de aşağı sayabilir). Her ikisinin de eşit şekilde çalışmasına ihtiyacımız var, bu yüzden orada olmayan bir şeyi kazara kullanmamak için açıkça daha zayıf olan ikinciyle başlamaya karar verdim. Bazı problemler, referans kılavuzundaki tüm zamanlayıcıların PWM işlevselliğinin açıklamasının ilk zamanlayıcıyla ilgili bölümde olmasıdır (17.5.7 PWM Modu), bu nedenle belge boyunca her zaman ileri geri atlamanız gerekir.

STM8'deki PWM'nin Atmega'daki PWM'ye göre önemli bir avantajı var:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Sınıra Hizalanmış PWM
Aşağıdan yukarıya doğru hesap yapılandırması
TIM_CR1 kaydındaki DIR biti temizlenirse aşağıdan yukarıya doğru sayma aktiftir
Örnek
Örnek ilk PWM modunu kullanıyor. PWM referans sinyali OCiREF, TIM1_CNT < TIM1_CCRi olduğu sürece yüksek tutulur. Aksi halde düşük seviye alır. TIM1_CCRi kaydındaki karşılaştırma değeri otomatik yükleme değerinden (TIM1_ARR kaydı) büyükse OCiREF sinyali 1'de tutulur. Karşılaştırma değeri 0 ise OCiREF sıfırda tutulur....

STM8 zamanlayıcısı sırasında güncelleme etkinliği önce kontrol eder değeri karşılaştırve ancak o zaman bir referans sinyali üretir. Atmega'nın zamanlayıcısı önce bozulur, sonra karşılaştırır, sonuçta compare value == 0 çıktı, bir şekilde ele alınması gereken bir iğnedir (örneğin, mantığı programlı olarak tersine çevirerek).

Peki yapmak istediğimiz şey: 8 bit PWM (AR == 255), aşağıdan yukarıya doğru sayma, kenarlık boyunca hizalama. Ampuller çipe katotlarla bağlı olduğundan, PWM'nin çıkışı 0 (LED açık) olmalıdır. değeri karşılaştır ve 1'den sonra.

Bazılarını zaten okuduk PWM modu, bu nedenle referans kılavuzunda bu ifadeyi (18.6.8 - TIMx_CCMR1) arayarak ikinci zamanlayıcının gerekli kaydını buluruz:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
110: Birinci PWM modu – aşağıdan yukarıya doğru sayarken, ilk kanal TIMx_CNT < TIMx_CCR1 iken aktiftir. Aksi takdirde ilk kanal aktif değildir. [belgenin ilerleyen kısımlarında zamanlayıcı 1'den hatalı kopyala-yapıştır var] 111: İkinci PWM modu – aşağıdan yukarıya doğru sayarken, ilk kanal TIMx_CNT < TIMx_CCR1 iken etkin değildir. Aksi halde ilk kanal aktiftir.

LED'ler MK'ye katotlarla bağlı olduğundan ikinci mod bize uygundur (birincisi de var ama bunu henüz bilmiyoruz).

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Bit 3 OC1PE: Pim 1 önyüklemesini etkinleştir
0: TIMx_CCR1'deki ön yükleme kaydı devre dışı. TIMx_CCR1'e istediğiniz zaman yazabilirsiniz. Yeni değer hemen çalışır.
1: TIMx_CCR1'deki ön yükleme kaydı etkinleştirildi. Okuma/yazma işlemleri ön yükleme kaydına erişir. Önceden yüklenen TIMx_CCR1 değeri, her güncelleme olayı sırasında gölge kaydına yüklenir.
*Not: PWM modunun düzgün çalışması için önyükleme kayıtlarının etkinleştirilmesi gerekir. Tek sinyal modunda bu gerekli değildir (OPM biti TIMx_CR1 kaydında ayarlanır).

Tamam, ikinci zamanlayıcının üç kanalı için ihtiyacımız olan her şeyi açalım:

#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309

#define PWM_MODE2   0x70 //PWM mode 2, 0b01110000
#define OCxPE       0x08 //preload enable

TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);

AR iki sekiz bitlik kayıttan oluşur, her şey basittir:

#define TIM2_ARRH  *(volatile uint8_t *)0x00530F
#define TIM2_ARRL  *(volatile uint8_t *)0x005310

TIM2_ARRH = 0;
TIM2_ARRL = 255;

İkinci zamanlayıcı yalnızca aşağıdan yukarıya doğru sayabilir, kenarlık boyunca hizalanabilir, hiçbir şeyin değiştirilmesine gerek yoktur. Frekans bölücüyü örneğin 256'ya ayarlayalım. İkinci zamanlayıcı için bölücü TIM2_PSCR kaydında ayarlanır ve ikinin katıdır:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Geriye kalan tek şey sonuçları ve ikinci zamanlayıcının kendisini açmak. İlk sorun kayıtlarla çözüldü Yakala/Karşılaştır etkinleştirme: Asimetrik olarak üzerlerine dağılmış iki, üç kanal var. Burada ayrıca sinyalin polaritesini değiştirmenin mümkün olduğunu da öğrenebiliriz; prensip olarak PWM Mod 1'i kullanmak mümkündü. Yazıyoruz:

#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B

#define CC1E  (1<<0) // CCER1
#define CC2E  (1<<4) // CCER1
#define CC3E  (1<<0) // CCER2

TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;

Ve son olarak zamanlayıcıyı TIMx_CR1 kaydında başlatıyoruz:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Karşılaştırma için gerçek değerleri zamanlayıcıya aktaracak basit bir AnalogWrite() analogu yazalım. Kayıtlar tahmin edilebileceği şekilde adlandırılmıştır Kayıtları Yakala/Karşılaştır, her kanal için bunlardan iki tane vardır: TIM8_CCRxL'deki düşük dereceli 2 bit ve TIM2_CCRxH'deki yüksek dereceli bitler. 8 bitlik bir PWM oluşturduğumuz için yalnızca en az anlamlı olan bitleri yazmamız yeterlidir:

#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316

void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
    TIM2_CCR1L = r;
    TIM2_CCR2L = g;
    TIM2_CCR3L = b;
}

Dikkatli okuyucu, %100 doluluk üretemeyen, hafif kusurlu bir PWM'ye sahip olduğumuzu fark edecektir (maksimum 255 değerinde, sinyal bir zamanlayıcı döngüsü için tersine çevrilir). LED'ler için bu önemli değil ve dikkatli okuyucu bunu nasıl düzelteceğini zaten tahmin edebilir.

İkinci zamanlayıcıdaki PWM çalışıyor, ilkine geçelim.

İlk zamanlayıcı, aynı kayıtlarda tamamen aynı bitlere sahiptir (sadece ikinci zamanlayıcıda "ayrılmış" kalan bitler, ilkinde her türlü gelişmiş şey için aktif olarak kullanılır). Bu nedenle veri sayfasında aynı kayıtların adreslerini bulup kodu kopyalamanız yeterlidir. Frekans bölücünün değerini değiştirin çünkü... ilk zamanlayıcı ikinin kuvvetini değil, iki kayıtta tam 16 bitlik bir değer almak istiyor Ön Ölçekleyici Yüksek и Düşük. Her şeyi yapıyoruz ve... ilk zamanlayıcı çalışmıyor. Sorun ne?

Sorun ancak ikinci zamanlayıcıda olmayanı aradığımız zamanlayıcı 1'in kontrol kayıtları hakkındaki bölümün tamamına bakılarak çözülebilir. Olacak 17.7.30 Kırılma kaydı (TIM1_BKR), burada bu bit var:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Ana çıkışı etkinleştir

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Artık kesin olan bu, kod aynı yerde.

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

STM8 Çoklu

STM8'de çoğullama

Üçüncü mini proje ise sekiz adet RGB LED'i PWM modunda ikinci zamanlayıcıya bağlayıp farklı renkler göstermelerini sağlamaktır. LED çoğullama kavramına dayanmaktadır; yani LED'leri çok çok hızlı bir şekilde açıp kapatırsanız, bize sürekli açıkmış gibi görünecektir (görüşün sürekliliği, görsel algının ataleti). bir keresinde yapmıştım Arduino'da buna benzer bir şey.

Çalışma algoritması şöyle görünür:

  • ilk RGB LED'in anotunu bağladı;
  • katotlara gerekli sinyalleri göndererek yaktı;
  • PWM döngüsünün sonuna kadar bekledim;
  • ikinci RGB LED'in anotunu bağladı;
  • yaktı...

Peki, vb. Elbette güzel bir çalışma için anodun bağlı olması ve LED'in aynı anda "ateşlenmesi" gerekir. Peki ya da neredeyse. Her durumda, ikinci zamanlayıcının üç kanalındaki değerleri çıkaracak, UEV'ye ulaşıldığında bunları değiştirecek ve aynı zamanda o anda aktif olan RGB LED'i değiştirecek bir kod yazmamız gerekiyor.

LED geçişi otomatik olduğundan, kesme işleyicisinin veri alacağı bir "video belleği" oluşturmamız gerekir. Bu basit bir dizidir:

uint8_t colors[8][3];

Belirli bir LED’in rengini değiştirmek için bu diziye gerekli değerleri yazmanız yeterli olacaktır. Ve değişken aktif LED'in sayısından sorumlu olacak

uint8_t cnt;

Demux

Düzgün çoğullama için, tuhaf bir şekilde, bir CD74HC238 çoğullayıcıya ihtiyacımız var. Demultiplexer - operatörü donanımda uygulayan bir çip <<. Üç giriş pini (bit 0, 1 ve 2) aracılığıyla ona üç bitlik bir X sayısı besliyoruz ve yanıt olarak çıkış numarasını etkinleştiriyor (1<<X). Çipin geri kalan girdileri tüm tasarımı ölçeklendirmek için kullanılır. Bu çipe yalnızca mikro denetleyicinin dolu pin sayısını azaltmak için değil, aynı zamanda güvenlik için de ihtiyacımız var - kazara mümkün olandan daha fazla LED'i açmamak ve MK'yi yakmamak için. Çipin maliyeti bir kuruştur ve her zaman evinizdeki ecza dolabında saklanmalıdır.

CD74HC238'imiz istenen LED'in anotuna voltaj sağlamaktan sorumlu olacaktır. Tam teşekküllü bir multiplekste, bir P-MOSFET aracılığıyla sütuna voltaj sağlar, ancak bu demoda bu doğrudan mümkündür, çünkü göre 20 mA çekiyor Mutlak Maksimum Puanlar veri sayfasında. İtibaren Veri sayfası CD74HC238 pinout'lara ve bu kopya kağıdına ihtiyacımız var:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
H = yüksek voltaj seviyesi, L = düşük voltaj seviyesi, X – umursama

E2 ve E1'i toprağa, E3, A0, A1 ve A3'ü STM5'in PD3, PC4, PC5 ve PC8 pinlerine bağlıyoruz. Yukarıdaki tablo hem düşük hem de yüksek seviyeleri içerdiğinden bu pinleri push-pull pinleri olarak yapılandırıyoruz.

PWM

İkinci zamanlayıcıdaki PWM, iki farkla önceki hikayedekiyle aynı şekilde yapılandırılmıştır:

Öncelikle kesmeyi etkinleştirmemiz gerekiyor. Etkinliği Güncelle (UEV) aktif LED'i değiştiren bir işlevi çağıracaktır. Bu, biti değiştirerek yapılır. Kesintiyi Güncelle Etkinleştir anlamlı bir isimle bir kayıt defterinde

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
Kesinti etkinleştirme kaydı

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

İkinci fark ise çoğullama olgusuyla ilgilidir. gölgelenme – diyotların parazitik parıltısı. Bizim durumumuzda, UEV'de bir kesintiye neden olan zamanlayıcının ilerlemeye devam etmesi ve zamanlayıcı pinlere bir şeyler yazmaya başlamadan önce kesme işleyicisinin LED'i değiştirmek için zamanı olmaması nedeniyle ortaya çıkabilir. Bununla mücadele etmek için mantığı tersine çevirmeniz (0 = maksimum parlaklık, 255 = hiçbir şey yanmıyor) ve aşırı görev döngüsü değerlerinden kaçınmanız gerekecek. Onlar. UEV'den sonra LED'lerin bir PWM döngüsü boyunca tamamen söndüğünden emin olun.

Polariteyi değiştirme:

//set polarity 
    TIM2_CCER1 |= (CC1P | CC2P);
    TIM2_CCER2 |= CC3P;

r, g ve b'yi 255'e ayarlamaktan kaçının ve bunları kullanırken ters çevirmeyi unutmayın.

Kesintiler

Kesintinin özü, belirli koşullar altında çipin ana programı yürütmeyi durdurması ve bazı harici işlevleri çağırmasıdır. Kesintiler, zamanlayıcı da dahil olmak üzere dış veya iç etkilerden dolayı meydana gelir.

ST Visual Develop'ta ilk kez bir proje oluşturduğumuzda, buna ek olarak main.c gizemli bir dosya içeren bir pencere aldık stm8_interrupt_vector.c, otomatik olarak projeye dahil edilir. Bu dosyada her kesmeye bir işlev atanır. NonHandledInterrupt. Fonksiyonumuzu istenen kesmeye bağlamamız gerekiyor.

Veri sayfasında ihtiyacımız olanı bulabileceğimiz bir kesme vektörleri tablosu vardır:

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler
13 TIM2 güncellemesi/taşması
14 TIM2 yakalama/karşılaştırma

UEV'deki LED'i değiştirmemiz gerekiyor, bu yüzden #13 kesmesine ihtiyacımız var.

Buna göre öncelikle dosyada stm8_interrupt_vector.c 13 numaralı kesintiden (IRQ13) sorumlu işlevin varsayılan adını kendi adınızla değiştirin:

{0x82, TIM2_Overflow}, /* irq13 */

İkinci olarak bir dosya oluşturmamız gerekecek main.h aşağıdaki içerikle:

#ifndef __MAIN_H
#define __MAIN_H

@far @interrupt void TIM2_Overflow (void);
#endif

Ve son olarak bu fonksiyonu dosyanıza yazın. main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // вырубаем демультиплексор
    PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
    PD_ODR |= (1<<5); // включаем демультиплексор

    TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending

    cnt++; 
    cnt &= 7; // двигаем счетчик LED

    TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
    TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Geriye kalan tek şey kesintileri etkinleştirmek. Bu, assembler komutu kullanılarak yapılır rim - onu aramanız gerekecek Programlama Kılavuzu:

//enable interrupts
_asm("rim");

Başka bir montajcı komutu sim – kesintileri kapatır. Yanlış anda meydana gelen bir kesintinin diziyi bozmaması için "video belleğine" yeni değerler yazılırken bunların kapatılması gerekir.

Tüm kod - GitHub'da.

Veri sayfalarını okuma 2: STM32'de SPI; STM8'de PWM, zamanlayıcılar ve kesintiler

En azından birisi bu makaleyi faydalı buluyorsa, boşuna yazmamışım demektir. Yorum ve görüş almaktan memnuniyet duyacağım, her şeye cevap vermeye çalışacağım.

Kaynak: habr.com

Yorum ekle