Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2C disabled when rendering image on the screen #8

Open
WhoseTheNerd opened this issue Feb 22, 2021 · 7 comments
Open

I2C disabled when rendering image on the screen #8

WhoseTheNerd opened this issue Feb 22, 2021 · 7 comments
Labels
good first issue Good for newcomers Solved - For Your Information Only This issue was solved and will stay open in order to be used as documentation

Comments

@WhoseTheNerd
Copy link

Like the project. I am trying to use my stm32 as display renderer, so I needed to make it as i2c slave and set the Wire pins to I2C2. Arduino nano is sending over i2c letters to put on the display. It doesn't work while it is displaying, only before displaying image. I know that the code relies on hardware timer, so delay, pwm etc doesn't work, but does it also disable i2c and spi interfaces?

STM32 code:

#include <Wire.h>
#include "bluevga.h"
#include "font.h"            // imports a ASCII Flash font bitmap
#include "bluebitmap.h"      // functions for drawing pixels in the screen

#define LED_PIN PB12

BlueVGA vga(ASCII_FONT);
//BlueVGA vga(USE_RAM);

uint8_t color_code = vga.getColorCode(RGB_WHITE, RGB_BLACK);
BlueBitmap fontBitmap(8, 8, (uint8_t *)ASCII_FONT);

volatile char text_buffer[VRAM_HEIGHT][VRAM_WIDTH] = {0};

volatile bool blink_led = true;

static void twi_receive(int bytes);
static void twi_request(void);

void setup() {
    Wire.setSCL(PB10);
    Wire.setSDA(PB11);
    Wire.begin(4);
    Wire.onReceive(twi_receive);
    Wire.onRequest(twi_request);

    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, LOW);

    {
        uint8_t tx = 0;
        uint8_t ty = 0;
        for (char letter = 32; letter < 127; letter++)
        {
            text_buffer[ty][tx++] = letter;
            if (tx >= 28)
            {
                tx = 0;
                ty++;
            }
        }
    }

    vga.clearScreen(color_code);
}

void loop() 
{
    BlueBitmap::eraseRamTiles();
    for(uint8_t y = 0; y < VRAM_HEIGHT; y++)
    {
        char* line = (char*)text_buffer[y];
        vga.printStr(0, y, color_code, line);
    }

    if (blink_led)
    {
        digitalWrite(LED_PIN, HIGH);
        vga.waitVSync(60);
        digitalWrite(LED_PIN, LOW);
        vga.waitVSync(60);
        blink_led = false;
    }
}

// Write from Master, Read from Slave
static void twi_receive(int bytes)
{
    if (bytes != 3)
        return;

    uint8_t buffer[3];
    Wire.readBytes(buffer, 3);
    uint8_t x = buffer[0];
    uint8_t y = buffer[1];
    uint8_t c = buffer[2];
    text_buffer[y][x] = c;
    blink_led = true;
}

// Read from Master, Write from Slave
static void twi_request(void)
{
    
}

Arduino Nano code:

#include <Arduino.h>
#include <Wire.h>

static void write_vga(uint8_t x, uint8_t y, uint8_t c);

void setup() 
{
    Wire.begin();
}

void loop() 
{
    write_vga(0, 0, 'A');
    write_vga(1, 0, 'B');
    write_vga(2, 0, 'C');
}

static void write_vga(uint8_t x, uint8_t y, uint8_t c)
{
    Wire.beginTransmission(4);
    Wire.write(x);
    Wire.write(y);
    Wire.write(c);
    Wire.endTransmission();
}
@RoCorbera
Copy link
Owner

RoCorbera commented Feb 28, 2021

@WhoseTheWerd,

About I2C, BlueVGA library doesn't block or prevent it from working. But it sets Timer Interrupt, which has a IRQ priority, that may or not block a lower priority IRQ from been executed. As the VGA drawing IRQ routine takes a "long time" to render a scanline, it may bring some issues, in case the I2C IRQ could not interrupt it.

I2C may also use DMA and IRQ to send data to a buffer with no CPU use. I don't know how Arduino Wire was implemented on STM32, therefore I can't say if there is any issue related to BlueVGA and I2C being used together.

Interrupts can be nested on the STM32 and there is a priority vector as well. So you may change VGA timer priority to the lower than I2C IRQ. For that it may be ncessary to read STM32 Arduino Core Code to understand how I2C works regarding Interrupts.
In order to change BlueVGA Timer IRQ priority, it shall use NVIC_SetPriority(...).

I2C may work diffentetly with STM32 Core from Roger's Core...

Setting I2C aside, below I have some points about BlueVGA usage that may help you.

About STM32 code.
1- When using BlueVGA vga(ASCII_FONT); it will use TRAM[][] and CRAM[][] as Text/Color 28x30 map with a fixed FLASH bitmap that can render ASCII characters using 32.127 char codes.

You can not use any "pixel manipulation function" because the bitmap is FLASH, not RAM.
Therefore, in order to use FLASH font bitmap, the sketch must not use any of the following code:

#include "bluebitmap.h"      // functions for drawing pixels in the screen
BlueBitmap fontBitmap(8, 8, (uint8_t *)ASCII_FONT);

or any method related to bluebitmap.h, such as
BlueBitmap::eraseRamTiles();

2- Use vga.clearScreen(); instead of BlueBitmap::eraseRamTiles(); in order to clear the Video Memory. It will write a blank space (" ") into the whole 28x30 Text/Tile code map.

 // Foreground Color = White || Background Color = Black with ASCII 32 Code as Text (blank space = ' ')
vga.clearScreen(vga.getColorCode(RGB_WHITE, RGB_BLACK));         

3 - This for_loop should not be necessary, since there is already a TRAM[H][W]. It doesn't need to use a text_buffer[][] at all.

   for(uint8_t y = 0; y < VRAM_HEIGHT; y++)
    {
        char* line = (char*)text_buffer[y];
        vga.printStr(0, y, color_code, line);
    }

Just remove the code block from loop() and make the twi_receive() to write the character directly to TRAM[][], using setTile()

// Write from Master, Read from Slave
static void twi_receive(int bytes)
{
    if (bytes != 3)
        return;

    uint8_t buffer[3];
    Wire.readBytes(buffer, 3);
    uint8_t x = buffer[0];
    uint8_t y = buffer[1];
    uint8_t c = buffer[2];

    //  Change here:
//    text_buffer[y][x] = c;
   vga.setTile(x, y, c);             // this will set the character at x,y at once. The driver will render from TRAM[][] and CRAM[][] directly

    blink_led = true;
}

@RoCorbera
Copy link
Owner

RoCorbera commented Mar 5, 2021

I have tested this case and found the solution.

Add this line to your setup()

NVIC_SetPriority(TIM1_CC_IRQn, 0xFF);

It will lower the priority of TIMER1, used to drive VGA.
Thus I2C will have a higher Interrupt priority and will work.
As a side effect, the image in the monitor will have a lot of noise when USB CDC Serial port is ON.

In order to reduce screen noise, it's necessary to set option "USB support (if available)" to NONE on the IDE Tools Menu Option. Even doing it, some noise will be seen when I2C send its data right when VGA driver is drawing the screen. As result Serial won't be available for the sketch.

Another way to send I2C data would be to transmit this data to Bluepill only during VBLANK time.
VBLANK starts right after vga.waitVSync() returns from its execution.
Thus, one possible solution it to raise a PIN on the Bluepill signaling to UNO that it can send data.
On UNO it's necessary to attach an interrupt to pin 2 or 3 setting an interrupt.

UNO only shall send I2C data inside the this pin interrupt.
Bluepill will receive it in VBLANK and it will be set.

VBLANK is only about 1.3 milliseconds. This is the time you have to send I2C data to Bluepill not disturbing the VGA Image on the monitor.

SPI is faster than I2C, thus you may be able to send more bits using SPI than I2C.
I2C 400KHz may be enough to send about 40 bytes in 1 ms.
SPI at 10MHz can send 25 times more data in 1 ms.

If no more questions about it, you can close the issue.

Good Luck.

@WhoseTheNerd
Copy link
Author

Thanks for the suggestions, but the image cannot still be modified during displaying of that image. I put the write_vga to the loop() in arduino side. When STM32 is already displaying the image, nothing happens, only if I reset the STM32 then the changes are displayed along with noise.
Basically hold reset on the arduino and press reset on the stm32, wait for the image to be displayed and then release reset on arduino. Nothing happens.

@RoCorbera
Copy link
Owner

RoCorbera commented Mar 5, 2021

I tested this sketch using STM32 Core and it works fine.
I disabled USB CDC Serial port on the menu, otherwise the screen gets a lot of noise with Serial Port Interrputs.

#include <Wire.h>

#include "bluevga.h"
#include "font.h"

BlueVGA vga(ASCII_FONT);

uint8_t color_code = vga.getColorCode(RGB_WHITE, RGB_BLACK);

static void twi_receive(int bytes);
static void twi_request(void);

void setup() {

  Wire.setSCL(PB10);
  Wire.setSDA(PB11);
  NVIC_SetPriority(TIM1_CC_IRQn, 0xFF);

  Wire.begin(4);

  Wire.onReceive(twi_receive);
  Wire.onRequest(twi_request);

  vga.clearScreen(color_code);
  vga.print("--Teste de I2C com BlueVGA!-");
  vga.print("0123456789012345678012345678");
  for (uint8_t y = 2; y < 30; y++) vga.setTile(0, y, ('0' + (y % 10)));

  // just to see the whole screen a couple seconds before starting loop()
  vga.waitVSync(120);             
}

void loop() {
  vga.waitVSync(1);
}

// Write from Master, Read from Slave
static void twi_receive(int bytes) {
  uint8_t x = Wire.read();
  uint8_t y = Wire.read();
  uint8_t c = Wire.read();

  vga.setTile(x, y, c);
}

// Read from Master, Write from Slave
static void twi_request(void) {

}

For Arduino I used this sketch:

#include <Arduino.h>
#include <Wire.h>

static void write_vga(uint8_t x, uint8_t y, uint8_t c);

void setup()
{
  Wire.begin();
  Serial.begin(115200);
  randomSeed(analogRead(0));

}

void loop()
{
  uint8_t x = random(28);
  uint8_t y = random(30);
  uint8_t c = random(90) + ' ';

  write_vga(x, y, c);
  Serial.println("Data Sent!");
  delay(500);
}

static void write_vga(uint8_t x, uint8_t y, uint8_t c) {
  Wire.beginTransmission(4);
  Wire.write(x);
  Wire.write(y);
  Wire.write(c);
  Wire.endTransmission();
}

@WhoseTheNerd
Copy link
Author

WhoseTheNerd commented Mar 6, 2021

Thanks, it seems that vga.waitVSync(1) seemed to do the trick.

@RoCorbera RoCorbera added the Solved - For Your Information Only This issue was solved and will stay open in order to be used as documentation label Apr 15, 2021
@RoCorbera RoCorbera reopened this Apr 15, 2021
@RoCorbera RoCorbera added the good first issue Good for newcomers label Nov 17, 2021
@kingodragon
Copy link

I have tested this case and found the solution.

Add this line to your setup()

NVIC_SetPriority(TIM1_CC_IRQn, 0xFF);

It will lower the priority of TIMER1, used to drive VGA. Thus I2C will have a higher Interrupt priority and will work. As a side effect, the image in the monitor will have a lot of noise when USB CDC Serial port is ON.

In order to reduce screen noise, it's necessary to set option "USB support (if available)" to NONE on the IDE Tools Menu Option. Even doing it, some noise will be seen when I2C send its data right when VGA driver is drawing the screen. As result Serial won't be available for the sketch.

Another way to send I2C data would be to transmit this data to Bluepill only during VBLANK time. VBLANK starts right after vga.waitVSync() returns from its execution. Thus, one possible solution it to raise a PIN on the Bluepill signaling to UNO that it can send data. On UNO it's necessary to attach an interrupt to pin 2 or 3 setting an interrupt.

UNO only shall send I2C data inside the this pin interrupt. Bluepill will receive it in VBLANK and it will be set.

VBLANK is only about 1.3 milliseconds. This is the time you have to send I2C data to Bluepill not disturbing the VGA Image on the monitor.

SPI is faster than I2C, thus you may be able to send more bits using SPI than I2C. I2C 400KHz may be enough to send about 40 bytes in 1 ms. SPI at 10MHz can send 25 times more data in 1 ms.

If no more questions about it, you can close the issue.

Good Luck.

For Roger's Core how should I lower its priority

@RoCorbera
Copy link
Owner

For Roger's Core how should I lower its priority

Just replace TIM1_CC_IRQn by NVIC_TIMER1_CC using NVIC_SetPriority(NVIC_TIMER1_CC, 0xFF);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers Solved - For Your Information Only This issue was solved and will stay open in order to be used as documentation
Projects
None yet
Development

No branches or pull requests

3 participants