ВНИМАНИЕ: эта версия схемы глючит при коммутации реальной нагрузки. Работающий вариант - с развязкой питания (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