Введение во встроенные системы и Windows Embedded CE

Программирование приложений в CE

Показывать лекцию целиком

Эту швейную машину Bernina Artisita можно запрограммировать для шитья сложными стежками. Можно загружать также новые стежки. На ней выполняется Windows Embedded CE. Фотография с разрешения Mike Hall.

Программирование приложений в CE

В качестве введения в разработку прикладных программ рассмотрим несколько коротких программ C/C++ для Windows Embedded CE для демонстрации базовых операций В/В на устройстве eBox, использующем Windows Embedded CE. Предполагается, что читатель уже знаком с базовым синтаксисом C/C++.

Чтобы сохранить эти программы короткими и легкими для рассмотрения, все несколько первых примеров будут простыми консольными приложениями CE (в консольное окно посылается только текст -- никаких окон или графики). Программы C/C++ могут писать текст на консоль, используя функцию printf для строк ASCII или функцию wprintf для строк Unicode. Это сохранит начальные примеры программ короткими и немного легче для рассмотрения. В последующих примерах будут рассмотрены более типичные приложения Windows (на основе GUI) и пример на C#.

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

Файлы исходного кода доступны для всех демонстрационных программ. Эти файлы можно скопировать с компакт-диска, приложенного к этому документу, и установлены как подпроекты в дизайн вашей ОС для быстрой демонстрации. Если они будут включены в ОС, примеры C/C++ можно выполнить с помощью Target $$\Rightarrow$$ Run Programs, или запустить, используя окно команд eBox. Программы C# необходимо будет развернуть, как было описано в последнем уроке в лекции 7.

Пример программы FileIO

Эта программа C++создает файл, записывает файл, и закрывает новый файл. Затем она открывает файл для операции чтения, считывает данные из файла, выводит их, и снова закрывает файл. На консоли выводятся статусные сообщения. После небольшой паузы, чтобы позволить пользователю просмотреть статусные сообщения в консольном окне, программа автоматически завершается. В Windows Embedded CE эти операции с файлами выполняют с помощью вызовов функций API CreateFile, ReadFile, WriteFile, и CloseHandle API.

Функция API CE Sleep(milliseconds) используется для задания паузы в программе. Отметим использование Unicode для строк имен файлов с помощью _T("..") и TCHAR. После выполнения программы можно также проверить содержимое нового файла, \Temp\CE_Demo.txt, используя на устройстве редактор, такой как WordPad. Отметим использование в C строки "\\" для представления одного символа обратной косой черты.

// FileIO.cpp : Определяет точку входа для консольного приложения.
//
// пример программы FileIO 
// Показывает как создать,записать, и прочитать файл, используя API файлов CE
//
// Помните, что CE использует 16-битный Unicode для символьных строк!
//		_T("....") генеририует строку Unicode
//		TCHAR является строковым типом Unicode 
//
#include "stdafx.h"
int _tmain(int argc, TCHAR *argv[], TCHAR *envp[]){
	HANDLE hFile;
	DWORD cBytes;
	char cBuffer[] = "Hello File World\n";
	printf("\nCreating CE_Demo.txt file\n");
// Открытие файла для записи 
hFile = CreateFile(_T("\\Temp\\CE_Demo.TXT"), GENERIC_WRITE, FILE_SHARE_WRITE, 
  NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

// Проверка ошибок открытия файла 
	if (hFile == INVALID_HANDLE_VALUE){
		printf("file write open errors\n");
		Sleep(1000);
		return 0;
	} // Запись в файл 
	if (!WriteFile(hFile, cBuffer, strlen(cBuffer), cBytes, NULL)){
		printf("file write errors\n");
		Sleep(1000);
		return 0;
	}
// Закрытие файла 
	CloseHandle(hFile);

// Снова открываем файл для чтения 
	hFile = CreateFile(TEXT(""), GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// Проверяем ошибки открытия файла 
	if (hFile == INVALID_HANDLE_VALUE){
		printf("file read open errors\n");
		Sleep(1000);
		return 0;
	}
// Считываем только что записанные в новый файл данные
	if (!ReadFile(hFile, cBuffer, 79, cBytes, NULL)){
		printf("file read errors\n");
		Sleep(1000);
		return 0;
	}
// Выводим считанные данные и делаем задержку на 4 секунды 
// перед выходом из программы 
	printf("%s\n",cBuffer,cBytes);
	printf("the file data was written and read back OK\n");
	Sleep(4000);
// Закрытие файла
	CloseHandle(hFile);
    return 1;
}

Пример программы SerialIO, использующей вызовы В/В файлов ОС

Пример программы C++ читает последовательный ввод данных с COM2:, выводит каждый символ в консольном окне eBox, и отправляет его назад на вывод на COM2:.

Программа использует базовые функции API В/В файлов CE, такие как CreateFile, ReadFile, WriteFile, и CloseHandle для передачи данных в/из последовательного порта. В функции ModifyCommSettings используются вызовы API GetCommStatus и SetCommStatus для получения, модификации, и затем задания для порта COM скорости в бодах, числа битов данных, и вариантов квитирования. Вызов API SetCommTimeouts используется для задания задержек для операций последовательного чтения и записи. Его можно использовать для определения, что операция чтения должна подождать ввода символа или вернуть управление немедленно.

Вызов API WaitCommEvent используется для блокирования процесса, пока не появится входящий символ. Это более эффективно в терминах времени процессора и питания, чем постоянное циклическое повторение. SetCommEvent используется для определения, какой тип коммуникационного события будет запускать WaitCommEvent. Обычно основной процесс не будет блокироваться, и для чтения и записи последовательного порта будет использоваться поток. Так как основной процесс в этом примере только посылает данные назад, то поток не требуется.

Для просмотра последовательных данных последовательный null-модемный кабель соединяется из COM2: на eBox с неиспользуемым портом COM на настольном ПК системы разработки. Выполняя HyperTerminal или другую программу эмулятора терминала, соединитесь с портом COMx: на скорости 9600 бод, с 8 битами данных, без контроля четности, с 1 стоп битом, и без задания управления потоком.

Когда выполняется программа, она печатает заголовок, а затем каждый символ, введенный на клавиатуре настольного ПК посылается в устройство eBox, которое считывает символы, выводит их в консольном окне, и посылает символы назад. Так как они посылаются назад, они также выводятся в эмуляторе терминала. Ввод Ctrl-C в эмуляторе терминала заставляет eBox выйти из программы, а вызов CloseHandle освобождает порт COM для использования в других приложениях.

(рис 8.1) HyperTerminal выполняющийся в системе разработки и взаимодействующий с COM-портом eBox
// SerialIO.cpp : Определяет точку входа для консольного приложения.
//
// Пример программы В/В файла через последовательный порт 
//
// FOR DEMO: Соедините Ebox COM2: с ПК с помощью null-модемного кабеля 
// Выполните HyperTerminal на скорости 9600 бод, с 8 битами данных, 1 стоп 
// битом, без четности и без управления потоком 

#include "stdafx.h"
BOOL ModifyCommSettings (HANDLE hComPort);
HANDLE hSerial;

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	DWORD cBytes_out, cBytes_in;
	DWORD dwMask;
	char cBuffer_out[] = "\f\n    Hello Serial World! \n\r Type something and watch it echo back
    \n\r Ctrl C to exit   \n\r";
	TCHAR cBuffer_in[80];
// Выводит сообщение на консоль 
	printf("\nOpening COM2: Serial Port - Type ctrl C on other device to exit\n\r");
// Откройте последовательный порт COM2: для чтения и записи 
// Примечание: COM1: обычно задается для отправки отладочной информации 
// В этом случае COM2: становится COM1: в ОС
	hSerial = CreateFile(_T("COM1:"), GENERIC_READ | GENERIC_WRITE, 0, NULL,
		OPEN_EXISTING, 0, NULL);
// Проверка ошибок открытия файла 
	if (hSerial == INVALID_HANDLE_VALUE){
		printf("file open errors\n");
		Sleep(4000);
		return 0;
	}
// Модифицируйте настройки порта Com (т.е., Baud Rate, #bits, parity и т.д.)
	if(!ModifyCommSettings (hSerial)){
		printf("com port settings errors\n");
		Sleep(4000);
		return 0;
	}

  // Запишите заголовок в последовательный порт.
	if (!WriteFile(hSerial, cBuffer_out, strlen(cBuffer_out), cBytes_out, NULL)) {
		printf("file write errors\n");
		Sleep(4000);
		return 0;
	}
// Задаем маску коммуникационного события для WaitCommEvent для rxchar (получить символ) в буфер 
	SetCommMask(hSerial, EV_RXCHAR | EV_ERR);

	cBuffer_in[0] = 0;
// Считать символы, скопировать в консольное окно и Echo
// Цикл, пока не будет нажато ctrl C 
	while (cBuffer_in[0] != 0x03){
// Ожидаем символ в буфере ввода - события являются более эффективными, чем 
// цикл 
		WaitCommEvent(hSerial, dwMask, 0);
		cBytes_in = 1;
// Цикл на тот случай, если более одного символа находится в буфере ввода 
// UART 
		while (cBytes_in != 0){
// Эхо-считывание всех последовательных данных и вывод 
		  if (ReadFile(hSerial, cBuffer_in, 64, cBytes_in, NULL)){
			if (cBytes_in == 0) break;
// Вывод эхо-считанных данных 
			printf("%s",cBuffer_in, cBytes_in);
// Эхо-Отправка символов отправителю 
			if (!WriteFile(hSerial, cBuffer_in, cBytes_in, cBytes_out, NULL)){
				printf("\rfile write errors\n");
				Sleep(4000);
				return 0;
				}
		  }
		}
	}
	// Закрытие файла 
	CloseHandle(hSerial);
    return 1;
}
// Функция для задания параметров порта COM 
BOOL   ModifyCommSettings (HANDLE hComPort)
{
      COMMTIMEOUTS ctos;
	DCB PortDCB;
// Инициализация члена DCBlength. 
	PortDCB.DCBlength = sizeof (DCB); 
// Получение информации DCB о настройках по умолчанию последовательного порта
	GetCommState (hSerial, PortDCB);
// Изменение настроек обычной структуры DCB для модификации настроек 
// последовательного порта.
PortDCB.BaudRate = 9600;              // Current baud 
PortDCB.fBinary = TRUE;               // Binary mode; no EOF check 
PortDCB.fParity = TRUE;               // Enable parity checking 
PortDCB.fOutxCtsFlow = FALSE;         // No CTS output flow control 
PortDCB.fOutxDsrFlow = FALSE;         // No DSR output flow control 
PortDCB.fDtrControl = DTR_CONTROL_ENABLE; // DTR flow control type 
PortDCB.fDsrSensitivity = FALSE;      // DSR sensitivity 
PortDCB.fTXContinueOnXoff = TRUE;     // XOFF continues Tx 
PortDCB.fOutX = FALSE;                // No XON/XOFF out flow control 
PortDCB.fInX = FALSE;                 // No XON/XOFF in flow control 
PortDCB.fErrorChar = FALSE;           // Disable error replacement 
PortDCB.fNull = FALSE;                // Disable null stripping 
PortDCB.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control 
PortDCB.fAbortOnError = FALSE;        // Do not abort reads/writes on error
PortDCB.ByteSize = 8;                 // Number of bits/byte, 4-8 
PortDCB.Parity = NOPARITY;            // 0-4=no,odd,even,mark,space 
PortDCB.StopBits = ONESTOPBIT;        // 0,1,2 = 1, 1.5, 2 
// Конфигурирование настроек порта согласно новым спецификациям 
// структуры DCB.
	if (!SetCommState (hSerial, PortDCB)){
	    printf("Unable to configure the serial port"); 
		Sleep(4000);
		return false;
		}
	// Set read time outs
	ctos.ReadIntervalTimeout = MAXDWORD;
	ctos.ReadTotalTimeoutMultiplier = MAXDWORD;
	ctos.ReadTotalTimeoutConstant = 1;
	ctos.WriteTotalTimeoutMultiplier = 0;
	ctos.WriteTotalTimeoutConstant = 0;
	if(!SetCommTimeouts(hSerial, ctos)){
	    printf("Unable to configure the serial port"); 
		Sleep(4000);
		return false;
	}
	return true;
	}

Пример программы PortIO, использующей CE Device Driver Kit

Подобно предыдущему примеру программы эта программа C++ читает данные последовательного ввода из COM2:, выводит каждый символ в консольном окне eBox, и выводит их назад в COM2:.

Вместо вызовов API В/В файлов CE, она использует функции WRITE_PORT_UCHAR и READ_PORT_UCHAR, находящиеся в библиотеке CEDDK. Требуется заголовочный файл CEDDK.h, и код должен быть скомпонован с библиотекой CEDDK.lib. Эти функции взаимодействуют прямо с портами В/В на оборудовании UART порта COM и являются типичными операциями, требуемыми на самом нижнем уровне драйвера устройства, который требуется для прямой коммуникации с оборудованием. Мы модифицируем позже этот пример, чтобы создать простой драйвер устройства потокового интерфейса ОС.

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

COM2: содержит совместимый с 16550 UART аппаратный интерфейс с базовым адресом В/В 0x2F8. Спецификация 16550 UART или справочник по оборудованию ПК объяснит детали того, как все восемь портов В/В UART используются для данных, статуса и многих других характеристик последовательной связи, таких как скорость в бодах, число битов данных и т.д. Прерывания отключены и для передачи данных последовательного порта используется программируемый В/В. Таблица 8.1 перечисляет базовые функции портов В/В UART.

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

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

Порты В/В 16550 UART
Функция регистра Адрес порта В/В
Данные (DLB=1, делитель скорости в бодах LSB) Base + 0
Разрешение прерываний (DLB=1, делитель скорости в бодах MSB) Base + 1
ID прерывания Base + 2
Формат данных Base + 3
Управление по модему Base + 4
Статус линии Base + 5
Статус модема Base + 6
Scratch-Pad Base + 7

Отметим использование программируемого В/В в функциях write_serial_character и read_serial_character. Эти функции считывают соответствующий бит готовности устройства UART из порта статуса, маскирует этот бит, тестирует его, и делает цикл, если понадобится, прежде чем передавать данные. Вспомните, что "" является побитовой операцией AND, а "|" является побитовой операцией OR в C/C++. Sleep(0) выходит на текущий квант времени и помещает процесс в очередь ожидания своего очередного выполнения.

Здесь также, чтобы увидеть данные последовательный null-модемный кабель соединяется из COM2: на eBox с неиспользуемым портом COM на настольном ПК системы разработки. Запустите HyperTerminal или другую программу эмулятора терминала, соединенную с соответствующим портом COMx: на скорости 9600 бод, с 8 битами данных, без контроля четности, с 1 стоп-битом, и без управления потоком. Проверьте, что не выполняется другое приложение на любом устройстве, которое использует тот же самый порт COM!

Когда выполняется программа, она печатает заголовок, а затем каждый символ, вводимый на клавиатуре настольного ПК, посылается в устройство eBox, оно считывает символы, выводит их в консольном окне, и посылает символы назад. Так как символы посылаются назад, они выводятся также в эмуляторе терминала. Ввод Ctrl-C в эмуляторе терминала заставляет eBox выйти из программы.

// PortIO.cpp : Определяет точку входа для консольного приложения.
// Учебный пример предназначен для иллюстрации того, как работает 
// программируемый В/В на целевой системе, используя оборудование 
// последовательного порта В/В, и показывающий использование   
// функций READ_PORT_UCHAR и WRITE_PORT_UCHAR
// из CE Device Driver Kit (CEDDK)
//
// Настройка для X86 PC  (CEPC)
// используя совместимое с 16550 UART оборудование последовательного порта 
// 
// Не предназначено для замены хорошего драйвера последовательного порта!
// Не использует прерывания, не имеет никаких задержек, и не предоставляет 
// поддержку для всех свойств последовательного порта 
// Обычно для этого используют вызовы API ОС!
//
// Для примера: Соедините Ebox COM2: с ПК с помощью null-модемного кабеля  
// Запустите HyperTerminal на скорости 9600 бод, с 8 битами данных, 
// 1 стоп битом, без контроля четности, и без управления потоком 

#include "stdafx.h"

// Для функций WRITE_PORT_UCHAR  READ_PORT_UCHAR 
#include "..\WINCE600\CEPC_x86\cesysgen\ddk\inc\ceddk.h"
// Также необходимо включить при компоновке CEDDK.lib 
// (см. файл sources)
// добавить $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\ceddk.lib
// в записи TARGETLIBS 

void Setup_UART (PUCHAR Data_Port_Address);
void Write_Serial_Character (PUCHAR Data_Port_Address, UCHAR Serial_Data);
UCHAR Read_Serial_Character (PUCHAR Data_Port_Address);

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	PUCHAR Data_Port_Address;
	UCHAR  Serial_Input_Data = 0;
	char  Title[]= "\f\n    Hello Serial World!\n\rType something and watch it echo back\n\rCtrl C to exit\n\r";
	int i;
// Принудительный вывод консоли 
	printf("I/O Port READ/WRITE Demo Program - Echos data on COM2:\n\r  Type Ctrl C on other device to exit\n\r");
// Адрес порта данных для COM2:
	Data_Port_Address = (PUCHAR)0x2F8;
// Настройка UART для скорости 9600 бод  без прерываний с 8D NP 1S
	Setup_UART(Data_Port_Address);
// Печать заголовка на последовательный порт 
	for (i=0; i<strlen(Title); i++)
		Write_Serial_Character(Data_Port_Address, (UCHAR)Title[i]);
// Запуск цикла Echo - Цикл пока не будет нажато Ctrl C 
	while (Serial_Input_Data != 0x03){
			// Считывание данных 
Serial_Input_Data = Read_Serial_Character (Data_Port_Address);
			// Копирование данных на консоль 
		printf("%c", Serial_Input_Data);
			// Возврат данных назад (Echo)
Write_Serial_Character(Data_Port_Address, Serial_Input_Data);
	}
	return 0;
}

void Write_Serial_Character (PUCHAR Data_Port_Address, UCHAR Serial_Data)
// Запись символа в последовательный порт 
{
	UCHAR Status;
		// Ожидание TX бита готовности вывода =1
		// Статус адреса порта В/В будет адрес порта В/В данных + 5
	do{
		Status = READ_PORT_UCHAR(Data_Port_Address + 5);
	} while ((Status  0x40) == 0);
		// Запись (Echo) новых данных назад на COM2:
    	WRITE_PORT_UCHAR(Data_Port_Address, Serial_Data);
	return;
}

UCHAR Read_Serial_Character (PUCHAR Data_Port_Address)
{
// Считывание символа из последовательного порта 
	UCHAR Serial_Data, Status;
		// Ожидание RX бит готовности ввода =1
		// Статус адреса порта В/В будет адрес порта В/В данных + 5
	do{
Status = READ_PORT_UCHAR(Data_Port_Address + 5); 
// Если не готов, отправить напоминание о кванте времени
		if ((Status  0x01) == 0) Sleep(0);
	} while ((Status  0x01) == 0);
		// Считать новые последовательные данные 
	Serial_Data = READ_PORT_UCHAR(Data_Port_Address);
	return Serial_Data;
}
void Setup_UART (PUCHAR Data_Port_Address)
{
	UCHAR Temp;
// Чтобы полностью понять это потребуется хороший справочник по 
// оборудованию ПК и/или спецификация 16550 UART!
// Отключение COMx: Прерывания (используйте программируемый В/В)
	WRITE_PORT_UCHAR(Data_Port_Address + 1, 0);
// Задаем скорость в бодах как 9600 с настройками делителя частоты 
// Вставляем задание режима делителя 
	Temp = READ_PORT_UCHAR(Data_Port_Address + 3);			
	WRITE_PORT_UCHAR(Data_Port_Address + 3, Temp | 0x83);
// Задаем LSB делителя  (примечание: 12 = 115200/9600)
	WRITE_PORT_UCHAR(Data_Port_Address , 12);
// Задаем MSB делителя
	WRITE_PORT_UCHAR(Data_Port_Address + 1, 0);
// Возврат в обычный операционный режим (и настройка 8D NP 1S)
	Temp = READ_PORT_UCHAR(Data_Port_Address + 3);			
	WRITE_PORT_UCHAR(Data_Port_Address + 3, Temp  0x03);
	return;
}

Компоновка с другими модулями с помощью файла sources проекта

При написании более сложных приложений должен использоваться файл sources подпроекта для задания путей поиска включаемых файлов и определения других модулей для компоновки с подпроектом. Процесс сборки (Build) читает файл sources каждого подпроекта, чтобы определить, как собрать проект. По умолчанию все подпроекты компонуются с coredll.lib в файле sources. Библиотека coredll.lib определяет много базовых вызовов API, но многие вызовы API определяются в других библиотеках.

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

Вызовы API, которые определяются в других библиотеках, должны будут иметь путь доступа к этой библиотеке в файле sources подпроекта. Они добавляются в раздел "TARGETLIBS=" в файле sources, или могут добавляться с помощью окна свойств подпроекта (сделайте щелчок правой кнопкой мыши на имени проекта, выберите properties и вкладку link). Если файл ссылки отсутствует, то будет сгенерирована ошибка компоновки, такая как "неопределенный символ" или "отсутствует внешняя ссылка".

Для длинных сложных путей доступа легче отредактировать непосредственно файл sources. Если сделать двойной щелчок на записи имени основного подпроекта, в редакторе откроется файл sources. Символ "\" используется для продолжения строки. Будьте внимательны, символ слеш ("\") должен быть последним символом в каждой строке, имена файлов должны разделяться пробелом, и после последнего "\" фактически перед следующим разделом требуется пустая строка или строка, содержащая последнюю текстовую строку данного раздела.

Если требуется дополнительный заголовочный файл, необходимо добавить раздел "INCLUDES=" в конце файла sources. Пути доступа для включаемых файлов разделяются точкой с запятой (не пробелами) и снова для продолжения используется "\". Это можно задать также с помощью окна свойств подпроекта на вкладке C/C++ или редактированием файла sources.

При вводе путей доступа в файл sources доступно несколько предопределенных переменных окружения путей доступ CE в форме $(……..). Когда возможно, старайтесь использовать предопределенные переменные путей, чтобы файлы проекта было легче перемещать в случае необходимости в другие каталоги или на другие диски. Переменная окружения содержит такую информацию, как диск, путь доступа, или конфигурационная строка. Они отделены от переменных Sysgen, которые контролируют включенные в дизайн ОС функции. Таблица 8.2 содержит различные переменные окружения, которые можно использовать для определения путей доступа каталогов. Многие из них задаются в Wince.bat.

Переменные окружения, используемые для путей доступа
Переменная окружения Описание
_WINCEROOT Определяет корневой каталог. Если у вас достаточно пространства в одном разделе, создайте корневой каталог в этом разделе, например, C:\WINCE600, и установите в нем ОС. Для этого задайте для %_WINCEROOT% полный путь доступа к корневому каталогу.
_FLATRELEASEDIR Определяет каталог, где будут размешаться выпущенный исходный код и двоичные файлы. По умолчанию используется %_WINCEROOT%\ PBWorkspaces\%_TGTPROJ%\RelDir\%_TGTPLAT%\%_TGTCPUFAMILY%_Release.
PBWORKSPACEROOT Определяет расположение текущего рабочего пространства Platform Builder. По умолчанию используется %_WINCEROOT%\PBWorkspaces \%_TGTPROJ%. Однако, если вы поместили свой корневой каталог в место, отличное от используемого по умолчанию, то это размещение используется как определение для %PBWORKSPACEROOT%.
_PLATFORMROOT Определяет расположение каталога Platform. По умолчанию используется %_WINCEROOT%\Platform..
_PRIVATEROOT Определяет расположение альтернативного корневого каталога, например, каталога для хранения другого инструмента задания переменных окружения, Setenv.bat. Можно задать эту переменную для указания на каталог по своему выбору. По умолчанию используется %_WINCEROOT%\Private.
_PROJECTOAKROOT Определяет расположение каталога Oak для проекта. Во время процесса сборки Build.exe помещает собираемые файлы в этот каталог. По умолчанию используется %_PROJECTROOT%\Oak.
_PROJECTROOT Определяет расположение собираемого проекта; например, %_PBWORKSPACEROOT%\WINCE500 \%PBCONFIG%.
_PUBLICROOT Определяет расположение публичных проектов. По умолчанию используется %_WINCEROOT%\Public.
_SDKROOT Определяет расположение инструментов, которые используются для сборки проектов, таких как компилятор командной строки, редактор внешних связей, отладчик, и библиотеки времени выполнения. По умолчанию используется %_WINCEROOT%\SDK.
_TGTPROJ Определяет имя активного в данный момент рабочего пространства.

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

Можно задавать или удалять переменные окружения используя:

  • Приглашение команды окна сборки с помощью команды set. Используйте меню Build верхнего уровня и откройте окно команды сборки.
  • Пакетного файла
  • IDE. Сделайте щелчок правой кнопкой мыши на проекте и выберите свойства, а затем раскройте объект configuration.
  • Откройте файл sources для подпроекта PORTIO и исследуйте разделы TARGETLIBS и INCLUDE. Отметим, что была добавлена строка для соединения этого модуля с ceddk.lib. Эта библиотека требуется для функций READ_PORT_UCHAR и WRITE_PORT_UCHAR.

    Пример программы ILASMIO, использующей включаемый код ассемблера X86

    Этот пример программы идентичен предыдущему примеру с последовательным портом, за исключением того только, что не использует библиотеку CEDDK для функций чтения и записи порта В/В. Две функции самого нижнего уровня, Read_Port и Write_Port, находящиеся в конце кода примера реализованы с помощью включаемого языка ассемблера X86. Эта программа C++ читает данные последовательного входа из COM2:, выводит каждый символ в консольном окне eBox, и посылает его назад как выход на COM2:.

    Использование языка ассемблера требует больше времени для разработки, делает код менее переносимым, и должно избегаться насколько возможно. Тем не менее существует несколько редких ситуаций, когда низкоуровневая процедура (т.е., процедуры OAL из CE) во встроенном устройстве должны быть реализованы на языке ассемблера, и это можно сделать прямо в исходном коде программы C\C++, используя включаемый ассемблер.

    Если внимательно исследовать файлы исходного кода библиотеки CEDDK, поставляемые вместе с CE, вы обнаружите, что низкоуровневые процедуры фактически также используют включаемый язык ассемблера для коммуникации с портом В/В. Однако CEDDK будет автоматически изменять код ассемблера для различных процессоров.

    Этот код также не предназначен в качестве замены драйвера производственного качества последовательного устройства управляемого прерываниями с буферизацией В/В и задержками, такого как поставляется с CE, который использовался в первом примере программы последовательного порта. Он служит только для демонстрации, как использовать включаемый язык ассемблера для прямой коммуникации с оборудованием в программе C/C++.

    Как и раньше, для просмотра данных последовательный null-модемный кабель соединяет COM2: на eBox с неиспользуемым портом COM на настольном ПК системы разработки. Запустите HyperTerminal или другую программу эмулятора терминала, соединенную с портом COMx: на скорости 9600 бод, 8 битами данных, без контроля четности, 1стоп битом, и без управления потоком. Проверьте, что не выполняется другое приложение, которое использует тот же самый порт COM на любой машине!

    Когда выполняется программа, она печатает заголовок, а затем каждый символ, введенный на клавиатуре ПК посылается в eBox, устройство считывает символы, выводит их в консольном окне, и посылает символы назад. Так как они посылаются назад, то они выводятся также в эмуляторе терминала. Ввод Ctrl-C в эмуляторе терминала заставляет eBox выйти из программы.

    // ILASMIO.cpp : Определяет точку входа для консольного приложения.
    //
    // Учебный пример, предназначенный для демонстрации использования 
    // включаемого языка ассемблера X86 
    // и иллюстрации, как работает программируемый В/В,
    // используя оборудование последовательного порта В/В на целевой системе 
    // (используйте ассемблер, только когда вы вынуждены это делать!)
    //
    // Настройка для X86 PC  (CEPC)
    // используя оборудование последовательного порта, совместимое с 16550 UART 
    // 
    // Не предназначено для замены хорошего драйвера последовательного порта!
    // Не использует прерывания, не имеет задержек, и не предоставляет поддержку 
    // для всех свойств последовательного порта 
    // Обычно будет использовать для этих операций вызовы API ОС!
    //
    // Для примера: Соедините Ebox COM2: с ПК с помощью null-модемного кабеля
    // Запустите HyperTerminal на скорости 9600 бод, с 8 битами данных, 
    // 1 стоп  битом, без контроля четности и без контроля потока 
    #include "stdafx.h"
    void Setup_UART (short int Data_Port_Address);
    void Write_Serial_Character (short int Data_Port_Address, UCHAR Serial_Data);
    void Write_Port (short int Data_Port_Address, UCHAR Data);
    UCHAR Read_Port (short int Data_Port_Address);
    UCHAR Read_Serial_Character (short int Data_Port_Address);
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	short int Data_Port_Address;
    	UCHAR  Serial_Input_Data = 0;
    	char  Title[]= "\f\n    Hello Serial World!\n\rType something and watch it echo back\n\rCtrl C to exit\n\r";
    	unsigned int i;
    // Выводим консоль 
    	printf("I/O Port X86 ASM READ/WRITE Demo Program - Echos data on COM2:\n\r  Type Ctrl C on other device to exit\n\r");
    // Адрес порта данных для COM2:
    	Data_Port_Address = 0x2F8;
    // Настройка UART для 9600 бод  без прерываний с 8D NP 1S
    	Setup_UART(Data_Port_Address);
    // Печать заголовка в последовательный порт
    	for (i=0; i<strlen(Title); i++)
    		Write_Serial_Character(Data_Port_Address, (UCHAR)Title[i]);
    // Начало цикла Echo - Цикл, пока не будет нажато Ctrl C 
    	while (Serial_Input_Data != 0x03){
    			// Чтение данных 
    	    Serial_Input_Data = Read_Serial_Character(Data_Port_Address);
    			// Copy Data to Console
    	    printf("%c", Serial_Input_Data);
    			// Запись данных назад (Echo)
    	    Write_Serial_Character(Data_Port_Address, Serial_Input_Data);
    	}
    	return 0;
    }
    
    
    UCHAR Read_Serial_Character (short int Data_Port_Address)
    {
    // Чтение символа из последовательного порта 
    	UCHAR Serial_Data, Status;
    		// Ожидаем RX бит готовности входа =1
    		// Адрес статуса порта В/В будет Адрес порта данных В/В + 5
    	do{
    		Status = Read_Port(Data_Port_Address + 5); 	// Если не готов, послать напоминание о кванте времени 
    if ((Status  0x01) == 0) Sleep(0);
    	} while ((Status  0x01) == 0);
    		// Считать новые последовательные данные 
    	Serial_Data = Read_Port(Data_Port_Address);
    	return Serial_Data;
    }
    void Setup_UART (short int Data_Port_Address)
    {
    	UCHAR Temp;
    // Чтобы полностью понять это потребуется хороший Справочник 
    // по оборудованию ПК и/или спецификация 16550 UART!
    // Отключение прерываний COMx: (использовать программируемый В/В)
    	Write_Port(Data_Port_Address + 1, 0);
    // Задание скорости в бодах как 9600 с настройками делителя генератора 
    // Включение задания режима делителя 
    	Temp = Read_Port(Data_Port_Address + 3);			
    	Write_Port(Data_Port_Address + 3, Temp | 0x83);
    // Задание LSB делителя (примечание: 12 = 115200/9600)
    	Write_Port(Data_Port_Address , 12);
    // Задание MSB делителя 
    	Write_Port(Data_Port_Address + 1, 0);
    // Возврат в нормальный операционный режим (и задание 8D NP 1S)
    	Temp = Read_Port(Data_Port_Address + 3);			
    	Write_Port(Data_Port_Address + 3, Temp  0x03);
    	return;
    }
    void Write_Port (short int Data_Port_Address, UCHAR Data)
    {
    // включаемый язык ассемблера X86 
    // использовать только когда требуется!
    	_asm {
    		mov dx,Data_Port_Address
    		mov al,Data
    		out dx,al
    	}
    return;
    }
    UCHAR Read_Port (short int Data_Port_Address)
    {
    UCHAR Data;
    // включаемый язвк ассемблера X86 
    // использовать только когда требуется!
    	_asm {
    		mov dx,Data_Port_Address
    		in  al,dx
    		mov Data,al
    	}
    return Data;
    }

    Пример программы с потоком и событием синхронизации

    Программа C/C++ Thread_Demo показывает, как создать поток, используя вызов API CreateThread. Основной процесс и поток оба печатают строку консольного сообщения, каждый раз при выполнении итерации цикла. Потоки обычно необходимо синхронизировать с другими событиями, в данном случае мы будем предполагать, что он должен синхронизироваться с каждой итерацией цикла основного процесса, который создал поток. На рисунке 8.2 никакая синхронизация не используется, и поток выполняется несколько раз, прежде чем основная программа заканчивает одну итерацию цикла. Функция Sleep используется для моделирования, что каждый поток имеет существенно отличное время выполнения, так чтобы любые проблемы синхронизации были более очевидны.

    (рис 8.2) Поток, выполняющийся без синхронизации, может выполниться несколько раз до основной программы

    На рисунке 8.3 было добавлено событие синхронизации, чтобы сохранить основной поток и рабочий поток синхронизированными на одной и той же итерации. Код синхронизации можно видеть в строках комментариев в цикле while в конце кода.

    (рис 8.3) Поток, использующий WaitForSingleObject для ожидания события синхронизации

    Функция CreateEvent задает событие, SetEvent сигнализирует о событии, а WaitForSingleObject блокирует рабочий поток, пока событие не будет задано основным потоком. Другие доступные в API CE методы синхронизации включают мьютексы, семафоры и критические разделы.

    Будут ли потоки по прежнему синхронизированы, если рабочий поток имеет очень длинное время выполнения, или что-то другое в системе заставляет его выполнять итерацию медленнее? Измените код (подсказка: Sleep) и проверьте. Если они не полностью синхронизированы, как можно было бы исправить проблему?

    // Thread_Demo.cpp : Определяет точку входа для консольного приложения.
    //
    // Пример, который показывает, как создать и синхронизировать поток 
    // с помощью события 
    //
    #include "stdafx.h"
    //Функция потока (Thread)
    DWORD WINAPI WorkerThread (LPVOID lpArg);
    HANDLE g_hMainEvent = 0;
    
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	HANDLE hThread1;
    	DWORD dwThread1ID = 0;
    	INT nParameter = 1;
    	int count1 = 0;
    	printf(" \n");
           _tprintf(_T("   Hello Thread World!\n"));
    	g_hMainEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    	Sleep(1000);
    	hThread1 = CreateThread (NULL, 0, WorkerThread, (LPVOID)nParameter, 0, dwThread1ID);
    	while (count1 < 100){
    		printf("   main thread computes value: %d\n",count1);
    		count1++;
    		SetEvent(g_hMainEvent);
    		Sleep(370);
    	}
    	CloseHandle(hThread1);
    	CloseHandle(g_hMainEvent);
        return 0;
    }
    DWORD WINAPI WorkerThread (LPVOID lpArg) {
    	INT threadnumber = (INT) lpArg;
    	INT count = 0;
    	INT RunCode;
    	while (1){
    //		RunCode = WaitForSingleObject(g_hMainEvent, INFINITE);
    //		if (RunCode == WAIT_OBJECT_0){
    printf("   worker thread %d computes value %d\n", threadnumber, count);
    		count++;
    		Sleep(50);
    //		}
    	}
    	return 0;
     }

    Удаленные инструменты ( Tools $$\Rightarrow$$ Remote Tools ) будут полезны для вывода информации о процессах и потоках, выполняющихся на целевой системе. Просмотрщик Remote Process выводит информацию о каждом выполняющемся процессе и потоке. Если выделить процесс, то кнопка с красным символом "X" будет убивать процесс. Удаленная утилита Kernel Tracker графически выводит процессы и события синхронизации относительно времени. Она выполняется быстрее, когда уменьшается числа объектов представления информации.

    (рис 8.4) Просмотрщик процессов можно использовать для проверки статуса процессов и потоков (рис 8.5) Утилита Kernel tracker показывает, какой процесс выполняется, и выводит события синхронизации (рис 8.6) Утилита Kernel tracker выводит все эти события

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

  • Zoom – используется для удаленного захвата экрана
  • Remote Registry Editor – Редактирование и просмотр реестра целевого устройства
  • Remote File Viewer – просмотр файлов на целевом устройстве, загрузка (экспорт) файла
  • Heap Walker - выводит информацию о куче.
  • Call Profiler - предоставляет графический интерфейс для профилирования и анализа.
  • Spy – выводит список окон, открытых на целевом устройстве, и сообщения.
  • Проблема Производитель-потребитель

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

    Буфер задается как кольцевая буферная область (т.е., конец буфера переходит в его начало). Используются два указателя буфера, один для записи данных в буфер с именем "in", и один для чтения данных из буфера с именем "out". Каждый раз, когда увеличивается один из указателей, используется оператор сравнения по модулю C/C++ "%" для переноса указателей, когда они достигают конца буфера. Также используется счетчик числа объектов в буфере. Производитель увеличивает счетчик, а потребитель уменьшает счетчик. Буфер может заполняться и заставлять производителя ожидать, или он может быть пустым и заставлять ожидать потребителя.

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

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

    // ProCom.cpp : Определяет точку входа для консольного приложения.
    // Проблема производитель-потребитель с общим круговым буфером 
    //
    // Пример, который показывает, как создавать и 
    // использовать критические разделы 
    //
    #include "stdafx.h"
    //Функция потока (Thread) 
    DWORD WINAPI ConsumerThread (LPVOID lpArg);
    // Критический раздел 
    CRITICAL_SECTION CriticalSection;
    // Общий круговой буфер и указатели 
    int count = 0;
    int in = 0;
    int out = 0;
    DWORD Buffer[3];
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	HANDLE hThread1;
    	DWORD dwThread1ID = 0;
    	INT nParameter = 1;
    	int i;
    
    	printf(" \n");
        _tprintf(_T("   Producer Consumer example\n"));
    	for (i=0; i<4; i++) Buffer[i] = 0;
    	InitializeCriticalSection(CriticalSection);
    	hThread1 = CreateThread (NULL, 0, ConsumerThread, (LPVOID)nParameter, 0, dwThread1ID);
    
    // Производитель
    	while (1){
    		// Проверка заполнения буфера 
    		while (count == 4)
    		{
    			printf("Buffer Full - Producer Waiting\n");
    		};
    		// Вставляем новый объект в буфер 
    		// Общие глобальные переменные - использование 
    //критических разделов 
    		EnterCriticalSection (CriticalSection);
    			printf("   producer thread produces new item at
     Buffer[%d]                 \n",in);
    			++count;
    			Buffer[in] = GetTickCount();
    			in = (in + 1) % 4;
    		LeaveCriticalSection (CriticalSection);
    	// Случайная задержка для моделирования процесса производства
    // нового объекта для буфера 
    		Sleep(Random()>>21);
    	}
    	CloseHandle(hThread1);
    	DeleteCriticalSection(CriticalSection);
        return 0;
    }
    DWORD WINAPI ConsumerThread (LPVOID lpArg) {
    	INT threadnumber = (INT) lpArg;
    
    
    // Потребитель
    	while (1){
    			// Проверка пустого буфера 
    			while (count == 0)
    			{
    				printf("Buffer Empty - Consumer Waiting\n");
    			};
    			// Удаление объекта из буфера 
    		      // Общие глобальные переменные - использование 
    // критических разделов			
    EnterCriticalSection (CriticalSection);		
    				--count;
    				printf("   consumer thread consumes item from
        Buffer[%d] with time stamp %d\n",out,Buffer[out]);
    				out = (out + 1) % 4;
    			LeaveCriticalSection (CriticalSection);
    // Случайная задержка для моделирования процесса 
    // потребления объекта буфера 
    			Sleep(Random()>>21);
    	}
    	return 0;
     }

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

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

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

    Второй и третий семафоры заменяют циклы while, указывая на условия полного и пустого буфера. Значение двух этих семафоров является ключом к пониманию решения. Они требуют различные начальные значения, одно задается как 0, а для второго задается максимальный размер буфера равный 4. Переменная счетчика на самом деле больше не нужна, но ее удобно использовать для печати сообщений о полном и пустом буфере. Поэтому потоки теперь блокируются с помощью семафора вместо прокручивания в цикле while (уменьшая время работы процессора и энергопотребление), и разрешаются возможные проблемы синхронизации со счетчиком.

    (рис 8.7) Пример вывода первого варианта программы производитель-потребитель
    // ProCom.cpp : Определяет точку входа для консольного приложения.
    // Проблема производитель-потребитель с общим круговым буфером 
    //
    // Пример, который показывает, как создавать и использовать семафоры
    //
    #include "stdafx.h"
    // Semaphores
    static HANDLE mutex_semaphore; // mutual exclusion lock
    static HANDLE full_semaphore;  // something is in buffer
    static HANDLE empty_semaphore; // buffer has an empty space
    // Общий круговой буфер и указатели 
    static int in = 0;
    static int out = 0;
    static int count = 0;
    // Общая область буфера 
    DWORD Buffer[4];
    //Функция потока (Thread) 
    DWORD WINAPI ConsumerThread (LPVOID lpArg);
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	HANDLE hThread1;
    	DWORD dwThread1ID = 0;
    	INT nParameter = 1;
    	int i;
    	printf(" \n");
    	// задание семафоров и их начальных и максимальных значений 
        mutex_semaphore = CreateSemaphore(NULL, 1, 1, TEXT("mutex"));  
    // 1 для блокировки мьютекса	
        full_semaphore  = CreateSemaphore(NULL, 0, 4, TEXT("full"));   
    // 0 объектов в буфере 
        empty_semaphore = CreateSemaphore(NULL, 4, 4, TEXT("empty"));  
    // 4	максимальное количество объектов в буфере 
        _tprintf(_T("   Producer Consumer example\n"));
    	for (i=0; i<4; i++) Buffer[i] = 0;
    	hThread1 = CreateThread (NULL, 0, ConsumerThread, (LPVOID)nParameter, 0, dwThread1ID);
    // Производитель 
    	while (1){
    			// Ожидание свободного пространства в буфере 
    		WaitForSingleObject(empty_semaphore, INFINITE);
    			// Общие глобальные переменные - используем мьютекс 
    		WaitForSingleObject(mutex_semaphore, INFINITE);
    				// Добавляем новый объект в буфер 
    			Buffer[in] = GetTickCount();
    			count++;
    				// Проверяем не полный ли буфер
    			if (count >= 4)
    			printf("producer thread produces new item at Buffer[%d]  %d  Buffer now Full    \n",in, Buffer[in]);
    			else
    			printf("producer thread produces new item at Buffer[%d]  %d              \n",in, Buffer[in]);
    			in = (in + 1) % 4;			
    		ReleaseSemaphore(mutex_semaphore, 1, NULL);
    		ReleaseSemaphore(full_semaphore, 1, NULL);
    	// Случайная задержка для моделирования процесса производства 
    //нового объекта для буфера 
    		Sleep(Random()>>21);
    	}
    	CloseHandle(hThread1);
     	CloseHandle(mutex_semaphore);
    	CloseHandle(full_semaphore);
    	CloseHandle(empty_semaphore);
       return 0;
    }
    DWORD WINAPI ConsumerThread (LPVOID lpArg) {
    	INT threadnumber = (INT) lpArg;
    // Потребитель 
    	while (1){
    			// Ожидаем объект в буфере 
    		WaitForSingleObject(full_semaphore, INFINITE);
    		   	// Общие глобальные переменные - используем мьютекс
    		WaitForSingleObject(mutex_semaphore, INFINITE);
    				count--;
    			// Проверяем, не пустой ли буфер 
    				if (count == 0)
    				printf("consumer thread consumes item from Buffer[%d]  %d  Buffer now Empty\n",out,Buffer[out]);
    				else	
    				printf("consumer thread consumes item from Buffer[%d]  %d\n",out,Buffer[out]);
    			// Удаляем объект из буфера 
    				out = (out + 1) % 4;		
    		ReleaseSemaphore(mutex_semaphore, 1, NULL);
    		ReleaseSemaphore(empty_semaphore, 1, NULL);
    	// Случайная задержка для моделирования процесса потребления 
    // объекта из буфера 
    		Sleep(Random()>>21);
    	}
    	return 0;
     }
    (рис 8.8) Пример вывода из второго варианта программы производитель-потребитель, использующей семафоры

    Пример программы AC97, использующей функции аудио API CE

    Следующий пример программы звонит семь раз в колокольчик на устройстве аудио-выхода AC97. Стандартный динамик ПК или стерео-наушники можно соединить с разъемом аудио выхода на передней панели eBox, чтобы слышать вывод (разъем с символом наушников). Ядро CE также должно быть собрано с выбранным драйвером аудио устройства BSP. Вызов API PlaySound проигрывает за раз только один звуковой файл. С помощью функции Sleep добавляется задержка, чтобы разделить звуки и создать паузу перед выходом программы и закрытием консольного окна. Этот пример использует встроенный звуковой файл Windows "EXCLAM". С небольшими изменениями вы сможете воспроизвести любой wav-файл пользователя.

    Для более сложных звуков и микширования используйте вызовы API waveout. Вызовы API wavein можно использовать для записи звуков с помощью микрофона ПК. Аудио звуки будут немного искажены, когда вы соединяетесь с отладчиком, так как он все замедляет, особенно, когда выводятся отладочные сообщения или один из удаленных инструментов соединяется с устройством. Примеры исходного кода, использующего функции API wavein и waveout можно найти в подкаталоге \SDK\SAMPLES\AUDIO.

    // Playsound.cpp : Определяет точку входа для консольного приложения.
    //
    
    #include "stdafx.h"
    
    int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
    {
    	int i;
        _tprintf(_T("Hello Audio World!\n"));
    	Sleep(1000);
    	// Воспроизводит файл WAV семь раз на аудио-устройстве 
    	for (i=0; i<7; i++)
    	{
    	// Использует запись в реестре для звукового файла EXCLAM
    	// Можно заменить путем доступа к своему файлу wav
    	// если добавить | SND_FILENAME в третий аргумент 
    		PlaySound (TEXT("EXCLAM"), NULL, SND_SYNC);
    		Sleep(500);
    	}
    	Sleep(1500);
        return 0;
    }

    Базовое приложение Windows, пример Hello World

    В этом примере программы создается приложение Windows (на основе GUI), а не простое текстовое консольное приложение. Важно понимать, что обычная программа Windows отличается от других программных моделей тем, что прикладная программа запрашивает ввод из операционной системы с помощью явных вызовов API, таких как getc и putc. В прикладной программе Windows операционная система уведомляет приложение, что доступен ввод клавиатуры и мыши, посылая приложению сообщения. Сообщения автоматически генерируются операционной системой, когда возникает активность мыши или клавиатуры, связанная с этим окном. Щелчок на кнопке или выбор пункта меню с помощью мыши генерирует сообщение. Приложения Windows написаны для ответа на эти последовательности сообщений от операционной системы. Сообщения распознаются прикладной программой с помощью последовательностей предопределенных констант, таких как WM_CREATE и WM_DESTROY. Этот процесс одинаков как в настольных операционных системах Windows, так и в Windows Embedded CE.

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

    Обычные префиксы венгерской нотации, используемые для переменных в программах Windows
    Тип переменной Венгерский префикс
    Integer i или n
    Word (16-bit) w или s
    Double Word (32-bit) dw
    Long (32-bit signed) l
    Char c
    String (с нулем на конце) sz
    Pointer p
    Long Pointer lp
    Handle h
    Window Handle hwnd
    Размер структуры cb

    Приложение Windows в следующем примере кода основывается на коде, автоматически генерируемом CE, когда создается простое приложение Windows "Hello World". При рассмотрении примера кода обратите внимание, что все программы Windows имеют точку входа программы WinMain. При входе приложение должно зарегистрировать свой основной класс окна. В примере кода это делается в MyRegisterClass. Определяются различные параметры стиля окна. Все окна являются экземплярами класса окна.

    Затем приложение должно создать, вывести и обновить новое окно. В примере программы эта операция выполняется в InitInstance.

    После того как процедура WinMain регистрирует, создает, выводит, и обновляет основное окно, она входит в цикл сообщений окон. В примере программы найдите цикл while в конце WinMain. Это критическая часть любого приложения Windows. Она остается в цикле сообщений While, пока выполняется приложение. Функция GetMessage блокируется, пока не будет доступно новое сообщение из операционной системы. TranslateMessage транслирует соответствующие клавиатурные сообщения в символьное сообщение. Функция DispatchMessage уведомляет операционную систему о передаче нового сообщения в соответствующее окно в приложении. Приложение остается в цикле while, пока не будет получено сообщение WM_QUIT. Это специальное сообщение заставляет GetMessage вернуть значение ноль и завершить цикл while, и приложение завершается.

    Когда посылается каждое новое сообщение, операционная система автоматически активирует процедуру обратного вызова, WndProc. ОС передает в нее новое сообщение. Найдите эту функцию в конце кода программы примера. Функция обратного вызова приложения Windows (т.е., WndProc ) обрабатывает каждое сообщение, декодируя его в операторе switch. Операции, требуемые для каждого сообщения, кодируются в каждом случае. После обработки сообщения функция обратного вызова возвращает управление.

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

    Большая часть усилий при разработке новой прикладной программы Windows тратится на кодирование процедуры обратного вызова Windows приложения, и код, требуемый для обработки каждого сообщения. Можно использовать графические инструменты для проектирования GUI и автоматической генерации шаблонов кода Windows для программ C/C++ и C#. Разработчику по прежнему необходимо вручную ввести код для реализации действий, требуемых для реакции на каждое сообщение окна.

    // WinAPP.cpp : Определяет точку входа для приложения.
    //
    #include "stdafx.h"
    #include "resource.h"
    
    #define MAX_LOADSTRING 100
    
    // Глобальные переменные:
    HINSTANCE hInst;                      // current instance
    TCHAR szTitle[MAX_LOADSTRING];        // The title bar text
    TCHAR szWindowClass[MAX_LOADSTRING];  // The title bar text
    
    // Предварительные объявления функций, включенных в этот модуль кода:
    ATOM MyRegisterClass(HINSTANCE hInstance);
    BOOL InitInstance(HINSTANCE, int);
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    
    int WINAPI WinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPTSTR     lpCmdLine,
                         int       nCmdShow)
    {
        // Что делать: Поместите здесь код.
        MSG msg;
        HACCEL hAccelTable;
    
        // Инициализация глобальных строк 
        LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
        LoadString(hInstance, IDC_WinAPP, szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);
    
        // Выполнить инициализацию приложения:
        if (!InitInstance (hInstance, nCmdShow)) 
        {
            return FALSE;
        }
    
        hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_WinAPP);
    
        // Основной цикл сообщений:
        while (GetMessage(msg, NULL, 0, 0)) 
        {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) 
            {
                TranslateMessage(msg);
                DispatchMessage(msg);
            }
        }
    
        return msg.wParam;
    }
    //
    //  FUNCTION: MyRegisterClass()
    //
    //  Назначение: Регистрирует класс окна.
    //
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
        WNDCLASS wc;
    
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = (WNDPROC) WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = 0;
        wc.hCursor = 0;
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName = 0;
        wc.lpszClassName = szWindowClass;
    
        return RegisterClass(wc);
    }
    //
    //   FUNCTION: InitInstance(HANDLE, int)
    //
    //   Назначение: Сохраняет handle экземпляра и создает основное окно 
    //
    //   COMMENTS:
    //
    //        В этой функции мы сохраняем handle экземпляра в глобальной 
    //        переменной и создаем и выводим основное окно программы.
    //
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {
       HWND hWnd;
    
       hInst = hInstance; // Store instance handle in our global variable
    
       hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
          0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    
       if (!hWnd)
       {
          return FALSE;
       }
    
       ShowWindow(hWnd, nCmdShow);
       UpdateWindow(hWnd);
    
       return TRUE;
    }
    //
    //  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
    //
    //  Назначение:  Обрабатывает сообщения для основного окна.
    //
    //  WM_COMMAND  - обработка меню приложения 
    //  WM_PAINT    - отрисовка основного окна 
    //  WM_DESTROY  - отправка сообщения о завершении и возврат управления 
    //
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        PAINTSTRUCT ps;
        HDC hdc;
        TCHAR szHello[MAX_LOADSTRING];
        LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
    
        switch (message) 
        {
            case WM_PAINT:
                hdc = BeginPaint(hWnd, ps);
    // Сделать: Добавить здесь какой-нибудь дополнительный код
    // для рисования...
                RECT rt;
                GetClientRect(hWnd, rt);
                DrawText(hdc, szHello, _tcslen(szHello), rt, DT_CENTER);
                EndPaint(hWnd, ps);
                break;
            case WM_DESTROY:
                PostQuitMessage(0);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
       }
       return 0;
    }

    Управляемый код и собственный код

    Разработчики встроенных систем встречаются с проблемой выбора при разработке приложений для встроенных устройств в Windows Embedded CE. Выбор состоит в использовании управляемого кода и/или собственного (неуправляемого) кода. Собственный код использует C/C++, как было в предыдущих примерах программ. В собственном коде процедуры компилируются непосредственно в машинный код целевого устройства.

    Управляемый код использует новый язык C# (произносится Си-шарп) или также Visual Basic.NET. Мы сконцентрируем здесь наше обсуждение и примеры на C#. C# компилируется в промежуточный байт-код, называемый Общим промежуточным языком (Common Intermediate Language -- CIL). Во время выполнения динамический процесс компиляции транслирует CIL в собственные инструкции, которые кэшируются. Это означает, что когда кэшированный код вызывается в следующий раз, будут выполняться непосредственно собственные инструкции, а повторная интерпретация CIL выполняется не будет. .NET Compact Framework выполняет код CLI в защищенной среде поверх Windows Embedded CE. Общая среда выполнения языка (CLR) и Framework Class Library (FCL) предоставляют слой полезных промежуточных функций для программистов, которые недоступны в C/C++.

    Те читатели, которые уже знакомы с Java, увидят много сходства в синтаксисе Java и C#, CIL и байт-коде Java, и Виртуальной машине Java (JVM) и .NET Compact Framework.

    С# предлагает разработчикам несколько преимуществ относительно C/C++. Программисты приложений C# являются более продуктивными, особенно при разработке сложных графических и сетевых приложений. С# предоставляет слой промежуточного программного обеспечения в тех прикладных областях, которые позволяют программистам работать на более высоком уровне абстракции, чем C/C++. C# включает встроенные средства языка для потоков и синхронизации (как в Java). C# создает также более безопасный код в том смысле, что выполняется больше проверок во время выполнения, чем в C/C++. Во время выполнения проверяются границы массивов и типы данных, и в C# не допускаются прямые манипуляции с указателями (как в Java). Другое преимущество состоит в том, что требуется только одна версия исполняемого кода CIL программы C# для поддержки приложения на различных типах процессоров, выполняющихся на различных аппаратных платформах.

    Недостатками C# являются влияние на использование памяти и производительность. Среда .NET Compact Framework, которая требуется для поддержки C# увеличивает размер ядра CE на несколько мегабайтов. Динамическая компиляция с проверкой во время выполнения работает медленнее, чем собственный код C/C++. Периодически должны выполняться процедуры сборки мусора для восстановления памяти из брошенных объектов кучи, и возможно даже удаления некоторого кода из кэша кода. Во время сборки мусора все потоки должны приостанавливаться, и это может иметь отрицательное влияние на производительность системы в реальном времени. В настольной системе с избытком памяти и быстрыми процессорами, это не имеет большого значения, если сравнивать с небольшим встроенным устройством с ограниченной памятью и более медленными процессорами. Дизайнерам потребуется тщательно оценить достоинства и недостатки при выборе языка в конкретной ситуации.

    Пример программы C# Hello World

    Теперь рассмотрим кратко код простого консольного приложения C#. C# использует пространства имен. Пространства имен предотвращают конфликты имен между независимо созданными классами. Первая строка "using System" позволяет компилятору проверить ссылки в пространстве имен System. Так находится Console.WriteLine. Без "using System" ссылка должна записываться в виде System.Console.WriteLine. Допускается использование нескольких операторов using. Console.WriteLine выводит строковый аргумент в окне консоли (также как функция C/C++ printf) . Console.Readline считывает вводимые на клавиатуре символы. Это приложение печатает заголовок и приглашение на консоли. Затем оно продолжает читать строки, вводимые на клавиатуре, пока не будет введена строка "quit".

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO.Ports;
    using System.Threading;
    
    namespace Hello_CS_World
    {
        class Program
        {
          static void Main(string[] args)
           {
            bool _continue = true ;
            string message;
            StringComparer stringComparer=StringComparer.OrdinalIgnoreCase
    // Выводит заголовок и строку приглашения на консоли 
            Console.WriteLine("Hello C# World");
            Console.WriteLine ("Type QUIT to Exit");
    
    // Цикл считывания строк с клавиатуры, 
    // пока пользователь не введет "quit"
                while (_continue)
                {
                    message = Console.ReadLine();
                    if (stringComparer.Equals("quit", message))
                    {
                        _continue = false;
                    }
                }   
            }
        }
    }

    Как было сделано в предыдущем уроке о C#, описанном в лекции 7 в разделе 7.7, программы C#, которые создает Windows, почти всегда создаются с помощью дизайнера приложений Visual Studio, который имеет графические инструменты проектирования, которые автоматически создают код для приложения Windows Forms. Код обработчика событий для ответа на каждое событие затем добавляется разработчиком вручную. События C# аналогичны сообщениям C/C++ Windows. Существуют многочисленные книги, которые посвящены более подробному рассмотрению инструментам разработки программного обеспечения C#.

    Пример программы C# с последовательным портом

    В C# можно использовать элемент управления SerialPort для отправки и получения данных из последовательного порта, такого как COM1: и COM2: на eBox. По умолчанию используется COM1: на скорости 9600 бод, с 8 битами данных, без контроля четности, с одним стоп битом, и без квитирования. Не забудьте установить SP2 для .NET Compact Framework 2.0 прежде чем использовать SerialPort, и проверьте, что все другие программы, которые могли бы использовать последовательный порт, его не используют (т.е., сообщения последовательного отладчика CE, ActiveSync, KITL). Ниже показан пример C#, который печатает "Hello CS Serial World" на COM1:.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO.Ports;
    namespace Hello_CS_Serial_World
    {
        class Program
        {
            static void Main(string[] args)
            {
            SerialPort _serialPort;
            // Создаем новый объект SerialPort с требуемыми настройками.
            _serialPort = new SerialPort();
            //По умолчанию ("COM1", 9600, Parity.None, 8, StopBits.One)    
            _serialPort.Open();
            _serialPort.WriteLine("Hello CS Serial World");
            _serialPort.Close();
            }
        }
    }

    Примеры программ и драйверы Phidget USB

    Phidgets являются семейством дешевых внешних устройств В/В на основе USB. Модули Phidgets поддерживают как аналоговый, так и цифровой В/В. Для Phidgets доступен широкий набор датчиков и интерфейсов. Один из наиболее популярных модулей Phidgets показан на рисунке 8.9. Этот модуль содержит дисплей LCD 20 на 2 символов и модули цифрового и аналогового интерфейса на задней стороне платы. Он поддерживает 8 цифровых входов и 8 цифровых выходов. Цифровой В/В соединяется с винтовыми терминальными блоками сзади платы. 8 входов модулей аналоговых датчиков можно соединить с платой. Каждый модуль аналогового датчика соединяется с помощью специальных 3-контактных разъемов (5V питание, аналоговый сигнал, и земля). При использовании стандартных драйверов частота дискретизации может составлять от 50 до 125 сэмплов в секунду.

    (рис 8.9) Это устройство Phidget USB содержит LCD и цифровые и аналоговые контакты В/В

    Для использования Phidgets на целевом оборудовании eBox 2300, необходимо установить в ядро ОС пять файлов драйверов устройств Phidget CE 6.0, и добавить соответствующие записи реестра. Когда драйверы будут установлены, будут доступны вызовы API Phidgets, которые могут использоваться в приложениях C/C++ и C#.

    Несколько руководств по API драйверов и примеры приложений доступны на Web-сайте Phidget в области загрузки (www.phidgets.com). Полный исходный код драйвера устройства Phidgets доступен как проект общедоступного исходного кода CE.

    Чтобы упростить установку драйвера, был разработан подпроект PhidgetsDrivers CE 6.02. Подпроект не перекомпилирует все драйверы, скорее он просто копирует двоичные файлы в каталог выпуска, задает необходимые записи реестра, и создает файл *.bib для включения драйверов в ядро ОС. Инструмент CEFileWiz3 будет полезен для начального создания файлов для подпроектов, таких как PhidgetsDrivers.

    Чтобы включить драйвер Phidgets в новое ядро ОС, чтобы прикладные программы могли использовать Phidgets, скопируйте весь каталог PhidgetsDrivers в каталог проекта OSDesigns, который содержит каталоги подпроектов (т.е., используемым по умолчанию местоположением будет WINCE600\OSDESIGNS\MyOSProject\MyOSProject ). Так как файл poslink.bat просто копирует файлы, каталог подпроекта должен быть в правильном местоположении, и он должен также называться PhidgetsDrivers.

    После копирования каталога PhidgetsDrivers, выберите Subprojects в представлении решения, сделайте щелчок правой кнопкой мыши, выберите добавить существующий подпроект, и выберите PhidgetsDrivers.pbpxml. Через несколько секунд должен появится новый подпроект PhidgetsDrivers.

    Сделайте щелчок правой кнопкой мыши на подпроекте PhidgetsDrivers и выберите Build. Новый подпроект PhidgetsDrivers должен теперь собраться без ошибок. Если каталог копируется в неправильное местоположение или записывается с ошибкой, в сборке появится файл postlink.bat копирования ошибок. Проверьте все обновления драйверов Phidgets для CE 6.0 на Web-сайте Phidgets. Новые двоичные файлы драйверов можно скопировать в существующий каталог подпроекта в \target\X86 в каталоги выпуска и отладки.

    Когда драйверы установлены правильно, то при выполнении нового образа времени выполнения ОС, когда какое-либо устройство USB Phidget подключено к eBox, устройство должно перечислить и загрузить соответствующий драйвер автоматически. Также в окне вывода отладки появится последовательность, связанных с USB сообщений. Если новое устройство USB неизвестно (т.е., драйвер Phidgets установлен неправильно), появится диалоговое окно, предлагающее пользователю ввести имя драйвера для нового устройства USB.

    Несколько примеров демонстрационных приложений C# можно найти в проекте WindowsCE. NET Examples, который поставляется вместе с драйверами. Они могут использоваться для тестирования наиболее распространенных модулей Phidget на правильность работы. Включен полный исходный код приложений. Доступен также файл проекта, который можно использовать для сборки демонстрационных приложений Phidget. После сборки приложений, их можно развернуть на eBox и выполнить. В примере решения WindowsCE.NET Phidget представлены следующие приложения C# вместе с драйверами:

  • Контроллер InterfaceKit выводит статус цифрового и аналогового модуля В/В 8/8/8. Цифровые состояния указываются флажком, а значения аналоговых датчиков появляются на ползунковых индикаторах. Удобный способ проверки датчиков.
  • Контроллер TextLCD можно использовать для вывода текста, вводимого в текстовом поле в модуле LCD Phidgets.
  • Phidget Manager выводит имя и тип всех присоединенных модулей Phidgets.
  • Контроллер RFID будет выводить теги RFID, прочитанные в данный момент модулем RFID Phidgets.
  • Эти демонстрационные приложения C# показаны во время выполнения на eBox на рисунке 8.10. Модули Phidget соединяются с eBox и посылают данные назад на дисплей GUI. Модуль датчика присоединяется к первому аналоговому входу, который выводится на первом ползунковом индикаторе в контроллере интерфейса. Позиция ползункового индикатора указывает относительное значение показателя аналогового напряжения датчика. Phidget LCD выводит "Hello Phidget World", а индикатор RFID показывает значение тега 010230dff3. Отметим, что Phidget Manager обнаружил, что RFID Phidget, Phidget Interface Kit с LCD, и Phidget Servo Controller подключены к портам USB eBox.

    (рис 8.10) Демонстрационные программы Phidget C#, выполняющиеся на eBox с несколькими присоединенными модулями Phidgets

    Чтобы разрабатывать свои собственные приложения C#, изучите эти примеры и загрузите соответствующие руководства интерфейса API Phidgets для получения дополнительной информации.

    Для приложений C/C++ Phidgets программы C/C++ нужно будет включить phidget21.h, загрузить драйвер устройства Phidgets, используя LoadLibrary(_T("phidget21.dll")), и используя несколько вызовов функций GetProcAddress для возврата каждой точки входа, необходимой для функций библиотеки Phidgets, так как для компоновки с проектом C++ нет доступной библиотеки phidget21.lib в текущем выпуске драйверов. Для каждой отдельной точки входа API Phidgets, используемой в приложении, требуется вызов функции GetProcAddress. Подпроект PhidgetsLCD4, содержит пример кода C++ использующего эту технику, который выводит текст и данные на модуле Phidgets LCD.

    // Пример использования LoadLibrary для загрузки драйвера Phidgets 
    hModule = ::LoadLibrary(_T("phidget21.dll"));
    	if (hModule == NULL)
    		return 0;
    
    
    // Пример показывающий использование GetProcAddress 
    // для каждой точки входа 
    	typedef int (__stdcall *Phidget_open)(CPhidgetHandle phid, 
    int serialNumber);
    Phidget_open open = (Phidget_open)::GetProcAddress(hModule,               _T("CPhidget_open"));

    Файлы настройки подпроекта PhidgetsDrivers CE 6.0 были получены из http://www.learningce.com/779.aspx. Файлы драйвера общественного проекта общего исходного кода для Phidgets доступны по адресу http://www.codeplex.com/PhidgetsWinCEDriver. Этот сайт является лучшим местом для комментариев и вопросов. Официальный сайт загрузки продуктов Phidgets может содержать более новые версии последних драйверов CE 6.0 http://www.phidgets.com/modules.php?op=modloadname=Downloadsfile=indexreq=getitlid=30.

    Проект Panner C# для настройки сервомеханизма Phidgets дистанционного управления с ИК датчиком расстояния, показанный на рисунке 5.23, сканирует ближайший объект5. Угол сервомеханизма выводится на индикаторе LED, а GUI выводит статусную информацию.

    Вызов функций C/C++ из C#

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

    Для этого используется специальный интерфейс, называемый Process Invoke (P/Invoke), который предоставляет эту возможность. Используемые функции C/C++ должны быть представлены в динамически подключаемой библиотеке (DLL). Следующий код необходимо добавить в определение класса или структуры в C#, чтобы вызвать функцию C/C++ Userfunction в Users.dll:

    [DllImport("Users.dll")]
    private static extern uint Userfunction( . . .);

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

    Дополнительная информация

  • Учебник, Programming Microsoft Windows CE.Net, Third Edition, Douglas Boling, опубликованный Microsoft Press содержит дополнительную информацию о разработке прикладных программ C/C++ Windows Embedded CE. Глава 23 содержит краткое введение в C#.
  • Оперативная справочная система в Windows Embedded CE содержит описания всех вызовов API и имеет дополнительные примеры кода.
  • Дополнительную информацию о написании программ C# можно найти в книге .NET Compact Framework Programming with C#, написанной Paul Yao и David Durant, а информацию о проектировании форм можно найти в книге Programming Windows with C#, Charles Petzold.
  • Полезный демонстрационный инструмент P/Invoke Wizard доступен по адресу http://www.paulyao.com/resources/tools/pinvoke.asp
  • Сравнение свойств языков C# и Java можно найти в книге The C# Programming Language for Java Developers, доступной в сети по адресу http://msdn.microsoft.com/vstudio/java/gettingstarted/csharpforjava/
  • Справочные руководства по языку ассемблера X86 свободно доступны в Web по адресу www.ece.gatech.edu/~hamblen/Ebox.
  • Лабораторные упражнения

  • Модифицируйте программу пример с файловой системой, чтобы файл записывался в долговременную память (флеш-устройство USB, плата CF, или внутренний флеш-диск). Найдите файл на eBox после создания и выведите данные, чтобы проверить, что все работает.
  • Модифицируйте программу примера с последовательным портом для использования более высокой скорости в бодах 38400 и протестируйте код с eBox, соединенный с ПК, выполняющим HyperTerminal. Просмотрите спецификацию 16550, если требуется дополнительное понимание и настройка делителя частоты.
  • Модифицируйте пример с потоками, чтобы потоки были полностью синхронизированы, независимо от задержки в вызовах API Sleep. Используйте случайные задержки, как показано в задаче производитель-потребитель и выполните код на eBox.
  • Модифицируйте второй пример производителя-потребителя для использования критического раздела вместо мьютексного семафора. Проверьте, что он работает, выполняя код на eBox.
  • Модифицируйте второй пример производителя-потребителя для использования блокировки мьютекса вместо мьютексного семафора для его мьютексной блокировки на общих данных. Проверьте, что он работает, выполняя код на eBox.
  • Напишите модель C/C++ для классической проблемы обедающих философов, которая использует потоки и семафоры CE. Имеется 5 философов, сидящих за круглым столом. Философы едят или думают (спят). В центре стола находится еда. Всего имеется пять вилок, по одной справа и слева от каждого философа. Так как имеется только пять вилок, философы должны делить свои вилки с философом с каждой стороны от себя. Чтобы есть, философ должен иметь обе вилки. Когда они пытаются есть, они оказываются в постоянном состоянии голода, пока не получат обе вилки. Проблема состоит в том, чтобы создать способ разбора вилок, так чтобы ни один философ не умер от голода. Также философы могут заблокировать друг друга (каждый захватил по одной вилке, и не выпускает ее) без синхронизации. Используйте случайную временную задержку (как в проблеме производитель-потребитель) для событий размышления и еды. Выполните свою модель на eBox в консольном приложении. Вы можете найти все детали этой проблемы в большинстве учебников по ОС в Web.
  • Повторите предыдущую проблему, но в этот раз используйте C# и любые его встроенные средства синхронизации.
  • Добавьте подпроект etchasketch в проект MyOS и выполните его на eBox. Исходный код на C для etchasketch можно найти в следующем каталоге C:\WINCE600\PUBLIC\COMMON\OAK\DEMOS\ETCHA. Нарисуйте свое имя на etchasketch и сделайте захват изображения с помощью удаленного инструмента Zoom.
  • Напишите приложение C# для Web-браузера с именем HelloWeb. Для ввода URL используется текстовое поле. Когда нажимается кнопка, новый URL посылается в браузер (текст из текстового поля). Используйте окно WebBrowser в панели инструментов C#. URL является свойством окна WebBrowser. Настройка очень похожа на код C#, разработанный в учебнике. Проверьте, что он работает, выполняя код на eBox и выводя Web-страницу.
  • Если доступно устройство вывода LCD Phidgets, разработайте приложение для вывода системного времени eBox на LCD. Проверьте, что оно работает, выполняя код на eBox. Может оказаться полезен вызов API GetLocalTime.
  • Если доступен датчик Phidgets, разработайте приложение для считывания значения аналогового датчика, преобразования его в десятичное значение, и вывода его на Phidgets LCD. Проверьте, что оно работает, выполняя код на eBox с помощью присоединенных модулей Phidgets.
  • Если доступен температурный датчик Phidgets, разработайте приложение, которое показывает температуру и функционирует как термореле системы нагрева. Если температура опускается очень низко, включите бит цифрового вывода, чтобы включить нагреватель, и когда он нагреется, выключите бит цифрового вывода. Температурный датчик можно нагреть прикасаясь к нему. Присоедините LED к биту цифрового вывода, чтобы можно было контролировать его состояние. Датчик вращения или скольжения также можно использовать для моделирования температурного датчика. Приложение такого типа не требует на самом деле достаточно быстрой частоты обновления, поэтому можно воспользоваться функцией Sleep. Выведите текущее показание температуры на LCD Phidget или на дисплее eBox.
  • Добавьте дополнительные свойства в предыдущую проблему с термореле, включая следующее:
  • Биты цифрового входа, соединенные с кнопкой или переключателем, который поднимает или повышает до требуемой температуры. Когда вы нажимаете кнопку, на дисплее на несколько секунд выводится заданное значение температуры, а не текущая температура.
  • Переключатель, который выбирает режим нагревания или охлаждения вместе с другим битом вывода и LED для управления кондиционированием воздуха.
  • Напишите приложение GUI Windows, которое вычерчивает график самых последних значений датчика Phidgets относительно времени в смещающемся временном окне.
  • Соберите автономного робота, используя eBox и серво-контроллеры PWM для управления любительским механизмом с дистанционным управлением, который использует стандартный сервомеханизмом с дистанционным управлением. Большинство дешевых игрушек не используют стандартные сервосигналы PWM дистанционного управления. Любительские модели более высокого уровня настраиваются для использования стандартных сигналов PWM для поворотов и управления скоростью. Для механизмов любительского уровня для удаленного управления обычно используется радио, продаваемое отдельно, но воспользуйтесь Phidgets и eBox для настройки приложения беспроводного удаленного управления 802.11 на другом ПК с беспроводной связью. Используйте небольшие скорости, чтобы избежать повреждения механизма. Для eBox потребуется максимум 5V DC 3A регулируемый источник питания на основе батареи.
  • Добавление других датчиков, таких как ИК датчик расстояния или сонар могут позволить механизму самостоятельно обнаруживать и избегать объекты. Несколько датчиков, возможно даже датчики различного типа, потребуются для надежной работы, и вам придется использовать меньшие скорости механизма, чтобы избежать повреждения.
  • Используйте плату Cypress PSoC с eBox для управления дешевым дистанционно управляемым игрушечным механизмом, как в двух предыдущих проблемах. Они не используют стандартные сигналы сервомеханизмов PWM дистанционного управления, они просто включают и выключают моторы. Управление скоростью можно добиться пульсируя и изменяя цикл нагрузки цифровых сигналов управления мотором на несколько килогерц. Вы можете подключить к существующему двигателю постоянного тока схемы с мощными транзисторами и использовать их, или можно создать свои собственные. eBox может посылать микроконтроллеру PSoC требуемые значения скорости и он может генерировать требуемые высокочастотные импульсы данных в прошивке. Выберите механизм достаточно большим, чтобы он мог нести eBox и еще одну батарею.
  • Соедините eBox с базой iRobot Create, используя COM-порт. Напишите код для eBox, который посылает команды базе робота Create, которые заставляют робот перемещаться по квадратам.
  • Вернуться к учебному плану