cancel
Showing results for 
Search instead for 
Did you mean: 

Read Parallel display commands with STM32

JBond.1
Senior

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.

1 ACCEPTED SOLUTION

Accepted Solutions

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.

View solution in original post

14 REPLIES 14

>>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
Up vote any posts that you find helpful, it shows what's working..

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?

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
Up vote any posts that you find helpful, it shows what's working..

You mean like this:

/**
  * @brief This function handles EXTI line 0 and 1 interrupts.
  */
void EXTI0_1_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_1_IRQn 0 */
	gpiocIdr = GPIOC->IDR;
 
  /* USER CODE END EXTI0_1_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
  /* USER CODE BEGIN EXTI0_1_IRQn 1 */
 
  /* USER CODE END EXTI0_1_IRQn 1 */
}

That should be faster? I will try that.

Do you have any specific module in mind for CPLD/FIFO?

Doesnt seem to work...

What if I would setup ex interupt on 4-15 and read

gpiocIdr = EXTI->PR;

Would that return all 4-15 pin values at interupt?

JBond.1
Senior

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?

Any help?

I've written a few replies on this topic, but they have disappeared, and I couldn't find this topic anymore. I hope you have saved them, because I haven't.

You've got a somewhat complicated task to solve, so I am going to break it down into smaller steps.

Setting the AFR looks like right, as long as bits 0-3 are 0 before. It is 0 after reset, so it would work for now. But keep in mind that some peripheral registers might have reset values different from 0, always check the register description in the reference manual.

Set and check the GPIO MODER bits. Check the reference manual again, note the reset state, all bits are 0 which means the pins are set to input, you don't have to change them except for PA0, which has to be set to alternate function mode. So figure out what should go into GPIOA->MODER, and be careful not to change the bits at higher positions corresponding to PA13 and PA14, because that's your debugger connection.

Now you can start with the timer. You will need it in input capture mode, so implement the procedure in the TIM2 functional description / Input capture mode chapter. Leave the input filter bits alone (0), you don't want to filter out a short pulse on WR completely, do you? Start the timer at the end by setting TIM_CR1_CEN, and observe TIM2->CCR1 changing each time there is a pulse on PA0. Try changing the code to detect falling edges instead of rising ones.

... to be continued when the above is working

Indeed something is with this forum, because I was not able to see your previous replies for some time... Any way I have edited and added my code to first post.

As for the GPIOA->MODER, I have observed that its initial value is 0x28000400 (not sure why there is "4" in it) and after that this:

GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER0) | GPIO_MODER_MODER0_1;

it becomes 0x28000402 which is not correct? it should not be 0x28000401 as "1" would represent that its zero pin "PA0"?

GPIOA->AFR[0] becomes 0x00000002

P.S.

I have another STM32 its STM32F103 if it makes this task any easier?