Using a PC to increase animation frames available

This can work the other way too!

Send the PC the screen description, for instance a list of items and locations: “Sprite 1 here frame 4, here, here, background offset here…” then the PC sends a fully rendered OLED RAM image that can just be placed in the buffer by the Arduboy.

Megabytes of sprites and animation!

Why not use the PC for implementing the game code?
Well, that makes the game PC/Arduboy only. If the game play code is on the Arduboy it can still be fully mobile.

Which means you can disconnect the PC, and get some sort of Arduboy supplied graphics… perhaps 1/5 the animated frames? Using the same “display description” code running on the PC so we don’t need to implement two completely separate drawing algorithms.

The intro screen could be a video stream when the Arduboys plugged into the PC, and just a static image when it isn’t.

Hm - I wonder what audio system we could have with MB’s of data sitting on the PC?

Time to work on a proof-of-concept…

2 Likes

Very interesting concept although have you made a field test?

I don’t mean to put a dampener on things, but that sounds slow and convoluted unless the rendering is severely complex.

It’s an interesting idea, but I have doubts about the practicality of it.

Once I’ve flashed my bootloader this weekend. I’ve got a long weekend coming up so I’ll have time to experiment.

As Pharap said - it’s likely going to be slow and convoluted… but that’s never stopped me playing around before. I just like the technical challenge/data architecture/program design. =)

1 Like

: nods :

I expect you’re right about it being slow and too finicky for anything useful. I like tinkering with ideas though, just to see how they turn out - code and design stuff.

1 Like

I’ve been thinking about this too, because it would a very nice way to use Scratch to program the Arduboy. Instead of reprogramming the Arduboy each time you want to test your game, you would just stream the whole screen and send the button touches back to Scratch.

It should work in theory at 14 frames per second with the maximum recommended baud rate of 115 200 bps. @SarahC Can you test with 60 FPS and a baud rate of 500 000 bps? I would be very interested with your findings.

Baud rate settings mean nothing for an Arduboy to PC USB connection. You can set any baud rate you like and it will still transfer at whatever maximum speed the USB interface is capable of.

Setting a baud rate is to inform a USB to serial (TTL, RS-232, RS-485, etc.) converter chip what speed to use on the serial interface. Since the Arduboy to PC link is USB end to end, without conversion to a different serial protocol anywhere, the baud rate setting doesn’t affect anything.

Caveat: Setting 1200 baud on an Arduino USB interface has a special meaning. Closing the port (setting DTR low) at 1200 baud signals an Arduino (Arduboy included) to reset to the bootloader to accept commands for a new sketch upload, the same way that toggling DTR on a true serial port forces the physical reset line to toggle. Therefore, it’s best not to set 1200 baud for general use with USB to USB links.

2 Likes

I just tested reading the characters from serial using Serial.readBytes and saving them directly into the buffer:

#include <Arduboy2.h>

Arduboy2 arduboy;

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(15);

  Serial.begin(500000);
}

void loop() {
  if (!(arduboy.nextFrame()))
    return;

  uint8_t* sBuffer = arduboy.getBuffer();
  const int characters = Serial.readBytes(sBuffer, 1024);

  if (characters > 0) {
    arduboy.display();
  }
}

I tested this by sending 1024 characters like the letter “f”, but for some reason it only reads the first 384 characters (128x24 pixels) and then doesn’t take characters anymore.

Maybe we can try using Serial.read instead?

Internally readBytes uses timedRead which times out after a period of time, so it’s likely that the timeout is occurring.
You could try using timedRead to see if it ever returns -1 (indicating read timeout) and figure out the issue from there.

For what its worth, your code isn’t working properly anyway.
The Arduboy2 buffer is ((128*64)/8) = 1024 bytes, not 256 bytes.
readBytes will continually overwrite the same bytes, it won’t continue to fill in the rest so 3/4 of the screen will always be blank.

To fill in the rest you’ll need to keep track of how many bytes have been read each loop with an accumulator variable, something like this:

#include <Arduboy2.h>

Arduboy2 arduboy;

void setup()
{
	arduboy.begin();
	arduboy.setFrameRate(15);

	Serial.begin(500000);
}

size_t accumulator = 0;

void loop()
{
	if (!(arduboy.nextFrame()))
		return;

	uint8_t * sBuffer = arduboy.getBuffer();
	const size_t characters = Serial.readBytes(&sBuffer[accumulator], 1024 - accumulator);
	accumulator += characters;

	if (accumulator >= 1024)
	{
		arduboy.display();
		accumulator = 0;
	}
}
1 Like

I’ve had this working on a D1 and TFT screen…

I’ll try and dig out the code - it’s on a virtual machine somewhere.

Ah, found the .Net Visual Studio files here (the sketch is in the root of the Zip):
http://untamed.co.uk/miscFolder/ESP8266.zip

The most interesting bit isn’t the D1 sketch - it just reads in bytes from the website (along with the length of time to display the frame) - it’s the drawing buffers on the server.

I wrote two - one for the TFT LCD ST7735 - which is straight forward, and then one for the OLED panel badly named as ESP8266Canvas.vb

It creates a .Net drawing canvas the same size as the OLED display’s pixels.

Imagine! MULTIPLE PLATFORM displays using the same drawing code, just formatted for the relevant system. Arduboy, Arduboy Color, Arduboy HDMI… and so on. =D

You just draw on it using all the nice graphics commands .Net has - and then when it’s due to be sent to the Arduboy - a command called translates the display data by rotating the bits 90 degrees and adding a little header that contains the frame display time.

It can send a response back from the server as an OLED memory stream, or as a PNG graphic magnified twice. This is great for debugging - you can call the ASPX page from the browser, and see the image the Arduboy would be getting.

Obviously the Arduboy will be using the Serial connection - but the concept stays the same - create a .Net canvas, draw the graphics using standard graphics commands, and then use something like the encoding code here to Serial.write the result back to the Arduboy, avoiding all the nasty byte handling normally needed. =)

Sadly I can’t find the OLED clock display code anywhere! Damn.

So this page… getFrame.aspx or whatever… returns data for the OLED via sendToClient:

Dim c As New ESP8266Canvas()
Dim g As Graphics = c.g
Dim asOLEDFormat = (Request("debug") Is Nothing)

'All the drawing code goes here, using 'g' as the graphics context.

c.sendToClient(frameDelay:=frameDelay, 
                           response:=Response,
                           asOLEDFormat:=asOLEDFormat)

Any none-0 value in RGB is a set pixel. This can be fine tuned by using the following subroutine, which forces the entire pixel to be black/white - that way, when using the “debugger - return as PNG” in a webpage, you can clamp the pixels to display exactly what will then be shown by the OLED panel.

blackWhiteFrame(ByVal limitRed As Integer, ByVal limitGreen As Integer, ByVal limitBlue As Integer)

And this is the D1 sketch - it was fairly reliable, though there was a pesky issue somewhere… (on inspection I think it’s if a byte is dropped, the code doesn’t request a new page, nor time out this one. Hm… it might time out and request a new page? I can’t remember the behaviour of get, streaming bytes, and dropped bits!)

#include <ESP8266WiFi.h>
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>

#define TFT_CS     5
#define TFT_RST    15
#define TFT_DC     2

String ssid="";
String password="";
const char* url="ESP8266.asp";
const char* host="untamed.co.uk";

uint16_t lineData[20480];
int lineDataPtr = 0; 

byte b1 = 0, b2 = 0;
uint16_t color = 0;

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);

WiFiClient client;
const int httpPort = 80;

inline void setCS(bool level) {
  digitalWrite(5, level);
}

inline void setRS(bool level) {
  digitalWrite(2, level);
}

inline void setDataBits(uint16_t bits) {
  const uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO));
  bits--;
  SPI1U1 = ((SPI1U1 & mask) | ((bits << SPILMOSI) | (bits << SPILMISO)));
}

void setup(void){
  Serial.begin(74880);

//SPI.setFrequency(500000);
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  tft.setRotation(2);
  tft.fillScreen(ST7735_BLACK);

  Serial.print("\n");
  Serial.setDebugOutput(true);

  tft.setCursor(0, 0);
  tft.setTextColor(65534);
  tft.setTextWrap(true);
  tft.println("Scanning networks...");
  tft.println("");

  int n = WiFi.scanNetworks();

  tft.fillScreen(ST7735_BLACK);
  tft.setCursor(0,0);
  
  for (int i = 0; i < n; ++i){
    tft.print(i+1);
    tft.print(" : ");
    tft.println(WiFi.SSID(i));
    if(WiFi.SSID(i)=="OfficeNetwork"){
       ssid="OfficeNetwork";
       password="PASSWORDHERE";
       break;
    }
    if(WiFi.SSID(i)=="Assombalonga"){
       ssid="Assombalonga";
       password="PASSWORDHERE";
       break;
    }
  }
  
  delay(2000);
  
  if(ssid==""){
    tft.println("No known network!");
  }else{
    tft.println("");
    tft.println("Identified: ");
    tft.println(ssid);
    tft.println("Logging in.");
  }

  tft.println("");
  
  Serial.print("Connecting to " );
  Serial.println(ssid.c_str());
  tft.println("Connecting to " );
  tft.println(ssid.c_str());
  tft.println("");
  
  if (String(WiFi.SSID()) != String(ssid)) {
    tft.println("Connecting.");
    WiFi.begin(ssid.c_str(), password.c_str());
  }
  while (WiFi.status() != WL_CONNECTED) {
    delay(50);
    Serial.print(".");
    tft.print(".");
  }
  
  Serial.println("Connected! IP address: ");
  Serial.println(WiFi.localIP());
  tft.println("");
  tft.println("Connected!");
  tft.println("IP: " + WiFi.localIP().toString());
  tft.println("");
  tft.println("Requesting data...");
  delay(2000);
  SPI.setFrequency(30000000);
  WiFi.mode(WIFI_OFF);
}


void loop(void){
  int frameDelay = 5; 
  if (!client.connect(host, 80)) {
    Serial.println("connection failed");
    return;
    } 
  client.print(String("GET ") + "/ESP8266/ST7735.aspx HTTP/1.1\r\n" +
               "Host: untamed.co.uk\r\n" + 
               "Connection: close\r\n\r\n\r\n");
  delay(50);
  String line ="";
  bool started = false;

  tft.setAddrWindow(0,0,127,160);
  
  while(client.available() || client.connected()){
    if(!started){// The top two linse are a "flag" to show were the data stream starts, and then the length of the delay in text.
      String temp = client.readStringUntil('\r');
      if(temp.indexOf("DataFollows:") >= 0) {
        Serial.print("HERE!");
        started = true;
        frameDelay = atoi(client.readStringUntil('\r').c_str());
        Serial.print("Delay: ");
        Serial.println(frameDelay);
        client.read();
        setRS(true);
        setCS(false);
        setDataBits(16);}
    }else{
      if(client.available()>1){//If the client has bytes available - read a couple in.

        lineData[lineDataPtr++] = client.read() | (uint16_t) client.read() << 8;
        if(lineDataPtr==20480){
          for(int loc=0;loc<20480;loc++){
            lineDataPtr = 0;
            while(SPI1CMD & SPIBUSY) {}
            SPI1W0 = lineData[loc]; 
            SPI1CMD |= SPIBUSY;
          }
        }
        
      } else {
        delay(1);// Wait for more data in the buffer
      }
    }//End of post "started" pixel collect
  }// No longer connected, and no longer any data pending.

  if(!started) {delay(100); Serial.print("Connection not available... trying again ");}

  setCS(true);
  delay(frameDelay);
}

Visual Basic? I’m having college flashbacks!

Odd choice to involve aspx though.