Ведение
Попав в отпуске в город на Неве и посетив множество красивых мест, я все таки, вечерами за чашкой пива, разбирался с 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, так чтобы эта программа подвоха не обнаружила. Сразу скажу, что все получилось :)
И даже удалось снять тренд температуры воздуха, измеряющийся внутренним сенсором температуры микроконтроллера:
А вот так вот видит мое устройство Pactware :)
Нормальная документация на 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();
Вначале прикинем канальный уровень, на данном рисунке показаны только публичные методы, чтобы не засорять картинку.
Немного поясню картинку, методы обработчика прерываний являются статическими, и чтобы обращаться к методам и полям экземпляра класса в этих статических методах нужно чтобы либо поля и методы тоже были статическими, либо обратиться непосредсвенно к экземпляру класса. Я пошел вторым — правильным путем, обращаюсь к конкретному экземпляру класса, который инициализирую в конструкторе указателем this.
LinkLayer будет принимать байты из UART порта и складывать их в буфер приема.
Но поскольку в HART есть сихронизирующие байты 0xFF, называемые преамбулами, и их количество может доходить до 20, (а они нам вообще не нужны, так как не несут в себе никакой инофрмации, и все что нам нужно, это определить начало кадра по последовательности( 0xFF 0xFF <Стартовый байт>)), то будет растратно скидывать 20 преамбул в буфер, поэтому я просто буду следить за ними прямо в прерывании и как только они кончатся, я начну складывать байты в буфер.
После приема каждого байта я буду перезапускать таймер на 19 мс, если он сработает, то посылка считается принятой.
Еще стоит задача оповестить linklayer из класса linklayertimer, как только сработает прерывание от таймера, говорящее о конце приема запроса от мастера. Для этого я воспользовался шаблоном проектирования Наблюдатель, в итоге LinkLayer просто подписывается на события от LinkLayerTimer, Выглядит это так:
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 также оповещает о конце приемке запроса от мастера своих подписчиков — это нам понадобиться позже
DWART. Разбор и формирование HART кадра
Далее разберемся с классом cFrame, он будет декодировать запросы от мастера, а также формировать кадры ответов и используя LinkLayer отсылать их:
Реализация:
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. Формирование команд
Теперь самое интересное — команды, как я уже говорил, хотелось бы чтобы все выглядело красиво, и можно было удобно обращаться к командам — вот так
Command0.Response.PrimaryVariable = 3.54
Понятно, что если для каждой команды я буду хранить всю структуру ответа Response, а команд может быть 254, то у меня никакой памяти не хватит, поэтому я буду хранить только указатель на структуру ответа Response, и каждый раз присваивать этому указателю, указатель на данные в буфере передачи, который получу с помощью метода buildFrameBeforeData класса сFrame.
Для всех команд сделаю базовый интерфейс iBaseDwartCommand с общими методами, а поскольку у меня запросы и ответы у каждой команды разной длины, и разных типов, то я создам шаблонный класс наследник iDwartCommand. Примерно это выглядит вот так:
Команда 0 — возвращает просто информацию об устройстве.
Команд 1 — возвращает значение первичной переменной, в моем случае это сTrimmer, доступ к которому дает класс cVariablesDirector, поэтому я должен его передать в конструкторе.
В итоге, заполнение данных команды 1 выглядит вот так:
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();
}
Здесь метод 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" //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();
}
По такому же принципу построены и другие команды. У меня большой запас памяти, я решил, что было бы неплохо все команды, а их может быть до 254 скинуть в один массив, далее я поясню почему посчитал это удобным. Я просто создал класс контейнер cCommandSet:
#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];
};
Для того, чтобы две программы Pactware и HartConfig приняли меня за Yokogawa, мне надо реализовать минимальный набор команд, и положить их в контейнер.
commandset.cpp выглядит так:
#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);
}
Тут у меня ошибка, так как почти весь массив не инициализирован, но мне лень было его забивать :) Держим в уме, что там ошибка.
Я понятия не имею, что делают команды 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 указатель на буффер приема и передачи. А поскольку у меня работа будет в режиме запрос-ответ, то буффер будет один и на передачу и на прием, заодно и память сэкономим.
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);
}
}
Осталось добавить создание нового класса cDwart в main.cpp и запустить на проверку
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 не собираемся возвращать указатели.
И вот немецкое чудо под названием Pactware видит Японский датчик давления, даже не замечая, что все это муляж.
И наше отечественное ПО тоже не замечает подвох.
Конечно, многое тут сделано не очень оптимально, например массив из 255 указателей на команды, его можно убрать, и сделать выполнение команд через обычный switch case, также можно использовать один placeholder на все команды, равной по размеру самой большой команде и каждый раз в нем создавать нужную команду, а после удалять.
Но в моем случае у меня памяти немеренно, и проблем оптимизации не было.
Сам проект как обычно в секретном месте
lzhl
Как будто для Хабра статья.
lamerok
Да, вначале статьи были на хабре, но потом разделы «Програмирование микроконтроллеров» и Сделай сам сюда переехали, а С++ там остался. А больше на Хабре, я ничего подходящего не нашел… И вообще, походу я что-то напутал, у меня и там и там статья опубликовалась.