emulating a OLED display on a VGA and/or a LCD monitor

Post here all questions related to LibMaple core if you can't find a relevant section!
Y@@J
Posts: 46
Joined: Mon Sep 28, 2020 8:51 pm

Re: Looking for a suitable SPI slave example/tutorial

Post by Y@@J »

Some news...
As at this time I can't understand a word of the STM32 SPI slave, I test with a Nano and the library TVOut. I get images, it seems reactive, but the images are garbled. I just filled the TVOut buffer directly with the SPI bus data, without knowing how it is organized (for example is it interlaced or whatever). But at least it is a proof of concept. Also, this library uses ports that are on PORTB, where SPI ports are...
Reading the sources from the links and the sources from Roger's Core, I can't figure how to deal with STM32 SPI.

I think it is not possible on a Nano, because the image I get on the terminal at the same time is also garbled. The library does port and clock manipulation. Or the lib manipulates the buffer ; and there's not enough memory for double buffering.

I've to move on : will be tested with another lib on a Mega or a Due.

[EDIT] : VGA libs for Arduino use the SPI shift registers ; at least for the 328 and 2560 -> dead end.
DSC_7843.JPG
DSC_7843.JPG (91.5 KiB) Viewed 850 times
Y@@J
Posts: 46
Joined: Mon Sep 28, 2020 8:51 pm

Re: Looking for a suitable SPI slave example/tutorial

Post by Y@@J »

Huge progress !

Copied the code from this link : http://stm32duinoforum.com/forum/viewto ... rt_10.html

Removed Tx related code, adapted the code made for the Nano, and got this (crappy image, the forum always saying : too large)
CaptureSTM32.jpg
CaptureSTM32.jpg (28.67 KiB) Viewed 831 times
But after a SPI frame capture is completed and transfered to memory, no more interrupts are triggered [EDIT] interrupts are triggered, but I can't figure how to re-enable DMA transfers (or at least get the data to go to my buffer...). As usual with STM32, dead links all over the place...
stevestrong
Posts: 454
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: Looking for a suitable SPI slave example/tutorial

Post by stevestrong »

That forum is a recovered version of the original stm32duino forum. The recovery had to be done manually and therefore is not complete, hence many links are dead or invalid. You are eventually better served using the wayback machine (see this post).

Regarding the SPI you should basically re-init the DMA for each new frame when dma_transfer_complete.
Y@@J
Posts: 46
Joined: Mon Sep 28, 2020 8:51 pm

Re: Looking for a suitable SPI slave example/tutorial

Post by Y@@J »

Yes, I'm aware of the story (Roger's Git and EEVBlogs), and of the work for the old forum with a website copier.

Resetting DMA after each new frame is what I've been trying to do since yesterday.
I'm out of ideas. It's like dma_get_irq_cause() doesn't reset the flags and the buffer. Tried everything I read or I've been thinking of, even calling the functions that inialize DMA and SPI. Even setup() itself.

This has no effect :

Code: Select all

void loop()
{
	if (dma_transfer_complete)
	{
		if (rebuidBitmap())
			printFrame();

		dma_transfer_complete = false;
		setupSPI();
		setupDMA();

		// TEST
		dma_clear_isr_bits(DMA1, DMA_CH2);
	}

	delay(500);
}
If I can't get this to work, there's a solution to my problem :
AVR SPI slave (working just fine) -> parallel transfer to another AVR or STM32 that does the video output (8 data pins + 1 or 2 control pins). No need for undocumented functions, and no need to learn Cube.
User avatar
Bakisha
Posts: 73
Joined: Fri Dec 20, 2019 6:50 pm
Answers: 4
Contact:

Re: Looking for a suitable SPI slave example/tutorial

Post by Bakisha »

As i recall from few experiments with DMA, when DMA transfer is complete, new transfer is initiated by setting new value to CNDTR register. But it can be done only when DMA_CCR_EN bit is disabled. Otherwise, it is read-only register that hold current value of DMA requests to be yet served. Once complete, it hold 0.
So, it should look like this:
-disable DMA_CCR_EN bit
-write buffer length to CNDTR register
-enable DMA_CCR_EN bit

I can't say the syntax how to write values to registers in roger's core (i was doing it on ST HAL core) and without interrupts, so there might be some other flags that need to reset that i am not aware...
Y@@J
Posts: 46
Joined: Mon Sep 28, 2020 8:51 pm

Re: Looking for a suitable SPI slave example/tutorial

Post by Y@@J »

Exactly ! The interrupt is triggered, but the buffer is not updated, it keeps the previous data. Verified filling it with zeros after first update. [EDIT] I mean : filled with the previous data.
The simplest (and most efficient) way to do what I need would be to get one interrupt for each and every byte, with no FIFO and DMA. Because there's data processing that is better and easier to do on the fly (and I already wrote a working ISR).
Reading some documentation, it seems it is not possible with the STM32, the hardware being designed to work this way (?)

Will have a look about how to access the registers.

But currently started to code for 2 Nanos. Want to see Marlin on VGA and interract with it ! Not even sure it will work. Will maybe have to transfer 4bits at once only. No luck these days.
It's all about frustration. One week and no usable results. Still no proof of concept. When it'll be done, will come back to the BluePill. The whole design of my "universal 3D printer controller" depends on that (one display, everything inside, including the OctoPrint server).
Y@@J
Posts: 46
Joined: Mon Sep 28, 2020 8:51 pm

Re: Looking for a suitable SPI slave example/tutorial

Post by Y@@J »

@Bakisha : the keywords you gave returned little information, and searching the sources on the HHD nearly nothing. I found this function, but no effect : dma_clear_isr_bits(DMA #, DMA channel #)

Something interesting I can't interpret :

I made mistake, with a weird effect...
I doubled the buffer size value in the dma tube config, without doubling the buffer size itself...
And I got something very interesting : a living "ascii art" of the Marlin UI. In fact, I know this distortion : the real time data are there (including the command bytes I normally remove).

My source file declares these global variables :
- 1 - the Rx buffer (1044 bytes)
- 2 - two volatile booleans (8 bytes)
- 3 - then the bitmap I fill with the buffer data after some processing

Looks like a living page : it seems a 2nd page of the Rx buffer overflows on my bitmap. I read about 2 pages circular buffer, but also read it was for F104, not F103. So I didn't care...

In the declaration of the DMA tube, there's the "CIRC_BUFFER" attribute. Nowhere in the code I use I detected a double sized buffer, but the attribute is there. Code if from stevestrong or helped by stevestrong. Weird. He's one of the SPI devs for the Roger's Core (with Roger Clarke)...
I'm missing something, but at worst I should be able to create an anti-bug bug . Or declare a 2x larger Rx Buffer, and read the 2nd part.

The more I think of it, the more I think the problem has a quick and nearly clean solution : SPI+DMA get the data on the first page, then constantly updates the second one as long as the circular buffer pointer is not incremented (the magic bits not set). It works as expected. As I don't really need a circ. buffer (1KB every 100ms @1 MHz maximum is nothing), I can use these data. Oh, and obviously, removing the CIRC_BUFFER attribute has no effect.

See tomorrow, 3:45 AM, sleepy. ZZZZZZZZ........
CaptureSTM32-2.JPG
CaptureSTM32-2.JPG (43.24 KiB) Viewed 772 times
User avatar
Bakisha
Posts: 73
Joined: Fri Dec 20, 2019 6:50 pm
Answers: 4
Contact:

Re: Looking for a suitable SPI slave example/tutorial

Post by Bakisha »

I successfully establish a connection between two blupills, one is set as master to send data to second bluepill that act as slave.
Master code:

Code: Select all

#include <SPI.h>

#define SPI1_NSS_PIN PA4    //SPI_1 Chip Select pin is PA4. You can change it to the STM32 pin you want.

uint8_t data1;

void setup() {

  // Setup SPI 1
  SPI.begin(); //Initialize the SPI_1 port.
  SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
  SPI.setDataMode(SPI_MODE0); //Set the  SPI_2 data mode 0
  SPI.setClockDivider(SPI_CLOCK_DIV128);      // Slow speed (72 / 16 = 4.5 MHz SPI_1 speed)
  pinMode(SPI1_NSS_PIN, OUTPUT);

}

void loop() {
  data1 = ((data1 + 1) & 0xff);
  sendSPI();
  delay(1);    //Delay 1 milisecond.
}

void sendSPI()
{
  digitalWrite(SPI1_NSS_PIN, LOW); // manually take CSN low for SPI_1 transmission
  SPI.transfer(data1); //Send the HEX data 0x55 over SPI-1 port and store the received byte to the <data> variable.
  digitalWrite(SPI1_NSS_PIN, HIGH); // manually take CSN high between spi transmissions
}

Slave code:

Code: Select all


#include <SPI.h>
#include <libmaple/dma.h>
#include <libmaple/spi.h>
//...
///*****************************************************************************/
SPIClass SPI_1(1);
///*****************************************************************************/
void SPI_setup(void)
{
  // use SPI 1 for recording
  SPI_1.beginTransactionSlave(SPISettings(18000000, MSBFIRST, SPI_MODE0, DATA_SIZE_8BIT));
  spi_rx_dma_enable(SPI_1.dev()); // enable SPI Rx DMA
}
///*****************************************************************************/
#define RECORDS_PER_BUFFER (256)
volatile uint8_t data_buffer[RECORDS_PER_BUFFER];
///*****************************************************************************/
void DMA_Setup(void)
{
  dma_init(DMA1);  // init DMA clock domain
  // DMA setup transfer for SPI1 Rx
  dma_channel dma_ch = DMA_CH2; // SPI1 Rx DMA channel is channel 2, see reference manual 13.3.7.
  dma_disable(DMA1, dma_ch);  // Disable the DMA tube.
  dma_setup_transfer(DMA1, dma_ch,
                     &((SPI_1.dev())->regs->DR), DMA_SIZE_8BITS,
                     data_buffer, DMA_SIZE_8BITS,
                     (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_HALF_TRNS  | DMA_TRNS_CMPLT)); // flags
  dma_set_num_transfers(DMA1, dma_ch, RECORDS_PER_BUFFER);
  dma_set_priority(DMA1, dma_ch, DMA_PRIORITY_VERY_HIGH);
  dma_attach_interrupt(DMA1, dma_ch, DMA_Rx_irq); // attach an interrupt handler.
  dma_enable(DMA1, dma_ch); // Enable the DMA tube. It will now begin serving requests.
}
//*****************************************************************************/
// variables
// set by HW when a complete DMA transfer was finished.
volatile uint8_t dma_irq_full_complete = 0;
// set by HW when a DMA transfer is at its half.
volatile uint8_t dma_irq_half_complete = 0;
//*****************************************************************************/
// This is our DMA interrupt handler.
//*****************************************************************************/
void DMA_Rx_irq(void)
{
  uint32_t dma_isr = dma_get_isr_bits(DMA1, DMA_CH2);

  if (dma_isr & DMA_ISR_HTIF1) {
    dma_irq_half_complete = 1; // signal for the main level
    digitalWrite(PB12, HIGH);
  }

  if (dma_isr & DMA_ISR_TCIF1) {
    dma_irq_full_complete = 1; // signal for the main level
    digitalWrite(PB13, HIGH);

  }
  dma_clear_isr_bits(DMA1, DMA_CH2);
}

void setup() {
  Serial1.begin(115200);
  delay(2000);
  pinMode(PB12, OUTPUT);
  pinMode(PB13, OUTPUT);

  SPI_setup();
  DMA_Setup();
}

void loop() {


  if (dma_irq_half_complete == 1) {
    digitalWrite(PB12, LOW);
    dma_irq_half_complete = 0;
    Serial1.println("first half of the buffer:");
    for (uint32_t i = 0; i < RECORDS_PER_BUFFER / 2; i++) {
      Serial1.print(data_buffer[i], HEX);
      Serial1.print(",");
    }
    Serial1.println("");
  }

  if (dma_irq_full_complete == 1) {
    digitalWrite(PB13, LOW);
    dma_irq_full_complete = 0;
    Serial1.println("second half of the buffer:");
    for (uint32_t i = RECORDS_PER_BUFFER / 2; i < RECORDS_PER_BUFFER; i++) {
      Serial1.print(data_buffer[i], HEX);
      Serial1.print(",");
    }
    Serial1.println("");
  }
}
All data is as expected on serial and on logic analyzer...

Reason for circular buffer, half transfer and full transfer is because slave don't know beginning and ending, and it must not have gaps in receiving data.

I don't know speed, timeframe and payload of your received SPI data, so i can't say how to handle stuff at half/full buffer.
stevestrong
Posts: 454
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: Looking for a suitable SPI slave example/tutorial

Post by stevestrong »

[mention]Bakisha[/mention], that is a perfect example how to use SPI.

I would only make two small suggestions:

1. Instead of using 4 lines to begin the master, you could use only one:

Code: Select all

SPI.beginTransaction(SPISettings(4500000, MSBFIRST, SPI_MODE0)); // use desired Hz instead of clock divider
2. You can also send a whole buffer using

Code: Select all

uint8_t data_buffer[RECORDS_PER_BUFFER ];
...
SPI.transfer(data_buffer, RECORDS_PER_BUFFER);
Y@@J
Posts: 46
Joined: Mon Sep 28, 2020 8:51 pm

Re: Looking for a suitable SPI slave example/tutorial

Post by Y@@J »

I found why my code wasn't working... It has nothing to do with SPI/DMA, it had to do with my decoding function (conversion to bitmap). For some reason I can't explain, at some point I put this at the begining of my decoding function :

Code: Select all

	
	if (frame_complete)
		return;
FACEPALM FACEPALM FACEPALM AGAIN !

Lost hours and hours because of these two stupid lines !!!

Therefore only incomplete frames were decoded (half ones, randomly ? the display was sometime a bit jumpy...).

Why did I put this here ? No idea ! I probably wanted to write "!frame_complete". As I was searching about how the SPI library had to be used, I didn't pay attention to the decoding part : it was supposed to work, as I could see the ASCCII art in the terminal !

At the moment, I decode only when tranfer is complete, according to several examples I found on the web.

@ Bakisha : many thanks for your code ! Now I will experiment with VGA. I will play with your code after I get something on the VGA display, and only if I don't get a steady image (I fear it will be jumpy).

The data I get are made of bursts of 1048 bytes (=1024 for the pixels + 24 command bytes). I will soon post screen copies of the logic analyzer. Data are very simple, fast (CLK = 1MHz), and frames are separated from each others. 1 second when idle on the info screen, faster when coordinates are to be displayed, or while the user plays with the menus and the encoder.

Will also post code, when it will be cleaned up.
Post Reply

Return to “General discussion”