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: |
26bd6e7610995ef2e5b06dabb6d1604e |
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. |