Getting started¶
Read configuration EEPROM:
fx2tool -S firmware/bootloader/bootloader.ihex read_eeprom 0 7
Load the Blinky example (a LED should be attached to PA0):
make -C examples/blinky load
Blinking a LED¶
Read the code for the blinky example if you’re looking for something minimal:
#include <fx2regs.h>
#include <fx2ints.h>
// Register an interrupt handler for TIMER0 overflow
void isr_TF0() __interrupt(_INT_TF0) {
static int i;
if(i++ % 64 == 0)
PA0 = !PA0;
}
int main() {
// Configure pins
PA0 = 1; // set PA0 to high
OEA = 0b1; // set PA0 as output
// Configure TIMER0
TCON = _M0_0; // use 16-bit counter mode
ET0 = 1; // generate an interrupt
TR0 = 1; // run
// Enable interrupts
EA = 1;
while(1);
}
TARGET = blinky
LIBFX2 = ../../firmware/library
include $(LIBFX2)/fx2rules.mk
Interacting over USB¶
Consider the code of the Cypress bootloader if you want to see how simple USB functionality can be implemented:
#include <fx2lib.h>
#include <fx2usb.h>
#include <fx2delay.h>
#include <fx2eeprom.h>
usb_desc_device_c usb_device = {
.bLength = sizeof(struct usb_desc_device),
.bDescriptorType = USB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = USB_DEV_CLASS_VENDOR,
.bDeviceSubClass = USB_DEV_SUBCLASS_VENDOR,
.bDeviceProtocol = USB_DEV_PROTOCOL_VENDOR,
.bMaxPacketSize0 = 64,
.idVendor = 0x04b4,
.idProduct = 0x8613,
.bcdDevice = 0x0000,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1,
};
usb_desc_interface_c usb_interface = {
.bLength = sizeof(struct usb_desc_interface),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = USB_IFACE_CLASS_VENDOR,
.bInterfaceSubClass = USB_IFACE_SUBCLASS_VENDOR,
.bInterfaceProtocol = USB_IFACE_PROTOCOL_VENDOR,
.iInterface = 0,
};
usb_configuration_c usb_config = {
{
.bLength = sizeof(struct usb_desc_configuration),
.bDescriptorType = USB_DESC_CONFIGURATION,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_ATTR_RESERVED_1,
.bMaxPower = 50,
},
{
{ .interface = &usb_interface },
{ 0 }
}
};
// check for "earlier than 3.5", but version macros shipped in 3.6
#if !defined(__SDCC_VERSION_MAJOR)
__code const struct usb_configuration *__code const usb_configs[] = {
#else
usb_configuration_set_c usb_configs[] = {
#endif
&usb_config,
};
usb_ascii_string_c usb_strings[] = {
[0] = "whitequark@whitequark.org",
[1] = "FX2 series Cypress-class bootloader",
};
usb_descriptor_set_c usb_descriptor_set = {
.device = &usb_device,
.config_count = ARRAYSIZE(usb_configs),
.configs = usb_configs,
.string_count = ARRAYSIZE(usb_strings),
.strings = usb_strings,
};
enum {
USB_REQ_CYPRESS_EEPROM_SB = 0xA2,
USB_REQ_CYPRESS_EXT_RAM = 0xA3,
USB_REQ_CYPRESS_RENUMERATE = 0xA8,
USB_REQ_CYPRESS_EEPROM_DB = 0xA9,
USB_REQ_LIBFX2_PAGE_SIZE = 0xB0,
};
// We perform lengthy operations in the main loop to avoid hogging the interrupt.
// This flag is used for synchronization between the main loop and the ISR;
// to allow new SETUP requests to arrive while the previous one is still being
// handled (with all data received), the flag should be reset as soon as
// the entire SETUP request is parsed.
volatile bool pending_setup;
void handle_usb_setup(__xdata struct usb_req_setup *req) {
req;
if(pending_setup) {
STALL_EP0();
} else {
pending_setup = true;
}
}
// The EEPROM write cycle time is the same for a single byte or a single page;
// it is therefore far more efficient to write EEPROMs in entire pages.
// Unfortunately, there is no way to discover page size if it is not known
// beforehand. We play it safe and write individual bytes unless the page size
// was set explicitly via a libfx2-specific request, such that Cypress vendor
// requests A2/A9 work the same as in Cypress libraries by default.
uint8_t page_size = 0; // log2(page size in bytes)
void handle_pending_usb_setup() {
__xdata struct usb_req_setup *req = (__xdata struct usb_req_setup *)SETUPDAT;
if(req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT) &&
req->bRequest == USB_REQ_CYPRESS_RENUMERATE) {
pending_setup = false;
USBCS |= _DISCON;
delay_ms(10);
USBCS &= ~_DISCON;
return;
}
if(req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT) &&
req->bRequest == USB_REQ_LIBFX2_PAGE_SIZE) {
page_size = req->wValue;
pending_setup = false;
ACK_EP0();
return;
}
if((req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_IN) ||
req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT)) &&
(req->bRequest == USB_REQ_CYPRESS_EEPROM_SB ||
req->bRequest == USB_REQ_CYPRESS_EEPROM_DB)) {
bool arg_read = (req->bmRequestType & USB_DIR_IN);
bool arg_dbyte = (req->bRequest == USB_REQ_CYPRESS_EEPROM_DB);
uint8_t arg_chip = arg_dbyte ? 0x51 : 0x50;
uint16_t arg_addr = req->wValue;
uint16_t arg_len = req->wLength;
pending_setup = false;
while(arg_len > 0) {
uint8_t len = arg_len < 64 ? arg_len : 64;
if(arg_read) {
while(EP0CS & _BUSY);
if(!eeprom_read(arg_chip, arg_addr, EP0BUF, len, arg_dbyte)) {
STALL_EP0();
break;
}
SETUP_EP0_BUF(len);
} else {
SETUP_EP0_BUF(0);
while(EP0CS & _BUSY);
if(!eeprom_write(arg_chip, arg_addr, EP0BUF, len, arg_dbyte, page_size,
/*timeout=*/166)) {
STALL_EP0();
break;
}
}
arg_len -= len;
arg_addr += len;
}
return;
}
if((req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_IN) ||
req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT)) &&
req->bRequest == USB_REQ_CYPRESS_EXT_RAM) {
bool arg_read = (req->bmRequestType & USB_DIR_IN);
uint16_t arg_addr = req->wValue;
uint16_t arg_len = req->wLength;
pending_setup = false;
while(arg_len > 0) {
uint8_t len = arg_len < 64 ? arg_len : 64;
if(arg_read) {
while(EP0CS & _BUSY);
xmemcpy(EP0BUF, (__xdata void *)arg_addr, len);
SETUP_EP0_BUF(len);
} else {
SETUP_EP0_BUF(0);
while(EP0CS & _BUSY);
xmemcpy((__xdata void *)arg_addr, EP0BUF, arg_len);
}
arg_len -= len;
arg_addr += len;
}
return;
}
STALL_EP0();
}
int main() {
CPUCS = _CLKOE|_CLKSPD1;
// Don't re-enumerate. `fx2tool -B` will load this firmware to access EEPROM, and it
// expects to be able to keep accessing the device. If you are using this firmware
// in your own code, set /*diconnect=*/true.
usb_init(/*disconnect=*/false);
while(1) {
if(pending_setup)
handle_pending_usb_setup();
}
}
TARGET = boot-cypress
LIBRARIES = fx2 fx2usb fx2isrs
LIBFX2 = ../library
include $(LIBFX2)/fx2rules.mk
Adding an DFU bootloader¶
It is easy to integrate a standards-compliant and OS-agnostic Device Firmware Upgrade bootloader as libfx2 provides all necessary infrastructure, and it only needs to be configured for a specific board and integrated into a target application:
#include <fx2lib.h>
#include <fx2delay.h>
#include <fx2eeprom.h>
#include <fx2usbdfu.h>
// Replace this with the actual EEPROM size on your board to use its full capacity.
#define FIRMWARE_SIZE 16384
// Application mode descriptors.
usb_desc_device_c usb_device = {
.bLength = sizeof(struct usb_desc_device),
.bDescriptorType = USB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = USB_DEV_CLASS_PER_INTERFACE,
.bDeviceSubClass = USB_DEV_SUBCLASS_PER_INTERFACE,
.bDeviceProtocol = USB_DEV_PROTOCOL_PER_INTERFACE,
.bMaxPacketSize0 = 64,
.idVendor = 0x04b4,
.idProduct = 0x8613,
.bcdDevice = 0x0000,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1,
};
extern usb_dfu_desc_functional_c usb_dfu_functional;
usb_desc_interface_c usb_interface_dfu_runtime = {
.bLength = sizeof(struct usb_desc_interface),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = USB_IFACE_CLASS_APP_SPECIFIC,
.bInterfaceSubClass = USB_IFACE_SUBCLASS_DFU,
.bInterfaceProtocol = USB_IFACE_PROTOCOL_DFU_RUNTIME,
.iInterface = 0,
};
usb_configuration_c usb_config_app = {
{
.bLength = sizeof(struct usb_desc_configuration),
.bDescriptorType = USB_DESC_CONFIGURATION,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_ATTR_RESERVED_1,
.bMaxPower = 50,
},
{
{ .interface = &usb_interface_dfu_runtime },
{ .generic = (struct usb_desc_generic *) &usb_dfu_functional },
{ 0 }
}
};
usb_configuration_set_c usb_configs_app[] = {
&usb_config_app,
};
usb_ascii_string_c usb_strings_app[] = {
[0] = "whitequark@whitequark.org",
[1] = "Example application with DFU support",
};
// DFU mode descriptors
usb_desc_interface_c usb_interface_dfu_upgrade = {
.bLength = sizeof(struct usb_desc_interface),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = USB_IFACE_CLASS_APP_SPECIFIC,
.bInterfaceSubClass = USB_IFACE_SUBCLASS_DFU,
.bInterfaceProtocol = USB_IFACE_PROTOCOL_DFU_UPGRADE,
.iInterface = 3,
};
usb_dfu_desc_functional_c usb_dfu_functional = {
.bLength = sizeof(struct usb_dfu_desc_functional),
.bDescriptorType = USB_DESC_DFU_FUNCTIONAL,
.bmAttributes = USB_DFU_ATTR_CAN_DNLOAD |
USB_DFU_ATTR_CAN_UPLOAD |
USB_DFU_ATTR_MANIFESTATION_TOLERANT |
USB_DFU_ATTR_WILL_DETACH,
.wTransferSize = 64,
.bcdDFUVersion = 0x0101,
};
usb_configuration_c usb_config_dfu = {
{
.bLength = sizeof(struct usb_desc_configuration),
.bDescriptorType = USB_DESC_CONFIGURATION,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_ATTR_RESERVED_1,
.bMaxPower = 50,
},
{
{ .interface = &usb_interface_dfu_upgrade },
{ .generic = (struct usb_desc_generic *) &usb_dfu_functional },
{ 0 }
}
};
usb_configuration_set_c usb_configs_dfu[] = {
&usb_config_dfu,
};
usb_ascii_string_c usb_strings_dfu[] = {
[0] = "whitequark@whitequark.org",
[1] = "FX2 series DFU-class bootloader",
[2] = "Boot EEPROM"
};
// Application and DFU code
__xdata struct usb_descriptor_set usb_descriptor_set = {
.device = &usb_device,
.config_count = ARRAYSIZE(usb_configs_app),
.configs = usb_configs_app,
.string_count = ARRAYSIZE(usb_strings_app),
.strings = usb_strings_app,
};
usb_dfu_status_t firmware_upload(uint32_t address, __xdata uint8_t *data,
__xdata uint16_t *length) __reentrant {
if(address < FIRMWARE_SIZE) {
// Only 2-byte EEPROMs are large enough to store any sort of firmware, and the address
// of a 2-byte boot EEPROM is fixed, so it's safe to hardcode it here.
if(eeprom_read(0x51, address, data, *length, /*double_byte=*/true)) {
return USB_DFU_STATUS_OK;
} else {
return USB_DFU_STATUS_errUNKNOWN;
}
} else {
*length = 0;
return USB_DFU_STATUS_OK;
}
}
usb_dfu_status_t firmware_dnload(uint32_t address, __xdata uint8_t *data,
uint16_t length) __reentrant {
if(length == 0) {
if(address == FIRMWARE_SIZE)
return USB_DFU_STATUS_OK;
else
return USB_DFU_STATUS_errNOTDONE;
} else if(address < FIRMWARE_SIZE) {
// Use 8-byte page writes, which are slow but universally compatible. (Strictly speaking,
// no EEPROM can be assumed to provide any page writes, but virtually every EEPROM larger
// than 16 KiB supports at least 8-byte pages).
//
// If the datasheet for the EEPROM lists larger pages as permissible, this would provide
// a significant speed boost. Unfortunately it is not really possible to discover the page
// size by interrogating the EEPROM.
if(eeprom_write(0x51, address, data, length, /*double_byte=*/true,
/*page_size=*/3, /*timeout=*/166)) {
return USB_DFU_STATUS_OK;
} else {
return USB_DFU_STATUS_errWRITE;
}
} else {
return USB_DFU_STATUS_errADDRESS;
}
}
usb_dfu_status_t firmware_manifest() __reentrant {
// Simulate committing the firmware. If this function is not necessary, it may simply be omitted,
// together with its entry in `usb_dfu_iface_state`.
delay_ms(1000);
return USB_DFU_STATUS_OK;
}
usb_dfu_iface_state_t usb_dfu_iface_state = {
// Set to bInterfaceNumber of the DFU descriptor in the application mode.
.interface = 0,
.firmware_upload = firmware_upload,
.firmware_dnload = firmware_dnload,
.firmware_manifest = firmware_manifest,
};
void handle_usb_setup(__xdata struct usb_req_setup *req) {
if(usb_dfu_setup(&usb_dfu_iface_state, req))
return;
STALL_EP0();
}
int main() {
// Run core at 48 MHz fCLK.
CPUCS = _CLKSPD1;
// Re-enumerate, to make sure our descriptors are picked up correctly.
usb_init(/*disconnect=*/true);
while(1) {
// Handle switching to DFU mode from application mode.
if(usb_dfu_iface_state.state == USB_DFU_STATE_appDETACH) {
// Wait until the host has received our ACK of the DETACH request before actually
// disconnecting. This is because if we disconnect immediately, the host might just
// return an error to the DFU utility.
delay_ms(10);
USBCS |= _DISCON;
// Switch to DFU mode.
usb_dfu_iface_state.state = USB_DFU_STATE_dfuIDLE;
// Re-enumerate using the DFU mode descriptors. For Windows compatibility, it is necessary
// to change USB Product ID in the Device descriptor as well, since Windows is unable
// to rebind a DFU driver to the same VID:PID pair. (Windows is euphemistically called out
// in the DFU spec as a "certain operating system").
((usb_desc_device_t *)usb_device)->idProduct++;
usb_descriptor_set.config_count = ARRAYSIZE(usb_configs_dfu);
usb_descriptor_set.configs = usb_configs_dfu;
usb_descriptor_set.string_count = ARRAYSIZE(usb_strings_dfu);
usb_descriptor_set.strings = usb_strings_dfu;
// Don't reconnect again in `usb_init`, as we have just disconnected explicitly.
usb_init(/*disconnect=*/false);
}
// Handle any lengthy DFU requests, i.e. the ones that call back into firmware_* functions.
usb_dfu_setup_deferred(&usb_dfu_iface_state);
}
}
TARGET = boot-dfu
LIBRARIES = fx2 fx2usb fx2dfu fx2isrs
MODEL = small
LIBFX2 = ../library
include $(LIBFX2)/fx2rules.mk
The DFU images suitable for flashing can be generated from Intel HEX firmware images using
the dfu
subcommand of the command-line tool.
Adding an UF2 bootloader¶
It is easy to integrate a very versatile and OS-agnostic UF2 compliant bootloader as libfx2 provides all necessary infrastructure, and it only needs to be configured for a specific board and integrated into a target application:
#include <fx2lib.h>
#include <fx2delay.h>
#include <fx2eeprom.h>
#include <fx2usbmassstor.h>
#include <fx2uf2.h>
usb_desc_device_c usb_device = {
.bLength = sizeof(struct usb_desc_device),
.bDescriptorType = USB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = USB_DEV_CLASS_PER_INTERFACE,
.bDeviceSubClass = USB_DEV_SUBCLASS_PER_INTERFACE,
.bDeviceProtocol = USB_DEV_PROTOCOL_PER_INTERFACE,
.bMaxPacketSize0 = 64,
.idVendor = 0x04b4,
.idProduct = 0x8613,
.bcdDevice = 0x0000,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1,
};
usb_desc_interface_c usb_interface_mass_storage = {
.bLength = sizeof(struct usb_desc_interface),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_IFACE_CLASS_MASS_STORAGE,
.bInterfaceSubClass = USB_IFACE_SUBCLASS_MASS_STORAGE_SCSI,
.bInterfaceProtocol = USB_IFACE_PROTOCOL_MASS_STORAGE_BBB,
.iInterface = 0,
};
usb_desc_endpoint_c usb_endpoint_ep2_out = {
.bLength = sizeof(struct usb_desc_endpoint),
.bDescriptorType = USB_DESC_ENDPOINT,
.bEndpointAddress = 2,
.bmAttributes = USB_XFER_BULK,
.wMaxPacketSize = 512,
.bInterval = 0,
};
usb_desc_endpoint_c usb_endpoint_ep6_in = {
.bLength = sizeof(struct usb_desc_endpoint),
.bDescriptorType = USB_DESC_ENDPOINT,
.bEndpointAddress = 6|USB_DIR_IN,
.bmAttributes = USB_XFER_BULK,
.wMaxPacketSize = 512,
.bInterval = 0,
};
usb_configuration_c usb_config = {
{
.bLength = sizeof(struct usb_desc_configuration),
.bDescriptorType = USB_DESC_CONFIGURATION,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_ATTR_RESERVED_1,
.bMaxPower = 50,
},
{
{ .interface = &usb_interface_mass_storage },
{ .endpoint = &usb_endpoint_ep2_out },
{ .endpoint = &usb_endpoint_ep6_in },
{ 0 }
}
};
usb_configuration_set_c usb_configs[] = {
&usb_config,
};
usb_ascii_string_c usb_strings[] = {
[0] = "whitequark@whitequark.org",
[1] = "FX2 series UF2-class bootloader",
// USB MS BBB 4.1.1 requires each device to have an unique serial number that is at least
// 12 characters long. We cannot satisfy the uniqueness requirement, but we at least provide
// a serial number in a valid format.
[2] = "000000000000",
};
usb_descriptor_set_c usb_descriptor_set = {
.device = &usb_device,
.config_count = ARRAYSIZE(usb_configs),
.configs = usb_configs,
.string_count = ARRAYSIZE(usb_strings),
.strings = usb_strings,
};
usb_mass_storage_bbb_state_t usb_mass_storage_state = {
.interface = 0,
.max_in_size = 512,
.command = uf2_scsi_command,
.data_out = uf2_scsi_data_out,
.data_in = uf2_scsi_data_in,
};
static bool firmware_read(uint32_t address, __xdata uint8_t *data, uint16_t length) __reentrant {
// Only 2-byte EEPROMs are large enough to store any sort of firmware, and the address
// of a 2-byte boot EEPROM is fixed, so it's safe to hardcode it here.
return eeprom_read(0x51, address, data, length, /*double_byte=*/true);
}
static bool firmware_write(uint32_t address, __xdata uint8_t *data, uint16_t length) __reentrant {
// Use 8-byte page writes, which are slow but universally compatible. (Strictly speaking,
// no EEPROM can be assumed to provide any page writes, but virtually every EEPROM larger
// than 16 KiB supports at least 8-byte pages).
//
// If the datasheet for the EEPROM lists larger pages as permissible, this would provide
// a significant speed boost. Unfortunately it is not really possible to discover the page
// size by interrogating the EEPROM.
return eeprom_write(0x51, address, data, length, /*double_byte=*/true,
/*page_size=*/3, /*timeout=*/166);
}
// Configure for 16Kx8 EEPROM, since this is upwards compatible with larger EEPROMs and
// any application integrating the UF2 bootloader will be at least ~12 KB in size.
// (The overhead of the bootloader is smaller than that, since much of the USB machinery
// can be shared between the bootloader and the application.)
uf2_configuration_c uf2_config = {
// Provide a virtual mass storage device of 32 MiB in size. Using a device that is
// too small will result in broken filesystem being generated (in particular, below
// a certain cluster count, the filesystm gets interpreted as FAT12 instead of FAT16),
// and a device that is too large will result in slower operations (mounting, etc).
// 32 MiB is a good number.
.total_sectors = 2 * 32768,
// Replace the Model: and Board-ID: fields with ones specific for your board.
// Note that Board-ID: field should be machine-readable.
// The INFO_UF2.TXT file can be up to 512 bytes in size.
.info_uf2_txt =
"UF2 Bootloader for Cypress FX2\r\n"
"Model: Generic Developer Board with 16Kx8 EEPROM\r\n"
"Board-ID: FX2-Generic_16Kx8-v0\r\n",
// Replace the URL with a hyperlink to a document describing your board.
.index_htm =
"<meta http-equiv=\"refresh\" content=\"0; url=https://github.com/whitequark/libfx2/\">",
// Replace this with the actual EEPROM size on your board to use its full capacity.
.firmware_size = 16384,
.firmware_read = firmware_read,
.firmware_write = firmware_write,
};
void handle_usb_setup(__xdata struct usb_req_setup *req) {
if(usb_mass_storage_bbb_setup(&usb_mass_storage_state, req))
return;
STALL_EP0();
}
volatile bool pending_ep6_in;
void isr_IBN() __interrupt {
pending_ep6_in = true;
CLEAR_USB_IRQ();
NAKIRQ = _IBN;
IBNIRQ = _IBNI_EP6;
}
int main() {
// Run core at 48 MHz fCLK.
CPUCS = _CLKSPD1;
// Use newest chip features.
REVCTL = _ENH_PKT|_DYN_OUT;
// NAK all transfers.
SYNCDELAY;
FIFORESET = _NAKALL;
// EP2 is configured as 512-byte double buffed BULK OUT.
EP2CFG = _VALID|_TYPE1|_BUF1;
EP2CS = 0;
// EP6 is configured as 512-byte double buffed BULK IN.
EP6CFG = _VALID|_DIR|_TYPE1|_BUF1;
EP6CS = 0;
// EP4/8 are not used.
EP4CFG &= ~_VALID;
EP8CFG &= ~_VALID;
// Enable IN-BULK-NAK interrupt for EP6.
IBNIE = _IBNI_EP6;
NAKIE = _IBN;
// Reset and prime EP2, and reset EP6.
SYNCDELAY;
FIFORESET = _NAKALL|2;
SYNCDELAY;
OUTPKTEND = _SKIP|2;
SYNCDELAY;
OUTPKTEND = _SKIP|2;
SYNCDELAY;
FIFORESET = _NAKALL|6;
SYNCDELAY;
FIFORESET = 0;
// Re-enumerate, to make sure our descriptors are picked up correctly.
usb_init(/*disconnect=*/true);
while(1) {
if(!(EP2CS & _EMPTY)) {
uint16_t length = (EP2BCH << 8) | EP2BCL;
if(usb_mass_storage_bbb_bulk_out(&usb_mass_storage_state, EP2FIFOBUF, length)) {
EP2BCL = 0;
} else {
EP2CS = _STALL;
EP6CS = _STALL;
}
}
if(pending_ep6_in) {
__xdata uint16_t length;
if(usb_mass_storage_bbb_bulk_in(&usb_mass_storage_state, EP6FIFOBUF, &length)) {
if(length > 0) {
EP6BCH = length >> 8;
SYNCDELAY;
EP6BCL = length;
}
} else {
EP6CS = _STALL;
}
pending_ep6_in = false;
}
}
}
TARGET = boot-uf2
LIBRARIES = fx2 fx2usb fx2usbmassstor fx2uf2 fx2isrs
MODEL = medium
LIBFX2 = ../library
include $(LIBFX2)/fx2rules.mk
The UF2 images suitable for flashing can be generated from Intel HEX firmware images using
the uf2
subcommand of the command-line tool.