Source file: /~heha/Mikrocontroller/LEDs/ws2812tn10.zip/win10/src/ws2812tn10.cpp

/*
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
Wrong umlauts? - Assume file is ANSI (CP1252) encoded