Pull to refresh
-22
0
Андрей Батищев @DX168B

Программист

Send message

Имею такой. Оригинальный, исправный, с руссифицированной оригинальной клавиатурой. Иногда использую в некоторых целях. Например, компилирую прямо на нем некоторый свой код. Да и лежит, как запасной, на всякий случай. Много раз выручал в этой роли.

У меня есть pet проект, где тоже планируется создание сущностей C++ в Lua. (Это большой высокопроизводительный дашборд для мониторинга огромного количества датчиков и имеющий возможность расширения функционала посредством Lua и в будущем ещё и Python). Для объектов, которые создаются и уничтожаются в Lua все просто. Создаём в C++ класс, который планируем экспортировать в Lua. Пишем фабрику объектов этого класса в Lua - по сути создаём таблицу с именем класса и помещаем в неё метод new, который создаёт объект класса, создаёт в Lua таблицу, в которой в поле с именем класса и типом lightuserdata помещает указатель на созданный объект. Далее в неё помещаются методы класса (обёртки над методами, которые извлекают сначала указатель с вышеупомянутого поля, проверяют аргументы и вызывают соответствующие методы класса с передачей аргументов и возвращают значение в Lua, если нужно). После этого, за метаметодом _gc закрепляется функция удаления объекта. В этом случае жизнь объекта полностью контролируется скриптом. Причем, даже если потерять ссылку на объект в Lua, то сборщик мусора корректно удаляет объект из памяти, дёрнув метаметод _gc() И это прекрасно работает.

В статье предлагается назначать объектам идентификаторы и хранить их в каком-нибудь "реестре". В принципе, в моем проекте такой реестр есть, но вот как организовать обращение к объектам, которые могут создаваться и удаляться не только скриптом, но и по иным событиям, идей не приходило. В принципе, статья меня уже натолкнула на идею, за что автору спасибо.

Бизнесу необходимо зарабатывание денег, потому что без них он существовать не будет. Другое дело - цель создания бизнеса. Кто-то создает только ради сколачивания бабла (чем больше, тем лучше, высасывая все соки с бизнеса), а у кого-то есть идея и бизнес служит не только целью заработка денег, но и заработку репутации, желанию сделать уникальный качественный продукт или услугу и т.п.

volatile - это в первую очередь сообщение компилятору, что содержимое переменной в любой момент может измениться по внешним причинам. Например, если это регистр, связанный с оборудованием, или элемент буфера DMA, или её может изменить другой процесс (если программа работает в среде операционной системы). Соответственно, оптимизация по отношению к ней будет иной.

Особенно это хорошо проявляется на процессорах RISC с гарвардской архитектурой (ARM, RISC-V и т.п.).

Допустим, мы объявили регистр ввода-вывода обычной переменной и каким-то образом назначили ей корректный адрес размещения в памяти, отраженный на физический регистр. А теперь нам нужно дрыгнуть один раз выходом операцией "чтение-модификация-запись". То есть, прочитать регистр, изменить один бит, записать, потом снова изменить, потом снова записать.

int reg_0;
reg_0 |= 0x01;
reg_0 &= 0xFE;

Что произойдет? Процессор загрузит содержимое регистра в регистр общего назначения. Потом изменит бит, потом снова изменит и запишет обратно. На выходе импульса не будет.

LDR R0, REG_0 ;Чтение регистра
ORI R0, 0x01	;Изменение содержимого
ANDI R0, 0xFE	;Изменение содержимого
STR REG_0, R0	;Запись изменений

При жесткой оптимизации, компилятор эти операции вообще выбросит, так как после них содержимое переменной не изменяется, и промежуточное измененное содержимое нигде не используется.

Если мы объявим переменную-регистр как volatile, то компилятор будет производить "чтение-модификация-запись" в физический регистр при каждом обращении к нему и операции эти не выбросит. Тогда на выходе мы получим желанный импульс.

LDR R0, REG_0 ;Чтение регистра
ORI R0, 0x01	;Изменение содержимого
STR REG_0, R0	;Запись изменений

LDR R0, REG_0 ;Чтение регистра
ANDI R0, 0xFE	;Изменение содержимого
STR REG_0, R0	;Запись изменений

Это же касается и операций чтения. При каждом обращении в Сишной программе к этому регистру, будут всегда производиться чтения с физического регистра.

Короче, выбросить всё, что активно используется в embedded. Не, так не пойдет.

На первом фото я узнал одного человека. Конкретнее, руководителя отдела R&D :)

Вам могу пожелать только успеха. Электронная промышленность - сложная и затратная (как экономически, так и по времени) отрасль. Особенно, если разрабатывать технологии с нуля.

Извиняюсь за ответ на очень старый коммент, но я соглашусь.

У меня STM32F407 очень тяжело справляется с преобразованием потока данных 24 бит 48k smp/s сначала в 24 бит 384k smp/s, потом в 9 бит 384k посредством дельта-сигма модуляции. А на МК возложена еще и куча других задач. Потому эту задачу я отвел для FPGA. Даже дешевый вариант Cyclone II (EP2C5T144C8N) справляется с преобразованием и дальнейшей обработкой этого потока просто на ура. По барабану даже на то, что там КИХ фильтр высокого порядка для апсемплинга.

Собственно, с этой задачи и началось мое освоение ПЛИС. Освоил быстро, благо с логической схемотехникой был знаком даже до освоения микроконтроллеров. В качестве языка выбрал Verilog. Писанины на нем меньше, чем на VHDL. Тот же КИХ писал сам. В железе заработал сразу, так как при разработке следовал стандарту: Строгая синхронщина, управляемая конечным автоматом. И так во всех функциональных модулях. Проект сложный, в нем я сразу столкнулся со всем многообразием методов отстрелить себе ногу. Столкнулся даже с синхронизацией между разными тактовыми доменами. Но все построил так, что Таймквесту достаточно было только сказать, какие и откуда у меня тактовые частоты и больше ничего ему не потребовалось.

Сделал Ваш вариант. Играет в принципе нормально, но чем больше каскадов ставлю последовательно (увеличиваю порядок модулятора), тем громче становится фоновый шум. По идее, он должен был смещаться за пределы слышимого диапазона, но пока что-то идет не так. Может где-то напортачил. Привожу главный кусок реализации модулятора 3 порядка на Verilog

Hidden text
	// Data lines
	wire[23:0] leftQuantizer;
	wire[23:0] rightQuantizer;
	
	// Delta 0
	wire[23:0] left_delta_0 = leftNsIn - leftQuantizer;
	wire[23:0] right_delta_0 = rightNsIn - rightQuantizer;
	
	// Sigma 0
	reg[23:0] left_sigma_0 = 0;
	reg[23:0] right_sigma_0 = 0;
	
	always @(posedge clock or posedge reset) begin
		if(reset == 1'b1) begin
			left_sigma_0 <= 0;
			right_sigma_0 <= 0;
		end else begin
			if(integr_enable == 1'b1) begin
				left_sigma_0 <= left_sigma_0 + left_delta_0;
				right_sigma_0 <= right_sigma_0 + right_delta_0;
			end
		end
	end
	
	// Delta 1
	wire[23:0] left_delta_1 = left_sigma_0 - leftQuantizer;
	wire[23:0] right_delta_1 = right_sigma_0 - rightQuantizer;
	
	// Sigma 1
	reg[23:0] left_sigma_1 = 0;
	reg[23:0] right_sigma_1 = 0;
	
	always @(posedge clock or posedge reset) begin
		if(reset == 1'b1) begin
			left_sigma_1 <= 0;
			right_sigma_1 <= 0;
		end else begin
			if(integr1_enable == 1'b1) begin
				left_sigma_1 <= left_sigma_1 + left_delta_1;
				right_sigma_1 <= right_sigma_1 + right_delta_1;
			end
		end
	end
	
	// Delta 2
	wire[23:0] left_delta_2 = left_sigma_1 - leftQuantizer;
	wire[23:0] right_delta_2 = right_sigma_1 - rightQuantizer;
	
	// Sigma 2
	reg[23:0] left_sigma_2 = 0;
	reg[23:0] right_sigma_2 = 0;
	
	always @(posedge clock or posedge reset) begin
		if(reset == 1'b1) begin
			left_sigma_2 <= 0;
			right_sigma_2 <= 0;
		end else begin
			if(integr2_enable == 1'b1) begin
				left_sigma_2 <= left_sigma_2 + left_delta_2;
				right_sigma_2 <= right_sigma_2 + right_delta_2;
			end
		end
	end
	
	assign leftQuantizer = {left_sigma_2[23:15], 15'd0};
	assign rightQuantizer = {right_sigma_2[23:15], 15'd0};

Управляется просто:

По приходу фронта на вход "data_valid", пришедшая выборка из апсемплера фиксируется во входных регистрах (в коде не приведены) leftNsIn и rightNsIn. Далее простая машина состояний по очереди раздает импульсы разрешения интеграторам, начиная с нулевого интегратора и заканчивая последним. После фиксирует результат в выходных регистрах.

Реализую похожее на FPGA. Только у меня это будет сразу усилитель D класса. Сильный упор на качество не делаю, но постараюсь сделать как можно качественнее, на сколько хватит ресурсов.

Несущую выбрал 384кГц, на эту частоту больше выбора силовых ключей. При этой частоте, разрядность ШИМ получается равной 9 битам (модулятор при этом просит около 200мГц). Больше сделать проблематично, так как от FPGA EP2C5T144C8N можно получить стабильную работу на частотах не более 260мГц. Другой ПЛИСины у меня пока нет, обкатываюсь на этой. Модулятор работает хорошо как в тестбенчах, так и в железе.

Исходный сигнал - 24 бит, 48кГц. АЦП - PCM1802. Просто потому что нашел ее в виде модуля на Али. До 384кГц довожу оверсемплингом, построенном на самопальном КИХ фильтре на 128 коэффициентов. КИХ тоже работает хорошо. Может работать на тактовой до 60МГц, но его можно еще ускорить, если конвейеризовать некоторые узкие места (в самом умножителе и между его выходом и сумматором. Мне лень было всовывать регистры между ними). Но текущей его производительности хватает и так с головой на 50 МГц. Однако, вместо Дельта-Сигмы я решил попробовать нойзшейпинг. Он точно так же должен сместить шум квантования вправо по спектру.

Из текущих проблем, чтобы впервые запустить это дело и проверить:

  1. Переделать I2S интерфейс. Я его делал первым, попутно осваивая Verilog и технологии FPGA. Потому там сплошная асинхронщина. На выходных реализую синхронный дизайн этого модуля.

  2. Разобраться с расчетом фильтра в петле ОС нойзшейпера. Можно попробовать сделать третьего порядка и вручную поиграться с коэффициентами, чтобы по получившейся импульсной характеристике провести анализ и определиться с методикой расчетов.

Ваш вариант с "многобитной" дельта-сигмой тоже попробую.

Я нечто подобное реализовал давно уже и таскаю с проекта в проект. Единственное, я сделал один буфер для приема, один для передачи и enum, сигнализирующий о текущем состоянии интерфейса:

1. UartState::FREE - Интерфейс свободен, принятых данных нет

2. UartState::RECEIVING - На данный момент, интерфейс принимает данные

3. UartState::TRANSMITTING - На данный момент, интерфейс передает данные

4. UartState::RECEIVED_DATA - Прием данных завершен, в буфере приема есть данные

5. UartState::TRANSMITTED_DATA - Передача данных завершена

6. UartState::ERROR - Сигнализирует об ошибке

Окончание приема определяется таймаутом. Как только начинают поступать байты, интерфейс автоматом переходит в состояние RECEIVING. Как данные перестанут передаваться, по истечению таймаута или если переполнится буфер, переход в RECEIVED_DATA. После обработки принятых данных, нужно вызвать функцию, которая переведет интерфейс в состояние FREE.

С передачей данных все то же самое.

А далее пишется автомат, который будет обслуживать интерфейс и реализует протокол обмена.

Hidden text
//////////////////////////////////////////////////////////////////////////
// Процесс обработки интерфейса
void CUart::Process() 
{
	if(uartState == UartState::RECEIVING) // Если запущен сеанс приема
	{
		if(rxTimer.CheckTimer() == true) // Проверяем таймаут. Если истек, то ...
		{
			if(uartRxCount == 0) // На всякий случай: Если счетчик принятых байтов равен нулю
			{
				uartState = UartState::FREE; // UART свободен
			}
			else
			{
				uartState = UartState::RECEIVED_DATA; // Иначе, выставляем флаг "Есть принятые данные"
				UCSR0B = 0; // Блокируем UART полностью
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////
// Process
void CProto::Process()
{
    // UART process
    CUart::Process();
    
    // If data is received
    if(CUart::GetStatus() == UartState::RECEIVED_DATA)
    {
        ProtoPackHead* pHead = (ProtoPackHead*)rxBuff;

        // Packets to device
        if(pHead->recipientAddress == deviceAddress)
        {
            // Check CRC
            uint8_t rxSize = CUart::GetRxCount() - 1;

            if(rxBuff[rxSize] != Crc8Calc(rxBuff, rxSize))
            {
                // "Bad CRC" response
                dataBuff[0] = PROTO_RESP_BAD_CRC;
                SendResponse(PROTO_CMD_ERR_CODE, 1);
            }
            else
            {
                // Packet is good
                ProcessUnicastPacket(pHead);
            }
        }
        // Broadcast packets
        else if(pHead->recipientAddress == PROTO_BROADCAST_ADDRESS)
        {
            // Check CRC
            uint8_t rxSize = CUart::GetRxCount() - 1;

            if(rxBuff[rxSize] == Crc8Calc(rxBuff, rxSize))
            {
                // Packet is good
                ProcessBroadcastPacket(pHead);
            }

            CUart::ResetState();
        }
        // Ignore unrecognized packets
        else
        {
            CUart::ResetState();
        }
        
    }

    // If data is transmitted
    if(CUart::GetStatus() == UartState::TRANSMITTED_DATA)
    {
        CUart::ResetState();
    }
}

В основном цикле постоянно вызываем метод CProto::Process(); и он уже сам делает всю "магию" обмена по конкретному протоколу. Главное, чтобы главный цикл постоянно "крутился". Для этого нужно всю программу продумывать так, чтобы она нигде не висела в ожидании чего-то. Реализовать всю программу по принципу конечного автомата. Или разбить на задачи, а уже задачи реализовать в виде автоматов. Разбить алгоритм задачи, где есть места, требующие ожиданий чего-то, на этапы выполнения. Типа, "Этап 1" - делаем что-то, что потом требует ожидания и переключаем состояние на "Этап 2". Выходим из автомата и при следующем обороте цикла попадаем в "Этап 2", где проверяем, не выполнилось ли действие или не истек ли таймаут. Если нет, то остаемся на этом же этапе и выходим. Если да, то что-то делаем и переключаемся на другие этапы. Для реализации задержек нужно использовать один из аппаратных таймеров. Таким образом главный цикл всегда в работе, нигде не стопорится и успевает обрабатывать все задачи.

Пример автомата, обслуживающего дисплей HD44780, подключенный к МК через I2C расширитель. (между прочим, на той же шине сидит еще и чип RTC, что не мешает им уживаться вместе, так как у драйвера I2C тоже есть флаги состояний)

Hidden text
///////////////////////////////////////////////////////////////////////
// Display update process
bool CHD44780::Process()
{
    // Stage 1 - send upstring address command
    if((procState == DispProcState::FREE) && (CI2Cbus::GetState() == I2Cstate::FREE))
    {
        dispBuffIndex = 0;
        WriteCmd(HD44780_CMD_DDRAM | HD44780_ADDR_1STR); // Set DDRAM pointer to up string
        procState = DispProcState::DDRAM_CMD_WAIT;
        return false;
    }

    // Stage 2 - is command was sended, then start command delay
    if(procState == DispProcState::DDRAM_CMD_WAIT)
    {
        if(CI2Cbus::IsAvailableAndNotReset() == true)
        {
            // Set timer for command delay
            cmdTimer.SetTimer(HD44780_COMMANDS_DELAY_MS); // delay
            procState = DispProcState::DDRAM_CMD_DELAY;
            return false;
        }
        else
        {
            // Wait for command send
            procState = DispProcState::DDRAM_CMD_WAIT;
            return false;
        }
        
    }

    // Stage 3 - wait command delay
    if(procState == DispProcState::DDRAM_CMD_DELAY)
    {
        if(cmdTimer.CheckTimer() == true)
        {
            // Delay expired, send display buffer
            procState = DispProcState::WRITE_BUFFER;
            return false;
        }
        else
        {
            // Delay is not expired, wait...
            procState = DispProcState::DDRAM_CMD_DELAY;
            return false;
        }
    }

    // Stage 4 - write data
    if(procState == DispProcState::WRITE_BUFFER)
    {
        WriteData(dispBuff[dispBuffIndex]);
        dispBuffIndex++;
        procState = DispProcState::WRITE_BUFFER_WAIT;
        return false;
    }

    // Stage 5 - write data wait
    if(procState == DispProcState::WRITE_BUFFER_WAIT)
    {
        if(CI2Cbus::IsAvailableAndNotReset() == true)
        {
            if(dispBuffIndex == (HD44780_DISPLAY_BUFFER_SIZE / 2))
            {
                // To down string
                WriteCmd(HD44780_CMD_DDRAM | HD44780_ADDR_2STR); // Set DDRAM pointer to down string
                procState = DispProcState::DDRAM_CMD_WAIT;
                return false;
            }
            else if(dispBuffIndex > (HD44780_DISPLAY_BUFFER_SIZE - 1))
            {
                // Write end
                CI2Cbus::ResetState();
                dispBuffIndex = 0;
                procState = DispProcState::FREE;
                return true;
            }
            else
            {
                // Write next symbol
                procState = DispProcState::WRITE_BUFFER;
                return false;
            }
        }
        else
        {
            procState = DispProcState::WRITE_BUFFER_WAIT;
            return false;
        }
    }

    return true;
}

Одна из главных проблем, на которую жалуются многие блогеры - долгая модерация публикуемого видеоконтента. Полагаю, "Газпром-Медиа" нужно будет вложить в технологии ИИ для автоматической модерации, как это сделано на YouTube, чтобы ускорить процесс модерации. Ведь в нынешней ситуации туда могут публиковать много видеороликов и чем больше их будет, тем сложнее будет их модерировать.

Неплохо.
По учету и хранению. В свое время мы тоже обустраивали такой же отдел разработки. Для хранения компонентов использовали кассетницы, где ячейки были промаркированы координатами и расположение компонента кодировалось специальным кодом в следующем формате:

НомерКассетницы КоординатаПоВертикали КоординатаПоГоризонтали - НомерСекцииВКассете.

Так как кассетницы у нас были вертикальные, то столбцы мы обозначали буквами латинского алфавита (так как их было меньше, да и буква выступает разделителем между номером кассетницы и номером строки в ней), а строки цифрами. Кассеты кассетниц были снабжены съемными перегородками, разбивающими кассету на секции. Номер секции ставился вконце основгого кода через дефис.

Пример кода размещения компонента: 1B3-4

Код говорит, что искомый компонент находится в первой кассетнице, в столбце B, во третьей строке, в четвертой секции. Очень удобно искать.

Списки мы хранили в базе данных MySQL. Для избежания ошибок при ее ведении, были написаны триггеры, которые копировали старые данные в отдельные таблицы при редактировании основной. База настолько развилась, что там даже стали появляться фотографии компонентов и ссылки на компоненты в библиотеках САПР (Altium 14). В одно время мне даже удалось интегрировать эту базу с Альтиумом так, что в BOM даже появлялись коды размещения компонентов, если они у нас есть на "складе". Не спрашивайте как - это было давно (8 лет назад) и я уже не помню всех тонкостей этого шаманства.

Поле по автоматизации и интеграции в этой области просто непаханное. Главное, чтобы были идеи, время и желание.

В комментариях коллеги пишут, что дешевле было бы взять трафаретный принтер. Так то оно так, но если аппарат, помимо нанесения пасты, умеет раскидывать компоненты вплоть до 0402, то почему бы и нет. Он сэкономит кучу времени и в некоторых случаях - денег на содержание нескольких монтажников руками.

Имел неосторожность ответить на политический комментарий и в итоге мою карму слили в унитаз. Хотел было написать цикл статей по железу (своему аудиопроекту на FPGA), но после того случая передумал.

Имею исправный HDD по типу последнего в статье. От КПК HTC Advantage.

Интересно, какой процент в этой статистике составляют работники, работающие чисто в белую?

Любая теория хорошо закрепляется практикой. Порой даже бывает проще освоить материал практикой после небольшого теоретического вступления. Тогда дальнейшая теория легче усваивается и запоминается. Я, к примеру, цифровую логику знал давно, Verilog для старта изучил по трем коротким статьям, взялся пилить свой довольно сложный проект на ПЛИС и по ходу уже осваивал все остальное. Да, я не стал гуру в ПЛИС, но таким методом я продвинулся в этом плане дальше, чем если бы зубрил всю теорию перед тем как прикоснуться к железу.

Ассемблер не пугает. Главное, чтобы мануалы по инструкциям были. В свое время я учил ассемблер для AVR микроконтроллеров. Потом перешёл на Си и С++. И вот однажды мне пришлось на STM32F407 организовать цепочку цифровых обработок одного сигнала и выжать максимум производительности с ядра ARM Cortex-M4. А тут только ассемблер и нужен. Мануал по ассемблеру вышеупомянутого ядра оказался довольно увлекательным. Там очень много интересных инструкций на все случаи жизни. Ну и задачу я свою решил. Не стал писать вставки и ограничился вызовами intrinsic функций библиотеки CMSIS.

Для меня это не простой приемник. Я только один FIR фильтр на ПЛИС рожал две недели. Но и задача была не такой простой. Нужно было сделать фильтр с сотней коэффициентов, затратив минимум ценных ресурсов ПЛИС.

Потенциально виновной. Не следует путать понятия. Они делали свои выводы на основе данных, предоставленных СБУ, которая в первые же дни после инцидента облажалась, выдавая сбой БУК за не свой. Потом почистила, когда появились контраргументы. Кто там курировал это "расследование" - многие прекрасно знают.

Это та самая "офигительная история", которую писали при участии одной из потенциально виновных сторон? Да ладно? Убей меня калиткой, но в правдивость этого расследования я ни за что не поверю только из-за этого одного факта.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity