Library 1 – sterownik silnika krokowego na STM32F429 DISCO (part 3).

Komunikacja pomiędzy PC, a płytką DISCOVERY oparta została na interfejsie UART. W celu podłączenia urządzeń wymagana jest przejściówka USB-UART, najlepiej, wyposażona w układ FT232. Konfiguracja mikrokontrolera pod obsługę UART przedstawiona została w części 1 omawianej biblioteki.

Projekt do pobrania z: https://github.com/PiotrPoterala/stm32f429i-disco_stepper_motor

oraz biblioteki: https://github.com/PiotrPoterala/PP_Library

Struktura klas

Mechanizm odbioru/wysyłania danych oparty został na wzorcu projektowym Dekorator. Pozwala on na:

  • dynamiczne dodawanie nowych funkcji do istniejących klas w trakcie działania programu,
  • „opakowanie” oryginalnej klasy w klasę „dekorującą”.

W przypadku omawianego projektu:

  1. Component to PIOdevice – wirtualna klasa bazowa, dostarczająca interfejs do realizacji wysyłania/odbierania danych po UART,
  2. ConcreteComponent to PSerialPortRTX5 – dziedziczy po PIOdevic; klasa implementująca metody zdefiniowane w klasie bazowej z wykorzystaniem funkcji specyficznych dla zastosowanego systemu czasu rzeczywistego. W przypadku korzystania z FreeRTOS’a jako ConcreteComponent mogłaby funkcjonować np. klasa PSerialPortFreeRTOS.
  3. Decorator to PIOdeviceDecorator – dziedziczy po PIOdevice i agreguje referencję do obiektu klasy PIOdevice. Zawierając ten sam zestaw metod jak obiekt docelowy (Component) i delegując mu wszystkie żądania stanowi interfejs dla „nakładek”.
  4. ConcreteDecorator to defORTX5atCommandInterpreter – „nakładka”; dziedziczy po PIOdeviceDecorator; implementuje tylko te metody wirtualne, których ostateczny wynik działania ma być zmieniony, dodając pewne operacje, przed lub po przekazaniu żądania do obiektu docelowego (Component). W przypadku omawianego projektu, do metody readLine() dodane zostały operacje:
    • interpretacji danych odebranych z portu UART,
    • przesłania obrobionych danych (poprzez kolejkę) do wątku realizującego podstawowe zadania projektu (a więc obrót wirnika silnika o określony kąt, z określoną trajektorią prędkości obrotowej).

UART – wysyłanie danych w projekcie opartym na RTX5

Bezpośrednie wysłanie danych na port UART

PIOdevice*serialPort= new PSerialPortRTX5(USART1);
serialPort->open(PIOdevice::ReadWrite);	
serialPort->write("test");
serialPort->close();

Wysłanie danych za pośrednictwem PTextStream:

PIOdevice* serialPort = new PSerialPortRTX5(USART1);
PTextStream out(serialPort);
serialPort->open(PIOdevice::ReadWrite);	
out<<”test”;
serialPort->close();

Metoda write() przepisuje ciąg znaków do kolejki nadawczej sendQueue, (i ponieważ UART nie jest wstanie jednocześnie nadawać i odbierać danych) wyłącza odbieranie danych przez port UART (CR1_RE) i włącza przerwania od pustego rejestru transmisji danych (CR1_TXEIE=1).

pp_rtx5_serial_port.cpp
bool PSerialPortRTX5::write(string &data){
	bool answer=true;
	
	if(openMode==WriteOnly || openMode==ReadWrite){
		for(auto it: data){
			if(osMessageQueuePut(sendQueue, &it, 0, osWaitForever)==osOK){
				name->CR1&=~USART_CR1_RE;	
				name->CR1|=USART_CR1_TXEIE;
			}else{
				answer=false;
				break;
			}				
		}
	}else{
		answer=false;
	}

	return answer;
}

W przypadku, gdy SR_TXE=1 (bit ustawiany sprzętowo) wywoływany jest podprogram przerwania, w którym poprzez metodę portListen(), pobierany jest znak z kolejki nadawczej sendQueue i wstawiany do rejestru DR (pełniącego funkcję bufora nadawczego).

pp_rtx5_serial_port.cpp
void PSerialPortRTX5::portListen(){
	
	if(port->SR & USART_SR_RXNE){  
		receiveSignAndWriteToReceiveQueue();
  }else if(port->SR & USART_SR_TXE){  
		sendSignFromSendQueue();
  }
}

...

int PSerialPortRTX5::sendSignFromSendQueue(){
		char sign;	
		osStatus_t status;

		status=osMessageQueueGet(sendQueue, &sign, NULL, 0);
		if(status==osOK){
			port->DR=sign;
		}else{
			port->CR1&=~USART_CR1_TXEIE;	
			port->CR1|=USART_CR1_RE;			
		}

		return status;
 }

Po wysłaniu wszystkich znaków z kolejki nadawczej, następuje wyłączenie przerwania od pustego rejestru CR1_TXEIE i ponowne włączenie odbieranie danych przez port UART.

UART – odbieranie danych w projekcie opartym na RTX5

W omawianym projekcie, odbieranie danych jest domyślnym trybem pracy portu UART, a więc domyślnie ustawiony jest bit RE w rejestrze CR1 oraz włączone jest przerwanie od pełnego rejestru odbioru danych (CR1_ RXNEIE=1).

W przypadku, gdy SR_RXNE=1 (bit ustawiany sprzętowo)  wywoływany jest podprogram przerwania, w którym poprzez metodę portListen(), pobierany jest znak z rejestru DR i wstawiany do kolejki odbiorczej receiveQueue.

pp_rtx5_serial_port.cpp
void PSerialPortRTX5::receiveSignAndWriteToReceiveQueue(){
	char receiveChar;
	
	receiveChar=(uint8_t)(port->DR); 
	osMessageQueuePut (receiveQueue, &receiveChar, 0, 0); 
	
}

Pojawienie się danych w kolejce receiveQueue odblokowuje wątek vReceiveAndInterpretDataFromComUartThread w którym pobierane są dostępne dane z receiveQueue i zapisywane w zmiennej receiveString, aż do napotkania znaku nowej linii (pojawienie się znaku nowej linii powoduje ustawienie flagi getStringFlag na wartość true). Powyższe operacje zaimplementowane zostały w metodzie waitForReadyRead () wywoływanej w ciele pętli wątku vReceiveAndInterpretDataFromComUartThread. Do momentu wywołania readLine() nie przyjmowane są dane z kolejki receiveQueue. Jeżeli metoda readLine() zwraca pusty ciąg znaków (readLine().empty()==true) oznacza to, że dane do odczytu nie są gotowe.

pp_rtx5_serial_port.cpp
bool PSerialPortRTX5::waitForReadyRead(int usec){
		char receiveChar;
		int tick;
	
		if(usec==osWaitForever){
			tick=osWaitForever;
		}else{
			tick=(OS_TICK_FREQ/1000000)*usec;
		}
	
		if(getStringFlag==false){
			if(osMessageQueueGet(receiveQueue, &receiveChar, NULL, tick) == osOK){

				receiveString+=receiveChar; 

				if(receiveChar=='\n'){

					getStringFlag=true;

				}else if(receiveString.size()>64){
						receiveString.clear();
					
				}
				return true;
			}  
		}
		
		return false;
	
}

Interpretacja odebranych danych

Interpretacja odebranych danych, w postaci AT komend, następuje w „dekoracji” metody readLine().

W przypadku podesłania AT+TRVV (przejazd o wartość) lub AT+TRVCO (przejazd na współrzędne) parametry danej operacji wysyłane są z wykorzystaniem kolejki taskCommunicationQueues do wątku vRealizationFunctionThread odpowiadającego za realizację podstawowych zadań projektu, a więc za kontrolę obrotów silnika krokowego. Ramka danych „ładowana” do kolejki zawiera informacje o:

  • Źródle wywołania danej operacji(w tym przypadku jest to AT komenda),
  • Typie operacji do realizacji,
  • Parametrach operacji.
pp_rtx5_at_commands_interpreter.cpp
string defORTX5atCommandInterpreter::readLine(){
	
	string receiveString=PIOdeviceDecorator::readLine();
	
	if(receiveString.empty()==false){
	
		PString data(receiveString);
		string answer="FAIL\r\n";
		int index=0;
		 if(data.find("AT+TRVV")!=string::npos){
				map<char, double> values;
				vector<int> valuesToSend;
				index=data.find("AT+TRVV");
				data.erase(0, index+7);
				values=data.findValuesAfterAcronims();
			 
				if(!values.empty()){
					valuesToSend.push_back(qMARK_ATC);
					valuesToSend.push_back(AT_TAG_TRVV);
					valuesToSend.push_back(values.size());
					
					for(auto it=values.begin(); it!=values.end(); ++it){
						valuesToSend.push_back((*it).first);
						valuesToSend.push_back((*it).second*pow(10.0, phyCoord->getParamUnit((*it).first)));
					}
					
					
					taskCommunicationQueues->xQueueSendConteinerToBackWithSemaphore(valuesToSend);
					answer="OK\r\n";
				};
		 }else if(data.find("AT+TRVCO")!=string::npos){
				map<char, double> values;
				vector<int> valuesToSend;
				index=data.find("AT+TRVCO");
				data.erase(0, index+8);
				values=data.findValuesAfterAcronims();
			 
				if(!values.empty()){
					valuesToSend.push_back(qMARK_ATC);
					valuesToSend.push_back(AT_TAG_TRVCO);
					valuesToSend.push_back(values.size());
					
					for(auto it=values.begin(); it!=values.end(); ++it){
						valuesToSend.push_back((*it).first);
						valuesToSend.push_back((*it).second*pow(10.0, phyCoord->getParamUnit((*it).first)));
					}
					
					
					
					taskCommunicationQueues->xQueueSendConteinerToBackWithSemaphore(valuesToSend);
					answer="OK\r\n";
				};
		 }else if(data.find("AT+BASEC")!=string::npos){
				index=data.find("AT+BASEC");
				if(data.at(index+8)=='?'){	
					answer="BASEC ";
					answer+=baseCoord->getStringWithParams();
					answer+="\r\n";
				}else{		
					data.erase(0, index+8);
					baseCoord->setParamsBasedString(&data);
					answer="OK\r\n";
				}
			}else if(data.find("AT+PHYC")!=string::npos){
				index=data.find("AT+PHYC");
				if(data.at(index+7)=='?'){	
					answer="PHYC ";
					answer+=phyCoord->getStringWithParams();
					answer+="\r\n";
				}else{		
					data.erase(0, index+8);
					phyCoord->setParamsBasedString(&data);
					answer="OK\r\n";
				}
			}
			
			if(response)PIOdeviceDecorator::write(answer);
		}
    
		return receiveString;
}

Lista interpretowanych AT komend

AT+BASEC X<x>\r\nUstawienie współrzędnych operatora  

Odpowiedź:
OK: zatwierdzenie odbioru danych,
FAIL: brak parametrów,

Parametry:
<x>: nowa wartość współrzędnej X [mm],
AT+BASEC? \r\nPobranie współrzędnych operatora.  

Odpowiedź: BASEC X<x>\r\n

Parametry:
<x>: wartość współrzędnej X [mm]
AT+PHYC X<x> \r\nUstawienie współrzędnych fizycznych.  

Odpowiedź:
OK: zatwierdzenie odbioru danych,
FAIL: brak parametrów,

Parametry:
<x>: nowa wartość współrzędnej X [mm],
AT+PHYC? \r\nPobranie współrzędnych fizycznych.  

Odpowiedź: PHYC X<x>\r\n

Parametry:
<x>: wartość współrzędnej X [mm],
AT+TRVCO X<x>\r\nPrzejazd na współrzędne.  

Odpowiedź:
OK: zatwierdzenie odbioru danych,
FAIL: brak parametrów,

Parametry:
<x>: wsp. operatora X na jaką ma przejechać maszyna [mm],  
AT+TRVV X<x> r\nPrzejazd o wartość.  

Odpowiedź:
OK: zatwierdzenie odbioru danych,
FAIL: brak parametrów,

Parametry:
<x>: wartość przejazdu po osi X [mm],

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *