Готовые программные библиотеки такие как например libusb (для ПК) и v-usb (для микроконтроллера) значительно упрощают создание связи ПК c микроконтроллером через usb. Пример с включением светодиода через usb(скачиваемый вместе с библиотекой v-usb) очень полезен. В этом примере, несмотря на то что включается один светодиод, размер передаваемой (для дальнейшего свободного использования) информации больше одного бита, рассмотрим часть кода из примера:
/* ------------------------------------------------------------------------- */
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR){
DBG1(0x50, &rq->bRequest, 1); /* debug output: print our request */
if(rq->bRequest == CUSTOM_RQ_SET_STATUS){
if(rq->wValue.bytes[0] & 1){ /* set LED */
LED_PORT_OUTPUT |= _BV(LED_BIT);
}else{ /* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED_BIT);
}
}else if(rq->bRequest == CUSTOM_RQ_GET_STATUS){
static uchar dataBuffer[1]; /* buffer must stay valid when usbFunctionSetup returns */
dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0);
usbMsgPtr = dataBuffer; /* tell the driver which data to return */
return 1; /* tell the driver to send 1 byte */
}
}else{
/* calss requests USBRQ_HID_GET_REPORT and USBRQ_HID_SET_REPORT are
* not implemented since we never call them. The operating system
* won't call them either because our descriptor defines no meaning.
*/
}
return 0; /* default for not implemented requests: return no data back to host */
}
/* ------------------------------------------------------------------------- */
Даже если код непонятен можно догадаться где и как используется принятая информация. В строке:
if(rq->wValue.bytes[0] & 1){ /* set LED */
Определяется равен ли единице первый бит первого байта информации (для свободного использования) принятой микроконтроллером от компьютера. По аналогии можно написать код для определения второго бита первого байта принятой информации:
if(rq->wValue.bytes[0] & 2){ }
Для третьего бита первого байта:
if(rq->wValue.bytes[0] & 4){ }
Для 4го бита первого байта:
if(rq->wValue.bytes[0] & 8){ }
и т.д. до 8го бита т.к. в 1ом байте 8бит. Если принято больше 1го байта то код для определения первого бита второго байта будет выглядеть так:
if(rq->wValue.bytes[1] & 1){ }
второго бита второго байта:
if(rq->wValue.bytes[1] & 2){ }
и т.д. по аналогии с первым байтом. Т.о. можно принимать много информации. Например если передаётся одно число типа int то минимум будет передано 2 байта (но скорее всего 4), если тип long то м.б. 4 байта (или 8), передавать можно не только числа но и массивы и другие типы данных. Если надо управлять 4мя светодиодами то можно использовать например такую схему:
Для управления всеми светодиодами достаточно 4 бита (или пол байта). Измененный код для схемы на рисунке 1:
#define LED_PORT_DDR DDRB
#define LED_PORT_OUTPUT PORTB
#define LED_BIT 1
#define LED_BIT2 2
#define LED_BIT3 3
#define LED_BIT4 4
#define LED_BIT_MASK 1 //в двоичной = 1
#define LED_BIT_MASK2 2 //в двоичной = 10
#define LED_BIT_MASK3 4 //в двоичной = 100
#define LED_BIT_MASK4 8 //в двоичной = 1000
#define F_CPU 12000000UL // 12 MHz
#include "avr/io.h"
#include "avr/wdt.h"
#include "avr/interrupt.h" /* for sei() */
#include "util/delay.h" /* for _delay_ms() */
#include "avr/pgmspace.h" /* required by usbdrv.h */
#include "usbdrv.h"
#include "oddebug.h" /* This is also an example for using debug macros */
#include "requests.h" /* The custom request numbers we use */
/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */
PROGMEM const char usbHidReportDescriptor[22] = { /* USB report descriptor */
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, 0x01, // REPORT_COUNT (1)
0x09, 0x00, // USAGE (Undefined)
0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)
0xc0 // END_COLLECTION
};
/* The descriptor above is a dummy only, it silences the drivers. The report
* it describes consists of one byte of undefined data.
* We don't transfer our data through HID reports, we use custom requests
* instead.
*/
/* ------------------------------------------------------------------------- */
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR){
DBG1(0x50, rq->bRequest, 1); /* debug output: print our request */
if(rq->bRequest == CUSTOM_RQ_SET_STATUS){
if(rq->wValue.bytes[0] & 1){ /* set LED */
LED_PORT_OUTPUT |= _BV(LED_BIT);
}else{ /* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED_BIT);
}
if(rq->wValue.bytes[0] & LED_BIT_MASK2){ /* set LED2 */
LED_PORT_OUTPUT |= _BV(LED_BIT2);
}else{ /* clear LED2 */
LED_PORT_OUTPUT &= ~_BV(LED_BIT2);
}
if(rq->wValue.bytes[0] & LED_BIT_MASK3){ /* set LED3 */
LED_PORT_OUTPUT |= _BV(LED_BIT3);
}else{ /* clear LED3 */
LED_PORT_OUTPUT &= ~_BV(LED_BIT3);
}
if(rq->wValue.bytes[0] & LED_BIT_MASK4){ /* set LED4 */
LED_PORT_OUTPUT |= _BV(LED_BIT4);
}else{ /* clear LED4 */
LED_PORT_OUTPUT &= ~_BV(LED_BIT4);
}
//if(rq-->wValue.bytes[0] & LED_BIT_MASK){ //в двоичной 1
// LED_PORT_OUTPUT |= _BV(LED_BIT);
//}else{
// LED_PORT_OUTPUT &= ~_BV(LED_BIT);
//}
//if(rq->wValue.bytes[1] & LED_BIT_MASK){ //в двоичной 1 0000 0000
// LED_PORT_OUTPUT |= _BV(LED_BIT);
//}else{
// LED_PORT_OUTPUT &= ~_BV(LED_BIT);
//}
}else if(rq->bRequest == CUSTOM_RQ_GET_STATUS){
static uchar dataBuffer[1]; /* buffer must stay valid when usbFunctionSetup returns */
dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0);
usbMsgPtr = dataBuffer; /* tell the driver which data to return */
return 1; /* tell the driver to send 1 byte */
}
}else{
/* calss requests USBRQ_HID_GET_REPORT and USBRQ_HID_SET_REPORT are
* not implemented since we never call them. The operating system
* won't call them either because our descriptor defines no meaning.
*/
}
return 0; /* default for not implemented requests: return no data back to host */
}
/* ------------------------------------------------------------------------- */
int __attribute__((noreturn)) main(void)
{
uchar i;
wdt_enable(WDTO_1S);
/* Even if you don't use the watchdog, turn it off here. On newer devices,
* the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
*/
/* RESET status: all port bits are inputs without pull-up.
* That's the way we need D+ and D-. Therefore we don't need any
* additional hardware initialization.
*/
odDebugInit();
DBG1(0x00, 0, 0); /* debug output: main starts */
usbInit();
usbDeviceDisconnect(); /* enforce re-enumeration, do this while interrupts are disabled! */
i = 0;
while(--i){ /* fake USB disconnect for > 250 ms */
wdt_reset();
_delay_ms(1);
}
usbDeviceConnect();
LED_PORT_DDR |= _BV(LED_BIT); /* make the LED bit an output */
LED_PORT_DDR |= _BV(LED_BIT2); /* make the LED2 bit an output */
LED_PORT_DDR |= _BV(LED_BIT3); /* make the LED3 bit an output */
LED_PORT_DDR |= _BV(LED_BIT4); /* make the LED4 bit an output */
sei();
DBG1(0x01, 0, 0); /* debug output: main loop starts */
for(;;){ /* main event loop */
#if 0 /* this is a bit too aggressive for a debug output */
DBG2(0x02, 0, 0); /* debug output: main loop iterates */
#endif
wdt_reset();
usbPoll();
}
}
/* ------------------------------------------------------------------------- */
Также необходимо не забыть про инициализацию портов. Если программа записывается в микроконтроллер через spi то во время записи нужно чтобы светодиоды VD5 и VD6 были отсоединены от выводов микроконтроллера иначе возможно что эти светодиоды помешают нормальной записи. Код (текст в файле файл set-led.c) можно скачать с Яндекс диска.
КАРТА БЛОГА (содержание)
/* ------------------------------------------------------------------------- */
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR){
DBG1(0x50, &rq->bRequest, 1); /* debug output: print our request */
if(rq->bRequest == CUSTOM_RQ_SET_STATUS){
if(rq->wValue.bytes[0] & 1){ /* set LED */
LED_PORT_OUTPUT |= _BV(LED_BIT);
}else{ /* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED_BIT);
}
}else if(rq->bRequest == CUSTOM_RQ_GET_STATUS){
static uchar dataBuffer[1]; /* buffer must stay valid when usbFunctionSetup returns */
dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0);
usbMsgPtr = dataBuffer; /* tell the driver which data to return */
return 1; /* tell the driver to send 1 byte */
}
}else{
/* calss requests USBRQ_HID_GET_REPORT and USBRQ_HID_SET_REPORT are
* not implemented since we never call them. The operating system
* won't call them either because our descriptor defines no meaning.
*/
}
return 0; /* default for not implemented requests: return no data back to host */
}
/* ------------------------------------------------------------------------- */
Даже если код непонятен можно догадаться где и как используется принятая информация. В строке:
if(rq->wValue.bytes[0] & 1){ /* set LED */
Определяется равен ли единице первый бит первого байта информации (для свободного использования) принятой микроконтроллером от компьютера. По аналогии можно написать код для определения второго бита первого байта принятой информации:
if(rq->wValue.bytes[0] & 2){ }
Для третьего бита первого байта:
if(rq->wValue.bytes[0] & 4){ }
Для 4го бита первого байта:
if(rq->wValue.bytes[0] & 8){ }
и т.д. до 8го бита т.к. в 1ом байте 8бит. Если принято больше 1го байта то код для определения первого бита второго байта будет выглядеть так:
if(rq->wValue.bytes[1] & 1){ }
второго бита второго байта:
if(rq->wValue.bytes[1] & 2){ }
и т.д. по аналогии с первым байтом. Т.о. можно принимать много информации. Например если передаётся одно число типа int то минимум будет передано 2 байта (но скорее всего 4), если тип long то м.б. 4 байта (или 8), передавать можно не только числа но и массивы и другие типы данных. Если надо управлять 4мя светодиодами то можно использовать например такую схему:
Рисунок 1 - usb светофор
Для управления всеми светодиодами достаточно 4 бита (или пол байта). Измененный код для схемы на рисунке 1:
#define LED_PORT_DDR DDRB
#define LED_PORT_OUTPUT PORTB
#define LED_BIT 1
#define LED_BIT2 2
#define LED_BIT3 3
#define LED_BIT4 4
#define LED_BIT_MASK 1 //в двоичной = 1
#define LED_BIT_MASK2 2 //в двоичной = 10
#define LED_BIT_MASK3 4 //в двоичной = 100
#define LED_BIT_MASK4 8 //в двоичной = 1000
#define F_CPU 12000000UL // 12 MHz
#include "avr/io.h"
#include "avr/wdt.h"
#include "avr/interrupt.h" /* for sei() */
#include "util/delay.h" /* for _delay_ms() */
#include "avr/pgmspace.h" /* required by usbdrv.h */
#include "usbdrv.h"
#include "oddebug.h" /* This is also an example for using debug macros */
#include "requests.h" /* The custom request numbers we use */
/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */
PROGMEM const char usbHidReportDescriptor[22] = { /* USB report descriptor */
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, 0x01, // REPORT_COUNT (1)
0x09, 0x00, // USAGE (Undefined)
0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)
0xc0 // END_COLLECTION
};
/* The descriptor above is a dummy only, it silences the drivers. The report
* it describes consists of one byte of undefined data.
* We don't transfer our data through HID reports, we use custom requests
* instead.
*/
/* ------------------------------------------------------------------------- */
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR){
DBG1(0x50, rq->bRequest, 1); /* debug output: print our request */
if(rq->bRequest == CUSTOM_RQ_SET_STATUS){
if(rq->wValue.bytes[0] & 1){ /* set LED */
LED_PORT_OUTPUT |= _BV(LED_BIT);
}else{ /* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED_BIT);
}
if(rq->wValue.bytes[0] & LED_BIT_MASK2){ /* set LED2 */
LED_PORT_OUTPUT |= _BV(LED_BIT2);
}else{ /* clear LED2 */
LED_PORT_OUTPUT &= ~_BV(LED_BIT2);
}
if(rq->wValue.bytes[0] & LED_BIT_MASK3){ /* set LED3 */
LED_PORT_OUTPUT |= _BV(LED_BIT3);
}else{ /* clear LED3 */
LED_PORT_OUTPUT &= ~_BV(LED_BIT3);
}
if(rq->wValue.bytes[0] & LED_BIT_MASK4){ /* set LED4 */
LED_PORT_OUTPUT |= _BV(LED_BIT4);
}else{ /* clear LED4 */
LED_PORT_OUTPUT &= ~_BV(LED_BIT4);
}
//if(rq-->wValue.bytes[0] & LED_BIT_MASK){ //в двоичной 1
// LED_PORT_OUTPUT |= _BV(LED_BIT);
//}else{
// LED_PORT_OUTPUT &= ~_BV(LED_BIT);
//}
//if(rq->wValue.bytes[1] & LED_BIT_MASK){ //в двоичной 1 0000 0000
// LED_PORT_OUTPUT |= _BV(LED_BIT);
//}else{
// LED_PORT_OUTPUT &= ~_BV(LED_BIT);
//}
}else if(rq->bRequest == CUSTOM_RQ_GET_STATUS){
static uchar dataBuffer[1]; /* buffer must stay valid when usbFunctionSetup returns */
dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0);
usbMsgPtr = dataBuffer; /* tell the driver which data to return */
return 1; /* tell the driver to send 1 byte */
}
}else{
/* calss requests USBRQ_HID_GET_REPORT and USBRQ_HID_SET_REPORT are
* not implemented since we never call them. The operating system
* won't call them either because our descriptor defines no meaning.
*/
}
return 0; /* default for not implemented requests: return no data back to host */
}
/* ------------------------------------------------------------------------- */
int __attribute__((noreturn)) main(void)
{
uchar i;
wdt_enable(WDTO_1S);
/* Even if you don't use the watchdog, turn it off here. On newer devices,
* the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
*/
/* RESET status: all port bits are inputs without pull-up.
* That's the way we need D+ and D-. Therefore we don't need any
* additional hardware initialization.
*/
odDebugInit();
DBG1(0x00, 0, 0); /* debug output: main starts */
usbInit();
usbDeviceDisconnect(); /* enforce re-enumeration, do this while interrupts are disabled! */
i = 0;
while(--i){ /* fake USB disconnect for > 250 ms */
wdt_reset();
_delay_ms(1);
}
usbDeviceConnect();
LED_PORT_DDR |= _BV(LED_BIT); /* make the LED bit an output */
LED_PORT_DDR |= _BV(LED_BIT2); /* make the LED2 bit an output */
LED_PORT_DDR |= _BV(LED_BIT3); /* make the LED3 bit an output */
LED_PORT_DDR |= _BV(LED_BIT4); /* make the LED4 bit an output */
sei();
DBG1(0x01, 0, 0); /* debug output: main loop starts */
for(;;){ /* main event loop */
#if 0 /* this is a bit too aggressive for a debug output */
DBG2(0x02, 0, 0); /* debug output: main loop iterates */
#endif
wdt_reset();
usbPoll();
}
}
/* ------------------------------------------------------------------------- */
Также необходимо не забыть про инициализацию портов. Если программа записывается в микроконтроллер через spi то во время записи нужно чтобы светодиоды VD5 и VD6 были отсоединены от выводов микроконтроллера иначе возможно что эти светодиоды помешают нормальной записи. Код (текст в файле файл set-led.c) можно скачать с Яндекс диска.
Остальные файлы без изменений, компилируются проекты также (командой make hex для микроконтроллера, командой make для ПК).