Пояснительная_записка (1211002), страница 6
Текст из файла (страница 6)
#define false 0
#define sbi(reg,bit) reg |= (1<<bit)//установить бит
#define cbi(reg,bit) reg &= ~(1<<bit)//сбросить бит
#define ibi(reg,bit) reg ^= (1<<bit)//инвертировать бит
#define CheckBit(reg,bit) (reg&(1<<bit))//проверить бит
#define CheckBit0(reg,bit) (reg&(0<<bit))//проверить бит на 0
#define BAUD 9600
#define UBRR F_CPU/16/BAUD-1
Часть кода отвечающая за сброс сторожевого таймера, установкой большей части регистров и таймера задержки, из расчёта фактической тактовой частоты процессора для программы инициализации, содержит функции RunTasks, Usart_init и timerDelayMs.
RunTasks вызывает сторожевой таймер и функцию usbPoll(), которую необходимо вызывать не реже, чем раз в 50 миллисекунд, тем самым давая понять программе-хосту что устройство готово к работе, а также сбрасывает сторожевой таймер.
Usart_init устанавливает настройки UART регистров, такие как UBRRH и UBRRL отвечающие за скорость передачи данных по USB в бодах. Биты TXEN и RXEN регистра UCSRB разрешающие приём и передачу данных. Бит URSEL для выбора регистра UCSRC отвечающий за использование регистра UCSRC, иначе будет UBRRH. Количество стоп-битов отвечает USBS, цифра «0» отвечает за один стоп-бит. Биты UCSZ0-USCZ2 разбросанные по регистрам UCSRC и UCSRB определяют размер кадра, данная конфигурация устанавливает размер в 8 бит.
void RunTasks(void)
{usbPoll();
wdt_reset();}
void timerDelayMs(unsigned int ms)
{ #define TIKS_1MS (F_CPU/64/1000)
while(ms--)
{TCNT0 = 0;
while(TCNT0 < TIKS_1MS) RunTasks();}}
void usart_init( unsigned int ubrr )
{UBRRH = (unsigned char)(ubrr>>8);
UBRRL = (unsigned char)ubrr;
UCSRB=(1<<TXEN)|(1<<RXEN)|(0<<UCSZ2);
UCSRC=(1<<URSEL)|(0<<USBS)|(1<<UCSZ1)|(1<<UCSZ0);}
Чтобы выполнить передачу данных от компьютера к устройству, создадим структуру для передачи данных в 3 байта:
-
port_a – который будет выполнять хранение данных ранее предназначенных для Status Port 379 LPT;
-
port_b – Data Port 378 LPT;
-
port_c – Control Port 37A LPT.
Листинг исходного кода прошивки отвечающий за создание структуры данных в памяти, а также описывающий её приведён ниже.
struct status_t
{unsigned char port_a;//LPT S3-S7
unsigned char port_b;//LPT D0-D7
unsigned char port_c;//LPT С0-С2} ;
struct status_t DeviceStatus;
Для обеспечения корректной работы функций приёма и передачи данных, необходимо обеспечить прошивку информацией о том, как поступать с приходящими пакетами данных и описать их структуру.
#define USB_MESSAGE_LENGTH sizeof(DeviceStatus)
PROGMEM const char usbHidReportDescriptor[22] = {
0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, USB_MESSAGE_LENGTH, // REPORT_COUNT
0x09, 0x00, // USAGE (Undefined)
0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)
0xc0 // END_COLLECTION};
static unsigned char currentAddress;
static unsigned char bytesRemaining;
struct {
unsigned char rx_buffer[25];
} usb;
Размер структуры обозначим USB_MESSAGE_LENGHT для дальнейшего удобства написания кода, если понадобится изменить размер пакета данных. Далее опишем структуру репорта, который будет отправляется по USB. Передачу данных обеспечивает ряд параметров:
-
LOGICAL_MINIMUM – минимальное значение данных;
-
LOGICAL_MAXIMUM – максимальное значение данных в байте;
-
REPORT_SIZE (8) – размер одного репорта, 8 бит;
-
REPORT_COUNT – количество репортов, вычисляется исходя из размера структуры.
Для написания кода обработки передачи и приёма введём переменные currentAddress и bytesRemaining, обозначающие текущее положение процесса передачи данных. Кроме того, для записи данных создадим буфер в памяти контроллера.
Чтобы записывать в памяти устройства, данные отправляемые ПК создаётся обработчик события, вызываемого драйвером при обращение к микроконтроллеру.
void usb_writedata(void){
unsigned char i;
for(i=0; i<sizeof(DeviceStatus); i++) ((unsigned char*)&DeviceStatus)[i] = usb.rx_buffer[i]; }
void usb_writedata(void)
{unsigned char i;
for(i=0; i<sizeof(DeviceStatus); i++) ((unsigned char*)&DeviceStatus)[i] = usb.rx_buffer[i]; }
uchar usbFunctionWrite(uchar *data, uchar len)
{ if(bytesRemaining == 0)
return 1;
if(len > bytesRemaining)
len = bytesRemaining;
unsigned char *buffer = usb.rx_buffer;
for(unsigned char i=0;i < len;i++)
buffer[i+currentAddress]=data[i];
currentAddress += len;
bytesRemaining -= len;
if(bytesRemaining == 0)
{usb_writedata();}
return bytesRemaining == 0; }
Событие usbFunctionWrite записывает данных из регистра UART пока все биты не будут получены и записаны в буфер. После этого выполняется функция записи полученных в буфер данных в структуру DeviceStatus.
usbMsgLen_t usbFunctionSetup(uchar data [USB_MESSAGE_LENGTH])
{ usbRequest_t*rq = (void *)data;
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS)
{ if(rq->bRequest == USBRQ_HID_GET_REPORT)
{usbMsgPtr= (unsigned char*)&DeviceStatus; return USB_MESSAGE_LENGTH;}
else if(rq->bRequest == USBRQ_HID_SET_REPORT)
{bytesRemaining = USB_MESSAGE_LENGTH;
currentAddress = 0;
return USB_NO_MSG;}}else{}
return 0;}
При возникновении события usbFunctionSetup начинается программа обработки, в которой определяется какого рода запрос пришёл от программы-хоста. Если пришёл запрос USBRQ_HID_GET_REPORT – то программа указывает на структуру DeviceStatus, из которой передается значения байтов, а если пришёл запрос USBRQ_HID_SET_REPORT то мы его игнорируем, так как для записи данных мы используем функцию usbFunctionWrite.
Основное тело программы состоит из двух частей. Первая часть отвечает за инициализацию и настройку портов ввода и вывода, настройку UBRRH и UBRRL, подключение к компьютеру, разрешение прерываний командой sei(). Вторая часть – это основной цикл программы.
int main (void)
{DDRC=0xff; PORTC=0x00;
DDRB=0xff; PORTB=0x00;
DDRA=0x00; PORTA=0xff;
TCCR0 = (1<<CS01) | (1<<CS00);
TCNT0 = 0;
wdt_enable(WDTO_1S);
usbInit();
timerDelayMs(250);
usbDeviceConnect();
sei();
usart_init(UBRR);
while(1)
{RunTasks();
PORTC = DeviceStatus.port_c;
RunTasks();
PORTB = DeviceStatus.port_b;
RunTasks();
DeviceStatus.port_a = PINA; }}
В начале листинга показана настройка портов B и С контроллера в режим работы на вывод и установка начальных значений выводов в 0, а порт A настраивается на ввод. Далее устанавливается режим предделителя тактовой частоты на 64 и сброс таймера в 0. Включается сторожевой таймер и начинается инициализация USB с последующем выжиданием в 250 миллисекунд, после чего устройство подключается к компьютеру, разрешаются прерывания и устанавливается скорость передачи данных. Основной цикл включает в себя периодический вызов функции RunTasks, которая обновляет сторожевой таймер и вызывает usbPoll(), и функций установки значений из структуры DeviceStatus на порты ввывода, и занесение в структуру данных поступающих на порт ввода.
2.4.2 Создание тестирующего приложения на ПК
2.4.2.1 Функции и задачи приложения
Разрабатываемая программа должна иметь связь с проектируемым устройством и иметь возможность обмена данными с ним. Целью данного приложения является замена старой тестирующей программы, но уже с переработанным протоколом обмена данными. Так основными задачами новой тестирующей программы являются:
-
поддерживать связь с устройством;
-
выполнять протокол обмена данными между компьютером и УСО;
-
иметь возможность обращения ко всем объектам управления во всех группах;
-
возможность обращения ко все группам объектов контроля;
-
интерфейс, явно отображающий назначение элементов программы.
2.3.2.2 Алгоритм работы тестирующего приложения
Среда разработки приложений Delphi 7 автоматически подключает библиотеки для работы по созданию интерфейса. Но стандартных библиотек элементов недостаточно для работы с проектируемым устройством, для обеспечения связи с микроконтроллером по USB протоколу необходимо использовать внешнюю библиотеку JEDI Visual Component Library (JVCL). В этой библиотеке необходим компонент под названием TJvHidDeviceController имеющий такие события как:
-
OnArrival – срабатывающее на подключение в систему HID-устройства, доступ к которому предоставляется в обработчике этого события через экземпляр класса TJvHidDevice;
-
OnDeviceChange – срабатывает при изменении состояния устройства;
-
OnDeviceData – возникает при получении данных от одного из HID-устройств и передаёт обработчику данные об устройстве от которого данные были получены;
-
OnDeviceUnplug – уведомляет об извлечении устройства из списка установленных в системе;
-
OnEnumerate – выполняет последовательное перечисление имеющихся в системе устройств в виде объектов;
-
OnRemoval – срабатывает на физическое извлечение устройства из системы.
Кроме компонента TJvHidDeviceController нужно использовать свойства и события создаваемого им класса TJvHIdDevice, являющимся виртуальным представлением отдельно взятого HID-устройства. Чтобы однозначно идентифицировать устройство нужно знать такие его свойства:
-
SerialNumber;
-
ProductName;
-
VendorName.
За однозначное определение устройства отвечает кода ниже. В нём константам присвоены наименование устройства, и двухбайтные VendorID и ProductD, такие же, как и в прошивке проектируемого устройства.
const
ProductName = 'FIRST-DEVICE';
VendorID = $0016;
ProductID = $00c0;
Подключение устройства выполняется обработкой двух процедур. HidCtlArrival выполняет вызов процедуры Enumerate, если устройство подключается первый раз. Сама процедура Enumerate выполняет проверку соответствия ProductName, VendorID и ProductID, если они соответствуют, то выполняется создание объекта класса TJvHidDevice. Кроме этого выполняется индикация состояние подключения устройства.
procedure Tfrm_main.HidCtlArrival(HidDev: TJvHidDevice);
begin
if not assigned(MyFD) then frm_main.HidCtl.Enumerate;
end;
function Tfrm_main.HidCtlEnumerate(HidDev: TJvHidDevice;
const Idx: Integer): Boolean;
begin
Result:= True;
If (Trim(HidDev.ProductName) = ProductName)
then If (HidDev.Attributes.VendorID = VendorID) and
(HidDev.Attributes.ProductID = ProductID)
then begin
frm_main.HidCtl.CheckOutByIndex(MyFD, Idx);
with frm_main.lbl_onoff do
begin
Caption:= MyFD.ProductName + ' - On Line';
Font.Color:=clGreen;
end;
frm_main.Caption:=MyFD.VendorName;
Result:= False;
end;
end;
Обработчик события HidCtlRemoval, срабатывающий на извлечение USB устройства, выполняет отключение микроконтроллера и индикацию на форме отключенного его состояния.
procedure Tfrm_main.HidCtlRemoval(HidDev: TJvHidDevice);
begin
If HidDev = MyFD then begin
MyFD:= nil;
with frm_main.lbl_onoff do
begin
Caption:= 'Disconnected';
Font.Color:=clRed;
end;end;end;
procedure Tfrm_main.FormClose(Sender: TObject; var Action: TCloseAction);
begin MyFD:= nil;
end;
С помощью метода GetFeature получаем пакет данных от устройства, если подключения не будет, то метод вернёт False. Этот метод используется. Если требуется отправить пакет с данными, то используется SetFeature. В программе отправка данных происходит вызовом процедуры ниже.
procedure Tfrm_main.RepClick(Sender: TObject);
begin
Rеsult:=MyFD.SetFeature(Report,MyFD.Caps.FeatureReportByteLength);
if not Result then ShowMessage(SysErrorMessage(GetLastError))
else
frm_main.panel3.color:=clRed;
end;
Процедура RepClick будет вызываться после того как пакет данных будет задан в соответствии с протоколом и готов к отправке. Структура пакета идентична той, которую использовали в прошивке микроконтроллера.
type
{$Align Off}
status_t = record
ID:byte;














