Use this forum to chat about hardware specific topics for the ESP8266 (peripherals, memory, clocks, JTAG, programming)

User avatar
By RFZ
#68280 TX only is indeed no problem, since I want to use it as output...
However, UART is HIGH on idle, which is not okay for my LED signal...

However, I now want to concentrate on the Serial Data and RCK signal first.
I want to use SPI to send the 36+1 bytes of serial data per multiplexed row and the hardware CS pin of the SPI interface (GPIO 15) as RCK signal. Since CS is active low for SPI it should give me a rising edge on RCK after serial data is transferred.

What I don't know is... How do I send the 36+1 bytes of data in a non blocking way?
By investigating the spi.cpp of the arduino library I found that I will have to write my data into the registers SPI1W0 to SPI1W15. It also looks like SPI1CMD |= SPIBUSY starts the transfer... However, only one byte gets transferred. I haven't found a register to write the fifo size into...

Code: Select all#include <SPI.h>

void setup()
{
   SPI.begin();
   SPI.setHwCs(true); // Enable Select ActiveLow on D8 / IO15 / CS / HSPI_CS used for RCK
   SPI.setFrequency(1e6);
   pinMode(D0, OUTPUT);
}
uint8_t buf[5];

void loop()
{
   digitalWrite(D0, HIGH);
   //SPI.writePattern(buf, 4, 3);
   spiSendNoBlock(); // non blocking SPI test
   digitalWrite(D0, LOW);
   delay(100);
}

void spiSendNoBlock() {
   // see https://github.com/esp8266/Arduino/blob/40c159fcf5c5ecf9edec8e7ada49c82ece19e6d0/libraries/SPI/SPI.cpp#L421
   uint32_t buffer[16]; // 16x32bit Buffer 4 HW FIFO
   uint8_t *bufferPtr = (uint8_t *)&buffer; // 64x8bit Buffer pointer
   volatile uint32_t * fifoPtr = &SPI1W0; // SPI1W0 - SPI1W15 32bit FIFO Buffer registers

   for(uint8_t i=0; i<64; i++)   bufferPtr[i] = i+1; // Fill buffer with dummy data 0-63

   // fill FIFO with Buffer data
   for (uint8_t i = 0; i<16; i++) {
      *fifoPtr = buffer[i];
      fifoPtr++;
   }

   //SPI1U = SPIUMOSI | SPIUSSE; // removes delay on CS before/after SPI Clock
   SPI1CMD |= SPIBUSY; // Sends first byte from FIFO
}
Last edited by RFZ on Sun Jul 30, 2017 1:44 pm, edited 1 time in total.
User avatar
By RFZ
#68283 Okay, got that... SPI1U1 contains the MOSI data length at bits SPILMOSI ... It's a 9 bit wide value representing the amount of bits to send, minus one.

Code: Select allvoid spiSendNoBlock() {
   // see https://github.com/esp8266/Arduino/blob/40c159fcf5c5ecf9edec8e7ada49c82ece19e6d0/libraries/SPI/SPI.cpp#L421
   // http://d.av.id.au/blog/hardware-spi-hspi-command-data-registers/
   uint32_t buffer[16]; // 16x32bit Buffer 4 HW FIFO
   uint8_t *bufferPtr = (uint8_t *)&buffer; // 64x8bit Buffer pointer
   volatile uint32_t * fifoPtr = &SPI1W0; // SPI1W0 - SPI1W15 32bit FIFO Buffer registers

   for(uint8_t i=0; i<64; i++)   bufferPtr[i] = i+1; // Fill buffer with dummy data 0-63

   // fill FIFO with Buffer data
   for (uint8_t i = 0; i<16; i++) {
      *fifoPtr = buffer[i];
      fifoPtr++;
   }

   //SPI1U = SPIUMOSI | SPIUSSE; // removes delay on CS before/after SPI Clock
   SPI1U = SPIUMOSI | SPIUCSHOLD; // MOSI mode; Keep CS low after SPI Clock for some cycles
   SPI1U1 &= ~(SPIMMOSI << SPILMOSI); // Clear MOSI length
   SPI1U1 |= ((37*8-1) << SPILMOSI); // Set MOSI length to 37 bytes
   SPI1CMD |= SPIBUSY; // Sends data from FIFO
}


Now I know how to send my data (SDA/SCK) and how to generate the RCK signal without blocking the CPU...

I'm still looking for something to pulse the LED signal high and something to repeat the process with 10kHz (I might just use a timer interrupt for that).

I was wondering if I could use the I2S word clock output on GPIO 2 for the LED pulse... However, GPIO 2 is pulled up to Vcc... I would have to pull it down low, but then the ESP won't boot up after reset - it continuously spits out serial data on GPIO 2. GPIO 2 also puts out some debug serial data on bootup...

Any better ideas? I need to generate a HIGH pulse of about 5-10µs with consistent timing... It is also important that the pin is LOW when idle/booting...
User avatar
By RFZ
#68309 I still struggle with generating a stable pulse for the LED signal ... I've now protected it with a RC high-pass filter, so it won't be dangerous for the LEDs anymore if it stayed HIGH for any reason... So I now tried to manage the LED signal via Software.
Since I only need a short pulse, I used the routine that prepares the SPI buffer for timing the LED pulse:

Code: Select all#define LED_LOW  GPOC = 1 << 12;   
#define LED_HIGH GPOS = 1 << 12;

noInterrupts();
LED_HIGH;
if (FrameBufferLayout == 0) {
    // unwrapped for loop for faster execution
    rowBuffer[0] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[0] + rowOffset];
    rowBuffer[1] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[1] + rowOffset];
    if (ledPulseWidth == 0) LED_LOW;
    rowBuffer[2] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[2] + rowOffset];
    rowBuffer[3] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[3] + rowOffset];
    if (ledPulseWidth == 1) LED_LOW;
    rowBuffer[4] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[4] + rowOffset];
    rowBuffer[5] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[5] + rowOffset];
    if (ledPulseWidth == 2) LED_LOW;
    rowBuffer[6] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[6] + rowOffset];
    rowBuffer[7] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[7] + rowOffset];
    if (ledPulseWidth == 3) LED_LOW;
    rowBuffer[8] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[8] + rowOffset];
    rowBuffer[9] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[9] + rowOffset];
    if (ledPulseWidth == 4) LED_LOW;
    rowBuffer[10] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[10] + rowOffset];
    rowBuffer[11] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[11] + rowOffset];
    if (ledPulseWidth == 5) LED_LOW;
    rowBuffer[12] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[12] + rowOffset];
    rowBuffer[13] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[13] + rowOffset];
    if (ledPulseWidth == 6) LED_LOW;
    rowBuffer[14] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[14] + rowOffset];
    rowBuffer[15] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[15] + rowOffset];
    if (ledPulseWidth == 7) LED_LOW;
    rowBuffer[16] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[16] + rowOffset];
    rowBuffer[17] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[17] + rowOffset];
    if (ledPulseWidth == 8) LED_LOW;
    rowBuffer[18] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[18] + rowOffset];
    rowBuffer[19] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[19] + rowOffset];
    if (ledPulseWidth == 9) LED_LOW;
    rowBuffer[20] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[20] + rowOffset];
    rowBuffer[21] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[21] + rowOffset];
    if (ledPulseWidth == 10) LED_LOW;
    rowBuffer[22] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[22] + rowOffset];
    rowBuffer[23] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[23] + rowOffset];
    if (ledPulseWidth == 11) LED_LOW;
    rowBuffer[24] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[24] + rowOffset];
    rowBuffer[25] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[25] + rowOffset];
    if (ledPulseWidth == 12) LED_LOW;
    rowBuffer[26] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[26] + rowOffset];
    rowBuffer[27] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[27] + rowOffset];
    if (ledPulseWidth == 13) LED_LOW;
    rowBuffer[28] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[28] + rowOffset];
    rowBuffer[29] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[29] + rowOffset];
    if (ledPulseWidth == 14) LED_LOW;
    rowBuffer[30] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[30] + rowOffset];
    rowBuffer[31] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[31] + rowOffset];
    if (ledPulseWidth == 15) LED_LOW;
    rowBuffer[32] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[32] + rowOffset];
    rowBuffer[33] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[33] + rowOffset];
    if (ledPulseWidth == 16) LED_LOW;
    rowBuffer[34] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[34] + rowOffset];
    rowBuffer[35] = FrameBufferInvert ^ displayBuffer[RowByteSelectorH[35] + rowOffset];
}
LED_LOW;
interrupts();


This is just the relevant part of the code that gets executed every 100µs / 10kHz by timer1 interrupt.

With this code, I can vary the pulse up to 4µs (I might turn the LED on little earlier in the code to get pulses up to 10µs but the principle is the same).

However, the LEDs flicker noticeably :|
At least 10 times a second I get an LED pulse that is ~20µs long, so I guess the code gets interrupted...

Is there a way to solve that issue? Either stop interruption within that section, or still find a suitable hardware component of the ESP8266 to generate the pulse?
I already use GPIO 13,14,15 for SPI and currently 12 for LED... It really would be nice if the pin wouldn't interfere with programming via UART :roll:

Another disadvantage of the code above is, that I will have to use 10% of CPU (10µs every 100µs) to get my desired LED pulse. I would be able to optimize the code to only consume ~3% if it wouldn't have to toggle the LED pin.


Edit: Just fount out that
Code: Select all   LED_HIGH;
   delayMicroseconds(ledPulseWidth);
   LED_LOW;

results in a stable pulse that doesn't get interrupted. But that is obviously the worst thing to do :|
However, it might be a solution... 10% CPU just for the LED pulse, another ~4% for preparing SPI data... Still ~86% left for wifi stack and my own application code.
User avatar
By RFZ
#68723 Since delayMicroseconds wastes lots of CPU, I also tried to use UART1 TX / GPIO2 for generating the LED pulse... Since UART is HIGH on idle, I must add hardware to invert the signal afterwards. I just use the start-bit (LOW pulse) and send ones (HIGH) afterwards... The duration of the start-bit is set by setting the UART1 clock divider.

Code: Select all   // Setup only needed once
   U1D = 80 * ledPulseWidth; // Clock divider (80 = 1µs)
   U1S |= 0x08 << USTXC; // Set TX FIFO counter to 8 bits

   // Data is sent when FIFO buffer gets written
   U1F = 0xFF; // Send 0xFF (all HIGH, so just start-symbol is LOW)

However, since UART1 sends uncontrollable data on bootup and during programming, my LED matrix will light up in these moments... Since I already accepted to add hardware for inverting the signal, I also might add an AND gate to generate an ENABLED signal with a second GPIO...