Skip to main content
JBond.1
Senior
March 19, 2020
Solved

Read Parallel display commands with STM32

  • March 19, 2020
  • 14 replies
  • 4316 views

Hi, is it possible to connect STM32 instead of display and read its pins (A0, RD, WR, D0, D1, D2, D3, D4, D5, D6, D7) to know what device is sending and maybe replace original display with different display?

For example I would need to detect that WR pin went LOW and read all pins values at that moment in time. WR is LOW only for 187 nanoseconds. So is there any way to read this fast with STM32? How does display read that data so fast?

I have attached display commands which I have read with logic analyzer.

    This topic has been closed for replies.
    Best answer by berendi

    When you are not sure, check the reference manual.

    Check the description of the GPIO MODER register. There are 2 bits for each pin.

    MODERy[1:0]: Port x configuration bits (y = 0..15)

    These bits are written by software to configure the I/O mode.

    00: Input mode (reset state)

    01: General purpose output mode

    10: Alternate function mode

    11: Analog mode

    You would like to map PA0 to the timer, so you should set the pin to Alternate function mode, put binary 0b10, i.e. 2 to the bits corresponding to PA0.

    Timers and DMA are almost identical between the STM32F0 and STM32F1 series. GPIO alternate modes are handled differently, but you have already done that part, so you would gain nothing with the STM32F103.

    14 replies

    Tesla DeLorean
    Guru
    March 19, 2020

    >>How does display read that data so fast?

    Got logic gates and does it in HW, not SW perhaps?

    You could perhaps use latches so your response to an EXTI are less critical.

    For the write side you could perhaps use the WR as a TIMx_CHx input, that acts as a DMA trigger, and that reads all the pins on a GPIO bank via GPIOx->IDR ?

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    JBond.1
    JBond.1Author
    Senior
    March 19, 2020

    I tried to do something like this:

    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
    	gpioIn = GPIO_IN_Port->IDR;
    	
    	gpioWr = GPIO_Pin;
    	gpioRd = gpioIn & GPIO_RD_PIN_MASK;
     gpioA0 = gpioIn & GPIO_A0_PIN_MASK;
    	gpioData = gpioIn & GPIO_DATA_PIN_MASK;
     
     DISP_WriteData(gpioWr, gpioRd, gpioA0, gpioData);
    }

    But "gpioIn" seems to get "bad" It reads it too late or too soon, so its not exactly synchronized with when I get interrupt for WR, at least that's my assumption.

    Or am I doing something wrong?

    Is there any other way to acheve my goal? Like getting any other cheap HW module?

    Tesla DeLorean
    Guru
    March 19, 2020

    Well wouldn't be using a callback, that's going to add even more latency into the mix.

    You want to read the register as it enters the IRQHandler, and that's still going to have 12+ cycles.

    Ideally you'd latch all the critical data externally at the WR edge where it is valid.

    Perhaps a cheap CPLD? Or FIFO buffer?

    Read is going to be really difficult to meet the timing deadlines in SW.

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    JBond.1
    JBond.1Author
    Senior
    March 28, 2020

    Code:

    #define bufferNumberOfElements 2
    uint16_t buffer[bufferNumberOfElements];
     
    void APP_Init(void)
    {
     // Enable GPIOAEN, GPIOBEN, GPIOCEN, DMA1EN and TIM2EN bits in RCC
     RCC->AHBENR |= ( RCC_AHBENR_GPIOAEN |
     RCC_AHBENR_GPIOBEN |
     RCC_AHBENR_GPIOCEN |
     RCC_AHBENR_DMA1EN );
     RCC->APB1ENR |= ( RCC_APB1ENR_TIM2EN );
     RCC->APB1ENR; // short delay might be necessary
    	
     // Configure the GPIO pins as input (GPIOC pins)
     GPIO_InitTypeDef GPIO_InitStruct = {0};
     GPIO_InitStruct.Pin = GPIO_IN_CS1_Pin|GPIO_IN_CS2_Pin|GPIO_IN_RD_Pin
     |GPIO_IN_A0_Pin|GPIO_IN_D0_Pin|GPIO_IN_D1_Pin|GPIO_IN_D2_Pin 
     |GPIO_IN_D3_Pin|GPIO_IN_D4_Pin|GPIO_IN_D5_Pin|GPIO_IN_D6_Pin 
     |GPIO_IN_D7_Pin;
     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
     GPIO_InitStruct.Pull = GPIO_NOPULL;
     HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    	
     // and the WR pin (PA0) as alternate function.
     // mapping PA0 to TIM2_CH1
     // AF number on your MCU datasheet:
     // Pin name AF0 AF1 AF2 ...
     // PA0 - USART2_CTS TIM2_CH1_ETR ...
     uint8_t AF_number = 0x2;
     GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER0) | GPIO_MODER_MODER0_1;
     GPIOA->AFR[0] = (GPIOA->AFR[0] & ~GPIO_AFRL_AFSEL0) | (AF_number << GPIO_AFRL_AFSEL0_Pos);
    	
     // Find TIM2_CH1 in the DMA request mapping table in the reference manual. This deterimens the channel to be used.
     // Table 31. Summary of the DMA1 requests for each channel on STM32F09x devices
     // CxS [3:0] Channel 5
     // 0000 TIM2_CH1 
     // Channel 5?
    	
     // Assign the address of the memory buffer (array of uint16_t) to the CMAR register
     DMA1_Channel5->CMAR = (uint32_t)&buffer[0];
    	
     // Assign the address of GPIOC->IDR to the CPAR register
     DMA1_Channel5->CPAR = (uint32_t)&GPIOC->IDR;
    	
     // Put the number of elements (not the number of bytes) into CNDTR
     DMA1_Channel5->CNDTR = bufferNumberOfElements;
    	
     // Set the DMA Channel Configuration Register (CCR) to transfer 16 bits both 
     // on the memory and the peripheral side, memory increment mode, and enable the channel.
     // If you want the capture to run forever in circular mode, set the CIRC bit as well. 
     // Enable half-transfer and transfer-complete interrupt as needed by the processing,
     DMA1_Channel5->CCR = 
    		DMA_CCR_MSIZE_0 |
    		DMA_CCR_PSIZE_0 |
    		DMA_CCR_MINC |
    		DMA_CCR_EN |
    		DMA_CCR_CIRC |
    		DMA_CCR_TCIE |
    		DMA_CCR_HTIE;
    		
     // in this case enable the DMA channel interrupt in NVIC too.
     HAL_NVIC_SetPriority(DMA1_Ch4_7_DMA2_Ch3_5_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ(DMA1_Ch4_7_DMA2_Ch3_5_IRQn);
    		
     // The digital filter is probably not needed, so set the IC1F bits in CCMR1 to 0b0000
     TIM2->CCMR1 &= ~TIM_CCMR1_IC1F;
     // To trigger on the falling edge of WR, set the CC1P bit in CCER to 1.
     TIM2->CCER |= TIM_CCER_CC1P; 
     // Enable channel DMA request, but no interrupt in DIER (only TIM_DIER_CC1DE)
     TIM2->DIER |= TIM_DIER_CC1DE;
     // Enable the timer by setting TIM_CR1_CEN
     TIM2->CR1 |= TIM_CR1_CEN;
    	
     while(1)
     {
     }
    }
     
    uint16_t a;
    uint16_t b;
     
    void DMA1_Ch4_7_DMA2_Ch3_5_IRQHandler(void)
    {
     a = buffer[0]; //read buffer values?
     b = buffer[1]; //read buffer values?
    }

    I did read the reference manual or at least tried to, but its not really understandable easily. Also I find many PDFs and not even sure if I am looking at correct table.

    Is it correct table and I need AF2?

    You mentioned TIM2_CH1, but all I can find is TIM2_CH1_ETR?

    (stm32f091cc.pdf)0693W000000UvVfQAK.png

    As I understand from reading you post more times the problem is in this line:

    GPIOA->AFR[0] = (GPIOA->AFR[0] & ~GPIO_AFRL_AFSEL0) | (1u << GPIO_AFRL_AFSEL0_Pos);
    //Note that AFRL corresponds to AFR[0], and AFRH to AFR[1].

    So I tried to think how to fix it:

    /*
    Select AF2 on PA0 in AFRL for TIM2_CH1_ETR
    AF2=0x02
    PA0=GPIO_AFRL_AFRL0_Pos
    AFRL=AFR[0] (because if want to use 0-7 PINS)?
    AFRH=AFR[1] (because if want to use 8-15 PINS)?
    */
    GPIOA->AFR[0] |= 0x02 << GPIO_AFRL_AFRL0_Pos;

    Is it correct?

    JBond.1
    JBond.1Author
    Senior
    March 31, 2020

    Any help?