Как стать автором
Обновить
-22
0
Андрей Батищев @DX168B

Программист

Отправить сообщение

На первом фото я узнал одного человека. Конкретнее, руководителя отдела 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 фильтр на ПЛИС рожал две недели. Но и задача была не такой простой. Нужно было сделать фильтр с сотней коэффициентов, затратив минимум ценных ресурсов ПЛИС.

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

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

То есть - это наказание крымчан за их выбор. И ссать в глаза про "оккупацию" тут не следует.

Я в домашнем роутере (у меня Mikrotik) использую сервис duckdns. Написал скрипт, который срабатывает всякий раз, когда DHCP получает адрес от провайдера и отсыает его сервису. Уже более двух лет небыло никаких проблем, кроме переноса токена на другой аккаунт. И даже эта мелочь небыла связана с самим сервисом.

Опираясь на статью о "тупиковости развития VLIW". А что, собственно мешает превратить одноядерный in-order процессор в многоядерный out-of-order? То есть, упростить VLIW ядро, сделав его упрощенным микроядром, поставить несколько таких в одно ядро и реализовать аппаратный модуль, который уже будет анализировать и распределять широкие команды по ядрам. Ну, типа, как это сделано у Intel, к примеру. В итоге получим процессор, в котором недостатки VLIW будут нивелированы.

У "права на ремонт" есть и свои плюсы и свои минусы.

Я вижу несколько сценариев развития последствий:

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

  2. Цены могут взлететь, качество подняться, чтобы не морочиться с запасами запчастей.

  3. Цены могут взлететь, но производители будут клепать новые модели, с новым функционалом, дизайном и прочим привлекающим потребителя. Запчасти при этом унифицируются, дабы избежать проблем накопления запчастей. Типа, один и тот же двигатель может использоваться в куче моделей кучи производителей.

Грубо говоря, это тип процессора. Их в мире три основных:

  1. CISC

  2. RISC

  3. VLIW

Первый отличается тем, что обладает инструкциями довольно высокой абстрактности. Одна инструкция может выполнить довольно сложную многоэтапную операцию. В качестве примера можно привести MAC инстукции DSP процессоров, где эта инструкция перемножает два значения и результат складывает с аккумулятором или третьим операндом. Два действия в одной инструкции.

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

Третий тип отличается тем, что может иметь инструкции как свойственные для RISC, так и для CISC, но главное отличие его в том, что тут инструкции и их операнды собираются в пачки и скармливаются ядру для их параллельного выполнения.

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность