Add TV output

This commit is contained in:
2025-10-18 17:59:20 +04:00
parent e3849ecded
commit 6cde4a2a9a
23 changed files with 1983 additions and 407 deletions

View File

@@ -1,18 +1,21 @@
# Desktop options # Desktop options
CC = gcc CC = gcc
CFLAGS = -std=gnu23 -Wall -Wextra -O2 CFLAGS = -std=gnu23 -Wall -Wextra -O2
CXXFLAGS = -std=gnu++17 -Wall -Wextra -O2 -fno-exceptions -fno-rtti
LIBS = -lgdi32 LIBS = -lgdi32
# Micro options # Micro options
MCU = atmega328p MCU = atmega328p
F_CPU = 16000000 F_CPU = 16000000
PROGRAMMER = arduino PROGRAMMER = arduino
PORT = COM4 PORT = COM5
MICRO_CC = avr-gcc MICRO_CC = avr-gcc
MICRO_CXX = avr-g++
OBJCOPY = avr-objcopy OBJCOPY = avr-objcopy
SIZE = avr-size SIZE = avr-size
AVRDUDE = avrdude AVRDUDE = avrdude
MICRO_CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU)UL $(CFLAGS) MICRO_CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU)UL $(CFLAGS)
MICRO_CXXFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU)UL $(CXXFLAGS)
LDFLAGS = -mmcu=$(MCU) LDFLAGS = -mmcu=$(MCU)
TARGET = main TARGET = main
@@ -26,8 +29,9 @@ UNIX_SOURCES = $(BASE_SOURCES) platforms/unix.c
UNIX_OBJECTS = $(UNIX_SOURCES:.c=.uo) UNIX_OBJECTS = $(UNIX_SOURCES:.c=.uo)
UNIX_TARGET = $(TARGET) UNIX_TARGET = $(TARGET)
MICRO_SOURCES = $(BASE_SOURCES) platforms/micro/micro.c platforms/micro/lib/ST7789/ST7789.c platforms/micro/lib/MPU6050/MPU6050.c platforms/micro/lib/I2C/I2C.c MICRO_SOURCES = $(BASE_SOURCES) platforms/micro/micro.cpp platforms/micro/lib/TVout/TVout.cpp platforms/micro/lib/TVout/video_gen.cpp platforms/micro/lib/MPU6050/MPU6050.c platforms/micro/lib/I2C/I2C.c
MICRO_OBJECTS = $(MICRO_SOURCES:.c=.mo) MICRO_OBJECTS = $(MICRO_SOURCES:.cpp=.mxxo)
MICRO_OBJECTS := $(MICRO_OBJECTS:.c=.mo)
MICRO_TARGET = $(TARGET) MICRO_TARGET = $(TARGET)
ELF = $(TARGET).elf ELF = $(TARGET).elf
HEX = $(TARGET).hex HEX = $(TARGET).hex
@@ -44,7 +48,7 @@ $(HEX): $(ELF)
$(SIZE) $@ $(SIZE) $@
$(ELF): $(MICRO_OBJECTS) $(ELF): $(MICRO_OBJECTS)
$(MICRO_CC) $(LDFLAGS) -o $@ $^ $(MICRO_CXX) $(LDFLAGS) -o $@ $^
%.wo: %.c %.wo: %.c
$(CC) $(CFLAGS) -DWIN -c $< -o $@ $(CC) $(CFLAGS) -DWIN -c $< -o $@
@@ -52,9 +56,14 @@ $(ELF): $(MICRO_OBJECTS)
%.uo: %.c %.uo: %.c
$(CC) $(CFLAGS) -DUNIX -c $< -o $@ $(CC) $(CFLAGS) -DUNIX -c $< -o $@
%.mxxo: %.cpp
$(MICRO_CXX) $(MICRO_CXXFLAGS) -DMICRO -c $< -o $@
%.mo: %.c %.mo: %.c
$(MICRO_CC) $(MICRO_CFLAGS) -DMICRO -c $< -o $@ $(MICRO_CC) $(MICRO_CFLAGS) -DMICRO -c $< -o $@
run_win: $(WIN_TARGET) run_win: $(WIN_TARGET)
.\$(WIN_TARGET) .\$(WIN_TARGET)
@@ -62,7 +71,7 @@ run_unix: $(UNIX_TARGET)
.\$(UNIX_TARGET) .\$(UNIX_TARGET)
upload_micro: $(HEX) upload_micro: $(HEX)
$(AVRDUDE) -p $(MCU) -c $(PROGRAMMER) -P $(PORT) -b 115200 -U flash:w:$(HEX):i $(AVRDUDE) -p $(MCU) -c $(PROGRAMMER) -P $(PORT) -b 57600 -U flash:w:$(HEX):i
clean: clean:
rm -f $(WIN_TARGET) $(WIN_OBJECTS) $(UNIX_TARGET) $(UNIX_OBJECTS) $(HEX) $(ELF) $(MICRO_OBJECTS); rm -f $(WIN_TARGET) $(WIN_OBJECTS) $(UNIX_TARGET) $(UNIX_OBJECTS) $(HEX) $(ELF) $(MICRO_OBJECTS);

View File

@@ -1,6 +1,7 @@
#include "object.h" #include "object.h"
#include "../utils/screen.h" #include "../utils/screen.h"
#include "camera.h"
#include "vector.h" #include "vector.h"
void object_transform(Object *const object, int size, float *translate_matrix) { void object_transform(Object *const object, int size, float *translate_matrix) {
@@ -27,12 +28,84 @@ Point object_get_centroid(const Object *const object) {
} }
void object_draw(const Object *object, const Screen *const screen, void object_draw(const Object *object, const Screen *const screen,
const float *render_matrix, const Color *const color) { const float *render_matrix, const Camera *const camera,
for (int i = 0; i < object->number_of_edges; i++) { const Color *const color) {
ScreenPoint screen_point1 = point_to_screen_point( // Массив для отслеживания, какие рёбра нужно нарисовать
&object->points[object->edges[i][0]], screen, render_matrix); char edge_drawn[object->number_of_edges];
ScreenPoint screen_point2 = point_to_screen_point( for (int i = 0; i < object->number_of_edges; ++i) {
&object->points[object->edges[i][1]], screen, render_matrix); edge_drawn[i] = 0;
screen->draw_line(&screen_point1, &screen_point2, color); }
// Проходим по всем граням
for (int face_idx = 0; face_idx < object->number_of_faces; ++face_idx) {
const int *face = object->faces[face_idx];
// Берём первые три вершины для вычисления нормали (достаточно для плоской
// грани)
Point p0 = object->points[face[0]];
Point p1 = object->points[face[1]];
Point p2 = object->points[face[2]];
// Векторы по ребрам грани
float v1[3] = {p1.coordinates[0] - p0.coordinates[0],
p1.coordinates[1] - p0.coordinates[1],
p1.coordinates[2] - p0.coordinates[2]};
float v2[3] = {p2.coordinates[0] - p0.coordinates[0],
p2.coordinates[1] - p0.coordinates[1],
p2.coordinates[2] - p0.coordinates[2]};
// Нормаль к грани (внешняя, если winding order — CCW при взгляде снаружи)
float normal[3];
vector_cross_product(v1, v2, normal);
// Вычисляем центр грани (для направления к камере)
float w[3] = {0.0f, 0.0f, 0.0f};
for (int i = 0; i < object->face_sizes[face_idx]; ++i) {
const Point *pt = &object->points[object->faces[face_idx][i]];
w[0] += pt->coordinates[0];
w[1] += pt->coordinates[1];
w[2] += pt->coordinates[2];
}
w[0] /= (float)object->face_sizes[face_idx];
w[1] /= (float)object->face_sizes[face_idx];
w[2] /= (float)object->face_sizes[face_idx];
// Вектор от центра грани к позиции камеры
float to_camera[3] = {camera->position->coordinates[0] - w[0],
camera->position->coordinates[1] - w[1],
camera->position->coordinates[2] - w[2]};
// Проверка видимости: если грань смотрит от камеры — пропускаем
float dot = vector_dot_product(normal, to_camera);
if (dot <= 0.0f) {
continue; // back-face: невидима
}
// Отмечаем все рёбра этой грани как "нужно нарисовать"
for (int j = 0; j < object->face_sizes[face_idx]; ++j) {
int v_start = face[j];
int v_end = face[(j + 1) % object->face_sizes[face_idx]];
for (int edge_idx = 0; edge_idx < object->number_of_edges; ++edge_idx) {
int e0 = object->edges[edge_idx][0];
int e1 = object->edges[edge_idx][1];
if ((e0 == v_start && e1 == v_end) || (e0 == v_end && e1 == v_start)) {
edge_drawn[edge_idx] = 1;
break;
}
}
}
}
// Рисуем только отмеченные рёбра
for (int i = 0; i < object->number_of_edges; ++i) {
if (edge_drawn[i]) {
ScreenPoint p1 = point_to_screen_point(
&object->points[object->edges[i][0]], screen, render_matrix);
ScreenPoint p2 = point_to_screen_point(
&object->points[object->edges[i][1]], screen, render_matrix);
screen->draw_line(&p1, &p2, color);
}
} }
} }

View File

@@ -2,14 +2,18 @@
#define OBJECT_H #define OBJECT_H
#include "../utils/screen.h" #include "../utils/screen.h"
#include "camera.h"
#include "point.h" #include "point.h"
typedef struct Object { typedef struct Object {
const char *const name; const char *const name;
Point *const points; Point *const points;
const int (*const edges)[2]; const int (*const edges)[2];
const int *const *const faces;
const int(*const face_sizes);
const int number_of_points; const int number_of_points;
const int number_of_edges; const int number_of_edges;
const int number_of_faces;
Point *const position; Point *const position;
const float (*const rotate_speed)[3]; const float (*const rotate_speed)[3];
} Object; } Object;
@@ -17,6 +21,7 @@ typedef struct Object {
void object_transform(Object *const object, int size, float *translate_matrix); void object_transform(Object *const object, int size, float *translate_matrix);
Point object_get_centroid(const Object *const object); Point object_get_centroid(const Object *const object);
void object_draw(const Object *object, const Screen *const screen, void object_draw(const Object *object, const Screen *const screen,
const float *render_matrix, const Color *const color); const float *render_matrix, const Camera *const camera,
const Color *const color);
#endif #endif

View File

@@ -7,6 +7,10 @@ void point_add_point(Point *const point, const Point *const other_point) {
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
point->coordinates[i] += other_point->coordinates[i]; point->coordinates[i] += other_point->coordinates[i];
} }
void point_substact_point(Point *const point, const Point *const other_point) {
for (int i = 0; i < 3; i++)
point->coordinates[i] -= other_point->coordinates[i];
}
void point_mult_number(Point *const point, const int k) { void point_mult_number(Point *const point, const int k) {
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)

View File

@@ -8,6 +8,7 @@ typedef struct Point {
} Point; } Point;
void point_add_point(Point *const point, const Point *const other_point); void point_add_point(Point *const point, const Point *const other_point);
void point_substact_point(Point *const point, const Point *const other_point);
void point_mult_number(Point *const point, const int k); void point_mult_number(Point *const point, const int k);
void point_transform(Point *const point, int size, void point_transform(Point *const point, int size,
const float *translate_matrix); const float *translate_matrix);

View File

@@ -47,7 +47,7 @@ void init_engine() {
void render(const Screen *const screen, const Color *const color) { void render(const Screen *const screen, const Color *const color) {
for (int i = 0; i < number_of_objects; i++) { for (int i = 0; i < number_of_objects; i++) {
object_draw(&(objects[i]), screen, render_matrix, color); object_draw(&(objects[i]), screen, render_matrix, &camera, color);
} }
} }

View File

@@ -3,59 +3,105 @@
#include "engine/engine.h" #include "engine/engine.h"
// == Cube ==
Point cube_points[] = { Point cube_points[] = {
{.coordinates = {0.0f, 0.0f, 0.0f}}, {.coordinates = {1.0f, 0.0f, 0.0f}}, {.coordinates = {0.0f, 0.0f, 0.0f}}, {.coordinates = {1.0f, 0.0f, 0.0f}},
{.coordinates = {1.0f, 1.0f, 0.0f}}, {.coordinates = {0.0f, 1.0f, 0.0f}}, {.coordinates = {1.0f, 1.0f, 0.0f}}, {.coordinates = {0.0f, 1.0f, 0.0f}},
{.coordinates = {0.0f, 0.0f, 1.0f}}, {.coordinates = {1.0f, 0.0f, 1.0f}}, {.coordinates = {0.0f, 0.0f, 1.0f}}, {.coordinates = {1.0f, 0.0f, 1.0f}},
{.coordinates = {1.0f, 1.0f, 1.0f}}, {.coordinates = {0.0f, 1.0f, 1.0f}}}; {.coordinates = {1.0f, 1.0f, 1.0f}}, {.coordinates = {0.0f, 1.0f, 1.0f}}};
Point cube_position = {.coordinates = {3.0f, 6.0f, 5.0f}};
const int cube_edges[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, const int cube_edges[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6},
{6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}}; {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}};
const float cube_speed[] = {0.03f, 0.0f, 0.0f}; int cube_face0[] = {3, 2, 1, 0};
int cube_face1[] = {4, 5, 6, 7};
int cube_face2[] = {0, 4, 7, 3};
int cube_face3[] = {2, 6, 5, 1};
int cube_face4[] = {1, 5, 4, 0};
int cube_face5[] = {3, 7, 6, 2};
const int *cube_faces[] = {cube_face0, cube_face1, cube_face2,
cube_face3, cube_face4, cube_face5};
const int cube_face_sizes[] = {4, 4, 4, 4, 4, 4};
const float cube_speed[] = {0.03f, -0.03f, 0.03f};
Point cube_position = {.coordinates = {2.0f, 2.0f, 2.0f}};
// Point cube_position = {.coordinates = {3.0f, 6.0f, 5.0f}};
const Object cube = {.name = "cube", const Object cube = {.name = "cube",
.points = cube_points, .points = cube_points,
.edges = cube_edges, .edges = cube_edges,
.faces = cube_faces,
.face_sizes = cube_face_sizes,
.number_of_points = 8, .number_of_points = 8,
.number_of_edges = 12, .number_of_edges = 12,
.number_of_faces = 6,
.position = &cube_position, .position = &cube_position,
.rotate_speed = &cube_speed}; .rotate_speed = &cube_speed};
Point pyramid_points[] = {{.coordinates = {-1.0f, 0.0f, -1.0f}}, // // == Pyramid ==
{.coordinates = {1.0f, 0.0f, -1.0f}},
{.coordinates = {1.0f, 0.0f, 1.0f}},
{.coordinates = {-1.0f, 0.0f, 1.0f}},
{.coordinates = {0.0f, 1.5f, 0.0f}}};
Point pyramid_position = {.coordinates = {6.0f, 3.0f, 5.0f}};
const int pyramid_edges[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0},
{0, 4}, {1, 4}, {2, 4}, {3, 4}};
const float pyramid_speed[] = {0.0f, 0.0f, 0.03f};
const Object pyramid = {.name = "pyramid",
.points = pyramid_points,
.edges = pyramid_edges,
.number_of_points = 5,
.number_of_edges = 8,
.position = &pyramid_position,
.rotate_speed = &pyramid_speed};
Point prism_points[] = { // Point pyramid_points[] = {{.coordinates = {-1.0f, 0.0f, -1.0f}},
{.coordinates = {0.0f, 0.0f, 0.0f}}, {.coordinates = {1.0f, 0.0f, 0.0f}}, // {.coordinates = {1.0f, 0.0f, -1.0f}},
{.coordinates = {0.5f, 0.0f, 1.0f}}, {.coordinates = {0.0f, 1.0f, 0.0f}}, // {.coordinates = {1.0f, 0.0f, 1.0f}},
{.coordinates = {1.0f, 1.0f, 0.0f}}, {.coordinates = {0.5f, 1.0f, 1.0f}}}; // {.coordinates = {-1.0f, 0.0f, 1.0f}},
Point prism_position = {.coordinates = {10.0f, 10.0f, 10.0f}}; // {.coordinates = {0.0f, 1.5f, 0.0f}}};
const int prism_edges[][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, // const int pyramid_edges[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0},
{5, 3}, {0, 3}, {1, 4}, {2, 5}}; // {0, 4}, {1, 4}, {2, 4}, {3, 4}};
const float prism_speed[] = {0.0f, 0.03f, 0.0f}; // const int pyramid_face0[] = {0, 1, 2, 3};
const Object prism = {.name = "prism", // const int pyramid_face1[] = {1, 0, 4};
.points = prism_points, // const int pyramid_face2[] = {2, 1, 4};
.edges = prism_edges, // const int pyramid_face3[] = {3, 2, 4};
.number_of_points = 6, // const int pyramid_face4[] = {0, 3, 4};
.number_of_edges = 9, // const int *pyramid_faces[] = {pyramid_face0, pyramid_face1, pyramid_face2,
.position = &prism_position, // pyramid_face3, pyramid_face4};
.rotate_speed = &prism_speed}; // const int pyramid_face_sizes[] = {4, 3, 3, 3, 3};
const int number_of_objects = 3; // const float pyramid_speed[] = {0.03f, 0.03f, -0.03f};
Object objects[] = {cube, pyramid, prism}; // Point pyramid_position = {.coordinates = {6.0f, 3.0f, 5.0f}};
// const Object pyramid = {.name = "pyramid",
// .points = pyramid_points,
// .edges = pyramid_edges,
// .faces = pyramid_faces,
// .face_sizes = pyramid_face_sizes,
// .number_of_points = 5,
// .number_of_edges = 8,
// .number_of_faces = 5,
// .position = &pyramid_position,
// .rotate_speed = &pyramid_speed};
// // == Prism ==
// Point prism_points[] = {
// {.coordinates = {0.0f, 0.0f, 0.0f}}, {.coordinates = {1.0f, 0.0f, 0.0f}},
// {.coordinates = {0.5f, 0.0f, 1.0f}}, {.coordinates = {0.0f, 1.0f, 0.0f}},
// {.coordinates = {1.0f, 1.0f, 0.0f}}, {.coordinates =
// {0.5f, 1.0f, 1.0f}}};
// const int prism_edges[][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5},
// {5, 3}, {0, 3}, {1, 4}, {2, 5}};
// const int prism_face0[] = {0, 1, 2};
// const int prism_face1[] = {3, 5, 4};
// const int prism_face2[] = {0, 3, 4, 1};
// const int prism_face3[] = {1, 4, 5, 2};
// const int prism_face4[] = {2, 5, 3, 0};
// const int *prism_faces[] = {prism_face0, prism_face1, prism_face2,
// prism_face3,
// prism_face4};
// const int prism_face_sizes[] = {3, 3, 4, 4, 4};
// const float prism_speed[] = {-0.03f, 0.03f, 0.03f};
// Point prism_position = {.coordinates = {10.0f, 10.0f, 10.0f}};
// const Object prism = {.name = "prism",
// .points = prism_points,
// .edges = prism_edges,
// .faces = prism_faces,
// .face_sizes = prism_face_sizes,
// .number_of_points = 6,
// .number_of_edges = 9,
// .number_of_faces = 5,
// .position = &prism_position,
// .rotate_speed = &prism_speed};
const int number_of_objects = 1;
// Object objects[] = {cube, pyramid, prism};
Object objects[] = {cube};
Point camera_position = {.coordinates = {0.0f, 0.0f, 0.0f}}; Point camera_position = {.coordinates = {0.0f, 0.0f, 0.0f}};
Point camera_target = {.coordinates = {1.0f, 1.0f, 1.0f}}; Point camera_target = {.coordinates = {1.0f, 1.0f, 1.0f}};

View File

@@ -4,6 +4,7 @@
#include <stdint.h> #include <stdint.h>
#define TWI_FREQ 400000UL // 400 kHz #define TWI_FREQ 400000UL // 400 kHz
// #define TWI_FREQ 100000UL // 400 kHz
void i2c_init(void); void i2c_init(void);
uint8_t i2c_start(uint8_t address); uint8_t i2c_start(uint8_t address);

View File

@@ -1,5 +1,5 @@
#include "../I2C/I2C.h"
#include "MPU6050.h" #include "MPU6050.h"
#include "../I2C/I2C.h"
#include <avr/io.h> #include <avr/io.h>
#include <util/delay.h> #include <util/delay.h>
@@ -76,7 +76,6 @@ void mpu6050_set_gyro_offsets(int16_t x, int16_t y, int16_t z) {
gyro_offset_y = y; gyro_offset_y = y;
gyro_offset_z = z; gyro_offset_z = z;
} }
void mpu6050_read_gyro(float *gx, float *gy, float *gz) { void mpu6050_read_gyro(float *gx, float *gy, float *gz) {
uint8_t buf[6]; uint8_t buf[6];
mpu6050_read_burst(MPU6050_REG_GYRO_XOUT_H, buf, 6); mpu6050_read_burst(MPU6050_REG_GYRO_XOUT_H, buf, 6);

View File

@@ -3,6 +3,10 @@
#include <stdint.h> #include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// Адрес по умолчанию: AD0 = GND → 0x68 → сдвинутый = 0xD0 // Адрес по умолчанию: AD0 = GND → 0x68 → сдвинутый = 0xD0
// Если AD0 = VCC → 0x69 → 0xD2 // Если AD0 = VCC → 0x69 → 0xD2
#define MPU6050_ADDR 0xD0 #define MPU6050_ADDR 0xD0
@@ -18,4 +22,8 @@ void mpu6050_set_gyro_offsets(int16_t x, int16_t y, int16_t z);
void mpu6050_calibrate_gyro(int16_t *gx_offset, int16_t *gy_offset, void mpu6050_calibrate_gyro(int16_t *gx_offset, int16_t *gy_offset,
int16_t *gz_offset); int16_t *gz_offset);
#ifdef __cplusplus
}
#endif
#endif #endif

View File

@@ -1,257 +0,0 @@
#include "ST7789.h"
#define DC_HIGH() (ST7789_PORT |= (1 << DC_PIN))
#define DC_LOW() (ST7789_PORT &= ~(1 << DC_PIN))
#define RESET_HIGH() (ST7789_PORT |= (1 << RESET_PIN))
#define RESET_LOW() (ST7789_PORT &= ~(1 << RESET_PIN))
#define CS_HIGH() (ST7789_PORT |= (1 << CS_PIN))
#define CS_LOW() (ST7789_PORT &= ~(1 << CS_PIN))
static void _spi_init(void) {
// Настройка пинов SPI: MOSI (PB3), SCK (PB5) как выходы
DDRB |= (1 << PB3) | (1 << PB5);
// Включение SPI: Master, режим 0, частота F_CPU/4
SPCR = (1 << SPE) | (1 << MSTR);
SPSR = (1 << SPI2X); // Удвоение скорости (F_CPU/2)
}
static void _spi_write(uint8_t data) {
SPDR = data;
while (!(SPSR & (1 << SPIF)))
;
}
void _st7789_write_command(uint8_t cmd) {
DC_LOW();
CS_LOW();
_spi_write(cmd);
CS_HIGH();
}
void _st7789_write_data(uint8_t data) {
DC_HIGH();
CS_LOW();
_spi_write(data);
CS_HIGH();
}
void _st7789_write_data_16(uint16_t data) {
DC_HIGH();
CS_LOW();
_spi_write(data >> 8); // Старший байт
_spi_write(data & 0xFF); // Младший байт
CS_HIGH();
}
void _st7789_set_address_window(uint16_t x0, uint16_t y0, uint16_t x1,
uint16_t y1) {
// Установка столбцов (CASET)
_st7789_write_command(0x2A);
_st7789_write_data(x0 >> 8);
_st7789_write_data(x0 & 0xFF);
_st7789_write_data(x1 >> 8);
_st7789_write_data(x1 & 0xFF);
// Установка строк (RASET)
_st7789_write_command(0x2B);
_st7789_write_data(y0 >> 8);
_st7789_write_data(y0 & 0xFF);
_st7789_write_data(y1 >> 8);
_st7789_write_data(y1 & 0xFF);
// Команда записи в память (RAMWR)
_st7789_write_command(0x2C);
}
void st7789_init(void) {
// Настройка пинов управления
ST7789_DDR |= (1 << DC_PIN) | (1 << RESET_PIN) | (1 << CS_PIN);
// Инициализация SPI
_spi_init();
// Процедура сброса дисплея
RESET_HIGH();
_delay_ms(10);
RESET_LOW();
_delay_ms(10);
RESET_HIGH();
_delay_ms(150);
// Последовательность инициализации ST7789
_st7789_write_command(0x01); // SWRESET: программный сброс
_delay_ms(150);
_st7789_write_command(0x11); // SLPOUT: выход из спящего режима
_delay_ms(255);
_st7789_write_command(0x3A); // COLMOD: установка формата цвета
_st7789_write_data(0x05); // 16 бит на пиксель (RGB565)
// ВАЖНО: Установка горизонтальной ориентации
_st7789_write_command(0x36); // MADCTL: управление ориентацией
_st7789_write_data(MADCTL_LANDSCAPE); // Горизонтальная ориентация
// Дополнительные настройки для лучшего отображения
_st7789_write_command(0xB2); // PORCTRL: настройка porch
_st7789_write_data(0x0C);
_st7789_write_data(0x0C);
_st7789_write_data(0x00);
_st7789_write_data(0x33);
_st7789_write_data(0x33);
_st7789_write_command(0xB7); // GCTRL: настройка gate control
_st7789_write_data(0x35);
_st7789_write_command(0xBB); // VCOMS: настройка VCOM
_st7789_write_data(0x2B);
_st7789_write_command(0xC0); // LCMCTRL: настройка LCM
_st7789_write_data(0x2C);
_st7789_write_command(0xC2); // VDVVRHEN: настройка VDV и VRH
_st7789_write_data(0x01);
_st7789_write_data(0xFF);
_st7789_write_command(0xC3); // VRHS: настройка VRH
_st7789_write_data(0x11);
_st7789_write_command(0xC4); // VDVS: настройка VDV
_st7789_write_data(0x20);
_st7789_write_command(0xC6); // FRCTRL2: настройка частоты
_st7789_write_data(0x0F);
_st7789_write_command(0xD0); // PWCTRL1: настройка питания
_st7789_write_data(0xA4);
_st7789_write_data(0xA1);
// Включение нормального режима и дисплея
_st7789_write_command(0x21); // INVON: инверсия цветов (опционально)
_st7789_write_command(0x13); // NORON: нормальный режим
_delay_ms(10);
_st7789_write_command(0x29); // DISPON: включение дисплея
_delay_ms(100);
}
void st7789_fill_screen(uint16_t color) {
// Установка окна на весь экран
_st7789_set_address_window(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
// Быстрая заливка через непрерывную передачу
DC_HIGH();
CS_LOW();
uint32_t total_pixels = (uint32_t)DISPLAY_WIDTH * DISPLAY_HEIGHT;
// Оптимизированная заливка экрана
for (uint32_t i = 0; i < total_pixels; i++) {
_spi_write(color >> 8); // Старший байт цвета
_spi_write(color & 0xFF); // Младший байт цвета
}
CS_HIGH();
}
void st7789_draw_hline(uint16_t x, uint16_t y, uint16_t length,
uint16_t color) {
// Проверка границ
if (y >= DISPLAY_HEIGHT)
return;
if (x >= DISPLAY_WIDTH)
return;
uint16_t x_end = x + length - 1;
if (x_end >= DISPLAY_WIDTH)
x_end = DISPLAY_WIDTH - 1;
_st7789_set_address_window(x, y, x_end, y);
DC_HIGH();
CS_LOW();
for (uint16_t i = x; i <= x_end; i++) {
_spi_write(color >> 8);
_spi_write(color & 0xFF);
}
CS_HIGH();
}
/**
* @brief Рисование вертикальной линии
* @param x X координата
* @param y Начальная Y координата
* @param length Длина линии
* @param color Цвет линии
*/
void st7789_draw_vline(uint16_t x, uint16_t y, uint16_t length,
uint16_t color) {
// Проверка границ
if (x >= DISPLAY_WIDTH)
return;
if (y >= DISPLAY_HEIGHT)
return;
uint16_t y_end = y + length - 1;
if (y_end >= DISPLAY_HEIGHT)
y_end = DISPLAY_HEIGHT - 1;
_st7789_set_address_window(x, y, x, y_end);
DC_HIGH();
CS_LOW();
for (uint16_t i = y; i <= y_end; i++) {
_spi_write(color >> 8);
_spi_write(color & 0xFF);
}
CS_HIGH();
}
void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
uint16_t color) {
// Оптимизация для горизонтальных и вертикальных линий
if (y0 == y1) {
if (x0 < x1)
st7789_draw_hline(x0, y0, x1 - x0 + 1, color);
else
st7789_draw_hline(x1, y0, x0 - x1 + 1, color);
return;
}
if (x0 == x1) {
if (y0 < y1)
st7789_draw_vline(x0, y0, y1 - y0 + 1, color);
else
st7789_draw_vline(x0, y1, y0 - y1 + 1, color);
return;
}
// Алгоритм Брезенхэма для произвольных линий
int16_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
int16_t dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
while (1) {
// Рисование пикселя (с проверкой границ)
if (x0 < DISPLAY_WIDTH && y0 < DISPLAY_HEIGHT) {
_st7789_set_address_window(x0, y0, x0, y0);
_st7789_write_data_16(color);
}
if (x0 == x1 && y0 == y1)
break;
int16_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}

View File

@@ -1,46 +0,0 @@
#ifndef ST7789_H
#define ST7789_H
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#define RGB565(r, g, b) \
((((255 - r) & 0xF8) << 8) | (((255 - g) & 0xFC) << 3) | ((255 - b) >> 3))
#define COLOR_BLACK RGB565(0, 0, 0)
#define COLOR_WHITE RGB565(255, 255, 255)
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 128
#define ST7789_PORT PORTB
#define ST7789_DDR DDRB
#define DC_PIN PB0 // Data/Command
#define RESET_PIN PB1 // Reset
#define CS_PIN PB2 // Chip Select
#define MADCTL_MY 0x80 // Page Address Order (обратный по Y)
#define MADCTL_MX 0x40 // Column Address Order (обратный по X)
#define MADCTL_MV 0x20 // Page/Column Order (перестановка X/Y)
#define MADCTL_ML 0x10 // Line Address Order (обратный порядок строк)
#define MADCTL_RGB 0x00 // RGB порядок цветов
#define MADCTL_BGR 0x08 // BGR порядок цветов
#define MADCTL_MH 0x04 // Display Data Latch Order
#define MADCTL_LANDSCAPE (MADCTL_MV | MADCTL_MX)
void st7789_init(void);
void st7789_fill_screen(uint16_t color);
void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
uint16_t color);
void st7789_draw_hline(uint16_t x, uint16_t y, uint16_t length, uint16_t color);
void st7789_draw_vline(uint16_t x, uint16_t y, uint16_t length, uint16_t color);
// void _st7789_write_command(uint8_t cmd);
// void _st7789_write_data(uint8_t data);
// void _st7789_write_data_16(uint16_t data);
// void _st7789_set_address_window(uint16_t x0, uint16_t y0, uint16_t x1,
// uint16_t y1);
#endif // ST7789_H

View File

@@ -0,0 +1,732 @@
/*
Copyright (c) 2010 Myles Metzer
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
/*
This library provides a simple method for outputting data to a tv
from a frame buffer stored in sram. This implementation is done
completly by interupt and will return give as much cpu time to the
application as possible.
*/
#include "TVout.h"
/* call this to start video output with the default resolution.
Arguments:
mode: The video standard to follow:
PAL = 1 = _PAL
NTSC = 0 = _NTSC
Returns:
0 if no error.
4 if there is not enough memory.
*/
uint8_t TVout::begin(uint8_t mode) { return begin(mode, 128, 96); }
/* call this to start video output with a specified resolution.
Arguments:
mode: The video standard to follow:
PAL = 1 = _PAL
NTSC = 0 = _NTSC
x: Horizonal resolution must be divisable by 8.
y: Vertical resolution.
Returns:
0 if no error.
1 if x is not divisable by 8.
2 if y is to large (NTSC only cannot fill PAL vertical resolution by
8bit limit) 4 if there is not enough memory for the frame buffer.
*/
uint8_t TVout::begin(uint8_t mode, uint8_t x, uint8_t y) {
// check if x is divisable by 8
if (x % 8) {
return 1;
}
x = x / 8;
screen = (unsigned char *)malloc(x * y * sizeof(unsigned char));
if (screen == NULL) {
return 4;
}
_cursorX = 0;
_cursorY = 0;
render_setup(mode, x, y, screen);
clearScreen();
return 0;
}
// stop video render and free the used memory
void TVout::end() {
TIMSK1 = 0;
free(screen);
}
/* Gets the Horizontal resolution of the screen
Returns: The horizonal resolution.
*/
unsigned int TVout::hres() { return display.hres * 8; }
/* Gets the Vertical resolution of the screen
Returns: The vertical resolution
*/
unsigned int TVout::vres() { return display.vres; }
/* Return the number of characters that will fit on a line
Returns: The number of characters that will fit on a text line starting
from x = 0. Will return -1 for dynamic width fonts as this cannot be
determined.
*/
/* Delay for x ms
The resolution is 16ms for NTSC and 20ms for PAL
Arguments:
x: The number of ms this function should consume.
*/
void TVout::delay(unsigned int x) {
unsigned long time = millis() + x;
while (millis() < time)
;
}
/* Delay for x frames, exits at the end of the last display line.
delayFrame(1) is useful prior to drawing so there is little/no flicker.
Arguments:
x: The number of frames to delay for.
*/
void TVout::delayFrame(unsigned int x) {
int stop_line = (int)(display.start_render +
(display.vres * (display.vscale_const + 1))) +
1;
while (x) {
while (display.scanLine != stop_line)
;
while (display.scanLine == stop_line)
;
x--;
}
}
/* Get the time in ms since begin was called.
The resolution is 16ms for NTSC and 20ms for PAL
Returns: The time in ms since video generation has started.
*/
unsigned long TVout::millis() {
if (display.lines_frame == _NTSC_LINE_FRAME) {
return display.frames * _NTSC_TIME_SCANLINE * _NTSC_LINE_FRAME / 1000;
} else {
return display.frames * _PAL_TIME_SCANLINE * _PAL_LINE_FRAME / 1000;
}
}
void TVout::clearScreen() { fill(0); }
void TVout::invert() { fill(2); }
/* Fill the screen with some color.
Arguments:
color: The color to fill the screen with
*/
void TVout::fill(uint8_t color) {
switch (color) {
case BLACK:
_cursorX = 0;
_cursorY = 0;
for (int i = 0; i < (display.hres) * display.vres; i++)
display.screen[i] = 0;
break;
case WHITE:
_cursorX = 0;
_cursorY = 0;
for (int i = 0; i < (display.hres) * display.vres; i++)
display.screen[i] = 0xFF;
break;
case INVERT:
for (int i = 0; i < display.hres * display.vres; i++)
display.screen[i] = ~display.screen[i];
break;
}
}
/* Set the color of a pixel
Arguments:
x: The x coordinate of the pixel.
y: The y coordinate of the pixel.
c: The color of the pixel
*/
void TVout::setPixel(uint8_t x, uint8_t y, char c) {
if (x >= display.hres * 8 || y >= display.vres)
return;
sp(x, y, c);
}
/* get the color of the pixel at x, y
Arguments:
x: The x coordinate of the pixel.
y: The y coordinate of the pixel.
Returns:
The color of the pixel
*/
bool TVout::getPixel(uint8_t x, uint8_t y) {
if (x >= display.hres * 8 || y >= display.vres)
return false;
if (display.screen[x / 8 + y * display.hres] & (0x80 >> (x & 7)))
return true;
return false;
}
/* Draw a line from one point to another
Arguments:
x0: The x coordinate of point 0.
y0: The y coordinate of point 0.
x1: The x coordinate of point 1.
y1: The y coordinate of point 1.
c: The color of the line.
*/
void TVout::drawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, char c) {
if (x0 > display.hres * 8 || y0 > display.vres || x1 > display.hres * 8 ||
y1 > display.vres)
return;
if (x0 == x1)
drawColumn(x0, y0, y1, c);
else if (y0 == y1)
drawRow(y0, x0, x1, c);
else {
int e;
int dx, dy, j, temp;
char s1, s2, xchange;
int x, y;
x = x0;
y = y0;
// take absolute value
if (x1 < x0) {
dx = x0 - x1;
s1 = -1;
} else if (x1 == x0) {
dx = 0;
s1 = 0;
} else {
dx = x1 - x0;
s1 = 1;
}
if (y1 < y0) {
dy = y0 - y1;
s2 = -1;
} else if (y1 == y0) {
dy = 0;
s2 = 0;
} else {
dy = y1 - y0;
s2 = 1;
}
xchange = 0;
if (dy > dx) {
temp = dx;
dx = dy;
dy = temp;
xchange = 1;
}
e = ((int)dy << 1) - dx;
for (j = 0; j <= dx; j++) {
sp(x, y, c);
if (e >= 0) {
if (xchange == 1) {
x = x + s1;
} else {
y = y + s2;
}
e = e - ((int)dx << 1);
}
if (xchange == 1) {
y = y + s2;
} else {
x = x + s1;
}
e = e + ((int)dy << 1);
}
}
}
/* Fill a row from one point to another
Argument:
line: The row that fill will be performed on.
x0: edge 0 of the fill.
x1: edge 1 of the fill.
c: the color of the fill.
*/
void TVout::drawRow(uint8_t line, uint16_t x0, uint16_t x1, uint8_t c) {
uint8_t lbit, rbit;
if (x0 == x1) {
setPixel(x0, line, c);
} else {
if (x0 > x1) {
lbit = x0;
x0 = x1;
x1 = lbit;
}
lbit = 0xff >> (x0 & 7);
x0 = x0 / 8 + display.hres * line;
rbit = ~(0xff >> (x1 & 7));
x1 = x1 / 8 + display.hres * line;
if (x0 == x1) {
lbit = lbit & rbit;
rbit = 0;
}
if (c == WHITE) {
screen[x0++] |= lbit;
while (x0 < x1) {
screen[x0++] = 0xff;
}
screen[x0] |= rbit;
} else if (c == BLACK) {
screen[x0++] &= ~lbit;
while (x0 < x1) {
screen[x0++] = 0;
}
screen[x0] &= ~rbit;
} else if (c == INVERT) {
screen[x0++] ^= lbit;
while (x0 < x1) {
screen[x0++] ^= 0xff;
}
screen[x0] ^= rbit;
}
}
}
/* Fill a column from one point to another
Argument:
row: The row that fill will be performed on.
y0: edge 0 of the fill.
y1: edge 1 of the fill.
c: the color of the fill.
*/
void TVout::drawColumn(uint8_t row, uint16_t y0, uint16_t y1, uint8_t c) {
unsigned char bit;
int byte;
if (y0 == y1) {
setPixel(row, y0, c);
} else {
if (y1 < y0) {
bit = y0;
y0 = y1;
y1 = bit;
}
bit = 0x80 >> (row & 7);
byte = row / 8 + y0 * display.hres;
if (c == WHITE) {
while (y0 <= y1) {
screen[byte] |= bit;
byte += display.hres;
y0++;
}
} else if (c == BLACK) {
while (y0 <= y1) {
screen[byte] &= ~bit;
byte += display.hres;
y0++;
}
} else if (c == INVERT) {
while (y0 <= y1) {
screen[byte] ^= bit;
byte += display.hres;
y0++;
}
}
}
}
/* draw a rectangle at x,y with a specified width and height
Arguments:
x0: The x coordinate of upper left corner of the rectangle.
y0: The y coordinate of upper left corner of the rectangle.
w: The widht of the rectangle.
h: The height of the rectangle.
c: The color of the rectangle.
fc: The fill color of the rectangle.
*/
void TVout::drawRect(uint8_t x0, uint8_t y0, uint8_t w, uint8_t h, char c,
char fc) {
if (fc != -1) {
for (unsigned char i = y0; i < y0 + h; i++) {
drawRow(i, x0, x0 + w, fc);
}
}
drawLine(x0, y0, x0 + w, y0, c);
drawLine(x0, y0, x0, y0 + h, c);
drawLine(x0 + w, y0, x0 + w, y0 + h, c);
drawLine(x0, y0 + h, x0 + w, y0 + h, c);
}
/* draw a circle given a coordinate x,y and radius both filled and non filled.
Arguments:
x0: The x coordinate of the center of the circle.
y0: The y coordinate of the center of the circle.
radius: The radius of the circle.
c: The color of the circle.
fc: The color to fill the circle.
*/
void TVout::drawCircle(uint8_t x0, uint8_t y0, uint8_t radius, char c,
char fc) {
int f = 1 - radius;
int ddF_x = 1;
int ddF_y = -2 * radius;
int x = 0;
int y = radius;
uint8_t pyy = y, pyx = x;
// there is a fill color
if (fc != -1) {
drawRow(y0, x0 - radius, x0 + radius, fc);
}
sp(x0, y0 + radius, c);
sp(x0, y0 - radius, c);
sp(x0 + radius, y0, c);
sp(x0 - radius, y0, c);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
// there is a fill color
if (fc != -1) {
// prevent double draws on the same rows
if (pyy != y) {
drawRow(y0 + y, x0 - x, x0 + x, fc);
drawRow(y0 - y, x0 - x, x0 + x, fc);
}
if (pyx != x && x != y) {
drawRow(y0 + x, x0 - y, x0 + y, fc);
drawRow(y0 - x, x0 - y, x0 + y, fc);
}
pyy = y;
pyx = x;
}
sp(x0 + x, y0 + y, c);
sp(x0 - x, y0 + y, c);
sp(x0 + x, y0 - y, c);
sp(x0 - x, y0 - y, c);
sp(x0 + y, y0 + x, c);
sp(x0 - y, y0 + x, c);
sp(x0 + y, y0 - x, c);
sp(x0 - y, y0 - x, c);
}
}
/* place a bitmap at x,y where the bitmap is defined as
{width,height,imagedata....} Arguments: x: The x coordinate of the upper
left corner. y: The y coordinate of the upper left corner. bmp: The
bitmap data to print. i: The offset into the image data to start at.
This is mainly used for fonts default = 0 width: Override the bitmap width.
This is mainly used for fonts. default = 0 (do not override) height: Override
the bitmap height. This is mainly used for fonts. default = 0 (do not
override)
*/
void TVout::bitmap(uint8_t x, uint8_t y, const unsigned char *bmp, uint16_t i,
uint8_t width, uint8_t lines) {
uint8_t temp, lshift, rshift, save, xtra;
uint16_t si = 0;
rshift = x & 7;
lshift = 8 - rshift;
if (width == 0) {
width = pgm_read_byte((uint32_t)(bmp) + i);
i++;
}
if (lines == 0) {
lines = pgm_read_byte((uint32_t)(bmp) + i);
i++;
}
if (width & 7) {
xtra = width & 7;
width = width / 8;
width++;
} else {
xtra = 8;
width = width / 8;
}
for (uint8_t l = 0; l < lines; l++) {
si = (y + l) * display.hres + x / 8;
if (width == 1) {
temp = 0xff >> rshift + xtra;
} else {
temp = 0;
}
save = screen[si];
screen[si] &= ((0xff << lshift) | temp);
temp = pgm_read_byte((uint32_t)(bmp) + i++);
screen[si++] |= temp >> rshift;
for (uint16_t b = i + width - 1; i < b; i++) {
save = screen[si];
screen[si] = temp << lshift;
temp = pgm_read_byte((uint32_t)(bmp) + i);
screen[si++] |= temp >> rshift;
}
if (rshift + xtra < 8) {
screen[si - 1] |= (save & (0xff >> rshift + xtra));
}
if (rshift + xtra - 8 > 0) {
screen[si] &= (0xff >> rshift + xtra - 8);
}
screen[si] |= temp << lshift;
}
}
/* shift the pixel buffer in any direction
This function will shift the screen in a direction by any distance.
Arguments:
distance: The distance to shift the screen
direction: The direction to shift the screen the direction and the
integer values: UP = 0 DOWN = 1 LEFT = 2 RIGHT = 3
*/
void TVout::shift(uint8_t distance, uint8_t direction) {
uint8_t *src;
uint8_t *dst;
uint8_t *end;
uint8_t shift;
uint8_t tmp;
switch (direction) {
case UP:
dst = display.screen;
src = display.screen + distance * display.hres;
end = display.screen + display.vres * display.hres;
while (src <= end) {
*dst = *src;
*src = 0;
dst++;
src++;
}
break;
case DOWN:
dst = display.screen + display.vres * display.hres;
src = dst - distance * display.hres;
end = display.screen;
while (src >= end) {
*dst = *src;
*src = 0;
dst--;
src--;
}
break;
case LEFT:
shift = distance & 7;
for (uint8_t line = 0; line < display.vres; line++) {
dst = display.screen + display.hres * line;
src = dst + distance / 8;
end = dst + display.hres - 2;
while (src <= end) {
tmp = 0;
tmp = *src << shift;
*src = 0;
src++;
tmp |= *src >> (8 - shift);
*dst = tmp;
dst++;
}
tmp = 0;
tmp = *src << shift;
*src = 0;
*dst = tmp;
}
break;
case RIGHT:
shift = distance & 7;
for (uint8_t line = 0; line < display.vres; line++) {
dst = display.screen + display.hres - 1 + display.hres * line;
src = dst - distance / 8;
end = dst - display.hres + 2;
while (src >= end) {
tmp = 0;
tmp = *src >> shift;
*src = 0;
src--;
tmp |= *src << (8 - shift);
*dst = tmp;
dst--;
}
tmp = 0;
tmp = *src >> shift;
*src = 0;
*dst = tmp;
}
break;
}
}
/* Simple tone generation
Arguments:
frequency: the frequency of the tone courtesy of adamwwolf
*/
void TVout::tone(unsigned int frequency) { tone(frequency, 0); }
/* Simple tone generation
Arguments:
frequency: the frequency of the tone
durationMS: the duration to play the tone in ms courtesy of adamwwolf
*/
void TVout::tone(unsigned int frequency, unsigned long durationMS) {
if (frequency == 0) {
return;
}
#define TIMER 2
// this is init code
TCCR2A = 0;
TCCR2B = 0;
TCCR2A |= _BV(WGM21);
TCCR2B |= _BV(CS20);
// end init code
// most of this is taken from Tone.cpp from Arduino
uint8_t prescalarbits = 0b001;
uint32_t ocr = 0;
// set pb3 (digital pin 11) to output
DDR_SND |= _BV(SND_PIN);
// we are using an 8 bit timer, scan through prescalars to find the best fit
ocr = F_CPU / frequency / 2 - 1;
// ck/1: same for both timers
prescalarbits = 0b001;
if (ocr > 255) {
ocr = F_CPU / frequency / 2 / 8 - 1;
// ck/8: same for both timers
prescalarbits = 0b010;
if (ocr > 255) {
ocr = F_CPU / frequency / 2 / 32 - 1;
prescalarbits = 0b011;
}
if (ocr > 255) {
ocr = F_CPU / frequency / 2 / 64 - 1;
prescalarbits = TIMER == 0 ? 0b011 : 0b100;
if (ocr > 255) {
ocr = F_CPU / frequency / 2 / 128 - 1;
prescalarbits = 0b101;
}
if (ocr > 255) {
ocr = F_CPU / frequency / 2 / 256 - 1;
prescalarbits = TIMER == 0 ? 0b100 : 0b110;
if (ocr > 255) {
// can't do any better than / 1024
ocr = F_CPU / frequency / 2 / 1024 - 1;
prescalarbits = TIMER == 0 ? 0b101 : 0b111;
}
}
}
}
TCCR2B = prescalarbits;
if (durationMS > 0) {
// 60 here represents the framerate
remainingToneVsyncs = durationMS * 60 / 1000;
} else {
remainingToneVsyncs = -1;
}
// set the OCR for the given timer,
OCR2A = ocr;
// set it to toggle the pin by itself
// set COM2A1 to 0
TCCR2A &= ~(_BV(COM2A1));
TCCR2A |= _BV(COM2A0);
}
/* stops tone generation
*/
void TVout::noTone() {
TCCR2B = 0;
// set pin 11 to 0
PORT_SND &= ~(_BV(SND_PIN));
}
/* set the vertical blank function call
The function passed to this function will be called one per frame. The
function should be quickish. Arguments: func: The function to call
*/
void TVout::setVBIHook(void (*func)()) { vbi_hook = func; }
/* set the horizonal blank function call
This function passed to this function will be called one per scan line.
The function MUST be VERY FAST(~2us max).
Arguments:
funct: The function to call
*/
void TVout::setHBIHook(void (*func)()) { hbi_hook = func; }
/* force the number of times to display each line
Arguments:
sfactor: The scale number of times to repeate each line.
*/
void TVout::forceVscale(char sfactor) {
delayFrame(1);
display.vscale_const = sfactor - 1;
display.vscale = sfactor - 1;
}
/* force the output start time of a scanline in micro seconds
Arguments:
time: The new output start time in micro seconds.
*/
void TVout::forceOutStart(uint8_t time) {
delayFrame(1);
display.output_delay = ((time * _CYCLES_PER_US) - 1);
}
/* force the start line for active video
Arguments:
line: The new active video output start line
*/
void TVout::forceLineStart(uint8_t line) {
delayFrame(1);
display.start_render = line;
}
/* Inline version of setPixel that does not perform a bounds check
*/
static void inline sp(uint8_t x, uint8_t y, char c) {
if (c == 1) {
display.screen[(x / 8) + (y * display.hres)] |= 0x80 >> (x & 7);
} else if (c == 0) {
display.screen[(x / 8) + (y * display.hres)] &= ~0x80 >> (x & 7);
} else {
display.screen[(x / 8) + (y * display.hres)] ^= 0x80 >> (x & 7);
}
}

View File

@@ -0,0 +1,103 @@
/*
Copyright (c) 2010 Myles Metzer
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TVOUT_H
#define TVOUT_H
#include "video_gen.h"
#include <avr/pgmspace.h>
#include <stdlib.h>
#define PAL 1
#define NTSC 0
#define _PAL 1
#define _NTSC 0
#define WHITE 1
#define BLACK 0
#define INVERT 2
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
#define BYTE 0
class TVout {
public:
uint8_t *screen;
uint8_t begin(uint8_t mode);
uint8_t begin(uint8_t mode, uint8_t x, uint8_t y);
void end();
// accessor functions
unsigned int hres();
unsigned int vres();
char charLine();
// flow control functions
void delay(unsigned int x);
void delayFrame(unsigned int x);
unsigned long millis();
// basic rendering functions
void clearScreen();
void invert();
void fill(uint8_t color);
void setPixel(uint8_t x, uint8_t y, char c);
bool getPixel(uint8_t x, uint8_t y);
void drawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, char c);
void drawRow(uint8_t line, uint16_t x0, uint16_t x1, uint8_t c);
void drawColumn(uint8_t row, uint16_t y0, uint16_t y1, uint8_t c);
void drawRect(uint8_t x0, uint8_t y0, uint8_t w, uint8_t h, char c,
char fc = -1);
void drawCircle(uint8_t x0, uint8_t y0, uint8_t radius, char c, char fc = -1);
void bitmap(uint8_t x, uint8_t y, const unsigned char *bmp, uint16_t i = 0,
uint8_t width = 0, uint8_t lines = 0);
void shift(uint8_t distance, uint8_t direction);
// tone functions
void tone(unsigned int frequency, unsigned long durationMS);
void tone(unsigned int frequency);
void noTone();
private:
// hook setup functions
void setVBIHook(void (*func)());
void setHBIHook(void (*func)());
// override setup functions
void forceVscale(char sfactor);
void forceOutStart(uint8_t time);
void forceLineStart(uint8_t line);
void incTxtLine();
uint8_t _cursorX, _cursorY;
};
static void inline sp(uint8_t x, uint8_t y, char c);
#endif

View File

@@ -0,0 +1,119 @@
#include "hardware_setup.h"
#ifndef ASM_MACROS_H
#define ASM_MACROS_H
// delay macros
__asm__ __volatile__ (
// delay 1 clock cycle.
".macro delay1\n\t"
"nop\n"
".endm\n"
// delay 2 clock cycles
".macro delay2\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 3 clock cyles
".macro delay3\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 4 clock cylces
".macro delay4\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 5 clock cylces
".macro delay5\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 6 clock cylces
".macro delay6\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 7 clock cylces
".macro delay7\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 8 clock cylces
".macro delay8\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 9 clock cylces
".macro delay9\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
// delay 10 clock cylces
".macro delay10\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n"
".endm\n"
);
// common output macros, specific output macros at top of file
__asm__ __volatile__ (
// save port 16 and clear the video bit
".macro svprt p\n\t"
"in r16,\\p\n\t"
ANDI_HWS
".endm\n"
// ouput 1 bit port safe
".macro o1bs p\n\t"
BLD_HWS
"out \\p,r16\n"
".endm\n"
);
#endif

View File

@@ -0,0 +1,146 @@
// sound is output on OC2A
// sync output is on OC1A
// ENABLE_FAST_OUTPUT chooses the highest bit of a port over the original output method
// comment out this line to switch back to the original output pins.
#define ENABLE_FAST_OUTPUT
#ifndef HARDWARE_SETUP_H
#define HARDWARE_SETUP_H
// device specific settings.
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__)
#if defined(ENABLE_FAST_OUTPUT)
#define PORT_VID PORTA
#define DDR_VID DDRA
#define VID_PIN 7
#else
//video
#define PORT_VID PORTB
#define DDR_VID DDRB
#define VID_PIN 6
#endif
//sync
#define PORT_SYNC PORTB
#define DDR_SYNC DDRB
#define SYNC_PIN 5
//sound
#define PORT_SND PORTB
#define DDR_SND DDRB
#define SND_PIN 4
#elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__)
//video
#if defined(ENABLE_FAST_OUTPUT)
#define PORT_VID PORTA
#define DDR_VID DDRA
#define VID_PIN 7
#else
#define PORT_VID PORTD
#define DDR_VID DDRD
#define VID_PIN 4
#endif
//sync
#define PORT_SYNC PORTD
#define DDR_SYNC DDRD
#define SYNC_PIN 5
//sound
#define PORT_SND PORTD
#define DDR_SND DDRD
#define SND_PIN 7
// Arduino UNO
#elif defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
//video
#if defined(ENABLE_FAST_OUTPUT)
#define PORT_VID PORTD
#define DDR_VID DDRD
#define VID_PIN 7
#else
#define PORT_VID PORTB
#define DDR_VID DDRB
#define VID_PIN 0
#endif
//sync
#define PORT_SYNC PORTB
#define DDR_SYNC DDRB
#define SYNC_PIN 1
//sound
#define PORT_SND PORTB
#define DDR_SND DDRB
#define SND_PIN 3
// Arduino DUE
#elif defined (__AVR_AT90USB1286__)
//video
#define PORT_VID PORTF
#define DDR_VID DDRF
#define VID_PIN 7
//sync
#define PORT_SYNC PORTB
#define DDR_SYNC DDRB
#define SYNC_PIN 5
//sound
#define PORT_SND PORTB
#define DDR_SND DDRB
#define SND_PIN 4
// Arduino Leonardo
#elif defined(__AVR_ATmega32U4__)
// video arduino pin 8
#define PORT_VID PORTB
#define DDR_VID DDRB
#define VID_PIN 4
// sync arduino pin 9
#define PORT_SYNC PORTB
#define DDR_SYNC DDRB
#define SYNC_PIN 5
// sound arduino pin 11
#define PORT_SND PORTB
#define DDR_SND DDRB
#define SND_PIN 7
#define TCCR2A TCCR0A
#define TCCR2B TCCR0B
#define OCR2A OCR0A
#define OCR2B OCR0B
#define COM2A0 COM0A0
#define COM2A1 COM0A1
#define CS20 CS00
#define WGM21 WGM01
#endif
//automatic BST/BLD/ANDI macro definition
#if VID_PIN == 0
#define BLD_HWS "bld r16,0\n\t"
#define BST_HWS "bst r16,0\n\t"
#define ANDI_HWS "andi r16,0xFE\n"
#elif VID_PIN == 1
#define BLD_HWS "bld r16,1\n\t"
#define BST_HWS "bst r16,1\n\t"
#define ANDI_HWS "andi r16,0xFD\n"
#elif VID_PIN == 2
#define BLD_HWS "bld r16,2\n\t"
#define BST_HWS "bst r16,2\n\t"
#define ANDI_HWS "andi r16,0xFB\n"
#elif VID_PIN == 3
#define BLD_HWS "bld r16,3\n\t"
#define BST_HWS "bst r16,3\n\t"
#define ANDI_HWS "andi r16,0xF7\n"
#elif VID_PIN == 4
#define BLD_HWS "bld r16,4\n\t"
#define BST_HWS "bst r16,4\n\t"
#define ANDI_HWS "andi r16,0xEF\n"
#elif VID_PIN == 5
#define BLD_HWS "bld r16,5\n\t"
#define BST_HWS "bst r16,5\n\t"
#define ANDI_HWS "andi r16,0xDF\n"
#elif VID_PIN == 6
#define BLD_HWS "bld r16,6\n\t"
#define BST_HWS "bst r16,6\n\t"
#define ANDI_HWS "andi r16,0xBF\n"
#elif VID_PIN == 7
#define BLD_HWS "bld r16,7\n\t"
#define BST_HWS "bst r16,7\n\t"
#define ANDI_HWS "andi r16,0x7F\n"
#endif
#endif

View File

@@ -0,0 +1,41 @@
/* This File contains the timing definitions for the TVout AVR composite video
generation Library
*/
#ifndef VIDEO_TIMING_H
#define VIDEO_TIMING_H
#define _CYCLES_PER_US (F_CPU / 1000000)
#define _TIME_HORZ_SYNC 4.7
#define _TIME_VIRT_SYNC 58.85
#define _TIME_ACTIVE 46
#define _CYCLES_VIRT_SYNC ((_TIME_VIRT_SYNC * _CYCLES_PER_US) - 1)
#define _CYCLES_HORZ_SYNC ((_TIME_HORZ_SYNC * _CYCLES_PER_US) - 1)
// Timing settings for NTSC
#define _NTSC_TIME_SCANLINE 63.55
#define _NTSC_TIME_OUTPUT_START 12
#define _NTSC_LINE_FRAME 262
#define _NTSC_LINE_START_VSYNC 0
#define _NTSC_LINE_STOP_VSYNC 3
#define _NTSC_LINE_DISPLAY 216
#define _NTSC_LINE_MID ((_NTSC_LINE_FRAME - _NTSC_LINE_DISPLAY) / 2 + _NTSC_LINE_DISPLAY / 2)
#define _NTSC_CYCLES_SCANLINE ((_NTSC_TIME_SCANLINE * _CYCLES_PER_US) - 1)
#define _NTSC_CYCLES_OUTPUT_START ((_NTSC_TIME_OUTPUT_START * _CYCLES_PER_US) - 1)
// Timing settings for PAL
#define _PAL_TIME_SCANLINE 64
#define _PAL_TIME_OUTPUT_START 12.5
#define _PAL_LINE_FRAME 312
#define _PAL_LINE_START_VSYNC 0
#define _PAL_LINE_STOP_VSYNC 7
#define _PAL_LINE_DISPLAY 260
#define _PAL_LINE_MID ((_PAL_LINE_FRAME - _PAL_LINE_DISPLAY) / 2 + _PAL_LINE_DISPLAY / 2)
#define _PAL_CYCLES_SCANLINE ((_PAL_TIME_SCANLINE * _CYCLES_PER_US) - 1)
#define _PAL_CYCLES_OUTPUT_START ((_PAL_TIME_OUTPUT_START * _CYCLES_PER_US) - 1)
#endif

View File

@@ -0,0 +1,517 @@
/*
Copyright (c) 2010 Myles Metzer
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#include "video_gen.h"
#include "spec/asm_macros.h"
//#define REMOVE6C
//#define REMOVE5C
//#define REMOVE4C
//#define REMOVE3C
int renderLine;
TVout_vid display;
void (*render_line)();
void (*line_handler)();
void (*hbi_hook)() = &empty;
void (*vbi_hook)() = &empty;
// sound properties
volatile long remainingToneVsyncs;
void empty() {
}
// start timer
void render_setup(uint8_t mode, uint8_t x, uint8_t y, uint8_t *scrnptr) {
display.screen = scrnptr;
display.hres = x;
display.vres = y;
display.frames = 0;
if (mode) {
display.vscale_const = _PAL_LINE_DISPLAY / display.vres - 1;
} else {
display.vscale_const = _NTSC_LINE_DISPLAY / display.vres - 1;
}
display.vscale = display.vscale_const;
// selects the widest render method that fits in 46us
// as of 9/16/10 rendermode 3 will not work for resolutions lower than
// 192(display.hres lower than 24)
unsigned char rmethod = (_TIME_ACTIVE*_CYCLES_PER_US) / (display.hres * 8);
switch(rmethod) {
case 6:
render_line = &render_line6c;
break;
case 5:
render_line = &render_line5c;
break;
case 4:
render_line = &render_line4c;
break;
case 3:
render_line = &render_line3c;
break;
default:
if (rmethod > 6) {
render_line = &render_line6c;
} else {
render_line = &render_line3c;
}
}
DDR_VID |= _BV(VID_PIN);
DDR_SYNC |= _BV(SYNC_PIN);
PORT_VID &= ~_BV(VID_PIN);
PORT_SYNC |= _BV(SYNC_PIN);
// for tone generation
DDR_SND |= _BV(SND_PIN);
// inverted fast pwm mode on timer 1
TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11);
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);
if (mode) {
display.start_render = _PAL_LINE_MID - ((display.vres * (display.vscale_const + 1)) / 2);
display.output_delay = _PAL_CYCLES_OUTPUT_START;
display.vsync_end = _PAL_LINE_STOP_VSYNC;
display.lines_frame = _PAL_LINE_FRAME;
ICR1 = _PAL_CYCLES_SCANLINE;
OCR1A = _CYCLES_HORZ_SYNC;
} else {
display.start_render = _NTSC_LINE_MID - ((display.vres * (display.vscale_const+1))/2) + 8;
display.output_delay = _NTSC_CYCLES_OUTPUT_START;
display.vsync_end = _NTSC_LINE_STOP_VSYNC;
display.lines_frame = _NTSC_LINE_FRAME;
ICR1 = _NTSC_CYCLES_SCANLINE;
OCR1A = _CYCLES_HORZ_SYNC;
}
display.scanLine = display.lines_frame + 1;
line_handler = &vsync_line;
TIMSK1 = _BV(TOIE1);
sei();
}
// render a line
ISR(TIMER1_OVF_vect) {
hbi_hook();
line_handler();
}
// вызываем синхронизацию или прорисовку строки
void blank_line() {
if (display.scanLine == display.start_render) {
renderLine = 0;
display.vscale = display.vscale_const;
line_handler = &active_line;
} else if (display.scanLine == display.lines_frame) {
line_handler = &vsync_line;
vbi_hook();
}
display.scanLine++;
}
void active_line() {
wait_until(display.output_delay);
render_line();
if (!display.vscale) {
display.vscale = display.vscale_const;
renderLine += display.hres;
} else {
display.vscale--;
}
if ((display.scanLine + 1) == (int)(display.start_render + (display.vres * (display.vscale_const + 1)))) {
line_handler = &blank_line;
}
display.scanLine++;
}
// генерируем синхроимпульс
void vsync_line() {
if (display.scanLine >= display.lines_frame) {
OCR1A = _CYCLES_VIRT_SYNC;
display.scanLine = 0;
display.frames++;
if (remainingToneVsyncs != 0) {
if (remainingToneVsyncs > 0) {
remainingToneVsyncs--;
}
} else {
// stop the tone
TCCR2B = 0;
PORTB &= ~(_BV(SND_PIN));
}
} else if (display.scanLine == display.vsync_end) {
OCR1A = _CYCLES_HORZ_SYNC;
line_handler = &blank_line;
}
display.scanLine++;
}
static void inline wait_until(uint8_t time) {
__asm__ __volatile__ (
"subi %[time], 10\n"
"sub %[time], %[tcnt1l]\n\t"
"100:\n\t"
"subi %[time], 3\n\t"
"brcc 100b\n\t"
"subi %[time], 0-3\n\t"
"breq 101f\n\t"
"dec %[time]\n\t"
"breq 102f\n\t"
"rjmp 102f\n"
"101:\n\t"
"nop\n"
"102:\n"
:
: [time] "a" (time),
[tcnt1l] "a" (TCNT1L)
);
}
void render_line6c() {
#ifndef REMOVE6C
__asm__ __volatile__ (
"ADD r26,r28\n\t"
"ADC r27,r29\n\t"
// save PORTB
"svprt %[port]\n\t"
"rjmp enter6\n"
"loop6:\n\t"
// 8
"bst __tmp_reg__,0\n\t"
"o1bs %[port]\n"
"enter6:\n\t"
// 1
"LD __tmp_reg__,X+\n\t"
"delay1\n\t"
"bst __tmp_reg__,7\n\t"
"o1bs %[port]\n\t"
// 2
"delay3\n\t"
"bst __tmp_reg__,6\n\t"
"o1bs %[port]\n\t"
// 3
"delay3\n\t"
"bst __tmp_reg__,5\n\t"
"o1bs %[port]\n\t"
// 4
"delay3\n\t"
"bst __tmp_reg__,4\n\t"
"o1bs %[port]\n\t"
// 5
"delay3\n\t"
"bst __tmp_reg__,3\n\t"
"o1bs %[port]\n\t"
// 6
"delay3\n\t"
"bst __tmp_reg__,2\n\t"
"o1bs %[port]\n\t"
// 7
"delay3\n\t"
"bst __tmp_reg__,1\n\t"
"o1bs %[port]\n\t"
"dec %[hres]\n\t"
// go too loopsix
"brne loop6\n\t"
"delay2\n\t"
// 8
"bst __tmp_reg__,0\n\t"
"o1bs %[port]\n"
"svprt %[port]\n\t"
BST_HWS
"o1bs %[port]\n\t"
:
: [port] "i" (_SFR_IO_ADDR(PORT_VID)),
"x" (display.screen),
"y" (renderLine),
[hres] "d" (display.hres)
: "r16" // try to remove this clobber later...
);
#endif
}
void render_line5c() {
#ifndef REMOVE5C
__asm__ __volatile__ (
"ADD r26,r28\n\t"
"ADC r27,r29\n\t"
// save PORTB
"svprt %[port]\n\t"
"rjmp enter5\n"
"loop5:\n\t"
// 8
"bst __tmp_reg__,0\n\t"
"o1bs %[port]\n"
"enter5:\n\t"
// 1
"LD __tmp_reg__,X+\n\t"
"bst __tmp_reg__,7\n\t"
"o1bs %[port]\n\t"
// 2
"delay2\n\t"
"bst __tmp_reg__,6\n\t"
"o1bs %[port]\n\t"
// 3
"delay2\n\t"
"bst __tmp_reg__,5\n\t"
"o1bs %[port]\n\t"
// 4
"delay2\n\t"
"bst __tmp_reg__,4\n\t"
"o1bs %[port]\n\t"
// 5
"delay2\n\t"
"bst __tmp_reg__,3\n\t"
"o1bs %[port]\n\t"
// 6
"delay2\n\t"
"bst __tmp_reg__,2\n\t"
"o1bs %[port]\n\t"
// 7
"delay1\n\t"
"dec %[hres]\n\t"
"bst __tmp_reg__,1\n\t"
"o1bs %[port]\n\t"
// go too loop5
"brne loop5\n\t"
"delay1\n\t"
// 8
"bst __tmp_reg__,0\n\t"
"o1bs %[port]\n"
"svprt %[port]\n\t"
BST_HWS
"o1bs %[port]\n\t"
:
: [port] "i" (_SFR_IO_ADDR(PORT_VID)),
"x" (display.screen),
"y" (renderLine),
[hres] "d" (display.hres)
: "r16" // try to remove this clobber later...
);
#endif
}
void render_line4c() {
#ifndef REMOVE4C
__asm__ __volatile__ (
"ADD r26,r28\n\t"
"ADC r27,r29\n\t"
"rjmp enter4\n"
"loop4:\n\t"
// 8
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
"enter4:\n\t"
// 1
"LD __tmp_reg__,X+\n\t"
"delay1\n\t"
"out %[port],__tmp_reg__\n\t"
// 2
"delay2\n\t"
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
// 3
"delay2\n\t"
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
// 4
"delay2\n\t"
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
"delay2\n\t"
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
// 6
"delay2\n\t"
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
// 7
"delay1\n\t"
"lsl __tmp_reg__\n\t"
"dec %[hres]\n\t"
"out %[port],__tmp_reg__\n\t"
// go too loop4
"brne loop4\n\t"
// 8
"delay1\n\t"
"lsl __tmp_reg__\n\t"
"out %[port],__tmp_reg__\n\t"
"delay3\n\t"
"cbi %[port],7\n\t"
:
: [port] "i" (_SFR_IO_ADDR(PORT_VID)),
"x" (display.screen),
"y" (renderLine),
[hres] "d" (display.hres)
: "r16" // try to remove this clobber later...
);
#endif
}
// only 16mhz right now!!!
void render_line3c() {
#ifndef REMOVE3C
__asm__ __volatile__ (
".macro byteshift\n\t"
"LD __tmp_reg__,X+\n\t"
// 0
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 1
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 2
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 3
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 4
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 5
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 6
"out %[port],__tmp_reg__\n\t"
"nop\n\t"
"lsl __tmp_reg__\n\t"
// 7
"out %[port],__tmp_reg__\n\t"
".endm\n\t"
"ADD r26,r28\n\t"
"ADC r27,r29\n\t"
// 615
"cpi %[hres],30\n\t"
"breq skip0\n\t"
"cpi %[hres],29\n\t"
"breq jumpto1\n\t"
"cpi %[hres],28\n\t"
"breq jumpto2\n\t"
"cpi %[hres],27\n\t"
"breq jumpto3\n\t"
"cpi %[hres],26\n\t"
"breq jumpto4\n\t"
"cpi %[hres],25\n\t"
"breq jumpto5\n\t"
"cpi %[hres],24\n\t"
"breq jumpto6\n\t"
"jumpto1:\n\t"
"rjmp skip1\n\t"
"jumpto2:\n\t"
"rjmp skip2\n\t"
"jumpto3:\n\t"
"rjmp skip3\n\t"
"jumpto4:\n\t"
"rjmp skip4\n\t"
"jumpto5:\n\t"
"rjmp skip5\n\t"
"jumpto6:\n\t"
"rjmp skip6\n\t"
"skip0:\n\t"
//1 643
"byteshift\n\t"
"skip1:\n\t"
// 2
"byteshift\n\t"
"skip2:\n\t"
// 3
"byteshift\n\t"
"skip3:\n\t"
// 4
"byteshift\n\t"
"skip4:\n\t"
// 5
"byteshift\n\t"
"skip5:\n\t"
// 6
"byteshift\n\t"
"skip6:\n\t"
// 7
"byteshift\n\t"
// 8
"byteshift\n\t"
// 9
"byteshift\n\t"
// 10
"byteshift\n\t"
// 11
"byteshift\n\t"
// 12
"byteshift\n\t"
// 13
"byteshift\n\t"
// 14
"byteshift\n\t"
// 15
"byteshift\n\t"
// 16
"byteshift\n\t"
// 17
"byteshift\n\t"
// 18
"byteshift\n\t"
// 19
"byteshift\n\t"
// 20
"byteshift\n\t"
// 21
"byteshift\n\t"
// 22
"byteshift\n\t"
// 23
"byteshift\n\t"
// 24
"byteshift\n\t"
// 25
"byteshift\n\t"
// 26
"byteshift\n\t"
// 27
"byteshift\n\t"
// 28
"byteshift\n\t"
// 29
"byteshift\n\t"
// 30
"byteshift\n\t"
"delay2\n\t"
"cbi %[port],7\n\t"
:
: [port] "i" (_SFR_IO_ADDR(PORT_VID)),
"x" (display.screen),
"y" (renderLine),
[hres] "d" (display.hres)
: "r16" // try to remove this clobber later...
);
#endif
}

View File

@@ -0,0 +1,71 @@
/*
Copyright (c) 2010 Myles Metzer
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef VIDEO_GEN_H
#define VIDEO_GEN_H
#include <avr/interrupt.h>
#include <avr/io.h>
#include "spec/video_properties.h"
#include "spec/hardware_setup.h"
typedef struct {
volatile int scanLine;
volatile unsigned long frames;
unsigned char start_render;
// remove me
int lines_frame;
uint8_t vres;
uint8_t hres;
// remove me
uint8_t output_delay;
// combine me with status switch
char vscale_const;
// combine me too.
char vscale;
// remove me
char vsync_end;
uint8_t* screen;
} TVout_vid;
extern TVout_vid display;
extern void (*hbi_hook)();
extern void (*vbi_hook)();
void render_setup(uint8_t mode, uint8_t x, uint8_t y, uint8_t *scrnptr);
void blank_line();
void active_line();
void vsync_line();
void empty();
// tone generation properties
extern volatile long remainingToneVsyncs;
// 6cycles functions
void render_line6c();
void render_line5c();
void render_line4c();
void render_line3c();
static void inline wait_until(uint8_t time);
#endif

View File

@@ -1,40 +0,0 @@
#include "./lib/MPU6050/MPU6050.h"
#include "./lib/ST7789/ST7789.h"
#include "../platform.h"
#include "micro.h"
void draw_line(const ScreenPoint *sp1, const ScreenPoint *sp2,
const Color *const color) {
st7789_draw_line(sp1->coordinates[0], sp1->coordinates[1],
sp2->coordinates[0], sp2->coordinates[1],
RGB565(color->red, color->green, color->blue));
};
const Screen screen = {
.width = DISPLAY_WIDTH, .height = DISPLAY_HEIGHT, .draw_line = &draw_line};
const Color white = {255, 255, 255};
const Color black = {0, 0, 0};
float gx, gy, gz;
int main(void) {
st7789_init();
mpu6050_init();
init_engine();
st7789_fill_screen(RGB565(0, 0, 0));
while (1) {
render(&screen, &black);
mpu6050_read_gyro(&gx, &gy, &gz);
float angles_speed[3] = {gx / 255.0f, gy / 255.0f, gz / 255.0f};
rotate(angles_speed);
render(&screen, &white);
}
return 0;
}

View File

@@ -0,0 +1,39 @@
#include "micro.h"
#include "../platform.h"
#include "./lib/MPU6050/MPU6050.h"
#include "./lib/TVout/TVout.h"
#include <util/delay.h>
TVout TV;
void draw_line(const ScreenPoint *const sp1, const ScreenPoint *const sp2,
const Color *const color) {
TV.drawLine(sp1->coordinates[0], sp1->coordinates[1], sp2->coordinates[0],
sp2->coordinates[1], 1);
}
Screen screen = {.width = 128, .height = 64, .draw_line = &draw_line};
Color white = {.red = (char)255, .green = (char)255, .blue = (char)255};
float gx, gy, gz;
int main(void) {
TV.begin(NTSC, 128, 64);
mpu6050_init();
init_engine();
while (1) {
TV.clearScreen();
mpu6050_read_gyro(&gx, &gy, &gz);
float angles[3] = {gx / 250.0f, gy / 250.0f, gz / 250.0f};
rotate(angles);
render(&screen, &white);
// _delay_ms(1000 / FPS);
}
return 0;
}

View File

@@ -1,7 +1,6 @@
#ifndef MICRO_H #ifndef MICRO_H
#define MICRO_H #define MICRO_H
#include "./lib/ST7789/ST7789.h"
#include <stdlib.h> #include <stdlib.h>
#endif #endif

View File

@@ -4,6 +4,18 @@
#include "../utils/screen.h" #include "../utils/screen.h"
#include "log.h" #include "log.h"
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
EXTERNC void init_engine();
EXTERNC void render(const Screen *const screen, const Color *const color);
EXTERNC void tic();
EXTERNC void rotate(const float angles_speed[3]);
EXTERNC void destroy();
#ifdef WIN #ifdef WIN
#include "win/win.h" #include "win/win.h"
@@ -17,20 +29,14 @@
#ifdef MICRO #ifdef MICRO
#define FPS 25 #define FPS 20
#include "micro/micro.h" #define DISPLAY_WIDTH 32
#define DISPLAY_HEIGHT 24
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 128
#define WINDOW_WIDTH DISPLAY_WIDTH #define WINDOW_WIDTH DISPLAY_WIDTH
#define WINDOW_HEIGHT DISPLAY_HEIGHT #define WINDOW_HEIGHT DISPLAY_HEIGHT
#include "micro/micro.h"
#endif // MICRO #endif // MICRO
void init_engine();
void render(const Screen *const screen, const Color *const color);
void tic();
void rotate(const float angles_speed[3]);
void destroy();
#endif #endif