Обо мне
    Резюме
    Портфолио
    Контакты
    Астро
    Схемотехника
    Мультикоптеры
    Автономный дом
    CNC фрезер
Язык:

Устройство включает / выключает несколько нагрузок через USB, контроллирует несколько концевиков

11/04/2011 10:50:25
Проект: блок реле и входов с USB управлением

ВНИМАНИЕ: эта версия схемы глючит при коммутации реальной нагрузки. Работающий вариант - с развязкой питания (5В питание проца от USB, 12В грязные от отдельного БП), с опторазвязкой. Обновлю схему/текст позже. Программы и почти вся схемотехника та же.

Статус: устройство готово, осталось в коробку затолкать.

Цель проекта: Устройство включает / выключает несколько нагрузок через USB, контроллирует несколько концевиков

Сгорела у меня в обсерватории, купленное с годик назад в purelogic.ru платка на 8 реле, управляемая по COM-порту. Сгорела по моей вине, к производителю вопросов и рекламаций нет. Платка сделана неплохо и доступно на L7805, atmega8, двух uln2308, st232, плюс светодиоды и реле. Думал уже купить такой же блок, чтобы ничего нового не придумывать, но зачесалось "я могу" и вот...

Блок-схема примерно такая:

Дальше монтажка, провода. Обожаемые ныне чип-резисторы и конденсаторы (по наличию). Мелко, дёшево и наловчившись очень удобно.

От прототипа ушёл в сторону изменения интерфейса на программный USB, проект v-usb. Посмотрим, насколько надёжным окажется устройство. К примеру, USB-COM переходники у меня вырубались при коммутации сильных индуктивных нагрузок, в частности мотора привода крыши. Постарался побольше конденсаторов дать. Двенадцативольтовая часть для питания реле будет подаваться от того же компа - это в ТЗ, так как должна быть возможность удалённо выключать комп и тем самым перегружать реле, если оно зависнет.

Беда в том, что монтажка оказалась мелкой, что мне надо больше реле. Так как в схеме стоит ULN2003, то доступны 7 ключей. Наверное, выведу на отдельном разъёме 4 реле на вторую платку.

Программа является адаптированной версией примера V-USB. Оригинал зажигал / тушил один светодиод, получая один байт от компа. И передавал один байт обратно при запросе состояния светодиода. Теперь же один байт передаёт состояние до 8 реле, обратно передаётся состояние до 8 концевиков (кнопок).

Подготовка среды программирования

Оригинал здесь: Разработка устройства USB - как начать работу с библиотеками AVR USB (V-USB) и libusb . На всяк случай выдержки вкратце:

Firmware (программа на устройстве)

За основу взят hid-custom-rq из примеров v-usb. Он зажигает светодиод и опрашивает статус зажжёного светодиода. Далее он грубо модифицирован, получился следующий код:

#define LED_PORT_DDR        DDRC
#define LED_PORT_OUTPUT     PORTC
#define LED_PORT_INPUT      PORTB
#define LED0_BIT             0
#define LED1_BIT             1
#define LED2_BIT             2

#include 
#include 
#include   /* for sei() */
#include      /* for _delay_ms() */

#include    /* 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 */

PROGMEM 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
};


/* ------------------------------------------------------------------------- */

usbMsgLen_t usbFunctionSetup(uchar data[8
])
{
usbRequest_t *rq = (void *)data
;

if((
rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR
){
if(
rq->bRequest == CUSTOM_RQ_SET_STATUS
){

if(
rq->wValue.bytes[0] & 1){
/* set LED */
LED_PORT_OUTPUT |= _BV(LED0_BIT
);
}else{
/* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED0_BIT
);
}
if(
rq->wValue.bytes[0] & 2){
/* set LED */
LED_PORT_OUTPUT |= _BV(LED1_BIT
);
}else{
/* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED1_BIT
);
}
if(
rq->wValue.bytes[0] & 4){
/* set LED */
LED_PORT_OUTPUT |= _BV(LED2_BIT
);
}else{
/* clear LED */
LED_PORT_OUTPUT &= ~_BV(LED2_BIT
);
}

}else if(
rq->bRequest == CUSTOM_RQ_GET_STATUS
){

static
uchar dataBuffer[1];
/* buffer must stay valid when usbFunctionSetup returns */
dataBuffer[0] = LED_PORT_INPUT
;
usbMsgPtr = dataBuffer;
/* tell the driver which data to return */
return 1;
/* tell the driver to send 1 byte */

}
}
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
();
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(LED0_BIT);
/* make the LED bit an output */
LED_PORT_DDR |= _BV(LED1_BIT);
/* make the LED bit an output */
LED_PORT_DDR |= _BV(LED2_BIT);
/* make the LED bit an output */
sei
();
for(;;){
/* main event loop */
wdt_reset
();
usbPoll
();
}
}

Надо бы, конечно, ещё почистить и переписать, но не сейчас, как обычно... ВНИМАНИЕ: Нужно (если нужно) добавить установку битов подтягивающих резисторов на входы.

Программа на компе, командная строка

Ограничения этой программы, может работать только с тремя реле. Суть ограничения в простой схеме преобразования входного параметра в передаваемый байт, до трёх реле, значит передаваемое число помещается в одну цифру (0...7), значит достаточно вычесть код '0' и получить передаваемый байт. Для работы с большим количеством реле надо грамотно переводить d{1,3} в передаваемый байт. Не суть, я разбираюсь с V-USB, а не программированием на Cи :)

#include 
#include 
#include 
#include         /* this is libusb */
#include "opendevice.h" /* common code moved to separate module */

#include "../firmware/requests.h"   /* custom request numbers */
#include "../firmware/usbconfig.h"  /* device's VID/PID and names */

static void usage(char *name
)
{
fprintf(stderr, "usage: "
);
fprintf(stderr, "  %s number.... set relay (use 1, 2, 4 and combinations for relays) ", name
);
fprintf(stderr, "  %s status ... ask current status of inputs ", name);
}

int main(int argc, char **argv
)
{
usb_dev_handle *handle = NULL
;
const
unsigned char rawVid[2] = {USB_CFG_VENDOR_ID}, rawPid[2] = {USB_CFG_DEVICE_ID
};
char                vendor[] = {USB_CFG_VENDOR_NAME, 0}, product[] = {USB_CFG_DEVICE_NAME, 0
};
char                buffer[4
];
int                 cnt, vid, pid, isOn
;

usb_init
();
if(
argc < 2){
/* we need at least one argument */
usage(argv[0
]);
exit(
1
);
}
/* compute VID/PID from usbconfig.h so that there is a central source of information */
vid = rawVid[1] * 256 + rawVid[0
];
pid = rawPid[1] * 256 + rawPid[0
];
/* The following function is in opendevice.c: */
if(usbOpenDevice(&handle, vid, vendor, pid, product, NULL, NULL, NULL) != 0
){
fprintf(stderr, "Could not find USB device "%s" with vid=0x%x pid=0x%x ", product, vid, pid
);
exit(
1
);
}

if(strcasecmp(argv[1], "status") == 0
){
cnt = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_GET_STATUS, 0, 0, buffer, sizeof(buffer), 5000
);
if(
cnt < 1
){
if(
cnt < 0
){
fprintf(stderr, "USB error: %s ", usb_strerror
());
}else{
fprintf(stderr, "only %d bytes received. ", cnt
);
}
}else{
printf("RELAY is %d ", buffer[0
]);
}
}else {
cnt = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, argv[1][0] - '0', 0, buffer, 0, 5000
);
if(
cnt < 0
){
fprintf(stderr, "USB error: %s ", usb_strerror
());
}
}
usb_close(handle
);
return
0
;
}

Программа на компе, c#, windows form

Используется обёртка для LibUsb для C#. Зовут пакет LibUsbDotNet, живёт пока здесь:http://sourceforge.net/projects/libusbdotnet/ . Качается, ставится, ссылка добавляется в проект, после чего в проект добавляется в начале:

// в самом начале

using LibUsbDotNet
;
using LibUsbDotNet.Info
;
using LibUsbDotNet.Main
;

// в определении класса (рядом с кодом кнопки)

public static UsbDevice MyUsbDevice
;
public static
UsbDeviceFinder MyUsbFinder = new UsbDeviceFinder(0x16c0, 0x05df);

В коде тестовых кнопок добавим. Для передачи значения 1, например (установка первого реле, остальные выключены):

private void button16_Click(object sender, EventArgs e)
{
MyUsbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder
);
if (
MyUsbDevice == null) throw new Exception("Device Not Found."
);


UsbSetupPacket packet = new UsbSetupPacket((byte)(UsbCtrlFlags.RequestType_Vendor | UsbCtrlFlags.Recipient_Device | UsbCtrlFlags.Direction_Out), 1, (short)1, 0, 0
);
int countIn
;
byte[] data = new byte[1
];
MyUsbDevice.ControlTransfer(ref packet, data, 0, out countIn
);
}

Для получения данных со входов:

private void button17_Click(object sender, EventArgs e)
{
MyUsbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder
);
if (
MyUsbDevice == null) throw new Exception("Device Not Found."
);

UsbSetupPacket packet = new UsbSetupPacket((byte)(UsbCtrlFlags.RequestType_Vendor | UsbCtrlFlags.Recipient_Device | UsbCtrlFlags.Direction_In), 2, (short)0, (short)0, (short)0
);
int countIn
;
byte[] data = new byte[1
];
if (
MyUsbDevice.ControlTransfer(ref packet, data, 1, out countIn) && (countIn == 1
))
{
MessageBox.Show("read ok, value = " + data[0].ToString
());
}
}

Драйвер устройства

Драйвер делает libusb/bin/inf-wizard.exe . Запустив его, когда устройство воткнуто в USB можно парой кликов получить драйвера для всех платформ и установить его под текущую платформу.

Все исходные файлы проекта в зипе здесь: http://oleg.milantiev.com/files/v-usb-atmega8-relay.zip

 


Архив

Телефон: +7 (928) 425-32-10
e-mail: