Toto je starší verze dokumentu!
Vypracoval Jonáš Čech
Vytvořte aplikaci na 32F746GDISCOVERY s dotykovým displejem, na kterém bude mít uživatel možnost zvolit nastavení AD převodníku (počet kanálů, rychlost vzorkování, …), dvou programovatelných zesilovačů (zisk, offset, …) a způsob spuštění vzorkování (externí signál nebo úrovní měřeného signálu). Získaná data z ADC během měření ukládejte do externí SDRAM paměti a po skončení měření je uložte na SD kartu.
Hardwarovou část projektu tvoří dvě vzájemně propojené DPS. První DPS tvoří analogově-digitální část, sloužící pro zpracování signálu z tenzometrického snímače tlaku. Druhá DPS tvoří čistě digitální část, zajišťující komunikaci s obsluhou, konfiguraci obvodů analogově-digitální části a záznam a uložení naměřených dat.
Tato část byla navržena v rámci semestrálního projektu. Její shcématické zapojení je na obrázku níže. Hlavními obvody (pro tento projekt) jsou dva přístrojové zesilovače s programovatelným ziskem, AD8557 od Analog Devices a PGA308 od Texas Instruments a také AD převodník AD7175-2 od Analog Devices. Zbylé obvody jsou na digitální části nezávislé, jako přístrojový zesilovač s OZ a napěťové stabilizátory.
Zapojení je realizováno na 4vrstvé DPS, pro zajištění co nejlepšího rozvodu zemí (digitální a analogová), referenčního a napájecího napětí, čímž bylo také dosaženo získání většího prostoru k rozmístění obvodů tak, aby byly minimalizováno jejich vzájemné ovlivňování.
Výsledná sestava je napájena ze zdroje napětí v rozashu 7-12 V. Toto napětí je stabilizováno na 5 V, kterým je napájena analogová část zapojení a některé obvody digitální části, na jejíž desce je toto napětí stabilizováno na 3,3 V pro většinu obvodů a také pro napájení digitální části AD převodníku.
Digitální část zapojení je realizována v rámci komerční vývojové desky STM32F746G-DISCO od STMicroelectronics. Tato deska poskytuje veškeré potřebné periferie pro záznam dat a komunikaci s uživatelem.
Spojení s digitální částí je zajištěno pomocí pinových lišt (Arduino kompatibilní).
Obvod - Pin | Pin DISCO board |
---|---|
AD8557 - 1W | D2 |
PGA308 - 1W | D1 |
AD7175-2 - CS | D14 |
AD7175-2 - SCK | D13 |
AD7175-2 - MISO | D12 |
AD7175-2 - MOSI | D11 |
Řídící firmware je psán ve vývojovém prostředí Mbed, jelikož je kompatibilní s použitou vývojovou deskou a poskytuje některé knihovny k jejím periferiím (LCD s dotykovou vrstvou apod.), což zjednodušuje tvorbu firmwaru pro tento prototyp. Tyto knihovny slouží většinou ve své podstatě jako „převdení“ BSP knihoven (poskytovaných výrobcem pro své konkrétní vývojové desky) na C++ knihovny, jelikož Mbed pracuje s tímto programovacím jazykem. I přes tuto skutečnost byla snaha psát kód v jazyku C, z důvodu možného pozdějšího převzetí částí kódu do finální verze firmwaru (psané v C).
void LCD_DISCO_F746NG::DrawPixel(uint16_t Xpos, uint16_t Ypos, uint32_t pixel) { BSP_LCD_DrawPixel(Xpos, Ypos, pixel); }
Komunikace s uživatelem je realizována pomocí GUI na LCD displeji s dotykovou vrstvou a v některých demonstračních případech výpisem do PC konzole. Prvky GUI jsou vykreslovány pomocí jednoduchých funkcí Mbed knihovny pro použitý LCD. Hlavním důvodem byla úspora paměti (oproti různým knihovnám sloužícícm pro tvorbu mnohem složitějších rozhraní, např. STemWin) a také fakt, že toto uživatelské rozhraní je vytvořeno pouze pro potřeby tohoto prototypu).
// Vykresleni barevneho obdelniku pouzivaneho jako tlacitko void Tlacitko(int x_poz, int y_poz, int sirka, int vyska, unsigned int barva, unsigned int barva_hrany) { lcd.SetTextColor(barva); lcd.FillRect(x_poz, y_poz, sirka, vyska); lcd.SetTextColor(barva_hrany); lcd.DrawRect(x_poz, y_poz, sirka, vyska); }
Vytvořené GUI (viz obrázek níže) je tvořeno jednoduchým menu po levém straně, ve kterém si uživatel stiskem zvolí, který obvod chce nakonfigurovat. V okně na pravé straně se mu zobrazí možné parametry, které lze měnit. Kliknutím na konkrétní parametr a poté na ovládací tlačítka níže ( ←, →, Prog), může hodnotu měnit anebo obvod vybranými parametry nakonfigurovat.
Přehled konfigurovatelných parametrů z jednotlivých části menu je zobrazen v tabulce níže. Vysvětlení parametrů prvních dvou obvodů je vždy v patřičném datasheetu na stránkách výrobce. Obecně řečeno se ale nastavuje zesílení a offset. U AD převodníku se nastavují parametry použitých kanálů jako je jejich povolení, využití vstupního analogového bufferu, výstupní datová rychlost a mód měření AD převodníku (kontinuální, jednorázové měření). Jako parametry měření lze zvolit způsob spuštění samotného měření (externím spouštěcím signálem, úrovní měřeného signálu), dobu měření a úroveň spouštěcího signálu.
PGA308 | AD8557 | AD7175-2 | Cas |
---|---|---|---|
Front-End gain | First Stage Gain | Aktivní kanál (CH0, CH2, CH3) | Způsob zpouštění |
GDAC Gain | Second Stage Gain | Vstupní buffer (CH0, CH2, CH3) | Mód měření |
OutAmp Gain | Offset | Výstupní datová rychlost (CH0, CH2, CH3) | Úroveň spouštění |
Coarse Offset | - | - | Doba měření |
Fine Offset | - | - | - |
Pro každé okno menu je deklarována struktura, která v sobě uchovává pole s možnými nastavitelnými hodnotami parametrů, jejich odpovídající kód pro konfiguraci patřičného obvodu a číslo údávající pořadí zvolené hodnoty v poli konkrétního parametru. Tyto struktury jsou vytvořeny a naplněny konkrétnimi daty na začátku funkce main.
// Struktura pro konfigurovatelné parametry obvodu AD8557 typedef struct {float fStageGain[25]; int sStageGain[8]; int offset[27]; int fStageGainCode[25]; int sStageGainCode[8]; int offsetCode[27]; int fStageGain_X; int sStageGain_X; int offset_X;}INA_AD;
Snímání doteků uživatele na dotykové vsrtvě displeje je řešeno pomocí následující funkce, která při každém jejím zavolání (umístěna v nekonečné smyčce) zkontroluje dotykovou vrstvu a při případném doteku (prvním doteku - vrstva umožňuje snímat až 5 doteků naráz) uloží jeho souřadnice.
// Testování doteku a uložení jeho pozice pro další vyhodnocení void testujDotek(dotekXY* dotek) { ts.GetState(&TS_State); if (TS_State.touchDetected) { dotek->X = TS_State.touchX[0]; dotek->Y = TS_State.touchY[0]; } dotek->pocetDoteku = TS_State.touchDetected; }
Po této funkci jsou volány funkce zajišťující vyhodnocení pozice doteku, a to jak na úrovni menu, tak na úrovni jednotlivých jeho oken. Konkrétně se jedná o funkce klikmenu(), klikParametry() a klikZmenParametru(), které dle pozice doteku upraví hodnotu ve výčtových proměnných.
Tyto proměnné udávají informaci o:
Níže je uvedena funkce klikMenu(), která vyhodnotí, zdali byl dotek proveden na některém z tlačítek menu a pokud ano, zapíše tuto informaci do již zmíněných výčtových proměnných. Ostatní funkce jsou napsány podobným způsobem.
void klikMenu(dotekXY* dotek) { //Detekovan dotek if (dotek->pocetDoteku > 0) { //kontrola doteku v nekonecne smycce //(*dotek).pocetDOteku > ...... //Stisk PGA308 if (dotek->X > 10 && dotek->X < 110 && dotek->Y > 30 && dotek->Y < 80) { tlacitko = PGA308; // Ulozeni informace o aktualnim stisku tlacitka menu zobrazNastaveni = PGA308n; // Informace o pozadavku vykresleni parametru obvodu PGA308 paramIO = Nic_P; // Vynulovani informace o zvolenem parametru nastPar = Nic_NP; } //Stisk AD8557 else if (dotek->X > 10 && dotek->X < 100 && dotek->Y > 90 && dotek->Y < 140) { tlacitko = AD8557; zobrazNastaveni = AD8557n; paramIO = Nic_P; nastPar = Nic_NP; } //Stisk AD7175 else if (dotek->X > 10 && dotek->X < 100 && dotek->Y > 150 && dotek->Y < 200) { tlacitko = AD7175; zobrazNastaveni = AD7175n; paramIO = Nic_P; nastPar = Nic_NP; } //Stisk Cas else if (dotek->X > 10 && dotek->X < 100 && dotek->Y > 210 && dotek->Y < 260) { tlacitko = Cas; zobrazNastaveni = Casn; paramIO = Nic_P; nastPar = Nic_NP; } } }
Po vytvoření požadavku na změnu parametru je v pořadí volána funkce zmenParametry(), která provede změnu požadového parametru požadovaným způsobem (pro každý parametr ověří požadavek vytvořený některým z tří ovládacích tlačítek), nebo zavolá konfigurační funkci, v případě stisku tlačítka „Prog“. Podobným způsobem funguje funkce Parametry(), která zajistí vykreslení parametrů vybraného prvku menu. Obě funkce (stejně jako ty předcházející) přijímají ve svém agrumentu ukazetel na strukturu/y, se kterou/kterými pracují.
void zmenParametry(dotekXY* dotek, INA_PGA* PGA308_val, INA_AD* AD8557_val, ADC_AD* AD7175_val, MERENI_CAS* cas_val) { //Detekovan dotek if (dotek->pocetDoteku > 0 ) { switch (paramIO) { // ******************** Menu PGA308 ******************** // ******** Front End Gain ********** case feGain_PGA308: if (nastPar != nastParPredchozi) { //Tlacitko < if (nastPar == l) { if (PGA308_val->feGain_X > 0) { PGA308_val->feGain_X = PGA308_val->feGain_X - 1; } param_PGA308(PGA308_val); } //Tlacitko > else if (nastPar == p) { if (PGA308_val->feGain_X < (sizeof(PGA308_val->feGain) / sizeof(PGA308_val->feGain[0])-1)) { PGA308_val->feGain_X = PGA308_val->feGain_X + 1; } param_PGA308(PGA308_val); } //Tlacitko Prog else if (nastPar == prog) { KonfigPGA308(PGA308_val); } } break; // *************************************** // vyhodnoceni vsech zbyvajicich parametru // *************************************** default: paramIO = Nic_P; konfigurace = N; } } }
Pro komunikaci s AD převodníkem AD7175-2 bylo využito knihovny AD717X No-OS Software Drivers od Analog Devices, obsahující samotnou knihovnu pro AD převodník a dále také generickou komunikační knihovnu pro SPI komunikaci. V této knihovně byly napsány funkce pro inicializaci, čtení a zápis po SPI. S touto knihovnou pracuje hlavičkový soubor AD7175_2_regs.h (pro každý konkrétní obvod jiný) obsahující deklaraci struktury s adresami vnitřních registrů AD převodníku, jejich počáteční hodnotou a velikostí. Při zápisu do jakéhokoliv registru AD převodníku je třeba nejprve zapsat požadovanou hodnotu do této struktury a poté zavolat funkci pro zápis do AD převodníku. Obdobný princip funguje při čtení, kdy je hodnota registru uložena do této struktury a poté je třeba hodnoty vyčíst z ní.
// Zapis do registru ADCMODE //Registr ADCMODE - interni oscilator (bez vyvedeni na pin), standby mode, vypnuti interni reference AD717X_GetReg(ad7175_2_handler, AD717X_ADCMODE_REG)->value = AD717X_ADCMODE_REG_CLKSEL(0) | AD717X_ADCMODE_REG_MODE(2) | AD717X_ADCMODE_REG_DELAY(0); //Zapis do ADCMODE registru AD717X_WriteRegister(ad7175_2_handler, AD717X_ADCMODE_REG);
AD převodník může fungovat v režimu continual nebo single, což lze nastavit v GUI. Nastavení AD převodníku do režimu single způsobí převedení jediného vzorku a poté dojde k přechodu do režimu standby. Režim continual naopak zajišťuje neustálý převod, dokud není AD převodník nastaven jinak. Knihovna od výrobce obsahuje dvě funkce, sloužící pro správné vyčítání výsledků převodu, AD717X_WaitForReady a AD717X_ReadData. Funkce AD717X_WaitForReady čeká a kontroluje, zda již byl dokončen převod a je k dispozici jeho výsledek (lze nastavit počet těchto „kontrol“). Funkce AD717X_ReadData poté výsledek vyčte a uloží do uživatelem zvolené proměnné.
Následující funkce zajištuje kontinuální měření po dobu k tomu určenou. V prvním případě je měření a ukládání vzorků spouštěno externím signálem, což je zde simulováno nastavením výčtové proměnné „mereni“ pomocí tlačítka na vývojové desce. V druhém případě měření probíhá při zavolání této funkce neustále (a správném nastavení v GUI), je ovšem kontrolována hodnota vzorků a pokud překročí nebo je rovna nastavené úrovni, je spuštěno měření času a vzorky jsou ukládány do paměti SDRAM. Po ukončení měření je zavolána funkce ulozData, která data vyčte z paměti SDRAM a uloží je na SD kartu ve formě surových dat, bez souborového systému (který bude přidám v pozdější fázi práce), což je v této fázi dostačující.
// Kontinualni mereni - spousteni ext. signalem / urovni mereneho signalu void kontMer(uint32_t *pocetVzorku, struct ad717x_device *ad7175_2_handler, MERENI_CAS* cas, FMC_SDRAM_CommandTypeDef* SDRAMCommand) { uint8_t err = 0; if ((strcmp(cas->zpusSpout[cas->zpusSpout_X], "Ext. sig.") == 0) && (mereni == start)) { // Kontrola externiho signalu // Spusteni mereni if (dobaMer == ceka) { dobaMer = trva; t.attach(&kontrolaCasu, cas->dobaMereni[cas->dobaMereni_X]); // spust mereni casu urceneho pro mereni } else if (dobaMer == trva) { // Cekani na vysledek konverze ret = AD717X_WaitForReady(ad7175_2_handler, timeout); if (ret < 0) { // Chyba pri cekani na vysledek printf("WaitForReady-Ext: %d\r\n", ret); //while(1); } // Vycteni vysledku konverze ret = AD717X_ReadData(ad7175_2_handler, &sample[0]); if (ret < 0) { // Chyba pri cteni dat printf("ReadData-Ext: %d\r\n", ret); //while(1); } //printf("cisloVzDoRAM: %d\r\n", *pocetVzorku); // Ulozeni vysledku do SDRAM err = sdram.WriteData(SDRAM_DEVICE_ADDR + WRITE_READ_ADDR + 4 * *pocetVzorku, (uint32_t*)&sample[0], 1); if (err) { printf("SDRAM Write err - S\r\n"); } *pocetVzorku += 1; } // Ukonceni mereni po uplynuti doby k tomu urcene else if (dobaMer == uplynula) { t.detach(); uint32_t pV = *pocetVzorku; ulozData(pV, SDRAMCommand); // Ulozeni dat na SD kartu // Nastaveni parametru urcujicich konec mereni *pocetVzorku = 0; mereni = stop; stav = nastaveniS; dobaMer = ceka; } } // Spousteni mereni urovni signalu else if (strcmp(cas->zpusSpout[cas->zpusSpout_X], "Uroven") == 0) { while(urSpust != mer) { // Cekani na vysledek konverze ret = AD717X_WaitForReady(ad7175_2_handler, timeout); if (ret < 0) { // Chyba pri cekani na vysledek printf("WaitForReady-urCek: %d\r\n", ret); //while(1); } // Vycteni vysledku konverze ret = AD717X_ReadData(ad7175_2_handler, &sample[0]); if (ret < 0) { // Chyba pri cteni dat printf("ReadData-urCek: %d\r\n", ret); //while(1); } // Porovnani vysledku konverze se spousteci urovni if ((sample[0] >= cas->urovenSpoust[cas->urovenSpoust_X]) && dobaMer == ceka) { urSpust = mer; // spust ukladani dat dobaMer = trva; t.attach(&kontrolaCasu, cas->dobaMereni[cas->dobaMereni_X]); // spust mereni casu urceneho pro mereni } } if (urSpust == mer) { // mereni je spusteno // Cekani na vysledek konverze ret = AD717X_WaitForReady(ad7175_2_handler, timeout); if (ret < 0) { // Chyba pri cekani na vysledek printf("WaitForReady-uroven: %d\r\n", ret); //while(1); } // Vycteni vysledku konverze ret = AD717X_ReadData(ad7175_2_handler, &sample[0]); if (ret < 0) { // Chyba pri cteni dat printf("ReadData-uroven: %d\r\n", ret); //while(1); } // Ulozeni vysledku do SDRAM err = sdram.WriteData(SDRAM_DEVICE_ADDR + WRITE_READ_ADDR + *pocetVzorku * 4, (uint32_t*)&sample[0], 1); if (err) { printf("SDRAM Write err - S\r\n"); } *pocetVzorku += 1; // Ukonceni mereni po uplynuti doby k tomu urcene if (dobaMer == uplynula) { t.detach(); uint32_t pV = *pocetVzorku; ulozData(pV, SDRAMCommand); // Ulozeni dat na SD kartu // Nastaveni parametru urcujicich konec mereni *pocetVzorku = 0; mereni = stop; stav = nastaveniS; dobaMer = ceka; urSpust = cekej; } } } }
Funkce zajišťující zápis naměřených dat na SD kartu (je zde zbavena částí, způsobujících hlášení chyby, kvůli zkrácení kódu).
void ulozData(uint32_t pocetVzorku) { int32_t b = 0; // pomocna promenna pro vytvoreni bloku dat uint32_t indexBlok = 0; uint8_t SD_state = MSD_OK; uint32_t vzorek[1]; uint32_t poleSD[128]; uint32_t NUM_OF_BLOCKS = (uint32_t)ceil((32.0*(float)pocetVzorku)/(4096.0)); // Pocet pametovych bloku v SD karte pro zapis vzorku printf("Num_of_blocks: %d\n", NUM_OF_BLOCKS); printf("Pocet vzorku: %d\n", pocetVzorku); // Vymazani casti pameti SD karty SD_state = sd.Erase(BLOCK_START_ADDR, (BLOCK_START_ADDR + NUM_OF_BLOCKS - 1)); // Cekej dokud neni mazani dokonceno while(sd.GetCardState() != SD_TRANSFER_OK){ } // Vycitani dat z SDRAM for (uint32_t a = 0; a < pocetVzorku; a++) { sdram.ReadData(SDRAM_DEVICE_ADDR + WRITE_READ_ADDR + 4 * a, (uint32_t*)&vzorek[0], 1); printf("DataSDRAM %d: %x\r\n", a, vzorek[0]); // Vypis vzorku v SDRAM pro kontrolu se vzorky v SDkarte // Naplneni pole odpovidajiciho bloku v SD karte if (b < 128) { poleSD[b] = vzorek[0]; b++; } // Zapis a vycitani dat z SD karty (po blocich) if ((b >= 128) || (a >= (pocetVzorku-1))) { SD_state = sd.WriteBlocks(poleSD, BLOCK_START_ADDR + 512 * indexBlok, 1, 10000); // Cekej dokud neni zapis hotov while(sd.GetCardState() != SD_TRANSFER_OK){ } uint32_t poleSD[128] = {NULL}; // Cteni bloku dat z SD karty SD_state = sd.ReadBlocks(poleSD, BLOCK_START_ADDR + 512 * indexBlok, 1, 10000); // Cekej dokud neni cteni dokonceno while(sd.GetCardState() != SD_TRANSFER_OK){ } b--; // dekrementace indexu pro vypsani vsech vzorku for (; b > -1; b--) { printf("SD data %d: %x\n", b, poleSD[b]); // vypsani vsech vzorku v bloku SD karty } indexBlok++; b = 0; } } pocetVzorku = 0; printf("Konec vypisu \r\n"); }