Add documentation.
This commit is contained in:
parent
11c051cfab
commit
d645c1ada2
170
frame.cpp
170
frame.cpp
@ -15,146 +15,202 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
enum ExitStatus {
|
enum ExitStatus
|
||||||
|
{
|
||||||
BAD_ARGUMENT = 1,
|
BAD_ARGUMENT = 1,
|
||||||
FILE_NOT_OPEN = 2,
|
FILE_NOT_OPEN = 2,
|
||||||
FILE_BAD_FORMAT = 3
|
FILE_BAD_FORMAT = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char **argv)
|
||||||
if (argc < 2) {
|
{
|
||||||
std::cerr << "Usage: " << argv[0] << " <FILE>" << std::endl;
|
// enable debug messages
|
||||||
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
|
// arguments parsing and sanity check
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
spdlog::error("Usage: {} <FILE> ...", argv[0]);
|
||||||
return BAD_ARGUMENT;
|
return BAD_ARGUMENT;
|
||||||
}
|
}
|
||||||
const int images = argc - 1;
|
const int images = argc - 1;
|
||||||
std::stringstream jump_table;
|
|
||||||
std::stringstream asm_code;
|
|
||||||
|
|
||||||
|
// Output assembly file content.
|
||||||
|
// Content is buffered into these stringstreams,
|
||||||
|
// and then dumped to a file on disk.
|
||||||
|
// Code is organized into two parts.
|
||||||
|
std::stringstream jump_table; // Assembly jump table for images.
|
||||||
|
// Each image is described by a jump table with 256 entries.
|
||||||
|
// Each entry is a jump to the code to produce a particular line on the screen.
|
||||||
|
std::stringstream asm_code; // Assembly code to describe lines.
|
||||||
|
// Each line is a sequence of a set and/or clear bit,
|
||||||
|
// conveniently timed to mangle the video signal at the right moment.
|
||||||
|
|
||||||
|
// Output file.
|
||||||
std::fstream outfile;
|
std::fstream outfile;
|
||||||
outfile.open("frame.S", std::ios::out);
|
outfile.open("frame.S", std::ios::out);
|
||||||
if (! outfile) {
|
if (!outfile)
|
||||||
return FILE_NOT_OPEN;
|
return FILE_NOT_OPEN;
|
||||||
}
|
|
||||||
|
|
||||||
|
// include headers
|
||||||
jump_table << "#include \"macro.h\"" << std::endl;
|
jump_table << "#include \"macro.h\"" << std::endl;
|
||||||
jump_table << "#include <avr/io.h>" << std::endl;
|
jump_table << "#include <avr/io.h>" << std::endl;
|
||||||
|
|
||||||
char** image = new char*[images];
|
// Pointer to images
|
||||||
|
char **image = new char *[images];
|
||||||
|
|
||||||
for (int current_image = 0; current_image < images; ++current_image) {
|
// Parse images and convert in assembly code
|
||||||
|
for (int current_image = 0; current_image < images; ++current_image)
|
||||||
|
{
|
||||||
std::fstream infile;
|
std::fstream infile;
|
||||||
|
|
||||||
infile.open(argv[1 + current_image], std::ios::in);
|
infile.open(argv[1 + current_image], std::ios::in);
|
||||||
if (! infile) {
|
if (!infile)
|
||||||
return FILE_NOT_OPEN;
|
return FILE_NOT_OPEN;
|
||||||
}
|
|
||||||
|
|
||||||
int width, height;
|
unsigned int width, height; // width and height of image
|
||||||
std::string format;
|
std::string format; // image format
|
||||||
char c;
|
char c; // current pixel
|
||||||
|
|
||||||
getline(infile, format);
|
getline(infile, format);
|
||||||
|
spdlog::debug("format: {}", format);
|
||||||
std::cout << format << std::endl;
|
if (format != "P1")
|
||||||
|
|
||||||
if (format != "P1") {
|
|
||||||
return FILE_BAD_FORMAT;
|
return FILE_BAD_FORMAT;
|
||||||
}
|
|
||||||
|
|
||||||
// skip one line TODO
|
// skip commented line / TODO in a proper way
|
||||||
getline(infile, format);
|
getline(infile, format);
|
||||||
std::cout << format << std::endl;
|
spdlog::debug("comment: {}", format);
|
||||||
|
|
||||||
|
// read width and height
|
||||||
infile >> width >> height;
|
infile >> width >> height;
|
||||||
std::cout << width << " x " << height << std::endl;
|
spdlog::debug("width x height = {} x {}", width, height);
|
||||||
|
|
||||||
|
// read full image from disk into memory
|
||||||
|
// in an organized row by column array
|
||||||
image[current_image] = new char[height * width];
|
image[current_image] = new char[height * width];
|
||||||
|
for (int y = 0; y < height;)
|
||||||
for (int y = 0; y < height; ) {
|
{
|
||||||
for (int x = 0; x < width; ) {
|
for (int x = 0; x < width;)
|
||||||
|
{
|
||||||
c = infile.get();
|
c = infile.get();
|
||||||
if (! infile.good())
|
|
||||||
|
// file is truncated
|
||||||
|
if (!infile.good())
|
||||||
return FILE_BAD_FORMAT;
|
return FILE_BAD_FORMAT;
|
||||||
if (c == '0' || c == '1') {
|
|
||||||
|
// if current character represents a pixel, add to array...
|
||||||
|
if (c == '0' || c == '1')
|
||||||
|
{
|
||||||
image[current_image][y * width + x] = c;
|
image[current_image][y * width + x] = c;
|
||||||
x++;
|
x++;
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else // ... else ignore current char
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
y++;
|
y++;
|
||||||
}
|
}
|
||||||
|
|
||||||
infile.close();
|
infile.close();
|
||||||
|
|
||||||
|
// add new jump table for current image
|
||||||
jump_table << ".global line_jump_table_" << current_image << std::endl;
|
jump_table << ".global line_jump_table_" << current_image << std::endl;
|
||||||
jump_table << "line_jump_table_" << current_image << ":" << std::endl;
|
jump_table << "line_jump_table_" << current_image << ":" << std::endl;
|
||||||
|
|
||||||
for (int y = 0; y < height; ++y) {
|
// Inspect each line one by one
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
int diff = 1;
|
int diff = 1;
|
||||||
|
|
||||||
for (int other_image = 0; other_image <= current_image; ++other_image) {
|
// Check if one of the previously inspected images has an identical line.
|
||||||
for (int other_y = 0; other_y < height && other_y < y; ++other_y) {
|
// If it has an identical line, just add it in the current image jump table,
|
||||||
|
// instead of generating again an identical assembly code.
|
||||||
|
// This allows us to save a lot of program memory space.
|
||||||
|
// Note: previous lines of current image are good candidates to check too!
|
||||||
|
for (int other_image = 0; other_image <= current_image; ++other_image)
|
||||||
|
{
|
||||||
|
for (int other_y = 0; other_y < height && other_y < y; ++other_y)
|
||||||
|
{
|
||||||
diff = memcmp(&(image[current_image][y * width + 0]), &(image[other_image][other_y * width + 0]), width);
|
diff = memcmp(&(image[current_image][y * width + 0]), &(image[other_image][other_y * width + 0]), width);
|
||||||
if (diff == 0) {
|
if (diff == 0)
|
||||||
|
{
|
||||||
jump_table << '\t' << "jmp line_" << other_image << "_" << other_y << std::endl;
|
jump_table << '\t' << "jmp line_" << other_image << "_" << other_y << std::endl;
|
||||||
goto nested_loop_end;
|
goto nested_loop_end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nested_loop_end:
|
nested_loop_end:
|
||||||
|
|
||||||
|
// Current line has already been found to be identical
|
||||||
|
// to one of the previous ones: then skip to next one.
|
||||||
if (diff == 0)
|
if (diff == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Add jump in the current image table.
|
||||||
jump_table << '\t' << "jmp line_" << current_image << "_" << y << std::endl;
|
jump_table << '\t' << "jmp line_" << current_image << "_" << y << std::endl;
|
||||||
|
|
||||||
|
// Add line description.
|
||||||
asm_code << "line_" << current_image << "_" << y << ":" << std::endl;
|
asm_code << "line_" << current_image << "_" << y << ":" << std::endl;
|
||||||
|
|
||||||
|
// Line description is a sequence of assembly code conveniently built
|
||||||
|
// in order to set/clear an output port bit at the right moment,
|
||||||
|
// and mangle the video signal in order to display the desired image.
|
||||||
|
// Code simulates an RLE scheme, exploting small conveniently timed loops,
|
||||||
|
// in order to save program space.
|
||||||
int count = 0;
|
int count = 0;
|
||||||
char last = image[current_image][y * width + 0];
|
char last = image[current_image][y * width + 0];
|
||||||
|
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x)
|
||||||
|
{
|
||||||
char current = image[current_image][y * width + x];
|
char current = image[current_image][y * width + x];
|
||||||
std::cout << "current " << current << " last " << last << std::endl;
|
// If current pixel is the same as previous one,
|
||||||
if (current == last) {
|
// then just count++
|
||||||
|
if (current == last)
|
||||||
++count;
|
++count;
|
||||||
} else {
|
else // current pixel is different from previous one
|
||||||
std::cout << "detected change" << std::endl;
|
{
|
||||||
if (last == '0') {
|
// Each pixel is 4 clock cycles long,
|
||||||
std::cout << "clear" << std::endl;
|
// because It Just Works out perfectly with the instruction set.
|
||||||
asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl;
|
|
||||||
} else if (last == '1') {
|
// Two clock cycles are spent to set/clear the output port.
|
||||||
std::cout << "set" << std::endl;
|
if (last == '0')
|
||||||
asm_code << '\t' << "sbi IO(PORTB), 4" << std::endl;
|
{
|
||||||
|
asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl; // +2
|
||||||
|
}
|
||||||
|
else if (last == '1')
|
||||||
|
{
|
||||||
|
asm_code << '\t' << "sbi IO(PORTB), 4" << std::endl; // +2
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 1) {
|
// Loop is entered only if RLE is needed,
|
||||||
asm_code << '\t' << "ldi r31, " << count - 1 << std::endl;
|
// eg. at least 2 subsequent pixels of the same color.
|
||||||
asm_code << "1:" << std::endl;
|
if (count > 1)
|
||||||
asm_code << '\t' << "dec r31" << std::endl;
|
{ // clock cycles
|
||||||
asm_code << '\t' << "nop" << std::endl;
|
asm_code << '\t' << "ldi r31, " << count - 1 << std::endl; // +1
|
||||||
asm_code << '\t' << "brne 1b" << std::endl;
|
asm_code << "1:" << std::endl; // +0
|
||||||
|
asm_code << '\t' << "dec r31" << std::endl; // +1
|
||||||
|
asm_code << '\t' << "nop" << std::endl; // +1
|
||||||
|
asm_code << '\t' << "brne 1b" << std::endl; // +2 if taken, +1 if not
|
||||||
}
|
}
|
||||||
|
|
||||||
asm_code << '\t' << "rjmp 2f" << std::endl;
|
// Add 2 more dummy clock cycles.
|
||||||
|
asm_code << '\t' << "rjmp 2f" << std::endl; // +2
|
||||||
asm_code << "2:" << std::endl;
|
asm_code << "2:" << std::endl;
|
||||||
|
|
||||||
last = current;
|
last = current;
|
||||||
count = 1;
|
count = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// At the end of the line, clear output bit, and return from interrupt.
|
||||||
asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl;
|
asm_code << '\t' << "cbi IO(PORTB), 4" << std::endl;
|
||||||
asm_code << '\t' << "jmp jump_table_return_address" << std::endl;
|
asm_code << '\t' << "jmp jump_table_return_address" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete[] image;
|
delete[] image;
|
||||||
|
|
||||||
|
// save the generated code on disk
|
||||||
outfile << jump_table.str() << asm_code.str();
|
outfile << jump_table.str() << asm_code.str();
|
||||||
|
|
||||||
outfile.close();
|
outfile.close();
|
||||||
}
|
}
|
||||||
|
2
main.S
2
main.S
@ -81,7 +81,7 @@ int_horizontal_sync: ; +3
|
|||||||
jmp int_horizontal_sync_end
|
jmp int_horizontal_sync_end
|
||||||
|
|
||||||
enter:
|
enter:
|
||||||
; here, +23 or +24 cycles have passed since horizontal sync
|
; here, +23 cycles have passed since horizontal sync
|
||||||
; so, there are still ~168 cycles before first useful data
|
; so, there are still ~168 cycles before first useful data
|
||||||
ldi r31, 0
|
ldi r31, 0
|
||||||
sts TCNT0, r31
|
sts TCNT0, r31
|
||||||
|
Loading…
Reference in New Issue
Block a user