Встраиваемые системы. Проектирование приложений на микроконтроллерах семейства 68HC12/HCS12 с применением языка С - Стивен Барретт
Шрифт:
Интервал:
Закладка:
Применение указателей в реальных программах требует особого внимания программистов, поскольку именно с указателями связано основное количество ошибок в исходном тексте программы на Си. При манипулировании указателями, а также при выполнении операций присваивания следует убедиться в корректном использовании различных типов переменных. Приведем пример применения указателя:
1 void main(void)
2 {
3 char *ptr;
4 static char message[] = "What a wonderful day!";
5 ptr = message;
6 printf("%sn", ptr);
7 }
В этом примере переменная ptr была определена как указатель для однобайтовой переменной типа char. Поэтому переменная ptr должна содержать 16 разрядный адрес однобайтовой переменной. В строке 4 примера был определен и инициализирован массив однобайтовых переменных message. Этот массив содержит 22 элемента: 21 символ в кодах ASCII и один байт признака конца строки. Выражение в строке 5 имеет своей целью запись в переменную ptr начального адреса массива message, т.е. адреса символа «W». Выражение, записанное в строке 6, должно начать вывод на экран монитора символа, на который указывает содержимое переменной указателя ptr. В строке 5 данного примера может быть записано другое, более понятное выражение:
ptr = &message[0];
В этом выражении символ «&» выполняет операцию взятия адреса первого элемента массива message. И именно эта функция была реализована нами в строке 5 исходного примера.
Выполняемое в рассмотренном выше программном фрагменте действие, может быть оформлено на Си другим образом, без использования указателя:
void main(void) {
static char message[] = "What a wonderful day!";
int i;
for (i=0; i<21; i++) putchar(message[i]);
}
Обратите внимание, что в данном примере внутренняя переменная цикла i принимает значения от 0 до 20 включительно, выводя при этом на экран 21 символ. Приведенный способ реализации задачи вывода на экран строки символов обладает одним существенным недостатком. При смене числа символов в записи, выводимой на экран, придется написать новый фрагмент кода. Указанный недостаток исправлен в следующем программном фрагменте:
void main(void) {
char *ptr;
static char message[] = "What a wonderful day!";
ptr = message;
while(*ptr != ' ') {
putchar(*ptr);
ptr++;
}
}
В этом примере указатель ptr используется для передачи в функцию putchar одного кода символа, который будет выведен на экран. Далее содержимое ptr увеличивается на 1, адресуя тем самым следующий элемент массива. По условию цикла while вывод на экран закончится, если в последовательно перебираемом массиве будет найден код конца строки.
Во встраиваемых системах указатели используются для обращения к регистрам специальных функций микроконтроллера. Рассмотрим следующую запись:
#define PORTA *(volatile unsigned char *) 0x1000
Эта запись информирует компилятор о том, что однобайтовая целочисленная переменная с именем PORTA располагается в памяти по адресу 0x1000. Служебное слово volatile информирует программу (и отладчик) о том, что значение PORTA может изменяться не только под управлением программы. Любое упоминание имени PORTA далее по тексту программы будет связано с выполнением операций чтения или записи в регистр данных PORTA по его физическому адресу 0x1000. Поэтому следующий программный фрагмент позволит прочитать содержимое порта PORTA в переменную new_value:
DDRA = 0x00; //инициализировать порт PORTA на ввод
new_value = PORTA; //читать содержимое регистра данных PORTA в
//переменную new_value
3.12. Структуры
Возможности языка Си позволяют объединить под одним именем переменные с разным форматом представления данных. Для этого используется понятие структуры.
Структура — это объект, состоящий из данных различных типов. При использовании структур следует различать объявление (или описание) структуры, как нового типа данных, например тип данных «структура x», от фактического определения некоторой переменной с типом данных «структура x». Пример объявления структуры типа car:
struct car {
int doors;
char color[10];
char *maker;
int num_cyl;
int year;
}
Служебное слово struct указывает начало объявления или определения структуры. За ним следует имя, которое присваивается данной структуре, оно называется идентификатором структуры. В фигурных скобках указывается список определений отдельных элементов, образующих структуру. Элементы структуры могут иметь любой тип, включая массивы, другие структуры и объединения.
Анализируя приведенную запись, отметим, что имя car — это имя нового типа данных. Не следует путать его с именем переменных типа car, которые далее будут определены в программе:
struct car your_car;
struct car my_car;
struct car his_car;
Записав приведенные выше строки, мы определили в программе три переменные с именами your_car, my_car, his_car, и каждая из этих переменных представляет собой структуру с одним и тем же набором элементов. Так первым элементом каждой из этих переменных структур будет двухбайтовое целое число. Численные значения этих первых элементов для всех переменных структур различаются, но формат представления данных для всех трех первых элементов одинаков.
Каждый элемент структуры имеет свое собственное имя: doors, color, maker и т.д. Допускается обращение к каждому элементу структуры с использованием его имени:
your_car.doors = 4;
Если переменная типа структура передается в качестве параметра в какую либо функцию, то следует помнить, что передаются не сами элементы структуры, а лишь указатель на первый элемент названной структуры. Поэтому при обращении внутри функции к отдельным элементам структуры следует вместо оператора «.» использовать оператор «–>». Например, в программе определяется функция assign_doornumber, которая присваивает численное значение первому элементу структуры типа car:
void assign_doornumber(struct car *some_car);
:
:
;
assign_doornumber(&your_car)
:
:
void assign_doornumber(struct car *some_car) {
some_car–>doors = 4;
return 0;
}
А сейчас мы обсудим, как создать массив структур. Допустим, что мы уже определили структуру circuit_board как новый тип данных:
struct circuit_board {
int transistor;
int bus;
int serial_port;
int parallel_port;
}
Создадим теперь массив new_board с пятью элементами, каждый из которых является структурой типа circuit_board:
struct circuit_board new_board[5];
К каждой записи внутри каждого элемента этого массива можно обратиться, используя номер элемента (фактически это номер структуры внутри массива) и имя записи внутри структуры:
new_board[2].transistor = 100;
Это выражение присваивает значение 100 записи под именем transistor третьего элемента массива new_board (элементы в составе массива отсчитываются с нулевого). Этим разделом мы закончили краткий обзор техники программирования МК на Си. Перейдем теперь к рассмотрению процесса генерации файла исполняемого кода программы для выбранного типа МК из файла исходного текста программы на Си.
3.13. Процесс программирования и отладки микропроцессорной системы
В данном разделе мы обсудим технологию создания и отладки прикладной программы в микроконтроллерных системах. Сначала рассмотрим процесс создания исходного текста управляющей программы и его превращение в файл исполняемого кода (ехеcutable file).
3.13.1. Технология создания программного кода
Процесс программирования кажется достаточно простой монотонной деятельностью до тех пор, пока задача управления, которую требуется реализовать, достаточно проста. Однако программисты с опытом знают, что большинство технических заданий удивительно быстро превращается достаточно сложный проект, часть задач которого разработчиком ранее не реализовывалась. Именно по этой причине необходимо правильно организовать исполнение проекта, даже если первоначальная задача кажется простой. Поэтому авторы настоятельно советуют читателю приобрести навыки системного подхода к написанию программ, независимо от языка программирования, на котором реализуется проект. Поскольку данная книга не претендует на системное изложение вопросов теории программирования, то наши советы по данной теме будут достаточно краткими.
При написании программы первым делом необходимо как можно более полно понять принцип действия и особенности работы того устройства, для которого программа предназначена. На основе этих знаний следует создать такую структуру программы, которая позволит удовлетворить как текущему техническому заданию, так и возможным будущим его дополнениям. На первых порах Вы можете начать самостоятельную работу, следуя рекомендациям главы 2. Вы уже умеете перейти от произвольного описания алгоритма управления к структуре функций управления и блок схемам алгоритмов. Рассмотренные в главе 2 методы структурного проектирования позволят Вам сначала увидеть задачу управления целиком, а затем произвести ее разбиение на отдельные блоки (подзадачи). Каждая такая подзадача должна быть оформлена в виде законченной подпрограммы или функции. Выделенные функции впоследствии будут объединены в один программный модуль, который должен быть отлажен и тестирован до того, как все другие составляющие модули программы будут объединены. Процессы написания, тестирования и отладки программ близко связаны. Вы никогда не должны сначала собрать полный текст программы, а затем начать ее отлаживать. Правильным подходом является написание, тестирование и отладка каждого программного модуля по отдельности перед тем, как эти модули будут объединены вместе. Процесс объединения должен использовать тот же подход: добавлять уже оттестированные модули к отлаженной части общей программы следует последовательно. И после добавления каждого нового модуля не лениться тестировать функционирование получившегося нового промежуточного программного продукта, в том числе убедиться в исполнении новых добавленных функциональных возможностей.