⬆️ ⬇️

STM32, C ++ and FreeRTOS. Development from scratch. Part 4 (Interrupts, UART and UnHART)

Conducting



Once on vacation in the city on the Neva and visiting many beautiful places, I still, in the evenings over a cup of beer, dealt with the UART. Moreover, I bought good Fisher FA011 headphones, which I had to buy USB SOUND BLASTER X-FI HD and wanted to listen to music.

Previous articles first moved to Geektime, then I overtook them back, I don’t even know where to put them now :)

But just in case they are here:

STM32, C ++ and FreeRTOS. Development from scratch. Part 1

STM32, C ++ and FreeRTOS. Development from scratch. Part 2 and

STM32, C ++ and FreeRTOS. Development from scratch. Part 3 (LCD and Screens)



UART



After a detailed study of the microcontroller, it seemed to me that everything is simple. Setup and test sending of a byte to the port went without a hitch, everything worked like a clock, and then I decided to use interrupts. It was necessary to make the interrupt handler be a static class method. And IAR in the compiler manual, and wrote:

Special function types can be used for static member functions. For example, in the

The following example is the interrupt function:

class Device { static __irq void handler(); }; 


But bad luck, for Cortex M this method does not fit and

On the ARM Cortex-M, an interrupt service line

normal function, which means no special keywords are required. Thus, the keywords

__irq, __fiq, and __nested are not available when you compile for ARM Cortex-M.

These exception function names are defined in cstartup_M.c and cstartup_M.s.

Vector code:

NMI_Handler

HardFault_Handler

MemManage_Handler

BusFault_Handler

...

The vector table is implemented as an array. It should always have the name

__vector_table



Or simply, your interrupt handler must have the same name as it has in the vector table defined in the startup file. This is done using a special keyword - the weak link __weak (in the PUBWEAK assembler), which means that this definition will be used until it is found that at least one matching letter is written without the keyword __week. Well, that is, if you define a function with the exact same name without this directive, then the compiler will use this definition, and if you do not define, then it is marked with __weak.

It is clear that I can’t insert C in the startup_stm32l1xx_md.s or startup_stm32l1xx_md.c file . C ++ name of the static method of the type cUart :: USART2_IRQHandler () , the assembler simply won’t understand it.

And just “USART2_IRQHandler” does not match the definition of “cUart :: USART2_IRQHandler ()” .

You can use extern "C" {void USART2_IRQHandler (void) {...}} , but this means that I will insert C from here, which I do not need at all, and in general access from such a function to the attributes of my class, for example the buffer will not, and it will be necessary to fence a bunch of ugly code :).

Therefore, I decided to go the other way and create the startup_stm32l1xx_md.cpp file. An Internet search found that some people had the exact same problem. For example,

In general, the idea is as follows: We declare classes with static methods in startup_stm32l1xx_md.cpp (which will be interrupt handlers), create a table __vector_table, where each interrupt vector has a pointer to these static methods. Further we do __weak definition of each method

And now when in the code the compiler sees the implementation of void cUart1 :: handler () , it takes it without a second thought. Of course, your classes and methods should be named exactly as defined in startup_stm32l1xx_md.cpp .

It is necessary not to forget about the functions of FreeRtos: vPortSVCHandler , xPortPendSVHandler , xPortSysTickHandler and put them on the right interrupt and voila - everything works:

startup_stm32l1xx_md.cpp
 #pragma language = extended #pragma segment = "CSTACK" extern "C" void __iar_program_start( void ); extern "C" void vPortSVCHandler(void); extern "C" void xPortPendSVHandler(void); extern "C" void xPortSysTickHandler(void); class cNMI { public: static void handler(void); }; class cHardFault { public: static void handler(void); }; class cMemManage { public: static void handler(void); }; class cBusFault { public: static void handler(void); }; class cUsageFault { public: static void handler(void); }; class cDebugMon { public: static void handler(void); }; class cWindowWatchdog { public: static void handler(void); }; class cPvd { public: static void handler(void); }; class cTamperTimeStamp { public: static void handler(void); }; class cRtcWakeup { public: static void handler(void); }; class cFlash { public: static void handler(void); }; class cRcc { public: static void handler(void); }; class cExti { public: static void line0Handler(void); static void line1Handler(void); static void line2Handler(void); static void line3Handler(void); static void line4Handler(void); static void line9Handler(void); static void line15_10Handler(void); }; class cDma { public: static void channellHandler(void); static void channel2Handler(void); static void channel3Handler(void); static void channel4Handler(void); static void channel5Handler(void); static void channel6Handler(void); static void channel7Handler(void); }; class cAdc { public: static void handler(void); }; class cDac { public: static void handler(void); }; class cUsb { public: static void highPriorityHandler(void); static void lowPriorityHandler(void); static void fsWakeupHandler(void); }; class cComp { public: static void handler(void); }; class cLcdDriver { public: static void handler(void); }; class cTim9 { public: static void handler(void); }; class cTim2 { public: static void handler(void); }; class cTim3 { public: static void handler(void); }; class cTim4 { public: static void handler(void); }; class cTim10 { public: static void handler(void); }; class cTim6 { public: static void handler(void); }; class cTim7 { public: static void handler(void); }; class cTim11 { public: static void handler(void); }; class cI2C1 { public: static void eventHandler(void); static void errorHandler(void); }; class cI2C2 { public: static void eventHandler(void); static void errorHandler(void); }; class cSpi1 { public: static void handler(void); }; class cSpi2 { public: static void handler(void); }; class cUart1 { public: static void handler(void); }; class cUart2 { public: static void handler(void); }; class cUart3 { public: static void handler(void); }; class cRtcAlarm { public: static void handler(void); }; typedef void( *intfunc )( void ); typedef union { intfunc __fun; void * __ptr; } intvec_elem; // The vector table is normally located at address 0. // When debugging in RAM, it can be located in RAM, aligned to at least 2^6. // If you need to define interrupt service routines, // make a copy of this file and include it in your project. // The name "__vector_table" has special meaning for C-SPY: // it is where the SP start value is found, and the NVIC vector // table register (VTOR) is initialized to this address if != 0. #pragma location = ".intvec" extern "C" const intvec_elem __vector_table[] = { { .__ptr = __sfe( "CSTACK" ) }, __iar_program_start, cNMI::handler, cHardFault::handler, cMemManage::handler, cBusFault::handler, cUsageFault::handler, 0, 0, 0, 0, vPortSVCHandler, // freeRTOS  ! cDebugMon::handler, 0, xPortPendSVHandler, // freeRTOS  ! xPortSysTickHandler, // freeRTOS  ! //External Interrupts cWindowWatchdog::handler, //Window Watchdog cPvd::handler, //PVD through EXTI Line detect cTamperTimeStamp::handler, //Tamper and Time Stamp cRtcWakeup::handler, //RTC Wakeup cFlash::handler, //FLASH cRcc::handler, //RCC cExti::line0Handler, //EXTI Line 0 cExti::line1Handler, //EXTI Line 1 cExti::line2Handler, //EXTI Line 2 cExti::line3Handler, //EXTI Line 3 cExti::line4Handler, //EXTI Line 4 cDma::channellHandler, //DMA1 Channel 1 cDma::channel2Handler, //DMA1 Channel 2 cDma::channel3Handler, //DMA1 Channel 3 cDma::channel4Handler, //DMA1 Channel 4 cDma::channel5Handler, //DMA1 Channel 5 cDma::channel6Handler, //DMA1 Channel 6 cDma::channel7Handler, //DMA1 Channel 7 cAdc::handler, //ADC1 cUsb::highPriorityHandler, //USB High Priority cUsb::lowPriorityHandler, //USB Low Priority cDac::handler, //DAC cComp::handler, //COMP through EXTI Line cExti::line9Handler, //EXTI Line 9..5 cLcdDriver::handler, //LCD cTim9::handler, //TIM9 cTim10::handler, //TIM10 cTim11::handler, //TIM11 cTim2::handler, //TIM2 cTim3::handler, //TIM3 cTim4::handler, //TIM4 cI2C1::eventHandler, //I2C1 Event cI2C1::errorHandler, //I2C1 Error cI2C2::eventHandler, //I2C2 Event cI2C2::errorHandler, //I2C2 Error cSpi1::handler, //SPI1 cSpi2::handler, //SPI2 cUart1::handler, //USART1 cUart2::handler, //USART2 cUart3::handler, //USART3 cExti::line15_10Handler, //EXTI Line 15..10 cRtcAlarm::handler, //RTC Alarm through EXTI Line cUsb::fsWakeupHandler, //USB FS Wakeup from suspend cTim6::handler, //TIM6 cTim7::handler //TIM7 }; __weak void cNMI::handler() { while (1) {} } __weak void cHardFault::handler() { while (1) {} } __weak void cMemManage::handler() { while (1) {} } __weak void cBusFault::handler() { while (1) {} } __weak void cUsageFault::handler() { while (1) {} } __weak void cDebugMon::handler() { while (1) {} } __weak void cWindowWatchdog::handler() { while (1) {} } __weak void cPvd::handler() { while (1) {} } __weak void cTamperTimeStamp::handler() { while (1) {} } __weak void cRtcWakeup::handler() { while (1) {} } __weak void cFlash::handler() { while (1) {} } __weak void cRcc::handler() { while (1) {} } __weak void cExti::line0Handler() { while (1) {} } __weak void cExti::line1Handler() { while (1) {} } __weak void cExti::line2Handler() { while (1) {} } __weak void cExti::line3Handler() { while (1) {} } __weak void cExti::line4Handler() { while (1) {} } __weak void cExti::line9Handler() { while (1) {} } __weak void cExti::line15_10Handler() { while (1) {} } __weak void cDma::channellHandler() { while (1) {} } __weak void cDma::channel2Handler() { while (1) {} } __weak void cDma::channel3Handler() { while (1) {} } __weak void cDma::channel4Handler() { while (1) {} } __weak void cDma::channel5Handler() { while (1) {} } __weak void cDma::channel6Handler() { while (1) {} } __weak void cDma::channel7Handler() { while (1) {} } __weak void cAdc::handler() { while (1) {} } __weak void cUsb::fsWakeupHandler() { while (1) {} } __weak void cUsb::highPriorityHandler() { while (1) {} } __weak void cUsb::lowPriorityHandler() { while (1) {} } __weak void cDac::handler() { while (1) {} } __weak void cComp::handler() { while (1) {} } __weak void cLcdDriver::handler() { while (1) {} } __weak void cTim2::handler() { while (1) {} } __weak void cTim3::handler() { while (1) {} } __weak void cTim4::handler() { while (1) {} } __weak void cTim6::handler() { while (1) {} } __weak void cTim7::handler() { while (1) {} } __weak void cTim9::handler() { while (1) {} } __weak void cTim10::handler() { while (1) {} } __weak void cTim11::handler() { while (1) {} } __weak void cI2C1::errorHandler() { while (1) {} } __weak void cI2C1::eventHandler() { while (1) {} } __weak void cI2C2::errorHandler() { while (1) {} } __weak void cI2C2::eventHandler() { while (1) {} } __weak void cSpi1::handler() { while (1) {} } __weak void cSpi2::handler() { while (1) {} } __weak void cUart1::handler() { while (1) {} } __weak void cUart2::handler() { while (1) {} } __weak void cUart3::handler() { while (1) {} } __weak void cRtcAlarm::handler() { while (1) {} } extern "C" void __cmain( void ); extern "C" __weak void __iar_init_core( void ); extern "C" __weak void __iar_init_vfp( void ); #pragma required=__vector_table void __iar_program_start( void ) { __iar_init_core(); __iar_init_vfp(); __cmain(); } 




image





')

DWART. Receive message transfer



So, with the interrupts figured out, now you can take up the implementation of a thread of a simple protocol. Modbus was dropped immediately - not very difficult :). Search for popular protocols for devices issued - HART, an industrial protocol for devices such as pressure sensors, temperature sensors and flowmeters - is perfect.

And the task that I set for myself will be communicating via this protocol with the device setup program, say Pactware , or the Elemental HARTConfig , having faked some popular sensor, for example, a pressure sensor - Yokogawa EJA, so that this program doesn’t detect . I must say that everything turned out :)

image

And even managed to remove the trend in air temperature, measured by the internal temperature sensor of the microcontroller:

image



This is how my Pactware device is seen :)

image



Normal HART documentation is closed. But I managed to find fragmentary descriptions that would be fine for my demo project. Here is an example of one of these resources: Description of the HART protocol or here is the OSI HART Model Protocol

In short - on the line there is a master (main) and slave (slave) device. The main thing sends requests, the slave responds at a speed of 1200 bits per second - everything is simple.

There may be two main devices on the line, but I will simply communicate with ONE RS232 master.

For the same reason, I don’t need to particularly follow the bus arbitration and determine when it’s difficult to have a token, and when not, and the whole arbitration in my case is only that - I have the token, after 2 symbols of silence on the line. At a speed of 1200 bits per second it is approximately 19 ms. Those. if within 19 ms, I have no interruption in reception - the parcel is considered accepted, and the token is with me, I can answer and should start responding within about 250 ms, otherwise the token will go to the master again.

In general, I called this protocol Dwart , a symbiosis between Dwarf - dwarf, gnome and HART.

First you need to select the entity that will operate:





In order to observe the principle of puff pie - the classes of the lower layer will not know about the classes of the layer located above, i.e. For example, Command will work only with Frame, and Frame with LinkLayer, while LinkLayer will not know anything about Command and Frame, and Frame about Command.

So what is the idea - to accept bytes until the silence timer has been activated, then to notify Dwart, which will call the parsing method of the received message and, depending on the request, execute the necessary command.

In general, ideally, say when requesting command 1 from the master, I want the answer to it to look something like this:

 Command1.Response.PrimaryVariable = 3.54 Command1.Send(); 


First, let's estimate the channel level, this figure shows only public methods, so as not to litter the picture.

image



I will clarify the picture a bit, the methods of the interrupt handler are static, and in order to access the methods and fields of the class instance in these static methods, it is necessary that either the fields and methods are also static, or they can be addressed directly to the class instance. I went to the second - the right way, referring to a specific instance of the class, which I initialize in the constructor with the this pointer.

LinkLayer will receive bytes from the UART port and add them to the receive buffer.

But since HART has 0xFF synchronization bytes, called preambles, and their number can reach 20, (and we don’t need them at all, because they don’t carry any information, and all we need is to determine the beginning of the frame by sequence ( 0xFF 0xFF <start byte>)), it will waste 20 preambles to the buffer, so I’ll just watch them right in the interrupt and as soon as they run out, I’ll start to add bytes to the buffer.

After receiving each byte, I will restart the timer for 19 ms, if it works, the package is considered to be accepted.

Another task is to notify the linklayer from the linklayertimer class as soon as the timer interrupt is triggered, indicating the end of receiving the request from the master. For this, I used the Designer Observer template, as a result LinkLayer simply subscribes to events from LinkLayerTimer. It looks like this:

linklayertimer.h
 #include "types.h" //lint !e537    #include "observable.h" //lint !e537  iObservable class cLinkLayerTimer : public iObservable { public: explicit cLinkLayerTimer(tU16 timeout); void start(void) const; private: static void irqHandler(void); static cLinkLayerTimer* instance; }; 




linklayertimer.cpp
 #include "linklayertimer.h" //lint !e537    #include "susuassert.h" //lint !e537  ASSERT #include <stdio.h> //lint !e537  NULL cLinkLayerTimer* cLinkLayerTimer::instance = NULL; /******************************************************************************* * Function: constructor * Description:    TIM2 ******************************************************************************/ cLinkLayerTimer::cLinkLayerTimer(tU16 timeout) { ASSERT(instance != NULL); this->instance = this; TIM2->ARR = (uint16_t)timeout; } /******************************************************************************* * Function: start * Description:  .     ******************************************************************************/ void cLinkLayerTimer::start(void) const { TIM2->CNT = (uint16_t)0; TIM2->CR1 |= TIM_CR1_CEN; } /******************************************************************************* * Function: irqHandler * Description:     . *     ,   ******************************************************************************/ void cLinkLayerTimer::irqHandler(void) { ASSERT(instance != NULL); instance->notifyObservers(); TIM2->CR1 &=~ TIM_CR1_CEN; TIM2->SR &= ~TIM_SR_UIF; } 




linklayer.h
 #include "types.h" //lint !e537    #include "linklayertimer.h" //lint !e537  cLinkLayerTimer #include "observer.h" //lint !e537  iObserver #define PREAMBUL_SYMBOL (uint16_t) 0xFF typedef enum { LLS_none = 0, LLS_write = 1, LLS_writeComplete = 2, LLS_readComplete = 3, LLS_error = 4 } tLinkLayerStatus; class cLinkLayer : private iObserver, public iObservable { public: explicit cLinkLayer(tU8 *pRxBuf, const tU8 rxBufSize,tU8 *pTxBuf, const tU8 preambulCount); void writeData(tU8 dataSize); tLinkLayerStatus getStatus() const { return eStatus; }; virtual void eventHandle(const iObservable* pObservable); tU8* pTxBuffer; tU8* pRxBuffer; private: static void irqHandler(void); static cLinkLayer* instance; void endMessageHandler(void); void enableReceive(void) const { USART2->CR1 |= USART_CR1_RXNEIE;}; //lint !e639 !e511    void disableReceive(void){USART2->CR1 &=~ USART_CR1_RXNEIE;};//lint !e639 !e511    void enableTransmit(void) const { USART2->CR1 |= USART_CR1_TCIE; };//lint !e639 !e511    void disableTransmit(void) const { USART2->CR1 &=~ USART_CR1_TCIE; };//lint !e639 !e511    tLinkLayerStatus eStatus; cLinkLayerTimer* pEndTransmitTimer; tU8 rxBufferSize; tU8 rxBufferIndex; tU8 txBufferIndex; tU8 txBufferSize; tU8 preambulsCount; tU8 preambulIndex; tBoolean readPreambuls; }; 




linllayer.cpp
 #include <stm32l1xx.h> //lint !e537  STM32 #include "linklayer.h" //lint !e537    #include "susuassert.h" //lint !e537  ASSERT #include "bitutil.h" //lint !e537      SETBIT, CLRBIT #include <stdio.h> //lint !e537  NULL #define END_MESSAGE_TIMEOUT (tU16) 19 #define GOOD_COUNT_RX_PREAMBULS (tU8) 2 cLinkLayer* cLinkLayer::instance = NULL; /******************************************************************************* * Function: constructor * Description:       ******************************************************************************/ cLinkLayer::cLinkLayer(tU8 *pRxBuf, const tU8 rxBufSize,tU8 *pTxBuf, const tU8 preambulCount) { ASSERT (rxBuffer != NULL); ASSERT (txBuffer != NULL); //     3 ASSERT(preambulCount > (tU8)2); this->preambulsCount = preambulCount; this->preambulIndex = (tU8)0; this->readPreambuls = TRUE; this->pRxBuffer = pRxBuf; this->rxBufferSize = rxBufSize; this->rxBufferIndex = (tU8)0; this->pTxBuffer = pTxBuf; this->txBufferSize = (tU8)0; this->txBufferIndex = (tU8)0; this->eStatus = LLS_none; this->instance = this; this->pEndTransmitTimer = new cLinkLayerTimer(END_MESSAGE_TIMEOUT); //    this->pEndTransmitTimer->addObserver(this); this->disableTransmit(); this->enableReceive(); } /******************************************************************************* * Function: writeData * Description:  ,     ******************************************************************************/ void cLinkLayer::writeData(tU8 dataSize) { //    ,     ,    // if (this->eStatus != LLS_write) { this->disableReceive(); this->txBufferSize = dataSize; this->eStatus = LLS_write; USART2->DR = PREAMBUL_SYMBOL; this->preambulIndex ++; this->enableTransmit(); } } /******************************************************************************* * Function: handler * Description:   ******************************************************************************/ void cLinkLayer::irqHandler(void) { ASSERT(instance != NULL); // if (USART2->SR & USART_SR_TC) { //    ,   3   if (instance->preambulIndex != instance->preambulsCount) { USART2->DR = PREAMBUL_SYMBOL; instance->preambulIndex ++; } else { //  -    if(instance->txBufferIndex < instance->txBufferSize) { USART2->DR = (uint16_t)instance->pTxBuffer[instance->txBufferIndex++]; } else { instance->txBufferIndex = (tU8)0; instance->txBufferSize = (tU8)0; instance->disableTransmit(); instance->eStatus = LLS_writeComplete; instance->preambulIndex = (tU8)0; instance->readPreambuls = TRUE; instance->enableReceive(); } } USART2->SR &=~ USART_SR_TC; }; // if (USART2->SR & USART_SR_RXNE) { instance->pRxBuffer[instance->rxBufferIndex] = (tU8)USART2->DR; instance->pEndTransmitTimer->start(); //    if (instance->readPreambuls) { if (instance->pRxBuffer[instance->rxBufferIndex] == (tU8)PREAMBUL_SYMBOL) { instance->preambulIndex++; } else { instance->readPreambuls = FALSE; instance->rxBufferIndex++; } } else { //      2 if ((instance->rxBufferIndex <= instance->rxBufferSize) && (instance->preambulIndex >= GOOD_COUNT_RX_PREAMBULS)) { instance->rxBufferIndex++; } else { instance->eStatus = LLS_error; instance->preambulIndex = (tU8)0; } } } } /******************************************************************************* * Function: endMessageHandler * Description:    ******************************************************************************/ void cLinkLayer::endMessageHandler(void) { this->eStatus = LLS_readComplete; this->rxBufferIndex = (tU8) 0; this->readPreambuls = TRUE; instance->preambulIndex = (tU8)0; } /******************************************************************************* * Function: eventHandle * Description:     ******************************************************************************/ void cLinkLayer::eventHandle(const iObservable* pObservable) { this->endMessageHandler(); this->notifyObservers(); } //lint !e715    pObservable 




LinkLayer itself also notifies the end of the acceptance of the request from the master of its subscribers - we need it later



DWART. Parsing and shaping the HART frame



Next, let's deal with the cFrame class, it will decode requests from the wizard, as well as form response frames and use LinkLayer to send them:

image



Implementation:

frame.h
 #include "types.h" //lint !e537    tBoolean #include "linklayer.h" //lint !e537  cLinkLayer #define LONG_ADDRESS_LENGTH (tU8)5 #define DATA_LENGTH (tU8)255 typedef enum { FE_good = 0, FE_addrError = 1, FE_comError = 2, FE_dataCountError = 3, FE_checkSummError = 4, FE_preambulsError = 5, FE_genericError = 6 } tFrameError; typedef struct { //   tU8 preambulsCount; //   tU8 startByte; //   tU8 shortAddr; //   tU8 longAddr[LONG_ADDRESS_LENGTH]; //  tU8 command; //   tU8 dataCount; // tU8 *pData; //   tU8 checkSumm; } tMasterFrame; class cFrame { public: cFrame(tU8* pLongAddress, const tU8 shortAddress, cLinkLayer *pLnkLayer); tU8* buildFrameBeforeData (const tU8 commandNumber, const tU8 dataLength); tU8 getCurrentCommand(void) const { return this->masterFrame.command; } tFrameError decode(void); void setCheckSumm(void); void send(void); private: tU8 getCheckSumm(const tU8 *pData, const tU8 dataLength) const; cLinkLayer *pLinkLayer; tU8 deviceShortAddress; tU8 *pDeviceId; tU8 bufferSize; tMasterFrame masterFrame; }; 




frame.cpp
 #include "susuassert.h" //lint !e537  ASSERT #include "frame.h" //lint !e537     #include <stddef.h> //lint !e537  NULL #define LONG_ADDRESS_MASK (tU8)0x80 #define SLAVE_MASK (tU8)0x7F #define START_BYTE_SLAVE_SHORT_FRAME (tU8) 6 #define START_BYTE_SLAVE_LONG_FRAME (tU8) 0x86 /******************************************************************************* * Function: constructor * Description: ******************************************************************************/ cFrame::cFrame(tU8* pLongAddress, const tU8 shortAddress, cLinkLayer *pLnkLayer) { ASSERT(pLnkLayer != NULL); ASSERT(pLongAddress != NULL); //      64 ASSERT(shortAddress <= (tU8)64); this->pLinkLayer = pLnkLayer; this->pDeviceId = pLongAddress; this->deviceShortAddress = shortAddress; this->bufferSize = (tU8)0; } /******************************************************************************* * Function: decode * Description:  ,   ******************************************************************************/ tFrameError cFrame::decode(void) { tU8 index = (tU8)0; tU8 cnt = (tU8)0; tFrameError eResult = FE_good; this->masterFrame.startByte = this->pLinkLayer->pRxBuffer[index]; index ++; //         if (this->masterFrame.startByte & LONG_ADDRESS_MASK) { for (tU8 i = (tU8)0; i < LONG_ADDRESS_LENGTH; i ++) { this->masterFrame.longAddr[i] = this->pLinkLayer->pRxBuffer[index]; index ++; } } else { this->masterFrame.shortAddr = this->pLinkLayer->pRxBuffer[index]; index ++; } //   this->masterFrame.command = this->pLinkLayer->pRxBuffer[index]; index ++; //     this->masterFrame.dataCount = this->pLinkLayer->pRxBuffer[index]; index ++; //        if (this->masterFrame.dataCount != (tU8)0) { this->masterFrame.pData = (tU8*)&this->pLinkLayer->pRxBuffer[index]; } index = index + this->masterFrame.dataCount; //,      DATA_LENGTH if(masterFrame.dataCount < DATA_LENGTH ) { //    this->masterFrame.checkSumm = this->getCheckSumm((tU8*)&this->pLinkLayer->pRxBuffer[cnt], index - cnt); //************************   *********************** //    if(this->pLinkLayer->pRxBuffer[index] != this->masterFrame.checkSumm) { eResult = FE_checkSummError; } //    if (this->masterFrame.startByte & LONG_ADDRESS_MASK) { if ((this->masterFrame.longAddr[0] & SLAVE_MASK) != (this->pDeviceId[0] & SLAVE_MASK)) { eResult = FE_addrError; } else { for (tU8 i = (tU8)1; i < LONG_ADDRESS_LENGTH; i ++) { if (this->masterFrame.longAddr[i] != this->pDeviceId[i]) { eResult = FE_addrError; break; } this->pDeviceId[0] = this->masterFrame.longAddr[0]; } } } else { if((this->masterFrame.shortAddr & SLAVE_MASK) != (this->deviceShortAddress & SLAVE_MASK)) { eResult = FE_addrError; } else { this->deviceShortAddress = this->masterFrame.shortAddr; } } } else { eResult = FE_dataCountError; } return eResult; } /******************************************************************************* * Function: buildFrameBeforeData * Description:    ,      *  .      ******************************************************************************/ tU8* cFrame::buildFrameBeforeData(const tU8 commandNumber, const tU8 dataLength) { tU8 index = (tU8)0; // ,         if (!(this->masterFrame.startByte & LONG_ADDRESS_MASK)) { this->pLinkLayer->pTxBuffer[index] = START_BYTE_SLAVE_SHORT_FRAME; index ++; this->pLinkLayer->pTxBuffer[index] = this->deviceShortAddress; index ++; } else { this->pLinkLayer->pTxBuffer[index] = START_BYTE_SLAVE_LONG_FRAME; index ++; for (tU8 i = (tU8)0; i < LONG_ADDRESS_LENGTH; i ++) { this->pLinkLayer->pTxBuffer[index] = this->pDeviceId[i]; index ++; } } //       this->pLinkLayer->pTxBuffer[index] = commandNumber; index ++; this->pLinkLayer->pTxBuffer[index] = dataLength; index ++; this->bufferSize = index + dataLength; return (tU8*)&this->pLinkLayer->pTxBuffer[index]; } /******************************************************************************* * Function: setCheckSumm * Description:        ******************************************************************************/ void cFrame::setCheckSumm(void) { this->pLinkLayer->pTxBuffer[this->bufferSize] = this->getCheckSumm(this->pLinkLayer->pTxBuffer, this->bufferSize); this->bufferSize++; } /******************************************************************************* * Function: getCheckSumm * Description:    ******************************************************************************/ tU8 cFrame::getCheckSumm(const tU8 *pData, const tU8 dataLength) const { tU8 index = (tU8) 0; tU8 checkSumm = (tU8)0; for (index = (tU8)0; index < dataLength; index ++) { if(pData != NULL) { checkSumm = checkSumm ^ pData[index]; } else { checkSumm = (tU8) 0; } } return checkSumm; } /******************************************************************************* * Function: send * Description:   ******************************************************************************/ void cFrame::send(void) { this->pLinkLayer->writeData(this->bufferSize); } 






DWART. Team building



Now the most interesting thing is that the teams, as I have already said, would like to make everything look beautiful, and it was possible to contact the teams comfortably — and
  Command0.Response.PrimaryVariable = 3.54 


It is clear that if for each command I keep the whole Response response structure, and there can be 254 commands, then I don’t have enough memory, so I will only store a pointer to the Response response structure, and each time assign this pointer a pointer to the data in the transfer buffer, which I will receive using the buildFrameBeforeData class method withFrame.

For all the teams I will do the basic interface iBaseDwartCommand with common methods, and since I have requests and responses from each command of different lengths and different types, I will create a template class derived from iDwartCommand. Approximately it looks like this:

image

Command 0 - returns just information about the device.

Commands 1 - returns the value of the primary variable, in my case it is with Trimmer, access to which is provided by the cVariablesDirector class, so I must pass it in the constructor.

As a result, filling in the data of command 1 looks like this:

 void cCommand1::setNewData(void) { //          this->pResponse = (tCommand1Response*) this->pFrame->buildFrameBeforeData(COMMAND1, (tU8)sizeof(tCommand1Response)); //lint !e826   this->pResponse->status1 = (tU8)0; this->pResponse->status2 = (tU8)0; this->pResponse->PrimaryVariableUnits = (tU8) pVariablesDirector->pTrimmer->getUnits(); this->pResponse->PrimaryVariableValue = cConversion<tF32>::swap(pVariablesDirector->pTrimmer->getValue()); this->pFrame->setCheckSumm(); } 


Here, the cConversion class's swap method is used to swap bytes in places, since HART uses the Big Endian view, and my microcontroller is Little Endian. Well, almost got what I wanted :)

Fully the whole implementation looks like this:

basedwartcommand.h
 class iBaseDwartCommand { public: virtual void send(void) = 0; virtual void setNewData(void) = 0; }; 




dwartcommand.h
 #include "frame.h" //lint !e537  cFrame #include "basecommand.h" //lint !e537  iBaseDwartCommand #include "susuassert.h" //lint !e537  ASSERT template <class req, class resp> class iDwartCommand : public iBaseDwartCommand { public: explicit iDwartCommand(cFrame *pDwratFrame); req *pRequest; resp *pResponse; void send(void); protected: cFrame *pFrame; }; /******************************************************************************* * Function: constructor * Description: ******************************************************************************/ template <class req, class resp> iDwartCommand<req, resp>::iDwartCommand(cFrame *pDwartFrame) { ASSERT (pFrame != NULL); this->pFrame = pDwartFrame; } /******************************************************************************* * Function: send * Description:   ******************************************************************************/ template <class req, class resp> void iDwartCommand<req, resp>::send(void) { this->pFrame->send(); } 




command0.h
 #include "command.h" //lint !e537  iDwartCommand #define COMMAND0 (tU8)0 #pragma pack(push, 1) typedef struct { tU8 status1; tU8 status2; tU8 expansion; tU8 manufacturer; tU8 deviceType; tU8 numberOfpreambuls; tU8 universalCommandRevision; tU8 deviceSpecificCommandRevision; tU8 softwareRevision; tU8 hardwareRevision; tU8 deviceFlags; tU8 deviceID[3]; } tCommand0Response; typedef struct { } tCommand0Request; #pragma pack(pop) class cCommand0: public iDwartCommand<tCommand0Request, tCommand0Response> { public: explicit cCommand0(cFrame *pDwartFrame); virtual void setNewData(void); }; 




command0.cpp
 include "susuassert.h" //lint !e537  ASSERT #include "command0.h" //lint !e537   #include "frame.h" //lint !e537  Frame #include "conversion.h" //lint !e537  cConversion /******************************************************************************* * Function: constructor * Description: ******************************************************************************/ cCommand0::cCommand0(cFrame *pDwartFrame): iDwartCommand(pDwartFrame) { } /******************************************************************************* * Function: setNewData * Description:     ******************************************************************************/ void cCommand0::setNewData(void) { //          this->pResponse = (tCommand0Response*) this->pFrame->buildFrameBeforeData(COMMAND0, (tU8)sizeof(tCommand0Response)); //lint !e826   this->pResponse->status1 = (tU8)0; this->pResponse->status2 = (tU8)0; this->pResponse->manufacturer = (tU8)0x37; this->pResponse->deviceType = (tU8)0x04; this->pResponse->expansion = (tU8)0; this->pResponse->deviceSpecificCommandRevision = (tU8)5; this->pResponse->universalCommandRevision = (tU8)5; this->pResponse->hardwareRevision = (tU8)1; this->pResponse->softwareRevision = (tU8)201; this->pResponse->numberOfpreambuls = (tU8)5; this->pResponse->deviceID[0] = (tU8)0; this->pResponse->deviceID[1] = (tU8)0; this->pResponse->deviceID[2] = (tU8)1; this->pResponse->deviceFlags = (tU8)0; this->pFrame->setCheckSumm(); } 




command1.h
 #include "command.h" //lint !e537  iDwartCommand #include "variablesdirector.h" //lint !e537  cVariablesDirector #define COMMAND1 (tU8)1 #pragma pack(push, 1) typedef struct { tU8 status1; tU8 status2; tU8 PrimaryVariableUnits; tF32 PrimaryVariableValue; } tCommand1Response; typedef struct { } tCommand1Request; #pragma pack(pop) class cCommand1: public iDwartCommand<tCommand1Request, tCommand1Response> { public: explicit cCommand1(cFrame *pDwartFrame,cVariablesDirector *pVarsDirector); virtual void setNewData(void); private: cVariablesDirector *pVariablesDirector; }; 




command1.cpp
 #include "susuassert.h" //lint !e537  ASSERT #include "command1.h" //lint !e537   #include "frame.h" //lint !e537  Frame #include "conversion.h" //lint !e537  cConversion /******************************************************************************* * Function: constructor * Description: ******************************************************************************/ cCommand1::cCommand1(cFrame *pDwartFrame, cVariablesDirector *pVarsDirector): iDwartCommand(pDwartFrame) { ASSERT(pVarsDirector != NULL); this->pVariablesDirector = pVarsDirector; } /******************************************************************************* * Function: setNewData * Description:     ******************************************************************************/ void cCommand1::setNewData(void) { //          this->pResponse = (tCommand1Response*) this->pFrame->buildFrameBeforeData(COMMAND1, (tU8)sizeof(tCommand1Response)); //lint !e826   this->pResponse->status1 = (tU8)0; this->pResponse->status2 = (tU8)0; this->pResponse->PrimaryVariableUnits = (tU8) pVariablesDirector->pTrimmer->getUnits(); this->pResponse->PrimaryVariableValue = cConversion<tF32>::swap(pVariablesDirector->pTrimmer->getValue()); this->pFrame->setCheckSumm(); } 




Other teams are built on the same principle. I have a large memory reserve, I decided that it would be nice for all the teams, and they could be up to 254 to throw them into one array, then I will explain why I thought it was convenient. I just created the cCommandSet container class:

 #include "command.h" //lint !e537  iDwartCommand #include "variablesdirector.h" //lint !e537  cVariablesDirector #define COMMANDS_COUNT 254 class cCommandSet { public: cCommandSet(cFrame *pFrame, cVariablesDirector *pVariablesDirector); iBaseDwartCommand *pCommands[COMMANDS_COUNT]; }; 




In order for the two programs Pactware and HartConfig to take me for Yokogawa, I need to implement a minimum set of commands, and put them in a container.

commandset.cpp looks like this:

 #include "susuassert.h" //lint !e537  ASSERT #include "commandset.h" //lint !e537   #include "command0.h" //lint !e537  cCommand0 #include "command1.h" //lint !e537  cCommand1 #include "command2.h" //lint !e537  cCommand2 #include "command3.h" //lint !e537  cCommand3 #include "command12.h" //lint !e537  cCommand12 #include "command13.h" //lint !e537  cCommand13 #include "command14.h" //lint !e537  cCommand14 #include "command15.h" //lint !e537  cCommand15 #include "command157.h" //lint !e537  cCommand157 #include "command159.h" //lint !e537  cCommand159 #include "command160.h" //lint !e537  cCommand160 #include "command180.h" //lint !e537  cCommand180 cCommandSet::cCommandSet(cFrame *pFrame, cVariablesDirector *pVariablesDirector) { this->pCommands[COMMAND0] = new cCommand0(pFrame); this->pCommands[COMMAND1] = new cCommand1(pFrame, pVariablesDirector ); this->pCommands[COMMAND2] = new cCommand2(pFrame, pVariablesDirector ); this->pCommands[COMMAND3] = new cCommand3(pFrame, pVariablesDirector ); this->pCommands[COMMAND13] = new cCommand13(pFrame); this->pCommands[COMMAND12] = new cCommand12(pFrame); this->pCommands[COMMAND14] = new cCommand14(pFrame); this->pCommands[COMMAND15] = new cCommand15(pFrame, pVariablesDirector); this->pCommands[COMMAND160] = new cCommand160(pFrame); this->pCommands[COMMAND157] = new cCommand157(pFrame); this->pCommands[COMMAND159] = new cCommand159(pFrame); this->pCommands[COMMAND180] = new cCommand180(pFrame); } 


Here I have a mistake, because almost the entire array is not initialized, but I was too lazy to score it :) We keep in mind that there is a mistake.

I have no idea what teams 157, 159 and 160 do (I assume only that there is some user variable that is calculated based on a variable pressure, for example, to calculate the flow rate (because, as you know, the flow rate is a function of root extraction from differential pressure, or let's say to count the level, which also depends on the pressure difference)), but I just filled them with some kind of biliberdah, and the programs were swallowed.

Due to the fact that I made an array of commands, the appeal to the commands will look very elegant - like this

 pCommandSet->pCommands[this->pFrame->getCurrentCommand()]->setNewData(); pCommandSet->pCommands[this->pFrame->getCurrentCommand()]->send(); 


As you can see, there are no need for any case, but it took “a bit of memory” :)



DWART. Completion



All that remains is to make an active class that will parse the request received from the master.

As I said before, this class subscribes to the accept accept event from cLinkLayer.

The event handler is called from the timer interrupt, and I have to quickly do something in it and exit, so I’ll just set the request readiness check from the master for processing, and in the active task I’ll poll this checkbox, if it is, then I’ll go to cFrame to decode the message for me and if everything went well, I’ll call the necessary command and send it.

The same class will create instances of cFrame, cLinkLayer and CommandSet and pass cLinkLayer a pointer to the receive and transmit buffer. And since my work will be in the request-response mode, the buffer will be one for transmission and reception, and at the same time we will save memory.

image

dwart.h
 #include "types.h" //lint !e537    tBoolean #include "frame.h" //lint !e537  oFrame #include "observer.h" //lint !e537  iObserver #include "linklayer.h" //lint !e537  cLinkLayer #include "variablesdirector.h" //lint !e537  cVariablesDirector #include "commandset.h" //lint !e537  cCommandSet #include "frtosWrapper.h" //lint !e537  iActiveObject #define MAX_BUFFER_SIZE (tU8)255 class cDwart: private iObserver, public iActiveObject { public: cDwart(cVariablesDirector *pVariableDirector); virtual void eventHandle(const iObservable* pObservable); virtual void run(void); private: cCommandSet *pCommandSet; cLinkLayer *pLinkLayer; cFrame *pFrame; tU8 buffer[MAX_BUFFER_SIZE]; tBoolean isToken; static const tU8 deviceID[5]; }; 




dwart.cpp
 #include "susuassert.h" //lint !e537  ASSERT #include "frame.h" //lint !e537     #include <stddef.h> //lint !e537  NULL #include "dwart.h" //lint !e537   #define SHORT_ADDR (tU8)0 #define PREAMBULS_COUNT (tU8)7 #define DWART_WAITING (tU32) (50/portTICK_PERIOD_MS) const tU8 cDwart::deviceID[5] = {(tU8)0x37,(tU8)0x04,(tU8)0x00,(tU8)0x00,(tU8)0x01}; /******************************************************************************* * Function: constructor * Description:    cLinkLayer, cFrame cCommandSet  *         ******************************************************************************/ cDwart::cDwart(cVariablesDirector *pVariablesDirector) { this->pLinkLayer = new cLinkLayer(this->buffer,MAX_BUFFER_SIZE, this->buffer, PREAMBULS_COUNT); this->pFrame = new cFrame((tU8*)deviceID, SHORT_ADDR,this->pLinkLayer); this->pCommandSet = new cCommandSet(this->pFrame, pVariablesDirector); this->pLinkLayer->addObserver(this); this->isToken = FALSE; } /******************************************************************************* * Function: eventHandle * Description:    .       ******************************************************************************/ void cDwart::eventHandle(const iObservable* pObservable) { ASSERT(pObservable != NULL); this->isToken = TRUE; } //lint !e715    pObservable /******************************************************************************* * Function: run * Description:      ******************************************************************************/ void cDwart::run(void) { for(;;) { if (this->isToken) { this->isToken = FALSE; if (this->pFrame->decode() == FE_good) { pCommandSet->pCommands[this->pFrame->getCurrentCommand()]->setNewData(); pCommandSet->pCommands[this->pFrame->getCurrentCommand()]->send(); } } oRTOS.taskDelay(DWART_WAITING); } } 




It remains to add the creation of a new cDwart class in main.cpp and run for verification.

 void main( void ) { const cAdcDirector* pAdcDirector = new cAdcDirector(); //lint !e429    . pAdcDirector->startConversion(); cVariablesDirector *pVariablesDirector = new cVariablesDirector(pAdcDirector); oRTOS.taskCreate(pVariablesDirector, VARIABLESDIRECTOR_STACK_SIZE, VARIABLESDIRECTOR_PRIORITY, "Var"); cDwart *pDwart = new cDwart(pVariablesDirector); oRTOS.taskCreate(pDwart, DWART_STACK_SIZE, DWART_PRIORITY, "Dwart"); ... oRTOS.startScheduler(); } //lint !e429    . 


And the German miracle called Pactware sees the Japanese pressure sensor, without even noticing that all this is a fake.







And our domestic software also does not notice the catch.







Of course, a lot of things are not very optimally done, for example, an array of 255 pointers to commands, it can be removed, and you can execute commands through the usual switch case, you can also use one placeholder for all commands equal in size to the largest team and every time create the necessary command, and then delete.

But in my case, my memory is unmeasured, and there were no optimization problems.

The project itself as usual in a secret place

Source: https://habr.com/ru/post/367843/



All Articles