вторник, 25 октября 2011 г.

ypweb literate programming tool

After I tried LP technique, I decided that it help me to understand my code better and cuts many possible errors. But I need easier tool, lesser verbose... I implemented very lightweight Python 3 tool (about 20Kb) and called it ypweb. It's hosted on Google.
Main differences with other LP tools are:
  • produces only reStructuredText output
  • very lightweight (only one Python 3 file) about 20Kb
  • doesn't need comment line symbols for document strings (like #!) and code-chunks: only plain text
  • document and code context are switched by special line with short variants
  • generated index contains used by user (not predefined) classifiers
  • doesn't allow code-chunks "placeholders" (defining in one place/inserting in another)
  • supports any input and output charsets (see -h)
Short example:

  1 | Some definitions (macroses) used in COM module:
  2 | ;; x.h; defs; COM definitions
  3 | ;; Port number
  4 | #define PORT 1
  5 | ;; Baudrate
  6 | #define BAUD 9600
  7 | ;; funs; open()
  8 | int open(char *name, char *mode);
  9 | ;;
 10 | Another doc strings...
 11 | ;; x.c; funs; open()
 12 | code of open function here...
 13 | ;; x.h; defs; XXX
 14 | #define XXX
 15 | ;;
 16 | docs strings again...

Line 1 is the document chunk (file may starts with any chunk: doc or code). Line 2 is the special kind of line which switch context to code and begins with special marker (;;) and consists of 3 parts (may be lesser) splited by ;. Both symbols (;; and ;) may be changed to another in command line. It "says": next chunk is code, generates x.h file with some definitions ("defs" but may be any word preferred by user) and is the "COM definitions". These fields will be used also in index generating.

Line 3 is short form of switch-context-line (only caption, no file name or classifier). Line 4 is code and will be saved in the source file. Line 4 is like line 3, next is source line again...

Line 7 is short switch-context-line too and consists of classifier and caption, no filename, so will be saved in the same file - x.h.

Line 9 is empty switch-context-line, so it ends code chunk, nothing else. Next is always doc chunk.
Line 11 is switch-context-line which starts new source file: x.c. Line 13 begins code chunk for early created x.h file. Well, switch-context-line switch into code chunk and defines what is the code if not empty, empty - switches into doc chunk.

It's very clean and simple format. And is the reStructuredText too. So, you can use in it any kind of usual reSt markup!

After processing this file, .rst file and x.h, x.c will be generated. Also .rst file will have index section in the end with references to definitions of the code chunks.

понедельник, 10 октября 2011 г.

AVR timers: calculate prescalers

Functions below are only for Atmega128, see "bitness" of timers, prescalers values...

When you use AVR timers, you should setup it's prescalers' values. Often, programmers calculates prescaler on the paper and then hardcodes it into the code. And this is done every time for new delay value. This task can be done automatically by next algorithm. Next is the Python prototype of the algorithm.
class Timer:
    freq = 0 # freq. of timer
    bits = 0 # "bitsness" of timer

# We have for example 4 timers
_timers = [Timer(), Timer(), Timer(), Timer()]

def init_timers():
    # setup bitness of the timers
    global _timers
    _timers[0].bits = 256
    _timers[1].bits = 65536
    _timers[2].bits = 256
    _timers[3].bits = 65536

# for Atmega128
def _alarm_setup(timer, D):
    prescales0 = (1, 8, 32, 64, 128, 256, 1024,)
    prescales = (1, 8, 64, 256, 1024,)
    # base freq of timer, the same for all but is possible to specify differents
    freq = 8000000 # Freq. of MCU

    if timer == 0:
        psc = prescales0
    else:
        psc = prescales

    _timers[timer].freq = freq

    T = 1000000 * psc[-1] * _timers[timer].bits / freq
    cycles = D/T
    if cycles:
        D -= cycles*T

    for _psc in psc:
        # T - timer period (step in usec)
        T = 1000000 * _psc * _timers[timer].bits / freq
        # D_T - how many T (timer periods) are in D (delay)
        D_T = float(D)/T
        if D_T <= .5:
            # can not be rounded even to 1, too short delay time!
            return None
        elif D_T > _timers[timer].bits:
            # delay is too long for this prescale value
            continue
        else:
            # bingo!
            cn = _timers[timer].bits - int(D_T) + 1
            prescale = _psc
            return (cn, prescale, cycles)
    return None

# test
init_timers()
print _alarm_setup(0, 10000000)
print _alarm_setup(0, 256)

Function _alarm_setup() returns triplet (start value of timer counter, prescaler value, total cycles of the timer counter). These 3 values defines initial parameters of counter which should count to delay D microseconds. It's possible to define any values of delay time in this manner.
This Python prototype example is only to show idea of algorithm. But next is the real implementation on C for WinAVR:
/* Calculates prescale (divisor of timer: 1,8,...) and start
 * counter value and total cycles number. Returns 0 on success, -1 on faulire.
 * D is the delay in microseconds.  timer is number of timer.
 * Out values are saved in corresponding timerc_t.
 * Atmega128 implementation!!!
 */
int
_alarm_setup(int timer,
             unsigned long D)
{
        int i;
        int max_psc; // max. value of prescaler of timer
        int *psc; // prescalers of timer
        unsigned long T; // period of tick
        unsigned long Tclk; // period of clock
        unsigned long Tall; // period of timer cycle (from 0 to bits)

        psc = _timers[timer].prescalers;
        max_psc = psc[_timers[timer].nprescalers-1];

        /* Base frequency is F_CPU. Real frequency is F_CPU/prescaler.
           One tick occupes 1/freq seconds (freq in MHz). N ticks occupes
           N/freq. N is max ticks number in counter = bits of this counter =
           _timers[timer].bits. So, total time to count all ticks of counter is
           N/freq = N/(F_CPU/prescaler) = N*prescaler/F_CPU. Freq is in MHz, to
           use microseconds instead of seconds, * 1000000.

           T is the total time of count all the counter with slowest freq (with
           last prescaler - max_psc). cycles is how many counter cycles are in
           D with slowest prescaler on this timer-counter.

           Then D set to D without cycles time.
           */

        // 1000000 * max_psc > F_CPU, so T/F_CPU != 0
        T = (1000000 * max_psc / F_CPU) * _timers[timer].bits;
        _timers[timer].cycles = D/T;

        if (_timers[timer].cycles > 0) {
                D -= (_timers[timer].cycles) * T;
                _timers[timer].cycles_prescaler = max_psc;
        }
        else {
                // If does not need cycles, cycles_prescaler will be 0
                // (may be used as flag of this)
                _timers[timer].cycles_prescaler = 0;
        }

        D *= 1000; // in nanoseconds
        Tclk = 1000000000 / F_CPU; // in nanoseconds
        for (i=0; i<_timers[timer].nprescalers; i++) {
                T = Tclk * psc[i]; // time of one tick
                Tall = T * _timers[timer].bits; // time to count all ticks of counter
                if (D > Tall) {
                        // delay D is too long for this Tall
                        continue;
                }
                else {
                        _timers[timer].prescaler = psc[i];
                        // D/T how many ticks in D, cn is ticks from 0
                        _timers[timer].cn = _timers[timer].bits - D/T;
                        return (0);
                }
        }
        return (-1);
}

This function uses global array of timers: _timers. This is the array of the timerc_t structs:
typedef struct timerc_t {
        volatile timer_handler_t alarm_handler;
        volatile unsigned long nover; // number of overflow
        unsigned long bits; // number of bits
        volatile unsigned long cn; // start from this count value
        volatile unsigned long cycles; // cycles number
        volatile int prescaler; // prescaler (1, 8, 32... - depends on arch. and timer)
        volatile int cycles_prescaler; // prescaler (usually 1024) for total counter cycles
        int *prescalers; // array of timer-counter prescalers
        int nprescalers; // length of prescalers' array
} timerc_t; 
Full implementation of timers support for AVR (Atmega128 only at the moment) are here.

среда, 21 сентября 2011 г.

Creating of Intel HEX with Tcl/Tk

Intel HEX format is the simple ASCII format usually used to program different devices (it's flash and EEPROM memories). To create such data records, I use this code:
package provide ihex 1.0

namespace eval ihex {
    namespace export data EOF eraser
    set EOF ":00000001FF"

    proc verint {type value} {
        # Verify that value is in the type range. Known types are b (byte),
        # w (word), d (doubleword), q (quadword). If is out-of-range, then
        # raise will be callen
        set absvalue [::tcl::mathfunc::abs $value]

        proc raise {t v} {
            # Raise error message. t is type name, such as "word" or "quadword"
            error [format "Value 0x%X is not a %s" $v $t]
        }

        switch -- $type {
            b { if {$absvalue > 255} {raise byte $absvalue} }
            w { if {$absvalue > 65536} {raise word $absvalue} }
            d { if {$absvalue > 4294967296} {raise doubleword $absvalue} }
            q { if {$absvalue > 18446744073709551616} {raise quadword $absvalue} }
        }
        return 1
    }

    proc args2bytes {args} {
        # Converts args to list of bytes (binary form of args). See
        # >>> args2bytes b1 w4A
        # 01 00 4A
        #
        # Each arg is integer prefixed by b|w|d|q and is in hex-form.
        set bytes [list]
        foreach arg $args {
            set r [scan $arg %1s%x argtype argvalue]
            if {$r != 2} {
                #puts "some string..."
                continue
            } else {
                # verify value of argvalue (in type bounds)
                verint $argtype $argvalue
                # format argvalue into string
                switch -- $argtype {
                    b {set fmt %02X}
                    w {set fmt %04X}
                    d {set fmt %08X}
                    q {set fmt %016X}
                }
                set sarg [format $fmt $argvalue]
                set argbytes [regexp -all -inline .. $sarg]
                set bytes [concat $bytes $argbytes]
            }
        }
        return $bytes
    }

    proc crc {bytes} {
        # Calculate Intel-hex CRC for list of bytes (usual numbers in hex format
        # but without prefixes like b|w|d|q)
        set sum 0
        foreach b $bytes {
            incr sum 0x$b
        }
        set sum [expr {(1 + ~$sum) & 0xFF}] ;# bcb
        return $sum
    }

    proc data {address args} {
        # Returns data record for Intel hex format. args are list of integers such
        # b23 wAE... - byte 0x23, word 0x00AE
        set bytes [args2bytes {*}$args]
        set len [args2bytes [format b%X [llength $bytes]]]
        set address [args2bytes $address]
        set type 00
        set cc [crc [concat {} $len $address $type $bytes]]

        # Prepare output line
        set head :$len[join $address ""]$type
        set tail [join $bytes ""][format %02X $cc]
        return $head$tail
    }

    proc join_eep {args} {
        # Join eep data records (":LLAAAA00..." strings) into one data record with size <= 256.
        # Does not check CRC of input records!
        set MAXSZ 256 ; # DONT USE > 256!!!
        # In the mem will be 256 "00 00 00..."
        for {set i 0} {$i < $MAXSZ} {incr i} { lappend mem 00 }
        set last_index 0
        foreach rec $args {
            set len [string range $rec 1 2]
            set address [string range $rec 3 6]
            set type [string range $rec 7 8]
            if {"0x$type" != 0} { continue } ;# skip not data records
            set bytes [string range $rec 9 end-2]
            set bytes [regexp -all -inline .. $bytes]
            #set ev [eval "expr {0x$address + 0x$len - 1}"]
            set from "0x$address"; set to [expr {"0x$address" + "0x$len" - 1}]
            set last_index [::tcl::mathfunc::max $last_index $to]
            set replacing "lreplace \$mem $from $to $bytes"
            set mem [eval $replacing]
        }
        set len [format b%X [expr {$last_index + 1}]]
        set len [args2bytes $len]
        set address "00 00"
        set type 00
        set mem [lrange $mem 0 $last_index]
        set cc [crc [concat {} $len $address $type $mem]]

        # Prepare output line
        set head :$len[join $address ""]$type
        set tail [join $mem ""][format %02X $cc]
        return $head$tail
    }
}
Procedure ihex::data is for create one record (ASCII line) from it's arguments as:
ihex::data b23 w0A
where each argument is value prefixed by b (for byte), w (for word), d (for doubleword), q (for quadword). Procedure ihex::join_eep is used to "join" into one memory 256-bytes chunk several records. It's arguments are in form of ":LLAAAATTDD...DCC". Procedure args2bytes is not exported but may be usable for creation bytes dump of it's arguments (also used b, w, d, q prefixes).

Changing icons in Windows Tcl Starpacks

Created Starpack will have default Tcl icons and you can see them in different view modes of Windows explorer (table, large icons...). This approach is based on http://wiki.tcl.tk/10922 receipt, but is in Makefile target form. And uses tools: reshacker.exe,  upx.exe and windres.exe from MinGW installation (at $MINGWHOME/bin/windres.exe), also supposes that you are using tclkit.exe as runtime. In my case I have application rfprg and "virtual" FS (catalog) - rfprg.vfs. And I have all these tools in the same catalog (with tclkit.exe). I created there the rfprg.rc file:
APPICONS ICON "rfprg.vfs/tclkit.ico"
TK ICON "rfprg.vfs/tclkit.ico"
Then I added next target into my Makefile:
icons:
	cp tclkit.exe tclkit1.exe
	upx.exe -d tclkit1.exe
	reshacker.exe -delete tclkit1.exe , tclkit2.exe , icongroup,,
	windres.exe -i rfprg.rc -o rfprg.res -J rc -O res -r
	reshacker.exe -add tclkit2.exe , tclkit3.exe , rfprg.res , ,,
	upx.exe tclkit3.exe
	mv tclkit3.exe tclkit.exe
	rm rfprg.res tclkit2.exe tclkit1.exe
And them:
make icons
This will replace "system" icons of tclkit.exe with rfprg.vfs/tclkit.ico. So you can use this "patched" tclkit.exe runtime to create Starpack with your custom icon.