• AVR Freaks

USB Printer Class(Client, not Host)

Author
errolt
New Member
  • Total Posts : 11
  • Reward points : 0
  • Joined: 2010/04/02 16:43:46
  • Location: 0
  • Status: offline
2010/04/06 04:02:32 (permalink)
0

USB Printer Class(Client, not Host)

Hi,

I've seached on the forum and didn't find a conclusive answer.

I'm building a plotter with a laser as "pen". The plotter contains 2 stepper motors, 2 limit switches, the laser and a PIC18F2550. Currently I'm using the USART port to send commands to the 18F2550 to create cutting paths.

I would like to go over to a USB Printer class but the info is scarce. I have found the USB.ORG docs for the Printer Class, but I have never been successfull in converting USB.ORG docs into code.

I also saw that the Microchip USB stack has a USB Printer Host, but i need Client.

In other words, I want to build a USB Printer.

Has anybody done this?

Thank you,
Errol
#1

8 Replies Related Threads

    DarioG
    Allmächtig.
    • Total Posts : 54081
    • Reward points : 0
    • Joined: 2006/02/25 08:58:22
    • Location: Oesterreich
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/06 16:31:08 (permalink)
    0
    Never thought about it, and I don't know if anybody did.
    But it would be nice indeed Smile

    GENOVA :D :D ! GODO
    #2
    chinzei
    Super Member
    • Total Posts : 2250
    • Reward points : 0
    • Joined: 2003/11/07 12:39:02
    • Location: Tokyo, Japan
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/08 14:40:05 (permalink)
    0
    errolt:

    Currently I'm using the USART port to send commands to the 18F2550 to create cutting paths.

    I would like to go over to a USB Printer class but the info is scarce.

    Then, CDC is better than printer class.
    CDC device exposes a virtual COM port on your PC.
    And then, you don't need to write any new PC application for the laser plotter over printer port connection. Just change the COM port number on your current application.

    Microhip USB examples are downloaded in this library.

    Microchip Application Libraries v2010-02-09
    http://ww1.microchip.com/downloads/en/devicedoc/MCHP_App_%20Lib%20v2010_02_09_Installer.zip

    This example is good for the starting point of your plotter project.
    C:\Microchip Solutions\USB Device - CDC - Basic Demo


    In other words, I want to build a USB Printer.

    The firmware of printer class is not so complicated.
    But you need to make up a custom PC device driver for the printer.
    It's more pain than the firmware development.

    I'll show you a snippet of printer firmware on the next post.
    When you plug in this device, Windows request drivers for each device of three driver layers.
    - IEEE1284.4 device
    - IEEE1284.4 compatible printer
    - Printer
    For the first and second, you can choose in-box driver on the new hardware wizard.
    But for the last one, you have to make up a custom driver (though you can force to assign an existing product from the list, but it doesn't match to your device).

    Examples of device driver is provided in WDK
    C:\WINDDK\7600.16385.1\src\print

    How to Get the WDK
    http://www.microsoft.com/whdc/DevTools/WDK/WDKpkg.mspx

    Tsuneo
    #3
    chinzei
    Super Member
    • Total Posts : 2250
    • Reward points : 0
    • Joined: 2003/11/07 12:39:02
    • Location: Tokyo, Japan
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/08 14:41:24 (permalink)
    0
    Snippet of printer class (device) implementation

    This snippet is based on this example (v2.6a)
    C:\Microchip Solutions\USB Device - WinUSB - Generic Driver Demo

    Tsuneo


    usb_config.h

    // add these definitions

    /* Printer class */
    #define PRINTER_INTF_ID 0
    #define PRINTER_EP_NUM 1
    #define PRINTER_EP_OUT_SIZE 64
    #define PRINTER_EP_IN_SIZE 64




    usb_descriptor.c

    #ifndef __USB_DESCRIPTORS_C
    #define __USB_DESCRIPTORS_C

    /** INCLUDES *******************************************************/
    #include "./USB/usb.h"

    /** CONSTANTS ******************************************************/
    #if defined(__18CXX)
    #pragma romdata
    #endif

    /* Device Descriptor */
    ROM USB_DEVICE_DESCRIPTOR device_dsc=
    {
    0x12, // Size of this descriptor in bytes
    USB_DESCRIPTOR_DEVICE, // DEVICE descriptor type
    0x0200, // USB Spec Release Number in BCD format
    0x00, // Class Code
    0x00, // Subclass code
    0x00, // Protocol code
    USB_EP0_BUFF_SIZE, // Max packet size for EP0, see usb_config.h
    0xFF07, // Vendor ID: temporary for development
    0x0100, // Product ID:
    0x0000, // Device release number in BCD format
    0x01, // Manufacturer string index
    0x02, // Product string index
    0x03, // Device serial number string index
    0x01 // Number of possible configurations
    };

    /* Configuration 1 Descriptor */
    ROM BYTE configDescriptor1[]={
    // Configuration Descriptor
    0x09, //sizeof(USB_CFG_DSC), // Size of this descriptor in bytes
    USB_DESCRIPTOR_CONFIGURATION, // CONFIGURATION descriptor type
    0x20,0x00, // Total length of data for this cfg
    1, // Number of interfaces in this cfg
    1, // Index value of this configuration
    0, // Configuration string index
    _DEFAULT | _SELF, // Attributes, see usb_device.h
    50, // Max power consumption (2X mA)

    // Interface Descriptor
    0x09, //sizeof(USB_INTF_DSC), // Size of this descriptor in bytes
    USB_DESCRIPTOR_INTERFACE, // INTERFACE descriptor type
    PRINTER_INTF_ID, // Interface Index
    0, // Alternate Setting Number
    2, // Number of endpoints in this intf
    0x07, // Class code : Printers
    0x01, // Subclass code: Printers
    0x03, // Protocol code: 1284.4 compatible bi-directional
    0, // Interface string index

    // Endpoint Descriptor
    0x07, //sizeof(USB_EP_DSC)
    USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
    PRINTER_EP_NUM | _EP_OUT, //EndpointAddress
    _BULK, //Attributes
    PRINTER_EP_OUT_SIZE,0x00, //size
    1, //Interval

    // Endpoint Descriptor
    0x07, //sizeof(USB_EP_DSC)
    USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
    PRINTER_EP_NUM | _EP_IN, //EndpointAddress
    _BULK, //Attributes
    PRINTER_EP_IN_SIZE,0x00, //size
    1, //Interval
    };


    //Language code string descriptor
    ROM struct{BYTE bLength;BYTE bDscType;WORD string[1];}sd000={
    sizeof(sd000),USB_DESCRIPTOR_STRING,{0x0409}};

    //Manufacturer string descriptor
    ROM struct{BYTE bLength;BYTE bDscType;WORD string[25];}sd001={
    sizeof(sd001),USB_DESCRIPTOR_STRING,
    {'M','i','c','r','o','c','h','i','p',' ',
    'T','e','c','h','n','o','l','o','g','y',' ','I','n','c','.'
    }};

    //Product string descriptor
    ROM struct{BYTE bLength;BYTE bDscType;WORD string[32];}sd002={
    sizeof(sd002),USB_DESCRIPTOR_STRING,
    {'M','i','c','r','o','c','h','i','p',' ','P','r','i','n','t','e','r',
    ' ','E','x','a','m','p','l','e',' ','D','e','v','i','c','e'}};

    //Serial number string descriptor
    ROM struct{BYTE bLength;BYTE bDscType;WORD string[4];}sd003={
    sizeof(sd003),USB_DESCRIPTOR_STRING,
    {'0','0','0','1',}};


    //Array of configuration descriptors
    ROM BYTE *ROM USB_CD_Ptr[]=
    {
    (ROM BYTE *ROM)&configDescriptor1
    };
    //Array of string descriptors
    ROM BYTE *ROM USB_SD_Ptr[]=
    {
    (ROM BYTE *ROM)&sd000,
    (ROM BYTE *ROM)&sd001,
    (ROM BYTE *ROM)&sd002,
    (ROM BYTE *ROM)&sd003
    };

    /** EOF usb_descriptors.c ***************************************************/

    #endif




    main.c

    USB_VOLATILE BOOL prn_flag_soft_reset = FALSE;

    void ProcessIO(void)
    {
    // Blink the LEDs according to the USB device status,
    // but only do so if the PC application isn't connected
    // and controlling the LEDs.
    if(blinkStatusValid)
    {
    BlinkUSBStatus();
    }

    if( (USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1) ) return;

    // a packet comes from host
    if( !USBHandleBusy( USBGenericOutHandle ) )
    {
    BYTE received_num;

    received_num = USBHandleGetLength( USBGenericOutHandle ); // number of data bytes on OUTPacket[]
    //
    // process data on OUTPacket[] here
    //
    // pass the buffer to USB engine for next packet
    USBGenericOutHandle = USBGenRead( PRINTER_EP_NUM, (BYTE*)&OUTPacket, PRINTER_EP_OUT_SIZE );
    }
    // Soft_Reset comes from host
    if ( prn_flag_soft_reset ) {
    prn_flag_soft_reset = FALSE;

    //
    // Stop current print process
    // Clear printer buffer, if any
    //
    }

    }//end ProcessIO


    void USBCBInitEP(void)
    {
    USBEnableEndpoint( PRINTER_EP_NUM, USB_OUT_ENABLED|USB_IN_ENABLED|USB_HANDSHAKE_ENABLED|USB_DISALLOW_SETUP );
    USBGenericOutHandle = USBGenRead( PRINTER_EP_NUM, OUTPacket, PRINTER_EP_OUT_SIZE );
    prn_flag_soft_reset = FALSE;
    }


    void USBCheckPrinterRequest( void ); // prototype

    void USBCBCheckOtherReq( void )
    {
    USBCheckPrinterRequest();
    }




    //
    // class-specific request handlers for printer
    //

    // external global variables

    extern volatile CTRL_TRF_SETUP SetupPkt;
    extern volatile BYTE CtrlTrfData[];
    extern volatile BDT_ENTRY *pBDTEntryOut[];

    // definition of USB_NEXT_PING_PONG - copied from usb_device.c

    #if (USB_PING_PONG_MODE == USB_PING_PONG__NO_PING_PONG)
    #define USB_NEXT_PING_PONG 0x0000
    #elif (USB_PING_PONG_MODE == USB_PING_PONG__EP0_OUT_ONLY)
    #define USB_NEXT_PING_PONG 0x0000
    #elif (USB_PING_PONG_MODE == USB_PING_PONG__FULL_PING_PONG)
    #if defined (__18CXX) || defined(__C30__)
    #define USB_NEXT_PING_PONG 0x0004
    #elif defined(__C32__)
    #define USB_NEXT_PING_PONG 0x0008
    #else
    #error "Not defined for this compiler"
    #endif
    #elif (USB_PING_PONG_MODE == USB_PING_PONG__ALL_BUT_EP0)
    #define USB_NEXT_PING_PONG 0x0004
    #else
    #error "No ping pong mode defined."
    #endif


    // bRequest value for printer class-specific requests
    enum {
    PRNT_GET_DEVICE_ID = 0,
    PRNT_GET_PORT_STATUS = 1,
    PRNT_SOFT_RESET = 2
    };

    // Port Status bitmap
    typedef union __attribute__ ((packed))
    {
    BYTE b;
    struct __attribute__ ((packed))
    {
    unsigned :3;
    unsigned prn_not_error :1;
    unsigned prn_select :1;
    unsigned prn_paper_empty :1;
    unsigned :2;
    };
    } T_prn_port_status;

    // Device ID string
    // see these links for the details
    // http://www.undocprint.org/formats/communication_protocols/ieee_1284
    // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/pnplpt.rtf

    typedef struct __attribute__ ((packed))
    {
    BYTE size_h, size_l;
    BYTE str[];
    } T_prn_Device_ID;

    static ROM T_prn_Device_ID prn_Device_ID =
    {
    0x00, 14 + 15 + 11 + 12 + 20, // size of string, two-bytes, MSB first
    { // these strings are concatenated by compiler
    "MFG:MICROCHIP;" // manufacturer
    "MDL:MCHP-PR001;" // model
    "CMD:1284.4;" // PDL command set
    "CLS:PRINTER;" // class
    "DES:MICROCHIP PR001;" // description
    // "CID:LPTENUM\Hewlett-PackardHP_La847D" // compatible ID: HP LaserJet 1200
    }
    };


    void USBCheckPrinterRequest( void )
    {
    // Get_Device_ID
    if ( (SetupPkt.bmRequestType == (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE))
    && (SetupPkt.bRequest == PRNT_GET_DEVICE_ID)
    && (SetupPkt.wValue == 0) // config index (start with 0, == bConfigurationValue - 1)
    && (SetupPkt.bIntfID == 0) // alternate interface
    && (SetupPkt.bIntfID_H == PRINTER_INTF_ID) // interface number, bInterfaceNumber
    ) {
    // pass the Device ID string except for the last '\0'
    USBEP0SendROMPtr( (ROM BYTE *)&prn_Device_ID, prn_Device_ID.size_l + 2, USB_EP0_INCLUDE_ZERO );
    }


    // Get_Port_Status
    if ( (SetupPkt.bmRequestType == (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE))
    && (SetupPkt.bRequest == PRNT_GET_PORT_STATUS)
    && (SetupPkt.wValue == 0) // must be zero
    && (SetupPkt.wIndex == PRINTER_INTF_ID) // interface number, bInterfaceNumber
    && (SetupPkt.wLength == 1) // size of reply: must be 1
    ) {
    T_prn_port_status port_status;

    port_status.b = 0; // initialize status bitmap

    port_status.prn_not_error = 1; // 1 = No Error, 0 = Error
    port_status.prn_select = 1; // 1 = Selected, 0 = Not Selected
    port_status.prn_paper_empty = 0; // 1 = Paper Empty, 0 = Paper Not Empty

    CtrlTrfData[0] = port_status.b; // pass the status to the stack
    USBEP0SendRAMPtr( CtrlTrfData, 1, USB_EP0_INCLUDE_ZERO );
    }


    // Soft_Reset
    if (((SetupPkt.bmRequestType == (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE))
    || (SetupPkt.bmRequestType == (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_OTHER)))
    && (SetupPkt.bRequest == PRNT_SOFT_RESET)
    && (SetupPkt.wValue == 0) // must be zero
    && (SetupPkt.wIndex == PRINTER_INTF_ID) // interface number, bInterfaceNumber
    && (SetupPkt.wLength == 0) // must be zero
    ) {
    BDT_ENTRY *p;
    // if the bulk endpint is stalled, recover it
    #if defined(__C32__)
    DWORD* pUEP;

    pUEP = (DWORD*)(&U1EP0);
    pUEP += (PRINTER_EP_NUM*4);
    #else
    unsigned char* pUEP;

    pUEP = (unsigned char*)(&U1EP0+PRINTER_EP_NUM);
    #endif
    if ( *pUEP & UEP_STALL ) {
    *pUEP &= ~UEP_STALL; // Clear EPSTALL bit in the UEP register
    // Clear BSTALL bit on BDnSTAT for OUT endpoint
    p = pBDTEntryOut[PRINTER_EP_NUM];
    #if (USB_PING_PONG_MODE == USB_PING_PONG__ALL_BUT_EP0) || (USB_PING_PONG_MODE == USB_PING_PONG__FULL_PING_PONG)
    p->STAT.Val = _USIE|_DAT0|_DTSEN;
    ((BYTE_VAL*)&p)->Val ^= USB_NEXT_PING_PONG; //toggle over to the next buffer
    p->STAT.Val = _USIE|_DAT1|_DTSEN;
    #else
    p->STAT.Val = _USIE|_DAT1|_DTSEN;
    #endif
    // Clear BSTALL bit on BDnSTAT for IN endpoint
    p = pBDTEntryIn[PRINTER_EP_NUM];
    #if (USB_PING_PONG_MODE == USB_PING_PONG__ALL_BUT_EP0) || (USB_PING_PONG_MODE == USB_PING_PONG__FULL_PING_PONG)
    p->STAT.Val = _UCPU|_DAT0;
    ((BYTE_VAL*)&p)->Val ^= USB_NEXT_PING_PONG; //toggle over the to the next buffer
    p->STAT.Val = _UCPU|_DAT1;
    #else
    p->STAT.Val = _UCPU|_DAT1;
    #endif
    }
    // discard and pass the buffer
    if( !USBHandleBusy( USBGenericOutHandle ) ) {
    USBGenericOutHandle = USBGenRead( PRINTER_EP_NUM, (BYTE*)&OUTPacket, PRINTER_EP_OUT_SIZE );
    }

    prn_flag_soft_reset = TRUE;
    USBEP0Transmit( USB_EP0_NO_DATA ); // report success on STATUS stage
    }
    }
    post edited by chinzei - 2010/04/08 15:02:18
    #4
    DarioG
    Allmächtig.
    • Total Posts : 54081
    • Reward points : 0
    • Joined: 2006/02/25 08:58:22
    • Location: Oesterreich
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/08 17:02:00 (permalink)
    0
    thanks Tsuneo!

    GENOVA :D :D ! GODO
    #5
    errolt
    New Member
    • Total Posts : 11
    • Reward points : 0
    • Joined: 2010/04/02 16:43:46
    • Location: 0
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/09 01:34:40 (permalink)
    0
    Thanks Tsuneo!

    I saw that it isn't complicated, only 3 commands on top of the standard Chapter 9 commands. But i always struggle with the implimentation.

    Currently i do use the CDC class, but for some reason i can't set up a Generic Text printer in windows on that com port. Windows's print spooler gives an error if i try to print to it. Will look into that more.

    Regarding the printer driver. I was hoping to also specify the Generic Printer Driver for the USB printer port.

    Thanks a lot for the code. I'll try and work through it this weekend.

    Thank you,
    Errol
    #6
    chinzei
    Super Member
    • Total Posts : 2250
    • Reward points : 0
    • Joined: 2003/11/07 12:39:02
    • Location: Tokyo, Japan
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/09 07:06:16 (permalink)
    0
    Regarding the printer driver. I was hoping to also specify the Generic Printer Driver for the USB printer port.

    It's a good idea.
    I've investigated Windows default INF files a little more, and found these clues.

    a) Windows have two default INF for USB printer class (class_07)

    usbprint.inf - USB\Class_07
    dot4.inf - USB\Class_07&SubClass_01&Prot_03

    When dot4.inf is applied, driver installation is interractive, not automatically.

    usbprint.inf
    [Microsoft]
    %USBPRINT.DeviceDesc% = USBPRINT_Inst,USB\Class_07,GENERIC_USB_PRINTER

    dot4.inf
    [ControlFlags]
    ExcludeFromSelect = *
    InteractiveInstall = USB\Class_07&SubClass_01&Prot_03

    [MS_Models]
    %DOT4USB.DeviceDesc% = DOT4USB_Inst,USB\Class_07&SubClass_01&Prot_03,GENERIC_USB_PRINTER


    b) Generic text-only printer is defined in another default INF, as follows.

    ntprint.inf
    [Generic]
    "Generic / Text Only" = TTY.GPD  ,GenericGeneric_/_Tex8040,Generic_/_Text_Only

    According to this MS document,
    http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/pnplpt.rtf
    this device ID on the INF is generated by these manufacturer and model fields of printer "Device ID" string. (revised)

    MFG:Generic;
    MDL:Generic_/_Text_Only;



    Now that we know these facts, modify above code as follows.

    a) To apply usbprint.inf silently on installation, change InterfaceProtocol value on the interface descriptor, from 3 (1284.4) to 2 (generic bi-directional)

       // Interface Descriptor
       0x09,                       //sizeof(USB_INTF_DSC),  // Size of this descriptor in bytes
       USB_DESCRIPTOR_INTERFACE,   // INTERFACE descriptor type
       PRINTER_INTF_ID,            // Interface Index
       0,                          // Alternate Setting Number
       2,                          // Number of endpoints in this intf
       0x07,                       // Class code   : Printers
       0x01,                       // Subclass code: Printers
       0x02,                       // Protocol code: bi-directional    <---------------
       0,                          // Interface string index


    b) To assign "Generic / Text Only" driver silently, change printer device ID string as follows. (revised)

    static ROM T_prn_Device_ID prn_Device_ID =
    {
    0x00, 12 + 24 + 11 + 12 + 30, // size of string, two-bytes, MSB first
    { // these strings are concatenated by compiler
    "MFG:Generic;" // manufacturer (case sensitive)
    "MDL:Generic_/_Text_Only;" // model (case sensitive)
    "CMD:1284.4;" // PDL command set
    "CLS:PRINTER;" // class
    "DES:Generic text only printer;" // description
    }
    };



    Tsuneo
    post edited by chinzei - 2010/04/10 20:08:30
    #7
    chinzei
    Super Member
    • Total Posts : 2250
    • Reward points : 0
    • Joined: 2003/11/07 12:39:02
    • Location: Tokyo, Japan
    • Status: offline
    RE: USB Printer Class(Client, not Host) 2010/04/10 20:16:26 (permalink)
    0
    In my above post, I've mistakenly posted printer ID string (already revised)
    The actual printer ID string for text-only is as follows.

    MFG:Generic;
    MDL:Generic_/_Text_Only;

    Also, the code for prn_Device_ID was revised.
    It's case sensitive, and with underscore.

    With this modification, the printer firmware is recognized by Windows silently (except for tray pop-up).

    If you've tried the wrong string, sorry for your inconvenience.

    Tsuneo
    #8
    zimou13
    New Member
    • Total Posts : 17
    • Reward points : 0
    • Joined: 2018/02/20 01:04:42
    • Location: 0
    • Status: offline
    Flagged as Spam (1)
    Re: RE: USB Printer Class(Client, not Host) 2018/03/27 04:48:33 (permalink)
    0
    We have a composite USB device stack that is composed of a printer class, HID class, and CDC class (in that order) and we have been seeing some strange behavior on the printer class on Windows. We have built a proper WHQL driver and for the most part, everything runs great. 
    ShowBox VidMate Mobdro
    post edited by zimou13 - 2018/03/30 04:28:47
    #9
    Jump to:
    © 2019 APG vNext Commercial Version 4.5