RoCorbera / BlueVGA

VGA library for STM32F103C (BluePill) that can manipulate a screen with 28x30 tiles with 8x8 pixels each, in a total resolution of 224x240 pixels with 8 colors using a very low footprint
Other
62 stars 12 forks source link

I2C disabled when rendering image on the screen #8

Open WhoseTheNerd opened 3 years ago

WhoseTheNerd commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

kingodragon commented 1 year ago

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 commented 1 year ago

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);