STM32F411RE Nucleo 40MHz SPI with Cypress FM25CL64B FRAM

nucleo

Previously, I tested a Cypress FRAM memory chip with the Arduino. A feature of FRAM memory is the speed at which it can be accessed. Cypress claims the FM25xxx chips can operate at 40MHz, however the maximum speed for SPI on an Arduino Uno is only 8MHz, or half the system clock frequency.

In order to test the FRAM at higher speeds, we need a µC capable of operating at much higher clock frequencies. The easiest option I had available was a STM32F411 Nucleo board.

The highly affordable STM32 Nucleo boards (only $10.33 USD at mouser.com) are available with an assortment of ARM Cortex-M µCs, share Arduino connectors, come with an integrated USB debugger/programmer, and can be used with a wide range of development environments. Admittedly, learning to program the ARM µC requires climbing a much steeper learning curve, but the Nucleo boards offer incredible performance at a fraction of the cost of Arduino. For example, my STM32F411RE based Nucleo board incorporates an ARM Cortex-M4 processor (with FPU), 512Kb of flash, 128kB of SRAM memory, up to 81 I/O ports, up to 13 communication interfaces (USB, USART, SPI, I2C and SDIO), up to 11 timers, a 12-bit ADC, and an RTC. Check them out.

While the F411 µC Nucleo board utilizes only a 16MHz internal clock (there is no external crystal installed on the Nucleos), it has provisions for a Phase Lock Loop (PLL). The PLL, in conjunction with the internal clock, can run the chip at up to 100MHz! However, getting 100MHz out of a 16MHz oscillator takes just a little programming magic.

I decided to select the PLL to drive the system clock at 80MHz, also choosing to operate the APB2 peripheral bus at this same 80MHz frequency. The µC SPI peripherals utilize either the APB1 or the APB2 bus for timing. The maximum speed for the APB1 bus is half the system bus. Selecting SPI #5 which is on the APB2 bus and using a divide by 2 prescaler creates an SPI peripheral operating at 40MHz. There is both a spreadsheet based clock configuration tool, and a graphical µC configuration program available for the STM32 µCs to assist with clock setup.

I set the Nucleo up to perform a simple loop-back test by connecting the MOSI (pa10) pin to the MISO (pa12) pin. Unfortunately, my Saleae Logic Analyzer is maxed out at 24MHz, so I can’t confirm the SPI speed. The following test program is our only proof. If the SPI frequency is increased to 50MHz in the program, as expected, the FRAM read/writes become unreliable and start to flash the error LED.

SPI Test:

//spi test
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_spi.h"

//FM25CL64b FRAM opcodes
#define WREN  0x06 //set write enable latch
#define RDSR  0x05 //read status register
#define WRDI  0x04 //write disable
#define READ  0x03 //read memory data
#define WRITE 0x02 //write memory data
#define WRSR  0x01 //write status register
//pinouts:
//  CS 1 - 5 VCC
//  SO 2 - 6 HOLD
//  WP 3 - 7 SCK
// GND 4 - 8 SI
//use 10K pull up on CS, tie WP & HOLD to VCC
//Nucleo: pb1=ss, pb0=sck, pa12=miso, pa10=mosi 

//counts 1ms timeTicks
volatile uint32_t msTicks; 

void SysTick_Handler(void) {
  msTicks++;
}

//delay a number of Systicks
void Delay (uint32_t dlyTicks) {
  uint32_t curTicks;

  curTicks = msTicks;
  while ((msTicks - curTicks) < dlyTicks) { 
    __NOP(); 
  }
}

//configure SystemCoreClock using HSI (HSE is not populated on Nucleo board)
void SystemCoreClockConfigure(void) {
  //enable HSI
  RCC->CR |= ((uint32_t)RCC_CR_HSION);                     
  while ((RCC->CR & RCC_CR_HSIRDY) == 0)
    ; //Wait for HSI Ready RCC->CFGR = RCC_CFGR_SW_HSI;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)
    ; //wait for HSI used as system clock
  FLASH->ACR  = FLASH_ACR_PRFTEN;      //enable Prefetch Buffer
  FLASH->ACR |= FLASH_ACR_ICEN;        //instruction cache enable
  FLASH->ACR |= FLASH_ACR_DCEN;        //data cache enable
  FLASH->ACR |= FLASH_ACR_LATENCY_5WS; //flash 5 wait state
  //HCLK = SYSCLK
  RCC->CFGR |= RCC_CFGR_HPRE_DIV1;                         
  //APB1 = HCLK/2
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;                        
  //APB2 = HCLK/1
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;                        
  //disable PLL
  RCC->CR &= ~RCC_CR_PLLON;                                
  //PLL configuration: VCO=HSI/M*N, Sysclk=VCO/P
  //PLL_M=16, PLL_N=320, PLL_P=4, PLL_SRC=HSI, PLL_Q=8 for 80MHz SYSCLK/APB2, 40MHz APB1
  RCC->PLLCFGR = (16ul | (320ul<<6) | (1ul<<16) | (RCC_PLLCFGR_PLLSRC_HSI) | (8ul<<24));
  //enable PLL
  RCC->CR |= RCC_CR_PLLON;                                 
  //Wait till PLL is ready
  while((RCC->CR & RCC_CR_PLLRDY) == 0) 
    __NOP();
  //select PLL as system clock source
  RCC->CFGR &= ~RCC_CFGR_SW;                               
  RCC->CFGR |=  RCC_CFGR_SW_PLL;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
    ; //wait till PLL is system clock src
}

//configure spi5 using: spi5 pb1=ss, pb0=sck, pa12=miso, pa10=mosi 
void SPIConfigure(void) {
  uint32_t temp = 0x0;

  //gpio pin setup
  //enable GPIOA peripheral clock for MISO & MOSI
  RCC->AHB1ENR |= (1ul<<0);
  //enable GPIOB clock for SS and SCK
  RCC->AHB1ENR |= (1ul<<1);
  //alternate pin functions (AF06)
  //sck
  GPIOB->AFR[0] &= ~((uint32_t)0xf);
  GPIOB->AFR[0] |= ((uint32_t)0x6);
  //miso
  GPIOA->AFR[1] &= ~((uint32_t)0xf0000);
  GPIOA->AFR[1] |= ((uint32_t)0x60000);
  //mosi
  GPIOA->AFR[1] &= ~((uint32_t)0xf00);
  GPIOA->AFR[1] |= ((uint32_t)0x600);
  //ss
  GPIOB->MODER &= ~(GPIO_MODER_MODER0<<(1*2));
  GPIOB->MODER |= (((uint32_t)GPIO_Mode_OUT)<<(1*2));
  GPIOB->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(1*2));
  GPIOB->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(1*2));
  GPIOB->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)1));
  GPIOB->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)1));
  GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)1*2));
  GPIOB->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(1*2));
  GPIOB->BSRR |= GPIO_Pin_1;  //set chip select to high
  //sck
  GPIOB->MODER &= ~(GPIO_MODER_MODER0<<(0*2));
  GPIOB->MODER |= (((uint32_t)GPIO_Mode_AF)<<(0*2));
  GPIOB->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(0*2));
  GPIOB->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(0*2));
  GPIOB->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)0)); 
  GPIOB->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)0));
  GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)0*2));
  GPIOB->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(0*2));
  //miso
  GPIOA->MODER &= ~(GPIO_MODER_MODER0<<(12*2));
  GPIOA->MODER |= (((uint32_t)GPIO_Mode_AF)<<(12*2));
  GPIOA->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(12*2));
  GPIOA->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(12*2));
  GPIOA->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)12)); 
  GPIOA->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)12));
  GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)12*2));
  GPIOA->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(12*2));
  //mosi
  GPIOA->MODER &= ~(GPIO_MODER_MODER0<<(10*2));
  GPIOA->MODER |= (((uint32_t)GPIO_Mode_AF)<<(10*2));
  GPIOA->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0<<(10*2));
  GPIOA->OSPEEDR |= ((uint32_t)(GPIO_High_Speed)<<(10*2));
  GPIOA->OTYPER &= ~((GPIO_OTYPER_OT_0)<<((uint16_t)10));
  GPIOA->OTYPER |= (uint16_t)(((uint16_t)GPIO_OType_PP)<<((uint16_t)10));
  GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR0<<((uint16_t)10*2));
  GPIOA->PUPDR |= (((uint32_t)GPIO_PuPd_NOPULL)<<(10*2));

  //enable spi peripheral clock 
  RCC->APB2ENR |= (1ul<<20);
  //spi polarity, phase, first data, baud prescale, master, mode
  temp = SPI5->CR1; 
  //clear BIDIMode, BIDIOE, RxONLY, SSM, SSI, LSBFirst, BR, MSTR, CPOL and CPHA bits
  temp &= (uint16_t)0x3040; //CR1_CLEAR_MASK
  temp |= (uint16_t)((uint32_t)SPI_Direction_2Lines_FullDuplex | SPI_Mode_Master |
          SPI_DataSize_8b | SPI_CPOL_Low | SPI_CPHA_1Edge | SPI_NSS_Soft | SPI_NSSInternalSoft_Set |
          SPI_BaudRatePrescaler_2 | SPI_FirstBit_MSB);
  SPI5->CR1 = temp;
  //enable ss output
  //SPI5->CR2 |= (uint16_t)SPI_CR2_SSOE;
  //activate spi mode
  SPI5->I2SCFGR &= (uint16_t)~((uint16_t)SPI_I2SCFGR_I2SMOD);
  //enable spi
  SPI5->CR1 |= SPI_CR1_SPE;
}

//bare send
void SPISend(uint8_t data) {
  SPI5->DR = data;
}
//bare receive
uint8_t SPIReceive(void) {
  return SPI5->DR;
}

//spi xmit with busy wait
uint8_t SPIt(uint8_t data) {
  SPI5->DR = data; //write data for transmit to data register
  while(!(SPI5->SR & SPI_I2S_FLAG_TXE))
    ; //wait until send complete
  while(!(SPI5->SR & SPI_I2S_FLAG_RXNE))
    ; //wait until receive complete
  while(SPI5->SR & SPI_I2S_FLAG_BSY)
    ; //wait until SPI is not busy anymore
  return SPI5->DR; //return received data from SPI data register
}

uint8_t SpiFRAMRead8(uint16_t address) {
  uint8_t data;
 
  //cs low
  GPIOB->BSRR |= (GPIO_Pin_1<<16);
  SPIt(READ);
  SPIt((uint8_t)((address>>8)&0xff));
  SPIt((uint8_t)address);
  data = SPIt(0xff);
  //cs high
  GPIOB->BSRR |= GPIO_Pin_1;
  return (data);
}

void SpiFRAMWrite8(uint16_t address, uint8_t data) {
  //cs low
  GPIOB->BSRR |= (GPIO_Pin_1<<16);
  SPIt(WREN);
  //cs high
  GPIOB->BSRR |= GPIO_Pin_1;
  //cs low
  GPIOB->BSRR |= (GPIO_Pin_1<<16);
  SPIt(WRITE);
  //13-bit address MSB, LSB
  SPIt((uint8_t)((address>>8)&0xff));
  SPIt((uint8_t)address);
  SPIt(data);
  //cs high
  GPIOB->BSRR |= GPIO_Pin_1;
}

int main(void) {
  uint8_t rxbuf[5], txbuf[5] = { 'H', 'e', 'l', 'l', 'o' };
	
  //configure HSI as System Clock
  SystemCoreClockConfigure();                              
  SystemCoreClockUpdate();

  //initialize spi5
  SPIConfigure();

  //configure nucleo led (pa5) pin as output, push-pull, no pull-up/down 
  GPIOA->MODER &= ~((3ul<<2*5));                //clear both mode bits
  GPIOA->MODER |= ((GPIO_Mode_OUT<<2*5));       //set as general purpose output
  GPIOA->OTYPER &= ~((1ul<<5));                 //clear (push/pull)
  GPIOA->OSPEEDR &= ~((3ul<<2*5));              //clear both speed bits
  GPIOA->OSPEEDR |= ((GPIO_Medium_Speed<<2*5)); //set medium speed
  GPIOA->PUPDR &= ~((3ul<<2*5));                //clear both pull up/down status (none)
  //configure nucleo blue button (pa13) pin as input, push-pull with pull-down 
  RCC->AHB1ENR |= (2ul<<1);                     //enable GPIOC clock
  GPIOC->MODER &= ~((3ul<<2*13));               //clear both mode bits
  GPIOC->MODER |= ((GPIO_Mode_IN<<2*13));       //set as input
  GPIOC->OTYPER &= ~((1ul<<13));                //clear (push/pull)
  GPIOC->OSPEEDR &= ~((3ul<<2*13));             //clear both speed bits
  GPIOC->OSPEEDR |= ((GPIO_High_Speed<<2*13));  //set high speed
  GPIOC->PUPDR |= (GPIO_PuPd_DOWN<<2*13);       //set pull down status
  
  //SysTick 1 msec interrupts
  SysTick_Config(SystemCoreClock/1000);                  

  //test it
  while(1) {
    uint8_t i;
		
    while ((GPIOC->IDR & GPIO_Pin_13) != (uint32_t)Bit_RESET)
      ; //wait until button press
    for (i=0; i<5; i++) {
      //send data
      SpiFRAMWrite8((uint16_t)i, txbuf[i]);
      //receive data
      rxbuf[i] = SpiFRAMRead8((uint16_t)i);
    }
    for (i=0; i<5; i++) {
      if (rxbuf[i] != txbuf[i]) {
        //flash led for fail
        GPIOA->BSRR |= GPIO_Pin_5;
        Delay(500);
        GPIOA->BSRR |= (GPIO_Pin_5<<16);
        Delay(500);
      }
    }
  }
}
Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s