Индусский код в Микрочипе

в 7:00, , рубрики: library, microchip, pic, библиотеки, индусский код, культура программирования, Программинг микроконтроллеров, Программирование, метки: , , , , ,

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

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

Индусский код в Микрочипе

Не подумайте, что я сейчас пытаюсь гнуть расово верную линию — не имел опыта общения конкретно с индусами, но точно знаю что среди наших их тоже достаточно (не верите — наберите «95» в гугле), но понятие «индусского кода» появилось давно и закрепилось довольно прочно, хотя вы и не найдете его в политкорректной википедии (но гугол о нем точно знает).

Индусский код (не индийский или индейский) — жаргонное нарицательное название для программного кода крайне низкого качества, использующего простые, но порочные принципы «copy-paste».
Почему именно индусский?
По слухам в Индии с некоторых времен существует практика оценки производительности труда программиста на основе количества написанного кода. Чем больше кода, тем больше программист работает, и, следовательно, выше его оклад. Шустрые индусы быстро сообразили, как обманывать неквалифицированных заказчиков.

Итак, если вы хотите научиться программировать так как это делают в микрочипе следуйте следующим простым советам…

0. Больше кода — больше профит!

Самое важное, что надо запомнить нанимаясь получив работу в микрочипе: «Они-таки платят за строки кода!». Поэтому любыми способами увеличивайте объемы исходных текстов. Совет общий, так что без примеров, включайте фантазию.

1. Классика жанра

Классика жанра индусского кино кода незыблема со времен его появления, для разминки попробуйте угадать что скрывается за этим куском кода, содержащемся в файле «MDD File SystemSD-SPI.c» на строчке 1042:

if(localCounter != 0x0000)
{
    localPointer--;
    while(1)
    {
        SPIBUF = 0xFF;
        localPointer++;
        if((--localCounter) == 0x0000)
        {
           break; 
        } 
        while(!SPISTAT_RBF);
        *localPointer = (BYTE)SPIBUF;
    }
    while(!SPISTAT_RBF);
    *localPointer = (BYTE)SPIBUF;  
}  

не спешите заглядывать в ОТВЕТ - это просто:

while (localCounter)
{
	SPIBUF = 0xFF;
	while (!SPISTAT_RBF);
	*localPointer++ = SPIBUF;
}

2. Копипаст

В отсутствии фантазии подойдет и копи-пейст, хотя по слухам многие работодатели проверяют код на копипаст, микрочип видимо не из их числа. Запомните, для срубания бабла индусским кодом никогда не используйте макросы — они зло и безобразно уменьшают код. В пример кусок, повторяющийся раз двадцать в файле «MDD File SystemFSIO.c»:

ctrl+v

if (utfModeFileName)
{
	utf16path++;
	i = *utf16path;
}
else
{
	temppath++;
	i = *temppath;
}

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

простая и понятная замена куску выше

#define getnextpathchar() ( utfModeFileName ? *++utf16path : *++temppath ) // где-то в начале
	...
	i = getnextpathchar(); // там где надо

Соотношение 10:1 в пользу первого варианта, а с учетом двадцатикратного повторения в абсолютных величинах это несколько сот рупий!

3. Линейный код

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

не стесняйтесь делать так

fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part3[1];
fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part3[0];

fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part2[5];
fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part2[4];
fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part2[3];
fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part2[2];
fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part2[1];
fileFoundString[fileFoundLfnIndex--] = lfnObject.LFN_Part2[0];

tempShift.byte.LB = lfnObject.LFN_Part1[8];
tempShift.byte.HB = lfnObject.LFN_Part1[9];
fileFoundString[fileFoundLfnIndex--] = tempShift.Val;
tempShift.byte.LB = lfnObject.LFN_Part1[6];
tempShift.byte.HB = lfnObject.LFN_Part1[7];
fileFoundString[fileFoundLfnIndex--] = tempShift.Val;
tempShift.byte.LB = lfnObject.LFN_Part1[4];
tempShift.byte.HB = lfnObject.LFN_Part1[5];
fileFoundString[fileFoundLfnIndex--] = tempShift.Val;
tempShift.byte.LB = lfnObject.LFN_Part1[2];
tempShift.byte.HB = lfnObject.LFN_Part1[3];
fileFoundString[fileFoundLfnIndex--] = tempShift.Val;
tempShift.byte.LB = lfnObject.LFN_Part1[0];
tempShift.byte.HB = lfnObject.LFN_Part1[1];
fileFoundString[fileFoundLfnIndex--] = tempShift.Val;

Инициализация структур должна быть побайтной, не надо писать простые инициализаторы типа:

const somestruct mystruct = {"Field1", 2, 4, 8 .... };

не стесняйтесь делать и так, memset для лузеров, а это живые деньги

gDataBuffer[0] = 0xEB;         //Jump instruction
gDataBuffer[1] = 0x3C;
gDataBuffer[2] = 0x90;
gDataBuffer[3] =  'M';         //OEM Name "MCHP FAT"
gDataBuffer[4] =  'C';
gDataBuffer[5] =  'H';
gDataBuffer[6] =  'P';
gDataBuffer[7] =  ' ';
gDataBuffer[8] =  'F';
gDataBuffer[9] =  'A';
gDataBuffer[10] = 'T';
gDataBuffer[11] = 0x00;             //Sector size 
gDataBuffer[12] = 0x02;
gDataBuffer[13] = disk->SecPerClus;   //Sectors per cluster
gDataBuffer[14] = 0x20;         //Reserved sector count
gDataBuffer[15] = 0x00;
disk->fat = 0x20 + disk->firsts;
gDataBuffer[16] = 0x02;         //number of FATs
gDataBuffer[17] = 0x00;          //Max number of root directory entries - 512 files allowed
gDataBuffer[18] = 0x00;
gDataBuffer[19] = 0x00;         //total sectors
gDataBuffer[20] = 0x00;
gDataBuffer[21] = 0xF8;         //Media Descriptor
gDataBuffer[22] = 0x00;         //Sectors per FAT
gDataBuffer[23] = 0x00;
gDataBuffer[24] = 0x3F;         //Sectors per track
gDataBuffer[25] = 0x00;
gDataBuffer[26] = 0xFF;         //Number of heads
gDataBuffer[27] = 0x00;
// Hidden sectors = sectors between the MBR and the boot sector
gDataBuffer[28] = (BYTE)(disk->firsts & 0xFF);
gDataBuffer[29] = (BYTE)((disk->firsts / 0x100) & 0xFF);
gDataBuffer[30] = (BYTE)((disk->firsts / 0x10000) & 0xFF);
gDataBuffer[31] = (BYTE)((disk->firsts / 0x1000000) & 0xFF);
// Total Sectors = same as sectors in the partition from MBR
gDataBuffer[32] = (BYTE)(secCount & 0xFF);
gDataBuffer[33] = (BYTE)((secCount / 0x100) & 0xFF);
gDataBuffer[34] = (BYTE)((secCount / 0x10000) & 0xFF);
gDataBuffer[35] = (BYTE)((secCount / 0x1000000) & 0xFF);
gDataBuffer[36] = fatsize & 0xFF;         //Sectors per FAT
gDataBuffer[37] = (fatsize >>  8) & 0xFF;
gDataBuffer[38] = (fatsize >> 16) & 0xFF;         
gDataBuffer[39] = (fatsize >> 24) & 0xFF;
gDataBuffer[40] = 0x00;         //Active FAT
gDataBuffer[41] = 0x00;
gDataBuffer[42] = 0x00;         //File System version  
gDataBuffer[43] = 0x00;
gDataBuffer[44] = 0x02;         //First cluster of the root directory
gDataBuffer[45] = 0x00;
gDataBuffer[46] = 0x00;
gDataBuffer[47] = 0x00;
gDataBuffer[48] = 0x01;         //FSInfo
gDataBuffer[49] = 0x00;
gDataBuffer[50] = 0x00;         //Backup Boot Sector
gDataBuffer[51] = 0x00;
gDataBuffer[52] = 0x00;         //Reserved for future expansion
gDataBuffer[53] = 0x00;
gDataBuffer[54] = 0x00;                   
gDataBuffer[55] = 0x00;
gDataBuffer[56] = 0x00;                   
gDataBuffer[57] = 0x00;
gDataBuffer[58] = 0x00;                   
gDataBuffer[59] = 0x00;
gDataBuffer[60] = 0x00;                   
gDataBuffer[61] = 0x00;
gDataBuffer[62] = 0x00;                   
gDataBuffer[63] = 0x00;
gDataBuffer[64] = 0x00;         // Physical drive number
gDataBuffer[65] = 0x00;         // Reserved (current head)
gDataBuffer[66] = 0x29;         // Signature code
gDataBuffer[67] = (BYTE)(serialNumber & 0xFF);
gDataBuffer[68] = (BYTE)((serialNumber / 0x100) & 0xFF);
gDataBuffer[69] = (BYTE)((serialNumber / 0x10000) & 0xFF);
gDataBuffer[70] = (BYTE)((serialNumber / 0x1000000) & 0xFF);
gDataBuffer[82] = 'F';
gDataBuffer[83] = 'A';
gDataBuffer[84] = 'T';
gDataBuffer[85] = '3';
gDataBuffer[86] = '2';
gDataBuffer[87] = ' ';
gDataBuffer[88] = ' ';
gDataBuffer[89] = ' ';

4. Изобретаем велосипед или деньги из пробелов

На очередную мысль меня навела идея функции FileObjectCopy в файле «MDD File SystemFSIO.c» на строчке 6065, подозреваю что если бы у них было больше разных структур то появились бы и другие SomeObjectCopy

сама функция

void FileObjectCopy(FILEOBJ foDest, FILEOBJ foSource)
{
	int size;
	BYTE* dest;
	BYTE* source;
	int Index;

	dest = (BYTE*)foDest;
	source = (BYTE*)foSource;

	size = sizeof(FSFILE);

	for (Index = 0; Index < size; Index++) {
		dest[Index] = source[Index];
	}
}

Описание очаровывает простотой:

The FileObjectCopy function will make an exacy copy of a specified FSFILE object.

Если «exacy» == «exact» как следует из кода, то это профитная замена прямого присвоения структур — стандартной операции в ANSI C, a сделанное компилятором, оно должно быть и быстрее и компактнее так как используются аппаратные FSR/INDF регистры. Для разных объектов подойдет memcpy(d, s, sizeof(s)) и работает он тоже быстро, во всяком случае его ассемблерная реализация.

не прибыльная версия FileObjectCopy, дизассемблированный вариант от HITech C18

; *FileObject1 = *FileObject2; // Одна строчка на С
; Загружаем индирект регистры
MOVLW FileObject1 >> 8
MOVWF FSR1H
MOVLW FileObject1
MOVWF FSR1L
MOVLW FileObject2 >> 8
MOVWF FSR0H
MOVLW FileObject2
MOVWF FSR0L
; Копируем
MOVLW sizeof(*FileObject)
loop:
MOVFF POSTINC0, POSTINC1 ; Три команды процессора на скпированный байт
DECFSZ WREG, F
BRA loop

Ну есть еще мелочи для раздувания кода, которыми можно добавить пару — тройку строк, типа:

int FSerror (void)
{
    return FSerrno;
}

Даже если это исключительно для того чтобы сделать переменную read-only то такого макроса вполне достаточно, чтобы компилятор выругался где надо:

#define FSerror() ( FSerrno )

5. Комментарии с фанатизмом

Комментируйте все подряд, кроме самых не очевидных кусков (см пример 1.) Если вы еще не достигли полного просветления и в вашей индусской программе случайно осталось две-три функции — создайте «шаблон описания функции», включите туда умные слова-разделы, в разделе «Description» перечислите еще раз все что было написано выше, но развернуто. Особенно эффект умножения строк кода проявляется с функциями типа «FSerror()» из примера выше.

даже пустой такая шапка смотрится значимо

/**************************************************************************
  Function:
    void func (void)
  Summary:
    Does a hard work
  Conditions:
    This function should not be called by the user
  Input:
    None
  Return Values:
    None
  Side Effects:
    None
  Description:
    This function will do <a hard work>, with <none> input parameter....
  Remarks:
    Optimize code later
**************************************************************************/

6. Используйте особенности архитектуры

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

Пишите код таким образом, чтобы он даже не мог компилироваться под разными версиями компиляторов, и используйте все специфические #pragma. В этом случае каждая функция будет присутствовать в версиях как минимум для двух компиляторов и трех-четырех архитектур PIC, итого до 8 крат увеличения кода.

Еще раз удвоить количество кода вам поможет то, что указатели RAM и ROM в компиляторах под PIC разные, то есть «char*» не может быть преобразован явно или неявно к «const char*» в хайтеке или «const rom char*» в микрочипе. Что вобщем-то проблем в хайтеке не вызывает совсем, так как void, far и const указатели могут адресовать всю память и применяться как к ROM так и RAM. Но в микрочиповской реализации си это может привести к созданию двух функций: одной работающей с ROM, а второй с RAM — чистый профит. Никогда не следует довольствоваться одной функцией, работающей с оперативной памятью (а при необходимости загружающей туда константы из ROM).

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

Вместо заключения

Попытавшись заставить заработать микрочиповское чудо (их сайт все-таки поднялся) я потратил чуть больше времени чем то, за которое написал свою реализацию работы с SD и портировал файловую систему, взятую здесь, о чем и вас предупреждаю — аккуратнее: «glitch inside».

Автор: ma5ter

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js