Check-in [26bd6e7610]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Added code to properly export the commands of the package and to build a mapping dictionary for the ensemble command.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:26bd6e7610995ef2e5b06dabb6d1604e16076ca6
User & Date: andrewm 2014-05-08 14:40:09
Context
2014-05-20
14:21
Configuration edits in preparation for 1.0 release of mpssespi package. check-in: 79e24c1a17 user: andrewm tags: trunk
2014-05-08
14:40
Added code to properly export the commands of the package and to build a mapping dictionary for the ensemble command. check-in: 26bd6e7610 user: andrewm tags: trunk
2014-04-25
03:40
Documentation clean up and adding files. I'm calling this beta 2 but expect few changes before an "official" 1.0 release. check-in: 1f8119e942 user: andrewm tags: trunk, mpssespi-1.0b1
Changes

Changes to mpssespi/generic/litsrc/mpssespi.aweb.

1
2












3
4
5
6
7
8
9
..
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
...
125
126
127
128
129
130
131
132




133
134
135
136
137
138
139
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
408
409
410
411
412
413
414

415
416
417
418
419
420
421
...
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
...
439
440
441
442
443
444
445
446
447




448
449
450
451
452
453
454
...
476
477
478
479
480
481
482




483
484
485







486
487
488
489
490
491
492
493







494
495
496
497
498
499
500
...
558
559
560
561
562
563
564














565
566
567
568
569
570
571
...
673
674
675
676
677
678
679




680
681
682







683
684
685
686
687
688
689
...
796
797
798
799
800
801
802




803
804
805







806
807
808
809
810
811
812
...
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
...
903
904
905
906
907
908
909




910
911
912







913
914
915
916
917
918
919
...
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
....
1202
1203
1204
1205
1206
1207
1208




1209
1210
1211







1212
1213
1214
1215
1216
1217
1218
....
1677
1678
1679
1680
1681
1682
1683




1684
1685
1686







1687
1688
1689
1690
1691
1692
1693
....
1952
1953
1954
1955
1956
1957
1958




1959
1960
1961







1962
1963
1964
1965
1966
1967
1968
....
2119
2120
2121
2122
2123
2124
2125




2126
2127
2128







2129
2130
2131
2132
2133
2134
2135
....
2418
2419
2420
2421
2422
2423
2424




2425
2426
2427







2428
2429
2430
2431
2432
2433
2434
....
2668
2669
2670
2671
2672
2673
2674




2675
2676
2677







2678
2679
2680
2681
2682
2683
2684
....
2759
2760
2761
2762
2763
2764
2765




2766
2767
2768







2769
2770
2771
2772
2773
2774
2775
....
2855
2856
2857
2858
2859
2860
2861




2862
2863
2864







2865
2866
2867
2868
2869
2870
2871
....
2959
2960
2961
2962
2963
2964
2965




2966
2967
2968







2969
2970
2971
2972
2973
2974
2975
....
3052
3053
3054
3055
3056
3057
3058




3059
3060
3061







3062
3063
3064
3065
3066
3067
3068
....
3180
3181
3182
3183
3184
3185
3186


3187
3188
3189
3190
3191
3192
3193
....
3195
3196
3197
3198
3199
3200
3201
3202
3203





3204
3205
3206
3207
3208
3209
3210
....
3216
3217
3218
3219
3220
3221
3222

3223


3224
3225
3226


3227
3228
3229
3230
3231
3232

3233
3234
3235
3236
3237
3238
3239
3240
3241
....
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
....
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
....
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
















3768
3769
3770
3771
3772
3773
3774
....
3821
3822
3823
3824
3825
3826
3827












3828
3829
3830
3831
3832
3833
3834
// vim:set syntax=asciidoc:
= The mpssespi Package













== Introduction

Future Technology Devices International, Ltd. (R)
http://www.ftdichip.com[(FTDI)]
produces a series of USB to serial converter chips that are popular for
interfacing a variety of systems across a USB bus.
................................................................................
Further,
it is possible to interface multiple SPI peripherals to the same FTDI USB to
serial converter chip and channel semantics would be further strained to
support that concept.
So this package provides a rather more direct interface to +libMPSSE-SPI+
functions.

The commands provided by the +mpssespi+ package were chosen to match those
of the ``C'' functions provided by the
http://www.ftdichip.com/Support/Documents/AppNotes/AN_178_User%20Guide%20for%20LibMPSSE-SPI.pdf[library].
However,
the commands do _not_ copy the signature of the library functions
exactly.
Some of the library functions require configuration data be passed at
each invocation.
This is tedious, especially considering that much of the configuration
information does not change for a given hardware arrangement.
Consequently,
this package provides a means of storing the necessary configuration
information as a set of defaults and commands will use that configuration
information where required by the underlying +libMPSSE-SPI+ ``C'' functions.
Commands are provided to specify and examine the configuration information
in keeping with the usual conventions of Tcl packages for introspection.

In the next section we discuss the data that the +mpssespi+ package stores.
That is followed by the commands the package provides.

== Package Data
................................................................................
This package is written in the _thread neutral_ style,
_i.e._ this package may be loaded into multiple interpreters
simultaneously.
Since the FTDI library calls use blocking I/O,
some applications may find it necessary to perform the I/O in a
separate thread to prevent blocking other activities within the application.
Although one would anticipate the I/O blocking time to be very small
for SPI bus devices,




the ability to use threads or separate interpreters is still useful
and requires little additional code to implement.
To accomplish thread neutrality,
package data is held in memory that is associated with the interpreter
and _not_ as static ``C'' variables.
Tcl provides facilities just for this purpose.

................................................................................
<4> +transferOptions+ are the default options used during read and writes
if the caller does not specify any.

As is conventional in Tcl,
resources outside of the interpreter are represented as simple strings
(_e.g._ file channels)
and extension commands then map these arbitrary strings into the
resource information that they represent.
In the +mpssespi+ package,
the open SPI channels of +libMPSSE-SPI+ are
represented by strings of the form +mpssespi<number>+,
where +<number>+ is replaced by one or more decimal digits.
The +NewHandleMapping()+ function below creates these handles and
sets up the hash table for mapping the handle string names to the
required information.

(((functions, NewHandleMapping)))

[source,c]
----
<<utility functions>>=
static Tcl_Obj *
................................................................................
                Tcl_GetString(handleName))) ;
        return TCL_ERROR ;
    }
}
----
<1> We do allow for a +NULL+ value of the +chanConfigPtr+ parameter.
This allows a lookup just to verify the channel name.


== Package Commands

With all the preliminaries out of the way,
we now turn our attention to the package commands themself.
The commands and their names were chose to map directly to the
``C'' functions provided by +libMPSSE-SPI+.
................................................................................
The commands are not a _rote_ mapping as we have discussed,
particularly in handling configuration information.

=== Get Number of Channels

(((commands,getNumChannels)))

One of the first calls that an application would make to to
obtain the number of potential FTDI SPI channels attached.
Until a channel is actually open,
+libMPSSE-SPI+ references them via indices.
Those indices start at 0 and run up to the number of attached channels
minus one (_i.e._ in traditional ``C'' array indexing style).
The +getNumChannels+ command is a direct invocation of
the +SPI_GetNumChannels()+ library function.

................................................................................
+::mpssespi getNumChannels+

The command returns an integer value that
is the the number of SPI channels available.
*****

This is usually the first command that an application would invoke.
It is necessary to determine if there are any properly connected
devices and if so, the valid range of channel indices.





(((functions, GetNumChannelsProc)))

[source,c]
----
<<command procedures>>=
static int
................................................................................
    result = SetStatusResult(interp, status) ;     /* <2> */
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewLongObj(numChannels)) ;
    }
    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::getNumChannels", GetNumChannelsProc,
        clientData, NULL) ;







----
<1> Support for testing the package code without a FTDI device installed.
<2> Translating +libMPSSE-SPI+ returned status values to meaningful
Tcl interpreter results is accomplished in one place <<error-handling,below>>.

We will follow the pattern established above for the commands.
First we present the source code to the command
followed by the statement to create the command.








We will also establish a pattern of including +tcltest+ test code
along with the package implementation.
Testing an attached peripheral device poses special challenges.
We must make assumptions about what is connected.
Replicating the testing environment may be difficult for others.
So to overcome some of these difficulties,
................................................................................

++serialnumber++::
    A string value giving the serial number of the channel.

++description++::
    A string value giving a description of the channel.
*****















----
<<command procedures>>=
static int
GetChannelInfoProc(
    ClientData clientData,
    Tcl_Interp *interp,
................................................................................
    return result ;

errout:
    Tcl_DecrRefCount(infoObj) ;
    return TCL_ERROR ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::getChannelInfo", GetChannelInfoProc,
        clientData, NULL) ;







----
<1> Argument parsing and processing.
<2> Invoke +SPI_GetChannelInfo()+.
<3> Convert the returned values into a Tcl dictionary.
<4> Okay, for all you +goto+ whiners out there. Yes, there will be ++goto++s
in the code. But note, they always jump *forward* in the code
(never backward as that is truly just wrong)
................................................................................
                handle) ;       /* <1> */
        Tcl_SetObjResult(interp, handleName) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::openChannel", OpenChannelProc,
        clientData, NULL) ;







----
<1> We finally see a channel information function invocation.
Note that the +clientData+ argument to the command function is being
cast to a pointer to a +mpssespi+  package specific object.
The command creation code arranges for that value to be passed to each
of the commands in the package.
We will see how that happens <<load-initialization, below>>,
................................................................................
It turns out that there are alot of configuration options for a SPI
channel.
So we have created some <<config-cmds,configuration commands>>
to deal with reading and updating the channel configuration.

So after opening a channel it is necessary to initialize it.
If you wish to change the configuration used for the initialization,
then you can set different configuration values before invoking
the +initChannel+ command.
Otherwise,
you end up with the <<default-config, default configuration>>.

*****
+::mpssespi initChannel+ _?handle?_

................................................................................
        chanConfig->isInitialized = 1 ;     /* <3> */
        Tcl_ResetResult(interp) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::initChannel", InitChannelProc,
        clientData, NULL) ;







----
<1> Here we see how the package information (as passed via the +clientData+
argument) is used to map the channel handle back to information that
is needed to invoke +libMPSSE-SPI+ functions.
We will see this pattern often in the other commands.
<2> Note that initializing the channel does not use all of the configuration
information that we are holding.
................................................................................
(((commands,getChannelConfig)))

[[config-cmds,configuration commands]]

We can no long postpone dealing with channel configuration.
There are two main pieces of configuration information.
One part deals with configuring the channel as is done in
+SPI_InitChanne()+ and
the other part is used as part of each SPI bus transaction.

We first present the +getChannelConfig+ command.
This allows us to introspect the configuration that is being stored.

*****
+::mpssespi getChannelConfig+ _handle_

_handle_::
    A +mpssespi+ channel handle as returned from a successful call to
    the +openChannel+ command.
................................................................................
    Tcl_DecrRefCount(pinObj) ;

errorout:
    Tcl_DecrRefCount(configObj) ;
    return TCL_ERROR ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::getChannelConfig",
        GetChannelConfigProc, clientData, NULL) ;







----
<1> +GetDirectionDict()+ is discussed <<pin-direction-configuration, below>>.
<2> +GetPinValueDict()+ is also discussed <<pin-direction-configuration, below>>.
<3> Constants of the form +SPI_TRANSFER_OPTIONS_XXX+ are found in
the +libMPSSE_spi.h+ header file.

[source,tcl]
................................................................................
    return TCL_OK ;

errorout:
    Tcl_DecrRefCount(keyObj) ;
    return TCL_ERROR ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::setChannelConfig",
        SetChannelConfigProc, clientData, NULL) ;







----
<1> Here we take a copy of the channel configuration.
As we decode the input dictionary argument, the values are placed in this
copy.
<2> Missing keys are just silently ignored.
<3> Since transfer options may also be given on commands that
cause SPI bus transactions,
................................................................................
        DeleteHandleMapping(pkgInfo, handleObj) ; /* <1> */
        Tcl_ResetResult(interp) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::closeChannel", CloseChannelProc,
        clientData, NULL) ;







----
<1> We clean up the handle mapping and configuration information here.

[source,tcl]
----
<<closeChannel tests>>=
test closeChannel-1.0 {
................................................................................
    } else {
        Tcl_DecrRefCount(inputObj) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::readChannel", ReadChannelProc,
        clientData, NULL) ;







----
<1> The returned result will be a byte array.
The length is determined by how much data was requested.
<2> For bit transfers, if the number of bits transferred is not a
byte multiple, then we shift the residual bits to the upper part
of the byte.
This recognizes that the order is most significant bits first and
................................................................................
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(xferActual)) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::writeChannel", WriteChannelProc,
        clientData, NULL) ;







----

One minor complication in writing is we need to make sure that if
the user has specified that the units of transfer is to be _bits_
that he has given us a buffer that contains at least that many bits.
For transfers in _bytes_ units, we can simply take the lenght of the
tranfer.
................................................................................
    } else {
        Tcl_DecrRefCount(inputObj) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::readWriteChannel",
        ReadWriteChannelProc, clientData, NULL) ;







----

[source,tcl]
----
<<readWriteChannel tests>>=
test readWriteChannel-1.0 {
    read/write a channel
................................................................................
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(busyStatus)) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::isBusy", IsBusyProc, clientData,
        NULL) ;







----

[source,tcl]
----
<<isBusy tests>>=
test isBusy-1.0 {
    check if a channel is busy
................................................................................
    if (result == TCL_OK) {
        Tcl_ResetResult(interp) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::changeCS", ChangeCSProc, clientData,
        NULL) ;







----

[source,tcl]
----
<<changeCS tests>>=
test changeCS-1.0 {
    change config options
................................................................................
    if (result == TCL_OK) {
        Tcl_ResetResult(interp) ;
    }

    return result ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::writeGPIO", WriteGPIOProc,
        clientData, NULL) ;







----
<1> The function for handling I/O pin directions and values were
also used for setting configuration information.

[source,tcl]
----
<<writeGPIO tests>>=
................................................................................
        return TCL_ERROR ;
    }

    Tcl_SetObjResult(interp, valueDict) ;
    return TCL_OK ;
}





<<command creation>>=
Tcl_CreateObjCommand(interp, "::mpssespi::readGPIO", ReadGPIOProc,
        clientData, NULL) ;







----

[source,tcl]
----
<<readGPIO tests>>=
test readGPIO-1.0 {
    input GPIO values
................................................................................
DLLEXPORT       /* <1> */
int
Mpssespi_Init(
    Tcl_Interp *interp)
{
    ClientData clientData ;
    Tcl_Namespace *ns ;



    if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
        return TCL_ERROR ;
    }

    clientData = NewMPSSEPkgInfo(interp) ;  /* <2> */

................................................................................

    <<namespace creation>>
    <<command creation>>
    <<ensemble creation>>

    <<package configuration>>

    Tcl_PkgProvide(interp, "mpssespi", PACKAGE_VERSION) ;
    return TCL_OK ;





}
----
<1> Needed to build under Windows.
<2> We create new package specific storage and pass it's pointer as the
+clientData+ to each command procedure.

=== Creating the Package Namespace
................................................................................
[source,c]
----
<<static data>>=
static char const mpssespi_ns_name[] = "::mpssespi" ;

<<namespace creation>>=
ns = Tcl_CreateNamespace(interp, mpssespi_ns_name, NULL, NULL) ;

----



We build the ensemble command by exporting all the commands in the
namespace and then creating the ensemble.



[source,c]
----
<<ensemble creation>>=
if (Tcl_Export(interp, ns, "*", 0) != TCL_OK) {
    return TCL_ERROR ;

}
(void)Tcl_CreateEnsemble(interp, mpssespi_ns_name, ns, TCL_ENSEMBLE_PREFIX) ;
----

==== Unloading

Unloading is supported and we use this as a good place to call the library
clean up function.

................................................................................
of package configuration in formation.

Here we support keys of +pkgname+, +version+ and +copyright+.

[source,c]
----
<<static data>>=
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 5
static Tcl_Config mpssespi_config[] = {
    {"pkgname", PACKAGE_NAME},
    {"version", PACKAGE_VERSION},
    {"copyright",
"This software is copyrighted 2014 by G. Andrew Mangogna.\
 Terms and conditions for use are distributed with the source code."},
    {NULL, NULL}
} ;
#endif
----

We must register the configuration information.

[source,c]
----
<<package configuration>>=
#       if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 5
    Tcl_RegisterConfig(interp, PACKAGE_NAME, mpssespi_config, "iso8859-1") ;
#       endif
----

== Source Organization

This document is a literate program.
As you have seen it contains both description and source
code to the package.
................................................................................
<<command procedures>>

/*
 * EXTERNAL FUNCTION DEFINITIONS
 */
<<initialization>>

#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 5
<<unloading>>
#endif /* TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 5 */
----

=== Test Source

A +tcltest+ source file can be extracted starting at the +mpssespi.test+ root.

[source,tcl]
................................................................................
        csactive high
    }
    mpssespi initChannel $spichan       ; # <1>

    for {set address 0} {$address < 16} {incr address} {
        set data [expr {$address + 3}]
        puts "writing address $address, data = $data"
        set bindata [binary format S $data]
        write_byte $spichan $address $bindata
    }

    for {set address 0} {$address < 16} {incr address} {
        set bindata [read_byte $spichan $address]
        binary scan $bindata Su $bindata data
        puts "reading address $address, data = $data"
    }

    mpssespi closeChannel $spichan
}

# Run the example
main
----
<1> We copy the configuration information from the example.
Note this chip uses an active high chip select.

















== Special Linux Considerations

Most modern version of Linux use *udev* as the means of handling
hot plugged devices.
Associated with *udev* are a set of rules that determine how devices
are handled.
................................................................................
For example, leaving out the +ATTR\{serial}== ...+, clause will
cause the rule to match all FTDI devices.
You can consult the many posting on the Internet about writing +udev+ rules.
The example above is just to get you started thinking about what would
be needed to make the file permissions work smoothly in your particular
configuration.













== Known Problems

[[known-problems, known problems]]
As of version 1.0,
it seems that invoking the +readWriteChannel+ command using transfer
units of +bytes+ does not function properly.
The SPI bus transactions are not correct.


>
>
>
>
>
>
>
>
>
>
>
>







 







|
|











|







 







|
>
>
>
>







 







|






|







 







>







 







|
|







 







|
|
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







|
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







|







 







>
>
>
>

|

>
>
>
>
>
>
>







 







|
|


|







 







>
>
>
>

|
|
>
>
>
>
>
>
>







 







>
>
>
>

|
|
>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







>
>
>
>

|
|
>
>
>
>
>
>
>







 







>
>
>
>

|
|
>
>
>
>
>
>
>







 







>
>
>
>

|
|
>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







>
>
>
>

|

>
>
>
>
>
>
>







 







>
>







 







|

>
>
>
>
>







 







>

>
>

<
<
>
>




|
|
>

<







 







<








<







<

<







 







<

<







 







|





|











>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
...
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
...
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
...
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
...
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
...
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
...
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
...
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
...
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
...
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
...
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
...
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
....
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
....
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
....
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
....
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
....
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
....
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
....
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
....
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
....
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
....
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
....
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
....
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
....
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
....
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430


3431
3432
3433
3434
3435
3436
3437
3438
3439
3440

3441
3442
3443
3444
3445
3446
3447
....
3497
3498
3499
3500
3501
3502
3503

3504
3505
3506
3507
3508
3509
3510
3511

3512
3513
3514
3515
3516
3517
3518

3519

3520
3521
3522
3523
3524
3525
3526
....
3663
3664
3665
3666
3667
3668
3669

3670

3671
3672
3673
3674
3675
3676
3677
....
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
....
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
// vim:set syntax=asciidoc:
= The mpssespi Package

[abstract]
--
This document is a literate program for the +mpssespi+ Tcl package.
As a literate program it contains both a discussion of the details
of the package as well as the implementation source code.
The +mpssespi+ package provides a Tcl interface to the FTDI
libMPSSE-SPI library.
This library is used to interface FTDI USB to serial converter chips
to SPI bus peripheral components allowing the peripherals
to be controlled across a USB interface.
--

== Introduction

Future Technology Devices International, Ltd. (R)
http://www.ftdichip.com[(FTDI)]
produces a series of USB to serial converter chips that are popular for
interfacing a variety of systems across a USB bus.
................................................................................
Further,
it is possible to interface multiple SPI peripherals to the same FTDI USB to
serial converter chip and channel semantics would be further strained to
support that concept.
So this package provides a rather more direct interface to +libMPSSE-SPI+
functions.

The names of the commands provided by the +mpssespi+ package were chosen to
match those of the ``C'' functions provided by the
http://www.ftdichip.com/Support/Documents/AppNotes/AN_178_User%20Guide%20for%20LibMPSSE-SPI.pdf[library].
However,
the commands do _not_ copy the signature of the library functions
exactly.
Some of the library functions require configuration data be passed at
each invocation.
This is tedious, especially considering that much of the configuration
information does not change for a given hardware arrangement.
Consequently,
this package provides a means of storing the necessary configuration
information as a set of defaults and commands will use that configuration
information where required by the underlying +libMPSSE-SPI+ functions.
Commands are provided to specify and examine the configuration information
in keeping with the usual conventions of Tcl packages for introspection.

In the next section we discuss the data that the +mpssespi+ package stores.
That is followed by the commands the package provides.

== Package Data
................................................................................
This package is written in the _thread neutral_ style,
_i.e._ this package may be loaded into multiple interpreters
simultaneously.
Since the FTDI library calls use blocking I/O,
some applications may find it necessary to perform the I/O in a
separate thread to prevent blocking other activities within the application.
Although one would anticipate the I/O blocking time to be very small
for SPI bus devicesfootnote:[Recall that the SPI bus is clocked
_synchronously_ and its operation does not really depend upon
the peripheral device even being present.
Consequently, the I/O time is simply the time associated with
the number of bits clocked onto and off of the SPI bus.],
the ability to use threads or separate interpreters is still useful
and requires little additional code to implement.
To accomplish thread neutrality,
package data is held in memory that is associated with the interpreter
and _not_ as static ``C'' variables.
Tcl provides facilities just for this purpose.

................................................................................
<4> +transferOptions+ are the default options used during read and writes
if the caller does not specify any.

As is conventional in Tcl,
resources outside of the interpreter are represented as simple strings
(_e.g._ file channels)
and extension commands then map these arbitrary strings into the
extension specific resource information they represent.
In the +mpssespi+ package,
the open SPI channels of +libMPSSE-SPI+ are
represented by strings of the form +mpssespi<number>+,
where +<number>+ is replaced by one or more decimal digits.
The +NewHandleMapping()+ function below creates these handles and
sets up the hash table for mapping the handle string names to the
channel information.

(((functions, NewHandleMapping)))

[source,c]
----
<<utility functions>>=
static Tcl_Obj *
................................................................................
                Tcl_GetString(handleName))) ;
        return TCL_ERROR ;
    }
}
----
<1> We do allow for a +NULL+ value of the +chanConfigPtr+ parameter.
This allows a lookup just to verify the channel name.
However, this is not a feature of the function used in the package.

== Package Commands

With all the preliminaries out of the way,
we now turn our attention to the package commands themself.
The commands and their names were chose to map directly to the
``C'' functions provided by +libMPSSE-SPI+.
................................................................................
The commands are not a _rote_ mapping as we have discussed,
particularly in handling configuration information.

=== Get Number of Channels

(((commands,getNumChannels)))

One of the first things that an application should determine is the
the number of attached FTDI SPI channels.
Until a channel is actually open,
+libMPSSE-SPI+ references them via indices.
Those indices start at 0 and run up to the number of attached channels
minus one (_i.e._ in traditional ``C'' array indexing style).
The +getNumChannels+ command is a direct invocation of
the +SPI_GetNumChannels()+ library function.

................................................................................
+::mpssespi getNumChannels+

The command returns an integer value that
is the the number of SPI channels available.
*****

This is usually the first command that an application would invoke.
It is necessary to determine the valid range of channel indices.
Note that if there are multiple channels attached,
this call still does not tell you enough information to determine
to which channel the peripheral is attached.
All you can determine here by this command is the total number
of attached channels.

(((functions, GetNumChannelsProc)))

[source,c]
----
<<command procedures>>=
static int
................................................................................
    result = SetStatusResult(interp, status) ;     /* <2> */
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewLongObj(numChannels)) ;
    }
    return result ;
}

<<static data>>=
static char const getNumChannelsCmdName[] = "::mpssespi::getNumChannels" ;
static char const getNumChannelsName[] = "getNumChannels" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, getNumChannelsCmdName, GetNumChannelsProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, getNumChannelsName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(getNumChannelsName, -1),
        Tcl_NewStringObj(getNumChannelsCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> Support for testing the package code without a FTDI device installed.
<2> Translating +libMPSSE-SPI+ returned status values to meaningful
Tcl interpreter results is accomplished in one place <<error-handling,below>>.

We will follow the pattern established above for the commands.
First we present the source code to the command
followed by the statements to create the command.
For this package,
we export all the commands and will then create a +namespace ensemble+
command that has the same name as the namespace where the commands
reside.
We will also supply an ensemble mapping dictionary.
The intent of all of this is to allow the ensemble of the package
to be extended at the script level.

We will also establish a pattern of including +tcltest+ test code
along with the package implementation.
Testing an attached peripheral device poses special challenges.
We must make assumptions about what is connected.
Replicating the testing environment may be difficult for others.
So to overcome some of these difficulties,
................................................................................

++serialnumber++::
    A string value giving the serial number of the channel.

++description++::
    A string value giving a description of the channel.
*****

The information given by this command can be used to determine which
SPI channel is connected to a particular peripheral.
By some experimentation of hot plugging and unplugging the FTDI converter,
you should be able to determine the serial number or location ID
of the channel attached to your peripheral.
The details of this are, of course, specific to your hardware arrangement
but once determined it is a simple script to search the channel
information of all the attached SPI channels for the one connected to
your peripheral.
It is also quite common to have only a single FTDI converter attached.
In this case, finding the correct channel is trivial.
The reason all this is necessary is that we must know the index of
the channel we wish to open and those indices are not predetermined.

----
<<command procedures>>=
static int
GetChannelInfoProc(
    ClientData clientData,
    Tcl_Interp *interp,
................................................................................
    return result ;

errout:
    Tcl_DecrRefCount(infoObj) ;
    return TCL_ERROR ;
}

<<static data>>=
static char const getChannelInfoCmdName[] = "::mpssespi::getChannelInfo" ;
static char const getChannelInfoName[] = "getChannelInfo" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, getChannelInfoCmdName, GetChannelInfoProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, getChannelInfoName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(getChannelInfoName, -1),
        Tcl_NewStringObj(getChannelInfoCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> Argument parsing and processing.
<2> Invoke +SPI_GetChannelInfo()+.
<3> Convert the returned values into a Tcl dictionary.
<4> Okay, for all you +goto+ whiners out there. Yes, there will be ++goto++s
in the code. But note, they always jump *forward* in the code
(never backward as that is truly just wrong)
................................................................................
                handle) ;       /* <1> */
        Tcl_SetObjResult(interp, handleName) ;
    }

    return result ;
}

<<static data>>=
static char const openChannelCmdName[] = "::mpssespi::openChannel" ;
static char const openChannelName[] = "openChannel" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, openChannelCmdName, OpenChannelProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, openChannelName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(openChannelName, -1),
        Tcl_NewStringObj(openChannelCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> We finally see a channel information function invocation.
Note that the +clientData+ argument to the command function is being
cast to a pointer to a +mpssespi+  package specific object.
The command creation code arranges for that value to be passed to each
of the commands in the package.
We will see how that happens <<load-initialization, below>>,
................................................................................
It turns out that there are alot of configuration options for a SPI
channel.
So we have created some <<config-cmds,configuration commands>>
to deal with reading and updating the channel configuration.

So after opening a channel it is necessary to initialize it.
If you wish to change the configuration used for the initialization,
then you can set different configuration values *before* invoking
the +initChannel+ command.
Otherwise,
you end up with the <<default-config, default configuration>>.

*****
+::mpssespi initChannel+ _?handle?_

................................................................................
        chanConfig->isInitialized = 1 ;     /* <3> */
        Tcl_ResetResult(interp) ;
    }

    return result ;
}

<<static data>>=
static char const initChannelCmdName[] = "::mpssespi::initChannel" ;
static char const initChannelName[] = "initChannel" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, initChannelCmdName, InitChannelProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, initChannelName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(initChannelName, -1),
        Tcl_NewStringObj(initChannelCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> Here we see how the package information (as passed via the +clientData+
argument) is used to map the channel handle back to information that
is needed to invoke +libMPSSE-SPI+ functions.
We will see this pattern often in the other commands.
<2> Note that initializing the channel does not use all of the configuration
information that we are holding.
................................................................................
(((commands,getChannelConfig)))

[[config-cmds,configuration commands]]

We can no long postpone dealing with channel configuration.
There are two main pieces of configuration information.
One part deals with configuring the channel as is done in
+SPI_InitChannel()+ and
the other part is used for each SPI bus transaction.

We first present the +getChannelConfig+ command.
This allows us to inspect the configuration that is being stored.

*****
+::mpssespi getChannelConfig+ _handle_

_handle_::
    A +mpssespi+ channel handle as returned from a successful call to
    the +openChannel+ command.
................................................................................
    Tcl_DecrRefCount(pinObj) ;

errorout:
    Tcl_DecrRefCount(configObj) ;
    return TCL_ERROR ;
}

<<static data>>=
static char const getChannelConfigCmdName[] = "::mpssespi::getChannelConfig" ;
static char const getChannelConfigName[] = "getChannelConfig" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, getChannelConfigCmdName, GetChannelConfigProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, getChannelConfigName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(getChannelConfigName, -1),
        Tcl_NewStringObj(getChannelConfigCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> +GetDirectionDict()+ is discussed <<pin-direction-configuration, below>>.
<2> +GetPinValueDict()+ is also discussed <<pin-direction-configuration, below>>.
<3> Constants of the form +SPI_TRANSFER_OPTIONS_XXX+ are found in
the +libMPSSE_spi.h+ header file.

[source,tcl]
................................................................................
    return TCL_OK ;

errorout:
    Tcl_DecrRefCount(keyObj) ;
    return TCL_ERROR ;
}

<<static data>>=
static char const setChannelConfigCmdName[] = "::mpssespi::setChannelConfig" ;
static char const setChannelConfigName[] = "setChannelConfig" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, setChannelConfigCmdName, SetChannelConfigProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, setChannelConfigName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(setChannelConfigName, -1),
        Tcl_NewStringObj(setChannelConfigCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> Here we take a copy of the channel configuration.
As we decode the input dictionary argument, the values are placed in this
copy.
<2> Missing keys are just silently ignored.
<3> Since transfer options may also be given on commands that
cause SPI bus transactions,
................................................................................
        DeleteHandleMapping(pkgInfo, handleObj) ; /* <1> */
        Tcl_ResetResult(interp) ;
    }

    return result ;
}

<<static data>>=
static char const closeChannelCmdName[] = "::mpssespi::closeChannel" ;
static char const closeChannelName[] = "closeChannel" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, closeChannelCmdName, CloseChannelProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, closeChannelName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(closeChannelName, -1),
        Tcl_NewStringObj(closeChannelCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> We clean up the handle mapping and configuration information here.

[source,tcl]
----
<<closeChannel tests>>=
test closeChannel-1.0 {
................................................................................
    } else {
        Tcl_DecrRefCount(inputObj) ;
    }

    return result ;
}

<<static data>>=
static char const readChannelCmdName[] = "::mpssespi::readChannel" ;
static char const readChannelName[] = "readChannel" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, readChannelCmdName, ReadChannelProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, readChannelName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(readChannelName, -1),
        Tcl_NewStringObj(readChannelCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> The returned result will be a byte array.
The length is determined by how much data was requested.
<2> For bit transfers, if the number of bits transferred is not a
byte multiple, then we shift the residual bits to the upper part
of the byte.
This recognizes that the order is most significant bits first and
................................................................................
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(xferActual)) ;
    }

    return result ;
}

<<static data>>=
static char const writeChannelCmdName[] = "::mpssespi::writeChannel" ;
static char const writeChannelName[] = "writeChannel" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, writeChannelCmdName, WriteChannelProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, writeChannelName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(writeChannelName, -1),
        Tcl_NewStringObj(writeChannelCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----

One minor complication in writing is we need to make sure that if
the user has specified that the units of transfer is to be _bits_
that he has given us a buffer that contains at least that many bits.
For transfers in _bytes_ units, we can simply take the lenght of the
tranfer.
................................................................................
    } else {
        Tcl_DecrRefCount(inputObj) ;
    }

    return result ;
}

<<static data>>=
static char const readWriteChannelCmdName[] = "::mpssespi::readWriteChannel" ;
static char const readWriteChannelName[] = "readWriteChannel" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, readWriteChannelCmdName, ReadWriteChannelProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, readWriteChannelName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(readWriteChannelName, -1),
        Tcl_NewStringObj(readWriteChannelCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----

[source,tcl]
----
<<readWriteChannel tests>>=
test readWriteChannel-1.0 {
    read/write a channel
................................................................................
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(busyStatus)) ;
    }

    return result ;
}

<<static data>>=
static char const isBusyCmdName[] = "::mpssespi::isBusy" ;
static char const isBusyName[] = "isBusy" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, isBusyCmdName, IsBusyProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, isBusyName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(isBusyName, -1),
        Tcl_NewStringObj(isBusyCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----

[source,tcl]
----
<<isBusy tests>>=
test isBusy-1.0 {
    check if a channel is busy
................................................................................
    if (result == TCL_OK) {
        Tcl_ResetResult(interp) ;
    }

    return result ;
}

<<static data>>=
static char const changeCSCmdName[] = "::mpssespi::changeCS" ;
static char const changeCSName[] = "changeCS" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, changeCSCmdName, ChangeCSProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, changeCSName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(changeCSName, -1),
        Tcl_NewStringObj(changeCSCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----

[source,tcl]
----
<<changeCS tests>>=
test changeCS-1.0 {
    change config options
................................................................................
    if (result == TCL_OK) {
        Tcl_ResetResult(interp) ;
    }

    return result ;
}

<<static data>>=
static char const writeGPIOCmdName[] = "::mpssespi::writeGPIO" ;
static char const writeGPIOName[] = "writeGPIO" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, writeGPIOCmdName, WriteGPIOProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, writeGPIOName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(writeGPIOName, -1),
        Tcl_NewStringObj(writeGPIOCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----
<1> The function for handling I/O pin directions and values were
also used for setting configuration information.

[source,tcl]
----
<<writeGPIO tests>>=
................................................................................
        return TCL_ERROR ;
    }

    Tcl_SetObjResult(interp, valueDict) ;
    return TCL_OK ;
}

<<static data>>=
static char const readGPIOCmdName[] = "::mpssespi::readGPIO" ;
static char const readGPIOName[] = "readGPIO" ;

<<command creation>>=
Tcl_CreateObjCommand(interp, readGPIOCmdName, ReadGPIOProc,
        clientData, NULL) ;
if (Tcl_Export(interp, ns, readGPIOName, 0) != TCL_OK) {
    goto errorout ;
}
if (Tcl_DictObjPut(interp, mapObj, Tcl_NewStringObj(readGPIOName, -1),
        Tcl_NewStringObj(readGPIOCmdName, -1)) != TCL_OK) {
    goto errorout ;
}
----

[source,tcl]
----
<<readGPIO tests>>=
test readGPIO-1.0 {
    input GPIO values
................................................................................
DLLEXPORT       /* <1> */
int
Mpssespi_Init(
    Tcl_Interp *interp)
{
    ClientData clientData ;
    Tcl_Namespace *ns ;
    Tcl_Obj *mapObj ;
    Tcl_Command cmdToken ;

    if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
        return TCL_ERROR ;
    }

    clientData = NewMPSSEPkgInfo(interp) ;  /* <2> */

................................................................................

    <<namespace creation>>
    <<command creation>>
    <<ensemble creation>>

    <<package configuration>>

    Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) ;
    return TCL_OK ;

errorout:
    Tcl_DecrRefCount(mapObj) ;
    Tcl_DeleteNamespace(ns) ;
    return TCL_ERROR ;
}
----
<1> Needed to build under Windows.
<2> We create new package specific storage and pass it's pointer as the
+clientData+ to each command procedure.

=== Creating the Package Namespace
................................................................................
[source,c]
----
<<static data>>=
static char const mpssespi_ns_name[] = "::mpssespi" ;

<<namespace creation>>=
ns = Tcl_CreateNamespace(interp, mpssespi_ns_name, NULL, NULL) ;
mapObj = Tcl_NewDictObj() ;     /* <1> */
----
<1> We also obtain a dictionary object that is used to create
the ensemble command mapping.



Once all the commands have been created and exported,
we can create the ensemble command and install the command map.

[source,c]
----
<<ensemble creation>>=
cmdToken = Tcl_CreateEnsemble(interp, mpssespi_ns_name, ns, TCL_ENSEMBLE_PREFIX) ;
if (Tcl_SetEnsembleMappingDict(interp, cmdToken, mapObj) != TCL_OK) {
    goto errorout ;
}

----

==== Unloading

Unloading is supported and we use this as a good place to call the library
clean up function.

................................................................................
of package configuration in formation.

Here we support keys of +pkgname+, +version+ and +copyright+.

[source,c]
----
<<static data>>=

static Tcl_Config mpssespi_config[] = {
    {"pkgname", PACKAGE_NAME},
    {"version", PACKAGE_VERSION},
    {"copyright",
"This software is copyrighted 2014 by G. Andrew Mangogna.\
 Terms and conditions for use are distributed with the source code."},
    {NULL, NULL}
} ;

----

We must register the configuration information.

[source,c]
----
<<package configuration>>=

    Tcl_RegisterConfig(interp, PACKAGE_NAME, mpssespi_config, "iso8859-1") ;

----

== Source Organization

This document is a literate program.
As you have seen it contains both description and source
code to the package.
................................................................................
<<command procedures>>

/*
 * EXTERNAL FUNCTION DEFINITIONS
 */
<<initialization>>


<<unloading>>

----

=== Test Source

A +tcltest+ source file can be extracted starting at the +mpssespi.test+ root.

[source,tcl]
................................................................................
        csactive high
    }
    mpssespi initChannel $spichan       ; # <1>

    for {set address 0} {$address < 16} {incr address} {
        set data [expr {$address + 3}]
        puts "writing address $address, data = $data"
        set bindata [binary format S $data]     ; # <2>
        write_byte $spichan $address $bindata
    }

    for {set address 0} {$address < 16} {incr address} {
        set bindata [read_byte $spichan $address]
        binary scan $bindata Su $bindata data   ; # <3>
        puts "reading address $address, data = $data"
    }

    mpssespi closeChannel $spichan
}

# Run the example
main
----
<1> We copy the configuration information from the example.
Note this chip uses an active high chip select.
<2> The SPI bus deals in raw binary data. The contents of the +data+
variable, like all things in Tcl, is represented as a string.
<3> Conversely, the data read from the SPI bus is binary and
needs to be scanned to obtain a Tcl string representation.

This example is very simple, as most examples are.
It also follows the design flow of the original ``C'' program
from FTDI.
My suggestion for users of this package is to create chip specific
packages that deal with the commands and transaction as
required for the specific chip.
That chip specific package would then use the +mpssespi+ package
to perform the actual SPI bus transactions.
It's worth sorting through the chip data sheet and encoding the
particular way a chip works into a package so that you can
get on with other matters.

== Special Linux Considerations

Most modern version of Linux use *udev* as the means of handling
hot plugged devices.
Associated with *udev* are a set of rules that determine how devices
are handled.
................................................................................
For example, leaving out the +ATTR\{serial}== ...+, clause will
cause the rule to match all FTDI devices.
You can consult the many posting on the Internet about writing +udev+ rules.
The example above is just to get you started thinking about what would
be needed to make the file permissions work smoothly in your particular
configuration.

Another problem you may have when loading the package is occurs if
the +D2XX+ library is not found.
In that case, there will be a core dump with the following message:

----
../../Infra/src/ftdi_infra.c:243:Init_libMPSSE(): NULL expression encountered
----

It may be necessary to set the value of the +LD_LIBRARY_PATH+ environment
variable to include the directory where the driver is installed.
FTDI installation instruction place the driver in +/usr/local/lib+.

== Known Problems

[[known-problems, known problems]]
As of version 1.0,
it seems that invoking the +readWriteChannel+ command using transfer
units of +bytes+ does not function properly.
The SPI bus transactions are not correct.