STM8. Ассемблерні вставки в Cosmic. Зсуви в C
Ассемблерні вставки в STM8 для Cosmic
При програмуванні мікроконтролерів іноді стикаються з необхідністю реалізації спеціальних завдань, які реалізувати в Сі неможливо. Я зіткнувся з реалізацією критичної секції, де необхідно зберегти регістр станів (Condition Code register) для фіксації стану глобального переривання. Реалізувати дану задачу можна тільки з допомогою асемблерної вставки. Далі буде показана реалізація вставки для компілятора Cosmic.
Приклад 1.
Реалізувати критичну секцію, яка зберігає стан глобального глобального переривання в регістрі станів.
uint8_t CondFlag; // Змінна для зберігання регістра станів void asm_insert(void) { enableInterrupts(); // дозволяємо глобальне переривання // початок критичної секції _asm("push CC"); // поміщаємо регістр станів в стек disableInterrupts(); // гарантовано забороняємо глобальне переривання _asm("pop _cc_reg"); // витягуємо регістр станів у створену змінну // кінець критичної секції GPIO_Init(GPIOE, GPIO_PIN_5, GPIO_MODE_OUT_OD_LOW_FAST); // тестовий код // ще якийсь код, який повинен гарантовано виконуватись без переривань // повертаємо початковий стан регістра станів, а заодно і переривання #pragma asm // початок асемблерної вставки push _CondFlag // поміщаємо регістр стану в стек pop CC // завантажуємо початковий стан зі стека в регістр станів #pragma endasm // кінець асемблерної вставки // кінець критичної секції }
Асемблерні команди вводяться всередині конструкції _asm(" ") або знаходяться між службовими конструкціями #pragma asm і #pragma endasm, особливо хочеться звернути увагу на те, як відбувається вставка 8-бітних змінних. Для цього необхідно в асемблерному коді перед нашою змінною поставити нижнє підкреслення "_", тобто якщо у нас змінна в Сі називається CondFlag, то для використання її в асемблері ми пишемо _CondFlag, і компілятор вставляє адресу нашої змінної. Прямого доступу до регістра станів у нас немає, тому доводиться його читати і писати через стек. Витягуємо значення регістра тільки після того, як гарантовано вимкнули переривання, інакше можливе виникнення переривання, в якому також використовується критична секція, і відловити дану проблему буде дуже складно.
Приклад 2.
Приведу ще один приклад по роботі з 16-бітною змінною. В якості задачі уявімо, що необхідно реалізувати круговий зсув бітів.
int16_t sdvig=0xa000; void rotate_left (void) { uint8_t i; for(i=0;i<4;i++) // Повторюємо круговий зсув вліво 4 рази { #pragma asm // початок асемблерної вставки LDW X,_sdvig // завантажуємо в регістр Х значення 16-бітної змінної за адресою sdvig LDW Y,#$7FFF // завантажуємо число для перевірки першого біта на 1 чи 0 CPW Y,_sdvig // якщо число більше, ніж $7FFF, то біт переносу завантажується в біт С регістра СС RLCW X // виконуємо круговий зсув з урахуванням біта С. LDW _sdvig,X // Завантажуємо за адресою _sdvig значення з регістра Х #pragma endasm // кінець асемблерної вставки } }
Для розуміння роботи раджу скопіювати код і подивитися, як він буде працювати. Біт переповнення (Carry) (С) в регістрі станів (Condition Code) я буду називати, як в документації (СС.С).
В рядку 8 ми копіюємо значення sdvig в 16-бітний регістр, далі в регістр ми завантажуємо константу $7FFF.
В рядку 10 виконуємо віднімання із завантаженої константи числа, яке ми зсуваємо. Що відбувається? Якщо у нас, наприклад, число $8200, то при виконанні команди віднімання з $7FFF числа $8200 відбудеться переповнення, і в біт СС.С буде завантажена 1, якщо число менше або дорівнює $7FFF, то біт СС.С буде рівно 0.
В рядку 11 при виклику команди RLCW відбудеться циклічний зсув регістра Х, при цьому найстарший регістр переміститься в біт СС.С, а поточний біт СС.С в молодший біт регістра Х.
В рядку 12 ми завантажуємо значення з регістра Х за адресою sdvig.
А далі ми все повторюємо.
Використання асемблерних вставок і поєднання з програмою на Сі дуже просте.
Арифметичні і логічні зсуви в Сі
Раз мова зайшла про зсуви, хотів би ще звернути увагу на стандартну операцію зсуву в Сі. Це операція зсуву вліво << і зсуву вправо >>. На це звертають мало уваги, але зсуви діляться на арифметичні і логічні. В чому різниця арифметичного і логічного зсуву? Арифметичний зсув - це зсув знакового числа, а логічний - беззнакового числа. Крім того, корисно пам'ятати, що зсув - це швидке ділення або множення на 2.
Створимо дві змінні: знакову і беззнакову і присвоїмо їм однакові біти числа, але компілятор сприйматиме їх по-різному. Далі проведемо операції зсувів над знаковими і беззнаковими числами. Для зручності аналізу результатів роботи я співвідніс бінарне подання чисел.
uint8_t ua=0xCD; // беззнакова змінна ісходне 205 або 0b11001101 int8_t sa=0xCD; // знакова змінна ісходне -51 або 0b11001101 uint8_t ua_right; // беззнакова змінна зсув вправо 51 або 0b00110011 int8_t sa_right; // знакова змінна зсув вправо -13 або 0b11110011 uint16_t ua_left; // беззнакова змінна зсув вліво 820 або 0b0000001100110100 int16_t sa_left; // знакова змінна зсув вліво -204 або 0b1111111100110100 ua_right=ua>>2; // результат зсув вправо 51 або 0b00110011 sa_right=sa>>2; // результат зсув вправо -13 або 0b11110011 ua_left =ua<<2; // результат зсув вліво 820 або 0b0000001100110100 sa_left =sa<<2; // результат зсув вліво -204 або 0b1111111100110100
Як видно, результат операції для зсуву вправо в залежності від того, знакове чи беззнакове число, відрізняється, але представляє із себе число, поділене на 4. Для зсуву вліво необхідно розширити діапазон з 8-бітного числа перейти до 16-бітного, тоді видно, що при зсуві вліво відбувається множення на 4, в залежності від того, знакове чи беззнакове число, бінарні результати відрізняються.
Іноді при роботі з АЦП в диференційному режимі дуже зручно виконувати арифметичний зсув, оскільки вбудований АЦП частіше за все 10-бітний і представляє негативні числа в додатковому коді для 10-біт, але контролер працює з додатковим кодом у 16-бітному форматі, тому далі задача зводиться до переведення додаткового коду з 10-бітного в 16-бітний формат.
Приклад. АЦП в диференційному режимі видав наступний 10-біт 1.001101101 в додатковому коді на 10-біт це буде відповідати -403 для додаткового коду.
Налаштування АЦП з вирівнюванням вліво дасть нам 16-бітне число a=1.001101101000000, а далі, якщо це число зберігаємо в знаковому форматі і виконуємо зсув вправо на 6 a=a>>6, тобто число стане 1111111.001101101 або -403 в додатковому коді для 16-бітного формату.