Emulating The Traktor Kontrol F1 – Part 3

In Part 2 of this series I described how to setup the hardware and software environment. Now we get to the juicy bits – the actual firmware that will make the Arduino Uno appear to your computer (and to Traktor) to be a Native Instruments Traktor Kontrol F1.

To jump right in download my code at https://github.com/tunecrew/emulate-the-f1.

Our starting point was the excellent LUFA Library (Lightweight USB Framework for AVRs) by Dean Camera. The LUFA Library includes a number of examples (including makefiles) designed to programme Atmel AVRs to emulate a wide variety of of USB devices. In our case, the F1 is a custom HID device, so I chose the GenericHID Class Driver example as the template- in the lufa distribution it can be found at lufa/Demos/Device/ClassDriver/GenericHID.

I started by editing the makefile to reflect the target board and the location of the LUFA Library:

MCU = atmega16u2
ARCH = AVR8
BOARD = UNO
F_CPU = 16000000
F_USB = $(F_CPU)
OPTIMIZATION = s
TARGET = SerialToF1
SRC = $(TARGET).c Descriptors.c $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS)
LUFA_PATH = ./LUFA
CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -IConfig/
LD_FLAGS =

Next I edited Config/AppConfig.h to reflect the side of the F1 HID report:

#define F1_REPORT_SIZE 22

In Config/LUFAConfig.h I also changed the endpoint size to be the smallest size of the available options – 8, 16, 32, 64 – that is greater than the report size.

#define FIXED_CONTROL_ENDPOINT_SIZE 32

The chances in descriptors.c and descriptors.h are much more extensive, but essentially this the part that makes the Arduino appear to be an F1. Essentially, you want the Arduino descriptors to match the USB dump found here in “Native Instruments F1 Controller Descriptor Dump.html”. If you compare the versions of these files with those in the LUFA example, the changes and additions are fairly self-explanatory. Some things to note:

  • A real serial number was not necessary (I used “00000000”)
  • The DFU endpoints (for upgrading the firmware in a real F1) may not be necessary either, but I included them when prototyping. In the future I may remove them.

The final set of files, SerialToF1.h and SerialToF1.c, contain the code that converts the data from the Arduino sketch into an F1 HID report. The key part from SerialToF1.c is here:

int main(void)
{
  SetupHardware();
  GlobalInterruptEnable();
  Serial_Init(38400, false);

  int ReportDataFill = 0;

  for (;;)
  {
    // receive 22 bytes from the serial port (this will be from the sketch)
    if (ReportDataFill < F1_REPORT_SIZE)
    {
      int16_t DataByte = Serial_ReceiveByte();
      if (DataByte > -1)
      {
        ReportData[ReportDataFill] = DataByte;
        ReportDataFill++;
      }
    }
    // once ReportData is full, send it out the USB port to the computer
    else
    {
      ReportDataFill = 0;

      if (USB_DeviceState != DEVICE_STATE_Configured)
      {
      }
      else
      {
        Endpoint_SelectEndpoint(F1_OUT_EPADDR);

        if (Endpoint_IsINReady())
        {
          Endpoint_Write_Stream_LE(&ReportData, sizeof(ReportData), NULL);
          Endpoint_ClearIN();
        }
      }
    }

    USB_USBTask();
  }
}

This loop uses the LUFA Library serial library at lufa/LUFA/Drivers/Peripheral/Serial.h to receive data from the Atmega16U2’s serial port (which is connected to the serial output of the ATmega328 on the Uno. When 22 bytes are received (the size of the F1 HID Report), the loop then writes it to the USB port as an HID Report. The key thing to understand here is that those 22 bytes will be sent by an Arduino sketch!

In the next post in this series, I’ll describe how to write Arduino sketches to translate between MIDI and F1 HID Report data.

Leave a Reply