diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff5b5acd..4ce1de88 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,6 +85,8 @@ file( GLOB core_files cpld_normal.c cpld_alternative.h cpld_alternative.c + cpld_atom.h + cpld_atom.c osd.h osd.c saa5050_font.h diff --git a/src/cpld.h b/src/cpld.h index 2562dfb5..84abcbae 100644 --- a/src/cpld.h +++ b/src/cpld.h @@ -7,6 +7,7 @@ #define DESIGN_NORMAL 0 #define DESIGN_ALTERNATIVE 1 +#define DESIGN_ATOM 2 typedef struct { int key; diff --git a/src/cpld_atom.c b/src/cpld_atom.c new file mode 100644 index 00000000..dba523e1 --- /dev/null +++ b/src/cpld_atom.c @@ -0,0 +1,391 @@ +#include +#include +#include +#include +#include +#include "defs.h" +#include "cpld.h" +#include "osd.h" +#include "logging.h" +#include "rpi-gpio.h" + +// The number of frames to compute differences over +#define NUM_CAL_FRAMES 10 + +typedef struct { + int sp_offset; + int h_offset; // horizontal offset (in psync clocks) + int v_offset; // vertical offset (in lines) + int h_width; // active horizontal width (in 8-bit characters) + int v_height; // active vertical height (in lines) + int fb_width; // framebuffer width in pixels + int fb_height; // framebuffer height in pixels + int clock; // cpld clock (in Hz) + int line_len; // number of clocks per horizontal line + int n_lines; // number of horizontal lines per frame (two fields) + int divider; // cpld divider: 0=divide-by-6; 1 = divide-by-8 +} config_t; + +// Current calibration state for mode 0..6 +static config_t default_config; + +// Current configuration +static config_t *config; + +// OSD message buffer +static char message[80]; + +// Per-Offset calibration metrics (i.e. errors) for mode 0..6 +static int raw_metrics_default[8][NUM_OFFSETS]; + +// Aggregate calibration metrics (i.e. errors summed across all offsets) for mode 0..6 +static int sum_metrics_default[8]; // Last two not used + +// Error count for final calibration values for mode 0..6 +static int errors_default; + +// The CPLD version number +static int cpld_version; + +// ============================================================= +// Param definitions for OSD +// ============================================================= + +enum { + // Sampling params + OFFSET, + // Geometry params + H_OFFSET, + V_OFFSET, + H_WIDTH, + V_HEIGHT, + FB_WIDTH, + FB_HEIGHT, + CLOCK, + LINE_LEN, + N_LINES, + DIVIDER, +}; + +static param_t default_sampling_params[] = { + { OFFSET, "Offset", 0, 7 }, + { -1, NULL, 0, 0 } +}; + + +static param_t default_geometry_params[] = { + { H_OFFSET, "H offset", 0, 59 }, + { V_OFFSET, "V offset", 0, 39 }, + { H_WIDTH, "H width", 1, 100 }, + { V_HEIGHT, "V height", 1, 300 }, + { FB_WIDTH, "FB width", 400, 800 }, + { FB_HEIGHT, "FB height", 320, 600 }, + { CLOCK, "Clock freq", 75000000, 100000000 }, + { LINE_LEN, "Line length", 1000, 9999 }, + { N_LINES, "Lines per frame", 500, 699 }, + { DIVIDER, "Divider", 0, 1 }, + { -1, NULL, 0, 0 } +}; + +// ============================================================= +// Private methods +// ============================================================= + +static void write_config(config_t *config) { + int sp = 0; + int scan_len = 3; + sp |= config->sp_offset & 7; + for (int i = 0; i < scan_len; i++) { + RPI_SetGpioValue(SP_DATA_PIN, sp & 1); + for (int j = 0; j < 1000; j++); + RPI_SetGpioValue(SP_CLKEN_PIN, 1); + for (int j = 0; j < 100; j++); + RPI_SetGpioValue(SP_CLK_PIN, 0); + RPI_SetGpioValue(SP_CLK_PIN, 1); + for (int j = 0; j < 100; j++); + RPI_SetGpioValue(SP_CLKEN_PIN, 0); + for (int j = 0; j < 1000; j++); + sp >>= 1; + } + RPI_SetGpioValue(SP_DATA_PIN, 0); +} + +static void osd_sp(config_t *config, int line, int metric) { + // Line ------ + sprintf(message, "Offset: %d", config->sp_offset); + osd_set(line, 0, message); + line++; + // Line ------ + if (metric < 0) { + sprintf(message, " Errors: unknown"); + } else { + sprintf(message, " Errors: %d", metric); + } + osd_set(line, 0, message); + line++; +} + +static void log_sp(config_t *config) { + log_info("sp_offset = %d", config->sp_offset); +} + +// ============================================================= +// Public methods +// ============================================================= + +static void cpld_init(int version) { + cpld_version = version; + default_config.h_offset = 19; + default_config.v_offset = 11; + default_config.h_width = 76; + default_config.v_height = 240; + default_config.fb_width = 608; + default_config.fb_height = 480; + default_config.clock = 57272720; + default_config.line_len = 3648; + default_config.n_lines = 524; + default_config.divider = 0; + default_config.sp_offset = 2; + config = &default_config; + for (int i = 0; i < 8; i++) { + sum_metrics_default[i] = -1; + for (int j = 0; j < NUM_OFFSETS; j++) { + raw_metrics_default[i][j] = -1; + } + } + errors_default = -1; +} + +static int cpld_get_version() { + return cpld_version; +} + +static int sum_channels(int *rgb) { + return rgb[CHAN_RED] + rgb[CHAN_GREEN] + rgb[CHAN_BLUE]; +} + +static void cpld_calibrate(capture_info_t *capinfo, int elk) { + int min_i = 0; + int metric; // this is a point value (at one sample offset) + int min_metric; + int win_metric; // this is a windowed value (over three sample offsets) + int min_win_metric; + int *rgb_metric; + + int range; // 0..5 in Modes 0..6, 0..7 in Mode 7 + int *sum_metrics; + int *errors; + + int (*raw_metrics)[8][NUM_OFFSETS]; + + log_info("Calibrating..."); + range = 8; + raw_metrics = &raw_metrics_default; + sum_metrics = &sum_metrics_default[0]; + errors = &errors_default; + + // Measure the error metrics at all possible offset values + min_metric = INT_MAX; + printf("INFO: "); + for (int i = 0; i < NUM_OFFSETS; i++) { + printf("%7c", 'A' + i); + } + printf(" total\r\n"); + for (int value = 0; value < range; value++) { + config->sp_offset = value; + write_config(config); + rgb_metric = diff_N_frames_by_sample(capinfo, NUM_CAL_FRAMES, 0, elk); + metric = 0; + printf("INFO: value = %d: metrics = ", value); + for (int i = 0; i < NUM_OFFSETS; i++) { + int offset_metric = sum_channels(rgb_metric + i * NUM_CHANNELS); + (*raw_metrics)[value][i] = offset_metric; + metric += offset_metric; + printf("%7d", offset_metric); + } + printf("%8d\r\n", metric); + sum_metrics[value] = metric; + osd_sp(config, 1, metric); + if (metric < min_metric) { + min_metric = metric; + } + } + + // Use a 3 sample window to find the minimum and maximum + min_win_metric = INT_MAX; + for (int i = 0; i < range; i++) { + int left = (i - 1 + range) % range; + int right = (i + 1 + range) % range; + win_metric = sum_metrics[left] + sum_metrics[i] + sum_metrics[right]; + if (sum_metrics[i] == min_metric) { + if (win_metric < min_win_metric) { + min_win_metric = win_metric; + min_i = i; + } + } + } + + // In all modes, start with the min metric + config->sp_offset = min_i; + log_sp(config); + write_config(config); + + // Perform a final test of errors + log_info("Performing final test"); + rgb_metric = diff_N_frames(capinfo, NUM_CAL_FRAMES, 0, elk); + *errors = sum_channels(rgb_metric); + osd_sp(config, 1, *errors); + log_sp(config); + log_info("Calibration complete, errors = %d", *errors); +} + +static void cpld_set_mode(int mode) { + config = &default_config; + write_config(config); + // The "mode7" pin on the CPLD switches between 6 clocks per + // pixel (modes 0..6) and 8 clocks per pixel (mode 7) + RPI_SetGpioValue(MODE7_PIN, config->divider); +} + +static void cpld_get_fb_params(capture_info_t *capinfo) { + capinfo->h_offset = config->h_offset; + capinfo->v_offset = config->v_offset; + capinfo->chars_per_line = config->h_width; + capinfo->nlines = config->v_height; + capinfo->width = config->fb_width; + capinfo->height = config->fb_height; +} + +static void cpld_get_clk_params(clk_info_t *clkinfo) { + clkinfo->clock = config->clock; + clkinfo->line_len = config->line_len; + clkinfo->n_lines = config->n_lines; +} + +static param_t *cpld_get_sampling_params() { + return default_sampling_params; +} + +static param_t *cpld_get_geometry_params() { + return default_geometry_params; +} + +static int cpld_get_value(int num) { + switch (num) { + case OFFSET: + return config->sp_offset; + case H_OFFSET: + return config->h_offset; + case V_OFFSET: + return config->v_offset; + case H_WIDTH: + return config->h_width; + case V_HEIGHT: + return config->v_height; + case FB_WIDTH: + return config->fb_width; + case FB_HEIGHT: + return config->fb_height; + case CLOCK: + return config->clock; + case LINE_LEN: + return config->line_len; + case N_LINES: + return config->n_lines; + case DIVIDER: + return config->divider; + } + return 0; +} + +static void cpld_set_value(int num, int value) { + switch (num) { + case OFFSET: + config->sp_offset = value; + break; + case H_OFFSET: + config->h_offset = value; + break; + case V_OFFSET: + config->v_offset = value; + break; + case H_WIDTH: + config->h_width = value; + break; + case V_HEIGHT: + config->v_height = value; + break; + case FB_WIDTH: + config->fb_width = value; + break; + case FB_HEIGHT: + config->fb_height = value; + break; + case CLOCK: + config->clock = value; + break; + case LINE_LEN: + config->line_len = value; + break; + case N_LINES: + config->n_lines = value; + break; + case DIVIDER: + config->divider = value; + break; + } + write_config(config); +} + +static void cpld_show_cal_summary(int line) { + osd_sp(config, line, errors_default); +} + +static void cpld_show_cal_details(int line) { + int *sum_metrics = sum_metrics_default; + int range = (*sum_metrics < 0) ? 0 : 8; + if (range == 0) { + sprintf(message, "No calibration data for this mode"); + osd_set(line, 0, message); + } else { + for (int value = 0; value < range; value++) { + sprintf(message, "Offset %d: Errors = %6d", value, sum_metrics[value]); + osd_set(line + value, 0, message); + } + } +} + +static void cpld_show_cal_raw(int line) { + int (*raw_metrics)[8][NUM_OFFSETS] = &raw_metrics_default; + int range = ((*raw_metrics)[0][0] < 0) ? 0 : 8; + if (range == 0) { + sprintf(message, "No calibration data for this mode"); + osd_set(line, 0, message); + } else { + for (int value = 0; value < range; value++) { + char *mp = message; + mp += sprintf(mp, "%d:", value); + for (int i = 0; i < NUM_OFFSETS; i++) { + mp += sprintf(mp, "%6d", (*raw_metrics)[value][i]); + } + osd_set(line + value, 0, message); + } + } +} + +cpld_t cpld_atom = { + .name = "Atom", + .init = cpld_init, + .get_version = cpld_get_version, + .calibrate = cpld_calibrate, + .set_mode = cpld_set_mode, + .get_fb_params = cpld_get_fb_params, + .get_clk_params = cpld_get_clk_params, + .get_sampling_params = cpld_get_sampling_params, + .get_geometry_params = cpld_get_geometry_params, + .get_value = cpld_get_value, + .set_value = cpld_set_value, + .show_cal_summary = cpld_show_cal_summary, + .show_cal_details = cpld_show_cal_details, + .show_cal_raw = cpld_show_cal_raw +}; diff --git a/src/cpld_atom.h b/src/cpld_atom.h new file mode 100644 index 00000000..03745ba7 --- /dev/null +++ b/src/cpld_atom.h @@ -0,0 +1,7 @@ +#ifndef CPLD_ATOM_H +#define CPLD_ATOM_H +#include "cpld.h" + +extern cpld_t cpld_atom; + +#endif diff --git a/src/rgb_to_hdmi.c b/src/rgb_to_hdmi.c index 0c18f1c4..d6b38a9e 100644 --- a/src/rgb_to_hdmi.c +++ b/src/rgb_to_hdmi.c @@ -19,6 +19,7 @@ #include "cpld.h" #include "cpld_normal.h" #include "cpld_alternative.h" +#include "cpld_atom.h" #include "rgb_to_fb.h" // #define INSTRUMENT_CAL @@ -547,6 +548,8 @@ static void cpld_init() { cpld = &cpld_normal; } else if ((cpld_version_id >> VERSION_DESIGN_BIT) == DESIGN_ALTERNATIVE) { cpld = &cpld_alternative; + } else if ((cpld_version_id >> VERSION_DESIGN_BIT) == DESIGN_ATOM) { + cpld = &cpld_atom; } else { log_info("Unknown CPLD: identifier = %03x", cpld_version_id); log_info("Halting\n"); diff --git a/src/scripts/cmdline.txt b/src/scripts/cmdline.txt index cf234369..6d904714 100644 --- a/src/scripts/cmdline.txt +++ b/src/scripts/cmdline.txt @@ -176,5 +176,8 @@ sampling06=3 sampling7=0,2,2,2,2,2,2,0,5 info=1 palette=0 deinterlace=5 scanline # Here's an example that might work with an Atom (CPLDv2) (even wider border) #sampling06=5 geometry06=24,16,76,240,608,480,85909091,5472,524,0 nbuffers=2 m7disable=1 # +# Here's an example that might work with an Atom (Atom CPLD) +#sampling06=7 geometry06=19,11,76,240,608,480,57272720,3648,524,0 nbuffers=2 m7disable=1 +# # Here's an example that might work with a ZX80/ZX81 #sampling06=4 geometry06=9,18,84,270,672,540,78000000,4968,620,0 nbuffers=2 m7disable=1 keymap=1323232 return=0