440 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			440 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// Concept tester for interfacing from the raspberry ppi
 | 
						|
// with theGigatron TTL microcomputer using a microcontroller
 | 
						|
// hooked to the input port (J4).
 | 
						|
//  (adapted from ../Utils/LoaderTest/LoaderTest.ino)
 | 
						|
 | 
						|
// /!\ it is not very reliable. /!\
 | 
						|
// The problem is that the raspberry pi is not a realtime system, whereas precise timing is needed to
 | 
						|
// communicate with the gigatron.
 | 
						|
// So:
 | 
						|
//  - on the one hand, the program tries to hint the kernel that it really 
 | 
						|
//        needs highpriority as-real-time-as-possible scheduling during certain periods 
 | 
						|
//        (and try to yield control back to the system when it needs to wait, hopping that the 
 | 
						|
//        system tasks will be scheduled at those times)
 | 
						|
//  - on the other hand it measures the time spent during a payload frame, and try to detect if
 | 
						|
//        it has been interrupted (sequence took longer than expected), and thus that the frame
 | 
						|
//        is probably corrupted on the receiving end.
 | 
						|
//  - if it thinks the frame is corrupt, reset the checksum and tries to send it again.
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <sys/mman.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <sched.h>
 | 
						|
#include <time.h>
 | 
						|
 | 
						|
typedef unsigned char byte;
 | 
						|
typedef int bool;
 | 
						|
 | 
						|
// === GPIO acess ============
 | 
						|
// ===========================
 | 
						|
//pins:  		 rpi2 BCM	 / rpi2 board  /  Gigatron DB9 (J4)
 | 
						|
static int GROUND    = 0;	//	34				8
 | 
						|
static int SER_DATA  = 12;	//	32				2 blanc 
 | 
						|
static int SER_LATCH = 16;	//	36				3 vert 
 | 
						|
static int SER_PULSE = 20;	//	38				4 rouge
 | 
						|
 | 
						|
 | 
						|
// ------------------------------------------ 
 | 
						|
#define BCM2708_PERI_BASE  0x20000000	//rpi1
 | 
						|
#define BCM2709_PERI_BASE	0x3F000000	//rpi2
 | 
						|
#define GPIO_BASE  (BCM2709_PERI_BASE + 0x200000) /* GPIO controller */
 | 
						|
#define FSEL_OFFSET                 0   // 0x0000
 | 
						|
#define SET_OFFSET                  7   // 0x001c / 4
 | 
						|
#define CLR_OFFSET                  10  // 0x0028 / 4
 | 
						|
#define PINLEVEL_OFFSET             13  // 0x0034 / 4
 | 
						|
 | 
						|
volatile unsigned *gpio;
 | 
						|
void gpio_setup()
 | 
						|
{
 | 
						|
	int  mem_fd;
 | 
						|
	if ((mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC) ) < 0) {
 | 
						|
		printf("can't open /dev/gpiomem \n");
 | 
						|
		exit(-1);
 | 
						|
	}
 | 
						|
 | 
						|
	size_t BLOCK_SIZE = (4*1024);
 | 
						|
	static void *gpio_map;
 | 
						|
	gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0);
 | 
						|
	close(mem_fd); //No need to keep mem_fd open after mmap
 | 
						|
 | 
						|
	if (gpio_map == MAP_FAILED) {
 | 
						|
		printf("mmap error %d %m\n", (int)gpio_map);
 | 
						|
		exit(-1);
 | 
						|
	}
 | 
						|
 | 
						|
	gpio = (volatile unsigned *)gpio_map;
 | 
						|
 | 
						|
	// try minimizing interruption causes...
 | 
						|
	mlockall(MCL_CURRENT); // (probably useless very little mem is used)
 | 
						|
}
 | 
						|
 | 
						|
void gpio_setrealtimesched(int enable)
 | 
						|
{
 | 
						|
	if (enable) {
 | 
						|
		struct sched_param sp = {32};
 | 
						|
		int err = sched_setscheduler(0, SCHED_FIFO, &sp);
 | 
						|
		if (err!=0) {
 | 
						|
			printf("could not switch to realtime prio: %m\n");
 | 
						|
		}
 | 
						|
		// nb for pthreads: pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp))
 | 
						|
	} else {
 | 
						|
		struct sched_param sp = {0};
 | 
						|
		int err = sched_setscheduler(0, SCHED_OTHER, &sp);
 | 
						|
		if (err!=0) {
 | 
						|
			printf("could not switch back to normal prio: %m\n");
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void gpio_setmode_out(int pin)
 | 
						|
{
 | 
						|
	int offset = FSEL_OFFSET + (pin/10);
 | 
						|
	int shift = (pin%10)*3;
 | 
						|
	*(gpio+offset) = (*(gpio+offset) & ~(7<<shift)) | (1<<shift);
 | 
						|
}
 | 
						|
 | 
						|
void gpio_setmode_in(int pin)
 | 
						|
{
 | 
						|
	int offset = FSEL_OFFSET + (pin/10);
 | 
						|
	int shift = (pin%10)*3;
 | 
						|
	*(gpio+offset) = (*(gpio+offset) & ~(7<<shift));
 | 
						|
}
 | 
						|
 | 
						|
void gpio_write(int pin, int val) 
 | 
						|
{
 | 
						|
	int offset = val ? SET_OFFSET :  CLR_OFFSET;
 | 
						|
	offset += (pin/32);
 | 
						|
	int mask = 1<<(pin%32);
 | 
						|
 | 
						|
	*(gpio+offset) = mask;
 | 
						|
}
 | 
						|
 | 
						|
bool gpio_read(int pin)
 | 
						|
{
 | 
						|
	int offset = PINLEVEL_OFFSET + (pin/32);
 | 
						|
	int mask = 1<<(pin%32);
 | 
						|
	int val = *(gpio+offset);
 | 
						|
	return (val & mask) ? 1 : 0;
 | 
						|
}
 | 
						|
 | 
						|
// === timing ============
 | 
						|
// ===========================
 | 
						|
static unsigned Clock()		// microsecond
 | 
						|
{
 | 
						|
	static struct timespec Init = {0};
 | 
						|
	if (Init.tv_sec == 0)
 | 
						|
		clock_gettime(CLOCK_MONOTONIC, &Init);
 | 
						|
 | 
						|
	struct timespec Now = {0};
 | 
						|
	clock_gettime(CLOCK_MONOTONIC, &Now);
 | 
						|
	
 | 
						|
	return ((Now.tv_sec - Init.tv_sec)*1000000)+((Now.tv_nsec - Init.tv_nsec)/1000);
 | 
						|
}
 | 
						|
 | 
						|
static void Delay(unsigned ms)	//milliseconds
 | 
						|
{
 | 
						|
	usleep(ms * 1000);
 | 
						|
}
 | 
						|
 | 
						|
void Nop()
 | 
						|
{
 | 
						|
//	DbgTiming_ticks++;
 | 
						|
}
 | 
						|
 | 
						|
// === Gigatron ============
 | 
						|
// ===========================
 | 
						|
static const byte buttonRight  = 1;
 | 
						|
static const byte buttonLeft   = 2;
 | 
						|
static const byte buttonDown   = 4;
 | 
						|
static const byte buttonUp     = 8;
 | 
						|
static const byte buttonStart  = 16;
 | 
						|
static const byte buttonSelect = 32;
 | 
						|
static const byte buttonB      = 64;
 | 
						|
static const byte buttonA      = 128;
 | 
						|
 | 
						|
bool DetectGigatron()
 | 
						|
{
 | 
						|
	gpio_setrealtimesched(1);
 | 
						|
 | 
						|
	unsigned timeout = Clock() + 85*1000;
 | 
						|
 | 
						|
	unsigned T[4] = {0,0,0,0};
 | 
						|
	while (Clock() < timeout) {
 | 
						|
		int latch = gpio_read(SER_LATCH);
 | 
						|
		int pulse = gpio_read(SER_PULSE);
 | 
						|
		T[latch*2+pulse*1] ++;
 | 
						|
	}
 | 
						|
 | 
						|
	unsigned Total = T[0] + T[1] + T[2] + T[3];
 | 
						|
	float vSync = (float)(T[0] + T[1]) / ( 8 * Total / 521.0); // Adjusted vSync signal
 | 
						|
	float hSync = (float)(T[0] + T[2]) / (96 * Total / 800.0); // Standard hSync signal
 | 
						|
 | 
						|
	gpio_setrealtimesched(0);
 | 
						|
 | 
						|
	printf("detection: ticks=%d  vSync=%g  hSync=%g\n", Total, vSync, hSync);
 | 
						|
	return (0.95 <= vSync && vSync <= 1.20) && (0.95 <= hSync && hSync <= 1.05);
 | 
						|
}
 | 
						|
 | 
						|
// wait vsync and send one byte
 | 
						|
void SendFirstByte(byte val)
 | 
						|
{
 | 
						|
	// Wait vertical sync NEGATIVE edge to sync with loader
 | 
						|
	while (!gpio_read(SER_LATCH)) {Nop();} 	// Ensure vSync is HIGH first
 | 
						|
	while (gpio_read(SER_LATCH))  {Nop();}	// Then wait for vSync to drop
 | 
						|
 | 
						|
	unsigned mask = 0x80;
 | 
						|
	while(mask) {
 | 
						|
		int bit = (val&mask)?1:0;
 | 
						|
		mask = mask >> 1;
 | 
						|
 | 
						|
		// Send bit
 | 
						|
		gpio_write(SER_DATA, bit);
 | 
						|
 | 
						|
		// Wait for bit transfer at horizontal sync POSITIVE edge.
 | 
						|
		// This timing is tight for the first bit of the first byte and
 | 
						|
		while (gpio_read(SER_PULSE))  {Nop();} // Ensure hSync is LOW first
 | 
						|
		while (!gpio_read(SER_PULSE)) {Nop();} // Then wait for hSync to rise
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void SendController(int button, int frames)
 | 
						|
{
 | 
						|
	gpio_setrealtimesched(1);
 | 
						|
 | 
						|
	// Note: The kit's controller gives inverted signals.
 | 
						|
	for (int i=0; i<frames; i++) {
 | 
						|
		SendFirstByte(~button);
 | 
						|
		gpio_write(SER_DATA, 1);	// Send 1 when idle
 | 
						|
		Delay(10);	// try to focus os interruptions out of the time sensitive code.
 | 
						|
	}
 | 
						|
 | 
						|
	gpio_setrealtimesched(0);
 | 
						|
}
 | 
						|
 | 
						|
void SendReset()
 | 
						|
{
 | 
						|
	SendController(buttonStart, 128+32);
 | 
						|
	
 | 
						|
	// Wait for main menu to be ready
 | 
						|
	Delay(1500);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void StartLoader()
 | 
						|
{
 | 
						|
	// Navigate menu. 'Loader' is at the bottom
 | 
						|
	for (int i=0; i<10; i++) {
 | 
						|
		SendController(buttonDown, 2);
 | 
						|
		Delay(20);
 | 
						|
	}
 | 
						|
 | 
						|
	// Start 'Loader' application on Gigatron
 | 
						|
	SendController(buttonA, 2);
 | 
						|
 | 
						|
	// Wait for Loader to be running
 | 
						|
	Delay(1500);
 | 
						|
}
 | 
						|
 | 
						|
//  ----- transfer -------------
 | 
						|
static const int FramePayload = 60;
 | 
						|
byte checksum; // Global is simplest
 | 
						|
void ResetChecksum()
 | 
						|
{
 | 
						|
	checksum = 'g';
 | 
						|
}
 | 
						|
 | 
						|
void SendBits(byte val, int bits)
 | 
						|
{
 | 
						|
	unsigned mask = 1 << (bits-1);
 | 
						|
	while(mask) {
 | 
						|
		int bit = (val&mask)?1:0;
 | 
						|
		mask = mask >> 1;
 | 
						|
 | 
						|
		// Send bit
 | 
						|
		gpio_write(SER_DATA, bit);
 | 
						|
 | 
						|
		// Wait for bit transfer at horizontal sync POSITIVE edge.
 | 
						|
		// This timing is tight for the first bit of the first byte and
 | 
						|
		while (gpio_read(SER_PULSE))  { Nop(); } // Ensure hSync is LOW first
 | 
						|
		while (!gpio_read(SER_PULSE)) { Nop(); } // Then wait for hSync to rise
 | 
						|
	}
 | 
						|
	checksum += val;
 | 
						|
}
 | 
						|
 | 
						|
bool SendFrame(byte ProtocolByte, int len, unsigned address, const byte* message)
 | 
						|
{
 | 
						|
	// Send one frame of data
 | 
						|
	//
 | 
						|
	// A frame has 65*8-2=518 bits, including protocol byte and checksum.
 | 
						|
	// The reasons for the two "missing" bits are:
 | 
						|
	// 1. From start of vertical pulse, there are 35 scanlines remaining
 | 
						|
	// in vertical blank. But we also need the payload bytes to align
 | 
						|
	// with scanlines where the interpreter runs, so the Gigatron doesn't
 | 
						|
	// have to shift everything it receives by 1 bit.
 | 
						|
	// 2. There is a 1 bit latency inside the 74HC595 for the data bit,
 | 
						|
	// but (obviously) not for the sync signals.
 | 
						|
	// All together, we drop 2 bits from the 2nd byte in a frame. This achieves
 | 
						|
	// byte alignment for the Gigatron at visible scanline 3, 11, 19, ... etc.
 | 
						|
 | 
						|
	// Wait vertical sync NEGATIVE edge to sync with loader
 | 
						|
	while (!gpio_read(SER_LATCH)) {Nop();} 	// Ensure vSync is HIGH first
 | 
						|
	while (gpio_read(SER_LATCH))  {Nop();}	// Then wait for vSync to drop
 | 
						|
	unsigned begin = Clock();
 | 
						|
 | 
						|
	SendBits(ProtocolByte, 8);
 | 
						|
	checksum += ProtocolByte << 6;  // Keep Loader.gcl dumb
 | 
						|
	SendBits((byte)len, 6);            // Length 0, 1..60
 | 
						|
	SendBits((byte)(address&255), 8);    // Low address bits
 | 
						|
	SendBits((byte)(address>>8), 8);     // High address bits
 | 
						|
	for (byte i=0; i<FramePayload; i++)     // Payload bytes
 | 
						|
		SendBits(message[i], 8);
 | 
						|
	byte lastByte = -checksum;   // Checksum must come out as 0
 | 
						|
	SendBits(lastByte, 8);
 | 
						|
	checksum = lastByte;         // Concatenate checksums
 | 
						|
	gpio_write(SER_DATA, 1);	// Send 1 when idle
 | 
						|
 | 
						|
	unsigned end = Clock();
 | 
						|
	bool IsValidFrame = (end - begin) <= 16548;		// full frame is 16684 be we expect to be done before!
 | 
						|
	return IsValidFrame;
 | 
						|
}
 | 
						|
 | 
						|
// Send execute command
 | 
						|
void SendGt1Execute(unsigned address)
 | 
						|
{
 | 
						|
	byte zero[60] = {0};
 | 
						|
	for(;;) {
 | 
						|
		ResetChecksum();
 | 
						|
		bool valid = SendFrame('L', 0, address, zero);
 | 
						|
		if (valid)
 | 
						|
			break;
 | 
						|
		while (gpio_read(SER_LATCH))  { Nop(); }	// skip a frame for the checksum to reset on the other side
 | 
						|
		Delay(10);									// let the raspi run during the frame.
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Send a 1..256 byte code or data segment into the Gigatron by
 | 
						|
// repacking it into Loader frames of max N=60 payload bytes each.
 | 
						|
void SendGt1Segment(unsigned address, int len, const byte* data)
 | 
						|
{
 | 
						|
	// Send segment data
 | 
						|
	ResetChecksum();
 | 
						|
	while (len > 0) {
 | 
						|
		byte n = len < FramePayload ? len : FramePayload;
 | 
						|
		bool valid = SendFrame('L', n, address, data);
 | 
						|
		if (valid) {
 | 
						|
			address += n;
 | 
						|
			data += n;
 | 
						|
			len -= n;
 | 
						|
		} else {
 | 
						|
			// retry... (and reset checksum)
 | 
						|
			ResetChecksum();
 | 
						|
			while (gpio_read(SER_LATCH))  { Nop(); }	// skip a frame for the checksum to reset on the other side
 | 
						|
			Delay(10);									// let the raspi run during the frame.
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Wait for vBlank to start so we're 100% sure to skip one frame and
 | 
						|
	// the checksum resets on the other side. (This is a bit pedantic)
 | 
						|
	while (gpio_read(SER_LATCH))  { Nop(); }
 | 
						|
}
 | 
						|
 | 
						|
void SendGt1File(const byte* gt1)
 | 
						|
{
 | 
						|
	const byte* ptr = gt1;
 | 
						|
	int firstsegment = 1;
 | 
						|
 | 
						|
	gpio_setrealtimesched(1);
 | 
						|
 | 
						|
	for (;;) {
 | 
						|
		unsigned addrhi = *ptr++;
 | 
						|
		if (addrhi == 0 && !firstsegment)
 | 
						|
			break;		// done.
 | 
						|
		unsigned addrlo = *ptr++;
 | 
						|
		unsigned  address = (addrhi<<8) + addrlo;
 | 
						|
		int len = *ptr++;
 | 
						|
		if (len == 0) len = 256;
 | 
						|
 | 
						|
		// Check that segment doesn't cross the page boundary
 | 
						|
		if ((address & 255) + len > 256) {
 | 
						|
			printf("GT1 data error (page overflow)\n");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		//printf("loading %d bytes at @%X\n", len, address);
 | 
						|
		SendGt1Segment(address, len, ptr);
 | 
						|
		ptr += len;
 | 
						|
		firstsegment = 0;
 | 
						|
 | 
						|
		Delay(10);	// we have a full frame to wait (16ms), so hopefully os activity can take place now instead of interrupting us during time sensitive code.
 | 
						|
	};
 | 
						|
 | 
						|
	unsigned addrhi = *ptr++;
 | 
						|
	unsigned addrlo = *ptr++;
 | 
						|
	unsigned  startaddress = (addrhi<<8) + addrlo;
 | 
						|
	if (startaddress != 0) {
 | 
						|
		printf("executing from @%X\n", startaddress);
 | 
						|
		SendGt1Execute(startaddress);
 | 
						|
	}
 | 
						|
 | 
						|
	gpio_setrealtimesched(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int main(int argc, char **argv)
 | 
						|
{
 | 
						|
	if (argc < 2) {
 | 
						|
		printf("usage: %s <file.gt1> [skip]\n", argv[0]);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	int fd = open(argv[1], O_RDONLY);
 | 
						|
	if (fd<0) {
 | 
						|
		printf("could not open '%s'\n", argv[1]);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	byte gt1data[32768];
 | 
						|
	int len = (int)read(fd, gt1data, 32768);
 | 
						|
	if (len == 0) {
 | 
						|
		printf("could not read '%s'\n", argv[1]);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		printf("loaded %d bytes\n", len);
 | 
						|
	}
 | 
						|
 | 
						|
	close(fd);
 | 
						|
 | 
						|
	int skipmenu = (argc >= 3 && strcmp(argv[2], "skip") == 0) ;
 | 
						|
 | 
						|
	// Set up gpi pointer for direct register access
 | 
						|
	gpio_setup();
 | 
						|
 | 
						|
	printf("Establishing communication with gigatron....\n");
 | 
						|
 | 
						|
	// Set GPIO pins mode for gigatron communication
 | 
						|
	gpio_setmode_in(SER_LATCH);
 | 
						|
	gpio_setmode_in(SER_PULSE);
 | 
						|
	gpio_setmode_out(SER_DATA);
 | 
						|
	gpio_write(SER_DATA, 1);	// Send 1 when idle
 | 
						|
 | 
						|
	printf("Detecting device: %s\n", (DetectGigatron()?"ok":"failed"));
 | 
						|
	Delay(500);
 | 
						|
 | 
						|
	if (!skipmenu) {
 | 
						|
		printf("Reset\n");
 | 
						|
		SendReset();
 | 
						|
 | 
						|
		printf("start loader\n");
 | 
						|
		StartLoader();
 | 
						|
	}
 | 
						|
 | 
						|
	SendGt1File(gt1data);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 |