• AVR Freaks

Helpful ReplyHot!Force Feedback Tutorial: PIC18F4550

Author
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
2016/11/08 12:29:45 (permalink)
5 (1)

Force Feedback Tutorial: PIC18F4550

 {WARNING!} - Sarcasm can be found throughout this tutorial.
                     - Grammar errors WILL be found throughout this tutorial.

Introduction

Hello, my name is Nutu and for a long time (years) I've been using PIC18FXXXX family chips to build devices.
This forum provided a lot of help for me and it's time to give something back to the community.
I sincerely thank you, the users of this forum for all the help you gave me to implement
a force-feedback device.

Scope:
Implementation of force-feedback capable firmware for USB devices powered by PIC18FXXXX family chips.


Future developments:
I intend to build a general Force-Feedback stack to be used with a wider range of PIC chips,
compliant with standard USB Protocol. ( no PC drivers for the effects);
I will release it on this page once it's finished.
----------------------------------------------------------------------------------------------------------------

Prologue:
"Lasciate ogni speranza, voi ch'entrate."
"Abandon all hope, you who enter here."

Implementing a force-feedback device is hard work.
Not because of the code length or code complexity, but because of the share amount of concepts
that are needed.

Force-Feedback devices are part of the HAPTIC devices category.
HAPTIC devices use force-feedback to transmit information to the user.

To keep it simple:
An application defines some forces witch have to be applied to an output device so the user can "FEEL" the forces.
The output device uses actuators to simulate the force defined by the application.
There is a lot of information throughout the internet but there isn't an A to Z tutorial, so whoever attempts to use force feedback on PIC chips it will soon find itself overwhelmed  with USB standards, HID, PID, Descriptors, configurations, etc.
 
There isn't a "ONE SIZE FITS ALL" solution for force-feedback devices.
Sometimes you have to write a driver on the OS side, sometimes you don't.
Your device should be portable and consumer oriented or is a specialized dedicated device?
Your device should follow strict ISO standards or not ?
Is it an industry oriented product or is intended for home use ?
Is it allowed to fail miserably without consequences or the world throws an "array out of bounds" exception if it fails and nasty stuff happens ?
...
...
These questions and considerations shall be answered in the design process of the device,before writing code for it.
 

TUTORIAL:
I will divide this tutorial in 6 parts:
1) Tools
- What tools are needed.
2) USB - Protocol
- Some general and necessary concepts.
3) USB - HID
4) USB - PID
5) Microchip USB Stack: Joystick
- Familiarize with the joystick example provided by Microchip.
6) Firmware: Force Feedback implementation on PIC18F4550 using Microchip USB Stack: Joystick
 
At the end of the tutorial, one should expect to have
a device: - recognized as a FFB PID Joystick under Windows OS.
             - able to receive the standard Force Feedback effect types as defined by (PID){Device Class Definition for Physical                      Interface Devices} (except user defined effect types).
#1
DarioG
Allmächtig.
  • Total Posts : 54081
  • Reward points : 0
  • Joined: 2006/02/25 08:58:22
  • Location: Oesterreich
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/08 14:59:58 (permalink)
0
thanks Smile
as I wrote some 6 months ago, I found an abandoned Joystick/cockppit for car games, and I changed the hardware using a PIC18 and connecting all buttons and axis. Then there is a motor for simple force feedback and... I had/have no clue how to interface it.
So, I am interested - I could not find and easy and standard way to do it...

GENOVA :D :D ! GODO
#2
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 01:30:36 (permalink) ☄ Helpfulby vtrx 2019/06/06 16:03:04
0
PART 1: Tools
This section contains the (N) necessary tools and (H) helpful tools you need in order to build/modify the firmware.
N 00 - Universal Serial Bus (USB)
Class Definition
for Human Interface
Devices (HID)
HID1_11.pdf from usb.org
link
N 01 - Universal Serial Bus (USB)
HID Usage Tables
V1.12
HUT1_12.pdf from usb.org
link
N 02 - Universal Serial Bus (USB)
Device Class Definition for
Physical Interface Devices
(PID)
V1.0
pid1_01.pdf from usb.org
link
( because books ARE tools. and these are the standards of reference )

N 1 - MPLAB IDE v8.56

N 2 - Microchip Application Libraries 2010/08/04

N 3 - MPLAB C18 Lite V3.36


H 1 - Joytester
can detect a joystick and shows what values are reported by the device (joystick)

H 2 - ForceTest
can detect a force-feedback capable joystick and can send force-feedback effects to the device

H 3 - Dt (usb.org HID descriptor tool)
tool for building Human Interface Device descriptors

H 4 - CFFDT (my force feedback PID descriptor tool)
badly made tool for building HID/PID descriptors.
i had to write one, because no PID descriptor tool is available on the usb.org site and i haven't found one online.
(source code included).
( once i can post links...)
#3
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 01:36:42 (permalink)
0
PART 2: USB - Protocol
I won't cover the entire usb protocol, as there are a lot of free resources for learning it. ( remember the books N 00/N 01/ N 03?)
I will however introduce concepts from the usb protocol that are relevant to this tutorial.
 
DEVICE CATEGORY:
 
The device we are building falls under the category: PID - Physical Interface Devices
PID itself is part of a larger category of devices known as HID - Human Interface Devices, or rather, is an extention to HID class.
 
The USB Protocol is intended to support a wide variety of devices from the most sophisticated thing you can imagine to a led lamp used for reading.
It then proceeds to describe how these devices transmit data over one common serial bus, without interfering and without loosing data.

It is a complex protocol, because of the share amount of different device categories that can use USB to transmit data, while being connected to the bus all at the same time.
 
Each device category is described by a DEVICE DESCRIPTOR a CONFIGURATION DESCRIPTOR and other descriptors that indicate to the HOST what the device is capable of doing and how it is going to do it.
The descriptors "DESCRIBE" the device to the host.
 
Example:
/* 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
MY_VID, // Vendor ID, see usb_config.h
MY_PID, // Product ID, see usb_config.h
0x0001, // Device release number in BCD format
0x01, // Manufacturer string index
0x02, // Product string index
0x00, // 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
DESC_CONFIG_WORD(0x0029), // 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
0, // Interface Number
0, // Alternate Setting Number
2, // Number of endpoints in this intf
HID_INTF, // Class code
0, // Subclass code
0, // Protocol code
0, // Interface string index
/* HID Class-Specific Descriptor */
0x09,//sizeof(USB_HID_DSC)+3, // Size of this descriptor in bytes RRoj hack
DSC_HID, // HID descriptor type
DESC_CONFIG_WORD(0x0111), // HID Spec Release Number in BCD format (1.11)
0x00, // Country Code (0x00 for Not supported)
HID_NUM_OF_DSC, // Number of class descriptors, see usbcfg.h
DSC_RPT, // Report descriptor type
DESC_CONFIG_WORD(HID_RPT01_SIZE), //sizeof(hid_rpt01), // Size of the report descriptor

/* Endpoint Descriptor */
0x07,/*sizeof(USB_EP_DSC)*/
USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
HID_EP | _EP_IN, //EndpointAddress
_INTERRUPT, //Attributes
DESC_CONFIG_WORD(64), //size
0x01, //Interval
/* Endpoint Descriptor */
0x07,/*sizeof(USB_EP_DSC)*/
USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
HID_EP | _EP_OUT, //EndpointAddress
_INTERRUPT, //Attributes
DESC_CONFIG_WORD(64), //size
0x01 //Interval
};
// the examples are taken directly from the firmware in this tutorial.

DEVICE SPEED:

Each device,even if belonging to the same category, needs a certain amount of bandwidth on the bus, can operate at different speeds or has time constraints that must be respected.
The speed indicate the maximum amount of data a device can transfer in a certain amount of time:
LOW SPEED slow 1.5 Mbit/s
FULL SPEED faster 12.0 Mbit/s
HIGH SPEED fastest 480.0 Mbit/s
 
The bandwidth and time constraints are handled by the type of data transfer between device and host.
Control Transfers // used for device enumeration (!) and configuration (this is where the descriptors are sent to the host)
Interrupt Transfers // limited amount of data,reserved bandwidth, used for mouse,keyboards,joysticks,things that must transmit the data in a short amount of time,loseless,with guaranteed time to transmit on the bus.
Isochronous Transfers // unlimited amount of data,lots of bandwidth,no reserved bandwidth, no bandwidth =no transfers=loss of data, no data control=loss of data. (webcam)
Bulk Transfers // limited amount of data,no guaranteed bandwidth, error control on the data. loseless
 
We will be using a FULL SPEED device with Interrupt Transfers.
( FULL SPEED HID/PID DEVICE using INTERRUPT TRANSFERS.)
#4
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 02:18:24 (permalink)
+1 (1)
PART 3: USB - HID
The only thing relevant for now is : REPORT DESCRIPTOR.
The other relevant thing: when you read the HID1_11.pdf, hid descriptor is split into:
Report descriptor and Physical descriptor.
THAT PHYSICAL DESCRIPTOR IS NOT WHAT IDENTIFIES A FORCE FEEDBACK DEVICE, IT IS SOMETHING ELSE.

The CONFIGURATION descriptor is used to identify the device as a HID class device, and such, when we plug the usb cable, the OS (Windows) reads the various descriptors and when the HID descriptor is sent, the OS looks for predefined drivers that can be used to communicate with the device.
As HID is standardized, Windows provides a series of predefined drivers for the device, as long as our device is compliant with the HID standard.
 
Thus, if we want to take full advantage of the HID (PID) class and use the predefined drivers Windows has already installed, we must be compliant with the HID(PID) class specifications (wich basically tells us how the HID (PID) driver on Windows sends/receives the data).
 
Information about HID class can be found in HID1_11.pdf
 
We will focus now on the REPORT DESCRIPTOR.
 
 
Report descriptor tells the HID class driver under windows how our device works, wich are it's capabilities ( axes, buttons, etc ), how will encode the transmitted data and how it wants the received data to be encoded.
The report descriptor is where our device is categorized as a Force Feedback capable device under Windows.
 
Page 12: HID1_11.pdf.  - here is where you want to start.
Page 47: HID1_11.pdf.  - here is where you want to stop.
 
Device descriptor structure - Interface descriptor - hid descriptor - REPORT descriptor
(the same rules apply when we will build the report descriptor for our PID device )
 
If you need to write a report descriptor, in these pages you will find out how those hexadecimal values are created:
 
Example:
0x85,0x01, // REPORT_ID (1)
0x05,0x02, // USAGE_PAGE (Simulation Controls)
0x09,0xBB, // USAGE (Throttle)
0x15,0x81, // LOGICAL_MINIMUM (-127)
0x25,0x7F, // LOGICAL_MAXIMUM (+127)
0x75,0x08, // REPORT_SIZE (8)
0x95,0x01, // REPORT_COUNT (1)
0x81,0x02, // INPUT (Data, Var, Abs)
 
The numeric values can be found on : HUT1_12v2.pdf
 
A brief attempt to explain how a report descriptor is constructed:
 
Report descriptors are composed by ITEMS.
Each ITEM has a [ 1 BYTE ] prefix, followed by 1,2,3,4 or more data bytes.
prefix,data
0x15,0xFF
The prefix is composed: 
 
         PREFIX
[bTag,bType,bSize]     [ DATA ]
[0001   01     01 ]       [ 0xFF ]
0x15,0xFF
 
if the data doesn't fit into 1 byte
         PREFIX
[bTag,bType,bSize]     [ DATA, DATA ]  here we have 2 bytes of data, and the bSize field, changed from 1 to 2. (01 to 10 )
[0001   01     10 ]       [ 0xFF, 0XFF ]
0x16,0xFF,0xFF
 
if you have 3 data bytes, 0x16 becomes 0x17, etc.
This is how you construct each row in the report descriptor.
 
Next, is the ITEM PARSER, wich basically scans the whole descriptor and extracts the items that are present, the values etc.
 
Item parser.
Item Types: main,global,local. ( defines the items scope,like the variables scope in c code (local to function, global, etc)).
Main item tags: Input,Output,Feature,Collection,End Collection. - there are values for these, and they are inserted into the bTag field of the PREFIX.
 
 
We will have a better look at how a report descriptor is constructed when we will examine the report descriptor present in the firmware, in part 6.
 
 
 
 
post edited by Coman - 2016/11/09 02:20:34
#5
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 02:29:56 (permalink)
0
PART 4: USB - PID
I'll make up for the lack of explanations for the HID class, by providing as well as I can an explanation for the PID class.
PID class is an extention to the HID class. (insert troll face here)

Before starting to write down code some more concepts have to be introduced:

ENDPOINTS AND PIPES.
The comunication between device and host is accomplished trough PIPES.
Pipe is a logic link/channel trough wich the data is sent/received.
Basically it is a location of memory on the device [ AN ADDRESS INTO DEVICE MEMORY ].
PIPES are composed by: ENDPOINTS.
ENDPOINTS are...locations of memory on the device. (indexes)
So what is the difference ?
A PIPE is an association ENDPOINT + direction (of the data: host to device, or device to host ).
You can use the same endpoint, with different directions: IN/OUT to set 2 pipes using the same endpoint.
1 INPUT PIPE
1 OUTPUT PIPE
For the PID class, 3 pipes are required.

pipe                   endpoint      direction
control Pipe        ENDPOINT 0  default
HID Input Pipe    ENDPOINT 1  IN
HID Output Pipe  ENDPOINT 1  OUT

The control pipe is used for configuring the device, when is attached to the bus or while is attached for changing some parameters.
The input pipe is used by the device to send data to the host. (axis position,button states, hat switches states, etc )
The output pipe is used by the host to send data to the device. (the force-feedback effects will be sent over this pipe).

To keep things short: The Joystick demo, provided by the Microchip USB Stack, already has: control pipe, input pipe and output pipes defined.
you don't have to worry ( for now ) about pipes and endpoints.

FILE REFERENCE: usb_config.h
/* HID */
#define HID_INTF_ID 0x00
#define HID_EP 1 // 1 endpoint , the 2 directions IN/OUT allows us to create 2 pipes.
#define HID_INT_OUT_EP_SIZE 64 // maximum data size for the output
#define HID_INT_IN_EP_SIZE 64 // maximum data size for the input
#define HID_NUM_OF_DSC 1 // using just 1 descriptor. ( we could have more than 1 descriptor and...it's out of scope for this tutorial )
#define HID_RPT01_SIZE 0x4FA // the size of the report descriptor ( PID descriptor )

pid1_01.pdf from usb.org defines how a standard plug and play force feedback device should behave.
It defines the data structures needed for the comunication and what each value/field inside the data structures reppresent.

To "describe" the device behaviour and its capabilities to the host, we need to write a report descriptor.
the report descriptor is a description of the data structure that is going to be sent, and what each data field reppresents.
When we write our report descriptor we have to decide how many axes our ffb device will have, how many buttons, how many effect types can support, etc.

Report descriptor, describes the Reports, and for reports we mean : data structure containing different types of data fields used to reppresent our device and it's behaviour.
Reports, after all, are just data that is being transmitted on the bus.
Reports can be INPUT, or OUTPUT.
INPUT Reports contain the data the device sends to the host. ( axes,buttons states )
OUTPUT Reports contain the data the host sends to the device.( force-feedback data )

pid1_01.pdf indicates how we must set our data structures inside the device as to successfully accept the OUTPUT REPOTRS the applications will send.
It is such a generic description, that boils down to this (for us, with c18 and mplab,and pic18):
You set up a:
struct
{
field 1;
field 2;
field n;
}struct_name;
struct_name OUTPUT_REPORT_TYPE;
if(good_things_happened_function()) // an output report arrived ?
{
read_output_report(OUTPUT_REPORT_TYPE); // read the report and save it inside our struct.
}

the data structure must be 1:1 compliant with the structure defined by the PID standard AND the structure defined in the report descriptor;
Output reports are sent by the host, when a force-feedback effect has to be played on the device
Output reports are sent by the host, when a force-feedback effect has to be created.

The host knows nothing about the device, then it enumerates the device, if successful will load a standard driver for it and leave it there.
The application ( game ) on the host starts, it looks arround to see if there are force feedback devices available. ( think as: it searches for ffb joysticks ).
When the application has filled it's list with the found devices ( you could have more that 1 ffb device attached ) then it procedes by interrogating the device about its capabilites.
It requests the report descriptor, then it tryes to create the standard force feedback effects on the device. ( constant force, periodic force etc ) because the application doesn't yet know if a device supports a certain effect type or not.
( you could give a look at DIRECT INPUT (part of DIRECTX) to see how an application interrogates a device about it's effects ).

The application sends OUTPUT REPORTS to the device: SET EFFECT.
If the device doesn't repond, no force feedback is supported by the device.
if the device responds:
negative: the device supports force feedback, but not this effect type,
Positive : the device supports the effect
IF NEGATIVE: the application tryes to create the next standard force feedback effect, for all the effects.
IF POSITIVE: the application completes the effect definition by sending additional data, and requesting additional data from the device.
I know this is just scratching the surface of the iceberg.
More details are in section 6, where we analyze the firmware.
#6
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 09:18:27 (permalink)
0
PART 5: Microchip USB Stack: Joystick
The USB Stack from Microchip is wonderful, works great, is structured and can be moved around easily etc, until your copy-paste counter increments too much and now nothing works and you have no idea why.
And while you are loosing time trying to find that magic line of code to be modified so that another part of code works, a good time investment BEFORE even trying to modify the examples provided by the stack should be to READ the code and try to understand it!

THE STACK:
what files we are going to use/modify.
main.c - Here we will be writing our joystick behaviour (reading axis from the ADC,buttons from input ports,move the motors so we have force-feedbacks, etc)
usb_descriptors.c - Here we will write our descriptors. ( the infamous report descriptor that tells the pc this is a force feedback joystick is defined here)
usb_device.c - Here is the code that sends and receives packets on the bus, low level stuff...
usb_functions_hid.c - Here we have the code that receives the force feedback requests and we decide what to do with them (of course we call a function that is in another file, because reasons).
usb_config.h - Here we define the length of our report descriptor.
Some data structures are used by the USB stack,predefined, and we need to access them to retrieve some informations.
Data structures relevant to force-feedback effects will be discussed in part 6.
#7
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 09:37:45 (permalink)
+1 (1)
PART 6: Firmware - Force Feedback implementation on PIC18F4550 using Microchip USB Stack: Joystick
 
Brace yourselfs, this is going to be long !
 
  • Open PID1_01.pdf
  • Open USB Device - HID - Joystick - C18 - PICDEM FSUSB project from the Microchip example.
 
The first step, before writing the report descriptor, is to define the data structures needed to store the effects informations.
 
File of reference: main.c
 
typedef union _SET_GET_EFFECT_STRUCTURE
{
 struct
 {
  BYTE report_id;
  BYTE effect_type;
  BYTE byte_count; // valid only for custom force data effect.
       // custom force effects are not supported by this device.
 }SET_REPORT_REQUEST;
 struct
 {
  BYTE report_id; // 2
  BYTE effect_block_index; // index dell'effetto
  BYTE block_load_status; // 1 ok, 2 -out of memory, 3 JC was here, or maybe not ? case: undefined.
  int ram_pool_available;
 }PID_BLOCK_LOAD_REPORT;

BYTE val[8];
}SET_GET_EFFECT_STRUCTURE;

 
CONFIGURED_EFFECT_NUMBER[x] is a vector of values, 1 value for each effect, set to 0 if not configured or set to 1 if configured.
You could use another way of keeping track of the effects that the host configured. ( not all applications use all the effects described by PID )
 
Host sends a Set Report request to device.
Once the device receives this Set Report request and validates that it is capable of performing the
requested operation, the device allocates the memory for the requested effect. The size of the
allocated memory is decided by device.
Host then sends a Get Report request to device for the PID Block Load Report.
 
If there is not enough memory in the device or the device can not create the effect for some
reason, the device will return a 0 Effect Block Index and the appropriate Block Load Status to
indicate an error in the PID Block Load Report request.
 
If the device can successfully create the effect, then the host will send a Set Effect Report to the
device to download the effect parameters for that effect with the ID previously received from
device in the Effect Index field.. The Set Effect Report is an Output report sent to device.
 
/*******************************************************************
 * Function: void USBSetEffect(void)
 *
 *
 * PreCondition: None
 *
 * Input: None
 *
 *
 *
 * Output: None
 *
 * Side Effects: None
 *
 * Overview: This function is called from USER_SET_REPORT_HANDLER
 * to handle the PID class specific request: SET REPORT REQUEST
 * SET REPORT REQUEST is issued by the host application when it needs
 * to create a new effect. The output report, specifies wich
 * effect type to create.
 * The firmware creates the effect ( sets CONFIGURED_EFFECT_NUMBER[EFFECT TYPE] to 1 )
 * and prepares the get_report for the host.
 * The set report request is saved in hid_report_out[n]
 *
 *
 * Note: None
 *******************************************************************/
void USBSetEffect(void)
{
set_get_effect_structure.SET_REPORT_REQUEST.report_id=hid_report_out[0]; // 1: based on the pid report descriptor
set_get_effect_structure.SET_REPORT_REQUEST.effect_type=hid_report_out[1];// the type of effect, based on pid report descriptor
set_get_effect_structure.SET_REPORT_REQUEST.byte_count=hid_report_out[2]; // this device does not support custom effects

/*
 struct
 {
  BYTE report_id; // 2
  BYTE effect_block_index; // index dell'effetto
  BYTE block_load_status; // 1 ok, 2 -out of memory, 3 JC was here, or maybe not ? case: undefined.
  int ram_pool_available;
 }PID_BLOCK_LOAD_REPORT;

BYTE val[8];
}SET_GET_EFFECT_STRUCTURE;
*/
switch(set_get_effect_structure.SET_REPORT_REQUEST.effect_type)
{
case 1:// Usage ET Constant Force 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=1; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[0]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.

break;
case 2:// Usage ET Ramp 1
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=2; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[1]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 3:// Usage ET Square 2
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=3; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[2]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 4:// Usage ET Sine 3
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=4; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated.

CONFIGURED_EFFECT_NUMBER[3]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 5:// Usage ET Triangle 4
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=5; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[4]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 6:// Usage ET Sawtooth Up 5
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=6; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[5]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 7:// Usage ET Sawtooth Down 6
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=7; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this, because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[6]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 8:// Usage ET Spring 7
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=8; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this  because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated.

CONFIGURED_EFFECT_NUMBER[7]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 9:// Usage ET Damper 8
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=9; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this , because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[8]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 10:// Usage ET Inertia 9
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=10; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this , because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[9]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 11:// Usage ET Friction 10
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=11; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=1; // ok, i can load this , because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0xFFFF; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[10]=1; // I HAVE CONFIGURED CONSTANT FORCE EFFECT FOR THIS DEVICE
// now, when the host issues a get_report blah blah, i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
case 12:// Usage ET Custom Force Data 11 ! NOT SUPPORTED BY THIS DEVICE !
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.report_id=2; // 2= PID BLOCK LOAD REPORT
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.effect_block_index=0; //0=i can't create effect, 1 = CONSTANT FORCE, index in the array = effect_block_index-1 : 0
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.block_load_status=3; // ok, i can load this , because i have already memory for it, and because : reasons
set_get_effect_structure.PID_BLOCK_LOAD_REPORT.ram_pool_available=0x0000; // i have no ideea why i need this, however i have no more memory except for the effects already preallocated. 

CONFIGURED_EFFECT_NUMBER[11]=0; //
// now, when the host issues a get_report , i send the set_get_effect_structure.PID_BLOCK_LOAD_REPORT.
break;
}
}

 
 
 
USBSetEffect(void) is defined, and now we can accept or deny any effects that the host requests us to create for our device ( remember that the host application doesn't know what type of effects are supported by our device, so it will try to create all the standard effects defined by PID.
 
 
Now that we have a function to handle the SET EFFECT requests, and a data structure to store the necessary data of the set effect requests, the next step is responding to the SET_REPORT.
 
To do that, we must look for setup packets that the host sends to the device, and upon receiving the set report request call the previous function.
 
File of reference : usb_functions_hid.c
 
USBCheckHIDRequest(void) scans the setup packets that are sent by the host.
 
This function is already defined by the usb stack from Microchip, so we just have to modify it, to suit our needs.
 
To do that, we look at SetupPkt.bRequest field, and if bRequest field is a SET_REPORT request, we call our function USBSetEffect(void), using a user defined callback. ( wich basically is a function that is called each time a set effect request is received, and that function calls our USBSetEffect(void) function to handle the information.
 
USER_SET_REPORT_HANDLER(); is our callback function.
 
 
post edited by Coman - 2016/11/09 19:09:20
#8
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 19:10:59 (permalink)
0
PART 6: SECTION 2:

/********************************************************************
 Function:
  void USBCheckHIDRequest(void)
  
  Summary:
   This routine handles HID specific request that happen on EP0. 
        This function should be called from the USBCBCheckOtherReq() call back 
        function whenever implementing a HID device.

  Description:
   This routine handles HID specific request that happen on EP0. These
        include, but are not limited to, requests for the HID report 
        descriptors. This function should be called from the 
        USBCBCheckOtherReq() call back function whenever using an HID device. 

        Typical Usage:
        <code>
        void USBCBCheckOtherReq(void)
        {
            //Since the stack didn't handle the request I need to check
            // my class drivers to see if it is for them
            USBCheckHIDRequest();
        }
        </code>
  
 PreCondition:
  None
  
 Parameters:
  None
  
 Return Values:
  None
  
 Remarks:
  None
 
 *******************************************************************/
void USBCheckHIDRequest(void)
{
    if(SetupPkt.Recipient != USB_SETUP_RECIPIENT_INTERFACE_BITFIELD) return;
    if(SetupPkt.bIntfID != HID_INTF_ID) return;
    
    /*
     * There are two standard requests that hid.c may support.
     * 1. GET_DSC(DSC_HID,DSC_RPT,DSC_PHY);
     * 2. SET_DSC(DSC_HID,DSC_RPT,DSC_PHY);
     */
    if(SetupPkt.bRequest == USB_REQUEST_GET_DESCRIPTOR)
    {
        switch(SetupPkt.bDescriptorType)
        {
            case DSC_HID: //HID Descriptor 
                if(USBActiveConfiguration == 1)
                {
                    USBEP0SendROMPtr(
                        (ROM BYTE*)&configDescriptor1 + 18, //18 is a magic number. It is the offset from start of the configuration descriptor to the start of the HID descriptor.
                        sizeof(USB_HID_DSC)+3,
                        USB_EP0_INCLUDE_ZERO);
                }
                break;
            case DSC_RPT: //Report Descriptor 
                if(USBActiveConfiguration == 1)
                {
                    USBEP0SendROMPtr(
                        (ROM BYTE*)&hid_rpt01,
                        sizeof(hid_rpt01), //See usbcfg.h
                        USB_EP0_INCLUDE_ZERO);
                }
                break;
            case DSC_PHY: //Physical Descriptor
    //Note: The below placeholder code is commented out. HID Physical Descriptors are optional and are not used
    //in many types of HID applications. If an application does not have a physical descriptor,
    //then the device should return STALL in response to this request (stack will do this automatically
    //if no-one claims ownership of the control transfer).
    //If an application does implement a physical descriptor, then make sure to declare
    //hid_phy01 (rom structure containing the descriptor data), and hid_phy01 (the size of the descriptors in bytes),
    //and then uncomment the below code.
                //if(USBActiveConfiguration == 1)
                //{
                // USBEP0SendROMPtr((ROM BYTE*)&hid_phy01, sizeof(hid_phy01), USB_EP0_INCLUDE_ZERO);
                //}
                break;
        }//end switch(SetupPkt.bDescriptorType)
    }//end if(SetupPkt.bRequest == GET_DSC)
    
    if(SetupPkt.RequestType != USB_SETUP_TYPE_CLASS_BITFIELD)
    {
        return;
    }
#warning "QUI DEVO INSERIRE I MIEI HANDLERS PER I SET/GET REPORT REQUEST FFB DEVICE
    switch(SetupPkt.bRequest)
    {
        case GET_REPORT:
            #if defined USER_GET_REPORT_HANDLER // FFB DEVICE has to send OK; EFFECT CREATED AND HANDLERS
                USER_GET_REPORT_HANDLER();
            #endif
            break;
        case SET_REPORT: // FFB DEVICE has to create an effect
            #if defined USER_SET_REPORT_HANDLER
                USER_SET_REPORT_HANDLER();
            #endif 
            break;
        case GET_IDLE:
            USBEP0SendRAMPtr(
                (BYTE*)&idle_rate,
                1,
                USB_EP0_INCLUDE_ZERO);
            break;
        case SET_IDLE:
            USBEP0Transmit(USB_EP0_NO_DATA);
            idle_rate = SetupPkt.W_Value.byte.HB;
            break;
        case GET_PROTOCOL:
            USBEP0SendRAMPtr(
                (BYTE*)&active_protocol,
                1,
                USB_EP0_NO_OPTIONS);
            break;
        case SET_PROTOCOL:
            USBEP0Transmit(USB_EP0_NO_DATA);
            active_protocol = SetupPkt.W_Value.byte.LB;
            break;
    }//end switch(SetupPkt.bRequest)

}//end USBCheckHIDRequest

#9
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 19:12:26 (permalink)
0
PART 6: SECTION 3:
 
and the callback function:
void USER_SET_REPORT_HANDLER(void)
 {
  //I get the set report, then i get the DATA !
  // THE DEVICE NOW NEEDS TO ALLOCATE THE EFFECT
  // ONCE THE EFFECT IS ALLOCATED; THE HOST SENDS A GET REPORT, function above
  /*
  Offset Field Size Value Description
  0 bmRequestType 1 10100001b From device to host
  1 bRequest 1 0x01 Get_Report
  2 wValue 2 0x0203 Report ID (2) and Report Type (feature)
  4 wIndex 2 0x0000 Interface
  6 wLength 2 0x0500 Number of bytes to transfer in the data phase
  (5 bytes)
  */
  // DEVICE RESPONDS TO THE GET REPORT; WITH THE PID BLOCK LOAD REPORT
  
  //USBSetEffect();
  // I HAVE TO: 1: BE SURE I CHECK THE FEATURE, BECAUSE: REASONS
  
  switch(SetupPkt.W_Value.byte.LB) 
  {
  case 0x01: // report ID: 1, SET NEW EFFECT,APPLICATION DATA, etc , and now ?
     // NOW I CHECK THE REPORT TYPE ! INPUT? OUTPUT? FEATURE ? 

   if(SetupPkt.W_Value.byte.HB==0x02) // FEATURE, the host wants to send me the data, to set the effects. aka effect start 
   {
   // READ THE DATA, FEEL THE DATA, SAVE THE DATA! and set the data effect, read it, and set it
   USBEP0Receive((BYTE*)&hid_report_out[0], SetupPkt.wLength, USBSetDataEffect);
   } 
 
   if(SetupPkt.W_Value.byte.HB==0x03) 
   {
   // READ THE DATA, FEEL THE DATA, SAVE THE DATA! and set the effect
   USBEP0Receive((BYTE*)&hid_report_out[0], SetupPkt.wLength, USBSetEffect); 
   } 
  break;
  }
 }

 
we use USBEP0Receive((BYTE*)&hid_report_out[0], SetupPkt.wLength, USBSetEffect); to read the incoming data for the get_report request.
USBEP0Receive is a macro already defined by the Microchip usb stack, we only have to pass our function callback to it (USBSetEffect) and how much data it needs to read ( SetupPkt.wLength ) and we will save the data inside hid_report_out[0].
hid_report_out is a byte vector and is already defined by the stack.
 
now we have our firmware listening for setup packets, and respond to set reports with our user defined callback that calls our function wich saves the set report request data, and prepares to create the effects inside our device.
 
The next step is: after the host sends the set report, it will send a get report, the set report tell us what effect the host tryes to create, and in the get report, it will send the configuration parameters for that effect.
 
We passively accept the set report request, and prepare to accept data for the requested effect in the next stage: get_report request. 
If we would have some sort of memory management inside our firmware, this is the moment to check if we have enough memory for the new effect to be created, before responding to the get report request.
 
The next data structures are to be set in:
File of reference: main.c
 
in these data structures we will save our effects parameters.
I didn't invented these data structures, they are defined by PID1_01.pdf from page 6 to page 22.
note: the names of the data structures and relative data fields are the same names used in PID1_01.pdf.
 
 
 
#10
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 19:14:17 (permalink)
+1 (1)
PART 6 : SECTION 4:
 
 
typedef union _EFFECT_BLOCK
{
 struct
 {
  BYTE set_effect_report;
  BYTE effect_block_index;
  BYTE parameter_block_offset;
         
  BYTE rom_flag;
 }effect_block_parameters;

BYTE val[5];
}EFFECT_BLOCK;

 
 
typedef union _EFFECT
{
 struct
 {
  BYTE effect_type;
  BYTE effect_duration;
  BYTE effect_sample_period;
  BYTE effect_gain;
  BYTE effect_trigger_button;
  BYTE trigger_repeat_interval;
  BYTE axes_enable;
  BYTE direction_enable;
  BYTE direction;
  BYTE start_delay;

  BYTE type_specific_block_handle_number;
  BYTE type_specific_block_handle_1;// index to the type specific block1.
  BYTE type_specific_block_handle_2;// index to the type specific block2.


 }effect_parameters;
BYTE val[13];
}EFFECT; // 12 effects

 
 
typedef union _ENVELOPE_BLOCK
{
 struct
 {
  BYTE attack_level;
  BYTE attack_time;
  BYTE fade_level;
  BYTE fade_time;
 }envelope_block_parameters;

BYTE val[4];
}ENVELOPE_BLOCK; // 12 effects, max 7 envelopes. 1 envelope per effect that support envelopes

 
 
typedef union _CONDITION_BLOCK
{
 struct
 {
  BYTE cp_offset;
  BYTE positive_coefficient;
  BYTE negative_coefficient;
  BYTE positive_saturation;
  BYTE negative_saturation;
  BYTE dead_band;
 }condition_block_parameters;

BYTE val[6];
}CONDITION_BLOCK; // 12 effects, max 8 conditions. 4 per axis x/y. 2 conditions(x/y) per effect that support conditions

 
typedef union _PERIODIC_BLOCK
{
 struct
 {
  BYTE offset;
  BYTE magnitude;
  BYTE phase;
  BYTE period;
 }periodic_block_parameters;

BYTE val[4];
}PERIODIC_BLOCK;// 12 effects, max 5 PERIODIC_BLOCKs. 1 PERIODIC_BLOCK per effect that support PERIODIC_BLOCK

 
typedef union _CONSTANT_FORCE_BLOCK
{
 struct
 {
  BYTE magnitude;
 }constant_force_block_parameters;

BYTE val[1];
}CONSTANT_FORCE_BLOCK; // only for constant force. idk, maybe change later.

 
typedef union _RAMP_FORCE_BLOCK
{
 struct
 {
  BYTE ramp_start;
  BYTE ramp_end;
 }ramp_force_block_parameters;

BYTE val[2];
}RAMP_FORCE_BLOCK; // same a constant force, only 1 effect uses it, and it needs only 1 structure.

 
typedef union _EFFECT_OPERATIONS
{
 struct
 {
  BYTE op_effect_start;
  BYTE op_effect_start_solo;
  BYTE op_effect_stop;
  BYTE loop_count;
 }effect_operations_parameters;

BYTE val[4];
}EFFECT_OPERATIONS;

 
typedef union _DEVICE_GAIN
{
 struct
 {
  BYTE device_gain;
 }device_gain_parameters;

BYTE val[1];
}DEVICE_GAIN;

 
typedef union _INTPUT_CONTROLS_TYPEDEF
{
    struct
    {
  struct
        {
            BYTE RID;
           
        } REPORT_ID;
 
        struct
        {
            BYTE square:1;
            BYTE x:1;
            BYTE o:1;
            BYTE triangle:1;
            BYTE L1:1;
            BYTE R1:1;
            BYTE L2:1;
            BYTE R2:1;//
            BYTE select:1;
            BYTE start:1;
            BYTE left_stick:1;
            BYTE right_stick:1;
            BYTE home:1;
            BYTE :3; //filler
        } buttons;
        struct
        {
            BYTE hat_switch:4;
            BYTE :4;//filler
        } hat_switch;
        struct
        {
             int X;
             int Y;
             int Z;
             int Rz;
        } analog_stick;
    } members;
    BYTE val[12];
} INPUT_CONTROLS;

 
 
 
post edited by Coman - 2016/11/09 19:19:17
#11
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/09 19:18:09 (permalink)
+1 (1)
PART 6 : SECTION 5 
 
 
Now that we have: a callback for the set report request defined, a function to save the information that set report request provides and the data structures for saving the effect parameters,
the next step is to : respond to the get request report.
 
To do so, we provide another callback : 
File of reference: usb_function_hid.c
 
this is the callback:
 void USER_GET_REPORT_HANDLER(void)
 {
  /*
     OK, SET REPORT has arrived, the effect was allocated or less.
    the host sends a get report, so he wants the censored, i mean the data.
    the data was already preconfigured by USBSetEffect,so now, USBSendPIDBlockLoadReport(),will send the data to
    the censored, i mean , host.
  */
  switch(SetupPkt.W_Value.byte.LB) 
  {
  case 0x02:// REPORT ID? 2? PURRFECT, SEND THE censored DATA TO THE HOST
   switch(SetupPkt.W_Value.byte.HB) // REPORT TYPE
    {
     case 0x01:// INPUT REPORT
        // IGNORE FOR NOW....
     break;
     case 0x02: // OUTPUT, the host wants to send me the data
        // IGNORE FOR NOW....
     break;
     case 0x03:// FEATURE, the host wants ME to send the data
         USBSendPIDBlockLoadReport();
     break;
    } 
  
  break;
  }
    
 }

 
this is where we use the callback:
 
void USBCheckHIDRequest(void)
{
//... stuff
//... stuff
//... 
case GET_REPORT:
            #if defined USER_GET_REPORT_HANDLER // FFB DEVICE has to send OK; EFFECT CREATED AND HANDLERS
                USER_GET_REPORT_HANDLER();
            #endif
            break;
//...
//... other stuff
//...

 
the function is defined in:
File of reference: main.c
 
void USBSendPIDBlockLoadReport(void)
{
/*
typedef union _SET_GET_EFFECT_STRUCTURE
{
 struct
 {
  BYTE report_id;
  BYTE effect_type;
  BYTE byte_count; // valid only for custom force data effect.
       // custom force effects are not supported by this device.
 }SET_REPORT_REQUEST;
 struct
 {
  BYTE report_id; // 2
  BYTE effect_block_index; // index dell'effetto
  BYTE block_load_status; // 1 ok, 2 -out of memory, 3 JC was here, or maybe not ? case: undefined.
  int ram_pool_available;
 }PID_BLOCK_LOAD_REPORT;

BYTE val[8];
}SET_GET_EFFECT_STRUCTURE;
*/
 
 USBEP0SendRAMPtr((BYTE*)&set_get_effect_structure.PID_BLOCK_LOAD_REPORT, 5, USB_EP0_RAM); 
}

 
upon transmitting the PID_BLOCK_LOAD_REPORT, we tell the host application if we have managed to create the effect ( is the effect supported ? ) or failed to do so ( the effect is not supported, we don't have memory, etc )
 
Now the host will send another set report, containing the effect data,
the same handler used, and this time 
the code of reference is :
file of reference : usb_function_hid.c
 
void USER_SET_REPORT_HANDLER(void)
{
 // ...
 case 0x01: // report ID: 1, SET NEW EFFECT,APPLICATION DATA, etc , and now ?
     // NOW I CHECK THE REPORT TYPE censored ! INPUT? OUTPUT? FEATURE ? 

   if(SetupPkt.W_Value.byte.HB==0x02) // FEATURE, the host wants to send me the data, to set the effects. aka effect start new ...
   {
   // READ THE DATA, FEEL THE DATA, SAVE THE DATA ! and set the data effect, read it, and set it
   USBEP0Receive((BYTE*)&hid_report_out[0], SetupPkt.wLength, USBSetDataEffect);
   } 
//...
}

 
the USBSetDataEffect function is defined in
File of reference : main.c
 
/*
basically, look into the received data, and save things...in the effect block indexes, etc etc
*/
void USBSetDataEffect(void)
{
 // for now i should have tricked the host to belive the report is accepted, and to send me the data
// the data indeed is sent, and saved, but i lied, and i'm doing nothing with that data, censored the host
// and censored PID book from usb.org. , i had to read the entire direct input, line by line,
// to make sense of that book. first is apples, then oranges, but in the end 
// apples and oranges are the same thing. sometimes they call it (effect) apples
// but they mean (brown)apples, and effect has nothing to do with it. and it's not brown, it's red, but 
// is encoded as RGB(00FF00), then they ask for the alpha channel. but you are stucked with something green
// it's just green, no mass, no shape, no nothing, worse than a void pointer.
// basically you have a green void pointer to a brown index of apples.
// but they let you guess that ! tricky :D 
// no, seriously, I'm out.!

// here we should read the incoming data on hid_report_out, and save the values in the relative //effect that is created, i am not doing it for now, because this is just a test version of the firmware
}

 
 
The next step now is to :listen for set reports, where the host sends a: Effect Operations report.
inside the effect operation report, the host will start or stop a created effect.
 
For now, altough the firmware does nothing with the effects ( it doesn't even save the effect data ) it however reponds to the effect creation correctly and the host application thinks the effects are supported by the device, and it will communicate with the device. 
 
you now have the basic firmware so that force feedback applications detect the device as a ffb device that successfully creates effects.
 
next steps:
1)writing the report descriptor.
2)finishing USBSetDataEffect(void) so that it will save the effect informations inside the effect structures.
3) responding to the Effect Operations report, so that we can start and stop an effect
4) use pwm module to drive an h-bridge for 1 motor ( so we can finally play those games using our own ffb device )
5) more to come, but it's late now and i'm tired, i'll complete this in the following days.
6) once the forum lets me put download links (?), i will provide the full firmware and all the neccessary materials ( programs, descriptor tools, books, etc ) for others to further improve the firmware, and save those months of frustration to any student brave enough to undertake  force-feedback implementation over usb on PIC chips.
post edited by Coman - 2016/11/09 19:20:00
#12
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/10 12:03:56 (permalink)
+2 (2)
PART 6 : SECTION 6
 
REPORT DESCRIPTOR FOR FORCE FEEDBACK DEVICES
 
FILE OF REFERENCE : usb_descriptors.c
 
ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={{

 0x05,0x01, // Usage Page Generic Desktop
 0x09,0x04, // Usage Joystick
 0xA1,0x01, // Collection Application
    0x85,0x01, // Report ID 1
0x05,0x09, // [0x03]USAGE PAGE (BUTTON)
0x19,0x01, // [0x04]USAGE_MINIMUM(BUTTON 1)
0x29,0x10, // [0x05]USAGE_MAXIMUM(BUTTON 16)
0x15,0x00, // [0x06]LOGICAL_MINIMUM
0x25,0x01, // [0x07]LOGICAL_MAXIMUM
0x35,0x00, // [0x08]PHYSICAL_MINIMUM
0x45,0x01, // [0x09]PHYSICAL_MAXIMUM
0x75,0x01, // [0x0A]REPORT_SIZE
0x95,0x10, // [0x0B]REPORT_COUNT
0x81,0x02, // [0x0C]INPUT
0x05,0x01, // [0x0D]USAGE PAGE (GENERIC DESKTOP CONTROLS)
0x09,0x39, // [0x0E]USAGE(HAT_SWITCH)
0x15,0x00, // [0x0F]LOGICAL_MINIMUM
0x25,0x07, // [0x10]LOGICAL_MAXIMUM
0x35,0x00, // [0x11]PHYSICAL_MINIMUM
0x46,0x3B,0x01, // [0x12]PHYSICAL_MAXIMUM
0x65,0x14, // [0x13]UNIT
0x75,0x04, // [0x14]REPORT_SIZE
0x95,0x01, // [0x15]REPORT_COUNT
0x81,0x42, // [0x16]INPUT
0x65,0x00, // [0x17]UNIT
0x95,0x01, // [0x18]REPORT_COUNT
0x81,0x01, // [0x19]INPUT
0x09,0x30, // [0x1A]USAGE(X)
0x09,0x31, // [0x1B]USAGE(Y)
0x09,0x32, // [0x1C]USAGE(Z)
0x09,0x33, // [0x1D]USAGE(Rx)
0x15,0x00, // [0x1E]LOGICAL_MINIMUM
0x26,0x00,0x04, // [0x1F]LOGICAL_MAXIMUM
0x35,0x00, // [0x20]PHYSICAL_MINIMUM
0x46,0x00,0x04, // [0x21]PHYSICAL_MAXIMUM
0x75,0x10, // [0x22]REPORT_SIZE
0x95,0x04, // [0x23]REPORT_COUNT
0x81,0x02, // [0x24]INPUT
    

 
 /*
  Input
  Collection Datalink (sub-collection)
  Physical Interface (Usage: PID State report)
  ID: 2
  state report: 5X1bit
  Padding: 3bit
  PID Device Control: 1bit
  Effect Block Index: 7bit
 */



    0x05,0x0F, // Usage Page Physical Interface
    0x09,0x92, // Usage PID State report
    0xA1,0x02, // Collection Datalink (logical)

       0x85,0x02, // Report ID 2
       0x09,0x9F, // Usage Device is Pause
       0x09,0xA0, // Usage Actuators Enabled
       0x09,0xA4, // Usage Safety Switch
       0x09,0xA5, // Usage Actuator Override Switch
       0x09,0xA6, // Usage Actuator Power
       0x15,0x00, // Logical Minimum 0
       0x25,0x01, // Logical Maximum 1
       0x35,0x00, // Physical Minimum 0
       0x45,0x01, // Physical Maximum 1
       0x75,0x01, // Report Size 1
       0x95,0x05, // Report Count 5
       0x81,0x02, // Input (Variable)
       0x95,0x03, // Report Count 3
       0x81,0x03, // Input (Constant, Variable)


       0x09,0x94, // Usage Effect Playing
       0x15,0x00, // Logical Minimum 0
       0x25,0x01, // Logical Maximum 1
       0x35,0x00, // Physical Minimum 0
       0x45,0x01, // Physical Maximum 1
       0x75,0x01, // Report Size 1
       0x95,0x01, // Report Count 1
       0x81,0x02, // Input (Variable)
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x07, // Report Size 7
       0x95,0x01, // Report Count 1
       0x81,0x02, // Input (Variable)
    0xC0 , // End Collection
 
 /*
  Output
  Collection Datalink:
  Usage Set Effect Report
  
  ID:1
  Effect Block Index: 8bit
  
  subcollection Effect Type
  12 effect types, 8bit each

 */
    0x09,0x21, // Usage Set Effect Report
    0xA1,0x02, // Collection Datalink (Logical)
       0x85,0x01, // Report ID 1
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)

       0x09,0x25, // Usage Effect Type
       0xA1,0x02, // Collection Datalink
          0x09,0x26, // Usage ET Constant Force
          0x09,0x27, // Usage ET Ramp
          0x09,0x30, // Usage ET Square
          0x09,0x31, // Usage ET Sine
          0x09,0x32, // Usage ET Triangle
          0x09,0x33, // Usage ET Sawtooth Up
          0x09,0x34, // Usage ET Sawtooth Down
          0x09,0x40, // Usage ET Spring
          0x09,0x41, // Usage ET Damper
          0x09,0x42, // Usage ET Inertia
          0x09,0x43, // Usage ET Friction
          0x09,0x28, // Usage ET Custom Force Data
          0x25,0x0C, // Logical Maximum Ch (12d)
          0x15,0x01, // Logical Minimum 1
          0x35,0x01, // Physical Minimum 1
          0x45,0x0C, // Physical Maximum Ch (12d)
          0x75,0x08, // Report Size 8
          0x95,0x01, // Report Count 1
          0x91,0x00, // Output

       0xC0 , // End Collection

       0x09,0x50, // Usage Duration
       0x09,0x54, // Usage Trigger Repeat Interval
       0x09,0x51, // Usage Sample Period
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x7F, // Logical Maximum 7FFFh (32767d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x7F, // Physical Maximum 7FFFh (32767d)
       0x66,0x03,0x10, // Unit 1003h (4099d)
       0x55,0xFD, // Unit Exponent FDh (253d)
       0x75,0x10, // Report Size 10h (16d)
       0x95,0x03, // Report Count 3
       0x91,0x02, // Output (Variable)

       0x55,0x00, // Unit Exponent 0
       0x66,0x00,0x00, // Unit 0
       0x09,0x52, // Usage Gain
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)

       0x09,0x53, // Usage Trigger Button
       0x15,0x01, // Logical Minimum 1
       0x25,0x08, // Logical Maximum 8
       0x35,0x01, // Physical Minimum 1
       0x45,0x08, // Physical Maximum 8
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)

       0x09,0x55, // Usage Axes Enable
       0xA1,0x02, // Collection Datalink
          0x05,0x01, // Usage Page Generic Desktop
          0x09,0x30, // Usage X
          0x09,0x31, // Usage Y
          0x15,0x00, // Logical Minimum 0
          0x25,0x01, // Logical Maximum 1
          0x75,0x01, // Report Size 1
          0x95,0x02, // Report Count 2
          0x91,0x02, // Output (Variable)

       0xC0 , // End Collection

       0x05,0x0F, // Usage Page Physical Interface
       0x09,0x56, // Usage Direction Enable
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)

       0x95,0x05, // Report Count 5
       0x91,0x03, // Output (Constant, Variable)

       0x09,0x57, // Usage Direction
       0xA1,0x02, // Collection Datalink
          0x0B,0x01,0x00,0x0A,0x00, // Usage Ordinals: Instance 1
          0x0B,0x02,0x00,0x0A,0x00, // Usage Ordinals: Instance 2
          0x66,0x14,0x00, // Unit 14h (20d)
          0x55,0xFE, // Unit Exponent FEh (254d)
          0x15,0x00, // Logical Minimum 0
          0x26,0xFF,0x00, // Logical Maximum FFh (255d)
          0x35,0x00, // Physical Minimum 0
          0x47,0xA0,0x8C,0x00,0x00, // Physical Maximum 8CA0h (36000d)
          0x66,0x00,0x00, // Unit 0
          0x75,0x08, // Report Size 8
          0x95,0x02, // Report Count 2
          0x91,0x02, // Output (Variable)
          0x55,0x00, // Unit Exponent 0
          0x66,0x00,0x00, // Unit 0
       0xC0 , // End Collection

       0x05,0x0F, // Usage Page Physical Interface
       0x09,0xA7, // Usage Undefined
       0x66,0x03,0x10, // Unit 1003h (4099d)
       0x55,0xFD, // Unit Exponent FDh (253d)
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x7F, // Logical Maximum 7FFFh (32767d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x7F, // Physical Maximum 7FFFh (32767d)
       0x75,0x10, // Report Size 10h (16d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x66,0x00,0x00, // Unit 0
       0x55,0x00, // Unit Exponent 0
    0xC0 , // End Collection

    0x05,0x0F, // Usage Page Physical Interface
    0x09,0x5A, // Usage Set Envelope Report
    0xA1,0x02, // Collection Datalink
       0x85,0x02, // Report ID 2
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x5B, // Usage Attack Level
       0x09,0x5D, // Usage Fade Level
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x95,0x02, // Report Count 2
       0x91,0x02, // Output (Variable)
       0x09,0x5C, // Usage Attack Time
       0x09,0x5E, // Usage Fade Time
       0x66,0x03,0x10, // Unit 1003h (4099d)
       0x55,0xFD, // Unit Exponent FDh (253d)
       0x26,0xFF,0x7F, // Logical Maximum 7FFFh (32767d)
       0x46,0xFF,0x7F, // Physical Maximum 7FFFh (32767d)
       0x75,0x10, // Report Size 10h (16d)
       0x91,0x02, // Output (Variable)
       0x45,0x00, // Physical Maximum 0
       0x66,0x00,0x00, // Unit 0
       0x55,0x00, // Unit Exponent 0
    0xC0 , // End Collection

    0x09,0x5F, // Usage Set Condition Report
    0xA1,0x02, // Collection Datalink
       0x85,0x03, // Report ID 3
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x23, // Usage Parameter Block Offset
       0x15,0x00, // Logical Minimum 0
       0x25,0x01, // Logical Maximum 1
       0x35,0x00, // Physical Minimum 0
       0x45,0x01, // Physical Maximum 1
       0x75,0x04, // Report Size 4
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x58, // Usage Type Specific Block Off...
       0xA1,0x02, // Collection Datalink

          0x0B,0x01,0x00,0x0A,0x00, // Usage Ordinals: Instance 1
          0x0B,0x02,0x00,0x0A,0x00, // Usage Ordinals: Instance 2
          0x75,0x02, // Report Size 2
          0x95,0x02, // Report Count 2
          0x91,0x02, // Output (Variable)
       0xC0 , // End Collection

       0x15,0x80, // Logical Minimum 80h (-128d)
       0x25,0x7F, // Logical Maximum 7Fh (127d)
       0x36,0xF0,0xD8, // Physical Minimum D8F0h (-10000d)
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x09,0x60, // Usage CP Offset
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x36,0xF0,0xD8, // Physical Minimum D8F0h (-10000d)
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x09,0x61, // Usage Positive Coefficient
       0x09,0x62, // Usage Negative Coefficient
       0x95,0x02, // Report Count 2
       0x91,0x02, // Output (Variable)
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x09,0x63, // Usage Positive Saturation
       0x09,0x64, // Usage Negative Saturation
       0x75,0x08, // Report Size 8
       0x95,0x02, // Report Count 2
       0x91,0x02, // Output (Variable)
       0x09,0x65, // Usage Dead Band
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x09,0x6E, // Usage Set Periodic Report
    0xA1,0x02, // Collection Datalink
       0x85,0x04, // Report ID 4
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x70, // Usage Magnitude
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x6F, // Usage Offset
       0x15,0x80, // Logical Minimum 80h (-128d)
       0x25,0x7F, // Logical Maximum 7Fh (127d)
       0x36,0xF0,0xD8, // Physical Minimum D8F0h (-10000d)
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x71, // Usage Phase
       0x66,0x14,0x00, // Unit 14h (20d)
       0x55,0xFE, // Unit Exponent FEh (254d)
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x47,0xA0,0x8C,0x00,0x00, // Physical Maximum 8CA0h (36000d)
       0x91,0x02, // Output (Variable)
       0x09,0x72, // Usage Period
       0x26,0xFF,0x7F, // Logical Maximum 7FFFh (32767d)
       0x46,0xFF,0x7F, // Physical Maximum 7FFFh (32767d)
       0x66,0x03,0x10, // Unit 1003h (4099d)
       0x55,0xFD, // Unit Exponent FDh (253d)
       0x75,0x10, // Report Size 10h (16d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x66,0x00,0x00, // Unit 0
       0x55,0x00, // Unit Exponent 0
    0xC0 , // End Collection
    0x09,0x73, // Usage Set Constant Force Rep...
    0xA1,0x02, // Collection Datalink
       0x85,0x05, // Report ID 5
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x70, // Usage Magnitude
       0x16,0x01,0xFF, // Logical Minimum FF01h (-255d)
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x36,0xF0,0xD8, // Physical Minimum D8F0h (-10000d)
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x75,0x10, // Report Size 10h (16d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x09,0x74, // Usage Set Ramp Force Report
    0xA1,0x02, // Collection Datalink
       0x85,0x06, // Report ID 6
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x75, // Usage Ramp Start
       0x09,0x76, // Usage Ramp End
       0x15,0x80, // Logical Minimum 80h (-128d)
       0x25,0x7F, // Logical Maximum 7Fh (127d)
       0x36,0xF0,0xD8, // Physical Minimum D8F0h (-10000d)
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x75,0x08, // Report Size 8
       0x95,0x02, // Report Count 2
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x09,0x68, // Usage Custom Force Data Rep...
    0xA1,0x02, // Collection Datalink
       0x85,0x07, // Report ID 7
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x6C, // Usage Custom Force Data Offset
       0x15,0x00, // Logical Minimum 0
       0x26,0x10,0x27, // Logical Maximum 2710h (10000d)
       0x35,0x00, // Physical Minimum 0
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x75,0x10, // Report Size 10h (16d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x69, // Usage Custom Force Data
       0x15,0x81, // Logical Minimum 81h (-127d)
       0x25,0x7F, // Logical Maximum 7Fh (127d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x00, // Physical Maximum FFh (255d)
       0x75,0x08, // Report Size 8
       0x95,0x0C, // Report Count Ch (12d)
       0x92,0x02,0x01, // Output (Variable, Buffered)
    0xC0 , // End Collection
    0x09,0x66, // Usage Download Force Sample
    0xA1,0x02, // Collection Datalink
       0x85,0x08, // Report ID 8
       0x05,0x01, // Usage Page Generic Desktop
       0x09,0x30, // Usage X
       0x09,0x31, // Usage Y
       0x15,0x81, // Logical Minimum 81h (-127d)
       0x25,0x7F, // Logical Maximum 7Fh (127d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x00, // Physical Maximum FFh (255d)
       0x75,0x08, // Report Size 8
       0x95,0x02, // Report Count 2
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x05,0x0F, // Usage Page Physical Interface
    0x09,0x77, // Usage Effect Operation Report
    0xA1,0x02, // Collection Datalink
       0x85,0x0A, // Report ID Ah (10d)
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x78, // Usage Effect Operation
       0xA1,0x02, // Collection Datalink
          0x09,0x79, // Usage Op Effect Start
          0x09,0x7A, // Usage Op Effect Start Solo
          0x09,0x7B, // Usage Op Effect Stop
          0x15,0x01, // Logical Minimum 1
          0x25,0x03, // Logical Maximum 3
          0x75,0x08, // Report Size 8
          0x95,0x01, // Report Count 1
          0x91,0x00, // Output
       0xC0 , // End Collection
       0x09,0x7C, // Usage Loop Count
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x00, // Physical Maximum FFh (255d)
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x09,0x90, // Usage PID Block Free Report
    0xA1,0x02, // Collection Datalink
       0x85,0x0B, // Report ID Bh (11d)
       0x09,0x22, // Usage Effect Block Index
       0x25,0x28, // Logical Maximum 28h (40d)
       0x15,0x01, // Logical Minimum 1
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x09,0x96, // Usage PID Device Control
    0xA1,0x02, // Collection Datalink
       0x85,0x0C, // Report ID Ch (12d)
       0x09,0x97, // Usage DC Enable Actuators
       0x09,0x98, // Usage DC Disable Actuators
       0x09,0x99, // Usage DC Stop All Effects
       0x09,0x9A, // Usage DC Device Reset
       0x09,0x9B, // Usage DC Device Pause
       0x09,0x9C, // Usage DC Device Continue
       0x15,0x01, // Logical Minimum 1
       0x25,0x06, // Logical Maximum 6
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x00, // Output
    0xC0 , // End Collection
    0x09,0x7D, // Usage Device Gain Report
    0xA1,0x02, // Collection Datalink
       0x85,0x0D, // Report ID Dh (13d)
       0x09,0x7E, // Usage Device Gain
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0x10,0x27, // Physical Maximum 2710h (10000d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
    0xC0 , // End Collection
    0x09,0x6B, // Usage Set Custom Force Report
    0xA1,0x02, // Collection Datalink
       0x85,0x0E, // Report ID Eh (14d)
       0x09,0x22, // Usage Effect Block Index
       0x15,0x01, // Logical Minimum 1
       0x25,0x28, // Logical Maximum 28h (40d)
       0x35,0x01, // Physical Minimum 1
       0x45,0x28, // Physical Maximum 28h (40d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x6D, // Usage Sample Count
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x00, // Logical Maximum FFh (255d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x00, // Physical Maximum FFh (255d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x09,0x51, // Usage Sample Period
       0x66,0x03,0x10, // Unit 1003h (4099d)
       0x55,0xFD, // Unit Exponent FDh (253d)
       0x15,0x00, // Logical Minimum 0
       0x26,0xFF,0x7F, // Logical Maximum 7FFFh (32767d)
       0x35,0x00, // Physical Minimum 0
       0x46,0xFF,0x7F, // Physical Maximum 7FFFh (32767d)
       0x75,0x10, // Report Size 10h (16d)
       0x95,0x01, // Report Count 1
       0x91,0x02, // Output (Variable)
       0x55,0x00, // Unit Exponent 0
       0x66,0x00,0x00, // Unit 0
    0xC0 , // End Collection
    0x09,0xAB, // Usage Create New Effect Report
    0xA1,0x02, // Collection Datalink
       0x85,0x01, // Report ID 1
       0x09,0x25, // Usage Effect Type
       0xA1,0x02, // Collection Datalink
       0x09,0x26, // Usage ET Constant Force
       0x09,0x27, // Usage ET Ramp
       0x09,0x30, // Usage ET Square
       0x09,0x31, // Usage ET Sine
       0x09,0x32, // Usage ET Triangle
       0x09,0x33, // Usage ET Sawtooth Up
       0x09,0x34, // Usage ET Sawtooth Down
       0x09,0x40, // Usage ET Spring
       0x09,0x41, // Usage ET Damper
       0x09,0x42, // Usage ET Inertia
       0x09,0x43, // Usage ET Friction
       0x09,0x28, // Usage ET Custom Force Data
       0x25,0x0C, // Logical Maximum Ch (12d)
       0x15,0x01, // Logical Minimum 1
       0x35,0x01, // Physical Minimum 1
       0x45,0x0C, // Physical Maximum Ch (12d)
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0xB1,0x00, // Feature
    0xC0 , // End Collection
    0x05,0x01, // Usage Page Generic Desktop
    0x09,0x3B, // Usage Reserved
    0x15,0x00, // Logical Minimum 0
    0x26,0xFF,0x01, // Logical Maximum 1FFh (511d)
    0x35,0x00, // Physical Minimum 0
    0x46,0xFF,0x01, // Physical Maximum 1FFh (511d)
    0x75,0x0A, // Report Size Ah (10d)
    0x95,0x01, // Report Count 1
    0xB1,0x02, // Feature (Variable)
    0x75,0x06, // Report Size 6
    0xB1,0x01, // Feature (Constant)
 0xC0 , // End Collection
 0x05,0x0F, // Usage Page Physical Interface
 0x09,0x89, // Usage Block Load Status
 0xA1,0x02, // Collection Datalink
    0x85,0x02, // Report ID 2
    0x09,0x22, // Usage Effect Block Index
    0x25,0x28, // Logical Maximum 28h (40d)
    0x15,0x01, // Logical Minimum 1
    0x35,0x01, // Physical Minimum 1
    0x45,0x28, // Physical Maximum 28h (40d)
    0x75,0x08, // Report Size 8
    0x95,0x01, // Report Count 1
    0xB1,0x02, // Feature (Variable)
    0x09,0x8B, // Usage Block Load Status
    0xA1,0x02, // Collection Datalink
       0x09,0x8C, // Usage Block Load Success
       0x09,0x8D, // Usage Block Load Full
       0x09,0x8E, // Usage Block Load Error
       0x25,0x03, // Logical Maximum 3
       0x15,0x01, // Logical Minimum 1
       0x35,0x01, // Physical Minimum 1
       0x45,0x03, // Physical Maximum 3
       0x75,0x08, // Report Size 8
       0x95,0x01, // Report Count 1
       0xB1,0x00, // Feature
    0xC0 , // End Collection
    0x09,0xAC, // Usage Undefined
    0x15,0x00, // Logical Minimum 0
    0x27,0xFF,0xFF,0x00,0x00, // Logical Maximum FFFFh (65535d)
    0x35,0x00, // Physical Minimum 0
    0x47,0xFF,0xFF,0x00,0x00, // Physical Maximum FFFFh (65535d)
    0x75,0x10, // Report Size 10h (16d)
    0x95,0x01, // Report Count 1
    0xB1,0x00, // Feature
 0xC0 , // End Collection
 0x09,0x7F, // Usage PID Pool Report
 0xA1,0x02, // Collection Datalink
    0x85,0x03, // Report ID 3
    0x09,0x80, // Usage RAM Pool size
    0x75,0x10, // Report Size 10h (16d)
    0x95,0x01, // Report Count 1
    0x15,0x00, // Logical Minimum 0
    0x35,0x00, // Physical Minimum 0
    0x27,0xFF,0xFF,0x00,0x00, // Logical Maximum FFFFh (65535d)
    0x47,0xFF,0xFF,0x00,0x00, // Physical Maximum FFFFh (65535d)
    0xB1,0x02, // Feature (Variable)
    0x09,0x83, // Usage Simultaneous Effects Max
    0x26,0xFF,0x00, // Logical Maximum FFh (255d)
    0x46,0xFF,0x00, // Physical Maximum FFh (255d)
    0x75,0x08, // Report Size 8
    0x95,0x01, // Report Count 1
    0xB1,0x02, // Feature (Variable)
    0x09,0xA9, // Usage Device Managed Pool
    0x09,0xAA, // Usage Shared Parameter Blocks
    0x75,0x01, // Report Size 1
    0x95,0x02, // Report Count 2
    0x15,0x00, // Logical Minimum 0
    0x25,0x01, // Logical Maximum 1
    0x35,0x00, // Physical Minimum 0
    0x45,0x01, // Physical Maximum 1
    0xB1,0x02, // Feature (Variable)
    0x75,0x06, // Report Size 6
    0x95,0x01, // Report Count 1
    0xB1,0x03, // Feature (Constant, Variable)
    0xC0, // End Collection
 0xC0 // End Collection
 
}
};

 
This is the report descriptor for a force feedback device.
I have copied this descriptor from another discussion about force feedback devices on this forums.
 
To make it work, you have to modify the descriptor size inside :
FILE OF REFERENCE : usb_config.h
 
/* HID */
#define HID_INTF_ID 0x00
#define HID_EP 1
#define HID_INT_OUT_EP_SIZE 64
#define HID_INT_IN_EP_SIZE 64
#define HID_NUM_OF_DSC 1
#define HID_RPT01_SIZE 0x4FA

 
 
 
#13
Coman
Starting Member
  • Total Posts : 14
  • Reward points : 0
  • Joined: 2016/11/06 14:56:29
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2016/11/10 16:46:03 (permalink)
+1 (1)
PART 6 : SECTION 7
 
The report descriptor.
 
Input report.
  • Contains data sent from device to host
  • Data describes the axes and button positions.
  • The data is used to control virtual objects. ( racing simulators, flight simulators, surgical robots )
 Example: 
--------------------------------
Byte [0]  : Report ID [1]
Byte [1]  : Throttle
Byte [2]  : X-axis
Byte [3]  : Y-Axis
Byte [4] - BIT[7]   : Button 4
Byte [4] - BIT[6]   : Button 3
Byte [4] - BIT[5]   : Button 2
Byte [4] - BIT[4]   : Button 1
Byte [4] - BIT[3-0]   : Hat Switch
 --------------------------------
Report ID is used to differentiate multiple reports so the host application knows how many data bytes
to read. Multiple reports have different length.
In this example we asaign the ID 1 to the input report, so each time the host reads report ID=1, it will call the right functions to deal with the input data from our joystick.
Report 1 [ ID = 1 ], contains
  • the axis position read trough the adc module.
  • the button states read from the input ports on the chip where the switches are attached
All those values are variables that change according to the user inputs. ( moving the joystick )
We need to define them, thus we create a struct and each field will be mapped to the Report [ ID=1 ]
bytes.
The map is bit alligned, this means that bit 0 to 7 represent the throttle axis position
bit 8 to 15 is the X axis position.
The values go from 0 to 255, so we have 256 positions.
On the PIC18F4550 we have a 10 bit analog to digital converter ( ADC ) so we can measure the input
positions with 10 bit resolution instead of 8 bit.
 
0 to 9 would represent the 10 bit axis position, 10 to 17 the 8 bit axis position and so on.
 
FILE OF REFERENCE:
main.c
 
This is the data defined inside our firmware, for managing the joystick axes and buttons

typedef union _INTPUT_CONTROLS_TYPEDEF
{
    struct
    {
  struct
        {
            BYTE RID;
           
        } REPORT_ID;
 
        struct
        {
            BYTE square:1;
            BYTE x:1;
            BYTE o:1;
            BYTE triangle:1;
            BYTE L1:1;
            BYTE R1:1;
            BYTE L2:1;
            BYTE R2:1;//
            BYTE select:1;
            BYTE start:1;
            BYTE left_stick:1;
            BYTE right_stick:1;
            BYTE home:1;
            BYTE :3; //filler
        } buttons;
        struct
        {
            BYTE hat_switch:4;
            BYTE :4;//filler
        } hat_switch;
        struct
        {
             int X;
             int Y;
             int Z;
             int Rz;
        } analog_stick;
    } members;
    BYTE val[12];
} INPUT_CONTROLS;

 
 We have:
1 report id (1 byte)
13 buttons ( 1 bit each ) and 3 bits for filling, so we complete the second byte. The first 13 bits will reppresent the 13 buttons and the last 3 bits will remain unused.
1 hat switch ( 4 bits ) and 4 bits for filling the byte.
4 axes, 16 bit long ( 2 bytes ),but we only use 10 bits, because the adc module has 10 bits resolution.
 
Total length of the data structure : 12 bytes, mapped to the report descriptor.
 
This is the report descriptor:
 
Byte [0]  : Report ID [1]
Byte [1]  : Buttons ( 0 to 7)
Byte [2]  : Buttons (8 to 12 )  + 3 bits unused
Byte [3]  : Hat switch + 4 bits unused
Byte [4]  : X axis 
Byte [5]  : X axis 
Byte [6]  : Yaxis 
Byte [7]  : Y axis 
Byte [8]  : Z axis 
Byte [9]  :Z axis 
Byte [10]  : Rz axis 
Byte [11]  : Rz axis 
 
Input Report descriptor (we call it input, because we use it as the joystick input to the host application) length: 12 bytes.
 
Now we write the values inside the device report descriptor for the Input Report Descriptor [ ID=1 ].
 0x85,0x01, // Report ID 1
0x05,0x09, // [0x03]USAGE PAGE (BUTTON)
0x19,0x01, // [0x04]USAGE_MINIMUM(BUTTON 1)
0x29,0x10, // [0x05]USAGE_MAXIMUM(BUTTON 16)
0x15,0x00, // [0x06]LOGICAL_MINIMUM
0x25,0x01, // [0x07]LOGICAL_MAXIMUM
0x35,0x00, // [0x08]PHYSICAL_MINIMUM
0x45,0x01, // [0x09]PHYSICAL_MAXIMUM
0x75,0x01, // [0x0A]REPORT_SIZE
0x95,0x10, // [0x0B]REPORT_COUNT
0x81,0x02, // [0x0C]INPUT
0x05,0x01, // [0x0D]USAGE PAGE (GENERIC DESKTOP CONTROLS)
0x09,0x39, // [0x0E]USAGE(HAT_SWITCH)
0x15,0x00, // [0x0F]LOGICAL_MINIMUM
0x25,0x07, // [0x10]LOGICAL_MAXIMUM
0x35,0x00, // [0x11]PHYSICAL_MINIMUM
0x46,0x3B,0x01, // [0x12]PHYSICAL_MAXIMUM
0x65,0x14, // [0x13]UNIT
0x75,0x04, // [0x14]REPORT_SIZE
0x95,0x01, // [0x15]REPORT_COUNT
0x81,0x42, // [0x16]INPUT
0x65,0x00, // [0x17]UNIT
0x95,0x01, // [0x18]REPORT_COUNT
0x81,0x01, // [0x19]INPUT
0x09,0x30, // [0x1A]USAGE(X)
0x09,0x31, // [0x1B]USAGE(Y)
0x09,0x32, // [0x1C]USAGE(Z)
0x09,0x33, // [0x1D]USAGE(Rx)
0x15,0x00, // [0x1E]LOGICAL_MINIMUM
0x26,0x00,0x04, // [0x1F]LOGICAL_MAXIMUM
0x35,0x00, // [0x20]PHYSICAL_MINIMUM
0x46,0x00,0x04, // [0x21]PHYSICAL_MAXIMUM
0x75,0x10, // [0x22]REPORT_SIZE
0x95,0x04, // [0x23]REPORT_COUNT
0x81,0x02, // [0x24]INPUT

 
 
 
 
 
 
 
post edited by Coman - 2016/11/10 17:08:50
#14
ebolzmagy
New Member
  • Total Posts : 1
  • Reward points : 0
  • Joined: 2017/01/23 03:34:33
  • Location: indonesia
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2017/01/24 04:50:24 (permalink)
0
HI,sorry first post..i registration here because your thread...
how to calculate forcefeedback data and translate into pwm?
sorry for my english..thanks
#15
vtrx
Starting Member
  • Total Posts : 37
  • Reward points : 0
  • Joined: 2013/01/04 17:42:57
  • Location: 0
  • Status: offline
Re: Force Feedback Tutorial: PIC18F4550 2019/05/17 12:28:14 (permalink)
0
Topic still active?
#16
katela
Super Member
  • Total Posts : 1328
  • Reward points : 0
  • Joined: 2013/06/11 05:25:18
  • Location: South Africa
  • Status: online
Re: Force Feedback Tutorial: PIC18F4550 2019/05/19 19:29:10 (permalink)
+2 (2)
vtrx
Topic still active?


It's better you start your own thread.
 

Free online Microcontroller Tutorials and Projects for Hobbyists and students. From beginners to advanced. Website: www.studentcompanion.co.za
YouTube Tutorials: https://www.youtube.com/StudentCompanionSA
#17
Jump to:
© 2019 APG vNext Commercial Version 4.5