====== 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. {{ :2015:comm-rs485:rs485schemtaic.png?600 |}} 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. {{ :2015:comm-rs485:rs485dpsfoto.png?800 |}} ===== 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. {{ :2015:comm-rs485:rfm23schematic.png?500 |}} Modul má rozteč vývodu 2mm proto byla vytvořena adaptérová deska pro připojení k discovery boardu. {{ :2015:comm-rs485:rfm23dpsfoto.png?800 |}} ===== 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. {{ :2015:comm-rs485:testovacisestava.png?800 |}} ====== 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===== {{ :2015:comm-rs485:analyzator_popis_2.png?900 |}} =====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 --- //[[xstupk00@stud.feec.vutbr.cz|Dominik Stupka]] 2016/01/17 23:31//