Pull to refresh

STM32, C++ и FreeRTOS. Разработка с нуля. Часть 4 (Прерывания, UART и недоHART)

Reading time 23 min
Views 15K

Ведение


Попав в отпуске в город на Неве и посетив множество красивых мест, я все таки, вечерами за чашкой пива, разбирался с UARTом. Тем более, что я купил неплохие наушники Fisher FA011, к которым пришлось прикупить USB SOUND BLASTER X-FI HD и хотел послушать музыку.
Предыдущие статьи вначале переехали на Geektime потом я обратно их перегнал, даже и не знаю, куда теперь их деть :)
Но так на всякий случай они тут:
STM32, C++ и FreeRTOS. Разработка с нуля. Часть 1
STM32, C++ и FreeRTOS. Разработка с нуля. Часть 2 и
STM32, C++ и FreeRTOS. Разработка с нуля. Часть 3 (LCD и Экраны)

UART


После детального изучения микроконтроллера, мне казалось, что все просто. Настройка и тестовая посылка байта в порт прошла без задоринки, все работало как часы, и тут я решил использовать прерывания. Нужно было сделать так, чтобы обработчик прерывания был статическим методом класса. И IAR в руководстве на компилятор, так и писал:
Special function types can be used for static member functions. For example, in the
following example, the function handler is declared as an interrupt function:
class Device
{
 static __irq void handler();
};

Но вот незадача, для Cortex M такой способ не подходит и
On ARM Cortex-M, an interrupt service routine enters and returns in the same way as a
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.
They are referred to by the library exception 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,

Или по простому, ваш обработчик прерывания должен иметь такое же имя, какое он имеет в таблице векторов определенной в startup файле. Это делается с помощью специального ключевого слова — слабой ссылки __weak (в ассемблере PUBWEAK), которая означает, что данное определение будет использоваться до тех пора, пока не найдется хотя бы одно совпадающее по написанию без ключевого слова __week. Ну т.е., если вы определите функцию с точно таким же именем без этой директивы, то компилятро будет использовать это определение, а если не определите, то которое помечено __weak.
Понятное дело, что я не могу в файл startup_stm32l1xx_md.s или startup_stm32l1xx_md.с вставить С++ имя статического метода типа cUart::USART2_IRQHandler(), ассемблер его просто не поймет.
А просто «USART2_IRQHandler» не совпадает с определением «cUart::USART2_IRQHandler()».
Можно использовать extern «C» { void USART2_IRQHandler(void) {...}}, но это означает, что я тут буду делать вставки из Си, что мне совсем не надо, и вообще доступа из такой функции к атрибутам моего класса, например буферу — не будет, и надо будет городить кучу некрасивого кода :).
Поэтому, я решил пойти другим путем и создать файл startup_stm32l1xx_md.cpp. Поиск в интернете обнаружил, что точно такая же проблема была у некоторых людей Вот например
В общем идея заключается в следующем: Определяем в startup_stm32l1xx_md.cpp классы со статическими методами (которые и будут являться обработчиками прерываний), создаем таблицу __vector_table, где на каждом из векторов прерываний стоит указатель на на эти статические методы. Дальше делаем __weak определение каждого метода
И теперь когда в коде компилятор видет реализацию void cUart1::handler(), он не задумываясь берет её. Конечно же при этом ваши классы и методы должны называться точь в точь так, как они определены в startup_stm32l1xx_md.cpp.
Нужно еще не забыть про функции FreeRtos: vPortSVCHandler, xPortPendSVHandler, xPortSysTickHandler и поставить их на нужное прерывание и вуаля — все работает:
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();
}





DWART. Прием передача сообщений


Итак, с прерываниями разобрались, теперь можно браться за реализацию какого-нить несложного протокола. Modbus отпал сразу — уж очень не сложный :). HART, промышленный протокол для устройств типа датчиков давления, температуры и расходомеров — отлично подойдет.
И задачей, которую я поставил перед собой, будет связаться по этому протоколу с прогрммой настройкой устройств, скажем Pactware, или Элемеровким HARTConfig, подделавшись, каким-нибудь популярным датчиком, скажем датчик давления — Yokogawa EJA, так чтобы эта программа подвоха не обнаружила. Сразу скажу, что все получилось :)
image
И даже удалось снять тренд температуры воздуха, измеряющийся внутренним сенсором температуры микроконтроллера:
image

А вот так вот видит мое устройство Pactware :)
image

Документация на HART доступна только члена фонда. Но удалось найти отрывочные описания, которые вполне сойдут для моего демо-проекта. Вот пример одного из таких ресурсов: Описание протокола HART или вот Модель OSI HART протокола
Вкратце — на линии есть мастер(главное) и слейв(подчиненное) устройства. Главное шлет запросы, подчиненное отвечает на скорости 1200 бит в секунду — все просто.
Может быть и два главных устройства на линии, но я просто буду общаться с ОДНИМ мастером по RS232.
По этой же причине мне не надо особо следить за арбитражем шины и по сложному определять когда токен у меня, а когда нет, и весь арбитраж в моем случае заключается только в том, что — токен находится у меня, после 2 символов тишины на линии. На скорости 1200 бит в секунду это примерно 19 мс. Т.е. если в течении 19 мс, у меня нет прерывания по приему — посылка считается принятой, и токен находится у меня, я могу отвечать и должен начать отвечать в течении примерно 250 мс, иначе токен снова перейдет мастеру.
В общем назвал я этот протокол Dwart, симбиоз между Dwarf — карлик, гном и HART.
Для начала нужно выделить сущности, которыми будем оперировать:

  • Канальный уровень (LinkLayer) — будет отвечать просто за посылку и передачу сообщений, а также за определение конца приема запроса от мастера
  • Таймер определения конца передачи (LinkLayerTimer) — таймер на 19 мс, по истечении которого будет считаться, что запрос от мастера принят. Таймер должен запускаться после каждого принятого байта.
  • Кадр (Frame) — отвечает за разбор и формирование кадров HART посылок
  • Команды (Command) — отвечает за реализацию отдельной команды
  • Сам главный класс (Dwart) — он всем этим делом будет управлять

Для того чтобы соблюсти принцип слоеного пирога — классы нижнего слоя не будут знать про классы слоя находящегося выше, т.е. Например, Command будет работать только с Frame, а Frame c LinkLayer, при этом LinkLayer ничего не будет знать про Command и Frame, а Frame про Command.
Итак в чем же заключается идея — принимать байты до тех пор пока не сработал таймер тишины, затем оповестить Dwart, который вызовет метод разбора принятого сообщения и в зависимости от запроса, выполнит нужную команду.
В общем в идеале, скажем при запросе команды 1 от мастера, хочется чтобы ответ на неё выглядел как-то так:
Command1.Response.PrimaryVariable = 3.54
Command1.Send();

Вначале прикинем канальный уровень, на данном рисунке показаны только публичные методы, чтобы не засорять картинку.
image

Немного поясню картинку, методы обработчика прерываний являются статическими, и чтобы обращаться к методам и полям экземпляра класса в этих статических методах нужно чтобы либо поля и методы тоже были статическими, либо обратиться непосредсвенно к экземпляру класса. Я пошел вторым — правильным путем, обращаюсь к конкретному экземпляру класса, который инициализирую в конструкторе указателем this.
LinkLayer будет принимать байты из UART порта и складывать их в буфер приема.
Но поскольку в HART есть сихронизирующие байты 0xFF, называемые преамбулами, и их количество может доходить до 20, (а они нам вообще не нужны, так как не несут в себе никакой инофрмации, и все что нам нужно, это определить начало кадра по последовательности( 0xFF 0xFF <Стартовый байт>)), то будет растратно скидывать 20 преамбул в буфер, поэтому я просто буду следить за ними прямо в прерывании и как только они кончатся, я начну складывать байты в буфер.
После приема каждого байта я буду перезапускать таймер на 19 мс, если он сработает, то посылка считается принятой.
Еще стоит задача оповестить linklayer из класса linklayertimer, как только сработает прерывание от таймера, говорящее о конце приема запроса от мастера. Для этого я воспользовался шаблоном проектирования Наблюдатель, в итоге LinkLayer просто подписывается на события от LinkLayerTimer, Выглядит это так:
linklayertimer.h
#include "susutypes.h"            
#include "observable.h"       
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"   
#include "susuassert.h"       
#include <stdio.h>            
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 "susutypes.h"            
#include "linklayertimer.h"   
#include "observer.h"         
#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;}; 
    void disableReceive(void){USART2->CR1 &=~ USART_CR1_RXNEIE;};
    void enableTransmit(void) const { USART2->CR1 |= USART_CR1_TCIE; };
    void disableTransmit(void) const { USART2->CR1 &=~ USART_CR1_TCIE; };
    tLinkLayerStatus eStatus;
    cLinkLayerTimer* pEndTransmitTimer;
    tU8  rxBufferSize;
    tU8  rxBufferIndex;
    tU8  txBufferIndex;
    tU8  txBufferSize; 
    tU8 preambulsCount;
    tU8 preambulIndex;
    tBoolean readPreambuls;
};


linllayer.cpp
#include <stm32l1xx.h>        
#include "linklayer.h"        
#include "susuassert.h"       
#include <stdio.h>            

#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();
} 


Сам LinkLayer также оповещает о конце приемке запроса от мастера своих подписчиков — это нам понадобиться позже

DWART. Формирование команд


Теперь самое интересное — команды, как я уже говорил, хотелось бы чтобы все выглядело красиво, и можно было удобно обращаться к командам — вот так
 Command0.Response.PrimaryVariable = 3.54

Понятно, что если для каждой команды я буду хранить всю структуру запроса Response, а команд может быть 254, то у меня никакой памяти не хватит, поэтому я буду хранить только указатель на структуру запроса Response, и каждый раз присваивать этому указателю, указатель на данные в буфере передачи, который получу с помощью метода buildFrameBeforeData класса сFrame.
Для всех команд сделаю базовый интерфейс iBaseDwartCommand с общими методами, а поскольку у меня запросы и ответы у каждой команды разной длины, и разных типов, то я создам шаблонный класс наследник iDwartCommand. Примерно это выглядит вот так:
image
Команда 0 — возвращает просто информацию об устройстве.
Команд 1 — возвращает значение первичной переменной, в моем случае это сTrimmer, доступ к которому дает класс cVariables, поэтому я должен его передать в конструкторе.
В итоге, заполнение данных команды 1 выглядит вот так:
void cCommand1::setNewData(void)
{
  //Заполнить начало кадра и получить указатель на буффер для данных
  this->pResponse = (tCommand1Response*) this->pFrame->buildFrameBeforeData(COMMAND1,
                    (tU8)sizeof(tCommand1Response)); 
  this->pResponse->status1 = (tU8)0;
  this->pResponse->status2 = (tU8)0;
  this->pResponse->PrimaryVariableUnits =  (tU8) pVariables->pTrimmer->getUnits();
  this->pResponse->PrimaryVariableValue =  std::swap<tF32>(pVariables->pTrimmer->getValue());
  this->pFrame->setCheckSumm();
}

Здесь метод swap класса cConversion используется для перестановки байтов местами, так как HART использует Big Endian представление, а мой микроконтроллер Little Endian. Ну почти получилось, что я хотел :)
Полностью вся реализация выглядит так:
basedwartcommand.h
class iBaseDwartCommand
{
  public:    
    virtual void send(void) = 0;   
    virtual void setNewData(void) = 0;  
};


dwartcommand.h
#include "frame.h"             
#include "basecommand.h"        
#include "susuassert.h"         
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"   
#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"       
#include "command0.h"          
#include "frame.h"            

/*******************************************************************************
* 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)); 
  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"          
#include "vars.h"
#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,Variablesr *Vars);
    virtual void setNewData(void);
  private:
   Variables *pVariables;
};


command1.cpp
#include "susuassert.h"       
#include "command1.h"          
#include "frame.h"            
/*******************************************************************************
* Function:  constructor
* Description: 
******************************************************************************/
cCommand1::cCommand1(cFrame *pDwartFrame, Variables *pVars): 
        iDwartCommand(pDwartFrame)                         
{
  ASSERT(pVars != NULL);
  this->pVariables = pVars;
}
/*******************************************************************************
* Function:  setNewData
* Description: Заполнение поля данных команды
******************************************************************************/
void cCommand1::setNewData(void)
{
    //Заполнить начало кадра и получить указатель на буффер для данных
  this->pResponse = (tCommand1Response*) this->pFrame->buildFrameBeforeData(COMMAND1,
                    (tU8)sizeof(tCommand1Response)); 
  this->pResponse->status1 = (tU8)0;
  this->pResponse->status2 = (tU8)0;
  this->pResponse->PrimaryVariableUnits =  (tU8) pVariables->pTrimmer->getUnits();
  this->pResponse->PrimaryVariableValue =  std::swap<tF32>(pVariables->pTrimmer->getValue());
  this->pFrame->setCheckSumm();
}


По такому же принципу построены и другие команды. У меня большой запас памяти, я решил, что было бы неплохо все команды, а их может быть до 254 скинуть в один массив, далее я поясню почему посчитал это удобным. Я просто создал класс контейнер cCommandSet:
#include "command.h"        
#include "variables.h" 

#define COMMANDS_COUNT 254
class cCommandSet
{
  public:
    cCommandSet(cFrame *pFrame, Variables *pVariables);
    iBaseDwartCommand *pCommands[COMMANDS_COUNT];    
};


Для того, чтобы две программы Pactware и HartConfig приняли меня за Yokogawa, мне надо реализовать минимальный набор команд, и положить их в контейнер.
commandset.cpp выглядит так:
#include "susuassert.h"       
#include "commandset.h"       
#include "command0.h"        
#include "command1.h"        
#include "command2.h"        
#include "command3.h"        
#include "command12.h"        
#include "command13.h"       
#include "command14.h"       
#include "command15.h"        
#include "command157.h"       
#include "command159.h"       
#include "command160.h"       
#include "command180.h"      

cCommandSet::cCommandSet(cFrame *pFrame, cVariables *pVariables)     
{
  this->pCommands[COMMAND0] =  new cCommand0(pFrame);
  this->pCommands[COMMAND1] =  new cCommand1(pFrame, pVariables);
  this->pCommands[COMMAND2] =  new cCommand2(pFrame, pVariables);
  this->pCommands[COMMAND3] =  new cCommand3(pFrame, pVariables);
  this->pCommands[COMMAND13] = new cCommand13(pFrame);
  this->pCommands[COMMAND12] = new cCommand12(pFrame);
  this->pCommands[COMMAND14] = new cCommand14(pFrame);
  this->pCommands[COMMAND15] = new cCommand15(pFrame, pVariables);
  this->pCommands[COMMAND160] = new cCommand160(pFrame);
  this->pCommands[COMMAND157] = new cCommand157(pFrame);
  this->pCommands[COMMAND159] = new cCommand159(pFrame);
  this->pCommands[COMMAND180] = new cCommand180(pFrame);
}

Тут у меня ошибка, так как почти весь массив не инициализирован, но мне лень было его забивать :) Держим в уме, что там ошибка.
Я понятия не имею, что делают команды 157, 159 и 160, (предполагаю только, что там какая-то пользовательская перменная, которая расчитывается на основе переменной давления, например, для расчета расхода (потому что как известно, расход это есть функция корнеизвлечения от диффиринциального давления, или скажем чтобы считать уровень, который также зависит от разности давления)), но я просто заполнил их какой-то билибердой, и программы это проглотили.
Из-за того, что я сделал массив команд, обращение к командам будет выглядеть очень элегантно — вот так
pCommandSet->pCommands[this->pFrame->getCurrentCommand()]->setNewData();
pCommandSet->pCommands[this->pFrame->getCurrentCommand()]->send();

Как можно заметить не нужны никакие switсh case, но это потребовало «немного памяти» :)

DWART. Завершение


Все, осталось сделать активный класс который будет разбирать принятый от мастера запрос.
Как я уже говорил вышел, этот класс подписывается на событие окончания примема от сLinkLayer,
Обработчик этого события вызывается из прерывания таймера, и я должен быстро в нем что-то сделать и выйти, поэтому я просто поставлю там флажок готовности запроса от мастера на обработку, а в активной задаче буду это флажок опрашивать, если он стоит, то обращусь к cFrame, чтобы он мне декодировал сообщение и если все прошло успешно, то вызову нужную команду и отошлю её.
Этот же класс создаст экземпляры cFrame, cLinkLayer и сCommandSet и передаст cLinkLayer указатель на буффер приема и передачи. А поскольку у меня работа будет в режиме запрос-ответ, то буффер будет один и на передачу и на прием, заодно и память сэкономим.
image
dwart.h
#include "susutypes.h"       
#include "frame.h"        
#include "observer.h"     
#include "linklayer.h"    
#include "variables.h"  
#include "commandset.h"   
#include "frtosWrapper.h" 

#define MAX_BUFFER_SIZE (tU8)255
class cDwart: private iObserver, public iActiveObject
{
  public:
    cDwart(Variables *pVariables);
    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"       
#include "frame.h"            
#include <stddef.h>          
#include "dwart.h"           

#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(Variables *pVariables)    
{
  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, pVariables);
  this->pLinkLayer->addObserver(this);  
  this->isToken = FALSE;
}
/*******************************************************************************
* Function:  eventHandle
* Description: Релизация метода интерфейса наблюдатель. Ставит флак что токен у нас
******************************************************************************/
void cDwart::eventHandle(const iObservable* pObservable) 
{
  ASSERT(pObservable != NULL);
  this->isToken = TRUE;
} 
/*******************************************************************************
* 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);
  }
}


Осталось добавить создание нового класса cDwart в main.cpp и запустить на проверку
void main( void )
{  
  const cAdcDirector* pAdcDirector = new cAdcDirector();   
  pAdcDirector->startConversion(); 
  cVariables *pVariables = new cVariables(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();
} 

И вот немецкое чудо под названием Pactware видит Японский датчик давления.



И наше отечественное ПО тоже понимает наш датчик.



Конечно, многое тут сделано не очень оптимально, например массив из 255 указателей на команды, его можно убрать, и сделать выполнение команд через обычный switch case, также можно использовать один placeholder на все команды, равной по размеру самой большой команде и каждый раз в нем создавать нужную команду, а после удалять.
Но в моем случае у меня памяти немеренно, и проблем оптимизации не было.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+4
Comments 2
Comments Comments 2

Articles