cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H753VITx with ULPI USB3320 - corrupted data?

vosahlov
Associate II

 

Dear all,

for the past few weeeks, I've been trying to get the STM32H753VIT6 up and running with the USB3320 ULPI interface but I have not been succesfull. First, I have tried to run a CubeMX generated USB CDC example to test the functionality, but the device was not detectable by a PC, yet the generated code was running, not hanging at any exception.

After this, I was intending to test the basic functionality by reading the interface registers (the VID in this case). For this, I have used the undocumented, but known, functions that popped up in STM's low power code for the HAL. I know that using undocumented functions pointing to undocumented registers is far from optimal, but there are no other oprtions that I know of at this time. With this, I have been able to communicate with the PHY and read the register succesfully, but, the data seemed to be incorrect. My issue was that while reading the Vendor Identifier register (address 0x00), I get a read result of 0x28 instead of the expected 0x24... Thus, I went and scoped the transaction on the bus and to my surprise, the bus data seems to actually transfer 0x24.

I'm attaching a capture of the read transfer on the ULPI. It's made up from multiple captures, as I have only one high bandwith probe, but all of them are synced to the DIR signal. All the signals (aside from DIR) are captured by a 1.5GHz probe on a 10GHz scope. The DIR is captured with a 350MHz probe, but I have checked (with the high bandwith probe) that the pulse is indeed quiet sharp without ringing and reflections. I cannot see any obvious issue with the data lines here. Looking at the USB3320 datasheet, the transfer shoul be correct in my opinion. The only odd thing is that the USB3320 response pulses (on D2 and D5) seem to decrease on amplitude with time, but I'm unsure of the cause. This behaviour can be seen on all data pins of the interface.

ulpi_read_reg_0x00.png

The schematic and layout files are also attached bellow. ULPI lines are length matched, impedance controlled and terminated (0R resistors in schematic have been changed to termination ones). No apparent ringing or reflection on the lines. The CLK, NXT and DIR seem a little overterminated, I've exchanged these and now they are also crisp (but I didn't bother to do a new capture here). The schematic and board supports multiple configurations. At the moment, I'm deriving the reference clock for the USB3320 from a 24MHz crystal. The DCDC is used for power but I also tested with the LDO and bench power supply, nothing helped.

vosahlov_0-1727957108920.png

The code I'm using to test is also attached bellow. There is some abstraction going on for the GPIO with a custom made library, but that has been confirmed to be working. Otherwise, it's bare metal.

 

 

 

 

//#include "main.hpp"
#include "stm32h753xx.h"
#include "core_cm7.h"
#include "cmsis_compiler.h"

#include <stmcpp/register.hpp>
#include <stmcpp/units.hpp>
#include <stmcpp/clock.hpp>
#include <stmcpp/gpio.hpp>

#include <string>

#include "git.hpp"

using namespace stmcpp::units;

std::uint32_t devMode = 0, scratchRegister = 0;


// Here is some magic that should allow us to read / write ulpi directly
#define USBULPI_PHYCR     ((uint32_t)(0x40040000 + 0x034))
#define USBULPI_D07       ((uint32_t)0x000000FF)
#define USBULPI_New       ((uint32_t)0x02000000)
#define USBULPI_RW        ((uint32_t)0x00400000) 
#define USBULPI_S_BUSY    ((uint32_t)0x04000000) 
#define USBULPI_S_DONE    ((uint32_t)0x08000000)

#define USB_OTG_READ_REG32(reg)  (*(__IO uint32_t *)(reg))
#define USB_OTG_WRITE_REG32(reg,value) (*(__IO uint32_t *)(reg) = (value))

/**
  * @brief  Read CR value
  * @PAram  Addr the Address of the ULPI Register
  * @retval Returns value of PHY CR register
  */
extern "C" uint32_t USB_ULPI_Read(uint32_t Addr)
{ 
   __IO uint32_t val = 0;
   __IO uint32_t timeout = 10000000; /* Can be tuned based on the Clock or etc... */
   
   USB_OTG_WRITE_REG32(USBULPI_PHYCR, USBULPI_New | (Addr << 16));
   val = USB_OTG_READ_REG32(USBULPI_PHYCR);
   while (((val & USBULPI_S_DONE) == 0) && (timeout--)) 
   { 
       val = USB_OTG_READ_REG32(USBULPI_PHYCR);
   } 
   val = USB_OTG_READ_REG32(USBULPI_PHYCR);  
   return  (val & 0x000000ff);
}

/**
  * @brief  Write CR value
  * @PAram  Addr the Address of the ULPI Register
  * @PAram  Data Data to write
  * @retval Returns value of PHY CR register
  */
extern "C" uint32_t USB_ULPI_Write(uint32_t Addr, uint32_t Data)   /* Parameter is the Address of the ULPI Register & Date to write */
{  
  __IO uint32_t val;
  __IO uint32_t timeout = 10000000;   /* Can be tuned based on the Clock or etc... */
  
  USB_OTG_WRITE_REG32(USBULPI_PHYCR, USBULPI_New | USBULPI_RW | (Addr << 16) | (Data & 0x000000ff));
  val = USB_OTG_READ_REG32(USBULPI_PHYCR);
  while (((val & USBULPI_S_DONE) == 0) && (timeout--)) 
  {
           val = USB_OTG_READ_REG32(USBULPI_PHYCR);
  }
  
  val = USB_OTG_READ_REG32(USBULPI_PHYCR);
  return 0; 
}

extern "C" void SystemInit(void){

	// Enable the LDO and wait for it to be ready
	PWR->CR3 |= PWR_CR3_LDOEN;
	while(!(PWR->CSR1 & PWR_CSR1_ACTVOSRDY_Msk)){;}

	//Enable the USB voltage detector
	PWR->CR3 |= PWR_CR3_USB33DEN;

	//Enable sysconfig clock
	RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN;

    //Set voltage scale to 1 and wait for it to be ready
	PWR->D3CR |= 0b11 << PWR_D3CR_VOS_Pos;
	while(!(PWR->D3CR & PWR_D3CR_VOSRDY_Msk)){;}

	//Enable overdrive and wait for the VOS to change to 0
	SYSCFG->PWRCR |= SYSCFG_PWRCR_ODEN;
	while(!(PWR->D3CR & PWR_D3CR_VOSRDY_Msk)){;}

	//Enable HSE on and wait for it to be ready
	RCC->CR |= RCC_CR_CSSHSEON | RCC_CR_HSEON;
	while(!(RCC->CR & RCC_CR_HSERDY_Msk)){;}

	//Enable HSI48 on and wait for it to be ready
	RCC->CR |= RCC_CR_HSI48ON;
	while(!(RCC->CR & RCC_CR_HSI48RDY_Msk)){;}

	//Set up the flash latency and wait for it to take effect
	constexpr std::uint32_t FLASH_ACR_CFG = (FLASH_ACR_LATENCY_4WS | (0b10 << FLASH_ACR_WRHIGHFREQ_Pos));
	FLASH->ACR = FLASH_ACR_CFG;
	while(FLASH->ACR != FLASH_ACR_CFG){;}

	//Setup the PLL1 for sysclock of 480MHz, source is HSE
	RCC->PLL1DIVR =	( (60 - 1) << RCC_PLL1DIVR_N1_Pos |
                	  (2 - 1) << RCC_PLL1DIVR_P1_Pos |
                      (4 - 1) << RCC_PLL1DIVR_Q1_Pos |
                      (2 - 1) << RCC_PLL1DIVR_R1_Pos 
                    );
	RCC->PLLCKSELR = (3 << RCC_PLLCKSELR_DIVM1_Pos) | RCC_PLLCKSELR_PLLSRC_HSE;
	RCC->PLLCFGR = (0b11 << RCC_PLLCFGR_PLL1RGE_Pos) | RCC_PLLCFGR_DIVP1EN;

	//Enable it and wait for it to lock
	RCC->CR |= RCC_CR_PLL1ON_Msk;	
	while(!(RCC->CR & RCC_CR_PLL1RDY_Msk)){;}

	//Setup the domain dividers
	RCC->D1CFGR = RCC_D1CFGR_D1PPRE_DIV1 | RCC_D1CFGR_D1PPRE_DIV2 | RCC_D1CFGR_HPRE_DIV2;
    RCC->D2CFGR = RCC_D2CFGR_D2PPRE1_DIV2 | RCC_D2CFGR_D2PPRE2_DIV2;
	RCC->D3CFGR = RCC_D3CFGR_D3PPRE_DIV2;

	//Switch to PLL1 and wait for lock
	RCC->CFGR |= RCC_CFGR_SW_PLL1;
	while((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL1){;}

	// Enable CSI (for clock compensation)
	RCC->CR |= RCC_CR_CSION;
    while(!(RCC->CR & RCC_CR_CSION_Msk)){;}

	// Enable clock compensation cell and wait for it to be ready
	SYSCFG->CCCSR |= SYSCFG_CCCSR_EN;
	while(!(SYSCFG->CCCSR & SYSCFG_CCCSR_READY_Msk)){;}

	// Select HSI48 as clock for the usb peripheral and check if it has been selected
    RCC->D2CCIP2R |= (0b11 << RCC_D2CCIP2R_USBSEL_Pos);
	while((RCC->D2CCIP2R & RCC_D2CCIP2R_USBSEL_Msk) != (0b11 << RCC_D2CCIP2R_USBSEL_Pos)){;}

	// Enable the necessary GPIO and USB clocks
	RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN | RCC_AHB4ENR_GPIOBEN | RCC_AHB4ENR_GPIOCEN | RCC_AHB4ENR_GPIODEN | RCC_AHB4ENR_GPIOEEN;
	RCC->AHB1ENR |= RCC_AHB1ENR_USB1OTGHSEN | RCC_AHB1ENR_USB1OTGHSULPIEN;

	__ASM volatile("dsb");

}

char * str;

extern "C" int main(void){
	// Setup the systick timer to count with resolution of 1ms
	stmcpp::clock::systick::init(1_ms);

	stmcpp::gpio::pin<stmcpp::gpio::port::porta, 2>  ulpi_rst (stmcpp::gpio::mode::output, stmcpp::gpio::otype::pushPull);

	// The clock reference selection pins
	stmcpp::gpio::pin<stmcpp::gpio::port::porte, 7> refsel0 (stmcpp::gpio::mode::output, stmcpp::gpio::otype::pushPull);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 2> refsel1 (stmcpp::gpio::mode::output, stmcpp::gpio::otype::pushPull);
	stmcpp::gpio::pin<stmcpp::gpio::port::portc, 5> refsel2 (stmcpp::gpio::mode::output, stmcpp::gpio::otype::pushPull);

	// ULPI pins
	stmcpp::gpio::pin<stmcpp::gpio::port::porta, 3>  ulpi_d0 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 0>  ulpi_d1 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 1>  ulpi_d2 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 10> ulpi_d3 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 11> ulpi_d4 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 12> ulpi_d5 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 13> ulpi_d6 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portb, 5>  ulpi_d7 (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);

	stmcpp::gpio::pin<stmcpp::gpio::port::portc, 0>  ulpi_stp (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portc, 2>  ulpi_dir (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::portc, 3>  ulpi_nxt (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);
	stmcpp::gpio::pin<stmcpp::gpio::port::porta, 5>  ulpi_clk (stmcpp::gpio::mode::af10, stmcpp::gpio::speed::high);

	// Make sure that the PC2 and PC3 analog switches are closed
	SYSCFG->PMCR &= ~SYSCFG_PMCR_PC2SO; 
    SYSCFG->PMCR &= ~SYSCFG_PMCR_PC3SO; 

	// Set up the reference clock to 24MHz (all refsel pins high)
	ulpi_rst.clear();
	refsel0.set();
	refsel1.set(); 
	refsel2.set();
	stmcpp::clock::systick::waitBlocking(20_ms);
	ulpi_rst.set();
	stmcpp::clock::systick::waitBlocking(100_ms);

	// Perform the core init according to the datasheet
	// Unmask global interrupt and indicate that periodic fifo is empty
	USB1_OTG_HS->GAHBCFG |= USB_OTG_GAHBCFG_GINT | USB_OTG_GAHBCFG_PTXFELVL;
	// Set RX FIFO not empty flag
	USB1_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_RXFLVL;
	
	// Configure the basics
	USB1_OTG_HS->GUSBCFG |=    ((0 << USB_OTG_GUSBCFG_PHYSEL_Pos)	  |
								(1 << USB_OTG_GUSBCFG_ULPIAR_Pos)	  |
								(0 << USB_OTG_GUSBCFG_ULPIFSLS_Pos)	  |
								(0 << USB_OTG_GUSBCFG_ULPIEVBUSI_Pos)	  |
								(0 << USB_OTG_GUSBCFG_ULPIEVBUSD_Pos)	  |
								(0 << USB_OTG_GUSBCFG_HNPCAP_Pos) |  // Not HNP capable
								(0 << USB_OTG_GUSBCFG_SRPCAP_Pos) |  // Not SRP capable
								(9 << USB_OTG_GUSBCFG_TRDT_Pos) |    // Turnaround time
								(7 << USB_OTG_GUSBCFG_TOCAL_Pos));   // Timeout calibration

	// Unmask global and mode mismatch interrupt
	USB1_OTG_HS->GINTMSK |=  (USB_OTG_GINTMSK_OTGINT | USB_OTG_GINTMSK_MMISM);
	
	// Reset the core to clean interrupt flags
	while (!(USB1_OTG_HS->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL)) {}
	USB1_OTG_HS->GRSTCTL |= USB_OTG_GRSTCTL_CSRST;
	while ((USB1_OTG_HS->GRSTCTL & USB_OTG_GRSTCTL_CSRST) == USB_OTG_GRSTCTL_CSRST) {}

	// Reset the ULPI interface (this seems to be needed, otherwise, there is no activity on the ULPI bus)
	// Also, this needs to be done after the core reset
	ulpi_rst.clear();
	stmcpp::clock::systick::waitBlocking(20_ms);
	ulpi_rst.set();
	stmcpp::clock::systick::waitBlocking(100_ms);


	while(1){
		scratchRegister = USB_ULPI_Read(0x00);
		if(scratchRegister == 0x24)	__ASM volatile("bkpt");
		stmcpp::clock::systick::waitBlocking(100_ms);
	}
	
}

// Increment the systick timer
extern "C" void SysTick_Handler(){
    stmcpp::clock::systick::increment();
}

extern "C" void HardFault_Handler(void){
	//Ooops, hard fault! Disable interrupts
	__disable_irq();
		__ASM volatile("bkpt");
}

void stmcpp::error::globalFaultHandler(std::uint32_t hash, std::uint32_t code) {
	__ASM volatile("bkpt");
}

 

 

 

 

Other registers than the VID also read weird values. I can also see a similar issue while writing to the Scratch Register. I tried to write the range 0x00-0xFF and read the values back. The range I've got back was 0x00-0x7F. Seems like the D7 is not getting set, but there is activity on the bus on this pin... As said, I've tried with different supplies, I've tried generating REFCLK from the STM, 12MHz crystal and 24MHz crystal. I tried to power the USB3320 from the internal LDO instead of directly from the 3V3... I tried running the STM stack, tinyusb and going bare metal. I Just kind of feel like I've tried everything :D

I would be very glad if you have any ideas on what could be wrong or have seen such a behaviour before.

 

Thanks in advance fr any help!

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

Receiving 0x28 instead of 0x24 would suggests data lines are mismatched, particularly D2/D3.

TDK_0-1727958636161.png

Looks like D2 on the PHY chip is connected to D3 on the STM32 (among other mismatches).

If you feel a post has answered your question, please click "Accept as Solution".

View solution in original post

3 REPLIES 3
TDK
Guru

Receiving 0x28 instead of 0x24 would suggests data lines are mismatched, particularly D2/D3.

TDK_0-1727958636161.png

Looks like D2 on the PHY chip is connected to D3 on the STM32 (among other mismatches).

If you feel a post has answered your question, please click "Accept as Solution".

Oh my, the more I've been looking into this, the blinder I was... I obviously triple checked the pinouts of the MCU and PHY, but not the sheet interconnect...

 

I'm gonna try to swap those and get back to you with the result.

vosahlov
Associate II

This was indeed the issue. Swapped the lines and now it is working flawlessly! Thanks again!