====== 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//