Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HID Discuss #5

Open
xxxajk opened this issue Oct 14, 2015 · 28 comments
Open

HID Discuss #5

xxxajk opened this issue Oct 14, 2015 · 28 comments

Comments

@xxxajk
Copy link
Collaborator

xxxajk commented Oct 14, 2015

Please review the UHS_HID_interface struct.
Lets make sure it covers most of what we will need to pass onto a hid USB driver.
One thing at a time here too. We can add stuff later on for BT.
https://github.com/felis/UHS30/blob/master/libraries/UHS_HID/UHS_HID.h

@felis
Copy link
Owner

felis commented Oct 14, 2015

I can't see a hook for the parser - where is it?
On Oct 13, 2015 11:27 PM, "Andrew Kroll" [email protected] wrote:

Please review the UHS_HID_interface struct.
Lets make sure it covers most of what we will need to pass onto a hid USB
driver.
One thing at a time here too. We can add stuff later on for BT.
https://github.com/felis/UHS30/blob/master/libraries/UHS_HID/UHS_HID.h


Reply to this email directly or view it on GitHub
#5.

@xxxajk
Copy link
Collaborator Author

xxxajk commented Oct 14, 2015

The info comes from the host. As far as parsing the HID data, that has to be done yet.
I'm only interested in making sure the struct holds everything needed.
One thing I can see that I already need to throw out is klass-- we're hid, we already know that.

@felis
Copy link
Owner

felis commented Oct 14, 2015

Do we need vidpid?
On Oct 14, 2015 12:44 AM, "Andrew Kroll" [email protected] wrote:

The info comes from the host. As far as parsing the HID data, that has to
be done yet.
I'm only interested in making sure the struct holds everything needed.
One thing I can see that I already need to throw out is klass-- we're hid,
we already know that.


Reply to this email directly or view it on GitHub
#5 (comment).

@xxxajk
Copy link
Collaborator Author

xxxajk commented Oct 14, 2015

Yes, to trump "basic" drivers to specifically crafted ones.
Basically the information is used as a weight on what interface driver to use within HID.
Think from the bottom up, not top down, which is what made earlier software pretty tangled.

@xxxajk
Copy link
Collaborator Author

xxxajk commented Oct 14, 2015

Oh and don't worry, once the information is used, it will be thrown away. it is only very basic at this point. Much of this information can be reduced to a newer struct that contains only the bare minimums to work.

@felis
Copy link
Owner

felis commented Oct 14, 2015

all looks good to me

@Lauszus
Copy link
Collaborator

Lauszus commented Oct 14, 2015

What is the purpose of UHS_BT_HOST_BASE?

@xxxajk
Copy link
Collaborator Author

xxxajk commented Jan 18, 2017

Same thing, it is a base class.

@rtek1000
Copy link

rtek1000 commented Nov 4, 2019

Hello, I tried the 2.0 library with the shield MAX3421E + Mega2560 and it worked (HID mouse/keyboard and pendrive). Outputs occur on the serial monitor.

But I could not get any output on the serial monitor with library 3.0, just start message.

What must I do to get results?

The MAX3421E RESET pin is connected directly to the Arduino Reset pin, does it have any influence?

Where are the pin definitions? In which file?

@xxxajk
Copy link
Collaborator Author

xxxajk commented Nov 4, 2019

There are a couple of ways. Here is how I do this.
First create the parser that you want.
See https://github.com/felis/UHS30/blob/master/libraries/UHS_host/UHS_HID/examples/USB_HOST_SHIELD/UHS_HID_KB_MOUSE/UHS_HID_KB_MOUSE.ino

Then create a global pointer to the host object, parser(s), and to the drivers that you will be using.

MAX3421E_HOST *UHS_Usb; // hardware USB host
UHS_USBHub *hub_1;  // in case we use a hub.
UHS_HID *hid1; // hid instance
myHID_parser *hid_parser; // parser

In setup do ONE of the following:
UHS_Usb = new MAX3421E_HOST(); // default pins
or
UHS_Usb = new MAX3421E_HOST(pss, pirq); // pss is SS pin, pirq is IRQ pin
or
UHS_Usb = new MAX3421E_HOST(pss, pirq, pspd); // pss is SS pin, pirq is IRQ pin, pspd is the SPI speed in case you want to try a set value.

Then allocate the drivers:

hid_parser = new myHID_parser(); // our HID parser
hub_1 = new UHS_USBHub(UHS_Usb); // in case we use a hub.
hid1 = new UHS_HID (UHS_Usb,  my_hid_parser); // attaches the parser to HID and the USB hardware
while(UHS_Usb->Init(1000) != 0); // start the USB services

Note that what happens in the parser should happen as quickly as possible.

It is best to just do what you need to do on the parser "event".
If you need to wait for several events, just use a simple finite state machine and you can move to the next event.

If you want to pass some kind of result back to loop(), you MUST use volatile objects and variables, you MUST also implement locking too, which is fine if you are advanced enough.

Does this help you?

@xxxajk
Copy link
Collaborator Author

xxxajk commented Nov 4, 2019

See also README.md, which includes specific AVR information!!!

@rtek1000
Copy link

rtek1000 commented Nov 5, 2019

Thanks, I hadn't noticed the instruction to redirect the interrupt signal (maybe it's the heat, lots of sunshine here these days). It worked with Mega2560. Thank you.

@xxxajk
Copy link
Collaborator Author

xxxajk commented Nov 5, 2019

"When all else fails, read the instructions." :-)

@rtek1000
Copy link

rtek1000 commented Nov 5, 2019

Should it also work when using a HUB? The HID device works without the HUB. But the HUB is recognized. Is it possible to use HID and Pendrive with only one MAX3421E?

@xxxajk
Copy link
Collaborator Author

xxxajk commented Nov 6, 2019 via email

@rtek1000
Copy link

Hi @xxxajk ,

To help monitor and control the state of keyboard LED's, I added these routines below, but it would be really nice to have GetReport() working, I tried to create this function, but did not work, the returned value is always 0x00.

  • The user can send values from 0 to 7 via the serial port (serial monitor) to change the status of the LEDs.
  • A byte has been added to the keyboard data info, with the current status of the LEDs.
  • The NumLock, CapsLock, ScrollLock keys change the state of the LEDs.

UHS_HID_RAW.ino (modified):

// Load the USB Host System core
#define LOAD_USB_HOST_SYSTEM
// Load USB Host Shield
#define LOAD_USB_HOST_SHIELD
// Use USB hub, you might want this for multiple devices.
#define LOAD_UHS_HUB

// Patch printf so we can use it.
#define LOAD_UHS_PRINTF_HELPER
#define DEBUG_PRINTF_EXTRA_HUGE 0
#define DEBUG_PRINTF_EXTRA_HUGE_USB_HID 1

#define LOAD_UHS_HID

#include <Arduino.h>
#ifdef true
#undef true
#endif
#ifdef false
#undef false
#endif

#include "UHS_host.h"

volatile bool NumLock = true;
volatile bool CapsLock = false;
volatile bool ScrollLock = false;

volatile uint8_t led_states = 0b1;
volatile uint8_t led_states_prev = 0;
uint8_t led_states_prev2 = 0;
UHS_HID_base *dev_kb0;

unsigned millis0;

class myHID_processor : public UHS_HID_PROCESSOR {
public:
  myHID_processor(void) {}

  void onRelease(UHS_HID_base *d) {
    printf_P(PSTR("HID driver type %d no longer available.\r\n"), d->driver);
  }
  void onStart(UHS_HID_base *d) {
    printf_P(PSTR("HID driver type %d started, Subclass %02x, Protocol %02x\r\n"), d->driver, d->parent->bSubClass, d->parent->bProtocol);
  }
  void onPoll(UHS_HID_base *d, uint8_t *data, uint16_t length) {
    switch (d->driver) {
      case UHS_HID_raw:
        printf_P(PSTR("RAW input %d bytes interface %d, Subclass %02x, Protocol %02x Data:"), length, d->parent->bIface, d->parent->bSubClass, d->parent->bProtocol);
        for (int i = 0; i < length; i++) {
          printf_P(PSTR(" %02x"), data[i]);
        }

        if (d->parent->bProtocol == 1) {
          dev_kb0 = d;

          if ((length == 8) && (data[0] == 0) && (data[1] == 0) && (data[3] == 0) && (data[4] == 0) && (data[5] == 0) && (data[6] == 0) && (data[7] == 0)) {

            if (data[2] == 0x53) {
              if (NumLock == false) {
                led_states |= 0b1;

                NumLock = true;
              } else {
                led_states &= ~0b1;

                NumLock = false;
              }
            } else if (data[2] == 0x39) {
              if (CapsLock == false) {
                led_states |= 0b10;

                CapsLock = true;
              } else {
                led_states &= ~0b10;

                CapsLock = false;
              }
            } else if (data[2] == 0x47) {
              if (ScrollLock == false) {
                led_states |= 0b100;

                ScrollLock = true;
              } else {
                led_states &= ~0b100;

                ScrollLock = false;
              }
            }
          }

          if (led_states_prev != led_states) {
            led_states_prev = led_states;

            uint8_t rv;
            d->parent->pUsb->DisablePoll();
            rv = d->parent->SetReport(d->parent->bIface, 2, 0, 1, &led_states);
            d->parent->pUsb->EnablePoll();
          }

          printf_P(PSTR(" %02x"), led_states);
        }
        
        printf_P(PSTR("\r\n"));
        
        break;
      default:
        break;
    }
  }
};

myHID_processor HID_processor1;
myHID_processor HID_processor2;
MAX3421E_HOST UHS_Usb;
UHS_USBHub hub_1(&UHS_Usb);
UHS_HID hid1(&UHS_Usb, &HID_processor1);
UHS_HID hid2(&UHS_Usb, &HID_processor2);

void setup() {
  USB_HOST_SERIAL.begin(115200);
  while (UHS_Usb.Init(1000) != 0)
    ;
  printf_P(PSTR("\r\nHID RAW demo Begin.\r\n"));
}

void loop() {
  if ((millis() - millis0) > 500) {
    millis0 = millis();

    if (dev_kb0->parent->bProtocol == 1) {
      uint8_t rv;
      dev_kb0->parent->pUsb->DisablePoll();
      rv = dev_kb0->parent->SetReport(dev_kb0->parent->bIface, 2, 0, 1, &led_states);
      dev_kb0->parent->pUsb->EnablePoll();

      //      uint8_t led_tmp;
      //      dev_kb0->parent->pUsb->DisablePoll();
      //      rv = dev_kb0->parent->GetReport(dev_kb0->parent->bIface, 2, 0, 1, &led_tmp);
      //      dev_kb0->parent->pUsb->EnablePoll();
      //
      //      printf_P(PSTR("GetReport Data: %02x\r\n"), led_tmp);
    }
  }

  delay(1);
}

/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();

    if ((inChar >= '0') && (inChar <= '7')) {
      led_states = (inChar - 48) & 0b111;

      NumLock = led_states & 1;

      CapsLock = (led_states >> 1) & 1;

      ScrollLock = (led_states >> 2) & 1;
    }
  }
}

GetReport is basically SetReport and the value 0x01 instead of 0x09:

#define USB_HID_GET_REPORT 0x01U
#define USB_HID_SET_REPORT 0x09U

https://github.com/STMicroelectronics/stm32_mw_usb_host/blob/master/Class/HID/Inc/usbh_hid.h

/**
  * @brief  USBH_HID_GetReport
  *         retrieve Set Report
  * @param  phost: Host handle
  * @param  reportType  : Report type to be sent
  * @param  reportId    : Targeted report ID for Set Report request
  * @param  reportBuff  : Report Buffer
  * @param  reportLen   : Length of data report to be send
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_HID_GetReport(USBH_HandleTypeDef *phost,
                                      uint8_t reportType,
                                      uint8_t reportId,
                                      uint8_t *reportBuff,
                                      uint8_t reportLen)
{

  phost->Control.setup.b.bmRequestType = USB_D2H | USB_REQ_RECIPIENT_INTERFACE | \
                                         USB_REQ_TYPE_CLASS;


  phost->Control.setup.b.bRequest = USB_HID_GET_REPORT;
  phost->Control.setup.b.wValue.w = (uint16_t)(((uint32_t)reportType << 8U) | (uint32_t)reportId);

  phost->Control.setup.b.wIndex.w = 0U;
  phost->Control.setup.b.wLength.w = reportLen;

  return USBH_CtlReq(phost, reportBuff, (uint16_t)reportLen);
}
/**
  * @brief  USBH_HID_Set_Report
  *         Issues Set Report
  * @param  phost: Host handle
  * @param  reportType  : Report type to be sent
  * @param  reportId    : Targeted report ID for Set Report request
  * @param  reportBuff  : Report Buffer
  * @param  reportLen   : Length of data report to be send
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_HID_SetReport(USBH_HandleTypeDef *phost,
                                      uint8_t reportType,
                                      uint8_t reportId,
                                      uint8_t *reportBuff,
                                      uint8_t reportLen)
{

  phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_INTERFACE | \
                                         USB_REQ_TYPE_CLASS;


  phost->Control.setup.b.bRequest = USB_HID_SET_REPORT;
  phost->Control.setup.b.wValue.w = (uint16_t)(((uint32_t)reportType << 8U) | (uint32_t)reportId);

  phost->Control.setup.b.wIndex.w = 0U;
  phost->Control.setup.b.wLength.w = reportLen;

  return USBH_CtlReq(phost, reportBuff, (uint16_t)reportLen);
}

https://github.com/STMicroelectronics/stm32_mw_usb_host/blob/master/Class/HID/Src/usbh_hid.c


UHS_HID_INLINE.h:

uint8_t UHS_NI UHS_HID::SetReport(uint8_t iface, uint8_t report_type, uint8_t report_id, uint16_t nbytes, uint8_t* dataptr) {
        uint8_t rv;
        pUsb->DisablePoll();
        rv = pUsb->ctrlReq(bAddress, mkSETUP_PKT8(0x21U, 0x09U, report_id, report_type, iface, nbytes), nbytes, dataptr);
        pUsb->EnablePoll();
        return rv;
}

uint8_t UHS_NI UHS_HID::GetReport(uint8_t iface, uint8_t report_type, uint8_t report_id, uint16_t nbytes, uint8_t* dataptr) {
        uint8_t rv;
        pUsb->DisablePoll();
        rv = pUsb->ctrlReq(bAddress, mkSETUP_PKT8(0x21U, 0x01U, report_id, report_type, iface, nbytes), nbytes, dataptr);
        pUsb->EnablePoll();
        return rv;
}

@xxxajk
Copy link
Collaborator Author

xxxajk commented Jun 13, 2023

Still on AVR or something better? If on AVR, using ram expansion?
It's important to know so I can see if it's actually possible.

@rtek1000
Copy link

Still on AVR or something better? If on AVR, using ram expansion? It's important to know so I can see if it's actually possible.

This was tested on a UNO board and USB Host Shield (of this type) with no memory expansion.

Sketch uses 20952 bytes (64%) of program storage space. Maximum is 32256 bytes.
Global variables use 1538 bytes (75%) of dynamic memory, leaving 510 bytes for local variables. Maximum is 2048 bytes.
Low memory available, stability problems may occur

@xxxajk
Copy link
Collaborator Author

xxxajk commented Jun 19, 2023

UNO won't be able to do much because of limitations.
First, there's really not enough RAM for this particular stack, since it actually follows the rules. Strange things will happen, such as freezing caused by being out of heap.

The only AVRs I would recommend are the ones that can be RAM extended. You really need about 32K RAM just for this stack to do anything useful. More is better, of course. RAM extending AVR's include the ATMEGA 1280,2560,1286 and 2566, however it is less expensive to just use an MCU with the required amount of RAM, e.g. ARM or MIPS based MCUs.

@rtek1000
Copy link

It would be interesting if UHS30 could work with STM32 or ESP32 maybe.

I tried to make some adaptations but I still couldn't get it to compile in ESP32 or STM32.

@xxxajk
Copy link
Collaborator Author

xxxajk commented Jun 19, 2023

ESP32 isn't (currently) supported because of the RTOS.
If the STM32 is using an RTOS, that's pretty much a dead deal too.

i.MX chip native EHCI should be supported, don't even need an extra USB shield! You can get that here: https://www.pjrc.com/store/teensy41.html

MIPS native (Digilent boards) was started, but there was no interest, however it should be able to use the stack with the additional USB host shield. I have not tested it in a very long time, it required board-level modifications due to how they designed the board, and the boards are not offered any longer. :-(

@rtek1000
Copy link

Interesting, why is it necessary to use RTOS on ESP32, if on Arduino UNO it is possible to compile without RTOS? Or is Arduino UNO using RTOS?

These Teensy boards are very expensive.

@xxxajk
Copy link
Collaborator Author

xxxajk commented Jun 20, 2023

Espressif uses an RTOS because they are basically lazy (but that's my opinion).

The Teensy boards might seem expensive, however, you should consider what you are getting...
A 600MHz ARM MCU, with a ton of memory, both RAM and flash, and you can expand either or both to ridiculous amounts. up to 16MB of RAM or flash, or you can split it with 8MB of RAM and 8MB flash.
Also Paul's support of his products are second to none. He usually has an answer of a fix in a day or two. I highly recommend his products.

@rtek1000
Copy link

Yes, I saw that the board has good features, yet many people have been looking for cheaper boards like ESP32 or RP2040.

Maybe in a next project I'll try to use Teensy, if that much resource is needed.

I made a data logger with STM32 and since then I've been struggling with the USB port.

But now, after years, I'm managing to get around the software and hardware limitations, today I would choose an NXP over STM.

@rtek1000
Copy link

Hi, can you tell if the error control of packets received by the Arduino, are performed by IC MAX3421 (via hardware) or are performed by Arduino (via software)?

I tested a gamepad with Arduino and it works fine, but in STM32 it is generating incorrect data, it seems to me to be a lack of error control of the packets. STM32 is using internal PHY and CubeMX libraries.

I made a post on the ST forum, with more details:

https://community.st.com/t5/stm32-mcu-products/stm32-usb-host-fails-miserably-but-arduino-mega2560-and-uno-with/td-p/569199

STM32_Fail

@rtek1000
Copy link

It looks like the error handling is done by IC MAX3421e, I found a PDF of application notes:

MAX3421e

https://pdfserv.maximintegrated.com/en/an/AN3785.pdf

@xxxajk
Copy link
Collaborator Author

xxxajk commented Jun 23, 2023

Yeah, you need to process depending on the result of the transfer.
Not all of them are errors.
If you are writing something for ST's USB, and if it isn't EHCI, you should be able to port the Kinetis SIE drivers, since they are all nearly the same IP.
What you have to remember is that UHS3 is packet based not pipe based. Pipes make everything a lot more confusing when errors happen, and there really isn't much to be gained on low/full speed, and very few MCU can keep up with a 480Mbit stream on high speed.

@rtek1000
Copy link

Thank you for commenting on the Package vs Pipe, I added your comment there on the ST forum, maybe someone else might be interested in this UHS3 library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants