/*
Steuersoftware für (eine!) chinesische RGB-Leuchtdiode WS2812
am ATtiny10 (oder ATtiny9) mit der auf < 1 KByte geschrumpften
V-USB-Firmware von Tim Böschke.
Henrik Haftmann, 171214, Freeware.
Codepage: UTF-8, Tabweite: 8, Einrücktiefe: 2
Dies ist für libusb-0.1! Nicht libusb-1.0!
libusb-0.1 und libusb-1.0 sind nicht kompatibel,
ungefähr so kompatibel wie Blaubeeren und Braunbären.
Dieser Quelltext ist für MSVC6 und meine msvcrt-light.lib erstellt.
Die Bindung der ATtiny10-Schaltung an libusb-0.1 erfolgt
am sichersten mit "inf-wizard.exe" aus dem Download-Paket
"libusb-win32-bin-1.2.6.0.zip".
Die dort enthaltenen Treiber sind zertifiziert.
Mit libusb-1.0 habe ich's partout nie zum Laufen bekommen,
für diese Erkenntnis 3 Tage benötigt;
mit "libusb-win32-xx" war's hingegen ganz einfach,
allerdings ohne gefundene Dokumentation.
Dass es bei 0.1 eine naheliegende Funktion
usb_open_by_hid_vid() nicht gibt, ist verschmerzbar.
HID ist in die 1 KByte Flash des ATtiny10 nur schwer hineinzubekommen,
ich werde mich mal damit beschäftigen … — nee, passt wirklich nicht hinein!
+200821 Zugriff auf den ATtiny9/10 via WinUSB, INF-Dateizuweisung „mit Gewalt“
per Geräte-Manager, unter Windows 10/64 kein Neustart mit abgeschaltetem
Treiberzertifizierungszwang erforderlich
+200821 Umstellung auf komplett dynamisches Laden sowohl der WinUSB.dll
als auch von libusb0.dll.
*/
#include "lusb0_usb.h" // fehlerhafte Funktionsdeklarationen (fehlendes _cdecl),
// daher selbstgemachte Deklarationen s.u.
#include <stdio.h>
#include <shlwapi.h>
// WinUSB:
#include <SetupAPI.h>
//#include <winusb.h> // geht irgendwie nicht mit MSVC6,
// daher die wenigen benötigten Zeilen kopiert, s.u.
// aus <winusb.h>:
typedef PVOID WINUSB_INTERFACE_HANDLE;
struct WINUSB_SETUP_PACKET{
UCHAR RequestType;
UCHAR Request;
USHORT Value;
USHORT Index;
USHORT Length;
};
// Praktische Basisklasse zum dynamischen Laden einer Liste von Funktionen
// Von <dynaload> abgeleitete Klassen müssen _am Anfang_ lauter Funktionszeiger
// (selten: Variablenimporte) definieren.
// Dazu entsprechend muss der Konstruktor aufgerufen werden
// mit einer doppelnullterminierten Namensliste
// und einem optionalen Template für die Funktionsnamen.
// Im Fall statischer Instanzen müssen die statischen Konstruktoren
// per Laufzeitbibliothek oder mit initterm() aufgerufen werden.
// Das Prüfen auf gültige Einsprungpunkte muss der Aufrufer übernehmen!
struct dynaload{ // Basisklasse für hLib + importierte Funktionszeiger
load(const char*n) {
if (hLib) for(FARPROC*fp=(FARPROC*)(&hLib+1);*n;n+=lstrlen(n)+1,fp++) {
*fp=GetProcAddress(hLib,n);
}
}
load(const BYTE*n) {
if (hLib) for(FARPROC*fp=(FARPROC*)(&hLib+1);*n;fp++,n++) {
*fp=GetProcAddress(hLib,LPCSTR(*n));
}
}
load(const WORD*n) {
if (hLib) for(FARPROC*fp=(FARPROC*)(&hLib+1);*n;fp++,n++) {
*fp=GetProcAddress(hLib,LPCSTR(*n));
}
}
HINSTANCE hLib;
dynaload(const char*n,const char*t):hLib(LoadLibraryA(n)) { // n = doppelnullterminierte Namensliste, t = Namens-Template (um gleichlautende Anfänge zu eliminieren u.ä.)
if (hLib) for(FARPROC*fp=(FARPROC*)(&hLib+1);*(n+=lstrlen(n)+1);fp++) {
char n2[64];
_snprintf(n2,sizeof n2,t,n);
*fp=GetProcAddress(hLib,n2);
}
}
dynaload(const char*n):hLib(LoadLibraryA(n)) { // n = doppelnullterminierte Namensliste
if (hLib) for(FARPROC*fp=(FARPROC*)(&hLib+1);*(n+=lstrlen(n)+1);fp++) {
*fp=GetProcAddress(hLib,n); // Ohne Namenstemplate geht's ohne snprintf()
}
}
dynaload(const char*n,const BYTE*b):hLib(LoadLibraryA(n)) {load(b);} // n = DLL-Name, nullterminierte Byte-Liste
dynaload(const char*n,BYTE):hLib(LoadLibraryA(n)) {load((BYTE*)(n+lstrlen(n)+1));} // n = DLL-Name + nullterminierte Byte-Liste
dynaload(const char*n,const WORD*w):hLib(LoadLibraryA(n)) {load(w);} // n = DLL-Name, nullterminierte Word-Liste
dynaload(const char*n,WORD):hLib(LoadLibraryA(n)) {load((WORD*)(n+lstrlen(n)+1));} // n = DLL-Name + nullterminierte Word-Liste
dynaload(const wchar_t*n,const BYTE*b):hLib(LoadLibraryW(n)) {load(b);}
dynaload(const wchar_t*n,const WORD*w):hLib(LoadLibraryW(n)) {load(w);}
~dynaload() {if (hLib) FreeLibrary(hLib);}
};
// Bei WinUsb sind es gerade mal 3 Funktionen, die benötigt werden.
// Dafür muss man den ATtiny9/10 mit Gewalt als winusb-Gerät verknüpfen.
// aber dafür geht das sogar ohne Admin und Neustart unter Windows 10/64.
struct WinUsb:public dynaload{ // rein dynamisch laden, damit's auch ohne winusb.dll funktioniert (unter W2k, Win98)
BOOL (__stdcall*Initialize) (HANDLE DeviceHandle,WINUSB_INTERFACE_HANDLE*InterfaceHandle);
BOOL (__stdcall*Free) (WINUSB_INTERFACE_HANDLE);
BOOL (__stdcall*ControlTransfer) (WINUSB_INTERFACE_HANDLE,WINUSB_SETUP_PACKET,PUCHAR,ULONG,PULONG,LPOVERLAPPED);
WinUsb():dynaload("winusb.dll\0Initialize\0Free\0ControlTransfer\0","WinUsb_%s") {};
};
// Die WinUSB-Version macht sich nicht die Mühe, nach VID unf PID zu fahnden.
// Stattdessen wird das erste USB-Device genommen, das sich mit WinUsb_Initialize öffnen lässt.
// Eine Interface-GUID-Zuweisung per INF-Datei wäre zwar der von Microsoft
// festgelegte Weg, aber da müsste man eine /signierte/ INF-Datei haben.
// Was sehr schwer hinzubekommen ist.
// WinUSB per USB-Deskriptor (im ATtiny) für Windows 10 und „treiberlose“
// Installation passt auf keinen Fall mehr in das eine Kilobyte Flash.
// Für einen smarten Rauchmelder ist es da in eine 14-beinige PIC16F1455
// in Assembler(!) implementiert: http://www.tu-chemnitz.de/~heha/ewa/Ofen/rauch.zip/
static bool tryWinusb(int color) {
WinUsb winusb;
if (!winusb.hLib) return false; // Konstruktor versagt
static const GUID iface={0xa5dcbf10,0x6530,0x11d2,{0x90,0x1f,0x00,0xc0,0x4f,0xb9,0x51,0xed}}; // GUID_DEVINTERFACE_USB_DEVICE
WINUSB_INTERFACE_HANDLE hWinusb=0;
HANDLE hDevice;
HDEVINFO di = SetupDiGetClassDevs((GUID*)&iface,0,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE); // Take a snapshot of all devices exposing the interface
if (di!=INVALID_HANDLE_VALUE) {
SP_DEVICE_INTERFACE_DATA id;
id.cbSize = sizeof id;
for (DWORD i=0;SetupDiEnumDeviceInterfaces(di,0,(GUID*)&iface,i,&id);i++) { // Get the <idx> interface in the result set
struct{
SP_DEVICE_INTERFACE_DETAIL_DATA d;
TCHAR space[MAX_PATH];
}d;
d.d.cbSize=sizeof d.d;
if (SetupDiGetDeviceInterfaceDetail(di,&id,&d.d,sizeof d,0,0)) {
hDevice=CreateFile(d.d.DevicePath,GENERIC_WRITE|GENERIC_READ,FILE_SHARE_WRITE|FILE_SHARE_READ,0, // get a file handle for the USB device
OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
if (hDevice==INVALID_HANDLE_VALUE) hDevice=0;
else if (winusb.Initialize(hDevice,&hWinusb)) break;
else CloseHandle(hDevice);
}
}
SetupDiDestroyDeviceInfoList(di);
}
if (!hWinusb) return false;
WINUSB_SETUP_PACKET setup={0x40,42,color&0xFF00,color>>16&0xFF|color<<8&0xFF00};
winusb.ControlTransfer(hWinusb,setup,0,0,0,0);
winusb.Free(hWinusb);
CloseHandle(hDevice);
return true;
}
/* libusb 0.1 */
// Im Zeitalter von WinUSB wird libusb nicht mehr überall verfügbar sein.
// Daher Umstellung auf dynamisches Leden der benötigten Einsprungpunkte.
// 200821: Ungetestet!
struct LibUsb:public dynaload{
void (_cdecl*init) ();
int (_cdecl*find_busses) ();
int (_cdecl*find_devices) ();
usb_bus*(_cdecl*get_busses) ();
usb_dev_handle*(_cdecl*open) (struct usb_device*);
int (_cdecl*close) (usb_dev_handle*);
char* (_cdecl*strerror) ();
int (_cdecl*set_configuration) (usb_dev_handle*,int config);
int (_cdecl*claim_interface) (usb_dev_handle*,int iface);
int (_cdecl*release_interface) (usb_dev_handle*,int iface);
int (_cdecl*control_msg) (usb_dev_handle*,int,int,int,int,char*,int,int);
LibUsb():dynaload("libusb0.dll\0"
"init\0find_busses\0find_devices\0get_busses\0"
"open\0close\0strerror\0set_configuration\0"
"claim_interface\0release_interface\0control_msg\0","usb_%s") {};
};
// Find and open the first ATtiny10 with WS2812 firmware
static usb_dev_handle* my_open(LibUsb&libusb) {
struct usb_bus *bus;
struct usb_device *dev;
for (bus = libusb.get_busses(); bus; bus = bus->next) {
for (dev = bus->devices; dev; dev = dev->next) {
if (dev->descriptor.idVendor == 0x1781
&& dev->descriptor.idProduct == 0x0C9F) return libusb.open(dev);
}
}
return NULL;
}
static bool tryLibusb(int color) {
LibUsb libusb; // ruft den Konstruktor
if (!libusb.hLib) return false;
libusb.init(); // initialize the library
libusb.find_busses(); // find all busses
libusb.find_devices(); // find all connected devices
usb_dev_handle*dev=my_open(libusb);
if (!dev) {
printf("error opening device: \n%s\n", libusb.strerror());
return false;
}
#if 0
if (libusb.set_configuration(dev,1) < 0)
{
printf("error setting config: %s\n", libusb.strerror());
goto done;
}
else
{
printf("success: set configuration\n");
}
if (libusb.claim_interface(dev,0) < 0)
{
printf("error claiming interface:\n%s\n", libusb.strerror());
goto done;
}
else
{
printf("success: claim_interface\n");
}
#endif
libusb.control_msg(dev,USB_TYPE_VENDOR,42,
color&0xFF00, // wValue: High byte = green
color>>16&0xFF|color<<8&0xFF00, // wIndex: Low byte = red, High byte = blue
NULL,0,1000); // = WS2812 order GRB, see ~heha/Mikrocontroller/LEDs#4.
libusb.release_interface(dev, 0);
libusb.close(dev);
return true;
}
// Main program avoiding run-time library code inclusion
EXTERN_C void mainCRTStartup() {
int color; // RGB value from command line
TCHAR s[10];
s[0]='0'; // StrToIntEx() needs this prefix
s[1]='x';
TCHAR*p=PathGetArgs(GetCommandLine());
if (!*p) {
printf("Usage: Hexadecimal RGB value as single argument\n");
ExitProcess(0);
}
if (*p=='#') p++; // Ignore HTML-style '#' prefix (for ease typing)
lstrcpyn(s+2,p,8);
StrToIntEx(s,STIF_SUPPORT_HEX,&color);
if (!tryWinusb(color) && !tryLibusb(color)) {
printf("Neither WinUSB nor libusb works.\n");
}
ExitProcess(0);
}
| Detected encoding: ANSI (CP1252) | 4
|
|
|