Commits

arkadi  committed 88a8192

Initial import

  • Participants

Comments (2)

    1. Arkadi Shishlov repo owner

      Hi! No board schematics - I never got to the point to implement it in the car. I used Olimex PIC-PG4 dev board with tdc-rtai.c simulator connected via LPT port and watched for signals with digital oscilloscope and serial port debug output. Looks good to me.

Files changed (4)

+CC = gcc
+LXRT_CFLAGS = $(shell /usr/realtime/bin/rtai-config --lxrt-cflags)
+LXRT_LDFLAGS = $(shell /usr/realtime/bin/rtai-config --lxrt-ldflags)
+
+all: hex emu
+
+hex: pic-fire.hex
+
+emu: tdc-rtai
+
+tdc: tdc.c
+	$(CC) -Wall -O -o $@ $< -lcurses
+
+tdc-rtai: tdc-rtai.c
+	$(CC) -g -Wall -O $(LXRT_CFLAGS) -o $@ $< -lcurses $(LXRT_LDFLAGS)
+
+pic-fire.hex: pic-fire.o
+	gplink -w -r -c -o $@ -m $^ -I /usr/share/sdcc/lib/pic libsdcc.lib pic16f628a.lib
+
+pic-fire.o: pic-fire.asm
+	gpasm -L -c $<
+
+pic-fire.asm: pic-fire.c Makefile
+	sdcc -S --nooverlay --opt-code-size --disable-warning 84 -mpic14 -p16f628a $<
+#	perl -pi~ -e 's/idata\s+0x2100/code\t0x2100/' $@
+
+install: pic-fire.hex
+	picprog --erase --rdtsc --burn -i $< -d pic16f628a -p /dev/ttyS0
+
+clean:
+	@rm -f pic-fire*.{hex,asm,as,o,cod,cof,map,lst,obj,rlf,hxl,sym} tdc tdc-rtai *~
+picl -S --chip=16f627a pic-fire.c
+picl --chip=16f627a -Opic-fire-picl.hex pic-fire.c
+pause
+#ifdef SDCC
+#include <pic/pic16f628a.h>
+
+typedef unsigned int config;
+config __at 0x2007 __CONFIG =
+    _WDT_OFF &              // watchdog reset
+    _PWRTE_OFF &            // power-up timer
+  //_INTOSC_OSC_NOCLKOUT &  // internal oscillator
+    _HS_OSC &               // 8-10MHz high speed crystal/resonator
+    _MCLRE_ON &             // master clear reset pin
+    _BOREN_ON &             // brown-out reset
+    _LVP_OFF &              // low-voltage programming
+    _CP_OFF &               // code protection
+    _DATA_CP_OFF;           // data protection
+
+//typedef unsigned char eeprom;
+//__code eeprom __at 0x2100 __EEPROM[] = { 0xAA, 0x1E, 0x00, 0x01, 0x0A, 0x64, 0x1E, 0x01 };
+
+#else // Hi-Tech PICC Lite
+#include <pic.h>
+
+__CONFIG(WDTDIS & PWRTDIS & HS & MCLREN & BOREN & LVPDIS);
+
+#define OPTION_REG OPTION
+
+#endif
+
+#define LED_PIN       RA2
+
+#define cam_trigger   RA0
+#define crank_trigger RA1
+#define spark_trigger RA3
+
+#define YES    2
+#define SEARCH 1
+#define NO     0
+
+#define TMR0_PER_MS 20 // TMR0_PER_MS/(20MHz/4 clocks per cycle/256 prescaler) = 0.001s (1ms)
+#define MAX_DWELL   (TMR0_PER_MS*7) // 7ms
+#define FLASH_TIME  800
+
+// the main loop events log is sent via usart
+#define START      'S'
+#define CURR_CYL   'n'
+#define NEXT_CYL   'N'
+#define WAIT_LOW_STARTED   'w'
+#define WAIT_LOW_COMPLETED 'W'
+#define BAD_DWELL  'L'
+#define HALT       'H'
+#define SYNCED     'Y'
+#define WAIT_SYNC  'y'
+#define CRANK_HIGH 'K'
+#define CRANK_LOW  'k'
+#define CAM_HIGH   'M'
+#define CAM_LOW    'm'
+#define RESTART_SEARCH 'R'
+
+// transmit a byte via usart
+void tx(char c) {
+    while (!TXIF); // wait for transmitter to become free
+    TXREG = c;
+}
+
+void txnl(void) {
+    tx('\r'); tx('\n');
+}
+
+void txnlc(char c) {
+    txnl(); tx(c); txnl();
+}
+
+unsigned char start_timer(void)
+{
+    unsigned char tmr;
+    T0IF = 0;
+    // here is a short window of TMR0 overflow
+    tmr = TMR0;
+    if (tmr != 255)
+        T0IF = 0;
+    return tmr;
+}
+
+int time_since(unsigned char tmr_start)
+{
+    int tm;
+    if (T0IF) { // if timer overflow
+        tm = 255 - tmr_start + TMR0;
+    } else {
+        tm = TMR0 - tmr_start;
+        if (tm < 0) // overflow happened just after T0IF is read
+            tm = 255 - tmr_start;
+    }
+    return tm;
+}
+
+void delay_ms(unsigned int ms)
+{
+
+    unsigned char tmr_start, debug_skip_i = 0;
+    while (ms--) {
+        tmr_start = start_timer();
+        if (!debug_skip_i++)
+            tx('d');
+        while (time_since(tmr_start) < TMR0_PER_MS);
+    }
+/*
+    int i;
+    while (ms--) for (i = 0; i < 440; ++i);
+*/
+}
+
+// to flash the led
+volatile unsigned int flash = 0;
+
+// cylinders 1..6 PORTB masks for wasted spark 2+5, 3+6, 1+4 in that order
+// after cylinder 1 crank goes low it is 75 degrees BTDC cylinder 2
+// and the spark should be for cylinder 2, obviously
+// spark 1+4 - RB0
+// spark 2+5 - RB3
+// spark 3+6 - RB5
+const char sparks[] = { 0x08, 0x20, 0x01, 0x08, 0x20, 0x01 };
+
+void main()
+{
+    unsigned char synced = NO, crank_trigger_prev, crank_raised_when_cam_active, cyl_no, cyl_no_prev, sync_search_i;
+    unsigned char spark_trigger_prev, spark_no, spark_start, dwell_no;
+    unsigned char debug_skip_i = 0;
+    int dwell;
+
+    TRISA = 0xFB; // RA4-7 is input/high-impedance output (1), RA2 (led) is output (0), RA0,1,3 are inputs
+    CMCON = 0x07; // disable comparators
+    TRISB = 0xD6; // RA0,3,5 are outputs, other pins are inputs
+    T0IE = 0; // TMR0 interrupt disable, default on POR, but set it anyway
+    OPTION_REG = 0xC7; // RB pull-ups are disabled, TMR0 transition on internal clock, prescaler assigned to TMR0 with a rate 256
+    // usart at async 115.2kbps
+    TXIE = 0; // TXREG free interrupt disable
+  //SPBRG = 64; // 19.2kbps
+    SPBRG = 10; // 115.2kbps
+    TXSTA = 0x24; // TXEN = 1; SYNC = 0; BRGH = 1; everything else is 0
+    RCSTA = 0x90; // SPEN = 1; CREN = 1; everything else is 0
+    RCIE = 1; // receiver interrupt enable
+
+    txnlc(START);
+    while (1) {
+        /*
+        LED_PIN = 0;
+        delay_ms(1000);
+        LED_PIN = 1;
+        delay_ms(1000);
+        continue;
+        */
+        /*
+        if (cam_trigger)
+            LED_PIN = 0;
+        else
+            LED_PIN = 1;
+        continue;
+        */
+        if (synced == YES) {
+            // output spark signal
+            if (spark_trigger) {
+                if (!spark_trigger_prev) { // start charging the coil
+                    spark_trigger_prev = 1;
+                    // at high RPM the coil is started charging before next crank trigger arrives
+                    // don't charge the same coil twice, assume next cylinder
+                    if (cyl_no == spark_no) {
+                        flash = FLASH_TIME; // XXX debug
+                        spark_no = cyl_no + 1;
+                        spark_no = (spark_no > 6) ? 1 : spark_no;
+                        tx(NEXT_CYL);
+                    } else {
+                        spark_no = cyl_no;
+                        tx(CURR_CYL);
+                    }
+                    tx(spark_no + 'A' - 1);
+                    PORTB = sparks[spark_no-1];
+                    // remember the moment the coil charge is started to check the dwell
+                    spark_start = start_timer();
+                } else if (spark_no != dwell_no) { // check the dwell
+                    dwell = time_since(spark_start);
+                    if (dwell > MAX_DWELL) {
+                        dwell_no = spark_no;
+                      //flash = FLASH_TIME;
+                      //tx(BAD_DWELL);
+                    }
+                }
+            } else {
+                PORTB = 0;
+                spark_trigger_prev = 0;
+            }
+            // flash the led for some time, even when already synced
+            if (flash) {
+                --flash;
+                LED_PIN = 0;
+            } else
+                LED_PIN = 1;
+        } else {
+            if (!debug_skip_i++)
+                tx(WAIT_SYNC);
+            PORTB = 0;
+            LED_PIN = 0;
+            if (synced != SEARCH) {
+                tx(WAIT_LOW_STARTED);
+                while (crank_trigger || cam_trigger); // loop until both signals are low
+                tx(WAIT_LOW_COMPLETED);
+                synced = SEARCH;
+                crank_trigger_prev = 0;
+                spark_trigger_prev = 0;
+                cyl_no = 0;
+                cyl_no_prev = 0;
+                sync_search_i = 0;
+                spark_no = 0;
+                dwell_no = 0;
+            }
+        }
+
+        if (crank_trigger) {
+            if (!crank_trigger_prev) { // crank low -> high: remember cam status
+                crank_trigger_prev = 1;
+                crank_raised_when_cam_active = cam_trigger;
+                tx(CRANK_HIGH); tx(crank_raised_when_cam_active ? CAM_HIGH : CAM_LOW);
+            }
+        } else {
+            if (crank_trigger_prev) { // crank high -> low: perform the checks
+                crank_trigger_prev = 0;
+                tx(CRANK_LOW); tx(cam_trigger ? CAM_HIGH : CAM_LOW);
+                // there could be inconsistence between event log and actual programm flow if CAM signal is lowered right now, but it is not possible on 6g72
+                if (cam_trigger) {
+                    if (crank_raised_when_cam_active) { // this happens at cylinder 1 only
+                        if (synced == YES) {
+                            if (cyl_no == 6)
+                                cyl_no = 1;
+                            else
+                                synced = NO;
+                        } else
+                            cyl_no = 1;
+                    } else { // cylinder 2 or 5
+                        if (cyl_no == 1 || cyl_no == 4)
+                            ++cyl_no;
+                        else if (synced == YES)
+                            synced = NO;
+                    }
+                } else { // cam trigger is low
+                    if (crank_raised_when_cam_active) { // this happens at cylinder 4 only
+                        if (synced == YES) {
+                            if (cyl_no == 3)
+                                cyl_no = 4;
+                            else
+                                synced = NO;
+                        } else
+                            cyl_no = 4;
+                    } else { // cylinder 3 or 6
+                        if (cyl_no == 2 || cyl_no == 5)
+                            ++cyl_no;
+                        else if (synced == YES)
+                            synced = NO;
+                    }
+                }
+                // format event log output
+                if (synced == YES && cyl_no == 1)
+                    txnl();
+                tx(cyl_no + '0'); // XXX delay here
+                // if not synced, then search for 6 successfull (in order, starting from cylinder 1 or 4) cylinder events and enter synced mode when found
+                if (synced == SEARCH && cyl_no != 0) {
+                    if (cyl_no_prev != 0) {
+                        if (cyl_no_prev == ((cyl_no == 1) ? 6 : cyl_no - 1))
+                            ++sync_search_i;
+                        else {
+                            synced = NO;
+                            tx(RESTART_SEARCH);
+                        }
+                    }
+                    cyl_no_prev = cyl_no;
+                    if (sync_search_i == 5) { // not 6, because there was no increment for first recognized cylinder
+                        synced = YES;
+                        txnlc(SYNCED);
+                        debug_skip_i = 0;
+                    }
+                } else if (synced == NO) {
+                    // shutdown gracefully by allowing engine to stop
+                    tx(HALT);
+                    delay_ms(5000);
+                }
+            }
+        }
+    }
+}
+
+#ifdef SDCC
+void isr(void) __interrupt(0) {
+#else
+interrupt isr(void) {
+#endif
+    while (RCIF) {
+        char c = RCREG;
+    }
+    // reset input overrun
+    if (OERR) {
+        flash = FLASH_TIME;
+        CREN = 0;
+        CREN = 1;
+    }
+}
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/io.h>
+#include <signal.h>
+
+#include <rtai_lxrt.h>
+
+#include <curses.h>
+
+#define LPTPORT 0x378 // lpt1
+// the pins of parallel port
+#define SPARK_PIN 1
+#define CRANK_PIN 2
+#define CAM_PIN   3
+// masks
+#define SPARK_HIGH (1 << (SPARK_PIN-1))
+#define CRANK_HIGH (1 << (CRANK_PIN-1))
+#define CAM_HIGH   (1 << (CAM_PIN-1))
+#define SPARK_LOW  (~(1 << (SPARK_PIN-1)))
+#define CRANK_LOW  (~(1 << (CRANK_PIN-1)))
+#define CAM_LOW    (~(1 << (CAM_PIN-1)))
+
+// signal to exit from Ctrl-C or key event loop
+int must_exit = 0;
+// ignition coil charge parameters
+int spark_start = 80, spark_end = 110;
+// the increment of crank rotation per tick
+const int inc = 5; // degrees
+// the tdc() period
+RTIME period;
+
+void *tdc(void *arg)
+{
+    RT_TASK *me;
+    // gradual adjustment of task period when engine speed is changed
+    RTIME period_target, period_curr;
+    int period_step = 0, period_adj_i = 0;
+    // output byte
+    char v = 0;
+    int angle, spark_start_curr, spark_end_curr, spark_angle;
+
+    if (!(me = rt_thread_init(nam2num("tdc"), 0, 0, SCHED_FIFO, -1))) {
+        fprintf(stderr, "rt_thread_init_schmod() failed: %s\n", strerror(errno));
+        exit(1);
+    }
+    rt_allow_nonroot_hrt();
+    rt_make_hard_real_time();
+
+    period_target = period_curr = period;
+    while (1) {
+        if (must_exit)
+            break;
+        spark_start_curr = spark_start;
+        spark_end_curr = spark_end;
+        for (angle = 0; angle < 720; angle += inc) {
+            switch (angle) {
+                // cam
+                case 70:  case 190: case 380: case 550: v &= CAM_LOW; break;  // cam goes low
+                case 150: case 340: case 510: case 705: v |= CAM_HIGH; break; // cam goes high
+                // crank
+                case 45:  case 165: case 285: case 405: case 525: case 645: v &= CRANK_LOW; break;  // crank goes low
+                case 115: case 235: case 355: case 475: case 595: case 715: v |= CRANK_HIGH; break; // crank goes high
+            }
+            // spark
+            spark_angle = angle%120;
+            if (spark_angle == spark_start_curr || spark_angle == spark_start_curr + 120 || angle >= 720 + spark_start /* next cycle spark */)
+                v |= SPARK_HIGH;
+            else if (spark_angle == spark_end_curr || spark_angle == spark_end_curr - 120)
+                v &= SPARK_LOW;
+
+            outb(v, LPTPORT);
+
+            // gradually adjust to desired rpms
+            if (period != period_target) {
+                period_target = period;
+                period_adj_i = 1000000000/period_curr;
+                if (period_adj_i > 0) {
+                    period_step = (period_target - period_curr)/period_adj_i;
+                    ++period_adj_i;
+                } else
+                    period_adj_i = 1;
+            }
+            if (period_adj_i) {
+                if (period_adj_i == 1) {
+                    period_adj_i = 0;
+                    period_curr = period_target;
+                } else {
+                    --period_adj_i;
+                    period_curr += period_step;
+                }
+            }
+
+            rt_task_make_periodic_relative_ns(me, 0, period_curr);
+            rt_task_wait_period();
+        }
+    }
+    outb(0, LPTPORT);
+    rt_make_soft_real_time();
+    rt_task_delete(me);
+    return NULL;
+}
+
+void stop(int x)
+{
+    must_exit = 1;
+}
+
+RTIME rpm2period(int rpm)
+{
+    return 1000000000/((float)rpm/60)/(360/inc); // (1 s == 10e9 ns)/(rpm/60 s)/(360 degrees per revolution/increment per tick == 5 degrees)
+}
+
+int main(int argc, char **argv)
+{
+    RT_TASK *task;
+    long tdc_thread;
+    int ch; // currently read keyboard char
+    int update = 1; // need screen update
+    int rpm = 800; // start at 800rpm
+    int rpm_step;
+
+    // lpt port permissions
+    if (ioperm(LPTPORT, 1, 1)) {
+        fprintf(stderr, "ioperm() failed: %s\n", strerror(errno));
+        return 1;
+    }
+    // rtai init - let the main() also be a rtai backed thread, just in case
+    if (!(task = rt_task_init(nam2num("main"), 1, 0, 0))) { // priority of 1, default stack and message size, SCHED_FIFO, and all CPU-s
+        fprintf(stderr, "rt_task_init() failed: %s\n", strerror(errno));
+        return 1;
+    }
+    period = rpm2period(rpm);
+    rt_set_oneshot_mode();
+    start_rt_timer(0);
+  //start_rt_timer(nano2count(period)); // for periodic time mode
+    tdc_thread = rt_thread_create(tdc, NULL, 0); // NULL arg and default stack size
+    // ncurses init
+    initscr(); cbreak(); noecho(); nonl(); /* nodelay(stdscr, TRUE); */ intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
+    signal(SIGINT, stop); // exit gracefully on Ctrl-C
+    // XXX at Ctrl-C tdc() stops but not the main thread until the key is pressed
+    while (1) {
+        if (must_exit)
+            break;
+        if (update) {
+            printw("\rrpm:%5d; spark start:%3d; end: %3d", rpm, spark_start, spark_end);
+            refresh();
+        }
+        update = 1;
+        switch ((ch = getch())) {
+            default:
+            case ERR: update = 0; break;
+            // exit
+            case 'x': case 'X': case 'q': case 'Q': must_exit = 1; break;
+            // lower or raise engine rpms
+            case ',': case '.':
+                if (rpm < 1000)      rpm_step = 10;
+                else if (rpm < 2000) rpm_step = 50;
+                else                 rpm_step = 100;
+
+                if (ch == ',') { if (rpm > 10)    rpm -= rpm_step; }
+                else           { if (rpm < 20000) rpm += rpm_step; }
+
+                period = rpm2period(rpm);
+                break;
+            // set spark timing
+            case KEY_LEFT:  if (spark_start > -25 && spark_start > spark_end + 5 - 120)  spark_start -= inc; break;
+            case KEY_RIGHT: if (                     spark_start < spark_end - 5)        spark_start += inc; break;
+            case KEY_DOWN:  if (spark_end > 80    && spark_end > spark_start + 5)        spark_end   -= inc; break;
+            case KEY_UP:    if (spark_end < 130   && spark_end < spark_start - 5 + 120)  spark_end   += inc; break;
+        }
+    }
+
+    endwin();
+
+    rt_thread_join(tdc_thread);
+    stop_rt_timer();
+    rt_task_delete(task);
+
+    return 0;
+}