Friday, December 17, 2010

STM32F103 SPI (SSI) Bus Code

So,
The docs that come from ST Micro are pretty good as far as listing methods, and all the info is there.  The code examples that come with  the Standard Peripheral Library are nifty, but nowhere are there explanations of how things work.  The code examples all use Interrupts or DMA.  Those are nifty once everything works, but with a new board and chips one needs to be able to test things in a polling fashion.  Also we use RTOS.


First, I have a struct for each SPI device, and use SPI1 for most devices, and SPI2 for a particular device that requires very frequent (6000 per second) access.

typedef struct SSIConfigStruct {
    // Arbitrary name of the device. e.g. "GYROS"
    char name[32];
    xSemaphoreHandle semaphore;
    SPI_InitTypeDef spiInitStruct;
    SPI_TypeDef * spiDevice;
    // The chip enable for the target slave device.
    // SPI chip enables are inverted.
    uint16_t GPIO_Pin;
    GPIO_TypeDef * GPIO_Port;
} xSSIConfig;

Each SPI bus has a lock semaphore to control acccess.

And here are the globals...
xSemaphoreHandle SSILock1;
xSemaphoreHandle SSILockGyros;
xSSIConfigHandle SSIDevices[SSI_DEVICE_COUNT];
// All except gyros.
SPI_InitTypeDef SPI1_InitStructure;
// Just the gyros.
SPI_InitTypeDef SPIGyros_InitStructure;
unsigned char * ssi1Lockee = "Uninitialized";
unsigned char * ssi2Lockee = "Uninitialized";
The Lockee pointers are set when a device gets selected and the lock is acquired so in debugging I know who did what.

So initializing one of the SPI busses is like this...
  ssi1Lockee = "Unused";
  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
                         RCC_APB2Periph_GPIOB |
                           RCC_APB2Periph_AFIO |
                             RCC_APB2Periph_SPI1,
                             ENABLE);
 
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
 
  // The general use SPI bus. SPI1
  vSemaphoreCreateBinary(SSILock1);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  SPI_StructInit(&SPI1_InitStructure);
  SPI1_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI1_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI1_InitStructure.SPI_CPOL = SPI_CPOL_Low;
  SPI1_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
  // of SPI_BaudRatePrescaler_64 results in a clock rate of 1.1 Mhz
  // (Measured with oscilloscope.)
  SPI1_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
  SPI_Init(SPI1 , &SPI1_InitStructure);
  SPI_Cmd(SPI1, ENABLE);
 
  // The 3 gyros on their own private SPI bus. SPI2
  vSemaphoreCreateBinary(SSILockGyros);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  SPI_StructInit(&SPIGyros_InitStructure);
  SPIGyros_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPIGyros_InitStructure.SPI_NSS = SPI_NSS_Soft;
  // of SPI_BaudRatePrescaler_16 results in a clock rate of 2.2 Mhz
  // (Measured with oscilloscope.)
  SPIGyros_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
  SPI_Init(SPI2 , &SPIGyros_InitStructure);
  SPI_Cmd(SPI2, ENABLE);
 
(removed code ... initialize each sub device on the SPI bus ... )
  Notice that you first enable clocking to the IO ports and peripheral devices.  Not having clocks on save power on the chip.  Also, the chip enables are low asserted and hooked to various bits on the GPIOE port.

Initializing a single device is like this...
  // The RESET line on the PIN ASIC. Normally low, toggle high then low to reset
  // and start next sample.  Pin PE7
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOE, &GPIO_InitStructure);
  GPIO_WriteBit(GPIOE, GPIO_Pin_7, Bit_RESET);
 
  SSIDevices[SSI_DEVICE_PNI11096] = &PNI11096SSIConfig;
  PNI11096SSIConfig.GPIO_Port = GPIOE;
  PNI11096SSIConfig.GPIO_Pin = GPIO_Pin_11;
  strncpy_safe(PNI11096SSIConfig.name, "PNIC", 32);
  PNI11096SSIConfig.spiDevice = SPI1;
  PNI11096SSIConfig.semaphore = SSILock1;
  SPI_StructInit(&PNI11096SSIConfig.spiInitStruct);
  PNI11096SSIConfig.spiInitStruct.SPI_DataSize = SPI_DataSize_8b;
  // TODO
  // Max is 1 MHz
  SPI_InitGPIOPin(&PNI11096SSIConfig);
The big secret here is that to change clock rate or between 8 bit and 16 bit requires that you disable the SPI bus, make the change, and enable the bus again.
So the support routines look like this...

// Delay loop is about 6 instructions per loop.
void SSI_Delay(int n)
{
  volatile int SPI_DelayCount;
  for(SPI_DelayCount=0; SPI_DelayCount < n; SPI_DelayCount++)
  {
  }
}
void SPI_InitGPIOPin(xSSIConfigHandle h)
{
  GPIO_InitStructure.GPIO_Pin = h->GPIO_Pin;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOE, &GPIO_InitStructure);
  GPIO_WriteBit(h->GPIO_Port, h->GPIO_Pin, Bit_SET);
}

// Make the device be the current device.
void SSI_SwitchTo(int deviceIdx, unsigned char * lockee)
{
  xSSIConfigHandle h = SSIDevices[deviceIdx];
 
  if(h->spiDevice == SPI1)
  {
    ssi1Lockee = lockee;
  }
  else if(h->spiDevice == SPI2)
  {
    ssi2Lockee = lockee;
  }
  SPI_Cmd(SPI1, DISABLE);
  SPI_DataSizeConfig(h->spiDevice, h->spiInitStruct.SPI_DataSize);
  // TODO - Set click divisor to get correct frequency.
  SPI_Cmd(SPI1, ENABLE);
 
  GPIO_ResetBits(h->GPIO_Port, h->GPIO_Pin);
}

void SSI_UnSwitch(int deviceIdx)
{
  xSSIConfigHandle h = SSIDevices[deviceIdx];
 
  GPIO_SetBits(h->GPIO_Port, h->GPIO_Pin);
  if(h->spiDevice == SPI1)
  {
    ssi1Lockee = "None";
  }
  else if(h->spiDevice == SPI2)
  {
    ssi2Lockee = "None";
  }
}

volatile short ittibits;
// Enables the device, sends data, waits for it to finish sending, gets results.
// Disables the device after the read.
unsigned short SSI_SendDataAndWaitAndDone(int deviceIdx, unsigned short data)
{
  short ret;
  xSSIConfigHandle h = SSIDevices[deviceIdx]; 
  // Clear any latent recieved data just in case. 
 SPI_I2S_ReceiveData (h->spiDevice);
 
  GPIO_ResetBits(h->GPIO_Port, h->GPIO_Pin);
  // 75Mhz / 6 cycles so this is a little under 1 microsecond.
  SSI_Delay(1);
  SPI_I2S_SendData (h->spiDevice, data);
  while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_TXE) == RESET)
  {
    portYIELD();
  }
  while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_RXNE) == RESET)
  {
    portYIELD();
  }
  ret = SPI_I2S_ReceiveData (h->spiDevice);
  // So we can debug the value.
  ittibits = ret;
  GPIO_SetBits(h->GPIO_Port, h->GPIO_Pin);
  SSI_Delay(1);
  return ret;
}

// Enables the device, sends data, waits for it to finish sending, gets results.
// Does NOT disable the device after the read.
unsigned short SSI_SendDataAndWaitAndNotDone(int deviceIdx, unsigned short data)
{
  unsigned short ret;
  xSSIConfigHandle h = SSIDevices[deviceIdx];
  ittibits = data;
 
  SPI_I2S_ReceiveData (h->spiDevice);
 
  GPIO_ResetBits(h->GPIO_Port, h->GPIO_Pin);
  SSI_Delay(1);
  SPI_I2S_SendData (h->spiDevice, data);
  while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_TXE) == RESET)
  {
    portYIELD();
  }
  while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_RXNE) == RESET)
  {
    portYIELD();
  }
  ret = SPI_I2S_ReceiveData (h->spiDevice);
  ittibits = ret;
  return ret;
}

unsigned short SSI_LockSendDataAndWaitAndDone(int deviceIdx, unsigned short data)
{
  xSSIConfigHandle h = SSIDevices[deviceIdx];
  unsigned short ret;
 
  xSemaphoreTake(h->semaphore, portMAX_DELAY);
  {
    SPI_I2S_ReceiveData (h->spiDevice);
  
    GPIO_ResetBits(h->GPIO_Port, h->GPIO_Pin);
    // 75Mhz / 6 cycles so this is a little under 1 microsecond.
    SSI_Delay(1);
    SPI_I2S_SendData (h->spiDevice, data);
    while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_TXE) == RESET)
    {
      portYIELD();
    }
    while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_RXNE) == RESET)
    {
      portYIELD();
    }
    ret = SPI_I2S_ReceiveData (h->spiDevice);
    // So we can debug the value.
    ittibits = ret;
    GPIO_SetBits(h->GPIO_Port, h->GPIO_Pin);
    SSI_Delay(1);
  }
  xSemaphoreGive(h->semaphore);
 
  return ret;
}

// Enables the device, sends data, waits for it to finish sending, gets results.
// Does NOT disable the device after the read.
unsigned short SSI_LockSendDataAndWaitAndNotDone(int deviceIdx, unsigned short data)
{
  xSSIConfigHandle h = SSIDevices[deviceIdx];
  unsigned short ret;
 
  xSemaphoreTake(h->semaphore, portMAX_DELAY);
  {
    SPI_I2S_ReceiveData (h->spiDevice);
  
    GPIO_ResetBits(h->GPIO_Port, h->GPIO_Pin);
    SSI_Delay(1);
    SPI_I2S_SendData (h->spiDevice, data);
    while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_TXE) == RESET)
    {
      portYIELD();
    }
    while (SPI_I2S_GetFlagStatus(h->spiDevice, SPI_I2S_FLAG_RXNE) == RESET)
    {
      portYIELD();
    }
    ret = SPI_I2S_ReceiveData (h->spiDevice);
    ittibits = ret;
  }
  xSemaphoreGive(h->semaphore);
 
  return ret;
}

TF 

No comments: