Jump to content

    

Организация интерфейса к объекту внутри таска

Доброго времени суток. Решил стать программистом :)  и заодно изучить связку STM32\FreeRTOS\C++.

Хочу запустить драйвер (LCD с I2C напр.) в отдельном таске FreeRTOS, потому что очень медленная комуникация.

Драйвер написан как С++ класс. Доступ к методам объекта драйвера (разны команды к LCD) соответственно с другого (MAIN) таска.

Просто очередь для обмена командами не подходит, ибо это не поток данных а разнообразные (разно форматные, с разным набором параметров) команды.

++++++++++++

Подскажите пожалуйста есть ли какие стандарты (или красивые способы) для организации доступа к методам такого объекта-драйвера с других тасков?

Может есть стандартный интерфейс драйверов для таких случаев?

 

 

Share this post


Link to post
Share on other sites
4 hours ago, topor_topor said:

Подскажите пожалуйста есть ли какие стандарты (или красивые способы)

Скорее эта красота относится к программированию на целевом языке программирования (Си, Си++). Операционка лишь предоставляет способ передать абстрактный объект из одного места к другому. Может быть вам поглядеть на юнионы в Си? Как раз для разнородных сообщений подходит)

Share this post


Link to post
Share on other sites
6 часов назад, topor_topor сказал:

Просто очередь для обмена командами не подходит, ибо это не поток данных а разнообразные (разно форматные, с разным набором параметров) команды.

Почему не подходит? См паттрен Command. Делайте очередь из Command-ов. 

Или, на вскидку.... структура

struct Command

{

void (*f)(void *data); //указатель на LCD::print(void *)/rect(void*)/clear(void*)

void *data;

};

в очередь структуру Command. в lcd-Таске из очереди извлекать структуру и выполнять Command::f(Command::data);

в методе аргумент void * приводить к нужному, например

void LCD::print(void *data)

{

char *text = (char*)data;

this->print(text);

}

 

void LCD::rect(void *data)

{

Rect *rect = (Rect *)data;

this->drawHorizon(rect->x0, rect->x1);

...

}

ну как-то так...

 

зы ну и ещё нужно быть уверенным, что память Command::data освободиться после выполнения.

Share this post


Link to post
Share on other sites

А вы уверены, что тут вообще нужна очередь?

Есть общие данные - содержимое дисплея. С ними работают из разных потоков через локи.

Отдельный поток занимается передачей этих данных в дисплей.

Share this post


Link to post
Share on other sites
18 hours ago, juvf said:

Почему не подходит? См паттрен Command. Делайте очередь из Command-ов. 

Или, на вскидку.... структура

Спасибо за дельный совет. Решение красивое и полностью в концепте ООП. Пробую реализовать. 

Планирую передать объект Command (объект практически та-же структура).

Это прекрасно сработает для передачи команды с параметром (данными) от MAIN в таск драйвера.

++++++++++++++++++++++++++++++++++++++

А что делать для команды чтения (типа ReadID например)?

Думаю в обратную сторону можно очередь только для голых прочитанных данных использовать. В неё драйвер вложит результат после приёма объекта Command::ReadID.

Соответственно со стороны MAIN нужно выдать в командную очередь объект Command::ReadID и делать периодический поллинг появления данных в приёмной очереди (это и будет ID). После получения данных команду ReadID можно считать завершенной.

Подскажите пожалуйста если есть другие варианты.

Share this post


Link to post
Share on other sites
6 часов назад, topor_topor сказал:

Command::ReadID

как вариант вторая очередь будет работать. Если ответов мало.... тасков мало.... вполне рабочий лёгкий вариант, без оверинженеринга.  НО.... вот допустим, в очереди команд у вас уже 10 команд на выполнение,  в обратной очереди 5 ответов (с других тасков были запросы). Ваш текущий таск отправляет  Command::ReadID, получаем следуещее состояние: 

в очереди команд 11 команд

в очереди ответов 5 ответов

как ваш таск найдет/дождется именно ваш ответ? нужно перебрать всю очередь, в объекты Command/Ответ вводить id по которому таск найдет свой ответ. А если MAIN не дождется ответа и продолжит что-то делать, после появится ответ в очереди. Но этот ответ уже ни кому не нужен. Кто его удалит из очереди ответов?

 

Ваша  MAIN должда выдать и дождаться Command::ReadID. Т.е.  MAIN заблокируется на команде. Если MAIN блокируется, то зачем Command::ReadID передавать в задачу драйвера LCD? Выполните непосредственно из MAIN медленную LCD::ReadID(). LCD::ReadID() должна быть потокозащищённая (мьютекс или симафор).

 

Ещё вариант.... (если хорошо владеете ООП, а то ногу отстрелите), то создать очередь команд из указателей на Command. В таске драйвера lcd извлекаете из очереди указатель на Command, выполняете команду, И.... если команда с ответом, то в Command помещаете ответ и выставляет в Command флаг "выполененно". Если Command безответная (типа clear), то после выполнения  команды удаляете этот объект (Command) по указателю. В MAIN ждите флаг в нужной своей Command.

 

ps Command::флаг можно оформить через фриртосовский эвент, тогда не нужно в MAIN периодически проверять флаг, а заблокироваться на ожидании евента.

Share this post


Link to post
Share on other sites
1 hour ago, juvf said:

как ваш таск найдет/дождется именно ваш ответ?

Именно по этой причине в своё время, вдоволь наигравшись с глюками индентификации своего ответа, сделал две очереди. Одну для команд, другую для ответов. В каждую очередь влазит по одной команде и по одному ответу. А функцию отправки сообщения защищена мьютексом. Т.е. если один процесс отправил команду, он ждёт ответ. Другие в это время, да, курят бамбук. Но мне показалось, что такаяреализация самая надёжная. Впрочем она и работает в серийном приборе.

/*!
 * Отправить сообщение системе сбора данных.
 *
 * @param _msg сообщение, см. структуру Msg в хидере.
 * @param _tout таймаут, мс. Либо portMAX_DELAY.
 *
 * @return см. файл RetVals.hpp.
 */
TRetVal MeasCore::sendMsg( Msg &_msg, TickType_t _tout ) {
    configASSERT(m_daqMsgMutex);
    configASSERT(m_daqMsgRxQueue);
    configASSERT(m_daqMsgTxQueue);

    TickType_t timeout = ( _tout == portMAX_DELAY ) ? portMAX_DELAY : MSEC(_tout);

    BaseType_t qResult = xSemaphoreTake(m_daqMsgMutex, timeout);
    if (qResult == pdTRUE) {
        xQueueReset(m_daqMsgTxQueue);
        qResult = xQueueSend(m_daqMsgRxQueue, &_msg, 0);
        configASSERT(qResult == pdTRUE);
        if (qResult == pdTRUE) {
            Msg rxMsg;
            qResult = xQueueReceive(m_daqMsgTxQueue, &rxMsg, timeout);
            if (qResult == pdTRUE) {
                _msg.outparams = rxMsg.outparams;
                _msg.retVal = rxMsg.retVal;
            }
        }
        xSemaphoreGive(m_daqMsgMutex);
    }
    return ( qResult == pdTRUE ) ? rvOK : rvTIME_OUT;
}

 

Share this post


Link to post
Share on other sites
1 минуту назад, haker_fox сказал:

Т.е. если один процесс отправил команду, он ждёт ответ. Другие в это время, да, курят бамбук

Так а зачем тогда вообще очереди и драйвер выводить в отдельный поток? Из любого потока вызывай драйвер LCD::ReadID() и жди ответа. На вскидку:

 

TRetVal MeasCore::sendMsg( Msg &request, Msg &replay, TickType_t _tout )

Share this post


Link to post
Share on other sites

А у меня так и есть, вот что из себя Msg представляет:

    /// Структура с сообщением, которое можно отправить системе сбора данных
    typedef struct _Msg {
        MsgCode code; /// код команды
        const void * inparams; /// входные параметры
        void * outparams; /// выходные параметры (ответ)
        TRetVal retVal; /// Результат исполнения команды

        _Msg() :
            code(MsgCode::NOP),
            inparams(0),
            outparams(0),
            retVal(rvFAILED) {}
        _Msg( MsgCode _code, const void * _params, void * _outparams ) :
            code(_code),
            inparams(_params),
            outparams(_outparams),
            retVal(rvFAILED) {}
        explicit _Msg( MsgCode _code ) :
            code(_code),
            inparams(0),
            outparams(0),
            retVal(rvFAILED) {}
    } Msg;

 

Share this post


Link to post
Share on other sites
14 hours ago, juvf said:

Так а зачем тогда вообще очереди и драйвер выводить в отдельный поток? 

LCD драйвер реализует программный I2C. Соответственно если он идёт в MAIN то забивает надолго любую другую активность.

А в отдельном потоке он работает паралельно и не видимо.

Share this post


Link to post
Share on other sites
2topor_topor
Цитата

 

 
21 час назад, juvf сказал:

Так а зачем тогда вообще очереди и драйвер выводить в отдельный поток? 

LCD драйвер реализует программный I2C. Соответственно если он идёт в MAIN то забивает надолго любую другую активность.

А в отдельном потоке он работает паралельно и не видимо.

 

Вы читайте полностью пост.

 
Цитата

 

 
22 часа назад, haker_fox сказал:

Т.е. если один процесс отправил команду, он ждёт ответ. Другие в это время, да, курят бамбук

Так а зачем тогда вообще очереди и драйвер выводить в отдельный поток?

 

 

Вам же пишет haker_fox - " Другие в это время, да, курят бамбук", а вам нужно "работает паралельно и не видимо". Вы уже определитесь - вам шашечки или ехать? Вот я и спрашиваю, если писать код, что другие в это время курят бамбук - то зачем тогда вообще LCD в другой поток и очереди, если всё равно при таком решении MAIN забивает надолго любую другую активность?

Share this post


Link to post
Share on other sites
8 minutes ago, juvf said:

то зачем тогда вообще LCD в другой поток и очереди, если всё равно при таком решении MAIN забивает надолго любую другую активность?

Ну мой случай - рудимент, когда я ещё думал, что все задачи могут одновременно давать команды. Потом переделывать просто не стал. Да, неоптимально. Но работает)

Share this post


Link to post
Share on other sites
56 минут назад, haker_fox сказал:

Ну мой случай - рудимент, когда я ещё думал, что все задачи могут одновременно давать команды. Потом переделывать просто не стал. Да, неоптимально. Но работает)

Про рудимент понятно..... но всётаки наверно очередь из указателей - опасно. В вашем коде заряженный пистолет ждёт чью-то ногу.

возможно в вашем коде пистолет ни когда не стрельнет, но в другом коде может быть очень больно.

допустим вы в майне на стеке создали структуру Msg _msg, передали ссылку в sendMsg(_msg, 100), которая передает указатель в очередь

qResult = xQueueSend(m_daqMsgRxQueue, &_msg, 0);

Допустим в ОС крутится много задач очень важные задачи (с приоритетом выше LCD), которые заняли МК например на 200 миллисекунд. MeasCore::sendMsg() не дождался ответа и вернул false. MAIN удалил _msg (или вышел из области _msg). Тут вдруг LCD дождался таки процессорного внимания и начал работу. Поток LCD  видит в извлекает из очереди команд указатель на _msg и.... упс.... а _msg уже удалена. Обращение к удаленному объекту!!!

 

Share this post


Link to post
Share on other sites
1 hour ago, juvf said:

Обращение к удаленному объекту!!!

Совершенно верно, я уже тоже с этим столкнулся, когда были указатели на стековые удалённые переменные. Поэтому таймаут везде при вызове portMAX_DELAY. Дикий рудимент:biggrin:

Share this post


Link to post
Share on other sites
2 hours ago, juvf said:
 Другие в это время, да, курят бамбук", а вам нужно "работает паралельно и не видимо". Вы уже определитесь - вам шашечки или ехать? Вот я и спрашиваю, если писать код, что другие в это время курят бамбук - то зачем тогда вообще LCD в другой поток и очереди,... 

Ok. LCD висит на I2C. На нем же висят AUDIO, EEPROM итд. Если один из них занял порт то ви прави - остальние курят.... Но зачем другим полезним функциям MAIN  липнуть? Например можно продолжать реагировать на кнопки,  усреднять измерения АЦП итд. 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now