Hello,
I'm currently attempting to get the a MK64FN1M0VLL12 to do quite a few things! I have a 50MHz reference clock coming in on EXTAL which I am using in tandem with Processor Expert to generate my clocking pathway to get the core clocked to 120Mhz. The 50MHz is imperative for RMII and Ethernet, which isn't the issue at hand. I'm developing this on a custom and in-house board at my employment.
The K64 family has a wonderful feature, an internal 48MHz reference clock that's part of the USB module. I thought "sure, let's use that for USB!", since RMII needs 50Mhz, every other peripheral is happy except USB. I was immediately face to face with a problem: Processor Expert doesn't seem to know about or acknowledge the 48MHz IRC as a clock source to use with the USB Logical Device Drivers. If you try to use the 48Mhz IRC for anything except a source for the MCG's PLL/FLL you are SOL. The documentation clearly shows USB as being able to run independently of the core clock, effectively putting it in its own clock domain. So I said "okay well I just need to write a simple USB CDC driver, couldn't be too bad" and I set down on a turbid path of wrapping my head around just enough of the processor internals and the USB 2.0 spec to get this thing moving.
I have USB primed and ready, I've verified my Buffer Descriptor Tables are set up properly, that my Buffer Descriptors themselves are ready (Endpoint 0 configured and ready). I get and handle my USB_RST interrupts appropriately and make sure EP0 is ready to receive the SETUP packet(s) from the host. I got it to the point where you plug it in and everything is off and running, the host computer sees the presence of a USB device, and through a serial log I get a TON of Start of Frames, but I almost never get a simple TOKDNE, or "Token Done", in fact I was getting USB Error interrupts with DMA errors more often than not (USB_ERRSTAT = 0x20, meaning "DMA Error" more or less). I didn't know much about this sort of problem set so I dug deeper.
The Kinetis K64 is a crossbar based architecture, which I understand to mean that all participants of the crossbar have basically zero or 1 cycle access to other participants across the crossbar, the documentation also strongly suggests that the USB module has its own DMA controller built into it. The idea that popped into my head is that I'm experiencing resource contention! The Core is running at 120MHz, USB is running in its own domain at 48MHz, USB's DMA must be failing to get to SRAM to interrogate the BDT and put things where they need to be! There's two modes of crossbar arbitration for all the crossbar slaves, Round Robin and Fixed Priority, the default being Fixed Priority. So I went ahead and tried to fiddle with the priority of the masters on the crossbar switch.
AXBS_PRS0 = 0x504321;
AXBS_PRS1 = 0x504321;
AXBS_PRS2 = 0x504321;
This seems to have stopped the DMA errors, and after I initially tried this I was pleased to see a consistent round of TOKDNE interrupts reaching my code! Strangely the buffer descriptors were untouched and empty (I've triple checked and audited the values of all the relevant registers and triple checked the way the pointer math plays out with the USB module and where it wants things to be) I went ahead and added some more verbosity to my debug prints so I could see more. Suddenly no more TOKDNEs, just an ocean of SOFs, a few RSTs, a few SLEEPs and eventually the host gives up. No more TOKDNEs! I quickly undid my exact changes (they were minor) -- still no more TOKDNEs! Confounded I come to you, Kinetis experts.
Is Cross Clocking USB via the IRC48MHz or via USB_CLKIN even possible? Where should I go next? I'd like to use the Freescale SDK and the USB stack but it is a BEHEMOTH that seems to really really want me to use an ocean of code I'm hoping to avoid for a simple USB peripheral that I'm inches away from finishing implementing.
I haven't posted any code really yet, and I'm happy to do so but I was hoping to hazard some discussion and figure out what is worth sharing.
Edit:
I do want to share my findings with getting the darn IRC48MHz to drive USB in the first place though It is heavily dependent on examples I've found elsewhere, mainly the TeensyDuino 3.1 code, where Paul has written his own minimal USB stack more or less.
Here's the snippet used to select the IRC48M, I put it in a function because you need to do this more than once when initializing USB from what I could tell!
void USB_ClockInit() {
// This selects the muxing of the high frequency clock options for
// extra peripherals (USB included of course).
// See Figure 5-1 "Clocking Diagram" in the Kinetis K64 Sub-Family reference manual
// You should see a muxer with the following options:
// MCGPLLCLK, MCGFLLCLK, IRC48MCLK, we need to use the 48MHz
// which is what the PLLFLLSEL bitfield does.
// The USBSRC is pretty self explanatory, it can either use the aforementioned
// mux pathway, or an external pin as a reference. We want the internal 48
// Select 48MHz IRC as the optional peripheral clock
SIM_SOPT2 |= SIM_SOPT2_SET_PLLFLLSEL;
// Select the USB src as the IRC48M
SIM_SOPT2 |= SIM_SOPT2_SET_USBSRC;
// Enables the 48MHz internal reference recovery
// clock feature and also the 48MHz regulator
USB0_CLK_RECOVER_IRC_EN |= (USB0_CLK_RECOVER_IRC_EN_SET_IRC_EN | 0x1);
USB0_CLK_RECOVER_CTRL |= (USB0_BIT_CLOCK_RECOVER_EN);
// temporarily output the 48mhz irc onto pin 73
SIM_SOPT2 |= CLKOUTSEL_IRC48MHZ;
//SIM_SOPT2 |= ( 0x40 );
}
Then here's the code where I actually initialize USB:
void USBInitialize() {
for ( int i = 0 ; i < NUM_OF_ENDPOINTS * 4 ; i++ ) {
BufferDescriptorTable[i].buffer_descriptor = 0;
BufferDescriptorTable[i].buffer_address = 0;
}
// Enable USB
SCGC4 |= SCGC4_BITFIELD_USBOTG;
USB_ClockInit();
// Documentation says "wait two USB cycles" but we can just wait for it to read 0
while (( USB0_USBTRC0 & USB0_USBTRC0_USBRESET ) != 0) ;
// Reset the USB SIE
USB0_USBTRC0 |= USB0_USBTRC0_USBRESET;
// Resetting USB actually kills the IRC48M so we need
// to turn it back on!
USB_ClockInit();
// Documentation says "wait two USB cycles" but we can just wait for it to read 0
while (( USB0_USBTRC0 & USB0_USBTRC0_USBRESET ) != 0) ;
// Let the Buffer Descriptor Pages get set
// Basically, the BDT needs to be aligned on 512 bytes
// We can truncate the lower 8 bits because of this fact
// So if the "BufferDescriptorTable" variable inside of
// USBStructures.h isn't aligned to have an address that starts
// with, you'll be in bad shape!
USB0_BDTPAGE1 = ( ( (uint32_t)BufferDescriptorTable ) >> 8 ) & 255; // Bits 8-15
USB0_BDTPAGE2 = ( ( (uint32_t)BufferDescriptorTable ) >> 16 ) & 255; // Bits 16-23
USB0_BDTPAGE3 = ( ( (uint32_t)BufferDescriptorTable ) >> 24 ) & 255; // Bits 24-31
// Clear ISTAT, ERRSTAT and OTGISTAT flags
USB0_ISTAT = 0xFF;
USB0_ERRSTAT = 0xFF;
USB0_OTGISTAT = 0xFF;
// I've witnessed a USB0_USBTRC0 |= 0x40 here
// and that field is specifically undocumented
// but that is the "USB_CLK_ RECOVERY_INT" field, it's possible
// this happens to clear the USB_CLK_ RECOVERY_INT flag even
// though it's declared as read only, currently I'm not doing
// that because I don't want to code in undefined behavior
// USB0_USBTRC0 |= 0x40;
// Enable the USB peripheral
USB0_CTL = USB0_CTL_USBENSOFEN;
// This makes sure the pulldowns are disabled and the transceiver isn't suspended
USB0_USBCTRL = 0;
USB0_INTEN = USB_INTEN_USBRSTEN;
NVIC_ENABLE_IRQ( IRQ_USBOTG );
// enable pull up
USB0_CONTROL = USB0_CONTROL_DLLPULLUPNONOTG;
// The clock is configured!
// Things should start happening now
}
Max
The 48MHz crystal-less USB is discussed here:
Since you however have a 50MHz crystal avaliable (due to Ethernet) there is no advantage in using the IRC48MCLK (it has lower accuracy intended to allow lowest bill of material cost) and mixing this with a USB development is making life harder than it is - simply set the USB clock source to MCGPLLCLK (120MHz) and divide it by 2.5 to get 48MHz in SIM_CLKDIV2. You can always switch back to the IRC48MCLK once your USB driver is working to compare the performance.
There is no problem with the default crossbar switch settings and USB unless you are performing intensive M2M in parallel using the eDMA (which has higher default priority and can indeed starve the USB DMA from bandwith - even in this case it won't stop it from working - just cause the Tx to maybe drops some frames and have to repeat [this won't usually be noticeable since it takes place at the USB HW level with no SW interaction - but can be seen with a USB HW analyser under heavy loading and default setting]). If you want to play really safe set Round-Robin scheduling - although in the absense parallel M2M eDMA operations I can't imagine differences.
You may find it useful to compare with the uTasker project: KINETIS Project Code
which has a simple USB SW structure (one HAL layer for Kinetis, one generic USB layer and one application/class layer) which is compatible with all K and KL devices with USB and can be build with most popular tools (CW, KDS, IAR, Keil, Rowely, GCC, CooCox, Atollic, VisualStudio simulator). [with the Coldfire HAL layer the code is also compatible with Freescale Coldfire processors]
Below is a screen shot for the FRDM-K64Z when running the simulator. This allows USB operation (emumeration, all interrupts etc.) to be simulated and also class scripts to be tested (to develop application code). Even if you are developing your own driver from scratch this can make it much easier and effcient (the simulator also checks for out-of-spec clock settings so will immediately find such bad settings in code).
The package does however include industrial proven CDC class operation for the K and KL family which is documented at http://www.utasker.com/docs/uTasker/USB_User_Guide.PDF
Regards
Mark
http://www.utasker.com/kinetis.html
Thank you so much for this reply. I've put the "bespoke artisinal USB stuff" on pause for now, as I am working on other peripherals for now.