Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7edbd2d3d0 |
Binary file not shown.
@@ -550,7 +550,7 @@
|
|||||||
<text x="3.085225" y="81.68279375" size="1.778" layer="51">GND</text>
|
<text x="3.085225" y="81.68279375" size="1.778" layer="51">GND</text>
|
||||||
<text x="2.3" y="53.85" size="1.778" layer="51">GND</text>
|
<text x="2.3" y="53.85" size="1.778" layer="51">GND</text>
|
||||||
<text x="3.336225" y="42.247028125" size="1.778" layer="51">GND</text>
|
<text x="3.336225" y="42.247028125" size="1.778" layer="51">GND</text>
|
||||||
<text x="2.99881875" y="12.58869375" size="1.778" layer="51">GND</text>
|
<text x="2.25" y="11.75" size="1.778" layer="51">GND</text>
|
||||||
<text x="21.75" y="12.15" size="1.778" layer="51" rot="R90">GND</text>
|
<text x="21.75" y="12.15" size="1.778" layer="51" rot="R90">GND</text>
|
||||||
<text x="37.45" y="10.05" size="1.778" layer="51" rot="R90">GND</text>
|
<text x="37.45" y="10.05" size="1.778" layer="51" rot="R90">GND</text>
|
||||||
<text x="60.5" y="9.4" size="1.778" layer="51" rot="R90">GND</text>
|
<text x="60.5" y="9.4" size="1.778" layer="51" rot="R90">GND</text>
|
||||||
@@ -589,11 +589,11 @@
|
|||||||
<text x="248.95" y="49.2" size="1.778" layer="51" rot="R180">GND</text>
|
<text x="248.95" y="49.2" size="1.778" layer="51" rot="R180">GND</text>
|
||||||
<text x="248.85" y="66.55" size="1.778" layer="51" rot="R180">GND</text>
|
<text x="248.85" y="66.55" size="1.778" layer="51" rot="R180">GND</text>
|
||||||
<text x="248.8" y="82.9" size="1.778" layer="51" rot="R180">GND</text>
|
<text x="248.8" y="82.9" size="1.778" layer="51" rot="R180">GND</text>
|
||||||
<text x="253.964015625" y="102.099125" size="1.778" layer="51" rot="R180">GND</text>
|
<text x="256.35" y="101.95" size="1.778" layer="51" rot="R180">GND</text>
|
||||||
<text x="249.054865625" y="112.111771875" size="1.778" layer="51" rot="R180">GND</text>
|
<text x="249.4" y="112.5" size="1.778" layer="51" rot="R180">GND</text>
|
||||||
<text x="237.75" y="280.1" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="237.75" y="280.1" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="199.75" y="273.55" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="199.75" y="273.55" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="188.539503125" y="273.018421875" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="188.45" y="272.75" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="177.95" y="272.75" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="177.95" y="272.75" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="113.4" y="281.65" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="113.4" y="281.65" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="2.992190625" y="248.58331875" size="1.778" layer="51">GND</text>
|
<text x="2.992190625" y="248.58331875" size="1.778" layer="51">GND</text>
|
||||||
@@ -635,13 +635,13 @@
|
|||||||
<wire x1="161.6" y1="158.7" x2="156.95" y2="163.4" width="2.54" layer="29"/>
|
<wire x1="161.6" y1="158.7" x2="156.95" y2="163.4" width="2.54" layer="29"/>
|
||||||
<wire x1="170.1" y1="150.2" x2="165.45" y2="154.9" width="2.54" layer="29"/>
|
<wire x1="170.1" y1="150.2" x2="165.45" y2="154.9" width="2.54" layer="29"/>
|
||||||
<text x="125.137784375" y="269.740521875" size="1.778" layer="51" rot="R90">+5V0_PA_1</text>
|
<text x="125.137784375" y="269.740521875" size="1.778" layer="51" rot="R90">+5V0_PA_1</text>
|
||||||
<text x="182.675396875" y="267.73684375" size="1.778" layer="51" rot="R90">-3V4</text>
|
<text x="185.45" y="267.2" size="1.778" layer="51" rot="R90">-3V4</text>
|
||||||
<text x="193.277878125" y="266.86315625" size="1.778" layer="51" rot="R90">+3V4</text>
|
<text x="196.5" y="267.4" size="1.778" layer="51" rot="R90">+3V4</text>
|
||||||
<text x="207.4" y="267.85" size="1.778" layer="51" rot="R90">-5V0_ADAR12</text>
|
<text x="207.4" y="267.85" size="1.778" layer="51" rot="R90">-5V0_ADAR12</text>
|
||||||
<text x="188.75" y="289.05" size="1.3" layer="51" rot="R45">+3V3_ADAR12</text>
|
<text x="188.75" y="289.05" size="1.3" layer="51" rot="R45">+3V3_ADAR12</text>
|
||||||
<text x="248.25" y="270.6" size="1.778" layer="51" rot="R90">+5V0_PA_2</text>
|
<text x="248.25" y="270.6" size="1.778" layer="51" rot="R90">+5V0_PA_2</text>
|
||||||
<text x="249.695853125" y="96.471690625" size="1.778" layer="51" rot="R180">+3V4</text>
|
<text x="242.8" y="98.7" size="1.778" layer="51" rot="R180">+3V4</text>
|
||||||
<text x="249.232640625" y="104.692303125" size="1.778" layer="51" rot="R180">-3V4</text>
|
<text x="242.9" y="106.65" size="1.778" layer="51" rot="R180">-3V4</text>
|
||||||
<text x="181.4" y="99.15" size="1.778" layer="51" rot="R270">-5V0_ADAR34</text>
|
<text x="181.4" y="99.15" size="1.778" layer="51" rot="R270">-5V0_ADAR34</text>
|
||||||
<text x="185.3" y="75.15" size="1.778" layer="51" rot="R270">+3V3_ADAR34</text>
|
<text x="185.3" y="75.15" size="1.778" layer="51" rot="R270">+3V3_ADAR34</text>
|
||||||
<text x="238.95" y="72.8" size="1.778" layer="51">+3V3_VDD_SW</text>
|
<text x="238.95" y="72.8" size="1.778" layer="51">+3V3_VDD_SW</text>
|
||||||
@@ -714,8 +714,8 @@
|
|||||||
<text x="147.05" y="25.3" size="1.778" layer="51" rot="R180">CHAN14</text>
|
<text x="147.05" y="25.3" size="1.778" layer="51" rot="R180">CHAN14</text>
|
||||||
<text x="157.1" y="25.25" size="1.778" layer="51" rot="R180">CHAN15</text>
|
<text x="157.1" y="25.25" size="1.778" layer="51" rot="R180">CHAN15</text>
|
||||||
<text x="167.15" y="25.35" size="1.778" layer="51" rot="R180">CHAN16</text>
|
<text x="167.15" y="25.35" size="1.778" layer="51" rot="R180">CHAN16</text>
|
||||||
<text x="51.802165625" y="131.052934375" size="1.778" layer="51" rot="R180">SV1</text>
|
<text x="50.15" y="131.25" size="1.778" layer="51" rot="R180">SV1</text>
|
||||||
<text x="35.60243125" y="132.092775" size="1.778" layer="51" rot="R270">VOLTAGE SEQUENCING</text>
|
<text x="43.25" y="128.5" size="1.778" layer="51" rot="R270">VOLTAGE SEQUENCING</text>
|
||||||
<text x="105.55" y="106.9" size="1.2" layer="51" rot="R90">AD9523_EEPROM_SEL</text>
|
<text x="105.55" y="106.9" size="1.2" layer="51" rot="R90">AD9523_EEPROM_SEL</text>
|
||||||
<text x="107.2" y="101.85" size="1.2" layer="51" rot="R45">AD9523_STATUS0</text>
|
<text x="107.2" y="101.85" size="1.2" layer="51" rot="R45">AD9523_STATUS0</text>
|
||||||
<text x="107.25" y="99.35" size="1.2" layer="51" rot="R45">STM32_MOSI4</text>
|
<text x="107.25" y="99.35" size="1.2" layer="51" rot="R45">STM32_MOSI4</text>
|
||||||
@@ -728,19 +728,20 @@
|
|||||||
<text x="99.8" y="100.75" size="1.2" layer="51" rot="R225">STM32_MISO4</text>
|
<text x="99.8" y="100.75" size="1.2" layer="51" rot="R225">STM32_MISO4</text>
|
||||||
<text x="99.8" y="103.4" size="1.2" layer="51" rot="R225">AD9523_STATUS1</text>
|
<text x="99.8" y="103.4" size="1.2" layer="51" rot="R225">AD9523_STATUS1</text>
|
||||||
<text x="99.7" y="105.85" size="1.2" layer="51" rot="R225">GND</text>
|
<text x="99.7" y="105.85" size="1.2" layer="51" rot="R225">GND</text>
|
||||||
<text x="68.73355625" y="72.201796875" size="1.778" layer="51">JP4</text>
|
<text x="68.7" y="82.55" size="1.778" layer="51">JP4</text>
|
||||||
<text x="62.77508125" y="75.956934375" size="1" layer="51" rot="R225">GND</text>
|
<text x="64.25" y="73.85" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="56.95" y="82.75" size="1.778" layer="51">JP9</text>
|
<text x="56.95" y="82.75" size="1.778" layer="51">JP9</text>
|
||||||
<text x="45.798875" y="84.61879375" size="1.778" layer="51" rot="R180">JP2</text>
|
<text x="37.85" y="78.6" size="1.778" layer="51" rot="R90">JP2</text>
|
||||||
<text x="43.09716875" y="85.33433125" size="1.778" layer="51" rot="R90">JP8</text>
|
<text x="43.95" y="88.9" size="1.778" layer="51">JP8</text>
|
||||||
<text x="29.1" y="93.2" size="1.778" layer="51">IMU</text>
|
<text x="29.1" y="93.2" size="1.778" layer="51">JP7</text>
|
||||||
<text x="27.568784375" y="88.61074375" size="1.778" layer="51">JP18</text>
|
<text x="21.75" y="85.35" size="1.778" layer="51">JP18</text>
|
||||||
<text x="89.3" y="75.5" size="1.778" layer="51" rot="R180">JP13</text>
|
<text x="89.3" y="75.5" size="1.778" layer="51" rot="R180">JP13</text>
|
||||||
<text x="75.2" y="77" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="75.2" y="77" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="62.1909375" y="71.621040625" size="1.778" layer="51">JP10</text>
|
<text x="69.6" y="74.1" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="54.996875" y="70.359128125" size="1.2" layer="51">STEPPER</text>
|
<text x="62.9" y="82.75" size="1.778" layer="51">JP10</text>
|
||||||
<text x="43.9" y="78.65" size="1.27" layer="51" rot="R270">GND</text>
|
<text x="53.75" y="64.4" size="1.2" layer="51" rot="R45">STEPPER_CLK+</text>
|
||||||
<text x="52.61158125" y="88.897171875" size="1.016" layer="51" rot="R90">GND</text>
|
<text x="43.9" y="78.65" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
|
<text x="53.95" y="86.4" size="1.778" layer="51">GND</text>
|
||||||
<text x="31.3" y="84.75" size="1.778" layer="51" rot="R270">GND</text>
|
<text x="31.3" y="84.75" size="1.778" layer="51" rot="R270">GND</text>
|
||||||
<text x="40.45" y="95.9" size="1.778" layer="51" rot="R90">GND</text>
|
<text x="40.45" y="95.9" size="1.778" layer="51" rot="R90">GND</text>
|
||||||
<rectangle x1="12.8295" y1="256.5735" x2="15.6235" y2="256.7005" layer="51"/>
|
<rectangle x1="12.8295" y1="256.5735" x2="15.6235" y2="256.7005" layer="51"/>
|
||||||
@@ -5386,56 +5387,6 @@
|
|||||||
<text x="122.221528125" y="146.5440625" size="1.27" layer="51" rot="R315">RX 3_4</text>
|
<text x="122.221528125" y="146.5440625" size="1.27" layer="51" rot="R315">RX 3_4</text>
|
||||||
<text x="145.05015" y="114.518025" size="1.27" layer="51" rot="R45">RX 4_4</text>
|
<text x="145.05015" y="114.518025" size="1.27" layer="51" rot="R45">RX 4_4</text>
|
||||||
<text x="150.25345625" y="4.79933125" size="5.4864" layer="51" font="vector">www.abac-industry.com</text>
|
<text x="150.25345625" y="4.79933125" size="5.4864" layer="51" font="vector">www.abac-industry.com</text>
|
||||||
<text x="47.269546875" y="127.64274375" size="1.27" layer="51" rot="R135">+1V0_FPGA</text>
|
|
||||||
<text x="47.220515625" y="125.152134375" size="1.27" layer="51" rot="R135">+1V8_FPGA</text>
|
|
||||||
<text x="47.270815625" y="122.549565625" size="1.27" layer="51" rot="R135">+3V3_FPGA</text>
|
|
||||||
<text x="47.317503125" y="119.8292125" size="1.27" layer="51" rot="R135">+5V0_ADAR</text>
|
|
||||||
<text x="47.30423125" y="117.319196875" size="1.27" layer="51" rot="R135">+3V3_ADAR12</text>
|
|
||||||
<text x="47.2552" y="114.8285875" size="1.27" layer="51" rot="R135">+3V3_ADAR34</text>
|
|
||||||
<text x="47.3055" y="112.22601875" size="1.27" layer="51" rot="R135">+3V3_ADTR</text>
|
|
||||||
<text x="47.3521875" y="109.505665625" size="1.27" layer="51" rot="R135">+3V3_SW</text>
|
|
||||||
<text x="47.262328125" y="107.0494875" size="1.27" layer="51" rot="R135">+3V3_VDD_SW</text>
|
|
||||||
<text x="47.262328125" y="104.6232625" size="1.27" layer="51" rot="R135">+5V0_PA1</text>
|
|
||||||
<text x="52.848896875" y="114.716634375" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="52.897928125" y="117.20724375" size="1.27" layer="51" rot="R315">+3V3_CLOCK</text>
|
|
||||||
<text x="52.847628125" y="119.8098125" size="1.27" layer="51" rot="R315">+1V8_CLOCK</text>
|
|
||||||
<text x="52.800940625" y="122.530165625" size="1.27" layer="51" rot="R315">+5V5_PA</text>
|
|
||||||
<text x="52.8908" y="124.98634375" size="1.27" layer="51" rot="R315">+5V0_PA3</text>
|
|
||||||
<text x="52.8908" y="127.41256875" size="1.27" layer="51" rot="R315">+5V0_PA2</text>
|
|
||||||
<text x="52.866228125" y="112.238071875" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="52.79689375" y="109.7075125" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="52.7795625" y="107.038290625" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="52.762228125" y="104.50773125" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="37.741834375" y="95.9444" size="1.778" layer="51" rot="R90">+3V3</text>
|
|
||||||
<text x="43.11376875" y="95.9444" size="1.778" layer="51" rot="R90">SCL3</text>
|
|
||||||
<text x="45.64435" y="95.9888" size="1.778" layer="51" rot="R90">SDA3</text>
|
|
||||||
<text x="48.232090625" y="95.98181875" size="1.016" layer="51" rot="R90">MAG_DRDY</text>
|
|
||||||
<text x="50.801084375" y="95.879059375" size="1.016" layer="51" rot="R90">ACC_INT</text>
|
|
||||||
<text x="52.907659375" y="95.95613125" size="1.016" layer="51" rot="R90">GYR_INT</text>
|
|
||||||
<text x="54.502678125" y="92.739546875" size="1.778" layer="51">JP7</text>
|
|
||||||
<text x="30.45236875" y="78.6816375" size="1.778" layer="51" rot="R90">+3V3</text>
|
|
||||||
<text x="35.56853125" y="79.257065625" size="1.778" layer="51" rot="R90">SCL3</text>
|
|
||||||
<text x="38.227" y="78.789975" size="1.778" layer="51" rot="R90">SDA3</text>
|
|
||||||
<text x="39.282209375" y="78.488071875" size="1.27" layer="51" rot="R270">+3V3</text>
|
|
||||||
<text x="41.4419875" y="78.63334375" size="1.27" layer="51" rot="R270">STM32_SWCLK</text>
|
|
||||||
<text x="46.663971875" y="78.473509375" size="1.27" layer="51" rot="R270">STM32_SWDIO</text>
|
|
||||||
<text x="49.16839375" y="78.5267875" size="1.27" layer="51" rot="R270">STM32_NRST</text>
|
|
||||||
<text x="51.7793875" y="78.473509375" size="1.27" layer="51" rot="R270">STM32_SWO</text>
|
|
||||||
<text x="53.6100625" y="82.81805625" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="53.75804375" y="77.6019375" size="1.27" layer="51" rot="R315">GND</text>
|
|
||||||
<text x="53.809425" y="80.29940625" size="1.27" layer="51" rot="R315">CW+</text>
|
|
||||||
<text x="53.520859375" y="75.467190625" size="1.27" layer="51" rot="R315">CLK+</text>
|
|
||||||
<text x="50.081" y="88.941571875" size="1.016" layer="51" rot="R90">RX5</text>
|
|
||||||
<text x="47.417228125" y="88.985971875" size="1.016" layer="51" rot="R90">TX5</text>
|
|
||||||
<text x="45.019834375" y="88.675175" size="1.016" layer="51" rot="R90">+3V3</text>
|
|
||||||
<text x="53.525646875" y="86.07393125" size="1.778" layer="51">GPS</text>
|
|
||||||
<text x="62.34479375" y="80.785540625" size="0.9" layer="51" rot="R45">EN/DIS_RFPA_VDD</text>
|
|
||||||
<text x="68.0472625" y="76.328084375" size="1" layer="51" rot="R225">GND</text>
|
|
||||||
<text x="67.5982" y="80.711553125" size="0.9" layer="51" rot="R45">EN/DIS_COOLING</text>
|
|
||||||
<text x="78.325053125" y="83.140434375" size="1.778" layer="51">ADF4382</text>
|
|
||||||
<text x="92.67903125" y="80.894575" size="1.016" layer="51">1</text>
|
|
||||||
<text x="92.77235625" y="78.2390125" size="1.016" layer="51">2</text>
|
|
||||||
<text x="73.362715625" y="77.945809375" size="1.016" layer="51">14</text>
|
|
||||||
</plain>
|
</plain>
|
||||||
<libraries>
|
<libraries>
|
||||||
<library name="eagle-ltspice">
|
<library name="eagle-ltspice">
|
||||||
@@ -24625,8 +24576,8 @@ Your PCBWay Team
|
|||||||
<vertex x="114" y="112" curve="-180"/>
|
<vertex x="114" y="112" curve="-180"/>
|
||||||
</polygon>
|
</polygon>
|
||||||
<polygon width="0.254" layer="1" spacing="5.08">
|
<polygon width="0.254" layer="1" spacing="5.08">
|
||||||
<vertex x="258.9164" y="116.0208" curve="-180"/>
|
<vertex x="258.75" y="116" curve="-180"/>
|
||||||
<vertex x="254.9164" y="112.0208" curve="-180"/>
|
<vertex x="254.75" y="112" curve="-180"/>
|
||||||
</polygon>
|
</polygon>
|
||||||
<polygon width="0.254" layer="1" spacing="5.08">
|
<polygon width="0.254" layer="1" spacing="5.08">
|
||||||
<vertex x="260" y="300"/>
|
<vertex x="260" y="300"/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -20,71 +20,18 @@ static const struct {
|
|||||||
{ADAR_4_CS_3V3_GPIO_Port, ADAR_4_CS_3V3_Pin} // ADAR1000 #4
|
{ADAR_4_CS_3V3_GPIO_Port, ADAR_4_CS_3V3_Pin} // ADAR1000 #4
|
||||||
};
|
};
|
||||||
|
|
||||||
// ADAR1000 Vector Modulator lookup tables (128-state phase grid, 2.8125 deg step).
|
// Vector Modulator lookup tables
|
||||||
//
|
|
||||||
// Source: Analog Devices ADAR1000 datasheet Rev. B, Tables 13-16, page 34
|
|
||||||
// (7_Components Datasheets and Application notes/ADAR1000.pdf)
|
|
||||||
// Cross-checked against the ADI Linux mainline driver (GPL-2.0, NOT vendored):
|
|
||||||
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/
|
|
||||||
// drivers/iio/beamformer/adar1000.c (adar1000_phase_values[])
|
|
||||||
// The 128 byte values themselves are factual data from the datasheet and are
|
|
||||||
// not subject to copyright; only the ADI driver code is GPL.
|
|
||||||
//
|
|
||||||
// Byte format (per datasheet):
|
|
||||||
// bit [7:6] reserved (0)
|
|
||||||
// bit [5] polarity: 1 = positive lobe (sign(I) or sign(Q) >= 0)
|
|
||||||
// 0 = negative lobe
|
|
||||||
// bits [4:0] 5-bit unsigned magnitude (0..31)
|
|
||||||
// At magnitude=0 the polarity bit is physically meaningless; the datasheet
|
|
||||||
// uses POL=1 (e.g. VM_Q at 0 deg = 0x20, VM_I at 90 deg = 0x21).
|
|
||||||
//
|
|
||||||
// Index mapping is uniform: VM_I[k] / VM_Q[k] correspond to phase angle
|
|
||||||
// k * 360/128 = k * 2.8125 degrees. Callers index as VM_*[phase % 128].
|
|
||||||
const uint8_t ADAR1000Manager::VM_I[128] = {
|
const uint8_t ADAR1000Manager::VM_I[128] = {
|
||||||
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, 0x3D, // [ 0] 0.0000 deg
|
// ... (same as in your original file)
|
||||||
0x3D, 0x3C, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, // [ 8] 22.5000 deg
|
|
||||||
0x36, 0x35, 0x34, 0x33, 0x32, 0x30, 0x2F, 0x2E, // [ 16] 45.0000 deg
|
|
||||||
0x2C, 0x2B, 0x2A, 0x28, 0x27, 0x25, 0x24, 0x22, // [ 24] 67.5000 deg
|
|
||||||
0x21, 0x01, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, // [ 32] 90.0000 deg
|
|
||||||
0x0B, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, 0x14, // [ 40] 112.5000 deg
|
|
||||||
0x16, 0x17, 0x18, 0x19, 0x19, 0x1A, 0x1B, 0x1C, // [ 48] 135.0000 deg
|
|
||||||
0x1C, 0x1D, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, 0x1F, // [ 56] 157.5000 deg
|
|
||||||
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, // [ 64] 180.0000 deg
|
|
||||||
0x1D, 0x1C, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, // [ 72] 202.5000 deg
|
|
||||||
0x16, 0x15, 0x14, 0x13, 0x12, 0x10, 0x0F, 0x0E, // [ 80] 225.0000 deg
|
|
||||||
0x0C, 0x0B, 0x0A, 0x08, 0x07, 0x05, 0x04, 0x02, // [ 88] 247.5000 deg
|
|
||||||
0x01, 0x21, 0x23, 0x24, 0x26, 0x27, 0x28, 0x2A, // [ 96] 270.0000 deg
|
|
||||||
0x2B, 0x2D, 0x2E, 0x2F, 0x31, 0x32, 0x33, 0x34, // [104] 292.5000 deg
|
|
||||||
0x36, 0x37, 0x38, 0x39, 0x39, 0x3A, 0x3B, 0x3C, // [112] 315.0000 deg
|
|
||||||
0x3C, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F, // [120] 337.5000 deg
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint8_t ADAR1000Manager::VM_Q[128] = {
|
const uint8_t ADAR1000Manager::VM_Q[128] = {
|
||||||
0x20, 0x21, 0x23, 0x24, 0x26, 0x27, 0x28, 0x2A, // [ 0] 0.0000 deg
|
// ... (same as in your original file)
|
||||||
0x2B, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x33, 0x34, // [ 8] 22.5000 deg
|
|
||||||
0x35, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3A, // [ 16] 45.0000 deg
|
|
||||||
0x3B, 0x3C, 0x3C, 0x3C, 0x3D, 0x3D, 0x3D, 0x3D, // [ 24] 67.5000 deg
|
|
||||||
0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3C, 0x3C, 0x3C, // [ 32] 90.0000 deg
|
|
||||||
0x3B, 0x3A, 0x3A, 0x39, 0x38, 0x38, 0x37, 0x36, // [ 40] 112.5000 deg
|
|
||||||
0x35, 0x34, 0x33, 0x31, 0x30, 0x2F, 0x2E, 0x2D, // [ 48] 135.0000 deg
|
|
||||||
0x2B, 0x2A, 0x28, 0x27, 0x26, 0x24, 0x23, 0x21, // [ 56] 157.5000 deg
|
|
||||||
0x20, 0x01, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, // [ 64] 180.0000 deg
|
|
||||||
0x0B, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x14, // [ 72] 202.5000 deg
|
|
||||||
0x15, 0x16, 0x17, 0x18, 0x18, 0x19, 0x1A, 0x1A, // [ 80] 225.0000 deg
|
|
||||||
0x1B, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1D, // [ 88] 247.5000 deg
|
|
||||||
0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1C, 0x1C, 0x1C, // [ 96] 270.0000 deg
|
|
||||||
0x1B, 0x1A, 0x1A, 0x19, 0x18, 0x18, 0x17, 0x16, // [104] 292.5000 deg
|
|
||||||
0x15, 0x14, 0x13, 0x11, 0x10, 0x0F, 0x0E, 0x0D, // [112] 315.0000 deg
|
|
||||||
0x0B, 0x0A, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, // [120] 337.5000 deg
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: a VM_GAIN[128] table previously existed here as a placeholder but was
|
const uint8_t ADAR1000Manager::VM_GAIN[128] = {
|
||||||
// never populated and never read. The ADAR1000 vector modulator has no
|
// ... (same as in your original file)
|
||||||
// separate gain register: phase-state magnitude is encoded directly in
|
};
|
||||||
// bits [4:0] of the VM_I/VM_Q bytes above. Per-channel VGA gain is a
|
|
||||||
// distinct register (CHx_RX_GAIN at 0x10-0x13, CHx_TX_GAIN at 0x1C-0x1F)
|
|
||||||
// written with the user-supplied byte directly by adarSetRxVgaGain() /
|
|
||||||
// adarSetTxVgaGain(). Do not reintroduce a VM_GAIN[] array.
|
|
||||||
|
|
||||||
ADAR1000Manager::ADAR1000Manager() {
|
ADAR1000Manager::ADAR1000Manager() {
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
@@ -868,22 +815,11 @@ void ADAR1000Manager::adarSetRamBypass(uint8_t deviceIndex, uint8_t broadcast) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
void ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4) per API contract documented in
|
|
||||||
// ADAR1000_AGC.cpp and matching ADI datasheet terminology.
|
|
||||||
// Reject out-of-range early so a stale 0-based caller does not
|
|
||||||
// silently wrap to ((0-1) & 0x03) == 3 and write to CH4.
|
|
||||||
// See issue #90.
|
|
||||||
if (channel < 1 || channel > 4) {
|
|
||||||
DIAG("BF", "adarSetRxPhase: channel %u out of range [1..4], ignored", channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t i_val = VM_I[phase % 128];
|
uint8_t i_val = VM_I[phase % 128];
|
||||||
uint8_t q_val = VM_Q[phase % 128];
|
uint8_t q_val = VM_Q[phase % 128];
|
||||||
|
|
||||||
// Subtract 1 to convert 1-based channel to 0-based register offset
|
uint32_t mem_addr_i = REG_CH1_RX_PHS_I + (channel & 0x03) * 2;
|
||||||
// before masking. See issue #90.
|
uint32_t mem_addr_q = REG_CH1_RX_PHS_Q + (channel & 0x03) * 2;
|
||||||
uint32_t mem_addr_i = REG_CH1_RX_PHS_I + ((channel - 1) & 0x03) * 2;
|
|
||||||
uint32_t mem_addr_q = REG_CH1_RX_PHS_Q + ((channel - 1) & 0x03) * 2;
|
|
||||||
|
|
||||||
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
||||||
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
||||||
@@ -891,16 +827,11 @@ void ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4). See issue #90.
|
|
||||||
if (channel < 1 || channel > 4) {
|
|
||||||
DIAG("BF", "adarSetTxPhase: channel %u out of range [1..4], ignored", channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t i_val = VM_I[phase % 128];
|
uint8_t i_val = VM_I[phase % 128];
|
||||||
uint8_t q_val = VM_Q[phase % 128];
|
uint8_t q_val = VM_Q[phase % 128];
|
||||||
|
|
||||||
uint32_t mem_addr_i = REG_CH1_TX_PHS_I + ((channel - 1) & 0x03) * 2;
|
uint32_t mem_addr_i = REG_CH1_TX_PHS_I + (channel & 0x03) * 2;
|
||||||
uint32_t mem_addr_q = REG_CH1_TX_PHS_Q + ((channel - 1) & 0x03) * 2;
|
uint32_t mem_addr_q = REG_CH1_TX_PHS_Q + (channel & 0x03) * 2;
|
||||||
|
|
||||||
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
||||||
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
||||||
@@ -908,23 +839,13 @@ void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
void ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4). See issue #90.
|
uint32_t mem_addr = REG_CH1_RX_GAIN + (channel & 0x03);
|
||||||
if (channel < 1 || channel > 4) {
|
|
||||||
DIAG("BF", "adarSetRxVgaGain: channel %u out of range [1..4], ignored", channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint32_t mem_addr = REG_CH1_RX_GAIN + ((channel - 1) & 0x03);
|
|
||||||
adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
|
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
void ADAR1000Manager::adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4). See issue #90.
|
uint32_t mem_addr = REG_CH1_TX_GAIN + (channel & 0x03);
|
||||||
if (channel < 1 || channel > 4) {
|
|
||||||
DIAG("BF", "adarSetTxVgaGain: channel %u out of range [1..4], ignored", channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint32_t mem_addr = REG_CH1_TX_GAIN + ((channel - 1) & 0x03);
|
|
||||||
adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
|
adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,12 +116,10 @@ public:
|
|||||||
bool beam_sweeping_active_ = false;
|
bool beam_sweeping_active_ = false;
|
||||||
uint32_t last_beam_update_time_ = 0;
|
uint32_t last_beam_update_time_ = 0;
|
||||||
|
|
||||||
// Vector Modulator lookup tables (see ADAR1000_Manager.cpp for provenance).
|
// Lookup tables
|
||||||
// Indexed as VM_*[phase % 128] on a uniform 2.8125 deg grid.
|
|
||||||
// No VM_GAIN[] table exists: VM magnitude is bits [4:0] of the I/Q bytes
|
|
||||||
// themselves; per-channel VGA gain uses a separate register.
|
|
||||||
static const uint8_t VM_I[128];
|
static const uint8_t VM_I[128];
|
||||||
static const uint8_t VM_Q[128];
|
static const uint8_t VM_Q[128];
|
||||||
|
static const uint8_t VM_GAIN[128];
|
||||||
|
|
||||||
// Named defaults for the ADTR1107 and ADAR1000 power sequence.
|
// Named defaults for the ADTR1107 and ADAR1000 power sequence.
|
||||||
static constexpr uint8_t kDefaultTxVgaGain = 0x7F;
|
static constexpr uint8_t kDefaultTxVgaGain = 0x7F;
|
||||||
|
|||||||
@@ -0,0 +1,693 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Jimmy Pentz
|
||||||
|
*
|
||||||
|
* Reach me at: github.com/jgpentz, jpentz1(at)gmail.com
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sells
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
/* ADAR1000 4-Channel, X Band and Ku Band Beamformer */
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Includes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
#include "main.h"
|
||||||
|
#include "stm32f7xx_hal.h"
|
||||||
|
#include "stm32f7xx_hal_spi.h"
|
||||||
|
#include "stm32f7xx_hal_gpio.h"
|
||||||
|
#include "adar1000.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Preprocessor Definitions and Constants
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// VM_GAIN is 15 dB of gain in 128 steps. ~0.12 dB per step.
|
||||||
|
// A 15 dB attenuator can be applied on top of these values.
|
||||||
|
const uint8_t VM_GAIN[128] = {
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
|
||||||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
|
||||||
|
};
|
||||||
|
|
||||||
|
// VM_I and VM_Q are the settings for the vector modulator. 128 steps in 360 degrees. ~2.813 degrees per step.
|
||||||
|
const uint8_t VM_I[128] = {
|
||||||
|
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, 0x3D, 0x3D, 0x3C, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37,
|
||||||
|
0x36, 0x35, 0x34, 0x33, 0x32, 0x30, 0x2F, 0x2E, 0x2C, 0x2B, 0x2A, 0x28, 0x27, 0x25, 0x24, 0x22,
|
||||||
|
0x21, 0x01, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, 0x14,
|
||||||
|
0x16, 0x17, 0x18, 0x19, 0x19, 0x1A, 0x1B, 0x1C, 0x1C, 0x1D, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, 0x1F,
|
||||||
|
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D, 0x1C, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17,
|
||||||
|
0x16, 0x15, 0x14, 0x13, 0x12, 0x10, 0x0F, 0x0E, 0x0C, 0x0B, 0x0A, 0x08, 0x07, 0x05, 0x04, 0x02,
|
||||||
|
0x01, 0x21, 0x23, 0x24, 0x26, 0x27, 0x28, 0x2A, 0x2B, 0x2D, 0x2E, 0x2F, 0x31, 0x32, 0x33, 0x34,
|
||||||
|
0x36, 0x37, 0x38, 0x39, 0x39, 0x3A, 0x3B, 0x3C, 0x3C, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F,
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t VM_Q[128] = {
|
||||||
|
0x20, 0x21, 0x23, 0x24, 0x26, 0x27, 0x28, 0x2A, 0x2B, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x33, 0x34,
|
||||||
|
0x35, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3A, 0x3B, 0x3C, 0x3C, 0x3C, 0x3D, 0x3D, 0x3D, 0x3D,
|
||||||
|
0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3C, 0x3C, 0x3C, 0x3B, 0x3A, 0x3A, 0x39, 0x38, 0x38, 0x37, 0x36,
|
||||||
|
0x35, 0x34, 0x33, 0x31, 0x30, 0x2F, 0x2E, 0x2D, 0x2B, 0x2A, 0x28, 0x27, 0x26, 0x24, 0x23, 0x21,
|
||||||
|
0x20, 0x01, 0x03, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x14,
|
||||||
|
0x15, 0x16, 0x17, 0x18, 0x18, 0x19, 0x1A, 0x1A, 0x1B, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1D,
|
||||||
|
0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1C, 0x1C, 0x1C, 0x1B, 0x1A, 0x1A, 0x19, 0x18, 0x18, 0x17, 0x16,
|
||||||
|
0x15, 0x14, 0x13, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0B, 0x0A, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Function Definitions
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Initialize the ADC on the ADAR by setting the ADC with a 2 MHz clk,
|
||||||
|
* and then enable it.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @warning This is setup to only read temperature sensor data, not the power detectors.
|
||||||
|
*/
|
||||||
|
void Adar_AdcInit(const AdarDevice * p_adar, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
data = ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN;
|
||||||
|
|
||||||
|
Adar_Write(p_adar, REG_ADC_CONTROL, data, broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a byte of data from the ADAR.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns a byte of data that has been converted from the temperature sensor.
|
||||||
|
*
|
||||||
|
* @warning This is setup to only read temperature sensor data, not the power detectors.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_AdcRead(const AdarDevice * p_adar, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
// Start the ADC conversion
|
||||||
|
Adar_Write(p_adar, REG_ADC_CONTROL, ADAR1000_ADC_ST_CONV, broadcast);
|
||||||
|
|
||||||
|
// This is blocking for now... wait until data is converted, then read it
|
||||||
|
while (!(Adar_Read(p_adar, REG_ADC_CONTROL) & 0x01))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
data = Adar_Read(p_adar, REG_ADC_OUT);
|
||||||
|
|
||||||
|
return(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Requests the device info from a specific ADAR and stores it in the
|
||||||
|
* provided AdarDeviceInfo struct.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param info[out] Struct that contains the device info fields.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERROR_NOERROR if information was successfully received and stored in the struct.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_GetDeviceInfo(const AdarDevice * p_adar, AdarDeviceInfo * info)
|
||||||
|
{
|
||||||
|
*((uint8_t *)info) = Adar_Read(p_adar, 0x002);
|
||||||
|
info->chip_type = Adar_Read(p_adar, 0x003);
|
||||||
|
info->product_id = ((uint16_t)Adar_Read(p_adar, 0x004)) << 8;
|
||||||
|
info->product_id |= ((uint16_t)Adar_Read(p_adar, 0x005)) & 0x00ff;
|
||||||
|
info->scratchpad = Adar_Read(p_adar, 0x00A);
|
||||||
|
info->spi_rev = Adar_Read(p_adar, 0x00B);
|
||||||
|
info->vendor_id = ((uint16_t)Adar_Read(p_adar, 0x00C)) << 8;
|
||||||
|
info->vendor_id |= ((uint16_t)Adar_Read(p_adar, 0x00D)) & 0x00ff;
|
||||||
|
info->rev_id = Adar_Read(p_adar, 0x045);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the data that is stored in a single ADAR register.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param mem_addr Memory address of the register you wish to read from.
|
||||||
|
*
|
||||||
|
* @return Returns the byte of data that is stored in the desired register.
|
||||||
|
*
|
||||||
|
* @warning This function will clear ADDR_ASCN bits.
|
||||||
|
* @warning The ADAR does not allow for block reads.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_Read(const AdarDevice * p_adar, uint32_t mem_addr)
|
||||||
|
{
|
||||||
|
uint8_t instruction[3];
|
||||||
|
|
||||||
|
// Set SDO active
|
||||||
|
Adar_Write(p_adar, REG_INTERFACE_CONFIG_A, INTERFACE_CONFIG_A_SDO_ACTIVE, 0);
|
||||||
|
|
||||||
|
instruction[0] = 0x80 | ((p_adar->dev_addr & 0x03) << 5);
|
||||||
|
instruction[0] |= ((0xff00 & mem_addr) >> 8);
|
||||||
|
instruction[1] = (0xff & mem_addr);
|
||||||
|
instruction[2] = 0x00;
|
||||||
|
|
||||||
|
p_adar->Transfer(instruction, p_adar->p_rx_buffer, ADAR1000_RD_SIZE);
|
||||||
|
|
||||||
|
// Set SDO Inactive
|
||||||
|
Adar_Write(p_adar, REG_INTERFACE_CONFIG_A, 0, 0);
|
||||||
|
|
||||||
|
return(p_adar->p_rx_buffer[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Block memory write to an ADAR device.
|
||||||
|
*
|
||||||
|
* @pre ADDR_ASCN bits in register zero must be set!
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param mem_addr Memory address of the register you wish to read from.
|
||||||
|
* @param p_data Pointer to block of data to transfer (must have two unused bytes preceding the data for instruction).
|
||||||
|
* @param size Size of data in bytes, including the two additional leading bytes.
|
||||||
|
*
|
||||||
|
* @warning First two bytes of data will be corrupted if you do not provide two unused leading bytes!
|
||||||
|
*/
|
||||||
|
void Adar_ReadBlock(const AdarDevice * p_adar, uint16_t mem_addr, uint8_t * p_data, uint32_t size)
|
||||||
|
{
|
||||||
|
// Set SDO active
|
||||||
|
Adar_Write(p_adar, REG_INTERFACE_CONFIG_A, INTERFACE_CONFIG_A_SDO_ACTIVE | INTERFACE_CONFIG_A_ADDR_ASCN, 0);
|
||||||
|
|
||||||
|
// Prepare command
|
||||||
|
p_data[0] = 0x80 | ((p_adar->dev_addr & 0x03) << 5);
|
||||||
|
p_data[0] |= ((mem_addr) >> 8) & 0x1F;
|
||||||
|
p_data[1] = (0xFF & mem_addr);
|
||||||
|
|
||||||
|
// Start the transfer
|
||||||
|
p_adar->Transfer(p_data, p_data, size);
|
||||||
|
|
||||||
|
Adar_Write(p_adar, REG_INTERFACE_CONFIG_A, 0, 0);
|
||||||
|
// Return nothing since we assume this is non-blocking and won't wait around
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the Rx/Tx bias currents for the LNA, VM, and VGA to be in either
|
||||||
|
* low power setting or nominal setting.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param p_bias[in] An AdarBiasCurrents struct filled with bias settings
|
||||||
|
* as seen in the datasheet Table 6. SPI Settings for
|
||||||
|
* Different Power Modules
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERR_NOERROR if the bias currents were set
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetBiasCurrents(const AdarDevice * p_adar, AdarBiasCurrents * p_bias, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t bias = 0;
|
||||||
|
|
||||||
|
// RX LNA/VGA/VM bias
|
||||||
|
bias = (p_bias->rx_lna & 0x0f);
|
||||||
|
Adar_Write(p_adar, REG_BIAS_CURRENT_RX_LNA, bias, broadcast); // RX LNA bias
|
||||||
|
bias = (p_bias->rx_vga & 0x07 << 3) | (p_bias->rx_vm & 0x07);
|
||||||
|
Adar_Write(p_adar, REG_BIAS_CURRENT_RX, bias, broadcast); // RX VM/VGA bias
|
||||||
|
|
||||||
|
// TX VGA/VM/DRV bias
|
||||||
|
bias = (p_bias->tx_vga & 0x07 << 3) | (p_bias->tx_vm & 0x07);
|
||||||
|
Adar_Write(p_adar, REG_BIAS_CURRENT_TX, bias, broadcast); // TX VM/VGA bias
|
||||||
|
bias = (p_bias->tx_drv & 0x07);
|
||||||
|
Adar_Write(p_adar, REG_BIAS_CURRENT_TX_DRV, bias, broadcast); // TX DRV bias
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the bias ON and bias OFF voltages for the four PA's and one LNA.
|
||||||
|
*
|
||||||
|
* @pre This will set all 5 bias ON values and all 5 bias OFF values at once.
|
||||||
|
* To enable these bias values, please see the data sheet and ensure that the BIAS_CTRL,
|
||||||
|
* LNA_BIAS_OUT_EN, TR_SOURCE, TX_EN, RX_EN, TR (input to chip), and PA_ON (input to chip)
|
||||||
|
* bits have all been properly set.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param bias_on_voltage Array that contains the bias ON voltages.
|
||||||
|
* @param bias_off_voltage Array that contains the bias OFF voltages.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERR_NOERROR if the bias currents were set
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetBiasVoltages(const AdarDevice * p_adar, uint8_t bias_on_voltage[5], uint8_t bias_off_voltage[5])
|
||||||
|
{
|
||||||
|
Adar_SetBit(p_adar, 0x30, 6, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x31, 2, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x38, 5, BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH1_BIAS_ON,bias_on_voltage[0], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH2_BIAS_ON,bias_on_voltage[1], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH3_BIAS_ON,bias_on_voltage[2], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH4_BIAS_ON,bias_on_voltage[3], BROADCAST_OFF);
|
||||||
|
|
||||||
|
Adar_Write(p_adar, REG_PA_CH1_BIAS_OFF,bias_off_voltage[0], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH2_BIAS_OFF,bias_off_voltage[1], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH3_BIAS_OFF,bias_off_voltage[2], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_PA_CH4_BIAS_OFF,bias_off_voltage[3], BROADCAST_OFF);
|
||||||
|
|
||||||
|
Adar_SetBit(p_adar, 0x30, 4, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x30, 6, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x31, 2, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x38, 5, BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_LNA_BIAS_ON,bias_on_voltage[4], BROADCAST_OFF);
|
||||||
|
Adar_Write(p_adar, REG_LNA_BIAS_OFF,bias_off_voltage[4], BROADCAST_OFF);
|
||||||
|
|
||||||
|
Adar_ResetBit(p_adar, 0x30, 7, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x31, 2, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x31, 4, BROADCAST_OFF);
|
||||||
|
Adar_SetBit(p_adar, 0x31, 7, BROADCAST_OFF);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup the ADAR to use settings that are transferred over SPI.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERR_NOERROR if the bias currents were set
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetRamBypass(const AdarDevice * p_adar, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
data = (MEM_CTRL_BIAS_RAM_BYPASS | MEM_CTRL_BEAM_RAM_BYPASS);
|
||||||
|
|
||||||
|
Adar_Write(p_adar, REG_MEM_CTL, data, broadcast);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the VGA gain value of a Receive channel in dB.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param channel Channel in which to set the gain (1-4).
|
||||||
|
* @param vga_gain_db Gain to be applied to the channel, ranging from 0 - 30 dB.
|
||||||
|
* (Intended operation >16 dB).
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERROR_NOERROR if the gain was successfully set.
|
||||||
|
* ADAR_ERROR_FAILED if an invalid channel was selected.
|
||||||
|
*
|
||||||
|
* @warning 0 dB or 15 dB step attenuator may also be turned on, which is why intended operation is >16 dB.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetRxVgaGain(const AdarDevice * p_adar, uint8_t channel, uint8_t vga_gain_db, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t vga_gain_bits = (uint8_t)(255*vga_gain_db/16);
|
||||||
|
uint32_t mem_addr = 0;
|
||||||
|
|
||||||
|
if((channel == 0) || (channel > 4))
|
||||||
|
{
|
||||||
|
return(ADAR_ERROR_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_addr = REG_CH1_RX_GAIN + (channel & 0x03);
|
||||||
|
|
||||||
|
// Set gain
|
||||||
|
Adar_Write(p_adar, mem_addr, vga_gain_bits, broadcast);
|
||||||
|
|
||||||
|
// Load the new setting
|
||||||
|
Adar_Write(p_adar, REG_LOAD_WORKING, 0x1, broadcast);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the phase of a given receive channel using the I/Q vector modulator.
|
||||||
|
*
|
||||||
|
* @pre According to the given @param phase, this sets the polarity (bit 5) and gain (bits 4-0)
|
||||||
|
* of the @param channel, and then loads them into the working register.
|
||||||
|
* A vector modulator I/Q look-up table has been provided at the beginning of this library.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param channel Channel in which to set the gain (1-4).
|
||||||
|
* @param phase Byte that is used to set the polarity (bit 5) and gain (bits 4-0).
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERROR_NOERROR if the phase was successfully set.
|
||||||
|
* ADAR_ERROR_FAILED if an invalid channel was selected.
|
||||||
|
*
|
||||||
|
* @note To obtain your phase:
|
||||||
|
* phase = degrees * 128;
|
||||||
|
* phase /= 360;
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetRxPhase(const AdarDevice * p_adar, uint8_t channel, uint8_t phase, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t i_val = 0;
|
||||||
|
uint8_t q_val = 0;
|
||||||
|
uint32_t mem_addr_i, mem_addr_q;
|
||||||
|
|
||||||
|
if((channel == 0) || (channel > 4))
|
||||||
|
{
|
||||||
|
return(ADAR_ERROR_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//phase = phase % 128;
|
||||||
|
i_val = VM_I[phase];
|
||||||
|
q_val = VM_Q[phase];
|
||||||
|
|
||||||
|
mem_addr_i = REG_CH1_RX_PHS_I + (channel & 0x03) * 2;
|
||||||
|
mem_addr_q = REG_CH1_RX_PHS_Q + (channel & 0x03) * 2;
|
||||||
|
|
||||||
|
Adar_Write(p_adar, mem_addr_i, i_val, broadcast);
|
||||||
|
Adar_Write(p_adar, mem_addr_q, q_val, broadcast);
|
||||||
|
Adar_Write(p_adar, REG_LOAD_WORKING, 0x1, broadcast);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the VGA gain value of a Tx channel in dB.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERROR_NOERROR if the bias was successfully set.
|
||||||
|
* ADAR_ERROR_FAILED if an invalid channel was selected.
|
||||||
|
*
|
||||||
|
* @warning 0 dB or 15 dB step attenuator may also be turned on, which is why intended operation is >16 dB.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetTxBias(const AdarDevice * p_adar, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t vga_bias_bits;
|
||||||
|
uint8_t drv_bias_bits;
|
||||||
|
uint32_t mem_vga_bias;
|
||||||
|
uint32_t mem_drv_bias;
|
||||||
|
|
||||||
|
mem_vga_bias = REG_BIAS_CURRENT_TX;
|
||||||
|
mem_drv_bias = REG_BIAS_CURRENT_TX_DRV;
|
||||||
|
|
||||||
|
// Set bias to nom
|
||||||
|
vga_bias_bits = 0x2D;
|
||||||
|
drv_bias_bits = 0x06;
|
||||||
|
|
||||||
|
// Set bias
|
||||||
|
Adar_Write(p_adar, mem_vga_bias, vga_bias_bits, broadcast);
|
||||||
|
// Set bias
|
||||||
|
Adar_Write(p_adar, mem_drv_bias, drv_bias_bits, broadcast);
|
||||||
|
|
||||||
|
// Load the new setting
|
||||||
|
Adar_Write(p_adar, REG_LOAD_WORKING, 0x2, broadcast);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the VGA gain value of a Tx channel.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param channel Tx channel in which to set the gain, ranging from 1 - 4.
|
||||||
|
* @param gain Gain to be applied to the channel, ranging from 0 - 127,
|
||||||
|
* plus the MSb 15dB attenuator (Intended operation >16 dB).
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERROR_NOERROR if the gain was successfully set.
|
||||||
|
* ADAR_ERROR_FAILED if an invalid channel was selected.
|
||||||
|
*
|
||||||
|
* @warning 0 dB or 15 dB step attenuator may also be turned on, which is why intended operation is >16 dB.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetTxVgaGain(const AdarDevice * p_adar, uint8_t channel, uint8_t gain, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint32_t mem_addr;
|
||||||
|
|
||||||
|
if((channel == 0) || (channel > 4))
|
||||||
|
{
|
||||||
|
return(ADAR_ERROR_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_addr = REG_CH1_TX_GAIN + (channel & 0x03);
|
||||||
|
|
||||||
|
// Set gain
|
||||||
|
Adar_Write(p_adar, mem_addr, gain, broadcast);
|
||||||
|
|
||||||
|
// Load the new setting
|
||||||
|
Adar_Write(p_adar, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the phase of a given transmit channel using the I/Q vector modulator.
|
||||||
|
*
|
||||||
|
* @pre According to the given @param phase, this sets the polarity (bit 5) and gain (bits 4-0)
|
||||||
|
* of the @param channel, and then loads them into the working register.
|
||||||
|
* A vector modulator I/Q look-up table has been provided at the beginning of this library.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param channel Channel in which to set the gain (1-4).
|
||||||
|
* @param phase Byte that is used to set the polarity (bit 5) and gain (bits 4-0).
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @return Returns ADAR_ERROR_NOERROR if the phase was successfully set.
|
||||||
|
* ADAR_ERROR_FAILED if an invalid channel was selected.
|
||||||
|
*
|
||||||
|
* @note To obtain your phase:
|
||||||
|
* phase = degrees * 128;
|
||||||
|
* phase /= 360;
|
||||||
|
*/
|
||||||
|
uint8_t Adar_SetTxPhase(const AdarDevice * p_adar, uint8_t channel, uint8_t phase, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t i_val = 0;
|
||||||
|
uint8_t q_val = 0;
|
||||||
|
uint32_t mem_addr_i, mem_addr_q;
|
||||||
|
|
||||||
|
if((channel == 0) || (channel > 4))
|
||||||
|
{
|
||||||
|
return(ADAR_ERROR_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//phase = phase % 128;
|
||||||
|
i_val = VM_I[phase];
|
||||||
|
q_val = VM_Q[phase];
|
||||||
|
|
||||||
|
mem_addr_i = REG_CH1_TX_PHS_I + (channel & 0x03) * 2;
|
||||||
|
mem_addr_q = REG_CH1_TX_PHS_Q + (channel & 0x03) * 2;
|
||||||
|
|
||||||
|
Adar_Write(p_adar, mem_addr_i, i_val, broadcast);
|
||||||
|
Adar_Write(p_adar, mem_addr_q, q_val, broadcast);
|
||||||
|
Adar_Write(p_adar, REG_LOAD_WORKING, 0x1, broadcast);
|
||||||
|
|
||||||
|
return(ADAR_ERROR_NOERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the whole ADAR device.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] ADAR pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
*/
|
||||||
|
void Adar_SoftReset(const AdarDevice * p_adar)
|
||||||
|
{
|
||||||
|
uint8_t instruction[3];
|
||||||
|
|
||||||
|
instruction[0] = ((p_adar->dev_addr & 0x03) << 5);
|
||||||
|
instruction[1] = 0x00;
|
||||||
|
instruction[2] = 0x81;
|
||||||
|
|
||||||
|
p_adar->Transfer(instruction, NULL, sizeof(instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset ALL ADAR devices in the SPI chain.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
*/
|
||||||
|
void Adar_SoftResetAll(const AdarDevice * p_adar)
|
||||||
|
{
|
||||||
|
uint8_t instruction[3];
|
||||||
|
|
||||||
|
instruction[0] = 0x08;
|
||||||
|
instruction[1] = 0x00;
|
||||||
|
instruction[2] = 0x81;
|
||||||
|
|
||||||
|
p_adar->Transfer(instruction, NULL, sizeof(instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a byte of @param data to the register located at @param mem_addr.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param mem_addr Memory address of the register you wish to read from.
|
||||||
|
* @param data Byte of data to be stored in the register.
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
if this set to BROADCAST_ON.
|
||||||
|
*
|
||||||
|
* @warning If writing the same data to multiple registers, use ADAR_WriteBlock.
|
||||||
|
*/
|
||||||
|
void Adar_Write(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t data, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t instruction[3];
|
||||||
|
|
||||||
|
if (broadcast)
|
||||||
|
{
|
||||||
|
instruction[0] = 0x08;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instruction[0] = ((p_adar->dev_addr & 0x03) << 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
instruction[0] |= (0x1F00 & mem_addr) >> 8;
|
||||||
|
instruction[1] = (0xFF & mem_addr);
|
||||||
|
instruction[2] = data;
|
||||||
|
|
||||||
|
p_adar->Transfer(instruction, NULL, sizeof(instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Block memory write to an ADAR device.
|
||||||
|
*
|
||||||
|
* @pre ADDR_ASCN BITS IN REGISTER ZERO MUST BE SET!
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param mem_addr Memory address of the register you wish to read from.
|
||||||
|
* @param p_data[in] Pointer to block of data to transfer (must have two unused bytes
|
||||||
|
preceding the data for instruction).
|
||||||
|
* @param size Size of data in bytes, including the two additional leading bytes.
|
||||||
|
*
|
||||||
|
* @warning First two bytes of data will be corrupted if you do not provide two unused leading bytes!
|
||||||
|
*/
|
||||||
|
void Adar_WriteBlock(const AdarDevice * p_adar, uint16_t mem_addr, uint8_t * p_data, uint32_t size)
|
||||||
|
{
|
||||||
|
// Prepare command
|
||||||
|
p_data[0] = ((p_adar->dev_addr & 0x03) << 5);
|
||||||
|
p_data[0] |= ((mem_addr) >> 8) & 0x1F;
|
||||||
|
p_data[1] = (0xFF & mem_addr);
|
||||||
|
|
||||||
|
// Start the transfer
|
||||||
|
p_adar->Transfer(p_data, NULL, size);
|
||||||
|
|
||||||
|
// Return nothing since we assume this is non-blocking and won't wait around
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set contents of the INTERFACE_CONFIG_A register.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param flags #INTERFACE_CONFIG_A_SOFTRESET, #INTERFACE_CONFIG_A_LSB_FIRST,
|
||||||
|
* #INTERFACE_CONFIG_A_ADDR_ASCN, #INTERFACE_CONFIG_A_SDO_ACTIVE
|
||||||
|
* @param broadcast Send the message as a broadcast to all ADARs in the SPI chain
|
||||||
|
* if this set to BROADCAST_ON.
|
||||||
|
*/
|
||||||
|
void Adar_WriteConfigA(const AdarDevice * p_adar, uint8_t flags, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
Adar_Write(p_adar, 0x00, flags, broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a byte of @param data to the register located at @param mem_addr and
|
||||||
|
* then read from the device and verify that the register was correctly set.
|
||||||
|
*
|
||||||
|
* @param p_adar[in] Adar pointer Which specifies the device and what function
|
||||||
|
* to use for SPI transfer.
|
||||||
|
* @param mem_addr Memory address of the register you wish to read from.
|
||||||
|
* @param data Byte of data to be stored in the register.
|
||||||
|
*
|
||||||
|
* @return Returns the number of attempts that it took to successfully write to a register,
|
||||||
|
* starting from zero.
|
||||||
|
* @warning This function currently only supports writes to a single regiter in a single ADAR.
|
||||||
|
*/
|
||||||
|
uint8_t Adar_WriteVerify(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t data)
|
||||||
|
{
|
||||||
|
uint8_t rx_data;
|
||||||
|
|
||||||
|
for (uint8_t ii = 0; ii < 3; ii++)
|
||||||
|
{
|
||||||
|
Adar_Write(p_adar, mem_addr, data, 0);
|
||||||
|
|
||||||
|
// Can't read back from an ADAR with HW address 0
|
||||||
|
if (!((p_adar->dev_addr) % 4))
|
||||||
|
{
|
||||||
|
return(ADAR_ERROR_INVALIDADDR);
|
||||||
|
}
|
||||||
|
rx_data = Adar_Read(p_adar, mem_addr);
|
||||||
|
if (rx_data == data)
|
||||||
|
{
|
||||||
|
return(ii);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(ADAR_ERROR_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Adar_SetBit(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t bit, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t temp = Adar_Read(p_adar, mem_addr);
|
||||||
|
uint8_t data = temp|(1<<bit);
|
||||||
|
Adar_Write(p_adar,mem_addr, data,broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Adar_ResetBit(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t bit, uint8_t broadcast)
|
||||||
|
{
|
||||||
|
uint8_t temp = Adar_Read(p_adar, mem_addr);
|
||||||
|
uint8_t data = temp&~(1<<bit);
|
||||||
|
Adar_Write(p_adar,mem_addr, data,broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Jimmy Pentz
|
||||||
|
*
|
||||||
|
* Reach me at: github.com/jgpentz, jpentz1( at )gmail.com
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sells
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
/* ADAR1000 4-Channel, X Band and Ku Band Beamformer */
|
||||||
|
#ifndef LIB_ADAR1000_H_
|
||||||
|
#define LIB_ADAR1000_H_
|
||||||
|
|
||||||
|
#ifndef NULL
|
||||||
|
#define NULL (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Includes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
#include "main.h"
|
||||||
|
#include "stm32f7xx_hal.h"
|
||||||
|
#include "stm32f7xx_hal_spi.h"
|
||||||
|
#include "stm32f7xx_hal_gpio.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" { // Prevent C++ name mangling
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Datatypes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
extern SPI_HandleTypeDef hspi1;
|
||||||
|
extern const uint8_t VM_GAIN[128];
|
||||||
|
extern const uint8_t VM_I[128];
|
||||||
|
extern const uint8_t VM_Q[128];
|
||||||
|
|
||||||
|
/// A function pointer prototype for a SPI transfer, the 3 parameters would be
|
||||||
|
/// p_txData, p_rxData, and size (number of bytes to transfer), respectively.
|
||||||
|
typedef uint32_t (*Adar_SpiTransfer)( uint8_t *, uint8_t *, uint32_t);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t dev_addr; ///< 2-bit device hardware address, 0x00, 0x01, 0x10, 0x11
|
||||||
|
Adar_SpiTransfer Transfer; ///< Function pointer to the function used for SPI transfers
|
||||||
|
uint8_t * p_rx_buffer; ///< Data buffer to store received bytes into
|
||||||
|
}const AdarDevice;
|
||||||
|
|
||||||
|
|
||||||
|
/// Use this to store bias current values into, as seen in the datasheet
|
||||||
|
/// Table 6. SPI Settings for Different Power Modules
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t rx_lna; ///< nominal: 8, low power: 5
|
||||||
|
uint8_t rx_vm; ///< nominal: 5, low power: 2
|
||||||
|
uint8_t rx_vga; ///< nominal: 10, low power: 3
|
||||||
|
uint8_t tx_vm; ///< nominal: 5, low power: 2
|
||||||
|
uint8_t tx_vga; ///< nominal: 5, low power: 5
|
||||||
|
uint8_t tx_drv; ///< nominal: 6, low power: 3
|
||||||
|
} AdarBiasCurrents;
|
||||||
|
|
||||||
|
/// Useful for queries regarding the device info
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t norm_operating_mode : 2;
|
||||||
|
uint8_t cust_operating_mode : 2;
|
||||||
|
uint8_t dev_status : 4;
|
||||||
|
uint8_t chip_type;
|
||||||
|
uint16_t product_id;
|
||||||
|
uint8_t scratchpad;
|
||||||
|
uint8_t spi_rev;
|
||||||
|
uint16_t vendor_id;
|
||||||
|
uint8_t rev_id;
|
||||||
|
} AdarDeviceInfo;
|
||||||
|
|
||||||
|
/// Return types for functions in this library
|
||||||
|
typedef enum {
|
||||||
|
ADAR_ERROR_NOERROR = 0,
|
||||||
|
ADAR_ERROR_FAILED = 1,
|
||||||
|
ADAR_ERROR_INVALIDADDR = 2,
|
||||||
|
} AdarErrorCodes;
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Function Prototypes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
void Adar_AdcInit(const AdarDevice * p_adar, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_AdcRead(const AdarDevice * p_adar, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_GetDeviceInfo(const AdarDevice * p_adar, AdarDeviceInfo * info);
|
||||||
|
|
||||||
|
uint8_t Adar_Read(const AdarDevice * p_adar, uint32_t mem_addr);
|
||||||
|
|
||||||
|
void Adar_ReadBlock(const AdarDevice * p_adar, uint16_t mem_addr, uint8_t * p_data, uint32_t size);
|
||||||
|
|
||||||
|
uint8_t Adar_SetBiasCurrents(const AdarDevice * p_adar, AdarBiasCurrents * p_bias, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_SetBiasVoltages(const AdarDevice * p_adar, uint8_t bias_on_voltage[5], uint8_t bias_off_voltage[5]);
|
||||||
|
|
||||||
|
uint8_t Adar_SetRamBypass(const AdarDevice * p_adar, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_SetRxVgaGain(const AdarDevice * p_adar, uint8_t channel, uint8_t vga_gain_db, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_SetRxPhase(const AdarDevice * p_adar, uint8_t channel, uint8_t phase, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_SetTxBias(const AdarDevice * p_adar, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_SetTxVgaGain(const AdarDevice * p_adar, uint8_t channel, uint8_t vga_gain_db, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
uint8_t Adar_SetTxPhase(const AdarDevice * p_adar, uint8_t channel, uint8_t phase, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
void Adar_SoftReset(const AdarDevice * p_adar);
|
||||||
|
|
||||||
|
void Adar_SoftResetAll(const AdarDevice * p_adar);
|
||||||
|
|
||||||
|
void Adar_Write(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t data, uint8_t broadcast_bit);
|
||||||
|
|
||||||
|
void Adar_WriteBlock(const AdarDevice * p_adar, uint16_t mem_addr, uint8_t * p_data, uint32_t size);
|
||||||
|
|
||||||
|
void Adar_WriteConfigA(const AdarDevice * p_adar, uint8_t flags, uint8_t broadcast);
|
||||||
|
|
||||||
|
uint8_t Adar_WriteVerify(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t data);
|
||||||
|
|
||||||
|
void Adar_SetBit(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t bit, uint8_t broadcast);
|
||||||
|
|
||||||
|
void Adar_ResetBit(const AdarDevice * p_adar, uint32_t mem_addr, uint8_t bit, uint8_t broadcast);
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Preprocessor Definitions and Constants
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Using BROADCAST_ON will send a command to all ADARs that share a bus
|
||||||
|
#define BROADCAST_OFF 0
|
||||||
|
#define BROADCAST_ON 1
|
||||||
|
|
||||||
|
// The minimum size of a read from the ADARs consists of 3 bytes
|
||||||
|
#define ADAR1000_RD_SIZE 3
|
||||||
|
|
||||||
|
// Address at which the TX RAM starts
|
||||||
|
#define ADAR_TX_RAM_START_ADDR 0x1800
|
||||||
|
|
||||||
|
// ADC Defines
|
||||||
|
#define ADAR1000_ADC_2MHZ_CLK 0x00
|
||||||
|
#define ADAR1000_ADC_EN 0x60
|
||||||
|
#define ADAR1000_ADC_ST_CONV 0x70
|
||||||
|
|
||||||
|
/* REGISTER DEFINITIONS */
|
||||||
|
#define REG_INTERFACE_CONFIG_A 0x000
|
||||||
|
#define REG_INTERFACE_CONFIG_B 0x001
|
||||||
|
#define REG_DEV_CONFIG 0x002
|
||||||
|
#define REG_SCRATCHPAD 0x00A
|
||||||
|
#define REG_TRANSFER 0x00F
|
||||||
|
#define REG_CH1_RX_GAIN 0x010
|
||||||
|
#define REG_CH2_RX_GAIN 0x011
|
||||||
|
#define REG_CH3_RX_GAIN 0x012
|
||||||
|
#define REG_CH4_RX_GAIN 0x013
|
||||||
|
#define REG_CH1_RX_PHS_I 0x014
|
||||||
|
#define REG_CH1_RX_PHS_Q 0x015
|
||||||
|
#define REG_CH2_RX_PHS_I 0x016
|
||||||
|
#define REG_CH2_RX_PHS_Q 0x017
|
||||||
|
#define REG_CH3_RX_PHS_I 0x018
|
||||||
|
#define REG_CH3_RX_PHS_Q 0x019
|
||||||
|
#define REG_CH4_RX_PHS_I 0x01A
|
||||||
|
#define REG_CH4_RX_PHS_Q 0x01B
|
||||||
|
#define REG_CH1_TX_GAIN 0x01C
|
||||||
|
#define REG_CH2_TX_GAIN 0x01D
|
||||||
|
#define REG_CH3_TX_GAIN 0x01E
|
||||||
|
#define REG_CH4_TX_GAIN 0x01F
|
||||||
|
#define REG_CH1_TX_PHS_I 0x020
|
||||||
|
#define REG_CH1_TX_PHS_Q 0x021
|
||||||
|
#define REG_CH2_TX_PHS_I 0x022
|
||||||
|
#define REG_CH2_TX_PHS_Q 0x023
|
||||||
|
#define REG_CH3_TX_PHS_I 0x024
|
||||||
|
#define REG_CH3_TX_PHS_Q 0x025
|
||||||
|
#define REG_CH4_TX_PHS_I 0x026
|
||||||
|
#define REG_CH4_TX_PHS_Q 0x027
|
||||||
|
#define REG_LOAD_WORKING 0x028
|
||||||
|
#define REG_PA_CH1_BIAS_ON 0x029
|
||||||
|
#define REG_PA_CH2_BIAS_ON 0x02A
|
||||||
|
#define REG_PA_CH3_BIAS_ON 0x02B
|
||||||
|
#define REG_PA_CH4_BIAS_ON 0x02C
|
||||||
|
#define REG_LNA_BIAS_ON 0x02D
|
||||||
|
#define REG_RX_ENABLES 0x02E
|
||||||
|
#define REG_TX_ENABLES 0x02F
|
||||||
|
#define REG_MISC_ENABLES 0x030
|
||||||
|
#define REG_SW_CONTROL 0x031
|
||||||
|
#define REG_ADC_CONTROL 0x032
|
||||||
|
#define REG_ADC_CONTROL_TEMP_EN 0xf0
|
||||||
|
#define REG_ADC_OUT 0x033
|
||||||
|
#define REG_BIAS_CURRENT_RX_LNA 0x034
|
||||||
|
#define REG_BIAS_CURRENT_RX 0x035
|
||||||
|
#define REG_BIAS_CURRENT_TX 0x036
|
||||||
|
#define REG_BIAS_CURRENT_TX_DRV 0x037
|
||||||
|
#define REG_MEM_CTL 0x038
|
||||||
|
#define REG_RX_CHX_MEM 0x039
|
||||||
|
#define REG_TX_CHX_MEM 0x03A
|
||||||
|
#define REG_RX_CH1_MEM 0x03D
|
||||||
|
#define REG_RX_CH2_MEM 0x03E
|
||||||
|
#define REG_RX_CH3_MEM 0x03F
|
||||||
|
#define REG_RX_CH4_MEM 0x040
|
||||||
|
#define REG_TX_CH1_MEM 0x041
|
||||||
|
#define REG_TX_CH2_MEM 0x042
|
||||||
|
#define REG_TX_CH3_MEM 0x043
|
||||||
|
#define REG_TX_CH4_MEM 0x044
|
||||||
|
#define REG_PA_CH1_BIAS_OFF 0x046
|
||||||
|
#define REG_PA_CH2_BIAS_OFF 0x047
|
||||||
|
#define REG_PA_CH3_BIAS_OFF 0x048
|
||||||
|
#define REG_PA_CH4_BIAS_OFF 0x049
|
||||||
|
#define REG_LNA_BIAS_OFF 0x04A
|
||||||
|
#define REG_TX_BEAM_STEP_START 0x04D
|
||||||
|
#define REG_TX_BEAM_STEP_STOP 0x04E
|
||||||
|
#define REG_RX_BEAM_STEP_START 0x04F
|
||||||
|
#define REG_RX_BEAM_STEP_STOP 0x050
|
||||||
|
|
||||||
|
// REGISTER CONSTANTS
|
||||||
|
#define INTERFACE_CONFIG_A_SOFTRESET ((1 << 7) | (1 << 0))
|
||||||
|
#define INTERFACE_CONFIG_A_LSB_FIRST ((1 << 6) | (1 << 1))
|
||||||
|
#define INTERFACE_CONFIG_A_ADDR_ASCN ((1 << 5) | (1 << 2))
|
||||||
|
#define INTERFACE_CONFIG_A_SDO_ACTIVE ((1 << 4) | (1 << 3))
|
||||||
|
|
||||||
|
#define LD_WRK_REGS_LDRX_OVERRIDE (1 << 0)
|
||||||
|
#define LD_WRK_REGS_LDTX_OVERRIDE (1 << 1)
|
||||||
|
|
||||||
|
#define RX_ENABLES_TX_VGA_EN (1 << 0)
|
||||||
|
#define RX_ENABLES_TX_VM_EN (1 << 1)
|
||||||
|
#define RX_ENABLES_TX_DRV_EN (1 << 2)
|
||||||
|
#define RX_ENABLES_CH3_TX_EN (1 << 3)
|
||||||
|
#define RX_ENABLES_CH2_TX_EN (1 << 4)
|
||||||
|
#define RX_ENABLES_CH1_TX_EN (1 << 5)
|
||||||
|
#define RX_ENABLES_CH0_TX_EN (1 << 6)
|
||||||
|
|
||||||
|
#define TX_ENABLES_TX_VGA_EN (1 << 0)
|
||||||
|
#define TX_ENABLES_TX_VM_EN (1 << 1)
|
||||||
|
#define TX_ENABLES_TX_DRV_EN (1 << 2)
|
||||||
|
#define TX_ENABLES_CH3_TX_EN (1 << 3)
|
||||||
|
#define TX_ENABLES_CH2_TX_EN (1 << 4)
|
||||||
|
#define TX_ENABLES_CH1_TX_EN (1 << 5)
|
||||||
|
#define TX_ENABLES_CH0_TX_EN (1 << 6)
|
||||||
|
|
||||||
|
#define MISC_ENABLES_CH4_DET_EN (1 << 0)
|
||||||
|
#define MISC_ENABLES_CH3_DET_EN (1 << 1)
|
||||||
|
#define MISC_ENABLES_CH2_DET_EN (1 << 2)
|
||||||
|
#define MISC_ENABLES_CH1_DET_EN (1 << 3)
|
||||||
|
#define MISC_ENABLES_LNA_BIAS_OUT_EN (1 << 4)
|
||||||
|
#define MISC_ENABLES_BIAS_EN (1 << 5)
|
||||||
|
#define MISC_ENABLES_BIAS_CTRL (1 << 6)
|
||||||
|
#define MISC_ENABLES_SW_DRV_TR_MODE_SEL (1 << 7)
|
||||||
|
|
||||||
|
#define SW_CTRL_POL (1 << 0)
|
||||||
|
#define SW_CTRL_TR_SPI (1 << 1)
|
||||||
|
#define SW_CTRL_TR_SOURCE (1 << 2)
|
||||||
|
#define SW_CTRL_SW_DRV_EN_POL (1 << 3)
|
||||||
|
#define SW_CTRL_SW_DRV_EN_TR (1 << 4)
|
||||||
|
#define SW_CTRL_RX_EN (1 << 5)
|
||||||
|
#define SW_CTRL_TX_EN (1 << 6)
|
||||||
|
#define SW_CTRL_SW_DRV_TR_STATE (1 << 7)
|
||||||
|
|
||||||
|
#define MEM_CTRL_RX_CHX_RAM_BYPASS (1 << 0)
|
||||||
|
#define MEM_CTRL_TX_CHX_RAM_BYPASS (1 << 1)
|
||||||
|
#define MEM_CTRL_RX_BEAM_STEP_EN (1 << 2)
|
||||||
|
#define MEM_CTRL_TX_BEAM_STEP_EN (1 << 3)
|
||||||
|
#define MEM_CTRL_BIAS_RAM_BYPASS (1 << 5)
|
||||||
|
#define MEM_CTRL_BEAM_RAM_BYPASS (1 << 6)
|
||||||
|
#define MEM_CTRL_SCAN_MODE_EN (1 << 7)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // End extern "C"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LIB_ADAR1000_H_ */
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "usb_device.h"
|
#include "usb_device.h"
|
||||||
#include "USBHandler.h"
|
#include "USBHandler.h"
|
||||||
#include "usbd_cdc_if.h"
|
#include "usbd_cdc_if.h"
|
||||||
|
#include "adar1000.h"
|
||||||
#include "ADAR1000_Manager.h"
|
#include "ADAR1000_Manager.h"
|
||||||
#include "ADAR1000_AGC.h"
|
#include "ADAR1000_AGC.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
"""ADAR1000 vector-modulator ground-truth table and firmware parser.
|
|
||||||
|
|
||||||
This module is a pure data + helpers library imported by the cross-layer
|
|
||||||
test suite (`9_Firmware/tests/cross_layer/test_cross_layer_contract.py`,
|
|
||||||
class `TestTier2Adar1000VmTableGroundTruth`). It has no CLI entry point
|
|
||||||
and no side effects on import beyond the structural assertion on the
|
|
||||||
table length.
|
|
||||||
|
|
||||||
Ground-truth source
|
|
||||||
-------------------
|
|
||||||
The 128-entry `(I, Q)` byte pairs below are transcribed from the ADAR1000
|
|
||||||
datasheet Rev. B, Tables 13-16, page 34 ("Phase Shifter Programming"),
|
|
||||||
which is the primary normative reference. The same values appear in the
|
|
||||||
Analog Devices Linux beamformer driver
|
|
||||||
(`drivers/iio/beamformer/adar1000.c`, `adar1000_phase_values[]`) and were
|
|
||||||
cross-checked against that driver as a secondary, independent
|
|
||||||
transcription. The byte values are factual data (5-bit unsigned magnitude
|
|
||||||
in bits[4:0], polarity bit at bit[5], bits[7:6] reserved zero); no
|
|
||||||
copyrightable creative expression. Only the datasheet is the
|
|
||||||
licensing-relevant source.
|
|
||||||
|
|
||||||
PLFM_RADAR firmware indexing convention
|
|
||||||
---------------------------------------
|
|
||||||
`adarSetRxPhase` / `adarSetTxPhase` in
|
|
||||||
`9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.cpp`
|
|
||||||
write `VM_I[phase % 128]` and `VM_Q[phase % 128]` to the chip. Each index
|
|
||||||
N corresponds to commanded beam phase `N * 360/128 = N * 2.8125 deg`. The
|
|
||||||
ADI table is also on a uniform 2.8125 deg grid (verified by
|
|
||||||
`check_uniform_2p8125_deg_step` below), so a 1:1 mapping is correct:
|
|
||||||
PLFM index N == ADI table row N.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Ground truth: ADAR1000 datasheet Rev. B Tables 13-16 p.34
|
|
||||||
# Each entry: (angle_int_deg, angle_frac_x10000, vm_byte_I, vm_byte_Q)
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
GROUND_TRUTH: list[tuple[int, int, int, int]] = [
|
|
||||||
(0, 0, 0x3F, 0x20), (2, 8125, 0x3F, 0x21), (5, 6250, 0x3F, 0x23),
|
|
||||||
(8, 4375, 0x3F, 0x24), (11, 2500, 0x3F, 0x26), (14, 625, 0x3E, 0x27),
|
|
||||||
(16, 8750, 0x3E, 0x28), (19, 6875, 0x3D, 0x2A), (22, 5000, 0x3D, 0x2B),
|
|
||||||
(25, 3125, 0x3C, 0x2D), (28, 1250, 0x3C, 0x2E), (30, 9375, 0x3B, 0x2F),
|
|
||||||
(33, 7500, 0x3A, 0x30), (36, 5625, 0x39, 0x31), (39, 3750, 0x38, 0x33),
|
|
||||||
(42, 1875, 0x37, 0x34), (45, 0, 0x36, 0x35), (47, 8125, 0x35, 0x36),
|
|
||||||
(50, 6250, 0x34, 0x37), (53, 4375, 0x33, 0x38), (56, 2500, 0x32, 0x38),
|
|
||||||
(59, 625, 0x30, 0x39), (61, 8750, 0x2F, 0x3A), (64, 6875, 0x2E, 0x3A),
|
|
||||||
(67, 5000, 0x2C, 0x3B), (70, 3125, 0x2B, 0x3C), (73, 1250, 0x2A, 0x3C),
|
|
||||||
(75, 9375, 0x28, 0x3C), (78, 7500, 0x27, 0x3D), (81, 5625, 0x25, 0x3D),
|
|
||||||
(84, 3750, 0x24, 0x3D), (87, 1875, 0x22, 0x3D), (90, 0, 0x21, 0x3D),
|
|
||||||
(92, 8125, 0x01, 0x3D), (95, 6250, 0x03, 0x3D), (98, 4375, 0x04, 0x3D),
|
|
||||||
(101, 2500, 0x06, 0x3D), (104, 625, 0x07, 0x3C), (106, 8750, 0x08, 0x3C),
|
|
||||||
(109, 6875, 0x0A, 0x3C), (112, 5000, 0x0B, 0x3B), (115, 3125, 0x0D, 0x3A),
|
|
||||||
(118, 1250, 0x0E, 0x3A), (120, 9375, 0x0F, 0x39), (123, 7500, 0x11, 0x38),
|
|
||||||
(126, 5625, 0x12, 0x38), (129, 3750, 0x13, 0x37), (132, 1875, 0x14, 0x36),
|
|
||||||
(135, 0, 0x16, 0x35), (137, 8125, 0x17, 0x34), (140, 6250, 0x18, 0x33),
|
|
||||||
(143, 4375, 0x19, 0x31), (146, 2500, 0x19, 0x30), (149, 625, 0x1A, 0x2F),
|
|
||||||
(151, 8750, 0x1B, 0x2E), (154, 6875, 0x1C, 0x2D), (157, 5000, 0x1C, 0x2B),
|
|
||||||
(160, 3125, 0x1D, 0x2A), (163, 1250, 0x1E, 0x28), (165, 9375, 0x1E, 0x27),
|
|
||||||
(168, 7500, 0x1E, 0x26), (171, 5625, 0x1F, 0x24), (174, 3750, 0x1F, 0x23),
|
|
||||||
(177, 1875, 0x1F, 0x21), (180, 0, 0x1F, 0x20), (182, 8125, 0x1F, 0x01),
|
|
||||||
(185, 6250, 0x1F, 0x03), (188, 4375, 0x1F, 0x04), (191, 2500, 0x1F, 0x06),
|
|
||||||
(194, 625, 0x1E, 0x07), (196, 8750, 0x1E, 0x08), (199, 6875, 0x1D, 0x0A),
|
|
||||||
(202, 5000, 0x1D, 0x0B), (205, 3125, 0x1C, 0x0D), (208, 1250, 0x1C, 0x0E),
|
|
||||||
(210, 9375, 0x1B, 0x0F), (213, 7500, 0x1A, 0x10), (216, 5625, 0x19, 0x11),
|
|
||||||
(219, 3750, 0x18, 0x13), (222, 1875, 0x17, 0x14), (225, 0, 0x16, 0x15),
|
|
||||||
(227, 8125, 0x15, 0x16), (230, 6250, 0x14, 0x17), (233, 4375, 0x13, 0x18),
|
|
||||||
(236, 2500, 0x12, 0x18), (239, 625, 0x10, 0x19), (241, 8750, 0x0F, 0x1A),
|
|
||||||
(244, 6875, 0x0E, 0x1A), (247, 5000, 0x0C, 0x1B), (250, 3125, 0x0B, 0x1C),
|
|
||||||
(253, 1250, 0x0A, 0x1C), (255, 9375, 0x08, 0x1C), (258, 7500, 0x07, 0x1D),
|
|
||||||
(261, 5625, 0x05, 0x1D), (264, 3750, 0x04, 0x1D), (267, 1875, 0x02, 0x1D),
|
|
||||||
(270, 0, 0x01, 0x1D), (272, 8125, 0x21, 0x1D), (275, 6250, 0x23, 0x1D),
|
|
||||||
(278, 4375, 0x24, 0x1D), (281, 2500, 0x26, 0x1D), (284, 625, 0x27, 0x1C),
|
|
||||||
(286, 8750, 0x28, 0x1C), (289, 6875, 0x2A, 0x1C), (292, 5000, 0x2B, 0x1B),
|
|
||||||
(295, 3125, 0x2D, 0x1A), (298, 1250, 0x2E, 0x1A), (300, 9375, 0x2F, 0x19),
|
|
||||||
(303, 7500, 0x31, 0x18), (306, 5625, 0x32, 0x18), (309, 3750, 0x33, 0x17),
|
|
||||||
(312, 1875, 0x34, 0x16), (315, 0, 0x36, 0x15), (317, 8125, 0x37, 0x14),
|
|
||||||
(320, 6250, 0x38, 0x13), (323, 4375, 0x39, 0x11), (326, 2500, 0x39, 0x10),
|
|
||||||
(329, 625, 0x3A, 0x0F), (331, 8750, 0x3B, 0x0E), (334, 6875, 0x3C, 0x0D),
|
|
||||||
(337, 5000, 0x3C, 0x0B), (340, 3125, 0x3D, 0x0A), (343, 1250, 0x3E, 0x08),
|
|
||||||
(345, 9375, 0x3E, 0x07), (348, 7500, 0x3E, 0x06), (351, 5625, 0x3F, 0x04),
|
|
||||||
(354, 3750, 0x3F, 0x03), (357, 1875, 0x3F, 0x01),
|
|
||||||
]
|
|
||||||
|
|
||||||
assert len(GROUND_TRUTH) == 128, f"GROUND_TRUTH must have 128 entries, has {len(GROUND_TRUTH)}"
|
|
||||||
|
|
||||||
VM_I_REF: list[int] = [row[2] for row in GROUND_TRUTH]
|
|
||||||
VM_Q_REF: list[int] = [row[3] for row in GROUND_TRUTH]
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Structural-invariant checks on the embedded ground-truth transcription.
|
|
||||||
# These defend against typos during the copy-paste from the datasheet / ADI
|
|
||||||
# driver. Each function returns a list of error strings (empty == pass) so
|
|
||||||
# callers (the pytest class) can assert-on-empty with a useful message.
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
def check_byte_format(label: str, table: list[int]) -> list[str]:
|
|
||||||
"""Each byte must have bits[7:6] == 0 (reserved)."""
|
|
||||||
errors = []
|
|
||||||
for i, byte in enumerate(table):
|
|
||||||
if byte & 0xC0:
|
|
||||||
errors.append(f"{label}[{i}]=0x{byte:02X}: reserved bits[7:6] non-zero")
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def check_uniform_2p8125_deg_step() -> list[str]:
|
|
||||||
"""Angles must form a uniform 2.8125 deg grid: angle[N] == N * 2.8125."""
|
|
||||||
errors = []
|
|
||||||
for i, (deg_int, deg_frac, _, _) in enumerate(GROUND_TRUTH):
|
|
||||||
# angle in units of 1/10000 degree; 2.8125 deg = 28125/10000 exactly
|
|
||||||
angle_e4 = deg_int * 10000 + deg_frac
|
|
||||||
expected_e4 = i * 28125
|
|
||||||
if angle_e4 != expected_e4:
|
|
||||||
errors.append(
|
|
||||||
f"GROUND_TRUTH[{i}]: angle {deg_int}.{deg_frac:04d} deg "
|
|
||||||
f"(={angle_e4}/10000) != expected {expected_e4}/10000 "
|
|
||||||
f"(=i*2.8125)"
|
|
||||||
)
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def check_quadrant_symmetry() -> list[str]:
|
|
||||||
"""Angle and angle+180 deg must have inverted polarity bits but identical
|
|
||||||
magnitudes. Index offset 64 corresponds to 180 deg on the 128-step grid.
|
|
||||||
|
|
||||||
Exemption: when magnitude is zero the polarity bit is physically
|
|
||||||
meaningless (sign of zero is undefined for the IQ phasor projection).
|
|
||||||
The datasheet uses POL=1 for both 0 and 180 deg Q components (both
|
|
||||||
encode Q=0). Skip the polarity assertion for zero-magnitude entries.
|
|
||||||
"""
|
|
||||||
errors = []
|
|
||||||
POL = 0x20
|
|
||||||
MAG = 0x1F
|
|
||||||
for i in range(64):
|
|
||||||
j = i + 64
|
|
||||||
mag_i_a, mag_i_b = VM_I_REF[i] & MAG, VM_I_REF[j] & MAG
|
|
||||||
if mag_i_a != mag_i_b:
|
|
||||||
errors.append(
|
|
||||||
f"VM_I[{i}]=0x{VM_I_REF[i]:02X} vs VM_I[{j}]=0x{VM_I_REF[j]:02X}: "
|
|
||||||
f"180 deg pair has different magnitude"
|
|
||||||
)
|
|
||||||
if mag_i_a != 0 and (VM_I_REF[i] & POL) == (VM_I_REF[j] & POL):
|
|
||||||
errors.append(
|
|
||||||
f"VM_I[{i}]=0x{VM_I_REF[i]:02X} vs VM_I[{j}]=0x{VM_I_REF[j]:02X}: "
|
|
||||||
f"180 deg pair has same polarity (should be inverted, mag={mag_i_a})"
|
|
||||||
)
|
|
||||||
mag_q_a, mag_q_b = VM_Q_REF[i] & MAG, VM_Q_REF[j] & MAG
|
|
||||||
if mag_q_a != mag_q_b:
|
|
||||||
errors.append(
|
|
||||||
f"VM_Q[{i}]=0x{VM_Q_REF[i]:02X} vs VM_Q[{j}]=0x{VM_Q_REF[j]:02X}: "
|
|
||||||
f"180 deg pair has different magnitude"
|
|
||||||
)
|
|
||||||
if mag_q_a != 0 and (VM_Q_REF[i] & POL) == (VM_Q_REF[j] & POL):
|
|
||||||
errors.append(
|
|
||||||
f"VM_Q[{i}]=0x{VM_Q_REF[i]:02X} vs VM_Q[{j}]=0x{VM_Q_REF[j]:02X}: "
|
|
||||||
f"180 deg pair has same polarity (should be inverted, mag={mag_q_a})"
|
|
||||||
)
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def check_cardinal_points() -> list[str]:
|
|
||||||
"""Spot-check cardinal phase points against datasheet expectations."""
|
|
||||||
errors = []
|
|
||||||
expectations = [
|
|
||||||
(0, 0x3F, 0x20, "0 deg: max +I, ~zero Q"),
|
|
||||||
(32, 0x21, 0x3D, "90 deg: ~zero I, max +Q"),
|
|
||||||
(64, 0x1F, 0x20, "180 deg: max -I, ~zero Q"),
|
|
||||||
(96, 0x01, 0x1D, "270 deg: ~zero I, max -Q"),
|
|
||||||
]
|
|
||||||
for idx, exp_i, exp_q, desc in expectations:
|
|
||||||
if VM_I_REF[idx] != exp_i or VM_Q_REF[idx] != exp_q:
|
|
||||||
errors.append(
|
|
||||||
f"index {idx} ({desc}): expected (0x{exp_i:02X}, 0x{exp_q:02X}), "
|
|
||||||
f"got (0x{VM_I_REF[idx]:02X}, 0x{VM_Q_REF[idx]:02X})"
|
|
||||||
)
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Parse VM_I[] / VM_Q[] from firmware C++ source.
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
ARRAY_RE = re.compile(
|
|
||||||
r"const\s+uint8_t\s+ADAR1000Manager::(?P<name>VM_I|VM_Q|VM_GAIN)\s*"
|
|
||||||
r"\[\s*128\s*\]\s*=\s*\{(?P<body>[^}]*)\}\s*;",
|
|
||||||
re.DOTALL,
|
|
||||||
)
|
|
||||||
HEX_RE = re.compile(r"0[xX][0-9a-fA-F]{1,2}")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_array(source: str, name: str) -> list[int] | None:
|
|
||||||
"""Extract a 128-entry uint8_t array from C++ source by name.
|
|
||||||
|
|
||||||
Returns None if the array is not found. Returns a list (possibly shorter
|
|
||||||
than 128) of the parsed bytes if found; caller is responsible for length
|
|
||||||
validation.
|
|
||||||
|
|
||||||
LIMITATION (intentional, see PR fix/adar1000-vm-tables review finding #2):
|
|
||||||
ARRAY_RE uses `[^}]*` for the body, which terminates at the first `}`.
|
|
||||||
This is sufficient for the *flat* `const uint8_t NAME[128] = { ... };`
|
|
||||||
declarations VM_I/VM_Q use today, but it would mis-parse if the array
|
|
||||||
body ever contained nested braces (e.g. designated initialisers, struct
|
|
||||||
aggregates, or macro-expansions producing braces). If the firmware ever
|
|
||||||
needs such a form for the VM tables, replace ARRAY_RE with a balanced
|
|
||||||
brace-counting parser. Until then, the current regex is preferred for
|
|
||||||
its simplicity and the round-trip tests will catch any silent breakage.
|
|
||||||
"""
|
|
||||||
for m in ARRAY_RE.finditer(source):
|
|
||||||
if m.group("name") != name:
|
|
||||||
continue
|
|
||||||
body = m.group("body")
|
|
||||||
body = re.sub(r"//[^\n]*", "", body)
|
|
||||||
body = re.sub(r"/\*.*?\*/", "", body, flags=re.DOTALL)
|
|
||||||
return [int(tok, 16) for tok in HEX_RE.findall(body)]
|
|
||||||
return None
|
|
||||||
@@ -26,14 +26,12 @@ layers agree (because both could be wrong).
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import ClassVar
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -43,7 +41,6 @@ import sys
|
|||||||
THIS_DIR = Path(__file__).resolve().parent
|
THIS_DIR = Path(__file__).resolve().parent
|
||||||
sys.path.insert(0, str(THIS_DIR))
|
sys.path.insert(0, str(THIS_DIR))
|
||||||
import contract_parser as cp # noqa: E402
|
import contract_parser as cp # noqa: E402
|
||||||
import adar1000_vm_reference as adar_vm # noqa: E402
|
|
||||||
|
|
||||||
# Also add the GUI dir to import radar_protocol
|
# Also add the GUI dir to import radar_protocol
|
||||||
sys.path.insert(0, str(cp.GUI_DIR))
|
sys.path.insert(0, str(cp.GUI_DIR))
|
||||||
@@ -80,78 +77,6 @@ if _in_ci:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _strip_cxx_comments_and_strings(src: str) -> str:
|
|
||||||
"""Return src with all C/C++ comments and string/char literals removed.
|
|
||||||
|
|
||||||
Tokenising state machine with four states:
|
|
||||||
* CODE — default; watches for `"`, `'`, `//`, `/*`
|
|
||||||
* STRING ("...") — handles `\\"` and `\\\\` escapes
|
|
||||||
* CHAR ('...') — handles `\\'` and `\\\\` escapes
|
|
||||||
* LINE_COMMENT — until next `\\n`
|
|
||||||
* BLOCK_COMMENT — until next `*/`
|
|
||||||
|
|
||||||
Used by test_vm_gain_table_is_not_reintroduced to ensure the substring
|
|
||||||
"VM_GAIN" appearing only inside an explanatory comment or a string
|
|
||||||
literal does NOT count as code reintroduction. We replace stripped
|
|
||||||
regions with a single space so token boundaries (and line counts, by
|
|
||||||
approximation — newlines preserved) are not collapsed.
|
|
||||||
"""
|
|
||||||
out: list[str] = []
|
|
||||||
i = 0
|
|
||||||
n = len(src)
|
|
||||||
CODE, STRING, CHAR, LINE_C, BLOCK_C = 0, 1, 2, 3, 4
|
|
||||||
state = CODE
|
|
||||||
while i < n:
|
|
||||||
c = src[i]
|
|
||||||
nxt = src[i + 1] if i + 1 < n else ""
|
|
||||||
if state == CODE:
|
|
||||||
if c == "/" and nxt == "/":
|
|
||||||
state = LINE_C
|
|
||||||
i += 2
|
|
||||||
elif c == "/" and nxt == "*":
|
|
||||||
state = BLOCK_C
|
|
||||||
i += 2
|
|
||||||
elif c == '"':
|
|
||||||
state = STRING
|
|
||||||
i += 1
|
|
||||||
elif c == "'":
|
|
||||||
state = CHAR
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
out.append(c)
|
|
||||||
i += 1
|
|
||||||
elif state == STRING:
|
|
||||||
if c == "\\" and i + 1 < n:
|
|
||||||
i += 2 # skip escape pair (handles \" and \\)
|
|
||||||
elif c == '"':
|
|
||||||
state = CODE
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
i += 1
|
|
||||||
elif state == CHAR:
|
|
||||||
if c == "\\" and i + 1 < n:
|
|
||||||
i += 2
|
|
||||||
elif c == "'":
|
|
||||||
state = CODE
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
i += 1
|
|
||||||
elif state == LINE_C:
|
|
||||||
if c == "\n":
|
|
||||||
out.append("\n") # preserve line numbering
|
|
||||||
state = CODE
|
|
||||||
i += 1
|
|
||||||
elif state == BLOCK_C:
|
|
||||||
if c == "*" and nxt == "/":
|
|
||||||
state = CODE
|
|
||||||
i += 2
|
|
||||||
else:
|
|
||||||
if c == "\n":
|
|
||||||
out.append("\n")
|
|
||||||
i += 1
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_hex_results(text: str) -> list[dict[str, str]]:
|
def _parse_hex_results(text: str) -> list[dict[str, str]]:
|
||||||
"""Parse space-separated hex lines from TB output files."""
|
"""Parse space-separated hex lines from TB output files."""
|
||||||
rows = []
|
rows = []
|
||||||
@@ -566,7 +491,7 @@ class TestTier1AgcCrossLayerInvariant:
|
|||||||
MCU must apply a 2-frame confirmation debounce before mutating
|
MCU must apply a 2-frame confirmation debounce before mutating
|
||||||
outerAgc.enabled from DIG_6 reads. A naive assignment straight from
|
outerAgc.enabled from DIG_6 reads. A naive assignment straight from
|
||||||
the latest GPIO sample would let a single-cycle glitch flip the AGC
|
the latest GPIO sample would let a single-cycle glitch flip the AGC
|
||||||
state for one frame — defeating the debounce claim in the PR body.
|
state for one frame.
|
||||||
"""
|
"""
|
||||||
main_cpp = (cp.MCU_CODE_DIR / "main.cpp").read_text()
|
main_cpp = (cp.MCU_CODE_DIR / "main.cpp").read_text()
|
||||||
|
|
||||||
@@ -627,420 +552,6 @@ class TestTier1AgcCrossLayerInvariant:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ===================================================================
|
|
||||||
# ADAR1000 channel→register round-trip invariant (issue #90)
|
|
||||||
# ===================================================================
|
|
||||||
#
|
|
||||||
# Ground-truth invariant crossing three system layers:
|
|
||||||
# Chip (datasheet) -> Driver (MCU helpers) -> Application (callers).
|
|
||||||
#
|
|
||||||
# For every logical element ch in {0,1,2,3} (hardware channels CH1..CH4),
|
|
||||||
# the round-trip
|
|
||||||
# caller_expr(ch) --> helper_offset(channel) * stride --> base + off
|
|
||||||
# must land on the physical register REG_CH{ch+1}_* defined in the ADI
|
|
||||||
# ADAR1000 register map parsed from ADAR1000_Manager.h.
|
|
||||||
#
|
|
||||||
# Catches:
|
|
||||||
# * #90 channel rotation regardless of which side is fixed (caller OR helper).
|
|
||||||
# * Wrong stride (e.g. phase written with stride 1 instead of 2).
|
|
||||||
# * Bad mask (e.g. `channel & 0x07`, `channel & 0x01`).
|
|
||||||
# * Wrong base register in a helper.
|
|
||||||
# * New setter added with mismatched convention.
|
|
||||||
# * Caller moved to a file the test no longer scans (fails loudly).
|
|
||||||
#
|
|
||||||
# Cannot be defeated by:
|
|
||||||
# * Renaming/refactoring helper layout: the setter coverage test
|
|
||||||
# (`test_helper_sites_exist_for_all_setters`) catches missing parse.
|
|
||||||
# * Changing 0x03 to 3 or adding a named constant: the offset is
|
|
||||||
# evaluated symbolically via AST, not matched by regex.
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_adar_register_map(header_text):
|
|
||||||
"""Extract `#define REG_CHn_(RX|TX)_(GAIN|PHS_I|PHS_Q)` values."""
|
|
||||||
regs = {}
|
|
||||||
for m in re.finditer(
|
|
||||||
r"^#define\s+(REG_CH[1-4]_(?:RX|TX)_(?:GAIN|PHS_I|PHS_Q))\s+(0x[0-9A-Fa-f]+)",
|
|
||||||
header_text,
|
|
||||||
re.MULTILINE,
|
|
||||||
):
|
|
||||||
regs[m.group(1)] = int(m.group(2), 16)
|
|
||||||
return regs
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_eval_int_expr(expr, **variables):
|
|
||||||
"""
|
|
||||||
Evaluate a small integer expression with +, -, *, &, |, ^, ~, <<, >>.
|
|
||||||
Python's & / | / ^ / ~ / << / >> have the same semantics as C for the
|
|
||||||
operand widths we care about here (uint8_t after the mask makes the
|
|
||||||
result fit in 0..3). No floating point, no function calls, no names
|
|
||||||
outside ``variables``.
|
|
||||||
|
|
||||||
SECURITY: ``expr`` MUST come from a trusted source -- specifically,
|
|
||||||
C/C++ source text under version control in this repository (e.g.
|
|
||||||
arguments parsed out of ``main.cpp``/``ADAR1000_AGC.cpp``). Although
|
|
||||||
the AST whitelist below rejects function calls, attribute access,
|
|
||||||
subscripts, and any name not in ``variables``, ``eval`` is still
|
|
||||||
invoked on the compiled tree. Do NOT pass user-supplied / network /
|
|
||||||
GUI input here.
|
|
||||||
"""
|
|
||||||
tree = ast.parse(expr, mode="eval")
|
|
||||||
allowed = (
|
|
||||||
ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant,
|
|
||||||
ast.Name, ast.Load,
|
|
||||||
ast.Add, ast.Sub, ast.Mult, ast.Mod, ast.FloorDiv,
|
|
||||||
ast.BitAnd, ast.BitOr, ast.BitXor,
|
|
||||||
ast.USub, ast.UAdd, ast.Invert,
|
|
||||||
ast.LShift, ast.RShift,
|
|
||||||
)
|
|
||||||
for node in ast.walk(tree):
|
|
||||||
if not isinstance(node, allowed):
|
|
||||||
raise ValueError(
|
|
||||||
f"disallowed AST node {type(node).__name__!s} in `{expr}`"
|
|
||||||
)
|
|
||||||
return eval(
|
|
||||||
compile(tree, "<expr>", "eval"),
|
|
||||||
{"__builtins__": {}},
|
|
||||||
variables,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_adar_helper_sites(manager_cpp, setter_names):
|
|
||||||
"""
|
|
||||||
For each setter, locate the body of ``void ADAR1000Manager::<setter>``
|
|
||||||
and return a list of (setter, base_register, offset_expr_c, stride)
|
|
||||||
for every ``REG_CHn_XXX + <expr>`` memory-address assignment.
|
|
||||||
"""
|
|
||||||
sites = []
|
|
||||||
for setter in setter_names:
|
|
||||||
m = re.search(
|
|
||||||
rf"void\s+ADAR1000Manager::{setter}\s*\([^)]*\)\s*\{{(.+?)^\}}",
|
|
||||||
manager_cpp,
|
|
||||||
re.MULTILINE | re.DOTALL,
|
|
||||||
)
|
|
||||||
if not m:
|
|
||||||
continue
|
|
||||||
body = m.group(1)
|
|
||||||
for access in re.finditer(
|
|
||||||
r"=\s*(REG_CH[1-4]_(?:RX|TX)_(?:GAIN|PHS_I|PHS_Q))\s*\+\s*([^;]+);",
|
|
||||||
body,
|
|
||||||
):
|
|
||||||
base = access.group(1)
|
|
||||||
rhs = access.group(2).strip()
|
|
||||||
# Trailing `* <integer>` = stride multiplier (2 for phase I/Q).
|
|
||||||
stride_match = re.match(r"(.+?)\s*\*\s*(\d+)\s*$", rhs)
|
|
||||||
if stride_match:
|
|
||||||
offset_expr = stride_match.group(1).strip()
|
|
||||||
stride = int(stride_match.group(2))
|
|
||||||
else:
|
|
||||||
offset_expr = rhs
|
|
||||||
stride = 1
|
|
||||||
sites.append((setter, base, offset_expr, stride))
|
|
||||||
return sites
|
|
||||||
|
|
||||||
|
|
||||||
# Method-definition line pattern: `[qualifier...] <ret-type> <Class>::<setter>(`
|
|
||||||
# Covers: plain `void X::f(`, `inline void X::f(`, `static bool X::f(`, etc.
|
|
||||||
_DEFN_RE = re.compile(
|
|
||||||
r"^\s*(?:inline\s+|static\s+|virtual\s+|constexpr\s+|explicit\s+)*"
|
|
||||||
r"(?:void|bool|uint\w+|int\w*|auto)\s+\S+::\w+\s*\("
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_adar_caller_sites(sources, setter):
|
|
||||||
"""
|
|
||||||
Find every call ``<obj>.<setter>(dev, <channel_expr>, ...)`` across
|
|
||||||
``sources = [(filename, text), ...]``. Returns (filename, line_no,
|
|
||||||
channel_expr) for each. Skips function declarations/definitions.
|
|
||||||
|
|
||||||
Arg list up to matching `)`: restricted to a single line. All existing
|
|
||||||
call sites fit on one line; a future multi-line refactor would drop
|
|
||||||
callers from the scan, which the round-trip test surfaces loudly via
|
|
||||||
`assert callers` (rather than silently missing a site).
|
|
||||||
"""
|
|
||||||
out = []
|
|
||||||
call_re = re.compile(rf"\b{setter}\s*\(([^;]*?)\)\s*;")
|
|
||||||
for filename, text in sources:
|
|
||||||
for line_no, line in enumerate(text.splitlines(), start=1):
|
|
||||||
# Skip method definition / declaration lines.
|
|
||||||
if _DEFN_RE.match(line):
|
|
||||||
continue
|
|
||||||
cm = call_re.search(line)
|
|
||||||
if not cm:
|
|
||||||
continue
|
|
||||||
args = _split_top_level_commas(cm.group(1))
|
|
||||||
if len(args) < 2:
|
|
||||||
continue
|
|
||||||
channel_expr = args[1].strip()
|
|
||||||
out.append((filename, line_no, channel_expr))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def _split_top_level_commas(text):
|
|
||||||
"""Split on commas that sit at paren-depth 0 (ignores nested calls)."""
|
|
||||||
parts, depth, cur = [], 0, []
|
|
||||||
for ch in text:
|
|
||||||
if ch == "(":
|
|
||||||
depth += 1
|
|
||||||
cur.append(ch)
|
|
||||||
elif ch == ")":
|
|
||||||
depth -= 1
|
|
||||||
cur.append(ch)
|
|
||||||
elif ch == "," and depth == 0:
|
|
||||||
parts.append("".join(cur))
|
|
||||||
cur = []
|
|
||||||
else:
|
|
||||||
cur.append(ch)
|
|
||||||
if cur:
|
|
||||||
parts.append("".join(cur))
|
|
||||||
return parts
|
|
||||||
|
|
||||||
|
|
||||||
class TestTier1Adar1000ChannelRegisterRoundTrip:
|
|
||||||
"""
|
|
||||||
Cross-layer round-trip: caller channel expr -> helper offset formula
|
|
||||||
-> physical register address must equal REG_CH{ch+1}_* for every
|
|
||||||
caller and every ch in {0,1,2,3}.
|
|
||||||
|
|
||||||
See module-level block comment above and upstream issue #90.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_SETTERS = (
|
|
||||||
"adarSetRxPhase",
|
|
||||||
"adarSetTxPhase",
|
|
||||||
"adarSetRxVgaGain",
|
|
||||||
"adarSetTxVgaGain",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Register base -> stride override. Parsed values of stride are
|
|
||||||
# trusted; this table is the independent ground truth for cross-check.
|
|
||||||
_EXPECTED_STRIDE: ClassVar[dict[str, int]] = {
|
|
||||||
"REG_CH1_RX_GAIN": 1,
|
|
||||||
"REG_CH1_TX_GAIN": 1,
|
|
||||||
"REG_CH1_RX_PHS_I": 2,
|
|
||||||
"REG_CH1_RX_PHS_Q": 2,
|
|
||||||
"REG_CH1_TX_PHS_I": 2,
|
|
||||||
"REG_CH1_TX_PHS_Q": 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setup_class(cls):
|
|
||||||
cls.header_txt = (cp.MCU_LIB_DIR / "ADAR1000_Manager.h").read_text()
|
|
||||||
cls.manager_txt = (cp.MCU_LIB_DIR / "ADAR1000_Manager.cpp").read_text()
|
|
||||||
cls.reg_map = _parse_adar_register_map(cls.header_txt)
|
|
||||||
cls.helper_sites = _extract_adar_helper_sites(
|
|
||||||
cls.manager_txt, cls._SETTERS,
|
|
||||||
)
|
|
||||||
# Auto-discover every C++ TU under the MCU tree so a new caller
|
|
||||||
# added to e.g. a future ``ADAR1000_Calibration.cpp`` cannot
|
|
||||||
# silently escape the round-trip check (issue #90 reviewer note).
|
|
||||||
# Exclude any path containing a ``tests`` segment so this test
|
|
||||||
# does not parse its own fixtures. The resulting list is
|
|
||||||
# deterministic (sorted) for reproducible parametrization.
|
|
||||||
scanned = []
|
|
||||||
seen = set()
|
|
||||||
for root in (cp.MCU_LIB_DIR, cp.MCU_CODE_DIR):
|
|
||||||
for path in sorted(root.rglob("*.cpp")):
|
|
||||||
if "tests" in path.parts:
|
|
||||||
continue
|
|
||||||
if path in seen:
|
|
||||||
continue
|
|
||||||
seen.add(path)
|
|
||||||
scanned.append((path.name, path.read_text()))
|
|
||||||
cls.sources = scanned
|
|
||||||
# Sanity: the two TUs known to call ADAR1000 setters at the time
|
|
||||||
# of issue #90 must be in scope. If a future refactor renames or
|
|
||||||
# moves them this assert fires loudly rather than silently
|
|
||||||
# passing an empty round-trip.
|
|
||||||
scanned_names = {n for (n, _) in scanned}
|
|
||||||
for required in ("ADAR1000_AGC.cpp", "main.cpp", "ADAR1000_Manager.cpp"):
|
|
||||||
assert required in scanned_names, (
|
|
||||||
f"Auto-discovery missed `{required}`; check MCU_LIB_DIR / "
|
|
||||||
f"MCU_CODE_DIR roots in contract_parser.py."
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------- Tier A: chip ground truth ----------------------------
|
|
||||||
|
|
||||||
def test_register_map_gain_stride_is_one_per_channel(self):
|
|
||||||
"""Datasheet invariant: RX/TX VGA gain registers are 1 byte apart."""
|
|
||||||
for kind in ("RX_GAIN", "TX_GAIN"):
|
|
||||||
for n in range(1, 4):
|
|
||||||
delta = (
|
|
||||||
self.reg_map[f"REG_CH{n+1}_{kind}"]
|
|
||||||
- self.reg_map[f"REG_CH{n}_{kind}"]
|
|
||||||
)
|
|
||||||
assert delta == 1, (
|
|
||||||
f"ADAR1000 register map invariant broken: "
|
|
||||||
f"REG_CH{n+1}_{kind} - REG_CH{n}_{kind} = {delta}, "
|
|
||||||
f"datasheet says 1. Either the header was mis-edited "
|
|
||||||
f"or ADI released a part with a different map."
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_register_map_phase_stride_is_two_per_channel(self):
|
|
||||||
"""Datasheet invariant: phase I/Q pairs occupy 2 bytes per channel."""
|
|
||||||
for kind in ("RX_PHS_I", "RX_PHS_Q", "TX_PHS_I", "TX_PHS_Q"):
|
|
||||||
for n in range(1, 4):
|
|
||||||
delta = (
|
|
||||||
self.reg_map[f"REG_CH{n+1}_{kind}"]
|
|
||||||
- self.reg_map[f"REG_CH{n}_{kind}"]
|
|
||||||
)
|
|
||||||
assert delta == 2, (
|
|
||||||
f"ADAR1000 register map invariant broken: "
|
|
||||||
f"REG_CH{n+1}_{kind} - REG_CH{n}_{kind} = {delta}, "
|
|
||||||
f"datasheet says 2."
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------- Tier B: driver parses cleanly -------------------------
|
|
||||||
|
|
||||||
def test_helper_sites_exist_for_all_setters(self):
|
|
||||||
"""Every channel-indexed setter must parse at least one register access."""
|
|
||||||
found = {s for (s, _, _, _) in self.helper_sites}
|
|
||||||
missing = set(self._SETTERS) - found
|
|
||||||
assert not missing, (
|
|
||||||
f"Helper parse failed for: {sorted(missing)}. "
|
|
||||||
f"Either a setter was renamed (update _SETTERS), moved out of "
|
|
||||||
f"ADAR1000_Manager.cpp (extend scan scope), or the register-"
|
|
||||||
f"access form changed beyond `REG_CHn_XXX + <expr>`. "
|
|
||||||
f"DO NOT weaken this test without reviewing issue #90."
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_helper_parsed_stride_matches_datasheet(self):
|
|
||||||
"""Parsed helper strides must match the datasheet register spacing."""
|
|
||||||
for setter, base, offset_expr, stride in self.helper_sites:
|
|
||||||
expected = self._EXPECTED_STRIDE.get(base)
|
|
||||||
assert expected is not None, (
|
|
||||||
f"{setter} writes to unrecognised base `{base}`. "
|
|
||||||
f"If ADI added a new channel-indexed register block, "
|
|
||||||
f"extend _EXPECTED_STRIDE with its datasheet stride."
|
|
||||||
)
|
|
||||||
assert stride == expected, (
|
|
||||||
f"{setter} helper uses stride {stride} for `{base}` "
|
|
||||||
f"(`{offset_expr} * {stride}`), datasheet says {expected}. "
|
|
||||||
f"Writes will overlap or skip channels."
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------- Tier C: round-trip to physical register ---------------
|
|
||||||
|
|
||||||
def test_all_callers_pass_one_based_channel(self):
|
|
||||||
"""
|
|
||||||
INVARIANT: every caller's channel argument must, for ch in
|
|
||||||
{0,1,2,3}, evaluate to a 1-based ADI channel index in {1,2,3,4}.
|
|
||||||
|
|
||||||
The bug fixed in #90 was that helpers used ``channel & 0x03``
|
|
||||||
directly, so a caller passing bare ``ch`` (0..3) appeared to
|
|
||||||
work for ch=0..2 and silently aliased ch=3 onto CH4-then-CH1.
|
|
||||||
After the fix, helpers do ``(channel - 1) & 0x03`` and reject
|
|
||||||
``channel < 1 || channel > 4``. A future caller written as
|
|
||||||
``adarSetRxPhase(dev, ch, ...)`` (bare 0-based) or
|
|
||||||
``adarSetRxPhase(dev, 0, ...)`` (literal 0) would silently be
|
|
||||||
dropped by the bounds-check at runtime; this test catches it at
|
|
||||||
CI time instead.
|
|
||||||
|
|
||||||
The check intentionally lives one tier above the round-trip test
|
|
||||||
so the failure message points the reader at the API contract
|
|
||||||
(1-based per ADI datasheet & ADAR1000_AGC.cpp:76) rather than at
|
|
||||||
a register-arithmetic mismatch.
|
|
||||||
"""
|
|
||||||
offenders = []
|
|
||||||
for setter in self._SETTERS:
|
|
||||||
callers = _extract_adar_caller_sites(self.sources, setter)
|
|
||||||
for filename, line_no, ch_expr in callers:
|
|
||||||
for ch in range(4):
|
|
||||||
try:
|
|
||||||
channel_val = _safe_eval_int_expr(ch_expr, ch=ch)
|
|
||||||
except (NameError, KeyError, ValueError) as e:
|
|
||||||
offenders.append(
|
|
||||||
f" - {filename}:{line_no} {setter}("
|
|
||||||
f"…, `{ch_expr}`, …) -- ch={ch}: "
|
|
||||||
f"unparseable ({e})"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
if channel_val not in (1, 2, 3, 4):
|
|
||||||
offenders.append(
|
|
||||||
f" - {filename}:{line_no} {setter}("
|
|
||||||
f"…, `{ch_expr}`, …) -- ch={ch}: "
|
|
||||||
f"channel={channel_val}, expected 1..4"
|
|
||||||
)
|
|
||||||
assert not offenders, (
|
|
||||||
"ADAR1000 1-based channel API contract violated. The fix "
|
|
||||||
"for issue #90 requires every caller to pass channel in "
|
|
||||||
"{1,2,3,4} (CH1..CH4 per ADI datasheet). Bare 0-based ch "
|
|
||||||
"or a literal 0 will be silently dropped by the helper's "
|
|
||||||
"bounds check. Offenders:\n" + "\n".join(offenders)
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"setter",
|
|
||||||
[
|
|
||||||
"adarSetRxPhase",
|
|
||||||
"adarSetTxPhase",
|
|
||||||
"adarSetRxVgaGain",
|
|
||||||
"adarSetTxVgaGain",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_round_trip_lands_on_intended_physical_channel(self, setter):
|
|
||||||
"""
|
|
||||||
INVARIANT: for every caller of ``<setter>`` and every logical ch
|
|
||||||
in {0,1,2,3}, the effective register address equals
|
|
||||||
REG_CH{ch+1}_*. Catches #90 regardless of fix direction.
|
|
||||||
"""
|
|
||||||
callers = _extract_adar_caller_sites(self.sources, setter)
|
|
||||||
assert callers, (
|
|
||||||
f"No callers of `{setter}` found. Either the test scope is "
|
|
||||||
f"incomplete (extend `setup_class.sources`) or the symbol was "
|
|
||||||
f"inlined/removed. A blind test is a dangerous test — "
|
|
||||||
f"investigate before weakening."
|
|
||||||
)
|
|
||||||
helpers = [
|
|
||||||
(b, e, s) for (nm, b, e, s) in self.helper_sites if nm == setter
|
|
||||||
]
|
|
||||||
assert helpers, f"helper body for `{setter}` not parseable"
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
for filename, line_no, ch_expr in callers:
|
|
||||||
for ch in range(4):
|
|
||||||
try:
|
|
||||||
channel_val = _safe_eval_int_expr(ch_expr, ch=ch)
|
|
||||||
except (NameError, KeyError, ValueError) as e:
|
|
||||||
pytest.fail(
|
|
||||||
f"{filename}:{line_no}: caller channel expression "
|
|
||||||
f"`{ch_expr}` uses symbol outside {{ch}} or a "
|
|
||||||
f"disallowed operator ({e}). Extend "
|
|
||||||
f"_safe_eval_int_expr variables or rewrite the "
|
|
||||||
f"call site with a supported expression."
|
|
||||||
)
|
|
||||||
for base_sym, offset_expr, stride in helpers:
|
|
||||||
try:
|
|
||||||
offset = _safe_eval_int_expr(
|
|
||||||
offset_expr, channel=channel_val,
|
|
||||||
)
|
|
||||||
except (NameError, KeyError, ValueError) as e:
|
|
||||||
pytest.fail(
|
|
||||||
f"helper `{setter}` offset expr "
|
|
||||||
f"`{offset_expr}` uses symbol outside "
|
|
||||||
f"{{channel}} or a disallowed operator ({e}). "
|
|
||||||
f"Extend _safe_eval_int_expr variables if new "
|
|
||||||
f"driver state is introduced."
|
|
||||||
)
|
|
||||||
final = self.reg_map[base_sym] + offset * stride
|
|
||||||
expected_sym = base_sym.replace("CH1", f"CH{ch + 1}")
|
|
||||||
expected = self.reg_map[expected_sym]
|
|
||||||
if final != expected:
|
|
||||||
errors.append(
|
|
||||||
f" - {filename}:{line_no} {setter} "
|
|
||||||
f"caller `{ch_expr}` | ch={ch} -> "
|
|
||||||
f"channel={channel_val} -> "
|
|
||||||
f"`{base_sym} + ({offset_expr})"
|
|
||||||
f"{' * ' + str(stride) if stride != 1 else ''}`"
|
|
||||||
f" = 0x{final:03X} "
|
|
||||||
f"(expected {expected_sym} = 0x{expected:03X})"
|
|
||||||
)
|
|
||||||
assert not errors, (
|
|
||||||
f"ADAR1000 channel round-trip FAILED for {setter} "
|
|
||||||
f"({len(errors)} mismatches) — writes routed to wrong physical "
|
|
||||||
f"channel. This is issue #90.\n" + "\n".join(errors)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTier1DataPacketLayout:
|
class TestTier1DataPacketLayout:
|
||||||
"""Verify data packet byte layout matches between Python and Verilog."""
|
"""Verify data packet byte layout matches between Python and Verilog."""
|
||||||
|
|
||||||
@@ -1154,204 +665,6 @@ class TestTier1STM32SettingsPacket:
|
|||||||
assert flag == [23, 46, 158, 237], f"Start flag: {flag}"
|
assert flag == [23, 46, 158, 237], f"Start flag: {flag}"
|
||||||
|
|
||||||
|
|
||||||
# ===================================================================
|
|
||||||
# TIER 2: ADAR1000 Vector Modulator Lookup-Table Ground Truth
|
|
||||||
# ===================================================================
|
|
||||||
#
|
|
||||||
# Cross-layer contract: the firmware constants
|
|
||||||
# ADAR1000Manager::VM_I[128] / VM_Q[128]
|
|
||||||
# (in 9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.cpp)
|
|
||||||
# MUST equal the byte values published in the ADAR1000 datasheet Rev. B,
|
|
||||||
# Tables 13-16 page 34 ("Phase Shifter Programming"), on a uniform 2.8125 deg
|
|
||||||
# grid (index N == phase N * 360/128 deg).
|
|
||||||
#
|
|
||||||
# Independent ground truth lives in tools/verify_adar1000_vm_tables.py
|
|
||||||
# (transcribed from the datasheet, cross-checked against the ADI Linux
|
|
||||||
# beamformer driver as a secondary source). This test imports that
|
|
||||||
# reference and asserts a byte-exact match.
|
|
||||||
#
|
|
||||||
# Historical bug guarded against: from initial commit through PR #94 the
|
|
||||||
# arrays shipped as empty placeholders ("// ... (same as in your original
|
|
||||||
# file)"), so every adarSetRxPhase / adarSetTxPhase call wrote I=Q=0 and
|
|
||||||
# beam steering was non-functional. A separate VM_GAIN[128] table was
|
|
||||||
# declared but never read anywhere; this test also enforces its removal so
|
|
||||||
# it cannot be reintroduced and silently shadow real bugs.
|
|
||||||
|
|
||||||
class TestTier2Adar1000VmTableGroundTruth:
|
|
||||||
"""Firmware ADAR1000 VM_I/VM_Q must match datasheet ground truth byte-exact."""
|
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
|
||||||
def cpp_source(self):
|
|
||||||
path = (
|
|
||||||
cp.REPO_ROOT
|
|
||||||
/ "9_Firmware"
|
|
||||||
/ "9_1_Microcontroller"
|
|
||||||
/ "9_1_1_C_Cpp_Libraries"
|
|
||||||
/ "ADAR1000_Manager.cpp"
|
|
||||||
)
|
|
||||||
assert path.is_file(), f"Firmware source missing: {path}"
|
|
||||||
return path.read_text()
|
|
||||||
|
|
||||||
def test_ground_truth_table_shape(self):
|
|
||||||
"""Sanity-check the imported reference (defends against import-path mishap)."""
|
|
||||||
gt = adar_vm.GROUND_TRUTH
|
|
||||||
assert len(gt) == 128, "Ground-truth table must have exactly 128 entries"
|
|
||||||
# Each row is (deg_int, deg_frac_e4, vm_i_byte, vm_q_byte)
|
|
||||||
for k, row in enumerate(gt):
|
|
||||||
assert len(row) == 4, f"Row {k} malformed: {row}"
|
|
||||||
assert 0 <= row[2] <= 0xFF, f"VM_I[{k}] out of byte range: {row[2]:#x}"
|
|
||||||
assert 0 <= row[3] <= 0xFF, f"VM_Q[{k}] out of byte range: {row[3]:#x}"
|
|
||||||
# Byte format: bits[7:6] reserved zero, bits[5] polarity, bits[4:0] mag
|
|
||||||
assert (row[2] & 0xC0) == 0, f"VM_I[{k}] reserved bits set: {row[2]:#x}"
|
|
||||||
assert (row[3] & 0xC0) == 0, f"VM_Q[{k}] reserved bits set: {row[3]:#x}"
|
|
||||||
|
|
||||||
def test_ground_truth_byte_format(self):
|
|
||||||
"""Transcription self-check: every VM_I/VM_Q byte has reserved bits clear."""
|
|
||||||
errors = adar_vm.check_byte_format("VM_I_REF", adar_vm.VM_I_REF)
|
|
||||||
errors += adar_vm.check_byte_format("VM_Q_REF", adar_vm.VM_Q_REF)
|
|
||||||
assert not errors, (
|
|
||||||
"Byte-format violations in embedded GROUND_TRUTH (likely transcription "
|
|
||||||
"typo from ADAR1000 datasheet Tables 13-16):\n " + "\n ".join(errors)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ground_truth_uniform_2p8125_deg_grid(self):
|
|
||||||
"""Transcription self-check: angles form a uniform 2.8125 deg grid.
|
|
||||||
|
|
||||||
This is the assumption that lets the firmware use `VM_*[phase % 128]`
|
|
||||||
as a direct index (no nearest-neighbour search). If the embedded
|
|
||||||
angles drift off the grid, the firmware's indexing model is wrong.
|
|
||||||
"""
|
|
||||||
errors = adar_vm.check_uniform_2p8125_deg_step()
|
|
||||||
assert not errors, (
|
|
||||||
"Non-uniform angle grid in GROUND_TRUTH:\n " + "\n ".join(errors)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ground_truth_quadrant_symmetry(self):
|
|
||||||
"""Transcription self-check: phi and phi+180 deg have same magnitude,
|
|
||||||
opposite polarity. Catches swapped/rotated rows in the table.
|
|
||||||
"""
|
|
||||||
errors = adar_vm.check_quadrant_symmetry()
|
|
||||||
assert not errors, (
|
|
||||||
"Quadrant-symmetry violation in GROUND_TRUTH (table rows may be "
|
|
||||||
"transposed or mis-transcribed):\n " + "\n ".join(errors)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ground_truth_cardinal_points(self):
|
|
||||||
"""Transcription self-check: the four cardinal phases (0, 90, 180,
|
|
||||||
270 deg) match the datasheet-published extrema exactly.
|
|
||||||
"""
|
|
||||||
errors = adar_vm.check_cardinal_points()
|
|
||||||
assert not errors, (
|
|
||||||
"Cardinal-point mismatch in GROUND_TRUTH vs ADAR1000 datasheet "
|
|
||||||
"Tables 13-16:\n " + "\n ".join(errors)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_firmware_vm_i_matches_datasheet(self, cpp_source):
|
|
||||||
gt = adar_vm.GROUND_TRUTH
|
|
||||||
firmware = adar_vm.parse_array(cpp_source, "VM_I")
|
|
||||||
assert firmware is not None, (
|
|
||||||
"Could not parse VM_I[128] from ADAR1000_Manager.cpp; "
|
|
||||||
"definition pattern may have drifted"
|
|
||||||
)
|
|
||||||
assert len(firmware) == 128, (
|
|
||||||
f"VM_I has {len(firmware)} entries, expected 128. "
|
|
||||||
"Empty placeholder regression — every phase write would emit I=0 "
|
|
||||||
"and beam steering would be silently broken."
|
|
||||||
)
|
|
||||||
mismatches = [
|
|
||||||
(k, firmware[k], gt[k][2])
|
|
||||||
for k in range(128)
|
|
||||||
if firmware[k] != gt[k][2]
|
|
||||||
]
|
|
||||||
assert not mismatches, (
|
|
||||||
f"VM_I diverges from datasheet at {len(mismatches)} indices; "
|
|
||||||
f"first 5: {mismatches[:5]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_firmware_vm_q_matches_datasheet(self, cpp_source):
|
|
||||||
gt = adar_vm.GROUND_TRUTH
|
|
||||||
firmware = adar_vm.parse_array(cpp_source, "VM_Q")
|
|
||||||
assert firmware is not None, (
|
|
||||||
"Could not parse VM_Q[128] from ADAR1000_Manager.cpp; "
|
|
||||||
"definition pattern may have drifted"
|
|
||||||
)
|
|
||||||
assert len(firmware) == 128, (
|
|
||||||
f"VM_Q has {len(firmware)} entries, expected 128. "
|
|
||||||
"Empty placeholder regression — every phase write would emit Q=0."
|
|
||||||
)
|
|
||||||
mismatches = [
|
|
||||||
(k, firmware[k], gt[k][3])
|
|
||||||
for k in range(128)
|
|
||||||
if firmware[k] != gt[k][3]
|
|
||||||
]
|
|
||||||
assert not mismatches, (
|
|
||||||
f"VM_Q diverges from datasheet at {len(mismatches)} indices; "
|
|
||||||
f"first 5: {mismatches[:5]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_vm_gain_table_is_not_reintroduced(self, cpp_source):
|
|
||||||
"""Dead-code regression guard: VM_GAIN[128] must not exist as code.
|
|
||||||
|
|
||||||
The ADAR1000 vector modulator has no separate gain register; magnitude
|
|
||||||
is bits[4:0] of the I/Q bytes themselves. Per-channel VGA gain uses
|
|
||||||
registers CHx_RX_GAIN (0x10-0x13) / CHx_TX_GAIN (0x1C-0x1F) written
|
|
||||||
directly by adarSetRxVgaGain / adarSetTxVgaGain. A VM_GAIN[] array
|
|
||||||
was declared in early development, never populated, never read, and
|
|
||||||
was removed in PR fix/adar1000-vm-tables. Reintroducing it would
|
|
||||||
suggest (falsely) that an extra lookup is needed and could mask the
|
|
||||||
real signal path.
|
|
||||||
|
|
||||||
Uses a tokenising comment/string stripper so that the historical
|
|
||||||
explanation comment in the cpp file, as well as any string literal
|
|
||||||
containing the substring "VM_GAIN", does not trip the check.
|
|
||||||
"""
|
|
||||||
stripped = _strip_cxx_comments_and_strings(cpp_source)
|
|
||||||
assert "VM_GAIN" not in stripped, (
|
|
||||||
"VM_GAIN symbol reappeared in ADAR1000_Manager.cpp executable code. "
|
|
||||||
"This array has no hardware backing and must not be reintroduced. "
|
|
||||||
"If you need to scale phase-state magnitude, modify VM_I/VM_Q "
|
|
||||||
"bits[4:0] directly per the datasheet."
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_adversarial_corruption_is_detected(self):
|
|
||||||
"""Adversarial self-test: a flipped byte in firmware MUST fail comparison.
|
|
||||||
|
|
||||||
Defends against silent bypass — e.g. a future refactor that mocks
|
|
||||||
parse_array() or compares len() only. We synthesise a corrupted cpp
|
|
||||||
source string, run the same parser, and assert mismatch is detected.
|
|
||||||
"""
|
|
||||||
gt = adar_vm.GROUND_TRUTH
|
|
||||||
# Build a minimal valid-looking cpp snippet with one corrupted byte.
|
|
||||||
good_i = ", ".join(f"0x{gt[k][2]:02X}" for k in range(128))
|
|
||||||
good_q = ", ".join(f"0x{gt[k][3]:02X}" for k in range(128))
|
|
||||||
snippet_good = (
|
|
||||||
f"const uint8_t ADAR1000Manager::VM_I[128] = {{ {good_i} }};\n"
|
|
||||||
f"const uint8_t ADAR1000Manager::VM_Q[128] = {{ {good_q} }};\n"
|
|
||||||
)
|
|
||||||
# Sanity: the unmodified snippet must parse and match.
|
|
||||||
parsed_i = adar_vm.parse_array(snippet_good, "VM_I")
|
|
||||||
assert parsed_i is not None and len(parsed_i) == 128
|
|
||||||
assert all(parsed_i[k] == gt[k][2] for k in range(128)), (
|
|
||||||
"Self-test setup error: golden snippet does not match GROUND_TRUTH"
|
|
||||||
)
|
|
||||||
# Now flip the low bit of VM_I[42] and confirm detection.
|
|
||||||
corrupted_byte = gt[42][2] ^ 0x01
|
|
||||||
bad_i = ", ".join(
|
|
||||||
f"0x{(corrupted_byte if k == 42 else gt[k][2]):02X}"
|
|
||||||
for k in range(128)
|
|
||||||
)
|
|
||||||
snippet_bad = (
|
|
||||||
f"const uint8_t ADAR1000Manager::VM_I[128] = {{ {bad_i} }};\n"
|
|
||||||
f"const uint8_t ADAR1000Manager::VM_Q[128] = {{ {good_q} }};\n"
|
|
||||||
)
|
|
||||||
parsed_bad = adar_vm.parse_array(snippet_bad, "VM_I")
|
|
||||||
assert parsed_bad is not None and len(parsed_bad) == 128
|
|
||||||
assert parsed_bad[42] != gt[42][2], (
|
|
||||||
"Adversarial self-test FAILED: corrupted byte at index 42 was "
|
|
||||||
"not detected by parse_array. The cross-layer test is bypassable."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
# TIER 2: Verilog Cosimulation
|
# TIER 2: Verilog Cosimulation
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|||||||
@@ -68,13 +68,13 @@ The AERIS-10 main sub-systems are:
|
|||||||
- Clock Generator (AD9523-1)
|
- Clock Generator (AD9523-1)
|
||||||
- 2x Frequency Synthesizers (ADF4382)
|
- 2x Frequency Synthesizers (ADF4382)
|
||||||
- 4x 4-Channel Phase Shifters (ADAR1000) for RADAR pulse sequencing
|
- 4x 4-Channel Phase Shifters (ADAR1000) for RADAR pulse sequencing
|
||||||
- 2x ADS7830 8-channel I²C ADCs (Main Board, U88 @ 0x48 / U89 @ 0x4A) for 16x Idq measurement, one per PA channel, each sensed through a 5 mΩ shunt on the PA board and an INA241A3 current-sense amplifier (x50) on the Main Board
|
- 2x ADS7830 ADCs (on Power Amplifier Boards) for Idq measurement
|
||||||
- 2x DAC5578 8-channel I²C DACs (Main Board, U7 @ 0x48 / U69 @ 0x49) for 16x Vg control, one per PA channel; closed-loop calibrated at boot to the target Idq
|
- 2x DAC5578 (on Power Amplifier Boards) for Vg control
|
||||||
- GPS module (UM982) for GUI map centering and per-detection position tagging
|
- GPS module for GUI map centering
|
||||||
- GY-85 IMU for pitch/roll correction of target coordinates
|
- GY-85 IMU for pitch/roll correction of target coordinates
|
||||||
- BMP180 Barometer
|
- BMP180 Barometer
|
||||||
- Stepper Motor
|
- Stepper Motor
|
||||||
- 1x ADS7830 8-channel I²C ADC (Main Board, U10) reading 8 thermistors for thermal monitoring; a single GPIO (EN_DIS_COOLING) switches the cooling fans on when any channel exceeds the threshold
|
- 8x ADS7830 Temperature Sensors for cooling fan control
|
||||||
- RF switches
|
- RF switches
|
||||||
|
|
||||||
- **16x Power Amplifier Boards** - Used only for AERIS-10E version, featuring 10Watt QPA2962 GaN amplifier for extended range
|
- **16x Power Amplifier Boards** - Used only for AERIS-10E version, featuring 10Watt QPA2962 GaN amplifier for extended range
|
||||||
|
|||||||
Reference in New Issue
Block a user