Доброго здравия всем!
Сегодня я хочу вам рассказать, как постепенно студенты учатся разрабатывать ПО для микроконтроллера на примере драйвера UART на STM32F411. Код и архитектуру с небольшими моими изменениями и доработками я попытаюсь привести здесь.
Сразу отмечу, что все сделано статикой, как я учил :) (статические классы, статическая подписка, статический странно-рекурсивный шаблон, статический контейнер для команд и так далее), но можно реализовать то же самое с помощью обычных объектов и обычного подхода. В обычном подходе архитектура была бы такая же, но кода немного больше, как по объему так и по количеству строк кода.
Данная статья не претендует на истину, а лишь показывает подход к реализации некоторых задач, в данном случае реализацию Uart драйвера на С++.
Идея
Итак была поставлена следующая задача:
- Драйвер должен уметь работать в асинхронном режиме
- Драйвер должен уметь отсылать заданное количество байт
- Драйвер должен уметь принимать заданное количество байт
- Драйвер должен вызывать событие по завершению передачи и по завершению чтения заданного количества данных
- Драйвер должен вызывать событие по приеме каждого байта
Общая архитектура
Картинка ниже поясняет назначение драйвера, чтобы иметь представление что такое вообще драйвер UART в данном контексте.
В общем случае — это слой не зависящий от аппаратной реализации UART, предоставляющий очень абстрактный интерфейс для приема, передачи данных.
Архитектура драйвера
Поскольку драйвер должен предоставлять не так много функций, его архитектуру очень упрощенно можно представить в таком виде:
У драйвера есть два метода:
WriteData(const uint8_t *pData, uint8_t size)
— для посылки заданного числа байтовReadData(uint8_t size)
— для приема заданного числа данных
А также события:
OnTransmit()
— вызывается UART модулем при передаче одного символаOnTransmitComplete()
— вызывается UART модулем при окончании передачиOnReceive()
— вызывается UART модулем при приеме одного символа
Драйвер будет иметь списки статических подписчиков. Всего 2 списка:
UartDriverTransmitCompleteObservers
— список содержит подписчиков на событиеOnTransmitComplete()
и просто вызывает у всех своих подписчиков методOnTransmitComplete()
template<typename ...Observers>
struct UartDriverTransmitCompleteObservers
{
__forceinline static void OnWriteComplete()
{
(Observers::OnTransmitComplete(), ...) ;
}
};
UartDriverReceiveObservers
— список содержит подписчиков на событиеOnReceiveComplete()
и просто вызывает у всех своих подписчиков методOnReceiveComplete()
template<typename ...Observers>
struct UartDriverReceiveCompleteObservers
{
__forceinline static void OnReadComplete(tBuffer& buffer, std::size_t bytesReceived)
{
(Observers::OnReceiveComplete(buffer, bytesReceived), ...) ;
}
};
Подписаться на драйвер может хоть кто, у кого есть метод OnReceiveComplete()
или
OnTransmitComplete()
. На картинке в качестве примера показаны два подписчика SomeProtocol
и OtherObserver
.
Реализация
Метод WriteData()
Давайте посмотрим, как реализован метод посылки данных. Для начала немного для себя определим спецификацию метода:
Метод должен:
- Не должны обрабатывать прием (мы хотим передавать, у нас один буфер (для экономии), мы не можем одновременно и принимать и передавать)
- Скопировать данные в буфер передачи
- Установить максимальное значение передаваемых байт в значение, которое передал пользователь
- Записать первый байт в UART
- Установить счетчик количества переданных байт в 1, так как один байт уже передали
- Инициировать передачу
Теперь можно это перевести в код:
static void WriteData(const std::uint8_t *pData, std::uint8_t bytesTosend)
{
assert(bytesTosend < txRxBuffer.size()) ;
const CriticalSection cs;
// Проверим, что мы не находимся в режиме или записи.
// т.е. что предыдущие данные либо приняты либо уже отосланы
if ((status != Status::Write) && (status != Status::Read))
{
bufferIndex = 0U;
bufferSize = bytesTosend;
std::memcpy(txRxBuffer.data(), pData, static_cast<std::size_t>(bytesTosend));
Uart::WriteByte(txRxBuffer[bufferIndex]);
bufferIndex++;
//устанавливаем режим передачи, что происходит передача
status = Status::Write;
Uart::StartTransmit();
}
}
Событие OnTransmit()
Теперь, когда передача инициирована, каждый раз как байт будет отправлен (из регистра данных в защелку) UART модуль вызовет событие OnTransmit()
драйвера UartDriver
и нужно будет отослать следующий символ. Собственно это все чем занимается OnTransmit()
— отсылает следующий байт.
__forceinline static void OnTransmit()
{
// проверка все ли данные переданы (до защелки)
if(bufferIndex < bufferSize)
{
Uart::WriteByte(txRxBuffer[bufferIndex]) ;
bufferIndex ++ ;
} else
{
//Если все данные переданы, инициируем прерывание по опустошению защелки
//Чтобы убедиться, что последний байт точно вышел в линию
Uart::EnableTcInterrupt() ;
}
};
Событие OnTransmitComplete()
Как вы знаете у UART обычно есть два события: одно по опустошению регистра данных, а второе по опустошению защелки (т.е. реально когда байт вышел в линию), поэтому логично, чтобы убедиться что последний байт вышел в линию, использовать прерывание по опустошению защелки.
Метод OnTransmitComplete() должен сделать несколько вещей:
- Сбросить счетчик количества переданных байт
- Сбросить максимальное количество переданных данных
- Запретить передачу и прерывания по передаче
- Установить статус, что передача завершена
- Оповестить подписчиков на событие
OnTransmitComplete()
, которые будут в спискеUartDriverTransmitCompleteObservers
static void OnTransmitComplete() {
bufferIndex = 0U;
bufferSize = 0U;
Uart::DisableTcInterrupt();
Uart::DisableTxInterrupt() ;
Uart::DisableTransmit();
status = Status::WriteComplete;
// оповещаем подписчиков о том, что передача завершена
UartDriverTransmitCompleteObservers::OnWriteComplete() ;
}
Метод ReadData()
То же самое для метода чтение данных.
Метод должен:
- Запретить передачу
- Запретить прерывания по передаче
- Установить максимальное значение принимаемых байт в значение, которое передал пользователь
- Обнулить счетчик количества принятых байт
- Инициировать прием
Смотрим код:
static auto ReadData(std::uint8_t size) {
assert(size < txRxBuffer.size()) ;
const CriticalSection cs;
// Проверим, что мы не находимся в режиме чтения или записи.
// т.е. что предыдущие данные либо приняты либо уже отосланы
if ((status != Status::Write) && (status != Status::Read))
{
Uart::DisableTcInterrupt();
Uart::DisableTxInterrupt();
Uart::DisableTransmit();
bufferIndex = 0U;
bufferSize = size;
//устанавливаем режим приема
status = Status::Read;
Uart::EnableReceive();
Uart::EnableRxInterrupt();
}
Событие OnReceive()
Это событие вызывается модулем UART каждый раз, как был принят байт. Необходимо считать количество принятых байт и как только оно станет равно количеству запрашиваемых пользователем, нужно закончить прием и оповестить подписчиков, что прием закончен.
static void OnReceive()
{
txRxBuffer[bufferIndex] = Uart::ReadByte() ;
bufferIndex ++ ;
if (bufferIndex == bufferSize)
{
status = Status::ReadComplete ;
const auto length = bufferIndex ;
bufferIndex = 0U;
UartDriverReceiveObservers::OnReadComplete(txRxBuffer, bufferIndex) ;
}
}
Весь код драйвера
Код драйвера полностью можно посмотреть под спойлером
#ifndef REGISTERS_UARTDRIVER_HPP
#define REGISTERS_UARTDRIVER_HPP
#include "susudefs.hpp" //for __forceinline
#include "hardwareuarttx.hpp" // for HardwareUartTx
#include "hardwareuarttc.hpp" //for HardwareUartTc
#include "hardwareuartrx.hpp" // for HardwareUartRx
#include <cstring> // for memcpy
#include "criticalsectionconfig.hpp" // for CriticalSection
#include "uartdriverconfig.hpp" // for tBuffer
template<typename UartModule, typename UartDriverTransmitCompleteObservers, typename UartDriverReceiveObservers>
struct UartDriver
{
using Uart = UartModule ;
enum class Status: std::uint8_t
{
None = 0,
Write = 1,
WriteComplete = 2,
Read = 3,
ReadComplete = 4
} ;
static void WriteData(const std::uint8_t *pData, std::uint8_t bytesTosend)
{
assert(bytesTosend < txRxBuffer.size()) ;
const CriticalSection cs;
if ((status != Status::Write) && (status != Status::Read))
{
bufferIndex = 0U;
bufferSize = bytesTosend;
std::memcpy(txRxBuffer.data(), pData, static_cast<std::size_t>(bytesTosend));
Uart::WriteByte(txRxBuffer[bufferIndex]);
bufferIndex++;
status = Status::Write;
Uart::StartTransmit();
//если работает без прерываний, то посылаем прямо тут
if constexpr (!std::is_base_of<UartTxInterruptable, typename Uart::Base>::value)
{
for(; bufferIndex < bytesTosend; ++bufferIndex)
{
while (!Uart::IsDataRegisterEmpty())
{
}
Uart::WriteByte(txRxBuffer[bufferIndex]);
}
while (!Uart::IsTransmitComplete())
{
}
status = Status::WriteComplete ;
UartDriverTransmitCompleteObservers::OnWriteComplete() ;
} else
{
}
}
}
__forceinline static void OnTransmit()
{
if(bufferIndex < bufferSize)
{
Uart::WriteByte(txRxBuffer[bufferIndex]) ;
bufferIndex ++ ;
}
else
{
Uart::EnableTcInterrupt() ;
}
};
static void OnTransmitComplete()
{
bufferIndex = 0U;
bufferSize = 0U;
status = Status::WriteComplete;
Uart::DisableTcInterrupt();
Uart::DisableTxInterrupt() ;
Uart::DisableTransmit();
UartDriverTransmitCompleteObservers::OnWriteComplete() ;
}
static auto ReadData(std::uint8_t size)
{
assert(size < txRxBuffer.size()) ;
const CriticalSection cs;
if ((status != Status::Write) && (status != Status::Read))
{
Uart::DisableTcInterrupt();
Uart::DisableTxInterrupt();
Uart::DisableTransmit();
bufferIndex = 0U;
bufferSize = size;
status = Status::Read;
Uart::EnableRxInterrupt();
Uart::EnableReceive();
}
}
static void OnReceive()
{
txRxBuffer[bufferIndex] = Uart::ReadByte() ;
bufferIndex ++ ;
if (bufferIndex == bufferSize)
{
status = Status::ReadComplete ;
const auto length = bufferIndex ;
bufferIndex = 0 ;
UartDriverReceiveObservers::OnReadComplete(txRxBuffer, static_cast<std::size_t>(length)) ;
}
}
static Status GetStatus()
{
return status ;
}
static void ResetAll()
{
Uart::DisableTcInterrupt();
Uart::DisableTxInterrupt();
Uart::DisableTransmit();
Uart::DisableReceive();
Uart::DisableRxInterrupt() ;
bufferIndex = 0U;
bufferSize = 0U;
status = Status::None;
}
friend UartDriver& operator<<(UartDriver &rOs, const char* pString)
{
WriteData(reinterpret_cast<const std::uint8_t*>(pString), strlen(pString)) ;
return rOs;
}
friend UartDriver& operator<<(UartDriver &rOs, float value)
{
WriteData(reinterpret_cast<const std::uint8_t*>(&value), sizeof(float)) ;
return rOs;
}
private:
inline static tBuffer txRxBuffer = {} ;
inline static std::uint8_t bufferSize = 0U ;
inline static std::uint8_t bufferIndex = 0U ;
inline static Status status = Status::None ;
};
#endif //REGISTERS_UARTDRIVER_HPP
Как этим пользоваться?
Например, мы хотим реализовать очень простенький протокол SomeProtocol
, который всегда принимает 10 байт и отсылает 10 байт. Нулевой байт — это команда, последний — это контрольная сумма. А данных 8 байт, т.е. окончание приема будем определять по количеству принятых байт, если 10, то посылка закончилась. (По хорошему так делать не надо, окончание посылки лучше делать по таймеру, но чтобы не плодить кода, я упростил до такого вот супер пупер протокола)
Все что нам нужно будет сделать это реализовать два метода OnTransmitComplete()
и OnReceiveComplete()
.
template <typename UartDriver>
struct SomeProtocol
{
__forceinline static void OnTransmitComplete()
{
//снова ожидаем приема 10 байт;
Proceed() ;
}
__forceinline static void OnReceiveComplete(tBuffer& buffer, std::size_t length)
{
// Примем завершен, разбираем посылку
assert(length <= buffer.size()) ;
//Надо проверить контрольну сумму, если не совпала скидываем протокол
if (CheckData(buffer))
{
//Команда лежит по 0 индексу буфера. Обрабатываем команду
// вообще хорошо бы тут выйти из прерывания. Т.е. оповестить задачу, что мол
// все пришло, обработай команду... но упростим все и обработаем команду в
// в прерывании.
cmds::ProceedCmd(buffer[0], buffer); // команда заполнит буфер ответом.
//Отсылаем ответ
UartDriver::WriteData(buffer.data(), length) ;
} else
{
UartDriver::ResetAll() ;
}
}
__forceinline static void Proceed()
{
//Запрашиваем чтение по 10 байту.
UartDriver::ReadData(10) ;
}
//контейнер для команд
using cmds = CmdContainer<
Singleton<CmdWriteSomeData>::GetInstance(),
Singleton<CmdReadSomeData>::GetInstance()
> ;
};
// Просто еще подписчик на завершение передачи, например хотим моргнуть светодиодом
struct TestObserver
{
__forceinline static void OnTransmitComplete()
{
Led1::Toggle() ;
}
};
Теперь нужно произвести настройку драйвера -подписать протокол на UartDriver
struct MyUartDriver: UartDriver<
//Это аппаратный модуль UART
HardwareUart,
// Подписываем SomeProtocol и TestObserver на событие OnTransmitComplete()
UartDriverTransmitCompleteObservers<SomeProtocol<MyUartDriver>, TestObserver>,
// Подписываем только SomeProtocol на событие OnReceiveComplete()
UartDriverReceiveCompleteObservers<SomeProtocol<MyUartDriver>>
> { };
using MyProtocol = SomeProtocol<MyUartDriver> ;
Заметьте, можно сделать сколь угодно много подписчиков на завершение приема или передачи. Например, на завершение передачи я подписал два класса TestObserver
и SomeProtocol
, а на завершение приема только один — SomeProtocol
. Также можно настроить драйвер на любой UART модуль.
и теперь можно запускать протокол на работу:
int main()
{
//Запуск стека протокола
MyProtocol::Proceed() ;
while(true) { }
return 1 ;
}
UART модуль
Если вы еще читаете, наверное у вас возник резонный вопрос, что такое HardwareUart
UART модуль и откуда он взялся. Его упрощенная модель выглядит так:
По большому счету — это обертка над аппаратным UART микроконтроллера, в которую через список подключаются 3 дополнительных класса для обработки прерываний:
HardwareUartTx
— класс для обработки прерывания по опустошению регистра данных, содержащий список подписчиков, подписанных на это прерываниеHardwareUartTc
— класс для обработки прерывания по опустошению защелки, содержащий список подписчиков, подписанных на это прерываниеHardwareUartRx
— класс для обработки прерывания по приходу байта, содержащий список подписчиков, подписанных на это прерывание
Обработчики прерывания вызываются из метода HandleInterrupt()
класса HardwareUartBase
, который должен подставляться в таблицу векторов прерываний
template<typename... Modules>
struct InterruptsList
{
__forceinline static void OnInterrupt()
{
//вызываем обработчики прерывания у подписчиков
(Modules::HandleInterrupt(), ...) ;
}
} ;
template<typename UartModule, typename InterruptsList>
struct HardwareUartBase
{
static void HandleInterrupt()
{
//обычно в списке HardwareUartTx, HardwareUartTc, HardwareUartRx и
// здесь вызываются их обработчики
InterruptsList::OnInterrupt() ;
}
...
} ;
template<typename UartModule, typename UartTransmitObservers>
struct HardwareUartTx
{
using Uart = typename UartModule::Uart ;
static void HandleInterrupt()
{
//Проверяем случилось ли прерывание по опустошению регистра данных
if(Uart::SR::TXE::DataRegisterEmpty::IsSet() &&
Uart::CR1::TXEIE::InterruptWhenTXE::IsSet())
{
UartTransmitObservers::OnTxDataRegEmpty();
}
}
};
template<typename UartModule, typename UartReceiveObservers>
struct HardwareUartRx
{
using Uart = typename UartModule::Uart ;
static void HandleInterrupt()
{
//Проверяем случилось ли прерывание по приему байта
if(Uart::CR1::RXNEIE::InterruptWhenRXNE::IsSet() &&
Uart::SR::RXNE::DataRecieved::IsSet() )
{
UartReceiveObservers::OnRxData();
}
}
};
template<typename UartModule, typename UartTransmitCompleteObservers>
struct HardwareUartTc
{
using Uart = typename UartModule::Uart ;
static void HandleInterrupt()
{
//Проверяем случилось ли прерывание по опустошению защелки
if(Uart::SR::TC::TransmitionComplete::IsSet() &&
Uart::CR1::TCIE::InterruptWhenTC::IsSet())
{
UartTransmitCompleteObservers::OnComplete();
Uart::SR::TC::TransmitionNotComplete::Set() ;
}
}
};
#ifndef REGISTERS_UART_HPP
#define REGISTERS_UART_HPP
#include "susudefs.hpp" //for __forceinline
#include <array> // for std::array
#include <cassert> // for assert
#include <cstring> // for memcpy
#include "criticalsectionguard.hpp" //for criticalsectionguard
template<typename UartModule, typename InterruptsList>
struct HardwareUartBase
{
using Uart = UartModule ;
using Base = Interface ;
__forceinline static void EnableTransmit()
{
UartModule::CR1::TE::Enable::Set();
};
static void DisableTransmit()
{
UartModule::CR1::TE::Disable::Set();
};
static void EnableReceive()
{
UartModule::CR1::RE::Enable::Set();
};
static void DisableReceive()
{
UartModule::CR1::RE::Disable::Set();
};
static void EnableTxInterrupt()
{
UartModule::CR1::TXEIE::InterruptWhenTXE::Set();
};
static void EnableRxInterrupt()
{
UartModule::CR1::RXNEIE::InterruptWhenRXNE::Set();
};
static void DisableRxInterrupt()
{
UartModule::CR1::RXNEIE::InterruptInhibited::Set();
};
static void DisableTxInterrupt()
{
UartModule::CR1::TXEIE::InterruptInhibited::Set();
};
static void EnableTcInterrupt()
{
UartModule::CR1::TCIE::InterruptWhenTC::Set();
};
static void DisableTcInterrupt()
{
UartModule::CR1::TCIE::InterruptInhibited::Set();
};
static void HandleInterrupt()
{
InterruptsList::OnInterrupt() ;
}
__forceinline static void ClearStatus()
{
UartModule::SR::Write(0);
}
static void WriteByte(std::uint8_t chByte)
{
UartModule::DR::Write(static_cast<std::uint32_t>(chByte)) ;
}
static std::uint8_t ReadByte()
{
return static_cast<std::uint8_t>(UartModule::DR::Get()) ;
}
static void StartTransmit()
{
EnableTransmit() ;
if constexpr (std::is_base_of<UartTxInterruptable, Interface>::value)
{
EnableTxInterrupt() ;
}
}
static bool IsDataRegisterEmpty()
{
return UartModule::SR::TXE::DataRegisterEmpty::IsSet() ;
}
static bool IsTransmitComplete()
{
return UartModule::SR::TC::TransmitionComplete::IsSet() ;
}
};
#endif //REGISTERS_UART_HPP
Настройка UART и подписчиков будет выглядеть так:
struct HardwareUart : HardwareUartBase<
USART2,
InterruptsList<
//Хотим использовать прерывание по опустошению регистра данных
HardwareUartTx<HardwareUart,
//Подписываем драйвер на прерывание по опустошению регистра данных Uart
UartTransmitObservers<MyUartDriver>>,
//Хотим использовать прерывание по опустошению защелки
HardwareUartTc<HardwareUart,
//Подписываем драйвер на прерывание по опустошению защелки
UartTransmitCompleteObservers<MyUartDriver>>,
//Хотим использовать прерывание по приему байта данных
HardwareUartRx<HardwareUart,
//Подписываем драйвер на прерывание по приему байта данных
UartReceiveObservers<MyUartDriver>>
>
>
{
};
Теперь легко можно подписывать на разные прерывания разных клиентов. Количество клиентов практически не ограничено, в данном случае мы подписали на все три прерывания UartDriver, но могли бы еще что-нибудь подписать. Также можно подключиться к любому UART, в примере подключено к USART2.
Затем настроенный Uart модуль можно передавать в драйвер, как было показано чуть выше. Драйвер же также в свою очередь должен подписаться на события от Uart модуля.
struct MyUartDriver: UartDriver<
//Это аппаратный модуль UART
HardwareUart,
// Подписываем SomeProtocol и TestObserver на событие OnTransmitComplete()
UartDriverTransmitCompleteObservers<SomeProtocol<MyUartDriver>, TestObserver>,
// Подписываем только SomeProtocol на событие OnReceiveComplete()
UartDriverReceiveCompleteObservers<SomeProtocol<MyUartDriver>>
> { };
using MyProtocol = SomeProtocol<MyUartDriver> ;
Заключение
В общем и целом перед поставленной задачей товарищи студенты справились.
При включенном принудительном inline, весь код проекта занимает 1600 байт без оптимизации. Сделаны две команды: запись и чтение 12 параметров во Flash микроконтроллера. В проекте, можно настроить драйвер, чтобы он работал в синхронном режиме, можно в асинхронном. Можно подключать любое количество подписчиков, к любому UART. Собственно все задачи были выполнены и работа тянет на отлично :)
Да, затрачено на кодирование было 2 целых дня (думаю часов 20 в сумме). Полагаю, из-за того, что архитектура мною уже была разработана на практических занятиях, а реализация — это дело уже не таке сложное.
Код был проверен мною в PVS-Studio. Изначально были найдены 4 предупреждения.
Все предупреждения уже не помню, отчет не сохранил: но точно были V2516 и V519, ошибки не критичные, но точно так делать не надо было :) Все исправлено, кроме V2516, он указывает на код, который используется для отладки, там поставил FIXME:.
Можно посмотреть код рабочего примера на IAR8.40.2 здесь, никаких доп библиотек не нужно, но нужна плата Nucleo-F411RE, сам проект лежит в папке FlashUart\DinamicStand.
Основной код драйвера и Uart модуля лежит на гитхабе .
PS: Спасибо fougasse, apro, gleb_l и besitzeruf за дельные замечания
fougasse
ReadData и критические секции — разве оно так будет работать (я про выделение на стеке)?
lamerok Автор
А почему нет, внутри фигурных скобок, области видимости, за ними объект уничтожается с вызовом деструктора.
apro
Потому что объект синхронизации должен с кем-то разделяться иначе его использование бессмысленно. А здесь видно что каждый вызов использует свой уникальный объект, то есть бессмыслица какая-то. Если только конечно внутри конструктора CriticalSection нет обращение к какой-то глобальной сущности.
lamerok Автор
Соглашусь, справедливости ради, скорее всего критическая здесь была добавлена чтобы обеспечить атомарности того, что в скобках (не совсем правда понятно зачем). Логично было бы вызвать его до проверки, тогда хоть какой-то смысл будет, поскольку операция ставит статус Read. Поправлю.
fougasse
С кем синхронизация происходит в вашем случае?
lamerok Автор
По идее тут нужна защита для установки статуса и его проверки, чтобы, если вдруг (чисто теоретический случай) в одном потоке вызывается
ReadData()
и делается проверка статуса, драйвер находится не в режиме Read или Write, здесь другой поток высоко-приоритетный тоже вызываетReadData()
и тоже делает проверку и тоже определяет, что находится не в режиме Read или Write, начинает менять ресурсы (BufferSize, BufferIndex и так далее)… потом возвращаемся в другой поток, и тот также меняет их, но уже под себя. Поэтому нужно, чтобы установка статуса и его проверка были атомарными.Это конечно так себе случай, но все сделано верно, тем более, что она особо есть не просит.
fougasse
Я еще раз спрошу, с кем вы синхронизируетесь локальной переменной в функции?
Вы, в принципе, понимаете, что так в многопоточной системе работать не будет?
lamerok Автор
С другим потоком, если ему вдруг вздумается вызывать
ReadData()
, он недолжен снова инициировать чтение своим запросом наReadData()
, если запросReadData()
уже инициируется другим потоком.Да понимаю.
fougasse
Если понимаете, зачем тогда писать?
Очень странный, как мне кажется, подход к обучению.
lamerok Автор
Писать что?
lamerok Автор
В предыдущем комментарии, "Да понимаю", следует воспринимать, как "не понял вопроса", почему не будет работать?
Я действительно не понял вопроса...
EighthMayer
Рискну предположить что, во-первых, автор неправильно понял вопрос, а во вторых что, упрощенно говоря, класс критической секции в конструкторе (при создании объекта) выключает прерывания, а в деструкторе (при выходе объекта из области видимости) включает. Если так, то что именно будет не работать в многопоточной системе?
lamerok Автор
Так и есть… практически
в конструкторе
В деструкторе