В процессе обсуждения первой части пришло понимание, что контроллер IDE всё же следует наделить некоторым "умом". Шина USB заточена под поточную передачу данных и поэтому Ping-Pong протоколы на ней откровенно тормозят, приводя к не оптимальному использованию самой шины. Это выливается и в тормоза на шине IDE, поэтому было принято волевое решение создать вторую версию контроллера, более умного, но в то же время не сильно усложнённого, чтобы он позволял как прямое управление шиной так и мог реализовывать базовые протоколы IDE без необходимости обращения к хосту. Если вам всё ещё интересна данная тема - добро пожаловать под кат.


При выборе контроллера преследовалась цель применить что-то, что уже есть в наличии и обязательно уйти от "ногодрыга", у которого есть свои известные проблемы. Выбор пал на STM32F407VGT в корпусе TSOP100 фирмы ST. У этого контроллера в этом корпусе доступен FSMC контроллер на 16 бит, что нам и нужно. А ещё, у него есть SDIO и можно прикрутить дополнительно uSD карту памяти как быстрый и практически безграничный буфер для данных. Так родился концепт второй версии контроллера IDE для изучения устройств IDE/ATAPI а так же для приближения к нашей цели: создать IDE ATAPI эмулятор CD/DVD привода. Вырисовалась вот такая схема:

Типовое включение контроллера и CPLD
Типовое включение контроллера и CPLD
Фото готового устройства, в этот раз никакого ЛУТа, увы...
Фото готового устройства, в этот раз никакого ЛУТа, увы...

CPLD выполняет роль буфера, чтобы усилить сигналы контроллера. Почему не поставить простые согласователи уровня? Потому, что в корпусе TSOP100 у STM32F407 только 1 сигнал FSMC_NE1, а для IDE требуется их 2. Так же, шина данных тут мультиплексирована и 16 младших адресов используют те же ножки что и 16 бит данных. Правда, при этом есть несколько старших адресов, но учитывая что доступен только один сигнал выбора смысла в них нет - всё равно надо делать внешнюю по отношению к контроллеру логику. Поэтому, это будет просто CPLD, которая будет демультиплексировать шину адреса и уже из неё формировать CS1X и CS3X. К тому же, в CPLD будет синхронизатор входящих сигналов, в том числе арбитражный IORDY, который можно напрямую подключить к FSMC_nWAIT. А так же, там можно расположить регистры управления сигналами RESET и CSEL на шине IDE. Проект CPLD достаточно прост:

Код для CPLD
// Шина IDE на STM32F4
// Адрес     Выбор  Чтение   Запись
// 0000 : 0  CS1X  DataPIO   DataPIO
// 0001 : 1  CS1X  Error     Features
// 0010 : 2  CS1X  SecCount  SecCount
// 0011 : 3  CS1X  SecNumber SecNumber
// 0100 : 4  CS1X  CylLow    CylLow
// 0101 : 5  CS1X  CylHigh   CylHigh
// 0110 : 6  CS1X  DevHead   DevHead
// 0111 : 7  CS1X  Status    Command 
// 1000 : 8  ----  DataDMA   DataDMA
// 1001 : 9  ----
// 1010 : A  ----
// 1011 : B  ----
// 1100 : C  ---- CSEL
// 1101 : D  ----
// 1110 : E  CS3X  AltStatus Control
// 1111 : F  ---- RESET
module IDE_STM32(
	// Шина STM32F4
	input		CLK,				// Такты 56МГц
	inout		[15:0]FSMC_AD,		// Мультиплексированная шина адреса и данных
	input		FSMC_NL,			// Защёлка адреса
	input		FSMC_NE1,			// Выбор устройства
	input		FSMC_NOE,			// Строб чтения
	input		FSMC_NWE,			// Строб записи
	output		reg FSMC_NWAIT,		// Сигнал готовности устройства
	output		reg STAT_INTRQ,		// Проброс статуса: INTRQ
	output		reg STAT_DMARQ,		// Проброс статуса: DMARQ
	output		reg STAT_DASP,		// Проброс статуса: DASP
	output		reg STAT_PDIAG,		// Проброс статуса: PDIAG
	// Шина IDE
	inout		[15:0]DD,			// Шина данных IDE
	output		reg [2:0]DA,		// Шина адреса
	output		CS1X,				// Основные регистры
	output		CS3X,				// Дополнительные регистры
	output		DIOR,				// Сигнал чтения
	output		DIOW,				// Сигнал записи
	output		DMACK,				// Сигнал подтверждения DMA
	output		reg RESET,			// Сигнал сброса
	inout		CSEL,				// Сигнал выбора по кабелю
	input		IORDY,				// Сигнал готовности
	input		INTRQ,				// Сигнал запроса прерывания
	input		DMARQ,				// Сигнал запроса DMA
	input		DASP,				//
	input		PDIAG,				//
	// Лампочка
	output		reg DRIVE			// Лампочка привода
);

// Шины
assign CSEL = (rCSEL) ? 1'b0 : 1'bZ;
assign DD[15:0]  = (~FSMC_NE1 & ~FSMC_NWE) ? FSMC_AD[15:0] : 16'hZZ;
assign FSMC_AD[15:0] = (~FSMC_NE1 & ~FSMC_NOE) ? DD[15:0] : 8'hZZ;

// Переменные
reg BANK;							// Банк адресации
reg rCSEL;
wire REG_CSEL;
wire REG_RESET;

// Комбинаторика
assign REG_CSEL  = ~FSMC_NE1 & BANK & DA[2] & ~DA[1] & ~DA[0];
assign REG_RESET = ~FSMC_NE1 & BANK & DA[2] & DA[1] & DA[0];
assign CS1X = ~(~FSMC_NE1 & ~BANK);
assign CS3X = ~(~FSMC_NE1 & BANK & DA[2] & DA[1] & ~DA[0]);
assign DMACK = ~(DMARQ & ~FSMC_NE1 & BANK & ~DA[2] & ~DA[1] & ~DA[0]);
assign DIOR = ~(~FSMC_NE1 & ~FSMC_NOE & (~BANK | (~(DA[2] ^ DA[1]) & ~DA[0])));
assign DIOW = ~(~FSMC_NE1 & ~FSMC_NWE & (~BANK | (~(DA[2] ^ DA[1]) & ~DA[0])));

// Синхронизация адреса
always @(posedge FSMC_NL) begin
	// Сохраняем адрес
	{BANK,DA[2:0]} <= FSMC_AD[3:0];
end

// Синхронная логика
always @(posedge CLK) begin
	// Лампочка
	DRIVE <= DASP;
	// Статусы
	{STAT_PDIAG,STAT_DASP,STAT_DMARQ,STAT_INTRQ} <= {PDIAG,DASP,DMARQ,INTRQ};
	// Синхронизируем IORDY
	FSMC_NWAIT <= REG_CSEL | REG_RESET | FSMC_NE1 | IORDY;
	// Сохраняем CSEL
	if (REG_CSEL & ~FSMC_NWE) rCSEL <= FSMC_AD[0];
	// Сохраняем RESET
	if (REG_RESET & ~FSMC_NWE) RESET <= ~FSMC_AD[0];
end

// Выход
endmodule

Как видно из кода CPLD реализует 16 адресных ячеек на 16 бит каждая, которые доступны и на чтение и на запись. Первые 8 реализуют 8 ячеек с активацией сигнала CS1X на шине IDE. Вторые 8 разделены на 3 секции:

  • Первая секция это доступ к регистру данных (ADR=0) IDE, но с использованием сигнала DMACK (при наличии сигнала DMARQ). Это позволяет имитировать DMA транзакцию на шине.

  • Вторая секция это регистр альтернативного статуса, который вызывается при ADR=6 и CS3X. Альтернативный статус используется для чтения статуса устройства без сброса запроса прерывания и DMA.

  • Третья секция это внутренние ресурсы CPLD. Обращение к ним не вызывает сигналов строба чтения или записи данных на шине IDE. Используется для управления сигналами RESET и CSEL.

Таким образом, CPLD просто согласующий мост между FSMC и шиной IDE. Может показаться, что требуется согласование уровней между ногами FSMC и CPLD, однако букварь на чип нам говорит, что у него все ноги типа FT.

Выборка из букваря

Подключение остальной периферии (USB и SDIO) сделано типично по букварю от ST и интереса не вызывает.

Переходим к программной части. Как уже было сказано выше, USB будет использовать для CLI. Поэтому, будет использоваться обычный драйвер ST vCOM а в качестве программы - любой терминал, умеющий работать с COM портом. Всё остальное будет запрограммировано в контроллер.

Доступ к шине IDE будет через FSMC контроллер, который отражается прямо в память ядра и его ресурсы доступны обычными командами чтения и записи LDR/STR. При этом тайминг доступа настраивается в самом контроллере и обращение получается атомарным. Отсутствие атомарности главный недостаток "ногодрыга". Настройка контроллера предельно простая:

	__HAL_RCC_FMCEN_CLK_ENABLE();
	FSMC_Bank1->BTCR[ 0 ] = /*| FSMC_BCR1_ASYNCWAIT | FSMC_BCR1_WAITEN |*/ FSMC_BCR1_WREN /*| FSMC_BCR1_WAITCFG*/ | 0x80 | FSMC_BCR1_FACCEN | FSMC_BCR1_MWID_0 | FSMC_BCR1_MTYP_1 | FSMC_BCR1_MUXEN | FSMC_BCR1_MBKEN;
	// Тайминги чтения
	FSMC_Bank1->BTCR[ 1 ] = FSMC_BTR1_BUSTURN_2 | FSMC_BTR1_DATAST_5 | FSMC_BTR1_DATAST_4 | FSMC_BTR1_DATAST_3 | FSMC_BTR2_ADDHLD_1 | FSMC_BTR2_ADDSET_3;
	// Тайминги записи
	FSMC_Bank1E->BWTR[ 0 ] = FSMC_BWTR1_BUSTURN_2 | FSMC_BWTR1_DATAST_5 | FSMC_BWTR1_DATAST_4 | FSMC_BWTR1_DATAST_3 | FSMC_BWTR1_ADDHLD_1 | FSMC_BWTR1_ADDSET_3;

Здесь временно отключен сигнал FSMC_nWAIT, он будет задействован позже. А время доступа настроено на 340 нс и для чтения и для записи. Этого должно хватать для большинства приводов, как старых так и новых. Адрес у FSMC_NE1 равен 0x60000000, поэтому объявляем структуру доступа к регистрам и прикручиваем её к этому адресу:

// Структура порта IDE
typedef struct
{	// CS1X
	__IO uint16_t	DATA_PIO;		// Регистр 0: DATA, режим PIO, 16 бит
	__IO uint16_t	FEATURE;		// Регистр 1: STATUS / FEATURES, 8 бит
	__IO uint16_t	SEC_COUNT;		// Регистр 2: SECTOR COUNT, 8 бит
	__IO uint16_t	SEC_NUMBER;		// Регистр 3: SECTOR NUMBER, 8 бит
	__IO uint16_t	CYL_LOW;		// Регистр 4: CYLINDER LOW, 8 бит
	__IO uint16_t	CYL_HIGH;		// Регистр 5: CYLINDER HIGH, 8 бит
	__IO uint16_t	CONTROL;		// Регистр 6: CONTROL/DEVICE&HEAD, 8 бит
	__IO uint16_t	COMMAND;		// Регистр 7: STATUS / COMMAND, 8 бит
	// DMACK
	__IO uint16_t	DATA_DMA;		// Регистр 0: DATA, режим DMA, 16 бит
	// Резерв
	__IO uint16_t	RES0[ 3 ];		// Резерв
	// Управление сигналом CSEL
	__IO uint16_t	CSEL;			// Регистр управления сигналом CSEL
	// Резерв
	__IO uint16_t	RES1[ 1 ];		// Резерв
	// CS3X
	__IO uint16_t	ALT_STATUS;		// Регистр 6: ALT STATUS, 8 бит
	// Управление сигналом RESET
	__IO uint16_t	RESET;			// Регистр управления сигналом RESET
} tIDE;

#define IDE_BASE_ADR				(uint32_t) 0x60000000
#define IDE							((tIDE *) IDE_BASE_ADR)

Теперь для доступа к нужным регистрам достаточно написать, например, IDE->DATA_PIO, прямо как со стандартными структурами у ST. Управление сбросом идёт через IDE->RESET, куда нужно записать 0x0001 для активации сброса и 0x0000 для деактивации его. Так что сброс устройства происходит такой последовательностью:

IDE->RESET = 0xFFFF;
HAL_Delay( 500 );
IDE->RESET = 0x0000;
HAL_Delay( 3000 );

Второе ожидание на 3 секунды обусловлено тем, что устройству необходимо время, прежде чем оно будет способно адекватно принимать команды. В PC это достигается тем, что аппаратный сброс происходит вместе со всем PC и пока происходит POST и прочие процедуры, этого времени хватает приводам проинициализироваться. А у нас сброс может поступить в любой момент, поэтому необходимо выжидать.

Результат выполнения команды сброса в CLI
Результат выполнения команды сброса в CLI

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

  • 0xA1 - IDENTIFY_ATAPI_DEVICE

  • 0xA0 - PACKET

Остальные команды шины IDE не замечены, даже в режиме DMA. Собственно, так и должно быть, ведь мы разбираем ATAPI привод, а он использует пакетные SCSI команды. Сами пакетные команды описаны в других стандартах, например, SCSI-3 и MMC-3. Буквари на них есть в интернете, к ним мы вернёмся когда дойдём до полноценного управления устройством и, собственно, когда начнём строить эмулятор. На данный момент, речь о шине IDE, которая хостит ATAPI. И начнём мы с команды 0xA1 - IDENTIFY_ATAPI_DEVICE. Для её активации следует заполнить регистры IDE следующим образом:

Как видно, имеет значение только бит DEV и сама команда
Как видно, имеет значение только бит DEV и сама команда

Мы же помним тот момент, что на одном кабеле IDE может быть 2 устройства, один в режиме MASTER/SINGLE а второй в режиме SLAVE. Вот именно бит DEV в регистре DEVICE/HEAD и указывает, к какому устройству в данный момент идёт обращение. Этот бит слушают оба устройства, но откликается лишь то, у которого настройка джампера MASTER/SLAVE соответствует. DEV=0 соответствует выбору MASTER. PC записывает сюда 0xA0, устанавливая оба бита "obs" (obsolete - устаревшие) в 1. Поступим так же. Эпюры выполнения команды:

Вся транзакция занимает порядка 356 микросекунд
Вся транзакция занимает порядка 356 микросекунд

Обнаружение устройства немного более сложная задача, чем просто подать команду IDENTIFY_ATAPI_DEVICE. Для начала следует убедиться, что устройство существует физически. Для этого делается 2 парных транзакции записи и чтения тестового значения в регистр SEC_COUNT (счётчик секторов), который доступен всегда как ячейка памяти. Затем, если регистр существует (возвращает записанные данные) делается чтение статуса STATUS для сброса всех запросов, которые могут быть в устройстве. И после чего посылается команда IDENTIFY_ATAPI_DEVICE последовательной записью 0xA0 в регистр DEVICE/HEAD и 0xA1 в регистр COMMAND/STATUS.

Установка команды видна как две подряд идущих записи максимально близко друг к другу в правой части графика
Установка команды видна как две подряд идущих записи максимально близко друг к другу в правой части графика

После установки команды нужно вычитывать статус до тех пор, пока устройство не снимет бит статуса BSY и не установит бит статуса DRQ. Вот карта битов для ожидания результата команды:

Все значащие биты статуса
Все значащие биты статуса

Установка других флагов не допускается. Собственно, установка DF (DRIVE_FAULT) или ERR (ERROR) говорит об неисправности устройства или его прошивки, потому что команда IDENTIFY_ATAPI_DEVICE является внутренней и не использует механику с носителем. А вот так выглядит вычитка ответа, которая составляет 256 слов (512 байт):

Плотно идущие транзакции чтения данных - недостижимая вещь для первой версии контроллера!
Плотно идущие транзакции чтения данных - недостижимая вещь для первой версии контроллера!
Результат команды в CLI с частичным парсингом ответа
Результат команды в CLI с частичным парсингом ответа
Код процедуры, которая обнаруживает подключённое устройство
// Обнаружение привода
FunctionalState IDE_DriveDetect( uint8_t *pBuf, uint32_t Size )
{	// Локальные переменные
	FunctionalState Res;
	tATAPI_ID *pATAPI_ID;
	uint32_t Cnt;
	uint16_t *PBuf,Data;
	// Инит
	Res = DISABLE; Size /= 2; PBuf = (uint16_t *)pBuf;
	pATAPI_ID = (tATAPI_ID *)pBuf;
	// Начинаем проверку привода
	while ( Size > 0 )
	{	// Пробуем запись 0x0A в регистр сектора
		IDE->SEC_COUNT = 0x000A;
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Пробуем считать записанное
		if ( (IDE->SEC_COUNT & 0x00FF) != 0x000A ) { break; }
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Пробуем запись 0x05 в регистр сектора
		IDE->SEC_COUNT = 0x0005;
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Пробуем считать записанное
		if ( (IDE->SEC_COUNT & 0x00FF) != 0x0005 ) { break; }
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Пробуем запись 0xA0 в регистр управления
		IDE->CONTROL = 0x00A0;
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Пробуем считать записанное
		if ( (IDE->CONTROL & 0x00FF) != 0x00A0 ) { break; }
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Считываем статус
		if ( (IDE->COMMAND & 0x0081) != 0x0000 ) { break; }
		// Задержка 2 мкс
		IDE_uDelay( 1 );
		// Устанавливаем команду чтения ID
		IDE->CONTROL = 0x00A0;
		IDE->COMMAND = 0x00A1;
		// Ожидаем устройство
		Cnt = 0; while ( ((IDE->COMMAND & STATUS_BSY) != 0x0000) && (Cnt < 1000) ) { IDE_uDelay( 1 ); Cnt++; }
		// Произошёл запрос или снятие BUSY?
		if ( Cnt > 0 )
		{	// Ожидаем данные
			while ( (IDE->COMMAND & STATUS_DRDY) == 0x0000 ) { __NOP(); }
			// Считываем данные
			Cnt = 0;
			while ( (Cnt < 256) && (Cnt < Size) )
			{	// Считываем данные
				Data = IDE->DATA_PIO;
				// Меняем местами байты
				if ( ((Cnt >= 10) && (Cnt <= 19)) || ((Cnt >= 23) && (Cnt <= 26)) || ((Cnt >= 27) && (Cnt <= 46)) )
				{	// Если мы на строках - обмениваем байты
					Data = (Data >> 8) | (Data << 8);
				}
				// Сохраняем данные
				*(PBuf) = Data;
				// Следующее слово
				Cnt++; PBuf++;
			}
		}
		else
		{	// Выход с ошибкой
			break;
		}
		// Всё пучком
		Res = ENABLE;
		// Выход
		break;
	}
	// Выход
	return Res;
}

Обратите внимание, что для некоторых полей ответа происходит обмен старшего и младшего байта в слове. Это обусловлено тем, что строки тут хранятся в big endian:

Вот мы и добрались до команды PACKET. Её карта тоже достаточно проста:

Флагов то уже побольше, да?
Флагов то уже побольше, да?

Так как это уже полноценная команда, то и параметров у неё побольше. Биты OVL и DMA относятся к арбитражу, OVL говорит о том, что эта команда может перекрывать по времени предыдущую/следующую а DMA говорит о том, что результат этой команды будет передан через DMA транзакции. Т.е., оба бита относятся к оптимизации времени использования шины и привода. TAG (метка) указывает на очерёдность команды в очереди (при использовании OVL). А содержимое регистров CYL_HI:CYL_LOW указывает на максимальный размер пакета пересылаемых данных за 1 транзакцию DRQ (и для DMA, и для PIO). Это полезно если у хоста небольшой буфер в драйвере устройства. Ну и может пригодиться нам, т.к. памяти в контроллере тоже не бесконечное количество. Результат выполнения команды тоже отличается:

Больше флагов Богу флагов!
Больше флагов Богу флагов!

I/O указывает на направление передачи данных (0 - запись в привод), C/D показывает, что ожидает привод (0 - команда, 1 - данные). SERV взводится если перекрываемая команда в очереди исполнена. Остальные флаги ведут себя так же, как и у IDENTIFY_ATAPI_DEVICE. Перекрытием команд мы пока пользоваться не будем, поэтому рассмотрим обычную транзакцию чтения сектора с CD.

Чтение сектора занимаем приличное время из-за ожидания носителя
Чтение сектора занимаем приличное время из-за ожидания носителя

Устанавливаем параметры для команды PACKET: FEATURES = 0x00, CYL_HI:CYL_LO = 0xC00, DEVICE = 0xA0. Затем вычитываем STATUS для очистки запросов и подаём команду COMMAND = 0xA0. Устройство сразу же ответило, что ждёт от нас данные пакета:

Подача команды PACKET и, собственно самого пакета
Подача команды PACKET и, собственно самого пакета

IDENTIFY_ATAPI_DEVICE показал, что у этого устройства используется пакет размером в 12 байт (6 слов). Структура пакета при этом следующая:

Байты пакета группируются в слова в порядке little endian
Байты пакета группируются в слова в порядке little endian

В логе видно, что команда чтения сектора 0x28, номер LBA = 0x00000011 а длина передачи 0x00000001 (1 сектор). После принятия устройством пакета не перекрываемой команды оно сразу же пытается её исполнить. При этом выставляя статусы по мере выполнения команды:

Поллинг регистра статуса с интервалом в 2 мс
Поллинг регистра статуса с интервалом в 2 мс

Необходимо следить за состоянием регистра STATUS или настроить приём сигнала INTRQ, последний позволяет устройству не торчать в ожидании а выполнять что-то полезное в процессе ожидания. Когда устройство выполнит запрос оно выставит INTRQ и соответствующие флаги: сначала установится флаг готовности данных DRQ а затем снимется флаг занятости устройства BSY:

Вычитка данных занимает всего 653 микросекунды
Вычитка данных занимает всего 653 микросекунды

В этот момент следует переходить к фазе чтения данных:

Бодро вычитываем данные
Бодро вычитываем данные

Как только получен разрешающий чтение данных статус, следует получить состояние регистров CYL_HI:CYL_LO, они будут содержать число фактически готовых данных в буфере устройства. В логе выше видно, что там 0x0800 или 2048 байт. Это стандартный сектор данных для CD. Имейте в виду, это количество байт, а так как мы считываем словами то количество транзакций должно быть вдвое меньше. При этом обменивать байты тут уже не требуется. Результат выполнения команды в CLI:

Считали сектор с описанием структуры ISO
Считали сектор с описанием структуры ISO
Считали сектор с данными директории
Считали сектор с данными директории

На данный момент, команда PACKET в CLI требует ввода всего пакета, поэтому она такая длинная. Однако, сейчас это же только проверка оборудования на работоспособность и оно уже позволяет считывать реальные данные с CD. Позднее будет введена команда автоматического формирования пакета по минимальному количеству параметров.

Команды в CLI можно объединять в пакет и устройство выполнит их последовательно друг за другом:

Добавлены команды I (чтение регистра) и * (пауза в секундах)
Добавлены команды I (чтение регистра) и * (пауза в секундах)

На этом можно завершать подготовительную часть и со следующей части статьи переходить уже к основной задаче. Контроллер получился неплохой, результатом его работы я прям доволен. Обратите внимание - все эпюры в этой статье сняты именно с него. Протестировал на трёх приводах разной степени древности - все работают как задумывалось. Добавлю позже работу с SD картой и соответствующих команд в CLI, но это уже будет "за кадром". Не переключайтесь.

Комментарии (4)


  1. sintech
    03.09.2025 12:49

    Отличное продолжение, но это все еще контроллер а не оконечное устройство поэтому FSMC тут подходит идеально.

    Интересно, как планируется реализовать обратное, чтобы устройство выступало приводом и желательно без EOL CPLD.


    1. HardWrMan Автор
      03.09.2025 12:49

      Это предмет следующей статьи.


  1. kenomimi
    03.09.2025 12:49

    Ох, прямо стариной повеяло... Похожим был один из моих первых проектов на МК, еще не было ардуины тогда, все паяли сами. Atmel Studio + atmega8535 + ЛУТовая плата + программатор на LPT-порту. Задача - читать и писать диск через RS232 плюс ставить/убирать пароль, и в принципе, оно даже получилось - на скорости 115200 работало на ура.


    1. HardWrMan Автор
      03.09.2025 12:49

      В середине-конце 00х ходили самодельные устройства на PICе как раз для низкоуровневого управления IDE HDD. Вот эту схему я собирал человеку, который занимался ремонтом HDD у нас в городе:

      Правда, не прошивал - он сам всё прошивал, я только собирал физическое устройство. Прошивки на подобные устройства у них ходили в закрытых кругах ремонтников HDD.