The non-standard 4-wire SPI protocol

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

The non-standard 4-wire SPI protocol

Jump to solution
1,636 Views
evgenik
Contributor IV

Hello. I am starting work with a new project. The LCD with which they chose based on ST7789H2 driver and works through the SPI protocol, but instead of two lines MISO and MOSI, only one is used, which changes from MISO to MOSI. I have not met this before. Are there any configuration examples and driver for the K21F120 MCU with this type of SPI?

Labels (1)
0 Kudos
1 Solution
1,449 Views
mjbcswitzerland
Specialist V

Hi Evgeni

Although the PIT may be able to interrupt at a 60MHz rate no Kinetis parts will be able to keep up with it due to the Cortex interrupt overhead, resulting in the much reduced 3MHz that you are seeing (it is only managing to handle about every 20th PIT interrupt!).

If you would like high speed SPI you must use the SPI interface since timer interrupt controlled bit banging has a high overhead that results in much lower speeds, with the processor working as hard as it can. If you do bit banging without timer control you will also find an improvement in performance since the interrupt overhead will not be present.

Regards

Mark

[uTasker project developer for Kinetis and i.MX RT]

View solution in original post

3 Replies
1,449 Views
mjbcswitzerland
Specialist V

Hi

You may be able to fulfill the requirement by connecting the MISO and MISO lines together and configuring the MISO to open drain. It will need a pull-up of maybe 1k (not the internal one) to ensure that a fairly high bus speed is still possible.

Regards

Mark

0 Kudos
1,449 Views
evgenik
Contributor IV

Hi Mark.

I wrote GPIO driver that simulates this communication the following problems appeared:

  1. my driver works with PIT timer. By datasheet PIT timer in MK21 can work with 60MHz frequency. But instead this frequency I see about 3MHz maximum. On timer interrupt, I do toggle to GPIO for test timer frequency and see 1.88MHz frequency (1.88*2=3.76MHz). Here this timer configuration to 60MHz:

// SIM_SCGC6: PIT=1 
SIM_SCGC6 |= SIM_SCGC6_PIT_MASK; 
// Enable device clock  
PIT_MCR = (uint32)0x00UL; 

// Clear control register 
PIT_TCTRL0 = (uint32)0x00UL; 

// Clear timer flag register 
PIT_TFLG0 = PIT_TFLG_TIF_MASK; 
// Set up load register PIT_LDVAL0: 60MHz
PIT_LDVAL0 = PIT_LDVAL_TSV(PLAY_TIMER_PERIOD); 
NVICIP48 = NVIC_IP_PRI48(0x00);

// Set up control register for Timer 0
PIT_TCTRL0 = (PIT_TCTRL_TEN_MASK | PIT_TCTRL_TIE_MASK);
PIT_TCTRL0 = (uint32_t)((PIT_TCTRL0 & (uint32_t)~(uint32_t)(PIT_TCTRL_TEN_MASK | 0xFFFFFFF8U)) | 
(uint32_t)(PIT_TCTRL_TIE_MASK));
// Clear interrupt flag 
PIT_ClearInterruptFlag(PIT_BASE_PTR, PIT_CHANNEL_0); 
// Enable interrupts
Enable_IRQ(PIT0_IRQn); 

To prepare command I use a function for every command.

typedef enum _SEND_Type
{
   SEND_COMMAND,       // Send Command
   SEND_DATA_PARAM    // Send Data or Parameter
} SEND_Type;

typedef enum _DATA_Type
{
   DATA_CONTINUED,       // Send continued data
   DATA_SIMPLE_COLOR    // Send simple color
} DATA_Type;

typedef struct
{
   uint8_t Command;       // Command for send to LCD
   SEND_Type WhatSend;    // Send Command or Data
   DATA_Type WhatData;    // Type of sent data
   uint32_t DataLen;      // LCD *data len
   uint8_t *Data;         // Pointer to data that contain LCD parameters
}LCD_SPI_PACKET;

For example,

/******************************************************************************
* Function Name: LCD_TFT_RAMWR_2C (Command 0x2C)
* Description: Memory write
* Parameters: none
* Return: none
* Build_Date: 11/01/2020
* Notes: --
******************************************************************************/
void LCD_TFT_RAMWR_2C(DATA_Type DataType, uint32_t DataLen, uint8_t *Data)
{
   LCD_SPI.WhatSend = SEND_COMMAND;
   LCD_SPI.Command = TFT_RAMWR;
   LCD_SPI.WhatData = DataType;

   if(DataType == DATA_SIMPLE_COLOR)
   {
      LCD_SPI.DataLen = DataLen * 2;
      TFT_RAMWR_SimpleData[1] = Data[0];
      TFT_RAMWR_SimpleData[0] = Data[1];
      LCD_SPI.Data = TFT_RAMWR_SimpleData;
   }
   else
   {
      LCD_SPI.DataLen = DataLen;
      LCD_SPI.Data = Data;
   }
   // Start timer for send Command/Data
   TIMER_Enable(MAIN_TIMER);
   while(MainTimer_Enabled);
}

// Global array

uint8_t TFT_FRCTRL1_Data[] = {0x54, 0x3F};

/******************************************************************************
* Function Name: LCD_TFT_FRCTRL1_B3 (0xB3)
* Description: Frame Rate Control 1
* Parameters: none
* Return: none
* Build_Date: 11/01/2020
* Notes: --
******************************************************************************/
void LCD_TFT_FRCTRL1_B3(void)
{
   LCD_SPI.WhatSend = SEND_COMMAND;
   LCD_SPI.Command = TFT_FRCTRL1;
   LCD_SPI.DataLen = 2;
   LCD_SPI.Data = TFT_FRCTRL1_Data;
   // Start timer for send Command/Data
   TIMER_Enable(MAIN_TIMER);
   while(MainTimer_Enabled);

}

On timer event, I change CLK and on CLK low state call the next function:

void LCD_SPI_Send_Command(void)
{
static uint8_t cmd_state = 0;
static uint8_t SendVal = 0;
static uint32_t DataIndx = 0;
static uint8_t bit = 7;

switch(cmd_state)
{
    case 0:
       // CS='0' - Chip select enable
       LCD_SPI_CS_ENABLE;
       // 30ns delay
       __NOP();
       // DC=’0’- command selection pin in parallel interface
       LCD_SPI_DC_COMMAND;
       // 30ns delay
       __NOP();
       // Start to send Command value
       // Set MSB bit on SDA
       if((LCD_SPI.Command&0x80) == 0U)
       {
           SDA_PIN_SET_LOW;
       }
       else
       {
           SDA_PIN_SET_HIGH;
       }
       // Shift to the next bit at the next rising edge of SCL Shift<<1
       SendVal = LCD_SPI.Command<<1; 
       bit--;
       cmd_state++;
    break;
    case 1:
       // Continue to send Command value
       // Set MSB bit on SDA
      if((SendVal&0x80) == 0U)
      {
         SDA_PIN_SET_LOW;
      }
      else
      {
         SDA_PIN_SET_HIGH;
      }

      if(bit > 0)
      {
         bit--;
         // Shift to the next bit at the next rising edge of SCL Shift<<1
         SendVal <<= 1; 
      }
      else
      {
         bit = 7;

         if(LCD_SPI.DataLen > 0)
         {
            cmd_state++;
         }
         else
         {
            cmd_state = 4;
         }
      }
   break;
   case 2:
      LCD_SPI_DC_DATA_PARAM;
      // 30ns delay
      __NOP();
      // Start to send Data
      SendVal = LCD_SPI.Data[DataIndx];
      // Set MSB bit on SDA
      if((LCD_SPI.Data[DataIndx]&0x80) == 0U)
      {
         SDA_PIN_SET_LOW;
      }
      else
      {
         SDA_PIN_SET_HIGH;
      }
      // Shift to the next bit at the next rising edge of SCL Shift<<1
      SendVal = LCD_SPI.Data[DataIndx]<<1; 
      bit--;
      cmd_state++; 
   break;
   case 3:
      // Continue send Data values
      // Set MSB bit on SDA
      if((SendVal&0x80) == 0U)
      {
         SDA_PIN_SET_LOW;
      }
      else
      {
         SDA_PIN_SET_HIGH;
      }
      // Shift to the next bit at the next rising edge of SCL Shift<<1
      SendVal <<= 1; 
      if(bit > 0)
      {
         bit--;
      }
      else
      {
         bit = 7;

         if(--LCD_SPI.DataLen > 0)
         {
            if(LCD_SPI.WhatData == DATA_CONTINUED)
            {
               SendVal = LCD_SPI.Data[++DataIndx];
            }
            else if(LCD_SPI.WhatData == DATA_SIMPLE_COLOR)
            {
               SendVal = LCD_SPI.Data[DataIndx];
               DataIndx ^= 1;
            }
         }
         else
         {
            cmd_state++;
         }
      } 
   break;
   case 4: 
      // CS='1' - Chip select disable 
      LCD_SPI_CS_DISABLE;
      cmd_state++;
   break;
   case 5:
      TIMER_Disable(MAIN_TIMER);
      cmd_state = 0;
      DataIndx = 0;
   break;
}
}

As a result, I can config LCD and fill any color, but only once. After this no reaction. I tried different configurations but nothing helps. LCD does not respond next time.

LCD configuration:

Version 1

LCD_TFT_SWRESET_01(); 
Delay_us(150000);
LCD_TFT_SLPOUT_11();   
Delay_us(120000);
LCD_TFT_COLMOD_3A();    
LCD_TFT_MADCTL_36();    
//LCD_TFT_INVON_21();    
LCD_TFT_INVOFF_20();
LCD_TFT_NORON_13();    
LCD_TFT_DISPON_29();    
Delay_us(150000); 
ST7789_FillScreen(BLUE); 
Version 2
LCD_TFT_SWRESET_01(); 
Delay_us(120000);
LCD_TFT_SLPOUT_11(); 
Delay_us(120000);
LCD_TFT_GAMSET_26();   // Data 0x01
LCD_TFT_TEON_35();
LCD_TFT_MADCTL_36();   // Data 0x08 
LCD_TFT_INVON_21();
LCD_TFT_COLMOD_3A();   // Data 0x55
LCD_TFT_FRCTRL1_B3();   // Data 0x54, 0x3F   
LCD_TFT_LCMCTRL_C0();   // Data 0x01

LCD_TFT_IDSET_C1();      // Data 0x06
LCD_TFT_VDVS_C4();      // Data 0x25, 0x00
LCD_TFT_VMCTR1_C5();   // Data 0x44, 0x64
LCD_TFT_DISPON_29();
ST7789_FillScreen(BLUE);

0 Kudos
1,450 Views
mjbcswitzerland
Specialist V

Hi Evgeni

Although the PIT may be able to interrupt at a 60MHz rate no Kinetis parts will be able to keep up with it due to the Cortex interrupt overhead, resulting in the much reduced 3MHz that you are seeing (it is only managing to handle about every 20th PIT interrupt!).

If you would like high speed SPI you must use the SPI interface since timer interrupt controlled bit banging has a high overhead that results in much lower speeds, with the processor working as hard as it can. If you do bit banging without timer control you will also find an improvement in performance since the interrupt overhead will not be present.

Regards

Mark

[uTasker project developer for Kinetis and i.MX RT]