So, I'm currently running a small display and I'm making particles bounce around on the screen. However, the problem is I have to store all of the info for each particle. My current code is like this:
dx is the velocity on the X-axis, positive or negative. And dy is velocity on Y-axis, also positive or negative. And xPos and yPos are their position in the (128, 64) screen. But this method of having 4 arrays for 4 values of each particle is hard on memory. I can only get MAX 20 with my code in its current state. Is there a way to make this more efficient? Like is there a way to make an array like this form?:
float velocity = (1,-1);
And if so, would that be better? or is there a way I'm not aware of?
Which board are you using? If the board is based on an AVR (e.g. Mega, Uno, Leonardo) and the values are fixed, you can store them in PROGMEM.
You will have to show you complete sketch. The reason that you can't use more than 20 particles is probably because of the graphics library that you're using; those libraries chew memory.
As positions on the screen are in integeres, is there a need to use floats? Reducing to int (int16_t) would allow you to double the amount of particles.
A better way to store related information is the use of a struct or class. I'm more familiar with struct.
// macro to calculate number of elements in array of any type
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
struct PARTICLE
{
float dx;
float dy;
float yPos;
float xPos;
};
PARTICLE particles[] =
{
{3.14, 2.71, 5.0, 11.3},
{101.0, 202.0, 1000.0, 33.3},
};
void setup()
{
Serial.begin(57600);
while (!Serial);
Serial.print("There are ");
Serial.print(NUMELEMENTS(particles));
Serial.println(" particles");
for (uint8_t cnt = 0; cnt < NUMELEMENTS(particles); cnt++)
{
Serial.print("\tdx = "); Serial.print(particles[cnt].dx);
Serial.print(", dy = "); Serial.print(particles[cnt].dy);
Serial.print(", xPos = "); Serial.print(particles[cnt].xPos);
Serial.print(", yPos = "); Serial.println(particles[cnt].yPos);
}
}
void loop()
{
}
Note that this still will use the same amount of memory. Also note that te variable particles is different from your one.
I'm using an arduino nano.
Unfortunately, the variables are changing ever like 10 milliseconds lol
Using a float seems to be the best way to give particles different speeds. So a particle with dx = 0.5 will go half as fast as a particle with dx = 1. Another reason for this is that I have to use less math in the overall code. So if it has a dx =1 it will move one pixel for each frame and so on. If I had ints instead of floats, I'd basically have to divide the dx by some number like 2 to get the same result as the previous example.
Here's the whole code:
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C
#define xRBound 128
#define xLBound 0
#define yDBound 64
#define yUBound 0
#define particles 20
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
float dx[particles];
float dy[particles];
float yPos[particles];
float xPos[particles];
int radius = 0;
int mass = 1;
const float DEG2RAD = PI / 180.0;
const float RAD2DEG = 180.0 / PI;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(1, INPUT);
randomSeed(analogRead(1));
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(0);
display.clearDisplay();
startParticlesRandom();
// startParticlesFine();
}
void loop() {
displayParticles();
moveParticles();
// collisionCheck();
delay(10);
}
void collisionCheck() {
for (int i = 0; i < particles; i++) {
for (int j = 0; j < particles; j++) {
if (i == j) continue;
float distance = sqrt(sq(yPos[j] - yPos[i]) + sq(xPos[j] - xPos[i]));
if (distance - radius * 2 <= 1) {
//Input complex physics equation that I don't understand yet so that particles will bounce and transfer energy
}
}
}
}
void startParticlesRandom() {
for (int i = 0; i < particles; i++) {
yPos[i] = random(yUBound + radius, yDBound - radius);
xPos[i] = random(xLBound + radius, xRBound - radius);
dx[i] = random(10, 200) * 0.01 * (random(2) * 2 - 1);
dy[i] = random(10, 200) * 0.01 * (random(2) * 2 - 1);
// Serial.print("particle ");
// Serial.print(i);
// Serial.println(" set!");
}
}
void startParticlesFine() {
yPos[0] = yDBound / 2;
yPos[1] = yDBound / 2;
xPos[0] = 5;
xPos[1] = 123;
dx[0] = 1.5;
dx[1] = -0.5;
dy[0] = 1.5;
dy[1] = -0.5;
}
void moveParticles() {
for (int i = 0; i < particles; i++) {
if (yPos[i] >= yDBound - radius || yPos[i] <= yUBound + radius) {
dy[i] *= -1;
}
if (xPos[i] >= xRBound - radius || xPos[i] <= xLBound + radius) {
dx[i] *= -1;
}
yPos[i] += dy[i];
xPos[i] += dx[i];
}
}
void displayParticles() {
display.clearDisplay();
for (int i = 0; i < particles; i++) {
int x = xPos[i];
int y = yPos[i];
display.drawCircle(x, y, radius, WHITE);
}
display.display();
}
The dx and dy are determined (in the random version of the startup command) as an int between 10 and 200 then multiplied by 0.01 and then a random number 1 or -1 (0 is excluded). So if the random number was 135 and -1, the result for dx would be 1.35, so the numbers never go over 2.
My goal is to make these particles bounce off of each other but, I only got to calculus in collage and though physics was (at one point) my major, the physics equations from the paper I'm reading is going over my head! haha but that's for a different post
Just how perfectly precise do they have to move? Arduino float on AVR sucks for precision and calculation speed in general. I'm thinking that you should have a look at the Teensy 4.1 with an M4F ARM CPU, the F is for FPU. Is 512K RAM enough? Is 600MHz fast enough? It might cost $30....
I mean, they're not super precise, but I don't really need them to be. This was more of a coding/physics exercise that I came up with to see if I could make something complex run on something simple. I'm not looking to run COD on the arduino or anything like that. But, if memory becomes a crippling issue, I'll probably look into something like what you mentioned.
Being the "extremist" that I am, I'm trying to get this thing to have like 40 particles, but at some point it will probably become unrealistic. But if there's a solution I don't know about that could help, I thought I'd ask anyone that has more experience than me in coding.
You can use fixed point numbers instead of floats; not sure how much you gain.
Your code compiles to 843 bytes RAM, I think that you should be able to squeeze a bit more out; how did you determine that you could only have 20 particles?
Pin 1 is the TX pin; why do you fiddle with it if you want to have the serial communication available for debugging?
If it's for the purpose of the randomSeed(analogRead(1)), it's not needed; and for that, it would be pinMode(A1, INPUT). The analogRead() will work properly with both A1 and 1.
I'm getting a maximum of around 22 before the code freezes up, remember that the Adafruit library allocates 1024 bytes of memory for the display buffer at runtime, so that is not shown in the memory usage.
Remove the Serial.begin() after you finish debugging the code, that frees up the memory used by the serial buffers.
Use the U8g2 library with a frame buffer, that frees up enough memory to get around 82 particles, although I have not tested the code over a long time.
< edit >
Remove the pinMode(), that is setting digital pin 1 (the Serial Rx pin) to input. Presumably you are attempting to set analog pin A1 to input, which you do not need to do anyway. The analogRead() makes the assumption that 1 refers to A1 because digital pins are unusable for analog reads, but pinMode makes no such assumption.
Here is a quick pass at changing from 32-bit floats to 8-bit velocity and 16-bit position. This makes each particle 48 bits instead of 128 bits. It should also speed up the math since most is done in 32-bit integers instead of floats.
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C
#define xRBound 128
#define xLBound 0
#define yDBound 64
#define yUBound 0
#define particles 20
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
struct Particle
{
int8_t dx; // Velocity in tenths of a pixel per step (+/-12.7 pixels)
int8_t dy; // Velocity in tenths of a pixel per step (+/-12.7 pixels)
int16_t xPos; // Position in hundredths of a pixel (+/-320.00 pixels)
int16_t yPos; // Position in hundredths of a pixel (+/-320.00 pixels)
} Particles[particles];
int radius = 0;
int mass = 1;
const float DEG2RAD = PI / 180.0;
const float RAD2DEG = 180.0 / PI;
void setup()
{
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(1, INPUT);
randomSeed(analogRead(1));
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(0);
display.clearDisplay();
startParticlesRandom();
// startParticlesFine();
}
void loop()
{
displayParticles();
moveParticles();
// collisionCheck();
delay(10);
}
void collisionCheck()
{
for (int i = 0; i < particles; i++)
{
int32_t x1 = Particles[i].xPos;
int32_t y1 = Particles[i].yPos;
for (int j = 0; j < particles; j++)
{
if (i == j) continue;
int32_t dx = Particles[j].xPos - x1;
int32_t dy = Particles[j].yPos - y1;
float distance = sqrt((dx * dx / 100) + (dy * dy / 100));
if (distance - radius * 2 <= 1)
{
//Input complex physics equation that I don't understand yet so that particles will bounce and transfer energy
}
}
}
}
void startParticlesRandom()
{
for (int i = 0; i < particles; i++)
{
Particles[i].yPos = random(yUBound + radius, yDBound - radius);
Particles[i].xPos = random(xLBound + radius, xRBound - radius);
Particles[i].dx = random(-120, +120);
Particles[i].dy = random(-120, +120);
// Serial.print("particle ");
// Serial.print(i);
// Serial.println(" set!");
}
}
void startParticlesFine()
{
Particles[0].yPos = (yDBound / 2) * 100;
Particles[1].yPos = (yDBound / 2) * 100;
Particles[0].xPos = 5 * 100;
Particles[1].xPos = 123 * 100;
Particles[0].dx = 15;
Particles[1].dx = -5;
Particles[0].dy = 15;
Particles[1].dy = -5;
}
void moveParticles()
{
for (int i = 0; i < particles; i++)
{
if (Particles[i].yPos / 100 >= yDBound - radius || Particles[i].yPos / 100 <= yUBound + radius)
{
Particles[i].dy *= -1;
}
if (Particles[i].xPos / 100 >= xRBound - radius || Particles[i].xPos / 100 <= xLBound + radius)
{
Particles[i].dx *= -1;
}
// Convert velocity in 10th to position change in 100ths
Particles[i].yPos += Particles[i].dy * 10;
Particles[i].xPos += Particles[i].dx * 10;
}
}
void displayParticles()
{
display.clearDisplay();
for (int i = 0; i < particles; i++)
{
// Convert hundredths of a pixel to pixels
int x = Particles[i].xPos / 100;
int y = Particles[i].yPos / 100;
display.drawCircle(x, y, radius, WHITE);
}
display.display();
}