Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Реализация конечного автомата на 8битном контроллере.
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
sigmaN
Всем привет!
Сейчас сделано как-то так.
Прекрасно работает, расширяется. Всем доволен, но хотелось бы сравнить с тру 8ми битным подходом. Собственно какую-нибудь хорошую реализацию конечного автомата и хотелось бы где-то посмотреть.
Код
enum DeviceEvents
{
    DEVEV_HEARTBEAT,            //this event occures every ~100ms
    DEVEV_STARTBTN_PRESSED,
    //etc...
    DEVEV_NO_EVENT = 0xff,
}

enum DeviceState_enum
{
    DEVSTATE_OFF,              
    DEVSTATE_LOCKED,
    //etc....
    DEVSTATE_INVALID = 0xff,
}

//state interface class
struct DeviceStateImplementation
{
    virtual void EnterThisStateFrom( const DeviceState_enum &prevState ) = 0;
    //Handles event and returns new device state in response of event
    virtual DeviceState_enum HandleEvent( const DeviceEvents &event ) = 0;
};

//Implementation of DEVSTATE_OFF state
class StateImpl_OFF: public DeviceStateImplementation
{
    timestamp_t MillisecondsCounter;
    uint8_t MinutesCounter;
    bool ReactOnStartBTNRelease;

    void ResetMillisecondsCounter();
public:
    virtual void EnterThisStateFrom( const DeviceState_enum &prevState );
    virtual DeviceState_enum HandleEvent( const DeviceEvents &event );
};

//Implementation of DEVSTATE_OFF state
void StateImpl_OFF::EnterThisStateFrom( const DeviceState_enum &prevState )
{
    TurnOffAllOutputs();
    DisableRPMMeasure();
    G_SettingsStateSelector.ResetPressCounter();
    ReactOnStartBTNRelease = false;
    ResetMillisecondsCounter();
}

void StateImpl_OFF::ResetMillisecondsCounter()
{
    MillisecondsCounter = GetTickCount();
    MinutesCounter = 0;
}

DeviceState_enum StateImpl_OFF::HandleEvent( const DeviceEvents &event )
{
    DeviceState_enum retval = DEVSTATE_OFF;

    switch(event)
    {
    case DEVEV_HEARTBEAT:
        ACC_LEDBlinker.DoTheJob();

        if( G_SettingsStateSelector.GetSelectedState() != DEVSTATE_INVALID )
        {
            ACC_LEDBlinker.StartEndlessBlinkSequence(100, 100);//fast blinking indicating some settings mode was selected
        }
        break;

    case DEVEV_STARTBTN_PRESSED:
        ReactOnStartBTNRelease = true;
        break;

    case DEVEV_STARTBTN_RELEASED:
        //react on release only if press also was made in this state
        if( ReactOnStartBTNRelease )
        {
            ResetMillisecondsCounter();
            if( GlobalVarsRef.BrakePedal.GetButtonState() == GPIOBTNSTATE_PUSHED )
            {
                DeviceState_enum SelectedSettingsModeState = G_SettingsStateSelector.GetSelectedState();
                if( SelectedSettingsModeState != DEVSTATE_INVALID )
                    retval = SelectedSettingsModeState;
                else
                    retval = DEVSTATE_CRANKING;
            }
            else
            {
                retval = DEVSTATE_IGNON;
            }
        }
        break;

    case DEVEV_STARTBTN_LONGPRESS:
        if( GlobalVarsRef.BrakePedal.GetButtonState() == GPIOBTNSTATE_PUSHED )
            retval = DEVSTATE_FORCEDCRANKING;
        else
            retval = DEVSTATE_ACCON;
        break;

    case DEVEV_BREAKPEDAL_PRESSED:
        ResetMillisecondsCounter();
        G_SettingsStateSelector.SelectorWasPressed();

        ACC_LED::Clear();
        ACC_LEDBlinker.StartBlinkSequence(2, 50, 100);
        break;

    case DEVEV_BREAKPEDAL_RELEASED:
        ACC_LED::Clear();
        ACC_LEDBlinker.AbortBlinkSequence();
        break;

    case DEVEV_ENGINE_STARTED:
        retval = DEVSTATE_RUNNING;
        break;

    case DEVEV_ENGINE_STALLED:
        break;
    }

    return retval;
}

/////main state machine implementation
class MainStateMachine
{
public:
    void EnterStateForced( const DeviceState_enum &state );
    void ProcessEvent( const DeviceEvents &evnt );

private:
    DeviceStateImplementation *CurrentDevStateImplementation; //<----pointer to state interface class
    DeviceState_enum CurrentDevState;

    void SetNewDeviceState( const DeviceState_enum &newDevState );
};

void MainStateMachine::ProcessEvent( const DeviceEvents &evnt )
{
    DeviceState_enum newDevState = CurrentDevStateImplementation->HandleEvent( evnt );
    if( newDevState != CurrentDevState )
        SetNewDeviceState( newDevState );
}

void MainStateMachine::SetNewDeviceState( const DeviceState_enum &newDevState )
{
    switch( newDevState )
    {

    case DEVSTATE_OFF:
        CurrentDevStateImplementation = &OFFState;
        break;
    case DEVSTATE_LOCKED:
        CurrentDevStateImplementation = &LockedState;
        break;
    }
    CurrentDevStateImplementation->EnterThisStateFrom( CurrentDevState );
    CurrentDevState = newDevState;
}


//main loop simplifiyed
int main()
{
    while(1)
    {
        event = DEVEV_HEARTBEAT;
        while( event != DEVEV_NO_EVENT )
        {
            MainStateMachine.ProcessEvent( event );
            event = DetectOneMoreEvents();
        }
    }
}


iosifk
Цитата(sigmaN @ Jul 9 2017, 13:42) *
Всем доволен, но хотелось бы сравнить с тру 8ми битным подходом. Собственно какую-нибудь хорошую реализацию конечного автомата и хотелось бы где-то посмотреть.

В ИТМО есть Шалыто А.А. И у них есть сайт со статьями по "switch-тенологиям"...
is.ifmo.ru
sigmaN
iosifk, спасибо, кажется нашел пдфку с которой стоит начать http://is.ifmo.ru/books/_book.pdf
iosifk
Цитата(sigmaN @ Jul 9 2017, 19:33) *
iosifk, спасибо, кажется нашел пдфку с которой стоит начать http://is.ifmo.ru/books/_book.pdf

Еще в КиТ, в архиве статей 2006-11 и до 2007-8 статьи, автор Татарчевский...

Dog Pawlowa
Цитата(sigmaN @ Jul 9 2017, 13:42) *
Всем доволен, но хотелось бы сравнить с тру 8ми битным подходом. Собственно какую-нибудь хорошую реализацию конечного автомата и хотелось бы где-то посмотреть.

Биты то при чем?

Реализация автомата - дело интимное. При реализации UI как автомата параметры такие:
- состояний - около сотни
- событий - два десятка (кнопки и таймеры)
- новое состояние определяется внутри обработки конкретного состояния (т.к. не все, что происходит и изменяет состояния - это события)
- изменение состояния - тоже событие (удобно для единообразия)
- Х-макросы для перечисления имен функций, названий состояний, текста в первой строке, особенностей поддержки каждого состояния (например, чистить или нет экран)
- механизм возврата в предыдущее состояние
Очень важная для меня цель была - прозрачность понимания, т.к. приходится корректировать проекты после большого перерыва.
sigmaN
Биты при том, что при моей нынешней реализации(которую я вкратце привел) У меня при всём удобстве в редактировании и расширении маленько так сожрало и флэша и памяти.
Труды Шалыто А.А. я конечно изучу, полезно будет по любому, но думал может ещё народ скинет разных исходников "на посмотреть".

Может есть что-то готовое и красиво реализованное, например как protothreads, где switch красиво обыгран макросами или я не знаю... Реализацию табличного какого-нибудь подхода глянуть.
И именно 8ми битный контроллер был обозначен для того, чтоб не приводили полномасштабных вариантов с UI
Dog Pawlowa
Цитата(sigmaN @ Jul 10 2017, 10:53) *
Биты при том, что при моей нынешней реализации(которую я вкратце привел) У меня при всём удобстве в редактировании и расширении маленько так сожрало и флэша и памяти.
И именно 8ми битный контроллер был обозначен для того, чтоб не приводили полномасштабных вариантов с UI

UI - это не GUI, это всего лишь дисплей 2х16, 4 кнопки и STM8.
Давайте адрес почты, пришлю файлов, но не весь проект.
sigmaN
Спасибо, будет интересно посмотреть!
altersoft mail.ru
XVR
Цитата(sigmaN @ Jul 9 2017, 19:33) *
iosifk, спасибо, кажется нашел пдфку с которой стоит начать http://is.ifmo.ru/books/_book.pdf
Хм. Читать осторожно - у неподготовленного читателя может вызвать взрыв мозга. sm.gif

Что касается автоматов на МК с минимумум ресурсов (будем так называть '8ми битные контролеры') то тут явно напрашивается нечто на голом С либо в виде большого switch, либо таблицей переходов (в зависимости от количества состояний, переходов и архитектуры самого МК). А что бы не было 'мучительно больно' это все поддерживать (как минимум модифицировать) напрашивается какой нибудь генератор (либо из готовых, что были упомянуты в статье, либо что нибудь самописное на каком нибудь Perl'е или Python'е)

sigmaN
Цитата
Что касается автоматов на МК с минимумум ресурсов (будем так называть '8ми битные контролеры') то тут явно напрашивается нечто на голом С либо в виде большого switch, либо таблицей переходов
Вот на исходнички чего-нибудь такого и было бы интересно посмотреть. В частности интересно реализовать одинаковые автоматы двумя разными способами и сравнить их по быстродействию/занимаемой памяти....
Dog Pawlowa
Цитата(XVR @ Jul 10 2017, 13:30) *
либо в виде большого switch, либо таблицей переходов


уже десять лет назад IAR AVR делал таблицу из большого свича на высоких уровнях оптимизации.
sigmaN
Вот дойдут как-нибудь руки до сравнений и будет интересно посмотреть на результаты.

Так то в моей текущей реализации вызов обработчика событий идет через указатель CurrentDevStateImplementation, который является указателем на интерфейс класса, хотя записан туда адрес конкретной реализации. Что при вызове метода через этот указатель ВНЕЗАПНО biggrin.gif приводит к использованию vtable https://ru.wikipedia.org/wiki/%D0%A2%D0%B0%...%B4%D0%BE%D0%B2

В связи с чем и становится интересно, а будет ли реальная экономия хоть чего-нибудь если все эти состояния положить в огромный switch, который потом IAR доблестно превратит в таблицу с адресами функций... Что в итоге будет снова ооочень напоминать vtable )
k155la3
Цитата(sigmaN @ Jul 11 2017, 02:02) *
. . .
В связи с чем и становится интересно, а будет ли реальная экономия хоть чего-нибудь если все эти состояния положить в огромный switch, который потом IAR доблестно превратит в таблицу с адресами функций... Что в итоге будет снова ооочень напоминать vtable )

Смотря как компилятором реализуется работа vtable и какая "вложенность".
Если использовать правильный switch (опятьже зависит от комилятора) который генерирует "вычисляемые" переходы.
"правильный" - это в IAR конструкция
Код
switch ( __even_in_range(TBIV, 14)  )
{ ...

pokk
Dog Pawlowa, можно тоже исходники глянуть? m65535625@gmail.com

А то как не крути вечно такой колхоз получается
(Передача пакета данных по usart)
CODE
void Sendpackage(void){
static uint8_t mode;
static uint16_t counter;
uint8_t* Data;
switch(mode){
case 0:{
//---------------------------------------------
//--------Ожидание команды на отправку---------
//---------------------------------------------
if(TransmitDataStart==Transmit_START){
TransmitDataStart=Transmit_STOP;
Data=&Protocol;
mode=1;
}
break;
}
//-------------------------------------------------
case 1:{
if(counter<sizeof(ProtocolExchange_t)){
if(USART3->SR & USART_SR_TC){
USART3->DR =(uint8_t)Data[counter];
counter++;
}
}else{
mode=0;
}
break;
}
//-------------------------------------------------
}
}

sigmaN
Цитата
Смотря как компилятором реализуется работа vtable и какая "вложенность".
Ну "вложенность" получается 1. Указатель на абстрактный класс указывает на реализацию класса.

Про __even_in_range() не знал, протестируем

Add:
Последний IAR STM8
Error[Pe020]: identifier "__even_in_range" is undefined main.cpp 140
sad.gif
jcxz
Цитата(sigmaN @ Jul 11 2017, 13:02) *
Error[Pe020]: identifier "__even_in_range" is undefined main.cpp 140

__even_in_range() встречал только в IAR for MSP430. Сдаётся мне, что макрос этот характерен только для данного ядра.
Насколько помню: там (в MSP430) значения некоторых регистров периферии, содержащие перечисления, сделаны с шагом ==2 (т.е. - значения enum-ов для них идут с шагом 2, а не 1 как в стандарте).
Сделано это чтобы такое значение можно было просуммировать с адресом начала таблицы переходов (по switch) и сразу выполнить ветвление без лишних команд.
even - потому что через 2, так как размер памяти - 16 бит.
В этом макросе первый аргумент - это регистр, содержащий перечисление в таком формате, второй аргумент определяет размер таблицы переходов.
В STM8 регистры периферии с перечислениями не выполнены в таком формате, соответственно и макрос этот там бесполезен.
k155la3
Цитата(sigmaN @ Jul 11 2017, 13:02) *
Error[Pe020]: identifier "__even_in_range" is undefined main.cpp 140
sad.gif

есть в IAR/MSP430 - используется в switch которые в векторах прерываний.
(для ускорения работы и для "одноразового" чтения параметра switch - если в кач-ве параметра исползован регистр с "автосбросом")

Есть смысл посмотреть в DASM как отрабатывают вирт. таблицы в Вашем компиляторе.
Скорее всего по томуже принципу.




jcxz
Цитата(k155la3 @ Jul 11 2017, 13:52) *
Есть смысл посмотреть в DASM как отрабатывают вирт. таблицы в Вашем компиляторе.
Скорее всего по томуже принципу.

Нет. В STM8 нет регистров периферии организованных по такому принципу. И система команд там совсем другая. Поэтому такой макрос там бессмысленнен.
sigmaN
С этим разобрались, осталось провести эксперименты и определить на сколько vtable проигрывает switch

Я не знаю кто как, а я лично пока думаю, что по удобству использования и расширения полиморфизм является самым(?) удобным способом реализации таких конструкций.
Более того, где-то у отцов ООП читал, что как раз полиморфизм и положено применять там, где в завимости от некоего условия кардинально меняется поведение класса.
У нас таким условием является состояние автомата и в зависимости от него меняется реакция на одни и те-же события(поведение).

Честно говоря пока у меня складывается впечатление, что если полиморфизм и проиграет голым уродливым свитчам то не значительно. Но без замеров это всё конечно пустые слова.
Lerk
Цитата(sigmaN @ Jul 11 2017, 14:47) *
Честно говоря пока у меня складывается впечатление, что если полиморфизм и проиграет голым уродливым свитчам то не значительно. Но без замеров это всё конечно пустые слова.


По поводу C++ vs C есть замечательное видео:
https://www.youtube.com/watch?v=D7Sd8A6_fYU...eature=youtu.be
sigmaN
Видел. Там даже мой лайк имеется под этим видео ))
Жалко что это немного не по теме получается
Dog Pawlowa
Цитата(pokk @ Jul 11 2017, 09:38) *
А то как не крути вечно такой колхоз получается

Что касается такого колхоза, то этого хватает у каждого.
И вообще это не колхоз, это классика - см ниже! wink.gif
Если речь идет о автоматах, кроющих практически всю функциональность устройства, то тут все становится сложнее, потому что не есть проблема реализовать это все, а проблемой является выбор собственного стиля и подхода, чтобы было все понятно и прозрачно в первую очередь для самого себя.

CODE

#define TRANSPORT_TIMEOUT_SERIAL_SEC 10
#define TRANSPORT_TIMEOUT_TCPIP_SEC 5
#define LONG_RUN_TIMEOUT_SEC 60
#define AUTO_PICKUP_TIME_MS 500
#define NUM_RETRIES 3

// added by me timeouts:
#define RECEIVE_TIMEOUT_MS 1000
#define ANSWER_TIMEOUT_MS 100









enum {
errNO=0,
errMESSAGE_SIZE,
errOP_CODE,
errRECORDS_QTY,
errINDEX1,
errINDEX2,
errINDEX3,
errNOT_SUPPORTED,
errREAD_ONLY,
errWRONG_SIZE,
errWRITE_ONLY,
errWRONG_OPCODE,
errSIZE,
};


#define RECEIVED_MESSAGE_LENGTH ((int)com1.rx_buf[1]+((int)com1.rx_buf[2])<<8)
#define RECEIVED_TYPE (com1.rx_buf[3])
#define RECEIVED_SEQUENCE (com1.rx_buf[4])
#define ctrlRECEIVED (com1.rx_buf[5])
#define ctrlACK ('A')
#define ctrlNAK ('N')
#define ctrlEND ('E')
#define ctrlWAIT ('W')
#define ctrlPICKUP ('P')
#define ctrlDROP ('D')
#define XML_PTR_FIRST (char*)(com1.rx_buf+5)
#define XML_PTR_LAST (com1.rx_buf+RECEIVED_MESSAGE_LENGTH-4)
#define ETX_INDEX (RECEIVED_MESSAGE_LENGTH-3)

/*
Control message
1 byte STX StartMessage
2 byte Unsigned Binary Length Length of the entire message; including StartMessage and EndMessage but excluding CRC bytes
1 byte ASCII Type 'C'
1 byte Unsigned Binary Sequence 0x00 is invalid value
1 byte ASCII ControlFunction 'A' ACK
'N' NAK
'E' End Sequence
'W' Wait
'P' Pickup
'D' Drop Link
1 byte ETX EndMessage
2 byte Binary CRC See CRC Calculation


Payload message
1 byte STX StartMessage
2 byte UnsignedBinary Length Length of the entire message; including StartMessage and EndMessage but excluding CRC bytes
1 byte ASCII Type 'P'
1 byte Unsigned Binary Sequence 0x00 is invalid value
Variable Unsigned Binary Payload The transported application protocol message
1 byte ETX EndMessage
2 byte Unsigned Binary CRC See CRC Calculation

*/

#define XML_START_OFFSET 5
#define XML_END_OFFSET 2

char payload_sent_wait_control;
char payload_sent_time;
char payload_sent_qty;





void PrepareTransportPayloadHeader(void)
{ com1.tx_head=com1.tx_buf;
*com1.tx_head++=STX;
com1.tx_head+=2; // pass length
*com1.tx_head++='P'; // payload type
*com1.tx_head++=RECEIVED_SEQUENCE;
}

void PrepareTransportControlMessage(char com)
{
int my_checksum;

com1.tx_head=com1.tx_buf;
*com1.tx_head++=STX;
*com1.tx_head++=7; // length = 7
*com1.tx_head++=0;
*com1.tx_head++='C'; // control type
*com1.tx_head++=RECEIVED_SEQUENCE;
*com1.tx_head++=com;
*com1.tx_head++=ETX;
my_checksum=crc16_ccitt(com1.tx_buf, 7);
*com1.tx_head++=(char)(my_checksum&0x00ff);
*com1.tx_head++=(char)(my_checksum>>8);
}

void PrepareTransportTrailer(void)
{
int my_size;
int my_checksum;

*com1.tx_head++=ETX;
my_size=com1.tx_head- com1.tx_buf;
com1.tx_buf[1]=(char)(my_size&0x00ff);
com1.tx_buf[2]=(char)(my_size>>8);
my_checksum=crc16_ccitt(com1.tx_buf, my_size);
*com1.tx_head++=(char)(my_checksum&0x00ff);
*com1.tx_head++=(char)(my_checksum>>8);

}




enum { UNCONFIGURED,
RESTART_RX,
WAITING_STX,
WAITING_ETX,
WAITING_BCC1,
WAITING_BCC2,
ANALYSE_MESSAGE,
PREPARE_MESSAGE,
START_SEND_MESSAGE,
WAITING_START_TRANSMIT,
WAITING_END_TRANSMIT,
FINAL,
PREPARE_NAK,
PREPARE_DROP,
PREPARE_END };

char transport_error;

#define USE_TIMEOUT 1

u16 TimeoutRunning(void)
{
#ifdef USE_TIMEOUT
return com1.timeout;
#else
return 1;
#endif
}


void fRnt(void)
{

static u16 my_checksum;
static u16 message_checksum;


switch (pstate)
{ case UNCONFIGURED:
Com1Init();
pstate++;
break;

case RESTART_RX:
Com1SetReceive(COM1_WAIT_STX);
prot_error=errNO;
pstate++;
OFF_LED5();
OFF_LED4();

break;

case WAITING_STX:
if ( payload_sent_wait_control )
{ if (payload_sent_qty<2)
{ if (event==evSec)
{ if (payload_sent_time<10)
payload_sent_time++;
else
{ pstate=START_SEND_MESSAGE;
payload_sent_qty++;
payload_sent_time=0;
}
}
}
else // all attempts done
payload_sent_wait_control=0;
}
else
{ payload_sent_qty=0;
payload_sent_time=0;
}


switch (com1.rx_status)
{ case COM1_RECEIVED:
pstate=RESTART_RX; // buffer is over
break;

case COM1_RECEIVING_:
pstate=WAITING_ETX;
com1.timeout=RECEIVE_TIMEOUT_MS;
break;
}
break;

case WAITING_ETX:
ON_LED4(); // recieving starts
if (TimeoutRunning())
{ if (com1.rx_head>com1.rx_tail)
{ switch (*com1.rx_tail)
{
case ETX:
// case EOT:
com1.rx_tail++;
if (com1.rx_tail>com1.rx_buf+5)
pstate++;
break;

default:
com1.rx_tail++;
break;
}
}
}
else
pstate=RESTART_RX;
break;

case WAITING_BCC1:
if (TimeoutRunning())
{ if (com1.rx_head>com1.rx_tail)
{ com1.rx_tail++;
pstate++;
}
}
else
pstate=RESTART_RX;
break;

case WAITING_BCC2:

if (TimeoutRunning())
{ if (com1.rx_head>com1.rx_tail)
{ com1.rx_tail++;
pstate++;
Com1SetReceive(COM1_PASSIVE);
}
}
else
pstate=RESTART_RX;
break;


case ANALYSE_MESSAGE:
OFF_LED4();
if (com1.rx_head<com1.rx_buf+8) // too short message
{ pstate=RESTART_RX;
break;
}
my_checksum=crc16_ccitt(com1.rx_buf, com1.rx_tail-com1.rx_buf-2);
message_checksum= ( ((unsigned int)*(com1.rx_head-2)) ) |
( ((unsigned int)*(com1.rx_head-1))<<8 );

#if (defined IGNORE_CHECKSUM)

#elif (defined USE_CHECKSUM)
if ( (my_checksum!=message_checksum) || // wrong checksum
(*(com1.rx_head-3) !=ETX) //not ETX
)
{ pstate=RESTART_RX;
break;
}
#else
#error "What to do with checksum?"
#endif



switch (RECEIVED_TYPE)
{ case 'C': // control message
switch (ctrlRECEIVED)
{ case ctrlEND:
pstate=PREPARE_END;
break;
case ctrlDROP:
pstate=PREPARE_DROP;
break;

default:
pstate=RESTART_RX;
break;
}
break;

case 'P': //
if (!AnalyseXmlOk(XML_PTR_FIRST,(char*)com1.rx_head-4))
pstate=PREPARE_NAK;
else
pstate=PREPARE_MESSAGE;
break;

}
break;

case PREPARE_MESSAGE:
ON_LED5(); // answering started
com1.tx_head=com1.tx_buf;
PrepareTransportPayloadHeader();
PrepareXml();
PrepareTransportTrailer();
pstate++;
payload_sent_wait_control=1;
payload_sent_time=0;
break;

case START_SEND_MESSAGE:
SET_TRANSMIT(); // for driver
pstate++;
com1.timeout=ANSWER_TIMEOUT_MS;
break;


case WAITING_START_TRANSMIT:
if (!com1.timeout)
{ Com1SetTransmit(COM1_TRANSMITTING);
pstate++;
}
break;

case WAITING_END_TRANSMIT:
if (com1.tx_status==COM1_TRANSMITTED)
if (TRANSMIT_OVER())
{ com1.timeout=0;
pstate++;
}
break;

case FINAL:
OFF_LED5();
pstate=RESTART_RX;
break;


case PREPARE_NAK:
pstate = UNCONFIGURED;
break;

case PREPARE_DROP: //0207004300440389AC
ON_LED5();
PrepareTransportControlMessage('D');
com1.timeout=ANSWER_TIMEOUT_MS;
SET_TRANSMIT();
pstate=WAITING_START_TRANSMIT;
break;

case PREPARE_END:
ON_LED5();
PrepareTransportControlMessage('E');
com1.timeout=ANSWER_TIMEOUT_MS;
SET_TRANSMIT();
pstate=WAITING_START_TRANSMIT;
payload_sent_wait_control=0;
break;

default:
pstate = UNCONFIGURED;
break;
}
}
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2024 Invision Power Services, Inc.