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-бітного формату.

Top