Laboratorní zdroj – 11. Slave modul ATmega16 UART
Popis programu, který prostřednictvím sériového kanálu UART na procesoru ATmega16 přijímá data z nadřízené desky Master, odesílá odpověď, nastavuje DA převodník DAC8563 a čte data z AD převodníku ADS8341.
Komunikaci mezi procesory řídí deska Master, takže sériový kanál procesoru ATmega16 čeká na data, která vyšle Master. Je detekován první znak ‚Z‘, kterým začíná každý paket 20-ti bajtů. Předpokládá se, že na sériové sběrnici je připojeno větší množství Slave modulů. Proto je při příjnu kontrolován ještě čtvrtý znak, který označuje číslo modulu. Pokud jsou tyto dva znaky součástí přijatého paketu, bude přijato všech dvacet znaků do pole dat SerialArray.
Potom program vypočítá CRC přijatých znaků a výsledek porovná s CRC, který vypočítal Master a uložil na konec vysílaného paketu. Když jsou oba CRC shodné, předpokládá se, že zpráva je bez chyb a data budou použita k ovládání Slave modulu.
Ihned po dokončení příjmu dat a vyhodnocení jeho správnosti jsou do pole dat SerialArray uloženy hodnoty, které bude Slave modul vysílat okamžitě vysílat. Předpokládá se, že Master po odeslání zprávy nechá dostatečný prostor na sběrnici a jiný modul nebude vysílat. Takže Slave nekontroluje, zda je sběrnice volná. Součástí vysílané zprávy jsou data naměřená AD převodníkem a CRC vysílaných dat.
Celá komunikace je obsluhována programy přerušení od sériového kanálu.
Hlavní program procesoru na začátku nastaví hodnoty registrů, které ovládají periferní zařízení, I/O porty procesoru, sériový kanál, SPI kanál pro komunikaci s převodníky a vynuluje hodnoty proměnných.
Ve smyčce hlavního programu jsou nastavovány hodnoty napětí a proudu na DA převodníků. Hodnoty z AD převodníku – napětí a proud, jsou ukládány do pole dat a jsou počítány průměrné hodnoty z toho, co je naměřeno.
Hlavičkový soubor definuje makra, deklaruje proměnné a funkce, které budou potřebné v hlavním programu
/********************************************************************************* * File Name : AT_slave_test.h * Created : 16.5.2016 * Autor : Frantisek Pospisil * *********************************************************************************/ /* definice maker */ #define LED1_set PORTC &=~0x20; // cervena #define LED1_clr PORTC |= 0x20; #define LED2_set PORTC &=~0x40; // zelena #define LED2_clr PORTC |= 0x40; #define LED3_set PORTC &=~0x80; // zelena #define LED3_clr PORTC |= 0x80; #define Nabijeni_set PORTA |= 0x01; // rele nabijeni #define Nabijeni_clr PORTA &=~0x01; // rele nabijeni #define Rel_30V PORTA |= 0x02; // rele vinuti 15V / 30V #define Rel_15V PORTA &=~0x02; // rele vinuti 15V / 30V #define Rel_filtr_set PORTA |= 0x04; // rele kapacita filtru #define Rel_filtr_clr PORTA &=~0x04; // rele kapacita filtru #define Rel_proud_10x PORTA |= 0x08; // rele mereni 100x / 10x #define Rel_proud_100x PORTA &=~0x08; // rele mereni 100x / 10x #define DA_CLR_clr PORTC &=~0x04; #define DA_CLR_set PORTC |= 0x04; #define DA_LDAC_clr PORTC &=~0x02; #define DA_LDAC_set PORTC |= 0x02; #define DA_SYNC_clr PORTC &=~0x18; // 4-ty bit - chyba procesoru Michal #define DA_SYNC_set PORTC |= 0x18; // 4-ty bit - chyba procesoru Michal #define AD_CS_set PORTB &=~0x01; #define AD_CS_clr PORTB |= 0x01; /* deklarace globalnich promennych */ static unsigned int TimCounter; // pocitadlo casovace static unsigned int ProgramCounter; // pocitadlo programu, merenych vzorku static unsigned char SerialCounter; // pocitadlo serioveho kanalu static unsigned char SerialArray[22]; // pole vysilanych dat static unsigned char SerialCRC; // scita CRC hodnotu static unsigned int VoltageInAd; // aktualni namerene napeti static unsigned int CurrentInAd; // aktualni namereny proud static unsigned long int VoltageInAdd; // soucet static unsigned long int CurrentInAdd; static unsigned int VoltageInAverage; // vypocitany prumer static unsigned int CurrentInAverage; static unsigned int VoltageArray[130]; // pole namerenych dat pro vypocet prumeru static unsigned int CurrentArray[130]; static unsigned int VoltageOutDa; // napeti pro DA prevodnik static unsigned int CurrentOutDa; // proud pro DA prevodnik static unsigned int TempReference; // teplota reference napeti static unsigned char ReleStatus; // predava stav rele static unsigned char CurrentStatus; // zobrazuje zdroj proudu /* uplne funkcni prototypy */ /********************************************************************************* * * Function Name : DAC8563_Send * Description : odesle data na DAC * *********************************************************************************/ void DAC8563_Send(unsigned char command, unsigned int val); /********************************************************************************* * * Function Name : ADS8341_Send * Description : prijme data z AD prevodniku * *********************************************************************************/ unsigned int ADS8341_Send(unsigned char command); /********************************************************************************* * * Function Name : SPI_Send * Description : odesle data na SPI * *********************************************************************************/ unsigned char SPI_Send(unsigned char val); /********************************************************************************* * * Function Name : SystemInit * Description : inicializuje ridici registry procesoru a zapoji rele * *********************************************************************************/ void System_Init(void); /********************************************************************************* * * Function Name : vypocet CRC ze zadaneho polynomu * Description : pole dat ze ktereho ma byt pocitano a velikost pole * Return : CRC kod * *********************************************************************************/ unsigned char CRC_Calculation( void );
Hlavní program
/********************************************************************************* * File Name : AT_slave_test.c * Created : 16.5.2016 * Autor : Frantisek Pospisil * *********************************************************************************/ /* Hlavni program: * nastavuje DA prevodnik * cte data z AD prevodniku * pocita prumer z namerenych dat, pocet vzorku je 128, nebo 64, nebo 32 * ovlada rele, podle promenne ReleStatus * * Preruseni serioveho kanalu: * prijimany i vysilany paket ma 20 znaku, kontroluje se CRC kod * prijima data z Master, detekuje znak 'Z' na zacatku, detekuje '0' na tretim miste * kdyz tyto znaky nejsou, nebude se vysilat * kdyz je vypocitane CRC shodne s prijatym na 20 znaku, pouzije data * vysila i kdyz je CRC prijateho paketu vadne */ /* hlavickove soubory */ #define CrcPolynom 0x91 // definice polynomu pro vypocet CRC #define F_CPU 8000000UL #include "main.h" #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #define DaVoltage 0b00011001 // nastaveni DA prevodniku pro rizeni napeti #define DaCurrent 0b00011000 // nastaveni DA prevodniku pro rizeni proudu #define AdVoltage 0b11010110 // nastaveni AD prevodniku pro mereni napeti #define AdCurrent 0b10010110 // nastaveni AD prevodniku pro mereni proudu #define AdTempRef 0b11100110 // nastaveni AD prevodniku pro mereni teploty reference // =============================================================================== // HLAVNI PROGRAM // =============================================================================== int main(void) { System_Init(); // inicializuje registry, promenne a rele pro nabijeni while(1) { DAC8563_Send( DaCurrent, CurrentOutDa ); // nastavi DA prevodnik DAC8563_Send( DaVoltage, VoltageOutDa ); VoltageInAd = ADS8341_Send( AdVoltage ); // data z AD prevodniku CurrentInAd = ADS8341_Send( AdCurrent ); TempReference = ADS8341_Send( AdTempRef ); // zmeri teplotu reference VoltageArray[ProgramCounter] = VoltageInAd; // namerena data ulozi do pole dat CurrentArray[ProgramCounter] = CurrentInAd; VoltageInAdd += VoltageInAd; // namerena data pricte k prumeru CurrentInAdd += CurrentInAd; if( ProgramCounter == 128 ) // pocet vzorku pro optimalizaci deleni 128, 64, 32 { LED3_set; // blika diodou ProgramCounter = 0; } else ProgramCounter++; if( ProgramCounter == 10 ) LED3_clr; VoltageInAdd -= VoltageArray[ProgramCounter]; // odecita posledni vzorek v poli CurrentInAdd -= CurrentArray[ProgramCounter]; VoltageInAverage = ( VoltageInAdd >> 7 ) & 0xFFFF; // deleni CurrentInAverage = ( CurrentInAdd >> 7 ) & 0xFFFF; if(( ReleStatus&0x01 ) && ( ~CurrentStatus&0x80 )) { Nabijeni_clr; // 7 bit CurrentStatus indikuje zmenu napeti _delay_ms( 100 ); Rel_30V; // zapinani 30V vinuti _delay_ms( 300 ); Nabijeni_set; // nabijeni CurrentStatus |= 0x80; } if( ~ReleStatus&0x01 ) // odpojeni 30V vinuti { Rel_15V; CurrentStatus &=~0x80; } if( ReleStatus&0x02 ) Rel_filtr_set // resi stav rele na zaklade prijatych dat else Rel_filtr_clr; if( ReleStatus&0x04 ) Rel_proud_10x else Rel_proud_100x; } } /********************************************************************************* * * Function Name : TIMER1_CAPT_vect * Description : blika diodou * *********************************************************************************/ ISR(TIMER1_CAPT_vect) { if ( TimCounter == 200 ) // pocita interval 0,2 sekunda { TimCounter = 0; LED1_set; } if ( TimCounter == 20 ) LED1_clr; // zhasne dioda TimCounter++; } /********************************************************************************* * * Function Name : USART0_RXC_vect * Description : preruseni prijimace serioveho kanalu * *********************************************************************************/ ISR(USART_RXC_vect) { LED2_set; SerialArray[SerialCounter] = UDR; // prijata data do pole // kontroluje znak kanalu, kdyz neodpovida zarizeni, nastavi SerialCounter = 50 if(( SerialCounter == 3 ) && ( SerialArray[3] != '0' )) SerialCounter = 50; if( SerialArray[0] == 'Z' ) SerialCounter++; // na zacatku komunikace musi byt 'Z' if( SerialCounter == 66 ) SerialCounter = 0; // konec komunikace, kdyz neodpovida zarizeni if( SerialCounter == 21 ) // konec komunikace, kdyz odpovida zarizeni { // =============================================================================== // kontrloa CRC, ulozeni prijatych dat a priprava na vysilani // =============================================================================== SerialCRC = CRC_Calculation(); // vypocita CRC kod if( SerialCRC == SerialArray[20] ) // CRC je v poradku, data budou pouzita v hlavnim programu { SerialArray[19] = 0; VoltageOutDa = (SerialArray[ 4] << 8) + SerialArray[ 5]; // z prijatych dat vypocita napeti DA prevodniku CurrentOutDa = (SerialArray[ 6] << 8) + SerialArray[ 7]; // z prijatych dat vypocita proud DA prevodniku ReleStatus = SerialArray[8]; } else SerialArray[19] = 2; // chyba CRC pri prijimani SerialArray[ 0] = 'Z'; SerialArray[ 1] = 'D'; SerialArray[ 2] = 'R'; SerialArray[ 3] = '0'; SerialArray[ 4] = (unsigned char) ( VoltageInAd >> 8 ); // horni polovina DA prevodniku napeti SerialArray[ 5] = (unsigned char) ( VoltageInAd ); // spodni polovina DA prevodniku napeti SerialArray[ 6] = (unsigned char) ( CurrentInAverage >> 8 ); // horni polovina DA prevodniku proudu SerialArray[ 7] = (unsigned char) ( CurrentInAverage ); // spodni polovina DA prevodniku proudu SerialArray[ 8] = (unsigned char) ( VoltageInAverage >> 8 ); SerialArray[ 9] = (unsigned char) ( VoltageInAverage ); SerialArray[10] = (unsigned char) ( CurrentInAd >> 8 ); SerialArray[11] = (unsigned char) ( CurrentInAd ); SerialArray[12] = (unsigned char) ( TempReference >> 8 ); // teplota referencniho zdroje SerialArray[13] = (unsigned char) ( TempReference ); // teplota referencniho SerialArray[14] = 0; SerialArray[15] = 0; SerialArray[16] = 0; SerialArray[17] = 0; SerialArray[18] = 0; SerialArray[20] = CRC_Calculation(); // posledni znak je CRC vysilanych dat while ( !( UCSRA & (1<<UDRE))); // ceka na volny kanal UDR = SerialArray[0]; // posle prvni znak SerialCounter = 1; } } /********************************************************************************* * * Function Name : USART0_TXC_vect * Description : preruseni vysilace serioveho kanalu * *********************************************************************************/ ISR(USART_TXC_vect) { if( SerialCounter < 21 ) { UDR = SerialArray[SerialCounter]; // posle dalsi znak SerialCounter++; } else { LED2_clr; SerialCounter = 0; // konec vysilani } } /********************************************************************************* * * Function Name : DAC8563_Send * Description : odesle data na DAC * *********************************************************************************/ void DAC8563_Send(unsigned char command, unsigned int val) { SPCR = 0b01010100; // nastaveni SPI kanalu // zmena dat na vzestupnou hranu CLK, cteni na sestupnou hranu // LED3_set; DA_SYNC_clr; SPI_Send( command ); // cislo kanalu SPI_Send( val >> 8 ); // horni polovina SPI_Send( val ); // spodni polovina DA_SYNC_set; // LED3_clr; } /********************************************************************************* * * Function Name : ADS8341_Send * Description : prijme data z AD prevodniku * Note : PB1 - vstup BUSY * *********************************************************************************/ unsigned int ADS8341_Send(unsigned char command) { unsigned int AdVal; SPCR = 0b01011100; // nastaveni SPI kanalu //zmena dat na sestupnou hranu CLK, cteni na vzestupnou hranu AD_CS_set; SPI_Send( command ); // cislo kanalu while( PINB&0x02 ); // ceka na signal BUSY AD_CS_clr; while(~PINB&0x02 ); AD_CS_set; AdVal = SPI_Send( 0 ) << 8; // horni polovina AdVal = AdVal + (SPI_Send( 0 )); // spodni polovina SPI_Send( 0 ); AdVal = AdVal - 32768; // 0 je uprostred rozsahu, posune se o polovinu AD_CS_clr; return ( AdVal ); } /********************************************************************************* * * Function Name : SPI_Send * Description : odesle data na SPI * *********************************************************************************/ unsigned char SPI_Send(unsigned char val) { SPDR = val; // odesilana data while(!(SPSR & (1<<SPIF))); return SPDR; // prijata data } /********************************************************************************* * * Function Name : SystemInit * Description : inicializuje ridici registry procesoru a zapoji rele * *********************************************************************************/ void System_Init(void) { PORTC = 0b00000000; // H-pull up, L vysoká impedance DDRC = 0b11111110; // H-output, L-input - DA prevodnik PORTB = 0b00000000; // H-pull up, L vysoká impedance DDRB = 0b10110001; // H-output, L-input - SPI sbernice PORTA = 0b00000000; DDRA = 0b11111111; // rele PORTD = 0b00000000; DDRD = 0b00000010; // seriovy kanal // * * * * * * * * * * * * * * * * * * * * * * * * * SPI sbernice SPCR = 0b01010111; // preruseni SPI, aktivace SPI, prenos od MSB do LSB, Master=1 // polarita hodinoveho signalu, faze hodin, prenosova rychlost SPSR = 0b00000000; // * * * * * * * * * * * * * * * * * * * * * * * * * seriovy kanal UBRRH = 0; UBRRL = 16; // rychlost prenosu 9600 baudu - 103 // rychlost prenosu 57600 baudu - 16 UCSRA = 0b00000010; // dvojnasobna rychlost UCSRB = 0b11011000; // povoleni vysilace a prijimace, preruseni pri vysilani i prijimani UCSRC = 0b10001110; // bez parity, osum bitu // * * * * * * * * * * * * * * * * * * * * * * * * * Nastaveni casovace T1 TCCR1A = 0b00000000; // Frekvence = F krystal TCCR1B = 0b00011010; // rezim PWM 12, citac se vynuluje pri dosazeni hodnoty ICR1 TIMSK = 0b00100000; // preruseni pri priznaku OCF1 ICR1 = 1000; // 1000 Hz kmitocet PWM // * * * * * * * * * * * * * * * * * * * * * * * * * Inicializace promennych TimCounter = 0; // pocitadlo casovace SerialCounter = 0; // vysilany, nebo prijimany znak na seriovem kanalu VoltageOutDa = 0; // data pro DA prevodnik CurrentOutDa = 0; // data pro DA prevodnik ProgramCounter = 0; // pocitadlo v hlavnim programu - vzorky mereni CurrentStatus = 0; // stav zdroj napeti / proudu Bit 0, prepinani vinuti Bit 7 VoltageInAdd = 0; // vypocet prumerne hodnoty CurrentInAdd = 0; // vypocet prumerne hodnoty LED1_set; LED2_set; LED3_set; // =============================================================================== // inicializace DA prevodniku // =============================================================================== DA_CLR_set; DA_LDAC_clr; DA_SYNC_set; AD_CS_clr; DAC8563_Send( 0b00101000, 1 ); // Reset DAC8563_Send( 0b00111000, 0 ); // Disable internal reference DAC8563_Send( 0b00100000, 3 ); // power up DAC-A and DAC-B DAC8563_Send( 0b00000010, 0 ); // Gain = 2 DAC8563_Send( 0b00110000, 3 ); // LDAC pin inaktive for DAC-A and DAC-B DAC8563_Send( DaVoltage, 0 ); DAC8563_Send( DaCurrent, 0); _delay_ms( 1500 ); // ceka na nabiti LED1_clr; _delay_ms( 500 ); LED2_clr; _delay_ms( 500 ); LED3_clr; _delay_ms( 500 ); Nabijeni_set; // pripoji rele nabijeni sei(); // pusti globalni preruseni } /********************************************************************************* * * Function Name : vypocet CRC podle polynomu * Return : CRC kod * *********************************************************************************/ unsigned char CRC_Calculation( void ) { unsigned char i, j, CrcCode = 0; for( i = 0; i < 20; i++ ) // pole dat ma 20 znaku { CrcCode ^= SerialArray[i]; // XOR CRC kodu a dat for( j = 0; j < 8; j++) { if( CrcCode & 1 ) CrcCode ^= CrcPolynom; // XOR CRC kodu a poynomu, kdyz je bit0 = 1 CrcCode >>=1; // rotace CRC kodu } } return CrcCode; }