mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-12-08 20:39:01 +00:00
Fix #421
Added SPI tests, simplified the shared SPI software bus implementation, and fixed several protocol errors in our MCP3xxx classes (the x2 and x1 protocols were wrong)
This commit is contained in:
@@ -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 |
@@ -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->Device -->
|
||||
<g id="edge1" class="edge"><title>SPIDevice->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->SPIDevice -->
|
||||
<g id="edge2" class="edge"><title>AnalogInputDevice->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->AnalogInputDevice -->
|
||||
<g id="edge3" class="edge"><title>MCP3xxx->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->MCP3xxx -->
|
||||
<g id="edge4" class="edge"><title>MCP30xx->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->MCP3xxx -->
|
||||
<g id="edge5" class="edge"><title>MCP32xx->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->MCP3xxx -->
|
||||
<g id="edge7" class="edge"><title>MCP3xx2->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->MCP3xxx -->
|
||||
<g id="edge6" class="edge"><title>MCP33xx->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->MCP30xx -->
|
||||
<g id="edge7" class="edge"><title>MCP3001->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->MCP30xx -->
|
||||
<g id="edge8" class="edge"><title>MCP3002->MCP30xx</title>
|
||||
<g id="edge8" class="edge"><title>MCP3001->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->MCP30xx -->
|
||||
<g id="edge9" class="edge"><title>MCP3002->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->MCP3xx2 -->
|
||||
<g id="edge16" class="edge"><title>MCP3002->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->MCP30xx -->
|
||||
<g id="edge9" class="edge"><title>MCP3004->MCP30xx</title>
|
||||
<g id="edge10" class="edge"><title>MCP3004->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->MCP30xx -->
|
||||
<g id="edge10" class="edge"><title>MCP3008->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->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->MCP32xx -->
|
||||
<g id="edge11" class="edge"><title>MCP3201->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->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->MCP32xx -->
|
||||
<g id="edge12" class="edge"><title>MCP3202->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->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->MCP3xx2 -->
|
||||
<g id="edge17" class="edge"><title>MCP3202->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->MCP32xx -->
|
||||
<g id="edge13" class="edge"><title>MCP3204->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->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->MCP32xx -->
|
||||
<g id="edge14" class="edge"><title>MCP3208->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->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->MCP33xx -->
|
||||
<g id="edge15" class="edge"><title>MCP3301->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->MCP33xx -->
|
||||
<g id="edge16" class="edge"><title>MCP3302->MCP33xx</title>
|
||||
<g id="edge18" class="edge"><title>MCP3301->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->MCP33xx -->
|
||||
<g id="edge17" class="edge"><title>MCP3304->MCP33xx</title>
|
||||
<!-- MCP3302->MCP33xx -->
|
||||
<g id="edge19" class="edge"><title>MCP3302->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->MCP33xx -->
|
||||
<g id="edge20" class="edge"><title>MCP3304->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 |
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
170
tests/test_spi.py
Normal 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
339
tests/test_spi_devices.py
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user