#lang scheme/base (require scheme/match "util.ss" "pk2script.ss") (provide (all-defined-out)) ;; Low-level byte-packet data communication using the synchronous ;; serial ICSP PGC/PGD lines. This is a marriage between the Staapl ;; monitor protocol and the programming primitives available in an ;; unmodified Microchip PICkit2 USB programmer, firmware V2.32. ;; ;; On top of the straightforward synchronous serial byte in/out ;; primitives of the PK2 this requires some extra, ad-hoc handshake ;; protocol. Operation is as follows: ;; ;; A Host starts handshake by sending out a clock pulse and reading ;; the data line. If target is there, it asserts a 1. Otherwise ;; the line is 0 due to PK2 weak pulldown. ;; ;; B If host did not receive a 1, it will retry step A. ;; ;; C Host sends out another clock pulse to allow the target to release ;; the line, returning the bus to the host. ;; ;; D Host writes out command bytes: ;;
[ [ ...]] ;; ;; E Host waits for handshake identical to step A and B. Target will ;; perform some computation, and when it is ready it will respond to ;; the handshake. ;; ;; F Host provides clock for target to shift out a reply packet that ;; has the same form as in step D. ;; ;; G Host clocks one more cycle for target to release the data line. ;; ;; H Next cycle restarts at A. ;; ;; ;; ;; Notes: ;; ;; * In D and F: target address is 0x00, host address is 0xFF. ;; ;; * In step D, the = 0 case is a NOP that's used for ;; recovering sync. The target will ignore this message and wait ;; for the next handshake clock pulse. ;; ;; * There seems to be a sync issue where PK2 delays the sync bit by ;; one. I don't think this is a Staapl bug as I get different ;; behaviours for the same code. (define (ICSP_OUT) (SET_ICSP_PINS 0)) (define (ICSP_IN) (SET_ICSP_PINS 2)) ;; Clock period is set to 15 as this seems to work for 8MHz internal ;; oscillator. It can be set to 3us for 48MHz / 40MHz operation. (define icsp-us (make-parameter 15)) (define icsp-debug (make-parameter #f)) ;; Needs size-tagged message. (define (icsp-send bytes #:handshake handshake) (when (icsp-debug) (printf "icsp:send h:~a b:~a\n" handshake bytes)) (when (> (length bytes) 26) (error 'buffer-overflow)) ;; It seems to be necessary to do this in a separate transaction. ;; Assuming the device is there and sending it data creates ;; unrecoverable problems. (when handshake (let sync () (when (zero? (icsp-recv-bit)) (sync)))) (apply EXECUTE_SCRIPT `(,(SET_ICSP_SPEED (icsp-us)) ,@(if handshake `(,(ICSP_IN) ,(READ_BITS 1)) ;; 2nd handshake bit '()) ,(ICSP_OUT) ,@(for/list ((b bytes)) (WRITE_BYTE_LITERAL b)) ,(ICSP_IN)))) ;; PK2 latches on falling edge. ;; Chunker: max 63 bytes at once. (define (icsp-recv bytes #:handshake (handshake #f) #:ack (ack #f)) (define max-bytes 63) (if (<= bytes max-bytes) (_icsp-recv bytes #:handshake handshake #:ack ack) (append (_icsp-recv max-bytes #:handshake handshake #:ack #f) (icsp-recv (- bytes max-bytes) #:handshake #f #:ack ack)))) (define (_icsp-recv bytes #:handshake (handshake #f) #:ack (ack #f)) (define (log-msg reply) (format "icsp-recv: b:~a h:~a a:~a -> ~a" bytes handshake ack reply)) ;; This would crash PK2, needing power cycle. (when (> bytes 63) (error 'icsp-recv-overflow "~s" bytes)) (CLR_UPLOAD_BFR) (if (< bytes 1) (begin (when ack (EXECUTE_SCRIPT (READ_BITS 1))) '()) (begin (apply EXECUTE_SCRIPT `(,(ICSP_IN) ,(SET_ICSP_SPEED (icsp-us)) ,@(if handshake ;; Handshake can be incorporated in recv packet ;; for speedup of the most common case where ;; the target is just sitting there waiting to ;; send. This adds an extra byte in front with ;; handshake info. ;; ;; If the handshake byte's value is not 1 -- ;; the two handshake bits 1, 0 on the line -- ;; the rest of the buffer needs to be searched ;; for a sync signal, and the clock needs to be ;; re-aligned! `(,(READ_BITS_BUFFER 2)) '()) ,(READ_BYTE_BUFFER) ,@(if (= 1 bytes) '() `(,(LOOP 1 (- bytes 1)))) ,@(if ack ;; clock in and discard `(,(READ_BITS 1)) '()) )) (let ((reply (UPLOAD_DATA))) (let ((expect-size (+ bytes (if handshake 1 0))) (real-size (length reply))) (when (icsp-debug) (display (log-msg reply)) (newline)) (unless (= expect-size real-size) (printf "WARNING: Short UPLOAD_DATA: ~a (expected size ~a)\n" reply expect-size)) ;; This can return short count! reply))))) ;; Clock in a single bit. (define (icsp-recv-bit) (CLR_UPLOAD_BFR) (EXECUTE_SCRIPT (ICSP_IN) (SET_ICSP_SPEED 255) ;; clock slowly (READ_BITS_BUFFER 1) ;; (ICSP_STATES_BUFFER) ;; for debug ) (let ((data (UPLOAD_DATA))) (when (icsp-debug) (printf "icsp-recv-bit ~a\n" data)) (car data))) ;; Write full message (define (icsp-send-message m) (if #f (begin (icsp-handshake) ;; handshake (icsp-send m)) (icsp-send m #:handshake #t))) ;; Poll for handshake. ;; An ad-hoc protocol that works with using just the PGC/PGD lines and ;; the standard PK2 firmware. Some extra bits are added to detect ;; target ready state, and hold the last bit without requiring ;; on-target timing. (define (icsp-handshake) (let again () ;; Poll until we have a 1 bit. (if (zero? (icsp-recv-bit)) (begin ;; It seems OK to just poll as fast as we can without sleep. ;; The USB driver slows us down to the bus packet rate. In ;; my current setup the delay between polling pulses is 3ms. ;; (sleep.1) (again)) (icsp-recv-bit)))) ;; clock in second bit ;; Read full message with handshake. This uses the Staapl message ;; byte protocol: [...] ;; Instead of waiting for ack, we just go ahead and read the 2 sync ;; bits followed by the 2 first message bytes. If sync is incorrect ;; we realign it by reading extra bits. This is in order to speed up ;; transfer, avoiding ping-pong across the USB bus delay. (define (icsp-recv-message) ;; If necessary, this can re-align the bitstream. (define (resync handshake addr bytes) (let again ((bits (bior handshake (bior (<<< addr 2) (<<< bytes 10))))) (when (icsp-debug) (printf "icsp-recv-message: resync: ~b\n" bits)) (cond ((= 1 (band bits 1)) ;; Got sync, fish out header data and continue. (let ((addr (band #xFF (>>> bits 2))) (bytes (band #xFF (>>> bits 10)))) (recv-tail addr bytes))) ((zero? bits) ;; Idle line -> retry header receive. (recv-header)) (else ;; One bit at a time. Can be optimized by scanning for the ;; first bit, then read a bunc at a time. (again (bior (>>> bits 1) (<<< (icsp-recv-bit) 17))))))) ;; Once header is parsed and synced, tail can be received. (define (recv-tail addr bytes) (let ((body (icsp-recv bytes #:ack #t))) (let ((reply (list* addr bytes body))) (when (icsp-debug) (printf "icsp-recv-message: ~a\n" reply)) reply))) ;; Get the handshake byte followed by the 2-byte header. (define (recv-header) (let ((header (icsp-recv 2 #:handshake #t))) (match header ((list handshake addr bytes) (if (= 1 handshake) ;; All OK. This is the most common case for which we ;; optimize. (recv-tail addr bytes) ;; If sync is off, the following will recover the sync ;; by looking for the handshake bit. (resync handshake addr bytes))) (else (error 'recv-header "malformed header: ~a" header) )))) (recv-header)) ;; Probe the state of the input lines ;; use ICSP_STATES_BUFFER (define (icsp-read-porta bit) ;; FIXME: Set both as input? (CLR_UPLOAD_BFR) (EXECUTE_SCRIPT (PEEK_SFR #x80)) ;; PORTA (band 1 (>>> (car (UPLOAD_DATA)) bit))) (define (icsp-read-dat) (icsp-read-porta 2)) (define (icsp-read-clk) (icsp-read-porta 3)) ;; (define (spi-send [speed 3]) ;; ok ;; (EXECUTE_SCRIPT ;; (SET_ICSP_SPEED (icsp-us)) ;; (SET_AUX 0) ;; AUX = out,0 ;; (ICSP_IN) ;; PGC = out,0 ; PGD = in ;; (SPI_WR_BYTE_LIT #x33))) ;; (define (i2c-send [speed 3]) ;; broken ;; (EXECUTE_SCRIPT ;; (SET_ICSP_PINS 4) ;; PGC = 1, out ;; (SET_AUX 1) ;; AUX = in ;; (SET_ICSP_SPEED (icsp-us)) ;; (I2C_START) ;; (I2C_WR_BYTE_LIT #x33))) (define (icsp-recv-loop) (printf "Starting ICSP receive loop.\n") (let loop () (let ((msg (icsp-recv-message))) (printf "~a ~a\n" (length msg) msg)) (loop))) ;; Testing ; (pk2-boot) ; (icsp-recv-loop)