Создание проектов для МК MSP430F5xxx (MSP430F5438A) на языке Си и ассемблере

Создание проектов  для МК MSP430F5xxx (MSP430F5438A) на языке Си и ассемблере

Методические указания по созданию проектов

для МК MSP430F5xxx (MSP430F5438A) на языке Си и ассемблере


Содержание :


1-Краткая техническая характеристика MSP430F5438A

2- Архитектура МК , устройство памяти

3- Таблица регистров МК MSP430F5438A

4- Система команд МК MSP430F5438A

5- Язык Си для MSP430 и отладчика Code Composer V4

6 – Твой первый проект « Hello Word »

7- Программирование синусоиды и ПИД регулятора





  1. Краткая техническая характеристика MSP430F5438A


Для начала определим термины -


Глоссарий :


ACLK - Вспомогательные часы
ADC - аналого-цифровой преобразователь
BOR - Brown-Out Reset, см. системный сброс, прерывания и режимы работы
BSL - загрузчик, см. www.ti.com/msp430 отчет о применении
CPU - Центральный процессор См. RISC-процессор 16-бит
DAC - цифро-аналоговый преобразователь
DCO - цифровой генератор, управляемый, см. FLL + модуль
DST - назначения; см. RISC-процессор 16-бит
FLL - модуль автоподстройки частоты, см. FLL + модуль
GIE - режим разрешения основного прерывания, см. системный сброс Прерываний и

управления
INT - (N / 2) целая часть от N / 2
I / O - порты ввода / вывода, см. цифровой ввод / вывод
ISR - обслуживание прерываний Рутинное
LSB - наименьший значащий разряд
LSD - наименьшая значимая цифра
LPM - энергосберегающий режим, см. система сброса прерывания и режимы

работы; также название PM для Режима питания


MAB - адресная шина памяти
MCLK - Основные часы
MDB - шина данных памяти
MSB - наибольший значащий разряд
MSD - Наименьший значащий разряд
NMI - (Non)-маскируемые прерывания, см. системный сброс , прерывания и режимы

работы; также разделение на UNMI и SNMI
PC - программный счетчик; см. RISC-процессор 16-бит
PM – см. режим питания ; система сброса прерываний и режимы работы
POR - сброса при включении питания; см. Сброс системы прерываний и режимы работы

PUC - очистка при включении питания, см. Сброс системы прерываний и режимы работы
RAM - оперативная память
SCG - системный тактовый генератор, см. система сбрасывает прерываний и режимы

работы
SFR – Регистр специального назначения ; см. система сброса, прерываний и режимы работы
SMCLK – подситстема основных часов
SNMI - системы NMI, см. система сброса, прерываний и режимы работы
SP - Указатель стека, см. RISC-процессор 16-бит
SR - регистра статуса, см. RISC-процессор 16-бит
SRC - Источник, см. RISC-процессор 16-бит
TOS - вершину стека, см. RISC 16-разрядных процессоров
UNMI - Пользовательские NMI, см. система сброса, прерываний и режимы работы
WDT - сторожевой таймер; см. сторожевой таймер
Z16 - 16-битное адресное пространство .

Возможное состояние ключевых битов :

RW чтение / запись
R только для чтения
r0 чтение как 0
R1 Чтение как 1
W Запись только
w0 запись как 0
W1 запись как 1
(W) не зарегистрировать немного реализованы; письменном 1 результатов в

импульсе. Регистрация бит всегда читается как 0.
h0 сбрасывается аппаратно
h1 Устанавливается аппаратно
-0, -1 Состояние после PUC
- (0), - (1) Состояние после POR
- [0] - [1] Состояние после BOR
- {0}, - {1} Состояние после Brownout














  1. Программирование на языке Си для MSP430 и отладка программы с помощью Code Composer V4




Задача программы любого МК:


- читать числа из регистров и памяти МК,

- делать что-то с числами, данными и

- записывать числа в регистры и память.


Только так программа может общаться с МК !!!.


Минимальная программа на Си может быть такой :



Main(){}


Эта программа не делает ни чего полезного - но это уже программа и она показывает что в программе на языке Си - должна быть главная функция main - обязательно !


Реальные программы на Си конечно больше.


Как это делать на языке Си ?



Регистры МК ( регистры - это ячейки-байты в памяти МК MSP430 ) в программе на Си имеют названия как и в Дата шите ( инструкции пользователя на микроконтроллер на сайте производителя ) и так как числа в большинстве из них можно менять - для программы регистры являются по сути переменными.

Переменная - это набор ячеек в памяти в которых можно хранить число или числа и менять их. Переменная имеет адрес и имя.

Константа - это как переменная но менять содержимое нельзя.

Начиная этот урок мы изначально предполагаем , что читатель знаком с система исчисления – двоичной , восьмеричной , десятеричной и шестнадцатеричной .

Подробней о переменных и константах написано ниже.



Мы покажем вам, что Си это довольно ПРОСТО



Начнём знакомство с использованием языка Си применительно к программированию микроконтроллеров MSP 430 , с выяснения общих вопросов, которым в существующих руководствах уделяется на удивление мало внимания.



§

> Общие вопросы. Переменные объявляемые пользователем.


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


Основной объект программирования для классического Си - переменная. Это может быть одиночная или группа особым образом связанных переменных, например, массив или структура. По сути переменная представляет из себя некое хранилище для числа, имеющее своё уникальное имя и допустимый диапазон значений, выходить за пределы которого крайне нежелательно. И первое, что мы должны сделать, перед тем как начать использовать имя переменной в тексте программы это познакомить программу с её свойствами. В языке Си этот процесс называется объявлением переменной.


Зачем нужно объявлять переменные?


Хоть язык Си и абстрактный, используемый разработчиком микроконтроллер, как правило, вполне конкретный и имеет своё адресное пространство памяти с заданными свойствам, где и будет храниться объявляемая переменная. Объявление, помимо присвоения переменной имени, заставляет компилятор разместить её по конкретному адресу в памяти микроконтроллера (по какому именно нас в большинстве случаев совершенно не интересует).

Как нужно объявлять переменные?

Правило для объявления можно формулировать так: до того как мы впервые употребим имя переменной в тексте нашей программы, необходимо разместить её объявление в следующем формате:



type name; // Переменная с именем "name" и типом "type".


здесь: type - так называемый идентификатор типа переменной из определённого набора стандартных типов;

name - произвольное имя переменной, лишь бы оно не начиналось с цифры, состояло только из латинских символов, и не совпадало со служебными словами языка Си (список которых не так велик, чтобы столкнуться с такой ситуацией нужно на самом деле очень постараться).



Что такое идентификатор типа и зачем его упоминать?



Для хранения переменной микроконтроллер использует ячейки памяти, размер которых определяется его разрядностью. Так например, микроконтроллеры семейства MSP430xxxxx -16 -разрядные, то он может работать как с байтами , так и со словами , имеющими младший байт и старший байт . А значит для хранения данных используют ячейки памяти размером в два байта, которые способны сохранять 65535 различных числовых значений. Если ожидаемые значения переменной могут превысить это количество, то для её хранения понадобится две или более ячеек памяти. Поскольку Си, строго говоря, не представляет какие значения мы планируем присваивать переменной, то просит нас указать её тип, который как раз и определяет допустимый диапазон значений. Это необходимо чтобы не зарезервировать за ней избыточный или недопустимо малый объём памяти, а так же предупреждать нас при попытке присвоить слишком большое значение переменной, не способной его сохранить. Для 16 -разрядных микроконтроллеров наиболее часто употребимые целочисленные типы данных следующие:



способные хранить только положительные значения (беззнаковые):



unsigned char - занимает один байт памяти, значения 0...255

unsigned int - два байта, значения 0...65535

unsigned long - четыре байта, от 0 до (2^32)-1


способные хранить значения со знаком (знаковые):



signed char - занимает один байт памяти, от -128...127

signed int - два байта, значения -32768...32767

signed long - требует четыре байта, значения от -(2^31) до (2^31)

Ключевое слово "unsigned" (беззнаковое), вообще говоря, можно не употреблять, поскольку в Си по умолчанию тип, для которого не указан этот признак, считается беззнаковым.



Для работы с дробными числами в Си предусмотрены типы с плавающей точкой:

float – 32 бита, значения от ±1.18E-38 до ±3.39E+38

double – 32 (±1.18E-38…±3.39E+38) или 64 бита (±2.23E-308…±1.79E+308) в зависимости

от настроек компилятора.



Таблица 1. согласно руководства http://focus.ti.com/lit/ug/slau132e/slau132e.pdf стр.78


MSP430 C/C++ Типы данных



Диапазон

Тип

размер

Представление

Минимальный

Максимальный

char, signed char

8 bits

ASCII

-128

-127

unsigned char, bool

8 bits

ASCII

0

255

short, signed short

16 bits

2s дополнение

-32 768

32 767

unsigned short, wchar_t

16 bits

Binary

0

65 535

int, signed int

16 bits

2s дополнение

-32 768

32 767

unsigned int

16 bits

Binary

0

65 535

long, signed long

32 bits

2s дополнение

-2 147 483 648

2 147 483 647

unsigned long

32 bits

Binary

0

4 294 967 295

enum

16 bits

2s дополнение

-32 768

32 767

float

32 bits

IEEE 32-bit

1.175 495e-38 (1)

3.40 282 35e+38

double

32 bits

IEEE 32-bit

1.175 495e-38 (1)

3.40 282 35e+38

long double

32 bits

IEEE 32-bit

1.175 495e-308(1)

3.40 282 35e+38

pointers, references, pointer to data members

16 bits

Binary

0

0xFFFF

MSP430X large-data model pointers, references, pointer to data members (2)

20 bits

Binary

0

0xFFFFF

MSP430 function pointers

16 bits

Binary

0

0xFFFF

MSP430X function pointers (3)

20 bits

Binary

0

0xFFFFF



(1) Цифры минимальной точности

(2) MSP430X большие данные модели определяется --silicon_version=mspx --data_model=larg

(3) MSP430X устройства задаются --silicon_version=msp


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

Для того чтобы перед началом использования переменной она уже имела конкретное значение, к объявлению часто дописывается инициализатор: знак равенства (в Си это оператор присваивания = ) и начальное значение переменной.


Например:


int A=100; // Переменная с именем "А" типом int и начальным значением равным 100.


Практический пример: пусть планируется написать программу, мигающую светодиодом 5 раз. Для подсчёта числа миганий потребуется переменная, значение которой, очевидно никогда не будет отрицательным и не выйдет за пределы диапазона от 0 до 255, а значит в данном случае будет вполне достаточно использовать однобайтовый тип char:



unsigned char ChisloMiganiy=0;


§

> Область видимости переменной.



Источником многих затруднений для начинающих становится свойство языка, которое называется областью видимости объявленной переменной. Язык Си имеет возможность ограничить действие переменной конкретной областью программного кода, при этом в других частях программы она становится недоступной, благодаря чему высвобождается память, которая на других участках программы может использоваться другими переменными. Такие переменные называются локальными, а их использование - основной способ получения экономичного кода.

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




§

> Область размещения переменной.



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





Для хранения пользовательских переменных может быть использована ОЗУ, энергонезависимая память EEPROM ( не во всех контроллерах присутствует ), а для хранения констант, значение которых не может быть изменено в процессе работы программы также и FLASH- память микроконтроллера.

Для начала полезно знать, что переменные, объявленные пользователем без использования специальных ключевых слов типа _eeprom или _flash, размещаются в ОЗУ микроконтроллера, в виде одной или нескольких ячеек статической памяти SRAM. В процессе работы они периодически копируются в быструю регистровую память РОН, которая непосредственно взаимодействует с арифметически-логическим блоком АЛУ микроконтроллера.

Вопросы размещения переменных внутри ОЗУ, как правило, представляют интерес только в контексте быстродействия программы.



§

> Регистры специального назначения микроконтроллера MSP430xxx - SFR.


Итак, мы кратко рассмотрели объявление переменных предназначенных для организации вычислительного процесса, которые мало связаны со спецификой аппаратной части МК.

Управление и контроль работы микроконтроллера и его отдельных внутренних модулей осуществляется путём записи и чтения специальных ячеек-регистров в служебной области памяти ОЗУ - регистров специального назначения (Special Function Register, далее просто SFR).

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

в Си для MSP430F5438A это беззнаковые 16-разрядные переменные. Во-вторых, SFR имеют строго определённые имена и адреса в памяти, являясь так называемыми регистрами ввода-вывода.


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


В начале любой программы на Си мы можем видеть строки типа:


#include "file1.h" // Включить в код содержимое файла "file1.h".

#include - это директива (указание), заставляющая среду разработки поместить в данное место программы содержимое файла с именем file1.h. Файлы с расширением .h называются заголовочными или h-файлами. Разработчик может создавать собственные h-файлы и помещать их, учитывая содержимое, в любое место программы. Однако, чтобы познакомить программу с SFR для данного типа микроконтроллера, необходимо подключать вполне конкретные заголовочные файлы. Их имена и количество зависит от конкретной среды разработки и типа используемого микроконтроллера, так, например, в Code Composer V4 для MSP430F5438A достаточно прописать строки:

#include "????????"

#include "????????"

После включения в текст необходимых h-файлов программа будет узнавать упоминаемые в ней имена SFR, например, регистр статуса микроконтроллера MSP430F5438A с именем SREG, буфер приёма/передачи модуля UART - UDR и так далее.
Заготовка программы для Code Composer V4, которая ничего не делает, но уже не "ругается" на имена регистров специального назначения микроконтроллера MSP430F5438A, должна выглядеть так:

#include "?????"

#include "?????.h"

unsigned char ChisloMiganiy=0;

void main (void)

{

// Здесь мы разместим программу, использующую переменную ChisloMiganiy

// и любые регистры MSP430, имена которых прописаны в файле ?????.h.

}

Хочется надеяться, что читатель знаком с правилами оформления комментариев в тексте программы. Это заметки, которые игнорируются языком Си и не считаются частью программного кода, если записаны в одной и более строках, заключённых между символами /* и */, или в одной строке, начинающейся с последовательности //.

§

> Обзор стандартных операций с регистрами.

Настало время перейти к более серьёзным операциям над регистрами и программными переменными. Управление работой микроконтроллера в большинстве случаев сводится к следующему простому набору действий с его регистрами:

1. Запись в регистр необходимого значения.

2. Чтение содержимого регистра.

3. Установка в единицу нужных разрядов регистра.

4. Сброс в ноль нужных разрядов регистра.

5. Проверка разряда регистра на логическую единицу и логический ноль.

6. Изменение логического состояния нужных разрядов регистра на противоположное.

Во всех указанных действиях принимает участие оператор присваивания языка Си, записываемый в виде знака равенства. Принцип действия оператора примитивно прост - он записывает в регистр или переменную расположенную слева от него, значение того, что записано справа. Справа может находится константа, другой регистр, переменная либо состоящее из них выражение, например:


A = 16; // Присвоить переменной A значение 16;

A = B; // Считать значение переменной B и присвоить это значение переменной A;

A = B+10; // Считать значение переменной B, прибавить к считанному значению 10, результат присвоить переменной A (значение переменной B при этом не изменяется).

§

> Запись и чтение регистров.


1) И так , мы уже знаем , чтобы поместить число в переменную (в регистр) в языке Си есть оператор присваивания

это знак = ( называемый в математике "равно" )

Запомните! В Си этот знак НЕ означает равенство !

= в Си означает вычислить результат того что справа от оператора присваивания и поместить этот результат в переменную находящуюся левее оператора присваивания.



PORTB = PINB + 34;/* Эта строчка на Си означает
Взять (прочитать, считать) значение переменной (регистра) PINB, затем прибавить к нему число 34 и поместить результат в переменную PORTB */

ПЕРЕМЕННАЯ = PINC; /* Эта строчка на Си означает
Взять (прочитать, считать) значение переменной (регистра) PINC и поместить результат в переменную с именем ПЕРЕМЕННАЯ */



Чтобы в Си взять (прочитать) число из регистра или значение переменной нужно написать его/её название НЕ непосредственно с лева от оператора присваивания !

примеры :

a) Строка где переменная стоит слева от = но через знак &

PORTB & = 0x23;

на Си означает - прочитать содержимое переменной PORTB, затем выполнить "поразрядное (побитное) логическое И" между прочитанным значением и числом 0x23 и поместить (записать, присвоить) результат в переменную PORTB

b) Строка где переменная стоит непосредственно слева от =

PORTB = 0x23;

на Си означает - не читая содержимое переменной PORTB
присвоить ей значение 0x23 уничтожив то что было там раньше.



Вместо & "И" (AND - только 1 и 1 дают 1) могут быть и другие побитные логические операции:

| "ИЛИ" (OR только 0 и 0 дают 0)

^ "Исключающее ИЛИ" (XOR изменить бит напротив "1")

~ "инвертирование битов" (INV изменить биты регистра)

и арифметические операции: + - * / %



Есть в Си операции которые изменяют значение
переменной и без оператора присваивания :





PORTA++; /* Эта строчка на Си означает
Взять значение переменной PORTA добавить к ней 1 и записать результат обратно в PORTA

говорят: Инкрементировать регистр PORTA */

PORTC--; /* Эта строчка на Си означает
обратное действие!

Декрементировать - вычесть 1 из значения регистра PORTC */



Инкремент и декремент удобно использовать для изменения
значения различных переменных счетчиков.

Важно помнить что они имеют очень низкий приоритет - поэтому
чтобы быть уверенными в порядке выполнения желательно писать
их отдельной строчкой программы !

Обратите внимание !

В конце выражения или конструкции в программе на Си ставят
точку с запятой.

Длинные выражения можно писать в несколько строк.

/* ЗЕЛЕНЫМ мы пишем комментарий к программе в Си он может быть написан в несколько
строк и пустых строк тоже */

// или в одну после двух черточек

Компилятор игнорирует все что написано в комментариях !

Вы не компилятор !

Не игнорируйте, пишите комментарии и читайте !



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




a=4;
b=7;

a = b++; /* Эта строчка на Си означает
Взять значение переменной b присвоить его переменной a затем добавить 1 к переменной b
и сохранить результат в b

Теперь a будет содержать число 7
b будет содержать число 8 */

a=4;
b=7;

a = ++b; /* Эта строчка на Си означает
Взять значение переменной b затем добавить к нему 1 и сохранить результат в b и этот же результат присвоить переменной a

Теперь a будет содержать число 8
и b будет содержать число 8 */




С оператором присваивания
используются вот такие сокращения:




ДЛИННАЯ ЗАПИСЬ

СМЫСЛ

СОКРАЩАЕТСЯ ДО

x = x + 1;

добавить 1

x++; или ++x;

x = x - 1;

вычесть 1

x--; или --x;

x = x + y;

прибавить y

x += y;

x = x - y;

вычесть y

x -= y;

x = x * y;

умножить на y

x *= y;

x = x / y;

поделить на y

x /= y;

x = x % y;

остаток от деления

x %= y;

x--;

вычесть 1

x -= 1;

x++;

добавить 1

x += 1;




Из рассмотренных примеров видно, что оператор присваивания сам по себе решает две первые задачи — запись и чтение значений регистров. Например для отправки микроконтроллером MSP430 байта по шине UART достаточно записать его в передающий регистр с именем UDR:




UDR = 8; // Отправить по UART число 8;

Чтобы получить принятый по UART байт достаточно считать его из регистра UDR:

A = UDR; // Считать принятый байт из UART и переписать в переменную A.



§

> Установка битов регистров.



Язык Си не имеет в своём составе команд непосредственного сброса или установки разрядов переменной, однако присутствуют побитовые логические операции "И" и "ИЛИ", которые успешно используются для этих целей.


Оператор побитовой логической операции "ИЛИ" записывается в виде вертикальной черты - "|" и может выполнятся между двумя переменными, а так же между переменной и константой. Напомню, что операция "ИЛИ" над двумя битами даёт в результате единичный бит, если хотя бы один из исходных битов находится с состоянии единицы. Таким образом для любого бита логическое "ИЛИ" с "1" даст в результате "1", независимо от состояния этого бита, а "ИЛИ" с логическим "0" оставит в результате состояние исходного бита без изменения. Это свойство позволяет использовать операцию "ИЛИ" для установки N-ого разряда в регистре. Для этого необходимо вычислить константу с единичным N-ным битом по формуле 2^N, которая называется битовой маской и выполнить логическое "ИЛИ" между ней и регистром, например для установки бита №7 в регистре SREG:

(SREG | 128) — это выражение считывает регистр SREG и устанавливает в считанном значении седьмой бит, далее достаточно изменённое значение снова поместить в регистр SREG:

SREG = SREG | 128; // Установить бит №7 регистра SREG.


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

Приведённый программный код, устанавливая седьмой бит в регистре SREG, выполняет вполне осмысленную работу - разрешает микроконтроллеру обработку программных прерываний. Единственный недостаток такой записи — в константе 128 не легко угадать установленный седьмой бит, поэтому чаще маску для N-ного бита записывают в следующем виде:

(1<<N) - это выражение на языке Си означает, число один, сдвинутое на N разрядов влево, это и есть маска с установленным N-ным битом. Тогда предыдущий код в более читабельном виде:

SREG = SREG | (1<<7);

или ещё проще с использование краткой формы записи языка Си:

SREG |= (1<<7);

которая означает - взять содержимое справа от знака равенства, выполнить между ним и регистром слева операцию, стоящую перед знаком равенства и записать результат в регистр или переменную слева.


примеры :



00010010 | 01001111 // "ИЛИ" - только 0 и 0 дают 0
// англ. название OR

01011111 // это результат

// только биты_5 в обоих числах были нули



00010010 & 01001111 // "И" - только 1 и 1 дают 1
// англ. название AND

00000010 // это результат

// только биты_2 в обоих числах были единицы



00010010 ^ 01001111
/* "исключающее ИЛИ" - результат любое из пары чисел в котором инвертированы (изменены) биты напротив битов равных "1" в другом числе.

англ. название XOR */

01011101 // это результат

/* изменились биты во втором числе напротив
установленных битов 4 и 1 первого числа. */




~ 01001111 /* инвертировать биты
те что были "1" станут "0" и наоборот */

10110000 // это результат



Запомните !

Результатом поразрядных (побитных)
логических операций :


& | ^ ~


является число !

Которое может быть интерпретировано компилятором как "истина"
если оно не ноль и "ложно" если число ноль.




Числа в компиляторе можно записывать в виде указанном в
его Help, обязательно
посмотрите раздел - константы - Constants. Там же указаны диапазоны чисел для данного компилятора.

например - Целые числа могут быть записаны :

- в десятичной форме: 12 234 -5493

- в двоичной форме с префиксом 0b так: 0b101001

- в шестнадцатеричной форме с префиксом 0x так: 0x5А

- в восьмеричной форме с префиксом 0 так: 0775


Числа с плавающей точкой обычно имеют в записи эту точку
- например: 61.234 или так: -73.0 и так: .786
- могут иметь в конце F вот так: 61.234F
- или с указанием степени вот так: 12.7234E-13

Цвета мы применили УСЛОВНО для лучшей читаемости.








Различные представления числа

D3h равно 0xD3 равно 0b1101 0011 равно 211

шестнадцатеричное число 0xD3

0

x

D

3

двоичное представление - число 0b1101 0011

0

b

1

1

0

1

0

0

1

1

номера бита

7

6

5

4

3

2

1

0

два в степени равной номеру бита

128

64

32

16

8

4

2

1

число 211 в десятичном виде
это сумма степеней двойки где биты равны "1"

Сложите

+128

+64

+16

+2

+1



Четыре бита это 1 "нибл" (полубайт) или 1 символ в 16-ричной системе или десятичное число от 0 до 15.

"В уме" удобно оперировать ниблами:





двоичный

десятичный

16-ричный

0000

0

0001

1

0010

2

0011

3

0100

4

0101

5

0110

6

0111

7

1000

8

1001

9

1010

10

A

1011

11

B

1100

12

C

1101

13

D

1110

14

E

1111

15

F




Для перевода чисел из одного вида в другой можно
использовать калькулятор Windows в инженерном виде.





§

> Сброс битов в регистрах.



Ещё одна логическая операция языка Си – побитовое "И", записывается в виде символа "&". Как известно, операция логического "И", применительно к двум битам даёт единицу тогда и только тогда, когда оба исходных бита имеют единичное значение, это позволяет применять её для сброса разрядов в регистрах. При этом используется битовая маска, в которой все разряды единичные, кроме нулевого на позиции сбрасываемого. Её легко получить из маски с установленным N-ным битом, применив к ней операцию побитного инвертирования:

~(1<<N) в этом выражении символ "~" означает смену логического состояния всех битов маски на противоположные. Так, например, если (1<<3) в двоичном представлении – 00001000b, то ~(1<<3) уже 11110111b. Сброс седьмого бита в регистре SREG будет выглядеть так:

SREG = SREG & (~ (1<<7)); или кратко: SREG &= ~ (1<<7);

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

#define OCIE0 1

здесь #define – указание компилятору заменять в тексте программы сочетание символов "OCIE0" на число 1, то есть стандартное имя бита OCIE0, который входит в состав регистра TIMSK микроконтроллера MSP430F5xxx на его порядковый номер в этом регистре. Благодаря этому установку бита OCIE0 в регистре TIMSK можно нагляднее записывать так:

TIMSK|=(1<<OCIE0);

Устанавливать или сбрасывать несколько разрядов регистра одновременно можно, объединяя битовые маски в выражениях оператором логического "ИЛИ":

PORTA |= (1<<1)|(1<<4); // Установить выводы 1 и 4 порта A в единицу;

PORTA&=~((1<<2)|(1<<3)); // Выводы 2 и 3 порта A сбросить в ноль.




§

> Проверка разрядов регистра на ноль и единицу.



Регистры специального назначения MSP430F5438A содержат в своём составе множество битов-признаков, так называемых "флагов”, уведомляющих программу о текущем состоянии микроконтроллера и его отдельных модулей. Проверка логического уровня флага сводится к подбору выражения, которое становится истинным или ложным в зависимости от того установлен или сброшен данный разряд в регистре. Таким выражением может служить логическое "И” между регистром и маской с установленным разрядом N на позиции проверяемого бита :


(REGISTR & (1<<N)) в этом выражении операция "И” во всех разрядах кроме N-ного даст нулевые значения, а проверяемый разряд оставит без изменения. Таким образом возможное значения выражения будут или 0 или 2^N, например для второго бита регистра SREG:







Приведённое выражение можно использовать в условном операторе if (выражение) или операторе цикла while (выражение), которые относятся к группе логических, то есть воспринимают в качестве аргументов значения типа истина и ложь. Поскольку язык Си, приводя числовые значения к логическим, любые числа не равные нулю воспринимает как логическую истину, значение (REGISTR & (1<<N)) равное 2^N в случае установленного бита, будет воспринято как "истина".

Если появляется необходимость при установленном бите N получить для нашего выражения логическое значение «ложь», достаточно дополнить его оператором логической инверсии в виде восклицательного знака - !(REGISTR & (1<<N)). Не следует путать его с похожим оператором побитовой инверсии (~) меняющим состояние битов разряда на противоположное. Логическая инверсия работает не с числовыми значениями, а с логическими, то есть преобразует истинное в ложное и наоборот. Такая конструкция приводится в DataSheet на MSP430F5xxx как пример для ожидания установки бита UDRE в регистре UCSRA, после которого можно отправлять данные в UART:



while ( !( UCSRA & (1<<UDRE)) ) { } // Ждать установки UDRE.



Здесь при сброшенном бите UDRE выражение ( UCSRA & (1<<UDRE)) даст значение ”ложь”, инвертированное

!( UCSRA & (1<<UDRE)) — ”истину”, и пока это так, программа будет выполнять действия внутри фигурных скобок, то есть не делать ничего (стоять на месте). Как только бит UDRE установится в единицу, программа перейдёт к выполнению действий следующих за конструкцией while(), например займётся отправкой данных в UART.




§

> Изменение состояния бита регистра на противоположное.


Эту, с позволения сказать, проблему с успехом решает логическая операция побитного "ИСКЛЮЧАЮЩЕГО ИЛИ” и соответствующий ей оператор Си, записываемый в виде символа " ^ ”. Правило "исключающего или" с двумя битами даёт "истину” тогда и только тогда, когда один из битов установлен, а другой сброшен. Не трудно убедиться, что этот оператор, применённый между битовой маской и регистром, скопирует в результат биты стоящие напротив нулевых битов маски без изменения и инвертирует расположенные напротив единичных. Например, если: reg=b0001 0110 и mask=b0000 1111, то reg^mask=b0001 1001. Таким способом можно менять состояние светодиода, подключенного к пятому биту порта A:



#define LED 5 // Заменять в программе сочетание символов LED на число 5 (вывод светодиода).



PORTA ^=(1<< LED); // Погасить светодиод, если он светится и наоборот.



§

> Арифметика и логика языка Си.



Мы рассмотрели типичный набор операций, используемый при работе с регистрами микроконтроллера. Помимо них в арсенале языка имеется ряд простейших арифметических и логических операций, описания которых можно найти в любом справочнике по Си, например:






Арифметические операции в Си




x + y // сложение
x - y // вычитание
x * y // умножение

x / y /* деление.

Если числа целые результат - целое число с отброшенной дробной частью - не округленное !

т.е. если в результате деления на калькуляторе получается 6.23411 или 6.94 то результат будет просто целое число 6 - запомните !

Если числа с плавающей точкой, то есть float или double и записываются с точкой и числом после точки, то и результат будет число с плавающей точкой без отбрасывания дробной части
131.9739 / 6.18 даст 21.355 */

x % y // вычислить остаток от деления нацело

// примеры:

5 / 2
// даст 2

5
% 2 // даст 1

75 / 29 // даст 2

75 % 29 // даст 17


Операторы сравнения (или отношения):


используются для сравнения переменных, чисел (констант) и выражений.



x < y // X меньше Y
x > y // больше
x <= y // меньше или равно
x >= y // больше или равно
x == y // равно
x != y /* не равно

Результат выполнения этих операторов:

"истина" это "1" (точнее "не ноль")

"ложно" это "0"

Значения хранимые в переменных (в регистрах)
х
и у НЕ изменяются!

Берутся (считываются) значения хранящиеся (или содержащиеся) в переменных и сравниваются */

! /* "НЕ" - логическое отрицание */


Логические операции :



|| // "ИЛИ" - только "ложь" и "ложь"
// дают "ложь"


&& // "И" - только "истина" и "истина"
// дают "истина"

! // "НЕ" - логическое отрицание

/* Правило - в Си считается:

"Ложь" (False) только ноль.

"Истина"(True)- не ноль. или так: (!0)

*/

!(истина) // дает "ложь"

!(ложь) // дает "истина"



В результате логической операции вы получаете
НЕ ЧИСЛО, а логическое значение "истина" или "ложь"

Для логических операций && и || берутся результаты выражений слева и справа от знака операции преобразованные в "истину" или "ложь" и определяется логический результат операции.

Компилятор, для определенности наверно, результат "истина" превращает в 1 а не в любое отличное от 0 число.

Логические операции могут объединять
несколько проверяемых условий.


Например:



if((выражение1)&&((выражение2)||(выражение3)))
{/*
Код программы здесь будет выполняться если:

Выражение1 "Истина" (значит не ноль) и хотя бы одно из выражений 2 и 3 тоже "Истина" (значит не ноль).
};




Подробнее о логических операциях обязательно
прочитайте по в низу этой страницы !




Приоритет операций в языке Си

перечислены в порядке убывания приоритета.


Операции, приведённые на одной строчке, имеют одинаковый приоритет. Операции, помеченные как R->L, исполняются справа налево.


() [] -> .

Унарные (R->L): ! ~ - * & sizeof (type) ++ --

Бинарные арифметические: * / %

Бинарные арифметические + -

Сдвиг: << >>

Сравнение: < <= > >=
Сравнение: == !=

Битовая: &

Битовая: ^

Битовая: |

Логическая: &&

Логическая: ||

Тернарная (R->L): ?:

Операции с присваиванием (R->L):
= += -= *= /= &= |= ^= <<= >>=


Совет:

Чтобы точно знать порядок выполнения операций программой используйте скобки ( )

( () + ( () * () ) )

Ведь скобки ( ) имеют наивысший приоритет.



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

Когда происходит и в чём заключается приведение типов? Таких ситуаций достаточно много. Рассмотрим наиболее опасные из них.




§

> Преобразование типа выражения перед присвоением переменной.




В первом разделе мы обращали своё внимание на необходимость явного указания типа объявляемой переменной. Это позволяет компилятору зарезервировать за ней нужное количество адресного пространства и определить диапазон значений, которые она способна хранить. Тем не менее, мы не застрахованы он того, что в процессе выполнения программы произойдёт попытка записать в переменную значение свыше предельно допустимого. В самых грубых случаях компилятор выдаст нам сообщение о возможной ошибке. Например, при желании записать в переменную типа unsigned char (диапазон от 0 до 255) число 400:



unsigned char a=400; // выдаст сообщение типа "integer conversion resulted in truncation”


компилятор предупреждает нас о том, что произошла попытка записать числовое значение, требующее для хранения два байта (400 это 1 в старшем байте и 144 в младшем) в однобайтовую переменную. Однако тех случаях, когда присваиваемое выражение содержит переменные, и компилятор мог бы заметить возможную потерю данных, он освобождает себя от этой обязанности, например:



unsigned char x=200, y=200;

x=x+y;



при таком варианте, не смотря на то, что значение выражение (x+y) так же равно 400, никаких предупреждений со стороны компилятора уже не последует. А в переменную x запишется только младший байт числа 400, то есть 144. И здесь компилятор трудно в чём-то упрекнуть, ведь вместо явно проинициализированной переменной в выражении может быть использован, например, приёмный регистр шины UART, в котором может оказаться любое значение, принятое от внешнего устройства.

Другой пример в этом же духе – присвоение дробного значения переменной целого типа:



float a=1.5; // Объявлена переменная с плавающей точкой.

char b=3; // Объявлена целочисленная переменная.

b=a*b; // Ожидается, что в переменную b будет записано значение 4,5.



В результате в переменной b сохранится только целая часть результата a*b – число 4.




§

> Преобразование результата выражения к типу наиболее точной переменной в выражении.



При таком преобразовании компилятор руководствуется следующим правилом: прежде чем начнется вычисление выражения, операторы с "низшим” типом повышаются до "высших” при этом результат также приводится к ”высшему” типу. Какой тип нужно считать ”высшим”? Тот, который без потери точности может сохранить любое допустимое значение другого типа. Так, в предыдущем примере:



float a =1.5; // Объявлена переменная a с плавающей точкой.
char b=3; // Объявлена целочисленная переменная.


В выражении (a*b) переменная float a имеет более высокий тип, потому что может сохранять любое целое значение из диапазона 0…255 типа char. Результат выражения (a*b) будет иметь тип float.
Типичный пример неожиданности для этого случая – попытка получить дробное число делением двух целочисленных:



char a=3; // Объявлена целочисленная переменная.
char b=4; // Объявлена целочисленная переменная.
float c; // Объявлена переменная "c" с плавающей точкой для сохранения результата.
c=a/b; // Ожидается, что
"c" будет равно 0,75 (¾).


В отличие от предыдущего примера, результат записывается в переменную способную хранить числа с плавающей точкой, однако компилятор в соответствии с правилом приведения, получив в результате деления число 0,75 приводит его к типу целочисленных операндов, отбросив дробную часть. В результате в переменную "c” будет записан ноль.
Более реалистичный пример из жизни – расчёт измеряемого напряжения из выходного кода АЦП:



int ADC; // Двухбайтовая целочисленная переменная для хранения кода АЦП.
float U; // Переменная с плавающей точкой для сохранения значения напряжения.
U= ADC*(5/1024); // Расчёт напряжения.


Здесь упущено из виду то, что константа в Си, как и любая переменная, тоже имеет свой тип. Его желательно указывать явно или, используя соответствующую форму записи. Константы 5 и 1024 записаны без десятичной точки и будут восприняты языком Си как целочисленные. Как следствие, результат выражения (5/1024) тоже будет приведён к целому – 0 вместо ожидаемого 0,00489. Это не случилось бы при записи выражения в формате (5.0/1024).

Приведённых ошибок также можно избежать, используя оператор явного приведения типов выражений языка Си, который записывается в виде названия типа, заключённого в круглые скобки и воздействует на выражение стоящее после него. Этот оператор приводит результат выражения к явно указанному типу, не взирая на типы его операндов:



c= (float) a/b; // Ожидается, что "c" будет равно 0,75 (¾);
U= ADC * ( (float)5/1024 ); // Расчёт напряжения.




§

> Назначение функций.



Ещё древние программисты обратили своё внимание на один занимательный факт – зачастую программа вынуждена несколько раз выполнять ровно одну и ту же последовательность действий. Именно тогда родилась идея при достаточно большом наборе таких действий и их повторов, с целью экономии программной памяти, оформлять их в виде отдельной группы, а затем при необходимости просто отправлять программу на её выполнение. Такой обособленный кусок кода в Си как раз и называется функцией. Само название термина "функция” исконно отражает другое свойство некоторых функций – способность (подобно функциям математическим) преобразовывать по заданному алгоритму некие входные данные. Но этом немного позже.

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

Такие функции могут вызываться программой только один раз. Зачем же тогда они нужны? Для обоснования такого подхода в литературе часто приводится фраза неизвестного, но по всей видимости, очень авторитетного древнеримского программиста: " Разделяй и властвуй!”. И действительно, программа, оформленная в виде целевых функциональных блоков гораздо проще для понимания, отладки и последующей модификации, чем набор отдельных, разрозненных по назначению кусков кода.

Обобщая сказанное, можно сформулировать формальные предпосылки к созданию функций в программе, это:

1. Наличие одинаковых, достаточно больших и многократно повторяющихся наборов действий.

2. Желание структурировать программу в виде отдельных блоков с общим функциональным назначением.


Здесь нужно сразу сделать важную оговорку. Дело в том, при каждом переходе на функцию и возврате из неё микроконтроллер вынужден сохранять некоторые системные данные, например адрес программы, с которого произошёл переход в функцию, а это требует дополнительных временных ресурсов. Нужно учитывать этот факт и стараться не плодить в программе множество коротких функций, если есть возможность объединить их в одну общую.




§

> Структура и оформление функций.


В любой функции структурно легко выделить две составные части: заголовок и тело функции.

Заголовок это самая первая строчка любой функции вида:



Тип выходной переменной Имя функции (Типы входных переменных и их имена через запятую)



Временно опустим рассмотрение содержимого заголовка до и после имени и рассмотрим функции, которые не обрабатывают никаких данных. Они предназначены только для выполнения определённых действий. В заголовках таких функций нужно указать названия пустого типа – void (англ. вакуум, пустота):



void имя функции (void)



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



void initialization (void)


можно вызвать так:


initialization ();



Тело функции это набор команд расположенный между первой открывающейся фигурной скобкой после заголовка и соответствующей ей закрывающейся фигурной скобкой. Пояснение: наборы действий внутри фигурных скобок в Си принято называть блоками. Они логически связывают несколько одиночных действий в одно сложное, которое либо полностью выполняется, либо полностью игнорируется в зависимости от контекста программы. В данном случае тело функции представляет собой блок команд, которые функция обязана выполнить от начала и до конца. Таким образом, программа, встретив переход на функцию, выполнит содержимое блока и по последней закрывающей скобке вернётся туда, откуда была вызвана.



Например, функция:



void initialization (void)

{

DDRA=0xFF; // PORTA на выход.

DDRB|=(1<<0)| (1<<3)| (1<<4); // PB0, PB3, PB4 на выход.

DDRC=0xF0; // Старшая тетрада PORTC на выход.

}




проинициализировав направление выводов портов A, B и С, вернётся в следующую после её вызова строчку.

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



§

> Обработка параметров функцией.



Всё функции могут обрабатывать и изменять значения специализированных регистров микроконтроллера и так называемых глобальных переменных, то есть тех, которые объявлены пользователем в самом начале программы вне каких либо функций. Помимо этого имеется возможность передавать данные в функцию для обработки непосредственно в момент её вызова. Это просто удобно и ничего больше.

Эти данные называются параметрами, передаваемыми функции. Они должны быть перечислены через запятую вместе с их типами в заголовке функции, внутри круглых скобок после её имени:



void initUart (char FrameLength, char StopBit)



Такое оформление заголовка будет означать, что функция способна принимать в качестве параметров два числа типа char с именами FrameLength и StopBit. Теперь при вызове функции компилятор не позволит оставить круглые скобки пустыми и потребует передачи конкретных значений, через запятую, например:


initUart (8, 2);


После этого внутри функции переменным с именами FrameLength и StopBit присвоятся конкретные значении 8 и 2, которые можно использовать, например, для настройки длинны посылки модуля UART и количества его стоп-битов:

void initUart (char FrameLength, char StopBit)

{

if (FrameLength==8) UCSR0C|=((1<<1)|(1<<2));

if (StopBit==2) UCSR0C|=(1<<3);

}




§

> Специализированные функции.



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

Самая главная функция такого рода, как это видно и самого её названия это функция main. Она характеризуется тем, что выполнение передаётся на неё самим микроконтроллером при подаче питания или после перезагрузки, то есть, именно с неё и начинается работа любой программы. Еще одно свойство функции main состоит в том, что при её выполнении до конца программа автоматически перейдёт на её же начало, то есть она выполняется по циклу, если внутри её самим пользователем специально не был организован бесконечный цикл.

Ещё один вариант системных функций – обработчики прерываний. Их так же невозможно вызвать программно. Микроконтроллер самостоятельно передаёт управление на них в случае возникновения особых аппаратных состояний – условий вызова прерываний. Подробнее об этом читайте в следующей главе.


Очевидно, что любая программа представляет собой совокупность действий, описанных в соответствии с правилами языка программирования и предназначенных для исполнения конкретным устройством, в данном случае микроконтроллером. Каков порядок выполнения этих действий на практике, мы и попытаемся уяснить в данном разделе.



§

> Общая структура простейшей программы. Инициализация, фон.


При рассмотрении программы на уровне языка Си можно сказать, что она начинает свою работу с первой строки функции main (строка 001 на рисунке):






Далее последовательно выполняются строки 002, 003, 004, объёдинённые одним общим свойством: программа проходит по ним только один раз, при запуске микроконтроллера. Эту часть программы принято называть инициализационной. Инициализационная часть - законное место для размещения действий по подготовке периферии микроконтроллера к работе с заданными параметрами - настройки портов на вход или выход, начальной инициализации таймеров, задания скорости и формата кадра UART и так далее для всего, что планируется использовать в дальнейшем.

Поскольку любая программа предназначена для непрерывной работы, нормальный режим её функционирования - это безостановочное повторение по кругу содержимого бесконечного цикла. На практике такой цикл чаще всего реализуется с помощью конструкции while(1) { }, предназначенной для многократного выполнения действий, размещённых внутри её фигурных скобок. Содержимое бесконечного цикла программы называется фоном. Именно здесь происходит основная часть работы по проверке состояния аппаратной части и соответствующее воздействие на неё для получения нужного результата.

Рассмотрим описанную структуру программы на простейшем примере. Пусть необходимо: отправлять по шине UART символ *, пока кнопка на выводе PA0 находится в нажатом состоянии (нулевой уровень сигнала). Программа в данном случае (без лишних процедур по подавлению дребезга кнопки и прочего) может выглядеть так:




void main (void)
{
PORTA|=(1<<0); // Притянуть вход кнопки PORTA.0 внутренним pull-up резистором.
UBRRL=51; // Скорость UART – 9600 bps.
UCSRB = (1<<RXEN)|(1<<TXEN); // Разрешить выводы RX, TX.
while (1)

{
if ( ! (PINA & (1<<0)) ) // Если кнопка нажата...
{
while( ! (UCSRA & (1<<UDRE)) ) { } // Ждать освобождения UDR.
UDR = ' * '; // Отправить *.
}

// другие команды фона:

00N

00N+1

...

}
}



Здесь конструкция if (...), расположенная в фоне программы проводит бесконечные опросы входного регистра PINA и проверку вывода PA0 на наличие низкого уровня. Далее выполняются другие действия фонового процесса, обозначенные строками 00N, 00N+1 и так далее.


Какие факторы, применительно к данной программе, определяют самые важные параметры её работы - надёжность и быстродействие?

Из примера видно, что частота опроса входа PA.0 определяется длительностью выполнения команд фона. Ведь прежде чем в очередной раз опросить кнопку, микроконтроллер должен выполнить следующие за этим строки 00N, 00N+1 и т. д. Очевидно, что надёжность фиксации внешнего события (нажатия на кнопку) в данном случае будет зависеть от соотношения длительности воздействия этого события к периоду его детектирования. Длительность фона в данной программе наверняка будет во много раз меньше длительности удержания кнопки, которое на практике составляет несколько десятков миллисекунд. Однако при разрастании фоновой части программы и малом времени внешнего воздействия, надёжность его отслеживания в определённый момент резко снизится. Что бы этого не произошло, а также для снижения времени реакции программы на внешнее событие, используется система прерываний.





§

> Прерывания.


Как работает механизм прерываний? Очень просто, особенно на уровне языка Си!


В архитектуру микроконтроллеров MSP430 , впрочем как и любых других, на аппаратном уровне заложена способность отслеживать определённые "интересные состояния железа” и устанавливать при этом соответствующие биты-признаки. Такие состояния называются условиями возникновения прерываний, а устанавливаемые признаки - флагами прерываний. В процессе работы, микроконтроллер непрерывно отслеживает состояние этих флагов. При обнаружении любого установленного флага прерывания, при условии, что оно разрешено включением соответствующего бита, а также установлен бит глобального разрешения прерываний (№7 в регистре SREG для MSP430F5438A), выполнение основной части программы будет временно приостановлено (прервано).


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








Какова роль программиста в этом процессе? При разработке на Си она сведена к минимуму.

Часть действий, как то отслеживание флагов прерываний реализованы на аппаратном уровне. Другую часть, например, защиту от изменений в обработчике важного для программы регистра статуса SREG, сохранение адреса программы в стеке и многое другое компилятор берёт на себя.

Единственное, в чём остаётся необходимость это:




1. Разрешить использование прерываний в программе.
2. Разрешить вызов интересующего нас прерывания специальным битом в соответствующем регистре. Каким именно и где подскажет описание на микроконтроллер.
3. Создать условия для возникновения прерывания, например, если это переполнение таймера, то банально запустить его. Если это прерывание по изменению состояния внешнего вывода то задать нужные условия для этого (фронт, срез или нулевой уровень).
4. Разместить в программе обработчик прерывания, оформив его в соответствии с требованиями компилятора.




Применительно к нашему примеру организовать отправку в UART по низкому уровню на входе кнопки, можно используя так называемое внешнее прерывание INT0. Данное прерывание вызывается по фронту, срезу или нулевому уровню на выводе INT0.


Перенесем кнопку на вывод PD.2 с альтернативной функцией INT0. В инициализационной части программы разрешим прерывания глобально и INT0 конкретно. Микроконтроллер по-умолчанию настроен на формирование прерывания INT по низкому уровню входного сигнала, поэтому дополнительных настроек не потребуется. Остаётся объявить за пределами функции main обработчик INT0, отправляющий в UART символ *:




void main (void)
{
PORTD|=(1<<2); // Притянуть вход кнопки PORTD.2 внутренним pull-up.
UBRRL=51; // Скорость UART – 9600 bps.
UCSRB = (1<<RXEN)|(1<<TXEN); // Разрешить RX,TX.
SREG|=(1<<7); // Разрешить прерывания.
GICR|=(1<<INT0); // Разрешить прерывание INT0.
while (1){}
}

#pragma vector=INT0_vect // Обработчик прерывания INT0/
__interrupt void INT0_INTPT()
{
if ( ! (PIND & (1<<2)) ) {while( ! (UCSRA & (1<<UDRE)) ) {} UDR = '*';}
}




Здесь обработчик прерывания объявлен в формате компилятора Code Composer V4. Принципиально в нём только имя вектора прерывания - INT0_vect, компилятор заменяет его на адрес памяти программ, на который передаётся выполнение программы при возникновении данного прерывания. Имя самого обработчика INT0_INTPT выбирается произвольно. Названия векторов всех возможных прерываний для данного МК описаны в h-файлах.


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

Хочется сразу упомянуть одно негласное правило относительно обработчиков прерываний, хоть это и достаточно узкий вопрос. В них следует размещать только то, что на самом деле необходимо для быстрой реакции на прерывание. Все остальные действия, которые можно отложить, необходимо размещать в фоне.

С чем это связано?

Если события, вызывающие прерывание, происходят достаточно часто, то на момент возникновения следующего прерывания слишком длинный обработчик может не успеть выполниться до конца. А это чревато неприятными последствиями в виде потери данных и нарушения нормальной последовательности действий. Например, если необходимо принять по UART некий массив байтов, то в обработчике, который вызывается после приёма каждого из них, не следует заниматься пристальным изучением принятых данных, а только переписывать их с заранее заготовленный массив. А уже после приёма последнего из них, в обработчике можно выставить соответствующий признак (мол, всё принято) и в фоне, обнаружив его, спокойно заняться исследованием всего принятого массива.


Написать:
22:33
1822
Нет комментариев. Ваш будет первым!