USB ile Veri Toplama Sistemi

Başlatan Tagli, 07 Mayıs 2019, 10:17:32

Tagli

Hayatımda ilk kez USB ile çalışan bir cihaz geliştirmeye çalışıyorum. Aslında mantık olarak karmaşık bir uygulama değil. Uygulamanın ana gövdesi, SPI üzerinden sürekli olarak veri topluyor ve bir dairesel tampon belleğe (circular buffer) bunu yazıyor. Amacım, talep edildiğinde, bunu USB üzerinden eş zamanlı olarak bilgisayara aktarmak ve veriyi canlı olarak bilgisayarda izleyebilmek.

İşimi fazla zorlaştırmamak için şimdilik full speed (FS) kullanıyorum. Kullandığım işlemci STM32F407VG. HAL falan kullanmadan tamamen register seviyesinde kod yazarak enumeration aşamasını atlattım ve deneme amaçlı olarak bir interrupt endpoint (EP) tanımlayarak, bilgisayar tarafında da pyusb kullanarak veri aktarımı gerçekleştirdim. Ancak ilk paragrafta anlattığım sistemi nasıl kurmam gerektiğinden emin olamadım.

Öncelikle, ses veya video aktarımı gibi eş zamanlı üretim/tüketim gerekmiyor. Yani isochronous transferlere ihtiyacım yok. Ufak bir tampon bellek ayırabilirim, 3-4 kB kadar. Veri aktarımının mümkün olan en hızlı şekilde olmasını istiyorum, çünkü sistemin diğer tarafta veri toplama hızını da bu belirleyecek. Anladığım kadarıyla bulk transferlere ihtiyacım var. Ayrıca, incelediğim standart aygıt sınıfları (device class) da işimi görmüyor.

Burada önemli bir sorun, USB bağlantısı olmasa bile cihazın veri toplamaya ve onu kullanarak işi her ne ise onu yapmaya devam edecek olması. USB bağlantısı olduğunda ise topladığı veriyi canlı olarak bilgisayardan izlemek de mümkün olacak. STM32F407'nin USB donanımı - muhtemelen diğer pek çok cihaz ile benzer bir şekilde - USB verisinin sorgu öncesi hazır edilmesini gerektiriyor. Buna "kurmak" diyelim (bazı İngilizce kaynaklarda "arming" olarak geçiyor). STM32F407'de bir FIFO buffer var ve buraya bir push register'ı ile ulaşıyorsunuz. Yani veriyi oraya yazıktan sonra değiştirme şansı yok. Ancak flush edilip silinebiliyor. Bu haliyle donanımın STM32F103 gibi cihazlardan farklı olduğunu söyleyebiliriz - gerçi onlarda da değişikliğin mümkün olup olmadığından emin değilim. USB bağlantısının her zaman aktif olacağını varsayamayacağım için veriyi önceden FIFO'da bekletemem, çünkü bu durumda USB bulk IN talebi yolladığında güncelliğini kaybetmiş eski bir veri paketi yollanmış olur.

USB ile ilgili bir başka mesele, bilgisayardan gelen veri taleplerinin (IN paketlerinin) kaç byte istedikleri bilgisine sahip olmamaları. Bilgisayarın umduğu bir değer var, bilgisayar tarafındaki yazılım bunu biliyor ve buna göre gerekli sayıda IN transaction üretiyor. Ama uC bunu bilmiyor. Kısa paket, yani umulandan az sayıda byte gönderebilir ama fazla gönderirse sorun çıkar bilgisayar tarafında. Bu durumda anladığım kadarıyla bilgisayarın körlemesine IN paketi göndererek talepte bulunması mümkün değil (üst paragraftaki sorunla da beraber düşünüldüğünde). Bilgisayarın kaç byte istiyorsa bunu önce bir talep olarak uC'ye iletmesi lazım, muhtemelen bir bulk OUT EP kullanarak. uC ise bunun hemen ardından bir bulk IN talebinin geleceğini bilerek ilgili EP'yi kurmalı. Hem böylece bilgisayara güncel bir veri gönderilmiş olur.

Peki bilgisayar uC'den kaç byte veri talep etmeli? Bu noktada da bir interrupt IN EP kullanabilirim diye düşündüm. uC, kendi dahili tampon belleğinde aktarılmayı bekleyen kaç byte olduğunu düzenli olarak bu EP sayesinde bilgisayara bildirir. Yukarıda bahsedilen güncelliği geçmiş bilgi sorunu burada da var, ama bilgisayar ilk bağlantı kurduğunda SetConfiguration gibi bir komutla uC'deki USB sistemini resetleyebilir. Interrupt EP de bu noktadan sonra güncel verilere sahip olur. Ancak sistemi öyle bir kurmalıyım ki, uC tampon belleğe SPI'dan DMA ile gelen verileri yazarken, USB ise onun birkaç adım önünde olup bu verileri arada çakışma olmadan bilgisayara aktarabilsin. Kafamda çember çizerek kendi kuyruğunu kovalayan bir yılan canlanıyor. Kafa tarafı SPI/DMA, kuyruk tarafı USB. Kafa kuyruğu yakalamamalı. Ama tabi USB bağlantısı yokken bu ister istemez olacak. USB bağlantısı kurulduğunda kafa ve kuyruk arasında güvenli bir mesafe bırakıp aktarıma başlamam gerekiyor.

Uzun bir mesaj oldu. Pek soru formatında da olmadı aslında. Aklıma gelen fikri yazdım ve işe yarayıp yaramayacağından emin olamadığım için fikirlerinizi almak istedim. İçinizde benzer konuda uygulama yapanınız var mı?
Gökçe Tağlıoğlu

kralsam

Selamlar hocam,

Ben şuan benzer bir çalışma yapmayı düşünüyorum. Vaktim olmadığı için hal kütüphaneleri ve USB içinde vcp düşünüyorum.

Daha önce bahsettiğim şekilde uygulama örnekleri denedim ama daha çok 1-2Mbit data aktarımı gerekiyordu. Sen SPI ile yapacağım dediğin an 20Mbit e yada 50Mbit e yakın bir data transfer gerekiyor olabilir. Eğer öyle ise yanlış hatırlamıyorsam FS USB 12Mbit. Öncelikle bunu hatırlatayım. Diğer konu da teorik olarak senin USB bus üzerinden periyodik olarak düşündüğünde asenkron data bile aktarsan hız olarak spı datasından hızlı olmalı. Bu gerçekleştiği sürece bir paket tanımlayıp sayısını da söyleyip gönderebilirsin.(ilk aklıma gelen)
Bunun dışında önceki uygulamada sebebini anlamadığım bir USB kopma sorunu yaşamıştım. Sanırım işletim sistemi tarafının bir buffer limiti var buna da dikkat etmelisin. Sanırım taşma olunca işletim sistemi USB yi salıyor. :)

Gelişmeleri yazarsan seviniriz.

İyi çalışmalar.

Tagli

Projenin SPI tarafı harici bir ADC çipi ve maksimum hızda çalıştığında saniyede 3.2 MB veri üretiyor. USB FS ile bu hıza yetişmemin mümkün olmadığını biliyorum. Projede her ne kadar ADC çipinin mümkün olduğunca hızlı çalışması istense de, çipin USB'nin kaldırabileceği hıza kadar yavaşlatılması mümkün. Sistemi iyi kurabilirsem 800 kB/s civarında bir hıza ulaşabileceğimi ve ADC çipini hiç değilse %25 kapasite ile kullanabileceğimi umuyorum.

Bu sistemi güzel bir şekilde oturtabilirsem ileride HS ile de deneme yapacağım. HS hız ihtiyacını fazlası ile karşılayacaktır.
Gökçe Tağlıoğlu

kralsam

3.5 megabit ise yapabilmesi gerekir.

Tagli

Sorunu çözdüm gibi. İlk mesajımın sonunda bahsettiğim gibi oldu genel olarak.

İşlemcide 4000 byte'lık bir üçlü buffer var, yani toplamda 12K. SPI'ın DMA'sı buraya double/circular buffer ile yazıyor. Tabi 2 değil 3 buffer olduğundan kesmede doğru yeri seçmek gerekiyor.

IN1 endpoint'i, eğer tam dolu bir buffer varsa 4000, ve ek olarak yazılmakta olan buffer'ın doluluk miktarını söylüyor. Mesela 5000 gibi. Bilgisayar her frame'de (1 ms) bunu soruyor. Endpoint'in kurulmasını bir kesme ile 500 us'de bir yapıyorum. Haliyle 2 kesmeden biri boşa sıkıyor ve endpoint zaten kurulu olduğundan veriyi güncellemiyor. Bu sorun değil.

3 buffer'ın biri güvenlik amaçlı. Yeni buffer'a geçilince arkadaki eğer bilgisayara aktarılmamışsa ölüyor. Yani 7999'dan sonra veri miktarı 4000'e düşüyor. Bilgisayar  IN1'den gelen veri 4000'den fazla ile OUT2'ye "1"'den ibaret bir sinyal gönderiyor ve IN2'den 4000 byte okuyor.

Aşağıdaki görüntüde, üst sıradaki her bir blok 4000 byte. Bu şekilde yaklaşık 512 kB/s hıza ulaşabiliyorum (kilobit değil, kilobyte). Biraz daha zorlayıp o aradaki boşlukları da kapatmak mümkün olabilir ama bence hattı tam kapasitede zorlamak iyi bir fikir değil.
[IMG]http://i67.tinypic.com/15i7saw.jpg[/img]
Gökçe Tağlıoğlu

kralsam

Selamlar, USB FS kullanıyorsanız 48Mhz USB bus frekansı var diye biliyorum. Bu Logic analyzer da sizi yanıltır.

Bunun dışında iç sebeplerle mümkün olmayabilir ama 12mbit yada 1.5 Mbyte iletişim sağlayabiliyor. Öyle ise siz bence 1 e zorlayın vaktiniz varsa.

Bende yakın zamanda başlıyorum bir çalışmaya unutmazsam durumu yazarım.

İyi çalışmalar.

Tagli

USB FS 12 Mbit/s, bir bit süresi de 83.33 ns civarında. 24 MHz'lik analizör zorla da olsa yetişiyor. Bazen hatalı okuma yapıp paketi kaçırabiliyor ama genelde doğru gösteriyor.
Gökçe Tağlıoğlu

Tagli

Sistemi düşük frekansta örnekleme yapacak şekilde ayarladığımda, buffer'ın dolması için fazla beklememek adına onun boyutunu da düşürüyorum. Ancak düşük frekanslarda yeni bir sorun ortaya çıktı: Öyle sanıyorum ki EP1 IN'den (interrupt endpoint) gelen buffer doluluk verisi bazı durumlarda güncelliğini kaybediyor ve bilgisayar yeni buffer dolmadan bir daha talepte bulunuyor. Bu durumda, aynı veri birden fazla kez bilgisayara aktarılıyor.

Tahminimce olan şey şu:
1) uC içindeki kesme buffer durumunu EP1 IN'e yazıyor, yani EP'yi kuruyor.
2) Ardından daha önce planlanmış bir okuma emri geliyor (EP2 IN) ve buffer bilgisayara aktarılıyor. EP1'deki değer güncelliğini yitiriyor.
3) EP1'e okuma emri geliyor, artık güncel olmayan (gerçek değerden daha büyük bir değer) bilgisayara aktarılıyor.
4) Bilgisayar buffer'ın dolu olduğunu sanıp tekrar okuma talebi gönderiyor ve zaten okunmuş eski buffer'ı tekrar okuyor.

Dediğim gibi, bu durum küçük boyutlu transferlerde ortaya çıkıyor. Çözüm için uC kodunda şöyle yaptım: Eğer yeni buffer dolmadan eskisi için tekrar bir talep olursa, boş cevap (ZLP) gönder. Güzel de çalıştı gibi.

Sonra aklıma başka bir şey geldi. Yukarıda anlattığım şeyi yaptığımda EP1 IN'e pek de gerek kalmıyor. Yani buffer bilgisini okumama gerek yok aslında. Körlemesine buffer okuma talebi yaparım. Eğer hazır veri varsa zaten gelir. Yoksa ZLP alırım. ZLP alınca 1 ms bekler tekrar denerim. Bu yöntemi de denedim. Çok fazla test yapmadım ama şimdilik düzgün çalışıyor gibi. Yani aslında işi baştan gereksiz yere karmaşıklaştırmışım.
Gökçe Tağlıoğlu

kralsam

Ben son yaptığını yaparsın diye düşünmüştüm. Bence bu yöntem hızlı olanda da sorun çıkarmaz.

Bu arada bilgisayar tarafında ne İle yazıyorsun libusb mi?

Tagli

Evet, interrupt EP kullanmadığım yöntem hem hızlı hem de yavaş örnekleme frekanslarında sorunsuz çalışıyor gibi.

Bilgisayar tarafında dolaylı olarak libusb kullanıyorum. Aslında pyusb kullanıyorum, o da arkada libusb'yi çağırıyor. Hem Windows hem de Ubuntu'da denedim. Windows tarafında, Zadig diye bir program yardımıyla sürücü ataması yapmak gerekiyor. Windows 7'de Zadig ile doğrudan çalışırken, Windows 10'da libusb'nin .dll dosyasını bulamadı nedense ve .py dosyasının yanına benim elle koymam gerekti (halbuki system32 altında dosya var). Nerede hata yaptığımı bulamadım ama sonuçta çalıştı bir şekilde.
Gökçe Tağlıoğlu

kralsam

bakalım bende bir çalışmaya başladım. Daha önce USB çeşitlerine çok bakmamıştım. Geçenlerde HID bağlantının 65kb/s civarı bir hızı olduğunu görünce şaşırdım. Aslında kafamdaki tek endişe ürün olarak yapmayı planladığım cihaz ile ilgili windows driver problemi çıkarması. Zadig falan pek kullanışlı gelmiyor. Bu konular ile ilgili bir incelemen yada tecrüben oldu mu? Yada sertifika uyarılarına takılmadan bir driver sağlamak mümkün olacak mı?

JKramer

Alıntı yapılan: Tagli - 01 Temmuz 2019, 10:43:50Windows 7'de Zadig ile doğrudan çalışırken, Windows 10'da libusb'nin .dll dosyasını bulamadı nedense ve .py dosyasının yanına benim elle koymam gerekti (halbuki system32 altında dosya var). Nerede hata yaptığımı bulamadım ama sonuçta çalıştı bir şekilde.
Yazılımın ve işletim sisteminin 32-64 bit olmasıyla ilgisi olabilir: https://www.howtogeek.com/326509/whats-the-difference-between-the-system32-and-syswow64-folders-in-windows/