Merge pull request #430 from waveform80/spi-tests

Fix #421
This commit is contained in:
Dave Jones
2016-09-08 22:29:35 +01:00
committed by GitHub
10 changed files with 975 additions and 194 deletions

View File

@@ -11,6 +11,7 @@ digraph classes {
MCP3xxx;
MCP30xx;
MCP32xx;
MCP3xx2;
MCP33xx;
/* Concrete classes */
@@ -21,6 +22,8 @@ digraph classes {
MCP30xx->MCP3xxx;
MCP32xx->MCP3xxx;
MCP33xx->MCP3xxx;
MCP3xx2->MCP3xxx;
MCP3001->MCP30xx;
MCP3002->MCP30xx;
MCP3004->MCP30xx;
@@ -29,6 +32,8 @@ digraph classes {
MCP3202->MCP32xx;
MCP3204->MCP32xx;
MCP3208->MCP32xx;
MCP3002->MCP3xx2;
MCP3202->MCP3xx2;
MCP3301->MCP33xx;
MCP3302->MCP33xx;
MCP3304->MCP33xx;

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.36.0 (20140111.2315)
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: classes Pages: 1 -->
<svg width="870pt" height="404pt"
@@ -11,38 +11,38 @@
<polygon fill="white" stroke="none" points="-4,4 -4,-400 866,-400 866,4 -4,4"/>
<!-- Device -->
<g id="node1" class="node"><title>Device</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="498,-396 444,-396 444,-360 498,-360 498,-396"/>
<text text-anchor="middle" x="471" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="418,-396 364,-396 364,-360 418,-360 418,-396"/>
<text text-anchor="middle" x="391" y="-375.5" font-family="Sans" font-size="10.00" fill="#000000">Device</text>
</g>
<!-- SPIDevice -->
<g id="node2" class="node"><title>SPIDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="503,-324 439,-324 439,-288 503,-288 503,-324"/>
<text text-anchor="middle" x="471" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">SPIDevice</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="423,-324 359,-324 359,-288 423,-288 423,-324"/>
<text text-anchor="middle" x="391" y="-303.5" font-family="Sans" font-size="10.00" fill="#000000">SPIDevice</text>
</g>
<!-- SPIDevice&#45;&gt;Device -->
<g id="edge1" class="edge"><title>SPIDevice&#45;&gt;Device</title>
<path fill="none" stroke="black" d="M471,-324.303C471,-332.017 471,-341.288 471,-349.888"/>
<polygon fill="black" stroke="black" points="467.5,-349.896 471,-359.896 474.5,-349.896 467.5,-349.896"/>
<path fill="none" stroke="black" d="M391,-324.303C391,-332.017 391,-341.288 391,-349.888"/>
<polygon fill="black" stroke="black" points="387.5,-349.896 391,-359.896 394.5,-349.896 387.5,-349.896"/>
</g>
<!-- AnalogInputDevice -->
<g id="node3" class="node"><title>AnalogInputDevice</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="524,-252 418,-252 418,-216 524,-216 524,-252"/>
<text text-anchor="middle" x="471" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">AnalogInputDevice</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="444,-252 338,-252 338,-216 444,-216 444,-252"/>
<text text-anchor="middle" x="391" y="-231.5" font-family="Sans" font-size="10.00" fill="#000000">AnalogInputDevice</text>
</g>
<!-- AnalogInputDevice&#45;&gt;SPIDevice -->
<g id="edge2" class="edge"><title>AnalogInputDevice&#45;&gt;SPIDevice</title>
<path fill="none" stroke="black" d="M471,-252.303C471,-260.017 471,-269.288 471,-277.888"/>
<polygon fill="black" stroke="black" points="467.5,-277.896 471,-287.896 474.5,-277.896 467.5,-277.896"/>
<path fill="none" stroke="black" d="M391,-252.303C391,-260.017 391,-269.288 391,-277.888"/>
<polygon fill="black" stroke="black" points="387.5,-277.896 391,-287.896 394.5,-277.896 387.5,-277.896"/>
</g>
<!-- MCP3xxx -->
<g id="node4" class="node"><title>MCP3xxx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="500.5,-180 441.5,-180 441.5,-144 500.5,-144 500.5,-180"/>
<text text-anchor="middle" x="471" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xxx</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="420.5,-180 361.5,-180 361.5,-144 420.5,-144 420.5,-180"/>
<text text-anchor="middle" x="391" y="-159.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xxx</text>
</g>
<!-- MCP3xxx&#45;&gt;AnalogInputDevice -->
<g id="edge3" class="edge"><title>MCP3xxx&#45;&gt;AnalogInputDevice</title>
<path fill="none" stroke="black" d="M471,-180.303C471,-188.017 471,-197.288 471,-205.888"/>
<polygon fill="black" stroke="black" points="467.5,-205.896 471,-215.896 474.5,-205.896 467.5,-205.896"/>
<path fill="none" stroke="black" d="M391,-180.303C391,-188.017 391,-197.288 391,-205.888"/>
<polygon fill="black" stroke="black" points="387.5,-205.896 391,-215.896 394.5,-205.896 387.5,-205.896"/>
</g>
<!-- MCP30xx -->
<g id="node5" class="node"><title>MCP30xx</title>
@@ -51,138 +51,158 @@
</g>
<!-- MCP30xx&#45;&gt;MCP3xxx -->
<g id="edge4" class="edge"><title>MCP30xx&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M220.7,-98.425C271.447,-111.112 374.849,-136.962 431.579,-151.145"/>
<polygon fill="black" stroke="black" points="431.055,-154.621 441.605,-153.651 432.752,-147.83 431.055,-154.621"/>
<path fill="none" stroke="black" d="M221.376,-101.631C256.184,-113.814 313.335,-133.817 351.556,-147.195"/>
<polygon fill="black" stroke="black" points="350.725,-150.612 361.319,-150.612 353.037,-144.005 350.725,-150.612"/>
</g>
<!-- MCP32xx -->
<g id="node6" class="node"><title>MCP32xx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="501,-108 441,-108 441,-72 501,-72 501,-108"/>
<text text-anchor="middle" x="471" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP32xx</text>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="461,-108 401,-108 401,-72 461,-72 461,-108"/>
<text text-anchor="middle" x="431" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP32xx</text>
</g>
<!-- MCP32xx&#45;&gt;MCP3xxx -->
<g id="edge5" class="edge"><title>MCP32xx&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M471,-108.303C471,-116.017 471,-125.288 471,-133.888"/>
<polygon fill="black" stroke="black" points="467.5,-133.896 471,-143.896 474.5,-133.896 467.5,-133.896"/>
<path fill="none" stroke="black" d="M421.112,-108.303C416.511,-116.356 410.94,-126.106 405.847,-135.018"/>
<polygon fill="black" stroke="black" points="402.696,-133.477 400.774,-143.896 408.774,-136.95 402.696,-133.477"/>
</g>
<!-- MCP3xx2 -->
<g id="node7" class="node"><title>MCP3xx2</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="381,-108 321,-108 321,-72 381,-72 381,-108"/>
<text text-anchor="middle" x="351" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP3xx2</text>
</g>
<!-- MCP3xx2&#45;&gt;MCP3xxx -->
<g id="edge7" class="edge"><title>MCP3xx2&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M360.888,-108.303C365.489,-116.356 371.06,-126.106 376.153,-135.018"/>
<polygon fill="black" stroke="black" points="373.226,-136.95 381.226,-143.896 379.304,-133.477 373.226,-136.95"/>
</g>
<!-- MCP33xx -->
<g id="node7" class="node"><title>MCP33xx</title>
<g id="node8" class="node"><title>MCP33xx</title>
<polygon fill="#9ec6e0" stroke="#9ec6e0" points="741,-108 681,-108 681,-72 741,-72 741,-108"/>
<text text-anchor="middle" x="711" y="-87.5" font-family="Sans" font-size="10.00" fill="#000000">MCP33xx</text>
</g>
<!-- MCP33xx&#45;&gt;MCP3xxx -->
<g id="edge6" class="edge"><title>MCP33xx&#45;&gt;MCP3xxx</title>
<path fill="none" stroke="black" d="M681.089,-99.724C637.918,-112.316 558.088,-135.599 510.235,-149.556"/>
<polygon fill="black" stroke="black" points="509.154,-146.226 500.534,-152.386 511.114,-152.946 509.154,-146.226"/>
<path fill="none" stroke="black" d="M680.895,-97.5854C622.855,-110.282 495.498,-138.141 430.644,-152.328"/>
<polygon fill="black" stroke="black" points="429.813,-148.927 420.792,-154.483 431.309,-155.765 429.813,-148.927"/>
</g>
<!-- MCP3001 -->
<g id="node8" class="node"><title>MCP3001</title>
<polygon fill="#2980b9" stroke="#2980b9" points="62,-36 3.55271e-15,-36 3.55271e-15,-0 62,-0 62,-36"/>
<text text-anchor="middle" x="31" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3001</text>
<g id="node9" class="node"><title>MCP3001</title>
<polygon fill="#2980b9" stroke="#2980b9" points="142,-36 80,-36 80,-0 142,-0 142,-36"/>
<text text-anchor="middle" x="111" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3001</text>
</g>
<!-- MCP3001&#45;&gt;MCP30xx -->
<g id="edge7" class="edge"><title>MCP3001&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M61.8496,-32.4967C87.5187,-43.7269 124.21,-59.7795 151.929,-71.9066"/>
<polygon fill="black" stroke="black" points="150.762,-75.2164 161.327,-76.018 153.568,-68.8032 150.762,-75.2164"/>
</g>
<!-- MCP3002 -->
<g id="node9" class="node"><title>MCP3002</title>
<polygon fill="#2980b9" stroke="#2980b9" points="142,-36 80,-36 80,-0 142,-0 142,-36"/>
<text text-anchor="middle" x="111" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3002</text>
</g>
<!-- MCP3002&#45;&gt;MCP30xx -->
<g id="edge8" class="edge"><title>MCP3002&#45;&gt;MCP30xx</title>
<g id="edge8" class="edge"><title>MCP3001&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M130.775,-36.3034C140.754,-45.0345 153.011,-55.7595 163.857,-65.2497"/>
<polygon fill="black" stroke="black" points="161.622,-67.9446 171.452,-71.8957 166.231,-62.6766 161.622,-67.9446"/>
</g>
<!-- MCP3002 -->
<g id="node10" class="node"><title>MCP3002</title>
<polygon fill="#2980b9" stroke="#2980b9" points="302,-36 240,-36 240,-0 302,-0 302,-36"/>
<text text-anchor="middle" x="271" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3002</text>
</g>
<!-- MCP3002&#45;&gt;MCP30xx -->
<g id="edge9" class="edge"><title>MCP3002&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M251.225,-36.3034C241.246,-45.0345 228.989,-55.7595 218.143,-65.2497"/>
<polygon fill="black" stroke="black" points="215.769,-62.6766 210.548,-71.8957 220.378,-67.9446 215.769,-62.6766"/>
</g>
<!-- MCP3002&#45;&gt;MCP3xx2 -->
<g id="edge16" class="edge"><title>MCP3002&#45;&gt;MCP3xx2</title>
<path fill="none" stroke="black" d="M290.775,-36.3034C300.754,-45.0345 313.011,-55.7595 323.857,-65.2497"/>
<polygon fill="black" stroke="black" points="321.622,-67.9446 331.452,-71.8957 326.231,-62.6766 321.622,-67.9446"/>
</g>
<!-- MCP3004 -->
<g id="node10" class="node"><title>MCP3004</title>
<g id="node11" class="node"><title>MCP3004</title>
<polygon fill="#2980b9" stroke="#2980b9" points="222,-36 160,-36 160,-0 222,-0 222,-36"/>
<text text-anchor="middle" x="191" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3004</text>
</g>
<!-- MCP3004&#45;&gt;MCP30xx -->
<g id="edge9" class="edge"><title>MCP3004&#45;&gt;MCP30xx</title>
<g id="edge10" class="edge"><title>MCP3004&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M191,-36.3034C191,-44.0173 191,-53.2875 191,-61.8876"/>
<polygon fill="black" stroke="black" points="187.5,-61.8956 191,-71.8957 194.5,-61.8957 187.5,-61.8956"/>
</g>
<!-- MCP3008 -->
<g id="node11" class="node"><title>MCP3008</title>
<polygon fill="#2980b9" stroke="#2980b9" points="302,-36 240,-36 240,-0 302,-0 302,-36"/>
<text text-anchor="middle" x="271" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
<g id="node12" class="node"><title>MCP3008</title>
<polygon fill="#2980b9" stroke="#2980b9" points="62,-36 3.55271e-15,-36 3.55271e-15,-0 62,-0 62,-36"/>
<text text-anchor="middle" x="31" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3008</text>
</g>
<!-- MCP3008&#45;&gt;MCP30xx -->
<g id="edge10" class="edge"><title>MCP3008&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M251.225,-36.3034C241.246,-45.0345 228.989,-55.7595 218.143,-65.2497"/>
<polygon fill="black" stroke="black" points="215.769,-62.6766 210.548,-71.8957 220.378,-67.9446 215.769,-62.6766"/>
<g id="edge11" class="edge"><title>MCP3008&#45;&gt;MCP30xx</title>
<path fill="none" stroke="black" d="M62.2294,-32.6629C87.7738,-43.8385 124.046,-59.7076 151.587,-71.757"/>
<polygon fill="black" stroke="black" points="150.369,-75.044 160.933,-75.8457 153.174,-68.6309 150.369,-75.044"/>
</g>
<!-- MCP3201 -->
<g id="node12" class="node"><title>MCP3201</title>
<polygon fill="#2980b9" stroke="#2980b9" points="382,-36 320,-36 320,-0 382,-0 382,-36"/>
<text text-anchor="middle" x="351" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3201</text>
<g id="node13" class="node"><title>MCP3201</title>
<polygon fill="#2980b9" stroke="#2980b9" points="542,-36 480,-36 480,-0 542,-0 542,-36"/>
<text text-anchor="middle" x="511" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3201</text>
</g>
<!-- MCP3201&#45;&gt;MCP32xx -->
<g id="edge11" class="edge"><title>MCP3201&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M380.355,-36.1239C396.045,-45.2764 415.557,-56.6583 432.454,-66.515"/>
<polygon fill="black" stroke="black" points="431.13,-69.7947 441.532,-71.8102 434.658,-63.7483 431.13,-69.7947"/>
<g id="edge12" class="edge"><title>MCP3201&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M491.225,-36.3034C481.246,-45.0345 468.989,-55.7595 458.143,-65.2497"/>
<polygon fill="black" stroke="black" points="455.769,-62.6766 450.548,-71.8957 460.378,-67.9446 455.769,-62.6766"/>
</g>
<!-- MCP3202 -->
<g id="node13" class="node"><title>MCP3202</title>
<polygon fill="#2980b9" stroke="#2980b9" points="462,-36 400,-36 400,-0 462,-0 462,-36"/>
<text text-anchor="middle" x="431" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3202</text>
<g id="node14" class="node"><title>MCP3202</title>
<polygon fill="#2980b9" stroke="#2980b9" points="382,-36 320,-36 320,-0 382,-0 382,-36"/>
<text text-anchor="middle" x="351" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3202</text>
</g>
<!-- MCP3202&#45;&gt;MCP32xx -->
<g id="edge12" class="edge"><title>MCP3202&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M440.888,-36.3034C445.489,-44.3564 451.06,-54.1055 456.153,-63.0176"/>
<polygon fill="black" stroke="black" points="453.226,-64.9497 461.226,-71.8957 459.304,-61.4767 453.226,-64.9497"/>
<g id="edge13" class="edge"><title>MCP3202&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M370.775,-36.3034C380.754,-45.0345 393.011,-55.7595 403.857,-65.2497"/>
<polygon fill="black" stroke="black" points="401.622,-67.9446 411.452,-71.8957 406.231,-62.6766 401.622,-67.9446"/>
</g>
<!-- MCP3202&#45;&gt;MCP3xx2 -->
<g id="edge17" class="edge"><title>MCP3202&#45;&gt;MCP3xx2</title>
<path fill="none" stroke="black" d="M351,-36.3034C351,-44.0173 351,-53.2875 351,-61.8876"/>
<polygon fill="black" stroke="black" points="347.5,-61.8956 351,-71.8957 354.5,-61.8957 347.5,-61.8956"/>
</g>
<!-- MCP3204 -->
<g id="node14" class="node"><title>MCP3204</title>
<polygon fill="#2980b9" stroke="#2980b9" points="542,-36 480,-36 480,-0 542,-0 542,-36"/>
<text text-anchor="middle" x="511" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
<g id="node15" class="node"><title>MCP3204</title>
<polygon fill="#2980b9" stroke="#2980b9" points="622,-36 560,-36 560,-0 622,-0 622,-36"/>
<text text-anchor="middle" x="591" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3204</text>
</g>
<!-- MCP3204&#45;&gt;MCP32xx -->
<g id="edge13" class="edge"><title>MCP3204&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M501.112,-36.3034C496.511,-44.3564 490.94,-54.1055 485.847,-63.0176"/>
<polygon fill="black" stroke="black" points="482.696,-61.4767 480.774,-71.8957 488.774,-64.9497 482.696,-61.4767"/>
<g id="edge14" class="edge"><title>MCP3204&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M559.771,-32.6629C534.226,-43.8385 497.954,-59.7076 470.413,-71.757"/>
<polygon fill="black" stroke="black" points="468.826,-68.6309 461.067,-75.8457 471.631,-75.044 468.826,-68.6309"/>
</g>
<!-- MCP3208 -->
<g id="node15" class="node"><title>MCP3208</title>
<polygon fill="#2980b9" stroke="#2980b9" points="622,-36 560,-36 560,-0 622,-0 622,-36"/>
<text text-anchor="middle" x="591" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
<g id="node16" class="node"><title>MCP3208</title>
<polygon fill="#2980b9" stroke="#2980b9" points="462,-36 400,-36 400,-0 462,-0 462,-36"/>
<text text-anchor="middle" x="431" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3208</text>
</g>
<!-- MCP3208&#45;&gt;MCP32xx -->
<g id="edge14" class="edge"><title>MCP3208&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M561.645,-36.1239C545.955,-45.2764 526.443,-56.6583 509.546,-66.515"/>
<polygon fill="black" stroke="black" points="507.342,-63.7483 500.468,-71.8102 510.87,-69.7947 507.342,-63.7483"/>
<g id="edge15" class="edge"><title>MCP3208&#45;&gt;MCP32xx</title>
<path fill="none" stroke="black" d="M431,-36.3034C431,-44.0173 431,-53.2875 431,-61.8876"/>
<polygon fill="black" stroke="black" points="427.5,-61.8956 431,-71.8957 434.5,-61.8957 427.5,-61.8956"/>
</g>
<!-- MCP3301 -->
<g id="node16" class="node"><title>MCP3301</title>
<polygon fill="#2980b9" stroke="#2980b9" points="702,-36 640,-36 640,-0 702,-0 702,-36"/>
<text text-anchor="middle" x="671" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text>
<g id="node17" class="node"><title>MCP3301</title>
<polygon fill="#2980b9" stroke="#2980b9" points="782,-36 720,-36 720,-0 782,-0 782,-36"/>
<text text-anchor="middle" x="751" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3301</text>
</g>
<!-- MCP3301&#45;&gt;MCP33xx -->
<g id="edge15" class="edge"><title>MCP3301&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M680.888,-36.3034C685.489,-44.3564 691.06,-54.1055 696.153,-63.0176"/>
<polygon fill="black" stroke="black" points="693.226,-64.9497 701.226,-71.8957 699.304,-61.4767 693.226,-64.9497"/>
</g>
<!-- MCP3302 -->
<g id="node17" class="node"><title>MCP3302</title>
<polygon fill="#2980b9" stroke="#2980b9" points="782,-36 720,-36 720,-0 782,-0 782,-36"/>
<text text-anchor="middle" x="751" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text>
</g>
<!-- MCP3302&#45;&gt;MCP33xx -->
<g id="edge16" class="edge"><title>MCP3302&#45;&gt;MCP33xx</title>
<g id="edge18" class="edge"><title>MCP3301&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M741.112,-36.3034C736.511,-44.3564 730.94,-54.1055 725.847,-63.0176"/>
<polygon fill="black" stroke="black" points="722.696,-61.4767 720.774,-71.8957 728.774,-64.9497 722.696,-61.4767"/>
</g>
<!-- MCP3304 -->
<g id="node18" class="node"><title>MCP3304</title>
<!-- MCP3302 -->
<g id="node18" class="node"><title>MCP3302</title>
<polygon fill="#2980b9" stroke="#2980b9" points="862,-36 800,-36 800,-0 862,-0 862,-36"/>
<text text-anchor="middle" x="831" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text>
<text text-anchor="middle" x="831" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3302</text>
</g>
<!-- MCP3304&#45;&gt;MCP33xx -->
<g id="edge17" class="edge"><title>MCP3304&#45;&gt;MCP33xx</title>
<!-- MCP3302&#45;&gt;MCP33xx -->
<g id="edge19" class="edge"><title>MCP3302&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M801.645,-36.1239C785.955,-45.2764 766.443,-56.6583 749.546,-66.515"/>
<polygon fill="black" stroke="black" points="747.342,-63.7483 740.468,-71.8102 750.87,-69.7947 747.342,-63.7483"/>
</g>
<!-- MCP3304 -->
<g id="node19" class="node"><title>MCP3304</title>
<polygon fill="#2980b9" stroke="#2980b9" points="702,-36 640,-36 640,-0 702,-0 702,-36"/>
<text text-anchor="middle" x="671" y="-15.5" font-family="Sans" font-size="10.00" fill="#ffffff">MCP3304</text>
</g>
<!-- MCP3304&#45;&gt;MCP33xx -->
<g id="edge20" class="edge"><title>MCP3304&#45;&gt;MCP33xx</title>
<path fill="none" stroke="black" d="M680.888,-36.3034C685.489,-44.3564 691.06,-54.1055 696.153,-63.0176"/>
<polygon fill="black" stroke="black" points="693.226,-64.9497 701.226,-71.8957 699.304,-61.4767 693.226,-64.9497"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -49,6 +49,9 @@ class SPIError(GPIOZeroError):
class SPIBadArgs(SPIError, ValueError):
"Error raised when invalid arguments are given while constructing :class:`SPIDevice`"
class SPIBadChannel(SPIError, ValueError):
"Error raised when an invalid channel is given to an :class:`AnalogInputDevice`"
class GPIODeviceError(GPIOZeroError):
"Base class for errors specific to the GPIODevice hierarchy"

View File

@@ -54,7 +54,8 @@ class MockPin(Pin):
self._when_changed = None
self.clear_states()
return self
if old_pin.__class__ != cls:
# Ensure the pin class expected supports PWM (or not)
if issubclass(cls, MockPWMPin) != isinstance(old_pin, MockPWMPin):
raise ValueError('pin %d is already in use as a %s' % (number, old_pin.__class__.__name__))
return old_pin
@@ -249,7 +250,6 @@ class MockPWMPin(MockPin):
"""
This derivative of :class:`MockPin` adds PWM support.
"""
def __init__(self, number):
super(MockPWMPin, self).__init__()
self._frequency = None
@@ -275,3 +275,141 @@ class MockPWMPin(MockPin):
if value is None:
self._change_state(0.0)
class MockSPIClockPin(MockPin):
"""
This derivative of :class:`MockPin` is intended to be used as the clock pin
of a mock SPI device. It is not intended for direct construction in tests;
rather, construct a :class:`MockSPIDevice` with various pin numbers, and
this class will be used for the clock pin.
"""
def __init__(self, number):
super(MockSPIClockPin, self).__init__()
if not hasattr(self, 'spi_devices'):
self.spi_devices = []
def _set_state(self, value):
super(MockSPIClockPin, self)._set_state(value)
for dev in self.spi_devices:
dev.on_clock()
class MockSPISelectPin(MockPin):
"""
This derivative of :class:`MockPin` is intended to be used as the select
pin of a mock SPI device. It is not intended for direct construction in
tests; rather, construct a :class:`MockSPIDevice` with various pin numbers,
and this class will be used for the select pin.
"""
def __init__(self, number):
super(MockSPISelectPin, self).__init__()
if not hasattr(self, 'spi_device'):
self.spi_device = None
def _set_state(self, value):
super(MockSPISelectPin, self)._set_state(value)
if self.spi_device:
self.spi_device.on_select()
class MockSPIDevice(object):
def __init__(
self, clock_pin, mosi_pin, miso_pin, select_pin=None,
clock_polarity=False, clock_phase=False, lsb_first=False,
bits_per_word=8, select_high=False):
self.clock_pin = MockSPIClockPin(clock_pin)
self.mosi_pin = None if mosi_pin is None else MockPin(mosi_pin)
self.miso_pin = None if miso_pin is None else MockPin(miso_pin)
self.select_pin = None if select_pin is None else MockSPISelectPin(select_pin)
self.clock_polarity = clock_polarity
self.clock_phase = clock_phase
self.lsb_first = lsb_first
self.bits_per_word = bits_per_word
self.select_high = select_high
self.rx_bit = 0
self.rx_buf = []
self.tx_buf = []
self.clock_pin.spi_devices.append(self)
self.select_pin.spi_device = self
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.close()
def close(self):
if self in self.clock_pin.spi_devices:
self.clock_pin.spi_devices.remove(self)
if self.select_pin is not None:
self.select_pin.spi_device = None
def on_select(self):
if self.select_pin.state == self.select_high:
self.on_start()
def on_clock(self):
# Don't do anything if this SPI device isn't currently selected
if self.select_pin is None or self.select_pin.state == self.select_high:
# The XOR of the clock pin's values, polarity and phase indicates
# whether we're meant to be acting on this edge
if self.clock_pin.state ^ self.clock_polarity ^ self.clock_phase:
self.rx_bit += 1
if self.mosi_pin is not None:
self.rx_buf.append(self.mosi_pin.state)
if self.miso_pin is not None:
try:
tx_value = self.tx_buf.pop(0)
except IndexError:
tx_value = 0
if tx_value:
self.miso_pin.drive_high()
else:
self.miso_pin.drive_low()
self.on_bit()
def on_start(self):
"""
Override this in descendents to detect when the mock SPI device's
select line is activated.
"""
self.rx_bit = 0
self.rx_buf = []
self.tx_buf = []
def on_bit(self):
"""
Override this in descendents to react to receiving a bit.
The :attr:`rx_bit` attribute gives the index of the bit received (this
is reset to 0 by default by :meth:`on_select`). The :attr:`rx_buf`
sequence gives the sequence of 1s and 0s that have been recevied so
far. The :attr:`tx_buf` sequence gives the sequence of 1s and 0s to
transmit on the next clock pulses. All these attributes can be modified
within this method.
The :meth:`rx_word` and :meth:`tx_word` methods can also be used to
read and append to the buffers using integers instead of bool bits.
"""
pass
def rx_word(self):
result = 0
bits = reversed(self.rx_buf) if self.lsb_first else self.rx_buf
for bit in bits:
result <<= 1
result |= bit
return result
def tx_word(self, value, bits_per_word=None):
if bits_per_word is None:
bits_per_word = self.bits_per_word
bits = [0] * bits_per_word
for bit in range(bits_per_word):
bits[bit] = value & 1
value >>= 1
assert not value
if not self.lsb_first:
bits = reversed(bits)
self.tx_buf.extend(bits)

View File

@@ -85,16 +85,16 @@ class SPIHardwareInterface(Device):
self._device.mode = value
def _get_clock_polarity(self):
return bool(self.mode & 2)
return bool(self.clock_mode & 2)
def _set_clock_polarity(self, value):
self.mode = self.mode & (~2) | (bool(value) << 1)
self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1)
def _get_clock_phase(self):
return bool(self.mode & 1)
return bool(self.clock_mode & 1)
def _set_clock_phase(self, value):
self.mode = self.mode & (~1) | bool(value)
self.clock_mode = self.clock_mode & (~1) | bool(value)
def _get_lsb_first(self):
return self._device.lsbfirst
@@ -130,9 +130,6 @@ class SPISoftwareBus(SharedMixin, Device):
self.miso = None
super(SPISoftwareBus, self).__init__()
self.lock = RLock()
self.clock_phase = False
self.lsb_first = False
self.bits_per_word = 8
try:
self.clock = OutputDevice(clock_pin, active_high=True)
if mosi_pin is not None:
@@ -166,13 +163,7 @@ class SPISoftwareBus(SharedMixin, Device):
def _shared_key(cls, clock_pin, mosi_pin, miso_pin):
return (clock_pin, mosi_pin, miso_pin)
def read(self, n):
return self.transfer((0,) * n)
def write(self, data):
return len(self.transfer(data))
def transfer(self, data):
def transfer(self, data, clock_phase=False, lsb_first=False, bits_per_word=8):
"""
Writes data (a list of integer words where each word is assumed to have
:attr:`bits_per_word` bits or less) to the SPI interface, and reads an
@@ -180,19 +171,19 @@ class SPISoftwareBus(SharedMixin, Device):
"""
result = []
with self.lock:
shift = operator.lshift if self.lsb_first else operator.rshift
shift = operator.lshift if lsb_first else operator.rshift
for write_word in data:
mask = 1 if self.lsb_first else 1 << (self.bits_per_word - 1)
mask = 1 if lsb_first else 1 << (bits_per_word - 1)
read_word = 0
for _ in range(self.bits_per_word):
for _ in range(bits_per_word):
if self.mosi is not None:
self.mosi.value = bool(write_word & mask)
self.clock.on()
if self.miso is not None and not self.clock_phase:
if self.miso is not None and not clock_phase:
if self.miso.value:
read_word |= mask
self.clock.off()
if self.miso is not None and self.clock_phase:
if self.miso is not None and clock_phase:
if self.miso.value:
read_word |= mask
mask = shift(mask, 1)
@@ -205,6 +196,9 @@ class SPISoftwareInterface(OutputDevice):
self._bus = None
super(SPISoftwareInterface, self).__init__(select_pin, active_high=False)
try:
self._clock_phase = False
self._lsb_first = False
self._bits_per_word = 8
self._bus = SPISoftwareBus(clock_pin, mosi_pin, miso_pin)
except:
self.close()
@@ -230,16 +224,17 @@ class SPISoftwareInterface(OutputDevice):
return "software SPI closed"
def read(self, n):
return self._bus.read(n)
return self.transfer((0,) * n)
def write(self, data):
return self._bus.write(data)
return len(self.transfer(data))
def transfer(self, data):
with self._bus.lock:
self.on()
try:
return self._bus.transfer(data)
return self._bus.transfer(
data, self._clock_phase, self._lsb_first, self._bits_per_word)
finally:
self.off()
@@ -250,40 +245,37 @@ class SPISoftwareInterface(OutputDevice):
value = int(value)
if not 0 <= value <= 3:
raise ValueError('clock_mode must be a value between 0 and 3 inclusive')
with self._bus.lock:
self._bus.clock.active_high = not (value & 2)
self._bus.clock.off()
self._bus.clock_phase = bool(value & 1)
self.clock_polarity = bool(value & 2)
self.clock_phase = bool(value & 1)
def _get_clock_polarity(self):
return not self._bus.clock.active_high
with self._bus.lock:
return not self._bus.clock.active_high
def _set_clock_polarity(self, value):
with self._bus.lock:
self._bus.clock.active_high = not value
self._bus.clock.off()
def _get_clock_phase(self):
return self._bus.clock_phase
return self._clock_phase
def _set_clock_phase(self, value):
with self._bus.lock:
self._bus.clock_phase = bool(value)
self._clock_phase = bool(value)
def _get_lsb_first(self):
return self._bus.lsb_first
return self._lsb_first
def _set_lsb_first(self, value):
with self._bus.lock:
self._bus.lsb_first = bool(value)
self._lsb_first = bool(value)
def _get_bits_per_word(self):
return self._bus.bits_per_word
return self._bits_per_word
def _set_bits_per_word(self, value):
if value < 1:
raise ValueError('bits_per_word must be positive')
with self._bus.lock:
self._bus.bits_per_word = int(value)
self._bits_per_word = int(value)
def _get_select_high(self):
return self.active_high

View File

@@ -7,7 +7,14 @@ from __future__ import (
str = type('')
from .exc import DeviceClosed, InputDeviceError
from math import log, ceil
from operator import or_
try:
from functools import reduce
except ImportError:
pass # py2's reduce is built-in
from .exc import DeviceClosed, SPIBadChannel
from .devices import Device
from .spi import SPI
@@ -34,6 +41,38 @@ class SPIDevice(Device):
def closed(self):
return self._spi is None
def _int_to_words(self, pattern):
"""
Given a bit-pattern expressed an integer number, return a sequence of
the individual words that make up the pattern. The number of bits per
word will be obtained from the internal SPI interface.
"""
try:
bits_required = int(ceil(log(pattern, 2))) + 1
except ValueError:
# pattern == 0 (technically speaking, no bits are required to
# transmit the value zero ;)
bits_required = 1
shifts = range(0, bits_required, self._spi.bits_per_word)[::-1]
mask = 2 ** self._spi.bits_per_word - 1
return [(pattern >> shift) & mask for shift in shifts]
def _words_to_int(self, words, expected_bits=None):
"""
Given a sequence of words which each fit in the internal SPI
interface's number of bits per word, returns the value obtained by
concatenating each word into a single bit-string.
If *expected_bits* is specified, it limits the size of the output to
the specified number of bits (by masking off bits above the expected
number). If unspecified, no limit will be applied.
"""
if expected_bits is None:
expected_bits = len(words) * self._spi.bits_per_word
shifts = range(0, expected_bits, self._spi.bits_per_word)[::-1]
mask = 2 ** expected_bits - 1
return reduce(or_, (word << shift for word, shift in zip(words, shifts))) & mask
def __repr__(self):
try:
self._check_open()
@@ -72,10 +111,10 @@ class AnalogInputDevice(SPIDevice):
.. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter
"""
def __init__(self, bits=None, **spi_args):
if bits is None:
raise InputDeviceError('you must specify the bit resolution of the device')
def __init__(self, bits, **spi_args):
self._bits = bits
self._min_value = -(2 ** bits)
self._range = 2 ** (bits + 1) - 1
super(AnalogInputDevice, self).__init__(shared=True, **spi_args)
@property
@@ -92,9 +131,9 @@ class AnalogInputDevice(SPIDevice):
def value(self):
"""
The current value read from the device, scaled to a value between 0 and
1.
1 (or -1 to +1 for certain devices operating in differential mode).
"""
return self._read() / (2**self.bits - 1)
return (2 * (self._read() - self._min_value) / self._range) - 1
@property
def raw_value(self):
@@ -128,10 +167,9 @@ class MCP3xxx(AnalogInputDevice):
@property
def differential(self):
"""
If ``True``, the device is operated in pseudo-differential mode. In
this mode one channel (specified by the channel attribute) is read
relative to the value of a second channel (implied by the chip's
design).
If ``True``, the device is operated in differential mode. In this mode
one channel (specified by the channel attribute) is read relative to
the value of a second channel (implied by the chip's design).
Please refer to the device data-sheet to determine which channel is
used as the relative base value (for example, when using an
@@ -141,28 +179,73 @@ class MCP3xxx(AnalogInputDevice):
return self._differential
def _read(self):
# MCP3008/04 or MCP3208/04 protocol looks like the following:
return self._words_to_int(
self._spi.transfer(self._send())[-2:], self.bits
)
def _send(self):
# MCP3004/08 protocol looks like the following:
#
# Byte 0 1 2
# ==== ======== ======== ========
# Tx 0001MCCC xxxxxxxx xxxxxxxx
# Rx xxxxxxxx x0RRRRRR RRRRxxxx for the 3004/08
# Rx xxxxxxxx x0RRRRRR RRRRRRxx for the 3204/08
# Tx 00000001 MCCCxxxx xxxxxxxx
# Rx xxxxxxxx xxxxx0RR RRRRRRRR
#
# The transmit bits start with 3 preamble bits "000" (to warm up), a
# start bit "1" followed by the single/differential bit (M) which is 1
# for single-ended read, and 0 for differential read, followed by
# 3-bits for the channel (C). The remainder of the transmission are
# "don't care" bits (x).
# MCP3204/08 protocol looks like the following:
#
# The first byte received and the top 1 bit of the second byte are
# don't care bits (x). These are followed by a null bit (0), and then
# the result bits (R). 10 bits for the MCP300x, 12 bits for the
# MCP320x.
# Byte 0 1 2
# ==== ======== ======== ========
# Tx 000001MC CCxxxxxx xxxxxxxx
# Rx xxxxxxxx xxx0RRRR RRRRRRRR
#
# XXX Differential mode still requires testing
data = self._spi.transfer([16 + [8, 0][self.differential] + self.channel, 0, 0])
return ((data[1] & 63) << (self.bits - 6)) | (data[2] >> (14 - self.bits))
# The transmit bits start with several preamble "0" bits, the number
# of which is determined by the amount required to align the last byte
# of the result with the final byte of output. A start "1" bit is then
# transmitted, followed by the single/differential bit (M); 1 for
# single-ended read, 0 for differential read. Next comes three bits for
# channel (C).
#
# Read-out begins with a don't care bit (x), then a null bit (0)
# followed by the result bits (R). All other bits are don't care (x).
#
# The 3x01 variant of the chips always operates in differential mode
# and effectively only has one channel (composed of an IN+ and IN-). As
# such it requires no input, just output.
return self._int_to_words(
(0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 2)
)
class MCP3xx2(MCP3xxx):
def _send(self):
# MCP3002 protocol looks like the following:
#
# Byte 0 1
# ==== ======== ========
# Tx 01MCLxxx xxxxxxxx
# Rx xxxxx0RR RRRRRRRR for the 3002
#
# MCP3202 protocol looks like the following:
#
# Byte 0 1 2
# ==== ======== ======== ========
# Tx 00000001 MCLxxxxx xxxxxxxx
# Rx xxxxxxxx xxx0RRRR RRRRRRRR
#
# The transmit bits start with several preamble "0" bits, the number of
# which is determined by the amount required to align the last byte of
# the result with the final byte of output. A start "1" bit is then
# transmitted, followed by the single/differential bit (M); 1 for
# single-ended read, 0 for differential read. Next comes a single bit
# for channel (M) then the MSBF bit (L) which selects whether the data
# will be read out in MSB form only (1) or whether LSB read-out will
# occur after MSB read-out (0).
#
# Read-out begins with a null bit (0) followed by the result bits (R).
# All other bits are don't care (x).
return self._int_to_words(
(0b1001 | (not self.differential) << 2 | self.channel << 1) << (self.bits + 1)
)
class MCP30xx(MCP3xxx):
@@ -196,20 +279,32 @@ class MCP33xx(MCP3xxx):
super(MCP33xx, self).__init__(channel, 12, differential, **spi_args)
def _read(self):
# MCP3304/02 protocol looks like the following:
if self.differential:
result = self._words_to_int(
self._spi.transfer(self._send())[-2:], self.bits + 1)
# Account for the sign bit
if result > 4095:
return -(8192 - result)
else:
return result
else:
return super(MCP33xx, self)._read()
def _send(self):
# MCP3302/04 protocol looks like the following:
#
# Byte 0 1 2
# ==== ======== ======== ========
# Tx 0001MCCC xxxxxxxx xxxxxxxx
# Rx xxxxxxxx x0SRRRRR RRRRRRRx
# Tx 00001MCC Cxxxxxxx xxxxxxxx
# Rx xxxxxxxx xx0SRRRR RRRRRRRR
#
# The transmit bits start with 3 preamble bits "000" (to warm up), a
# start bit "1" followed by the single/differential bit (M) which is 1
# for single-ended read, and 0 for differential read, followed by
# 3-bits for the channel (C). The remainder of the transmission are
# "don't care" bits (x).
# The transmit bits start with 4 preamble bits "0000", a start bit "1"
# followed by the single/differential bit (M) which is 1 for
# single-ended read, and 0 for differential read, followed by 3-bits
# for the channel (C). The remainder of the transmission are "don't
# care" bits (x).
#
# The first byte received and the top 1 bit of the second byte are
# The first byte received and the top 2 bits of the second byte are
# don't care bits (x). These are followed by a null bit (0), then the
# sign bit (S), and then the 12 result bits (R).
#
@@ -217,22 +312,11 @@ class MCP33xx(MCP3xxx):
# result is effectively 12-bits. In differential mode, the sign bit is
# significant and the result is a two's-complement 13-bit value.
#
# The MCP3301 variant of the chip always operates in differential
# mode and effectively only has one channel (composed of an IN+ and
# IN-). As such it requires no input, just output. This is the reason
# we split out _send() below; so that MCP3301 can override it.
data = self._spi.transfer(self._send())
# Extract the last two bytes (again, for MCP3301)
data = data[-2:]
result = ((data[0] & 63) << 7) | (data[1] >> 1)
# Account for the sign bit
if self.differential and result > 4095:
result = -(8192 - result)
assert -4096 <= result < 4096
return result
def _send(self):
return [16 + [8, 0][self.differential] + self.channel, 0, 0]
# The MCP3301 variant operates similarly to the other MCP3x01 variants;
# no input, just output and always differential.
return self._int_to_words(
(0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 3)
)
@property
def differential(self):
@@ -259,15 +343,25 @@ class MCP33xx(MCP3xxx):
class MCP3001(MCP30xx):
"""
The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel
The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel.
Please note that the MCP3001 always operates in differential mode,
measuring the value of IN+ relative to IN-.
.. _MCP3001: http://www.farnell.com/datasheets/630400.pdf
"""
def __init__(self, **spi_args):
super(MCP3001, self).__init__(0, differential=True, **spi_args)
def _read(self):
# MCP3001 protocol looks like the following:
#
# Byte 0 1
# ==== ======== ========
# Rx xx0RRRRR RRRRRxxx
return self._words_to_int(self._spi.read(2), 13) >> 3
class MCP3002(MCP30xx):
class MCP3002(MCP30xx, MCP3xx2):
"""
The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels
(0-1).
@@ -276,7 +370,7 @@ class MCP3002(MCP30xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 2:
raise InputDeviceError('channel must be 0 or 1')
raise SPIBadChannel('channel must be 0 or 1')
super(MCP3002, self).__init__(channel, differential, **spi_args)
@@ -289,7 +383,7 @@ class MCP3004(MCP30xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 4:
raise InputDeviceError('channel must be between 0 and 3')
raise SPIBadChannel('channel must be between 0 and 3')
super(MCP3004, self).__init__(channel, differential, **spi_args)
@@ -302,21 +396,31 @@ class MCP3008(MCP30xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 8:
raise InputDeviceError('channel must be between 0 and 7')
raise SPIBadChannel('channel must be between 0 and 7')
super(MCP3008, self).__init__(channel, differential, **spi_args)
class MCP3201(MCP32xx):
"""
The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel
The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel.
Please note that the MCP3201 always operates in differential mode,
measuring the value of IN+ relative to IN-.
.. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf
"""
def __init__(self, **spi_args):
super(MCP3201, self).__init__(0, differential=True, **spi_args)
def _read(self):
# MCP3201 protocol looks like the following:
#
# Byte 0 1
# ==== ======== ========
# Rx xx0RRRRR RRRRRRRx
return self._words_to_int(self._spi.read(2), 13) >> 1
class MCP3202(MCP32xx):
class MCP3202(MCP32xx, MCP3xx2):
"""
The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels
(0-1).
@@ -325,7 +429,7 @@ class MCP3202(MCP32xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 2:
raise InputDeviceError('channel must be 0 or 1')
raise SPIBadChannel('channel must be 0 or 1')
super(MCP3202, self).__init__(channel, differential, **spi_args)
@@ -338,7 +442,7 @@ class MCP3204(MCP32xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 4:
raise InputDeviceError('channel must be between 0 and 3')
raise SPIBadChannel('channel must be between 0 and 3')
super(MCP3204, self).__init__(channel, differential, **spi_args)
@@ -351,23 +455,33 @@ class MCP3208(MCP32xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 8:
raise InputDeviceError('channel must be between 0 and 7')
raise SPIBadChannel('channel must be between 0 and 7')
super(MCP3208, self).__init__(channel, differential, **spi_args)
class MCP3301(MCP33xx):
"""
The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note
that the MCP3301 always operates in differential mode between its two
channels and the output value is scaled from -1 to +1.
that the MCP3301 always operates in differential mode measuring the
difference between IN+ and IN-. Its output value is scaled from -1 to +1.
.. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf
"""
def __init__(self, **spi_args):
super(MCP3301, self).__init__(0, differential=True, **spi_args)
def _send(self):
return [0, 0]
def _read(self):
# MCP3301 protocol looks like the following:
#
# Byte 0 1
# ==== ======== ========
# Rx xx0SRRRR RRRRRRRR
result = self._words_to_int(self._spi.read(2), 13)
# Account for the sign bit
if result > 4095:
return -(8192 - result)
else:
return result
class MCP3302(MCP33xx):
@@ -382,7 +496,7 @@ class MCP3302(MCP33xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 4:
raise InputDeviceError('channel must be between 0 and 4')
raise SPIBadChannel('channel must be between 0 and 4')
super(MCP3302, self).__init__(channel, differential, **spi_args)
@@ -398,6 +512,6 @@ class MCP3304(MCP33xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 8:
raise InputDeviceError('channel must be between 0 and 7')
raise SPIBadChannel('channel must be between 0 and 7')
super(MCP3304, self).__init__(channel, differential, **spi_args)

170
tests/test_spi.py Normal file
View File

@@ -0,0 +1,170 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import sys
import mock
import pytest
from collections import namedtuple
from gpiozero import *
from gpiozero.pins.mock import MockPin, MockSPIDevice
from gpiozero.spi import *
def setup_function(function):
import gpiozero.devices
gpiozero.devices.pin_factory = MockPin
def teardown_function(function):
MockPin.clear_pins()
def test_spi_hardware_params():
with mock.patch('gpiozero.spi.SpiDev') as spidev:
with SPI() as device:
assert isinstance(device, SPIHardwareInterface)
with SPI(port=0, device=0) as device:
assert isinstance(device, SPIHardwareInterface)
with SPI(port=0, device=1) as device:
assert isinstance(device, SPIHardwareInterface)
with SPI(clock_pin=11) as device:
assert isinstance(device, SPIHardwareInterface)
with SPI(clock_pin=11, mosi_pin=10, select_pin=8) as device:
assert isinstance(device, SPIHardwareInterface)
with SPI(clock_pin=11, mosi_pin=10, select_pin=7) as device:
assert isinstance(device, SPIHardwareInterface)
with SPI(shared=True) as device:
assert isinstance(device, SharedSPIHardwareInterface)
with pytest.raises(ValueError):
SPI(port=1)
with pytest.raises(ValueError):
SPI(device=2)
with pytest.raises(ValueError):
SPI(port=0, clock_pin=12)
with pytest.raises(ValueError):
SPI(foo='bar')
def test_spi_software_params():
with mock.patch('gpiozero.spi.SpiDev') as spidev:
with SPI(select_pin=6) as device:
assert isinstance(device, SPISoftwareInterface)
with SPI(clock_pin=11, mosi_pin=9, miso_pin=10) as device:
assert isinstance(device, SPISoftwareInterface)
with SPI(select_pin=6, shared=True) as device:
assert isinstance(device, SharedSPISoftwareInterface)
# Ensure software fallback works when SpiDev isn't present
with SPI() as device:
assert isinstance(device, SPISoftwareInterface)
def test_spi_hardware_conflict():
with mock.patch('gpiozero.spi.SpiDev') as spidev:
with LED(11) as led:
with pytest.raises(GPIOPinInUse):
SPI(port=0, device=0)
def test_spi_hardware_read():
with mock.patch('gpiozero.spi.SpiDev') as spidev:
spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)]
with SPI() as device:
assert device.read(3) == [0, 1, 2]
assert device.read(6) == list(range(6))
def test_spi_hardware_write():
with mock.patch('gpiozero.spi.SpiDev') as spidev:
spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)]
with SPI() as device:
assert device.write([0, 1, 2]) == 3
assert spidev.return_value.xfer2.called_with([0, 1, 2])
assert device.write(list(range(6))) == 6
assert spidev.return_value.xfer2.called_with(list(range(6)))
def test_spi_hardware_modes():
with mock.patch('gpiozero.spi.SpiDev') as spidev:
spidev.return_value.mode = 0
spidev.return_value.lsbfirst = False
spidev.return_value.cshigh = True
spidev.return_value.bits_per_word = 8
with SPI() as device:
assert device.clock_mode == 0
assert not device.clock_polarity
assert not device.clock_phase
device.clock_polarity = False
assert device.clock_mode == 0
device.clock_polarity = True
assert device.clock_mode == 2
device.clock_phase = True
assert device.clock_mode == 3
assert not device.lsb_first
assert device.select_high
assert device.bits_per_word == 8
device.select_high = False
device.lsb_first = True
device.bits_per_word = 12
assert not spidev.return_value.cshigh
assert spidev.return_value.lsbfirst
assert spidev.return_value.bits_per_word == 12
def test_spi_software_read():
class SPISlave(MockSPIDevice):
def on_start(self):
super(SPISlave, self).on_start()
for i in range(10):
self.tx_word(i)
with SPISlave(11, 10, 9, 8) as slave, SPI() as master:
assert master.read(3) == [0, 1, 2]
assert master.read(6) == [0, 1, 2, 3, 4, 5]
slave.clock_phase = True
master.clock_phase = True
assert master.read(3) == [0, 1, 2]
assert master.read(6) == [0, 1, 2, 3, 4, 5]
def test_spi_software_write():
with MockSPIDevice(11, 10, 9, 8) as test_device, SPI() as master:
master.write([0])
assert test_device.rx_word() == 0
master.write([2, 0])
assert test_device.rx_word() == 512
master.write([0, 1, 1])
assert test_device.rx_word() == 257
def test_spi_software_clock_mode():
with SPI() as master:
assert master.clock_mode == 0
assert not master.clock_polarity
assert not master.clock_phase
master.clock_polarity = False
assert master.clock_mode == 0
master.clock_polarity = True
assert master.clock_mode == 2
master.clock_phase = True
assert master.clock_mode == 3
master.clock_mode = 0
assert not master.clock_polarity
assert not master.clock_phase
with pytest.raises(ValueError):
master.clock_mode = 5
def test_spi_software_attr():
with SPI() as master:
assert not master.lsb_first
assert not master.select_high
assert master.bits_per_word == 8
master.bits_per_word = 12
assert master.bits_per_word == 12
master.lsb_first = True
assert master.lsb_first
master.select_high = True
assert master.select_high
with pytest.raises(ValueError):
master.bits_per_word = 0
# XXX Test two simultaneous SPI devices sharing clock, MOSI, and MISO, with
# separate select pins (including threaded tests which attempt simultaneous
# reading/writing)

339
tests/test_spi_devices.py Normal file
View File

@@ -0,0 +1,339 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import sys
import pytest
from collections import namedtuple
try:
from math import isclose
except ImportError:
from gpiozero.compat import isclose
from gpiozero import *
from gpiozero.pins.mock import MockSPIDevice, MockPin
def setup_function(function):
import gpiozero.devices
gpiozero.devices.pin_factory = MockPin
def teardown_function(function):
MockPin.clear_pins()
def clamp(v, min_value, max_value):
return min(max_value, max(min_value, v))
def scale(v, ref, bits):
v /= ref
vmin = -(2 ** bits)
vmax = -vmin - 1
vrange = vmax - vmin
return int(((v + 1) / 2.0) * vrange + vmin)
class MockMCP3xxx(MockSPIDevice):
def __init__(
self, clock_pin, mosi_pin, miso_pin, select_pin=None,
channels=8, bits=10):
super(MockMCP3xxx, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin)
self.vref = 3.3
self.channels = [0.0] * channels
self.channel_bits = 3
self.bits = bits
self.state = 'idle'
def on_start(self):
super(MockMCP3xxx, self).on_start()
self.state = 'idle'
def on_bit(self):
if self.state == 'idle':
if self.rx_buf[-1]:
self.state = 'mode'
self.rx_buf = []
elif self.state == 'mode':
if self.rx_buf[-1]:
self.state = 'single'
else:
self.state = 'diff'
self.rx_buf = []
elif self.state in ('single', 'diff'):
if len(self.rx_buf) == self.channel_bits:
self.on_result(self.state == 'diff', self.rx_word())
self.state = 'result'
elif self.state == 'result':
if not self.tx_buf:
self.state = 'idle'
self.rx_buf = []
else:
assert False
def on_result(self, differential, channel):
if differential:
pos_channel = channel
neg_channel = pos_channel ^ 1
result = self.channels[pos_channel] - self.channels[neg_channel]
result = clamp(result, 0, self.vref)
else:
result = clamp(self.channels[channel], 0, self.vref)
result = scale(result, self.vref, self.bits)
self.tx_word(result, self.bits + 2)
class MockMCP3xx1(MockMCP3xxx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, bits=10):
super(MockMCP3xx1, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits)
def on_start(self):
super(MockMCP3xx1, self).on_start()
result = self.channels[0] - self.channels[1]
result = clamp(result, 0, self.vref)
result = scale(result, self.vref, self.bits)
self.tx_word(result, self.bits + 3)
def on_bit(self):
pass
class MockMCP3xx2(MockMCP3xxx):
def __init__(
self, clock_pin, mosi_pin, miso_pin, select_pin=None,
bits=10):
super(MockMCP3xx2, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits)
self.channel_bits = 1
class MockMCP33xx(MockMCP3xxx):
def __init__(
self, clock_pin, mosi_pin, miso_pin, select_pin=None,
channels=8):
super(MockMCP33xx, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels, 12)
def on_result(self, differential, channel):
if differential:
pos_channel = channel
neg_channel = pos_channel ^ 1
result = self.channels[pos_channel] - self.channels[neg_channel]
result = clamp(result, -self.vref, self.vref)
else:
result = clamp(self.channels[channel], 0, self.vref)
result = scale(result, self.vref, self.bits)
if result < 0:
result += 8192
self.tx_word(result, self.bits + 3)
class MockMCP3001(MockMCP3xx1):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3001, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, bits=10)
class MockMCP3002(MockMCP3xx2):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3002, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, bits=10)
class MockMCP3004(MockMCP3xxx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3004, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=10)
class MockMCP3008(MockMCP3xxx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3008, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=10)
class MockMCP3201(MockMCP3xx1):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3201, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, bits=12)
class MockMCP3202(MockMCP3xx2):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3202, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, bits=12)
class MockMCP3204(MockMCP3xxx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3204, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=12)
class MockMCP3208(MockMCP3xxx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3208, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=12)
class MockMCP3301(MockMCP3xxx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3301, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=12)
def on_start(self):
super(MockMCP3301, self).on_start()
result = self.channels[0] - self.channels[1]
result = clamp(result, -self.vref, self.vref)
result = scale(result, self.vref, self.bits)
if result < 0:
result += 8192
self.tx_word(result, self.bits + 4)
class MockMCP3302(MockMCP33xx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3302, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=4)
class MockMCP3304(MockMCP33xx):
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
super(MockMCP3304, self).__init__(
clock_pin, mosi_pin, miso_pin, select_pin, channels=8)
def single_mcp_test(mock, pot, channel, bits):
scale = 2**bits
tolerance = 1 / scale
mock.channels[channel] = 0.0
assert pot.raw_value == 0
assert isclose(pot.value, 0.0, abs_tol=tolerance)
mock.channels[channel] = mock.vref / 2
assert pot.raw_value == (scale / 2) - 1
assert isclose(pot.value, 0.5, abs_tol=tolerance)
mock.channels[channel] = mock.vref
assert pot.raw_value == scale - 1
assert isclose(pot.value, 1.0, abs_tol=tolerance)
def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False):
scale = 2**bits
tolerance = 1 / scale
mock.channels[pos_channel] = 0.0
mock.channels[neg_channel] = 0.0
assert pot.raw_value == 0
assert isclose(pot.value, 0.0, abs_tol=tolerance)
mock.channels[pos_channel] = mock.vref / 2
assert pot.raw_value == (scale / 2) - 1
assert isclose(pot.value, 0.5, abs_tol=tolerance)
mock.channels[pos_channel] = mock.vref
assert pot.raw_value == scale - 1
assert isclose(pot.value, 1.0, abs_tol=tolerance)
mock.channels[neg_channel] = mock.vref / 2
assert pot.raw_value == (scale / 2) - 1
assert isclose(pot.value, 0.5, abs_tol=tolerance)
mock.channels[pos_channel] = mock.vref / 2
assert pot.raw_value == 0
assert isclose(pot.value, 0.0, abs_tol=tolerance)
mock.channels[pos_channel] = 0.0
mock.channels[neg_channel] = mock.vref
if full:
assert pot.raw_value == -scale
assert isclose(pot.value, -1.0, abs_tol=tolerance)
else:
assert pot.raw_value == 0
assert isclose(pot.value, 0.0, abs_tol=tolerance)
def test_MCP3001():
mock = MockMCP3001(11, 10, 9, 8)
with MCP3001() as pot:
differential_mcp_test(mock, pot, 0, 1, 10)
def test_MCP3002():
mock = MockMCP3002(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3002(channel=5)
with MCP3002(channel=1) as pot:
single_mcp_test(mock, pot, 1, 10)
with MCP3002(channel=1, differential=True) as pot:
differential_mcp_test(mock, pot, 1, 0, 10)
def test_MCP3004():
mock = MockMCP3004(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3004(channel=5)
with MCP3004(channel=3) as pot:
single_mcp_test(mock, pot, 3, 10)
with MCP3004(channel=3, differential=True) as pot:
differential_mcp_test(mock, pot, 3, 2, 10)
def test_MCP3008():
mock = MockMCP3008(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3008(channel=9)
with MCP3008(channel=0) as pot:
single_mcp_test(mock, pot, 0, 10)
with MCP3008(channel=0, differential=True) as pot:
differential_mcp_test(mock, pot, 0, 1, 10)
def test_MCP3201():
mock = MockMCP3201(11, 10, 9, 8)
with MCP3201() as pot:
differential_mcp_test(mock, pot, 0, 1, 12)
def test_MCP3202():
mock = MockMCP3202(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3202(channel=5)
with MCP3202(channel=1) as pot:
single_mcp_test(mock, pot, 1, 12)
with MCP3202(channel=1, differential=True) as pot:
differential_mcp_test(mock, pot, 1, 0, 12)
def test_MCP3204():
mock = MockMCP3204(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3204(channel=5)
with MCP3204(channel=1) as pot:
single_mcp_test(mock, pot, 1, 12)
with MCP3204(channel=1, differential=True) as pot:
differential_mcp_test(mock, pot, 1, 0, 12)
def test_MCP3208():
mock = MockMCP3208(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3208(channel=9)
with MCP3208(channel=7) as pot:
single_mcp_test(mock, pot, 7, 12)
with MCP3208(channel=7, differential=True) as pot:
differential_mcp_test(mock, pot, 7, 6, 12)
def test_MCP3301():
mock = MockMCP3301(11, 10, 9, 8)
with MCP3301() as pot:
differential_mcp_test(mock, pot, 0, 1, 12, full=True)
def test_MCP3302():
mock = MockMCP3302(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3302(channel=4)
with MCP3302(channel=0) as pot:
single_mcp_test(mock, pot, 0, 12)
with MCP3302(channel=0, differential=True) as pot:
differential_mcp_test(mock, pot, 0, 1, 12, full=True)
def test_MCP3304():
mock = MockMCP3304(11, 10, 9, 8)
with pytest.raises(ValueError):
MCP3304(channel=9)
with MCP3304(channel=5) as pot:
single_mcp_test(mock, pot, 5, 12)
with MCP3304(channel=5, differential=True) as pot:
differential_mcp_test(mock, pot, 5, 4, 12, full=True)