Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:comm-rs485

Zadání

Realizujte driver a HW zapojení pro instantní využití sběrnice RS485 na platformě STM32. S driverem bude jednoduše možné realizovat Master-Slave síť o více zařízeních. Pro případ nedostupnosti komunikačního vedení rozšiřte možnosti driveru o využití modulu RFM12B pro bezdrátovou komunikaci

Hardware

Sběrnice RS485

Je dvouvodičová sériová sběrnice pro komunikaci na delší vzdálenosti až stovek metrů. Standartní mikroprocesory neobsahují HW rozhraní pro přímé napojení na tuto sběrnici. Je tedy nutné použít převodník z rozhraní UART na RS485. V projektu je použit převodník SN65HVD485EDR. Schéma zapojení je na následujícím obrázku. DPS obsahuje řadové piny pro připojení terminačního odporu a pro nastavení klidové úrovně na sběrnici. Vývody jsou přizpůsobeny pro připojení k STM32F0 Discovery Boardu ze kterého je převodník zároveň napájen.

RFM23B

RFM23B je bezdrátový modul pracující v ISM pásmu, modul pro komunikaci s hostitelským mikroprocesorem využívá rozhraní SPI, má výstup pro vyvolání přerušení a jeho rozměry jsou pouze 16x15mm. Modul má rozteč vývodu 2mm proto byla vytvořena adaptérová deska pro připojení k discovery boardu.

MCU

MCU Pro testování projektu byly použity vývojové kyty od STMicroelectronics, konkrétně pro master modul deska STM32Nucleo osazená mikroprocesorem STM32F411RET6 a pro slave moduly deska STM32F0 discovery osazená mikroprocesorem STM32F030R8T6.

Software

Pro vývoj software byly využito vývojové prostředí Keil uVision5 a konfigurační software pro STM32 mikroprocesory STM32CubeMX. Tento softwarový nástroj generuje projekt obsahující HAL knihovny, které podporují většinu dostupných periférii na STM32 mikroprocesorech. Software byl vyvíjen s použitím operačního systému FreeRTOS a veškeré funkce jsou tak neblokující. Software je rozdělen do tří oddělených části fyzické vrstvy v souboru RS485.c, RFM23.c a vrstvy komunikačního protokolu Dombus.c . Popis funkcí

RS485

Pro obsluhu rozhraní RS485 slouží několik funkcí.

RS485 Init

void RS485_Init(void)
{
    RS485RxQueue = xQueueCreate(2, 4);
 
    __HAL_UART_FLUSH_DRREGISTER(&huart1);
    __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_ORE | UART_FLAG_FE);
    HAL_UART_Receive_DMA(&huart1,RS485RxBuffer,RS485RxBuffLen);
 
#if DEBUG
    printf("RS485 Interface is Initialized Now\n");
#endif
}

RS485_Master_Send

uint8_t RS485_Master_Send(uint8_t *pSData, uint16_t lenght)
{
	HAL_GPIO_WritePin(RS485_DIR_PORT, RS485_DIR_PIN,GPIO_PIN_SET); // Put RS485 transceiver into transmiting state
	uint8_t status = HAL_UART_Transmit_DMA(&huart1, pSData, lenght);	//Transmit data
	printf("RS485 frame send\n");
	if (status == HAL_OK) {
		return(RS485TransmitOk);
	} else {
		return(RS485TransmitNok);
	}
}

Pro odesílání dat je v master i slave kodu využit DMA přenos tak aby byla knihovna ve spojení s FreeRTOS plně neblokující.

RS485_Master_Received

uint8_t RS485_Master_Received(uint8_t* pRData, uint16_t length)
{
	__HAL_UART_FLUSH_DRREGISTER(&huart1);
 
	//xSemaphoreGive(RS485RxEnable);	//UnBlock receiver task
	//printf("RS485 Give semaphore\n");
	if(length != UnKNOWN)
	{
		printf("Lenght is Known\n");
		pRData[0] = length & 0x00FF;
		pRData[1] = length >> 8;
		xQueueSend(RS485RxQueue, &pRData ,portMAX_DELAY);//500/portTICK_PERIOD_MS );	// Send a pointer to the data in the queue
		vTaskResume( RS485RxTaskHandle );	//UnBlock receiver task
	}
 
	if(length == UnKNOWN)
	{
		printf("Lenght is UnKnown\n");
		pRData[0] = NULL;
		pRData[1] = NULL;
		xQueueSend(RS485RxQueue, &pRData ,portMAX_DELAY);//500/portTICK_PERIOD_MS );	// Send a pointer to the data in the queue
		vTaskResume( RS485RxTaskHandle );	//UnBlock receiver task
	}
 
	return(0);
}

Při požadavku na příjem dat je zjištěno zda je známá délka odesílaných dat a nastaven příznak. Do FreeRTOS fronty je předán ukazatel na pole pro zápis příjmaných dat a v případě známe délky příjmaných dat je uložena do pole. Voláním funkce vTaskResume( RS485RxTaskHandle ); je vlákno RS485RX přesunuto do stavu Ready.

Vlákno pro obsluhu příjmu dat pomocí kruhového DMA

void	StartRS485RxTask(void const * argument)
{
    for(;;)
    {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
 
        // --- UART receive ---
        while (RS85RxReadPtr != RS485_rx_write_ptr) {
            uint8_t ch = RS485RxBuffer[RS85RxReadPtr];
            RS485RxBuffer[RS85RxReadPtr] = 0; // invalidate received byte
            if (++RS85RxReadPtr >= RS485RxBuffLen) RS85RxReadPtr = 0; // increase read pointer
 
            RS485_ExecuteIncomingBytes(ch); // process every received byte with the RX state machine
        }
 
        osDelay(1);
    }
}

Makro pro posun ukazatele v kruhovém bufferu na zakladě proměné NDTR indikující stav DMA přenosu

#define RS485_rx_write_ptr (RS485RxBuffLen - (huart1.hdmarx->Instance->NDTR))

Vlákno RS485RxTask kontroluje pomocí makra RS485_rx_write_ptr stav ukazatele kruhového bufferu DMA řadiče a na jeho základě volá zpracování nově přijatých bytů.

RS485_ExecuteIncomingBytes

uint8_t RS485_ExecuteIncomingBytes(uint8_t ch)
{
	uint32_t	test;
	static uint8_t *pArray;
	static uint8_t ArrayIndex = 0;
	static uint8_t Mode = 0;
	static uint16_t BytsToRecived = 0;
 
	printf("Incoming char = %i\n",ch);
 
	if(!ArrayIndex)	//If ther is first byte
	{
		printf("Waiting for Queue\n");
		xQueueReceive( RS485RxQueue, &pArray, portMAX_DELAY );	//Get pointer to Data Buffer
 
		printf("Have Queue\n");
		printf("Adress of pointer is = %u\n",pArray);
 
		if(pArray[0] || pArray[1] == 0)	//If first byte of array contains zero, mode read of lenght from incoming data is activated
		{
			Mode = UnKNOWN_LENGHT;
			BytsToRecived = 3;
			printf("UnKNOWN_LENGHT\n");
		}
		else if(pArray[0] || pArray[1] != 0) //If first byte of array contains numbers, mode read of specific amount of data is activated
		{
			Mode = KNOWN_LENGHT;
			BytsToRecived = pArray[1];
			BytsToRecived = (BytsToRecived << 8) + pArray[0];
			printf("KNOWN_LENGHT = %i\n",BytsToRecived);
		}
	}
 
	pArray[ArrayIndex++] = ch;
 
	if(ArrayIndex == 3 && Mode == UnKNOWN_LENGHT) // When we have recieved 3 bytes, first - PREFIX, Second - lower byte of lenght, Third - upper byte of lenght
	{
		BytsToRecived = pArray[2];
		BytsToRecived = (BytsToRecived << 8) + pArray[1];
		printf("UnKNOWN_LENGHT = %i\n",BytsToRecived);
	}
 
 
	if(ArrayIndex== BytsToRecived)
	{
		#if DEBUG
		printf("RS485 recieved frame ");
		for(uint8_t k = 0; k < BytsToRecived; k++)
		{
			printf("%i,",pArray[k]);
		}
		printf("\n");
		#endif
 
		ArrayIndex = 0;
		RS485_RX_Callback();
		xSemaphoreGive( xRS485_UART_Mutex);
		vTaskSuspend( RS485RxTaskHandle );
	}
 
	return(1);
 
}

Funkce RS485_ExecuteIncomingBytes načte z fronty ukazatel na pole pro uložení nově příchozích dat, po příjmu předem zvoleného množství dat nebo množství dat na základě 2 a 3 přijatého bytu uvolní semafor a suspenduje vlákno pro příjem dat.

RS485_Slave_Received

uint8_t RS485_Slave_Received(uint8_t* pRData, uint16_t length)
{
	__HAL_UART_FLUSH_DRREGISTER(&huart1);
 
	if(length != UnKNOWN)
	{
		pRData[0] = length & 0x00FF;
		pRData[1] = length >> 8;
		xQueueSend(RS485RxQueue, &pRData ,portMAX_DELAY);	// Send a pointer to the data in the queue
		vTaskResume( RS485_RXTaskHandle );	//UnBlock receiver task
	}
 
	if(length == UnKNOWN)
	{
		pRData[0] = NULL;
		pRData[1] = NULL;
 
		xQueueSend(RS485RxQueue, &pRData ,portMAX_DELAY);	// Send a pointer to the data in the queue
		vTaskResume( RS485_RXTaskHandle );	//UnBlock receiver task
	}
 
	return(0);
}

RS485_Slave_Send

uint8_t RS485_Slave_Send(uint8_t *pSData, uint16_t lenght)
{
	HAL_GPIO_WritePin(RS485_DIR_PORT, RS485_DIR_PIN,GPIO_PIN_SET); // Put RS485 transceiver into transmiting state
	uint8_t status = HAL_UART_Transmit_DMA(&huart1, pSData, lenght);	//Transmit data
 
	if (status == HAL_OK) {
		return(RS485TransmitOk);
	} else {
		return(RS485TransmitNok);
	}
}

RS485 Slave funkce pro příjem a odeslání dat jsou velmi podobné Master funkcím.

Master SendFrame

uint8_t SendFrame(Type_Def_union_Outgoing_Frame *Outgoing_Frame)
{
	static uint8_t Signature = 1;
	uint16_t DataCount = 0;
 
	xSemaphoreTake(xRS485_UART_Mutex,portMAX_DELAY);
 
	while(Outgoing_Frame->Outgoing_Struct.Data[DataCount] || Outgoing_Frame->Outgoing_Struct.Data[DataCount + 1])
	{
		DataCount++;
	}
 
	Outgoing_Frame->Outgoing_Struct.Lenght = StructLength + DataCount;
	Outgoing_Frame->Outgoing_Struct.Prefix = PREFIX;
	Outgoing_Frame->Outgoing_Struct.Signature = Signature;//(uint8_t)(rand() % 254 + 1);
	Signature < 10 ? (Signature++) : (Signature = 1);
	Outgoing_Frame->Outgoing_Struct.Crc = 0;
	Outgoing_Frame->Outgoing_Struct.Crc = CheckSum16( Outgoing_Frame->Outgoing_Array, Outgoing_Frame->Outgoing_Struct.Lenght);
 
	#if DEBUG
	printf("Frame =");
	for(uint8_t k = 0; k < Outgoing_Frame->Outgoing_Struct.Lenght; k++)
	{
		printf("%i,",Outgoing_Frame->Outgoing_Array[k]);
	}
	printf("\n");
	#endif
 
	printf("DomBus Sending frame\n");
	RS485_Master_Send(Outgoing_Frame->Outgoing_Array, Outgoing_Frame->Outgoing_Struct.Lenght);
 
	return(1);
}

Funkce Master_SendFrame z knihovný Dombus.c vezme semafor pro zablokování vlákna ze kterého je funkce volána, spočítá délku odesílaných dat, vypočítá CRC, přidá do zprávy podpis a odešle data na zpracování do RS485.c knihovny.

RecievedFrame

uint8_t	RecievedFrame(Type_Def_union_Incoming_Frame *Incoming_Frame)
{
	xSemaphoreTake(xRS485_UART_Mutex,portMAX_DELAY);
 
	RS485_Slave_Received((uint8_t*)&Incoming_Frame->Incoming_Array,UnKNOWN);
 
#if DEBUG
	printf("IncomingFrame =");
	for(uint8_t k = 0; k < Incoming_Frame->Incoming_Struct.Lenght; k++)
	{
		printf("%i,",Incoming_Frame->Array[k]);
	}
	printf("\n");
#endif
 
	return(1);
}

Funkce Master_RecievedFrame Vezme semafor a zavolá funkci pro příjem z knihovny RS485.c.

Master ParseFrame

uint8_t ParseFrame(Type_Def_union_Outgoing_Frame *SendFrame, Type_Def_union_Incoming_Frame *ReceivedFrame)
{
	uint8_t ParseResult = 0;
	uint16_t CRCtemp = 0;
	uint16_t CRCReceived = 0;
 
	xSemaphoreTake(xRS485_UART_Mutex,portMAX_DELAY);
 
	CRCReceived = ReceivedFrame->Incoming_Struct.Crc;
	ReceivedFrame->Incoming_Struct.Crc = 0;
 
	CRCtemp = CheckSum16(ReceivedFrame->Incoming_Array,ReceivedFrame->Incoming_Struct.Lenght);
	(CRCtemp != CRCReceived) ? (ParseResult +=	CrcNok) : (ParseResult += CrcOk);
	(SendFrame->Outgoing_Struct.Signature != ReceivedFrame->Incoming_Struct.Signature) ? (ParseResult +=	SignatureNok) : (ParseResult += SignatureOk);
 
	printf("Parse Frame Result = %i,",ParseResult);
	return(ParseResult);
}

Funkce ParseFrame slouží po příjmu odpovědi od Slave jednotky ke kontrole CRC a podpisu.

Slave SendFrame

uint8_t SendFrame(Type_Def_union_Outgoing_Frame *Outgoing_Frame)
{
	uint16_t DataCount = 0;
 
	xSemaphoreTake(xRS485_UART_Mutex,portMAX_DELAY);
 
	while(Outgoing_Frame->Outgoing_Struct.Data[DataCount] || Outgoing_Frame->Outgoing_Struct.Data[DataCount + 1])
	{
		DataCount++;
	}
 
	Outgoing_Frame->Outgoing_Struct.Lenght = StructLength + DataCount;
	Outgoing_Frame->Outgoing_Struct.Prefix = PREFIX;
	Outgoing_Frame->Outgoing_Struct.Crc = CheckSum16( Outgoing_Frame->Outgoing_Array, Outgoing_Frame->Outgoing_Struct.Lenght);
	RS485_Slave_Send(Outgoing_Frame->Outgoing_Array, Outgoing_Frame->Outgoing_Struct.Lenght);
 
	return(1);
}

RecievedFrame

uint8_t	RecievedFrame(Type_Def_union_Incoming_Frame *Incoming_Frame)
{
	xSemaphoreTake(xRS485_UART_Mutex,portMAX_DELAY);
	RS485_Slave_Received((uint8_t*)&Incoming_Frame->Incoming_Array,UnKNOWN);
 
	return(1);
}

Slave ParseFrame

uint8_t ParseFrame(Type_Def_union_Outgoing_Frame *Outgoing_Frame, Type_Def_union_Incoming_Frame *ReceivedFrame)
{
	uint8_t ParseResult = 0;
	uint16_t CRCtemp = 0;
	uint16_t CRCReceived = 0;	
	uint8_t lenght = 0;
 
	xSemaphoreTake(xRS485_UART_Mutex,portMAX_DELAY);
	CRCReceived = ReceivedFrame->Incoming_Struct.Crc;
	ReceivedFrame->Incoming_Struct.Crc = 0;
	lenght = ReceivedFrame->Incoming_Struct.Lenght;
	CRCtemp = CheckSum16(ReceivedFrame->Incoming_Array,lenght);
	(CRCtemp != CRCReceived) ? (ParseResult +=	CrcNok) : (ParseResult += CrcOk);
	(ReceivedFrame->Incoming_Struct.SlaveAddress == MyAdress) ? (ParseResult +=	 AdressOk) : (ParseResult +=  AdressNok);
	Outgoing_Frame->Outgoing_Struct.Signature = ReceivedFrame->Incoming_Struct.Signature;
	Outgoing_Frame->Outgoing_Struct.Data[0] = 0x10;
 
	xSemaphoreGive(xRS485_UART_Mutex);
 
 
	return(ParseResult);
}

Funkce knihovny Dombus pro Slave moduly jsou opět velmi podobné.

CheckSum16

uint16_t CheckSum16(uint8_t* data_p, uint16_t length)
{
	uint8_t x;
	uint16_t crc = 0xFFFF;
 
	while (length--) {
		x = crc >> 8 ^ *data_p++;
		x ^= x>>4;
		crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x);
	}
	return crc;
}

*Vlákno pro komunikaci*

oid StartRS485TxTask(void const * argument)
{
 
    for(;;)
    {
        uint8_t FramData[] = "MPOAProjekt";
 
        Type_Def_union_Outgoing_Frame Frameout;
        Type_Def_union_Incoming_Frame Framein;
 
        Frameout.Outgoing_Struct.SlaveAddress = 0x10;
        Frameout.Outgoing_Struct.IOAddress = 10;
        Frameout.Outgoing_Struct.Instruction = 0x20;
 
        for(uint8_t k = 0; k < (sizeof(Frameout.Outgoing_Struct.Data)/sizeof(Frameout.Outgoing_Struct.Data[0])); k++)
        {
            Frameout.Outgoing_Struct.Data[k] = NULL;
        }
 
        strcpy((char*)Frameout.Outgoing_Struct.Data, (char*)FramData);
 
        SendFrame(&Frameout);
 
        RecievedFrame(&Framein);
 
        ParseFrame(&Frameout,&Framein);
    }
}

Vlákno Master modulu simulující odesílání a příjem dat.

Datové proměnné pro příjem a odesílání dat

typedef __packed struct {
    //-----Sender------//
    uint8_t Prefix;
    uint16_t Lenght;
    uint8_t SlaveAddress;
    uint8_t IOAddress;
    uint8_t Signature;
    uint8_t Instruction;
    uint16_t Crc;
    uint8_t Data[20];
} Type_Def_Struc_Outgoing_Frame;
 
typedef union {
    //-----Sender------//
    __packed	Type_Def_Struc_Outgoing_Frame	Outgoing_Struct;
    uint8_t	Outgoing_Array[sizeof(Type_Def_Struc_Outgoing_Frame)];
} Type_Def_union_Outgoing_Frame;
 
typedef __packed struct {
    //-----Receiver------//
    uint8_t Prefix;
    uint16_t Lenght;
    uint8_t Signature;
    uint16_t Crc;
    uint8_t Data[20];
} Type_Def_Struc_Incoming_Frame;
 
typedef union {
	//-----Receiver------//
    __packed	Type_Def_Struc_Incoming_Frame	Incoming_Struct;
    uint8_t	Incoming_Array[sizeof(Type_Def_Struc_Incoming_Frame)];
} Type_Def_union_Incoming_Frame;

Datové struktůry umístěné v unionech spolu s poly pro serializaci dat a jejich odeslání na UART.

Ukázka komunikace zaznamenáné logickým analyzátorem

RFM23

Knihovna pro RFM23 nebyla implementována do prostředí FreeRTOS a nevyužívá DMA přenos. Bylo vytvořeno pouze pár základních funkcí pro obsluhu RFM modulu

void rfm23_init() 
{
	rfm23_read(RFM23_03h_ISR1);
	rfm23_read(RFM23_04h_ISR2);
 
	osDelay(16);
}
 
uint8_t rfm23_spi_write(uint8_t val) 
{
	uint8_t buffer[1];
	HAL_SPI_Transmit(&hspi,(uint8_t*)val,1,500);
	HAL_SPI_Receive(&hspi,buffer,1,500);	
 
	return(buffer[0]);
}
 
void rfm23_spi_select() 
{
	HAL_GPIO_WritePin(RFM23_SPI_PORT, RFM23_SPI_SELECT,GPIO_PIN_RESET); 
}
 
void rfm23_spi_unselect() 
{
	HAL_GPIO_WritePin(RFM23_SPI_PORT, RFM23_SPI_SELECT,GPIO_PIN_SET); 
}
 
uint8_t rfm23_read(uint8_t addr) 
{
	addr &= 0x7F;
	rfm23_spi_select();
	rfm23_spi_write(addr);
	uint8_t val = rfm23_spi_write(0x00);
	rfm23_spi_unselect();
 
	return val;
}
 
void rfm23_write(uint8_t addr, uint8_t val) 
{
	addr |= 0x80;	//Write mode	
	rfm23_spi_select();
	rfm23_spi_write(addr);
	rfm23_spi_write(val);
	rfm23_spi_unselect();
}
 
void rfm23_write_burst(uint8_t addr, uint8_t val[], uint8_t len) 
{
	addr |= 0x80;	//Write mode
	rfm23_spi_select();
	rfm23_spi_write(addr);
	for (uint8_t i = 0; i < len; i++) {
		rfm23_spi_write(val[i]);
	}
	rfm23_spi_unselect();
}
 
void rfm23_read_burst(uint8_t addr, uint8_t val[], uint8_t len) 
{	
	addr &= 0x7F;
	rfm23_spi_select();
	rfm23_spi_write(addr);
	for (uint8_t i = 0; i < len; i++) {
		val[i] = rfm23_spi_write(0x00);
	}
	rfm23_spi_unselect();
}
 
void rfm23_mode_ready() 
{
	rfm23_write(RFM23MODE, XTMODE); 
	osDelay(200);
}
 
void rfm23_mode_rx() 
{
	rfm23_write(RFM23MODE, RXMODE);
	osDelay(200);
}
 
void rfm23_mode_tx() 
{
	rfm23_write(RFM23MODE, TXMODE);
	osDelay(200);
}
 
 
void rfm23_clear_rxfifo() 
{
	rfm23_write(0x08, 0x02);
	rfm23_write(0x08, 0x00);
}
 
void rfm23_clear_txfifo() 
{
	rfm23_write(0x08, 0x01);
	rfm23_write(0x08, 0x00);
}
 
void rfm23_send(uint8_t data[], uint8_t len) 
{
	rfm23_clear_txfifo();
	rfm23_write(0x3e, len);
	rfm23_write_burst(0x7f, data, len);
	rfm23_write(0x07, 0x09);
}
 
void rfm23_set_address(uint8_t addr) 
{
	rfm23_write(0x3A, addr);
	rfm23_write(0x40, addr);
	rfm23_write(0x32, 0x04);
}
 
void rfm23_send_addressed(uint8_t addr, uint8_t data[], uint8_t len) 
{
	rfm23_write(0x3b, addr);
	rfm23_send(data, len);
}
 
void rfm23_receive(uint8_t data[], uint8_t len) 
{
	rfm23_read_burst(0x7f, data, len);
}
 
 
uint8_t rfm23_get_packet_length() 
{
	return rfm23_read(0x4b);
}

Závěr

V projektu byla realizována knihovna pro komunikaci po rozhraní RS485 s plným využitím výhod operačního systému FreeRTOS a DMA řadiče pro přenos dat z paměti d periferii. S knihovnou je tak možné přenášet velké množství dat s minimálním vytížením jádra MCU. Dále byl implementován komunikační protokol a jeho jednoduché zpracování. Díky funkcím operačního systému je možné jednoduše doplnit ochranu proti neodeslání odpovědi například nastavením konkrétní maximální čekací doby na uvolnění semaforu. V aktuální konfiguraci je čekání nekonečné. Modul RFM23 se nepodařilo plně implementovat. Velká část řídící logiky v rámci RTOS a DMA se pro něj dá využít z RS485 knihovny. Jeho doplnění už tedy není tolik komplikované. Pro jednoduché testování byly vytvořeny desky plošných spojů které lze přímo připojit k vývojovým kitům.

autor — Dominik Stupka 2016/01/17 23:31

2015/comm-rs485.txt · Poslední úprava: 2016/01/17 23:43 autor: Dominik Stupka