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
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 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 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.
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í
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.
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); }
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