feat(comm): 添加EtherCAT CAN桥接和PDO功能模块

添加了ethercat_can_bridge.h/cpp和ethercat_pdo.h/cpp四个文件,
扩展通信模块功能,支持CAN桥接和PDO相关操作。

fix(comm): 修复LAN9252 EXMC总线时序配置问题

将BUSLAT从1调整为4,确保满足数据手册要求的最小13ns间隔,
解决因时序余量不足导致的温漂偶发读错问题。

feat(comm): 配置ESC过程数据看门狗时间为1秒

修改ESC_REG_WD_TIME_PD寄存器值从默认100ms到1s,
防止主站调试周期触发SM2看门狗导致状态被拖回SAFEOP。
This commit is contained in:
cbb
2026-09-05 16:54:38 +08:00
parent 44d407390b
commit f9ef1b8da2
16 changed files with 2218 additions and 86 deletions
+851 -5
View File
@@ -23,11 +23,13 @@
<!-- SM1: Mailbox Send (Slave -> Master) -->
<Sm DefaultSize="256" StartAddress="#x1200" ControlByte="#x22" Enable="1">MBoxIn</Sm>
<!-- SM2: Process Data Output (RxPDO, Master -> Slave) -->
<Sm StartAddress="#x1400" ControlByte="#x64" Enable="1">Outputs</Sm>
<Sm DefaultSize="384" StartAddress="#x1400" ControlByte="#x64" Enable="1">Outputs</Sm>
<!-- SM3: Process Data Input (TxPDO, Slave -> Master) -->
<Sm StartAddress="#x1480" ControlByte="#x20" Enable="1">Inputs</Sm>
<!-- RxPDO (Output, Master -> Slave): 索引 0x1600-0x17FF,映射 0x7000:01..04
(1.5.27:与 dc_app.c 对齐,4×64bit;原 TxPdo/RxPdo 元素互换 + 1×256bit 均已修正) -->
<Sm DefaultSize="640" StartAddress="#x1880" ControlByte="#x20" Enable="1">Inputs</Sm>
<!-- RxPDO (Output, Master -> Slave): 索引 0x1600-0x17FF,映射 0x7000:01..48
1.5.56 扩到 48 条目(384B):兼容单帧区 48B + CANFD 批量发送区 336B。
与 dc_app.c 的 PDO_OUT_ENTRIES / gen_eeprom.py 的 PD_OUT_SIZE 必须一致,
否则主站按旧长度建映射,SAFEOP 报 AL 0x001A/0x0011。 -->
<RxPdo Fixed="1" Mandatory="1" Sm="2">
<Index>#x1600</Index>
<Name>Output</Name>
@@ -59,8 +61,320 @@
<Name>Control Data 4</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>5</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 5</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>6</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 6</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>7</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 7</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>8</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 8</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>9</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 9</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>10</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 10</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>11</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 11</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>12</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 12</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>13</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 13</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>14</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 14</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>15</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 15</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>16</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 16</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>17</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 17</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>18</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 18</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>19</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 19</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>20</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 20</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>21</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 21</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>22</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 22</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>23</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 23</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>24</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 24</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>25</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 25</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>26</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 26</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>27</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 27</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>28</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 28</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>29</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 29</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>30</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 30</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>31</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 31</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>32</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 32</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>33</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 33</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>34</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 34</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>35</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 35</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>36</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 36</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>37</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 37</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>38</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 38</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>39</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 39</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>40</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 40</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>41</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 41</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>42</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 42</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>43</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 43</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>44</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 44</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>45</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 45</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>46</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 46</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>47</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 47</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>48</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 48</Name>
<DataType>ULINT</DataType>
</Entry>
</RxPdo>
<!-- TxPDO (Input, Slave -> Master): 索引 0x1A00-0x1BFF,映射 0x6000:01..04 -->
<!-- TxPDO (Input, Slave -> Master): 索引 0x1A00-0x1BFF,映射 0x6000:01..80
1.5.56 扩到 80 条目(640B):电机状态窗口 + CANFD 批量接收区 280B。
SM3 起址随之挪到 #x1880 —— SM2 是 3-buffer 模式,384B×3 占到 #x187F。
(384+640)×3 = 3072 = 过程数据区 0x1400-0x1FFF 全长,恰好用满。
撞址会被 SSC ecatslv.c 重叠检查拦下,报 INVALIDSMOUTCFG。 -->
<TxPdo Fixed="1" Mandatory="1" Sm="3">
<Index>#x1A00</Index>
<Name>Input</Name>
@@ -92,6 +406,538 @@
<Name>Status Data 4</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>5</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 5</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>6</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 6</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>7</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 7</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>8</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 8</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>9</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 9</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>10</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 10</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>11</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 11</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>12</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 12</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>13</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 13</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>14</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 14</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>15</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 15</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>16</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 16</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>17</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 17</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>18</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 18</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>19</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 19</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>20</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 20</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>21</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 21</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>22</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 22</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>23</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 23</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>24</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 24</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>25</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 25</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>26</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 26</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>27</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 27</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>28</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 28</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>29</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 29</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>30</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 30</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>31</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 31</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>32</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 32</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>33</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 33</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>34</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 34</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>35</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 35</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>36</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 36</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>37</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 37</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>38</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 38</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>39</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 39</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>40</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 40</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>41</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 41</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>42</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 42</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>43</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 43</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>44</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 44</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>45</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 45</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>46</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 46</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>47</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 47</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>48</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 48</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>49</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 49</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>50</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 50</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>51</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 51</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>52</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 52</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>53</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 53</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>54</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 54</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>55</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 55</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>56</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 56</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>57</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 57</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>58</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 58</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>59</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 59</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>60</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 60</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>61</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 61</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>62</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 62</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>63</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 63</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>64</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 64</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>65</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 65</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>66</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 66</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>67</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 67</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>68</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 68</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>69</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 69</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>70</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 70</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>71</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 71</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>72</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 72</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>73</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 73</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>74</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 74</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>75</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 75</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>76</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 76</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>77</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 77</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>78</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 78</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>79</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 79</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>80</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 80</Name>
<DataType>ULINT</DataType>
</Entry>
</TxPdo>
<Mailbox DataLinkLayer="true">
<CoE SdoInfo="true" CompleteAccess="true" SegmentedSdo="true"/>
+233 -59
View File
@@ -4,12 +4,17 @@
*
* 对象字典结构 (匹配 DCU_ESI.xml / gen_eeprom.py):
* 0x1C12 RxPDO Assign → 0x1600
* 0x1600 RxPDO Mapping → 0x7000:01..04 (4×64bit = 32 bytes)
* 0x7000 Output Data (主站→从站, 32 bytes)
* 0x1600 RxPDO Mapping → 0x7000:01..08 (8×64bit = 64 bytes)
* 0x7000 Output Data (主站→从站, 64 bytes)
* 0x1C13 TxPDO Assign → 0x1A00
* 0x1A00 TxPDO Mapping → 0x6000:01..04 (4×64bit = 32 bytes)
* 0x6000 Input Data (从站→主站, 32 bytes)
* 0x1A00 TxPDO Mapping → 0x6000:01..48 (48×64bit = 384 bytes)
* 0x6000 Input Data (从站→主站, 384 bytes)
* 0x2000 SYNC Diag (只读: sync0/sync1/escirq/uptime, 主站 SDO 轮询看 DC 通路)
*
* 1.5.55:为电机控制台把过程数据从 32B/32B 扩到 64B/384B。
* 0x7000/0x6000/0x1600/0x1A00 由 OBJCODE_REC 改为 OBJCODE_ARR —— 关键原因见
* 下方 Entry Descriptions 注释:REC 会逐下标索引 pEntryDesc[Subindex],48 个
* 条目就得写 49 项描述符;ARR 由 OBJ_GetEntryDesc 复用 pEntryDesc[1],只需 2 项。
*/
#include "ecat_def.h"
@@ -18,9 +23,11 @@
#include "ecatappl.h"
#include "objdef.h"
#include "dc_app.h"
#include "../../ethercat_pdo_size.h" /* ECAT_PDO_OUT_BYTES / ECAT_PDO_IN_BYTES */
#include <string.h>
#include <stddef.h> /* offsetof:下方布局守卫断言使用 */
#include <stdio.h> /* printf:0x204 守护 WARN 日志(1.5.48) */
/*---------------------------------------------------------------------------
* el9800appl.h 桩 include (SSC 内部需要)
@@ -31,31 +38,49 @@
/*---------------------------------------------------------------------------
* PDO 数据缓冲
*
* 1.5.56 扩到 DPRAM 硬上限:输出 48×64bit=384B,输入 80×64bit=640B(非对称)。
* 长度的唯一真源是 comm/ethercat_pdo_size.h;本文件用 _Static_assert 与之绑定,
* 改一处漏一处会编译失败。为何硬上限是 out+in≤1024B 见该头文件的推导。
*---------------------------------------------------------------------------*/
/** 输出/输入 PDO 的 64bit 条目数。 */
#define PDO_OUT_ENTRIES 48u /* 48×8 = 384 B */
#define PDO_IN_ENTRIES 80u /* 80×8 = 640 B */
/** 输出数据对象 0x7000 (主站→从站, RxPDO) */
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT64 u64Data[4]; /* 4×8 = 32 bytes */
UINT64 u64Data[PDO_OUT_ENTRIES];
} OBJ_STRUCT_PACKED_END
TOBJ7000;
/** 输入数据对象 0x6000 (从站→主站, TxPDO) */
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT64 u64Data[4]; /* 4×8 = 32 bytes */
UINT64 u64Data[PDO_IN_ENTRIES];
} OBJ_STRUCT_PACKED_END
TOBJ6000;
static TOBJ7000 s_outputObj = {4, {0, 0, 0, 0}};
static TOBJ6000 s_inputObj = {4, {0, 0, 0, 0}};
static TOBJ7000 s_outputObj = {PDO_OUT_ENTRIES, {0}};
static TOBJ6000 s_inputObj = {PDO_IN_ENTRIES, {0}};
/* 布局守卫(1.5.42):SSC 的 SDO 单条目访问按"si0 占 16bit,其后按 BitLength 紧排"
* 计算偏移(OBJ_GetEntryOffset),故 UINT64 数组必须紧跟在 UINT16 之后落在字节 2。
* OBJ_STRUCT_PACKED_START 曾被留空导致其落在字节 8、SDO 读写错位,加断言防回归。 */
_Static_assert(offsetof(TOBJ7000, u64Data) == 2, "TOBJ7000 未紧排:检查 OBJ_STRUCT_PACKED_START");
_Static_assert(offsetof(TOBJ6000, u64Data) == 2, "TOBJ6000 未紧排:检查 OBJ_STRUCT_PACKED_START");
_Static_assert(sizeof(TOBJ7000) == 34, "TOBJ7000 尺寸应为 2+32");
_Static_assert(sizeof(TOBJ7000) == 2 + 8 * PDO_OUT_ENTRIES, "TOBJ7000 尺寸应为 2+384");
_Static_assert(sizeof(TOBJ6000) == 2 + 8 * PDO_IN_ENTRIES, "TOBJ6000 尺寸应为 2+640");
/* 与 C++ 侧桥接层的长度约定绑定:任一处改了尺寸而忘了另一处,这里直接编译失败,
* 而不是等到运行期 SAFEOP 报 AL 0x001A/0x0011 再回头查。 */
_Static_assert(8 * PDO_OUT_ENTRIES == ECAT_PDO_OUT_BYTES, "输出 PDO 长度与桥接层不一致");
_Static_assert(8 * PDO_IN_ENTRIES == ECAT_PDO_IN_BYTES, "输入 PDO 长度与桥接层不一致");
/* SSC 编译期门限(ecat_def.h)必须容得下:否则 ecatslv.c 在 SM 校验时拒绝进 SAFEOP。 */
_Static_assert(8 * PDO_OUT_ENTRIES <= MAX_PD_OUTPUT_SIZE, "MAX_PD_OUTPUT_SIZE 太小");
_Static_assert(8 * PDO_IN_ENTRIES <= MAX_PD_INPUT_SIZE, "MAX_PD_INPUT_SIZE 太小");
/*---------------------------------------------------------------------------
* SYNC 诊断对象 0x2000 (只读, 主站 SDO 轮询)
@@ -83,6 +108,13 @@ extern UINT32 EcatDbg_GetSync1(void);
extern UINT32 EcatDbg_GetEscIrq(void);
extern UINT32 EcatDbg_GetUptimeSec(void);
/* 过程数据收发模块 (comm/ethercat_pdo.cpp 的 extern "C" shim, 1.5.54)。
* 1.5.53 时这里直接调 EcatCan_* 转发桥;1.5.54 把"一个 EtherCAT 周期怎么收发"
* 收拢进 EthercatPdo 类,本文件退回纯 SSC 移植层:只留对象字典 + 三行转发。 */
extern void EcatPdo_ReadOutputs(const UINT8 *esc);
extern void EcatPdo_Process(void);
extern void EcatPdo_WriteInputs(UINT8 *esc);
/*---------------------------------------------------------------------------
* PDO Assign 对象 (0x1C12 / 0x1C13)
*---------------------------------------------------------------------------*/
@@ -98,30 +130,49 @@ static TOBJ_PDO_ASSIGN sTxPDOassign = {1, {0x1A00}};
/*---------------------------------------------------------------------------
* PDO Mapping 对象 (0x1600 / 0x1A00)
*
* 映射条目值 = Index<<16 | SubIndex<<8 | BitLen,这里 BitLen 恒为 0x40(64bit)。
* 48 个条目手写既冗长又容易错位,改用宏按 8 个一组展开。
*---------------------------------------------------------------------------*/
#define PDO_MAP_ENTRY(idx, sub) (((UINT32)(idx) << 16) | ((UINT32)(sub) << 8) | 0x40u)
/* 一组 8 个:sub = base+1 .. base+8 */
#define PDO_MAP_8(idx, base) \
PDO_MAP_ENTRY(idx, (base) + 1), PDO_MAP_ENTRY(idx, (base) + 2), \
PDO_MAP_ENTRY(idx, (base) + 3), PDO_MAP_ENTRY(idx, (base) + 4), \
PDO_MAP_ENTRY(idx, (base) + 5), PDO_MAP_ENTRY(idx, (base) + 6), \
PDO_MAP_ENTRY(idx, (base) + 7), PDO_MAP_ENTRY(idx, (base) + 8)
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT32 aEntries[4];
UINT32 aEntries[PDO_OUT_ENTRIES];
} OBJ_STRUCT_PACKED_END
TOBJ_PDO_MAPPING;
TOBJ_RXPDO_MAPPING;
/* 0x1600: 映射 0x7000:01..04, 每个 64 bits */
static TOBJ_PDO_MAPPING sRxPDOMap = {4, {
0x70000140, /* Index=0x7000, SubIndex=0x01, BitLen=64 */
0x70000240, /* Index=0x7000, SubIndex=0x02, BitLen=64 */
0x70000340, /* Index=0x7000, SubIndex=0x03, BitLen=64 */
0x70000440 /* Index=0x7000, SubIndex=0x04, BitLen=64 */
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT32 aEntries[PDO_IN_ENTRIES];
} OBJ_STRUCT_PACKED_END
TOBJ_TXPDO_MAPPING;
/* 0x1600: 映射 0x7000:01..48, 每个 64 bits */
static TOBJ_RXPDO_MAPPING sRxPDOMap = {PDO_OUT_ENTRIES, {
PDO_MAP_8(0x7000, 0), PDO_MAP_8(0x7000, 8), PDO_MAP_8(0x7000, 16),
PDO_MAP_8(0x7000, 24), PDO_MAP_8(0x7000, 32), PDO_MAP_8(0x7000, 40)
}};
/* 0x1A00: 映射 0x6000:01..04, 每个 64 bits */
static TOBJ_PDO_MAPPING sTxPDOMap = {4, {
0x60000140, /* Index=0x6000, SubIndex=0x01, BitLen=64 */
0x60000240, /* Index=0x6000, SubIndex=0x02, BitLen=64 */
0x60000340, /* Index=0x6000, SubIndex=0x03, BitLen=64 */
0x60000440 /* Index=0x6000, SubIndex=0x04, BitLen=64 */
/* 0x1A00: 映射 0x6000:01..80, 每个 64 bits */
static TOBJ_TXPDO_MAPPING sTxPDOMap = {PDO_IN_ENTRIES, {
PDO_MAP_8(0x6000, 0), PDO_MAP_8(0x6000, 8), PDO_MAP_8(0x6000, 16),
PDO_MAP_8(0x6000, 24), PDO_MAP_8(0x6000, 32), PDO_MAP_8(0x6000, 40),
PDO_MAP_8(0x6000, 48), PDO_MAP_8(0x6000, 56), PDO_MAP_8(0x6000, 64),
PDO_MAP_8(0x6000, 72)
}};
_Static_assert(sizeof(sRxPDOMap.aEntries) / 4 == PDO_OUT_ENTRIES, "0x1600 条目数不符");
_Static_assert(sizeof(sTxPDOMap.aEntries) / 4 == PDO_IN_ENTRIES, "0x1A00 条目数不符");
/*---------------------------------------------------------------------------
* Entry Descriptions
*
@@ -131,7 +182,11 @@ static TOBJ_PDO_MAPPING sTxPDOMap = {4, {
* 相邻常量数组的垃圾, 主站 config_map 报 "Unsupported access to an object" /
* "Attempt to read to a write only object"(实测 0x1600/0x1A00 的 sub0/3/4 报错
* 而 sub1/2 侥幸通过, 正是越界特征)。
* 另: 可被 PDO 映射的数据子条目必须带 OBJACCESS_RxPDOMAPPING/TxPDOMAPPING,
*
* 1.5.55: 输入 PDO 扩到 48 条目后, 继续用 REC 就得写 49 项描述符(且 0x1600/0x1A00
* 与 0x7000/0x6000 条目数不同, 不能再共用一个数组)。这些条目类型完全一致, 正是
* ARR 的语义, 故统一改为 OBJCODE_ARR —— 描述数组固定 2 项, 由 SSC 复用 [1]。
* 注意: 可被 PDO 映射的数据子条目必须带 OBJACCESS_RxPDOMAPPING/TxPDOMAPPING,
* 否则主站校验 0x1600/0x1A00 的映射目标时会拒绝(官方 sampleappl.h 同此)。
*---------------------------------------------------------------------------*/
@@ -142,30 +197,21 @@ OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOAssignEntryDesc[] = {
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}
};
/* PDO Mapping (0x1600/0x1A00): REC 含 4 个映射条目 -> 必须给 Sub0..Sub4 共 5 项。 */
/* PDO Mapping (0x1600/0x1A00): ARR of UINT32 映射条目, 2 项即可。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOMappingEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* Sub0: 条目数 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* Sub1 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* Sub2 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* Sub3 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ} /* Sub4 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ} /* Sub1..n: 通用项 */
};
/* 0x7000 Output Data: REC, Sub0..Sub4 共 5 项; 子条目可被 RxPDO 映射。 */
/* 0x7000 Output Data: ARR of UINT64; 子条目可被 RxPDO 映射。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asOutputEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING}
};
/* 0x6000 Input Data: REC, Sub0..Sub4 共 5 项; 子条目可被 TxPDO 映射, 输入侧只读。 */
/* 0x6000 Input Data: ARR of UINT64; 子条目可被 TxPDO 映射, 输入侧只读。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asInputEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING}
};
@@ -199,24 +245,24 @@ TOBJECT OBJMEM ApplicationObjDicList[] = {
{NULL, NULL, 0x1C12, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
asPDOAssignEntryDesc, aName0x1C12, &sRxPDOassign, NULL, NULL, 0x0000},
/* 0x1600: RxPDO Mapping (REC, 4 条目 -> 描述数组必须 5 项) */
{NULL, NULL, 0x1600, {DEFTYPE_PDOMAPPING, 4 | (OBJCODE_REC << 8)},
/* 0x1600: RxPDO Mapping (ARR of UINT32, PDO_OUT_ENTRIES 个映射条目) */
{NULL, NULL, 0x1600, {DEFTYPE_PDOMAPPING, PDO_OUT_ENTRIES | (OBJCODE_ARR << 8)},
asPDOMappingEntryDesc, aName0x1600, &sRxPDOMap, NULL, NULL, 0x0000},
/* 0x7000: Output Data (主站→从站) */
{NULL, NULL, 0x7000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
/* 0x7000: Output Data (主站→从站, ARR of UINT64) */
{NULL, NULL, 0x7000, {DEFTYPE_UNSIGNED64, PDO_OUT_ENTRIES | (OBJCODE_ARR << 8)},
asOutputEntryDesc, aName0x7000, &s_outputObj, NULL, NULL, 0x0000},
/* 0x1C13: TxPDO Assign */
{NULL, NULL, 0x1C13, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
asPDOAssignEntryDesc, aName0x1C13, &sTxPDOassign, NULL, NULL, 0x0000},
/* 0x1A00: TxPDO Mapping (REC, 4 条目 -> 描述数组必须 5 项) */
{NULL, NULL, 0x1A00, {DEFTYPE_PDOMAPPING, 4 | (OBJCODE_REC << 8)},
/* 0x1A00: TxPDO Mapping (ARR of UINT32, PDO_IN_ENTRIES 个映射条目) */
{NULL, NULL, 0x1A00, {DEFTYPE_PDOMAPPING, PDO_IN_ENTRIES | (OBJCODE_ARR << 8)},
asPDOMappingEntryDesc, aName0x1A00, &sTxPDOMap, NULL, NULL, 0x0000},
/* 0x6000: Input Data (从站→主站) */
{NULL, NULL, 0x6000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
/* 0x6000: Input Data (从站→主站, ARR of UINT64) */
{NULL, NULL, 0x6000, {DEFTYPE_UNSIGNED64, PDO_IN_ENTRIES | (OBJCODE_ARR << 8)},
asInputEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路) */
@@ -234,8 +280,8 @@ TOBJECT OBJMEM * VARMEM ApplicationObjDic = ApplicationObjDicList;
*---------------------------------------------------------------------------*/
UINT16 APPL_GenerateMapping(UINT16 *pInputSize, UINT16 *pOutputSize)
{
*pInputSize = 32; /* 4×UINT64 = 32 bytes */
*pOutputSize = 32;
*pInputSize = ECAT_PDO_IN_BYTES; /* 80×UINT64 = 640 bytes */
*pOutputSize = ECAT_PDO_OUT_BYTES; /* 48×UINT64 = 384 bytes */
return 0;
}
@@ -250,7 +296,19 @@ UINT16 APPL_StopMailboxHandler(void) { return 0; }
*---------------------------------------------------------------------------*/
UINT16 APPL_StartInputHandler(UINT16 *pIntMask)
{
*pIntMask = 0x0800U; /* SM3 AL Event */
/* 1.5.44:此处**不能**覆写 *pIntMask。
*
* pIntMask 指向 SSC 全局 u16ALEventMask,StartInputHandler() 已按同步模式算好:
* free-run + 有输出 PDO 时它是 PROCESS_OUTPUT_EVENT(0x0400)(ecatslv.c:959/1090),
* 即 SM2 收到输出这一事件才是 SM-Synchron 模式的同步节拍。
* 旧代码写死 0x0800(PROCESS_INPUT_EVENT) 把 0x0400 顶掉了,而随后的
* SetALEventMask() 只做“或”,于是寄存器 0x204 永远缺 SM2 位:
* 输出到达不再产生 IRQ,PDI_Isr 只能被主站“读输入”事件带起来,
* 输入刷新因此永远落在主站读之后 —— SM3 是 3-buffer 缓冲模式,
* 主站读到未被从站重写的缓冲不累加 WKC,表现为几乎每周期 wkc < expected_wkc。
*
* 官方 sampleappl.c / el9800appl.c 同样只 return,不碰 pIntMask。 */
(void)pIntMask;
return 0;
}
@@ -283,7 +341,11 @@ void APPL_InputMapping(UINT16 *pInputData)
s_syncDiag.u32EscIrq = EcatDbg_GetEscIrq();
s_syncDiag.u32UptimeS = EcatDbg_GetUptimeSec();
memcpy(pInputData, &s_inputObj.u64Data[0], 32);
/* 1.5.54:先让 EthercatPdo 把本周期的输入数据写进 0x6000 的对象存储,
* 再交给 ESC。经由对象存储中转(而非直接写 pInputData)是为了让主站
* SDO 读 0x6000 时看到与过程数据一致的内容。 */
EcatPdo_WriteInputs((UINT8 *)&s_inputObj.u64Data[0]);
memcpy(pInputData, &s_inputObj.u64Data[0], ECAT_PDO_IN_BYTES);
}
/*---------------------------------------------------------------------------
@@ -291,7 +353,8 @@ void APPL_InputMapping(UINT16 *pInputData)
*---------------------------------------------------------------------------*/
void APPL_OutputMapping(UINT16 *pOutputData)
{
memcpy(&s_outputObj.u64Data[0], pOutputData, 32);
memcpy(&s_outputObj.u64Data[0], pOutputData, ECAT_PDO_OUT_BYTES);
EcatPdo_ReadOutputs((const UINT8 *)&s_outputObj.u64Data[0]);
}
/*---------------------------------------------------------------------------
@@ -299,15 +362,12 @@ void APPL_OutputMapping(UINT16 *pOutputData)
*---------------------------------------------------------------------------*/
void APPL_Application(void)
{
/* Phase 2: 回环测试 — 将输出数据原样回填到输入数据。
* TwinCAT 主站写 0x7000 → ESC → APPL_OutputMapping → s_outputObj
* APPL_Application: s_inputObj = s_outputObj (回环)
* APPL_InputMapping → ESC → 主站读 0x6000
* 主站可验证:写入的值 == 读回的值。 */
s_inputObj.u64Data[0] = s_outputObj.u64Data[0];
s_inputObj.u64Data[1] = s_outputObj.u64Data[1];
s_inputObj.u64Data[2] = s_outputObj.u64Data[2];
s_inputObj.u64Data[3] = s_outputObj.u64Data[3];
/* 1.5.54:应用逻辑全部搬进 EthercatPdo::process()(当前实现是 PDO↔CAN 转发)。
*
* 原 Phase 2 的 32B 回环已被取代:回环的验证价值由输入 PDO 的 seq 回显
* (ECAT_CAN_RX_OFF_ACK)承担——主站写 seq=N 后应在下一周期读回 ack=N,
* 同样能证明 输出→应用→输入 整条过程数据链路通。 */
EcatPdo_Process();
}
/*---------------------------------------------------------------------------
@@ -322,3 +382,117 @@ UINT32 EcatDbg_ReadAlDiag(void)
alStatus = SWAPWORD(alStatus);
return ((UINT32)alStatus << 16) | (UINT32)SWAPWORD(HW_GetALEventRegister());
}
/*---------------------------------------------------------------------------
* AL Event Mask (0x204) 守护 + SM2 事件楔死自愈 — 1.5.48 新增, 1.5.49 扩展
* (C 链接, task_runner 1s 节流调用)
*
* 背景:实测"停止回 INIT 后重进 OP"概率性失败——主站 LRW WKC=3/3(输出
* 确实写进 SM2),但从站 9s ESM 超时报 0x001B:bEcatFirstOutputsReceived 一直
* 没被置位。该标志只在 PDI_Isr 收到 SM2 事件(AL Event 0x220 bit10)时置位。
* 01:03 串口日志钉死现场:evt=0x0400 在 0x220 挂起 10s 无人处理(PDI_Isr 的
* SM2 分支从未执行),escirq 2.6/s 证明 EXTI 只在 AL Control 事件时跳动——
* 即 PDI_Isr 在跑但 SM2 分支被某个内部旗标挡住(bEscIntEnabled 门控)。
*
* 守护(1)0x204 缺位自愈:从站 AL>=SAFEOP 且有输出 PDO 时,SSC 不变式是
* "0x204 ⊇ u16ALEventMask"。读实际值比对,缺位则补写并打 WARN。
*
* 自愈(2)SM2 事件楔死(1.5.49):SAFEOP 下 0x220 bit10 挂起 且 标志未置位
* = PDI_Isr 通路楔死。从主循环直接完成 PDI_Isr 该做的事:置位
* bEcatFirstOutputsReceived + 读 SM2 首/末字应答事件(ecatappl.c:317-320 同款)。
* 协议等价:bit10 挂起 = 主站已写过 SM2 = "收到输出"成立;AL_ControlRes 下一个
* MainLoop 周期即完成 SAFEOP→OP。WARN 附带全部内部旗标(bEscInt/bDcSync/
* 期望与实际 mask/看门狗状态),下次复现一行日志即可定位楔死路径。
*---------------------------------------------------------------------------*/
UINT32 EcatDbg_GuardAleventMask(void)
{
/* SSC 全局(ecatslv.c 定义;除 u16ALEventMask 外均已由 ecatslv.h PROTO 声明) */
extern UINT16 u16ALEventMask;
extern BOOL bMbxRunning; /* mailbox.c 定义(dc_app.c 未包含 mailbox.h,局部声明) */
UINT16 evt = 0U;
UINT16 actual = 0U;
UINT16 wdState = 0U;
UINT16 dummy = 0U;
const UINT8 al = (UINT8)(nAlStatus & 0x0FU);
if (al < 2U /* STATE_PREOP */) {
return 0xFFFFU; /* 状态不适用:返回哨兵值,调用方不打印 */
}
/* (0) 邮箱 SM 地址/长度失配自愈(2026-09-03 03:00 根治,1.5.52):
*
* 主站侧探针(楔死态)实锤的证据链:
* - ESC/SSC 邮箱处理全程活着(注入合法 CoE 请求后 200ms 内 SM1 出响应);
* - 但响应帧头出现在 SM1 start+8 而非 start(且逐会话累积:+4→+8,断电才清零);
* - 网络侧(FPRD)读 SM0/SM1 寄存器 start 恒正确(0x1000/0x1200);
* => SSC 的 u16EscAddrSendMbx(Init→PreOP 时经 GetSyncMan/CSR 间接读)拿到了错值,
* 主站按协议从 0x1200 读 → 帧头永远错位 → SOEM 报 WkcError/MailboxError,
* 且预清理重枚举(重跑 StartMailboxHandler)重读仍错 → 死锁,只有断电恢复。
* 自愈:AL>=PREOP 时用寄存器真值(0x0800/0x0808)校验 SSC 缓存的 start/len,
* 失配即写回 + WARN(旧值/新值一并打印,下次串口复现可确凿定位 CSR 读错还是
* 变量污染——两种成因此写回均有效,写回后下一个邮箱事务即恢复)。
* 变量仅在 Stop/StartMailboxHandler(INIT<->PREOP 迁移,主循环上下文)被写,
* 守护同样在主循环——无并发风险。 */
if (al >= 2U && bMbxRunning) {
UINT16 sm0Start = 0U, sm0Len = 0U, sm1Start = 0U, sm1Len = 0U;
HW_EscReadWord(sm0Start, ESC_SYNCMAN_REG_OFFSET);
HW_EscReadWord(sm0Len, ESC_SYNCMAN_REG_OFFSET + 2U);
HW_EscReadWord(sm1Start, ESC_SYNCMAN_REG_OFFSET + SIZEOF_SM_REGISTER);
HW_EscReadWord(sm1Len, ESC_SYNCMAN_REG_OFFSET + SIZEOF_SM_REGISTER + 2U);
sm0Start = SWAPWORD(sm0Start);
sm0Len = SWAPWORD(sm0Len);
sm1Start = SWAPWORD(sm1Start);
sm1Len = SWAPWORD(sm1Len);
if (sm0Len >= 8U && sm1Len >= 8U
&& (u16EscAddrReceiveMbx != sm0Start || u16EscAddrSendMbx != sm1Start
|| u16ReceiveMbxSize != sm0Len || u16SendMbxSize != sm1Len)) {
printf("[ECAT][MBX] WARN: SM addr/len mismatch, SSC rx=0x%04X/%u tx=0x%04X/%u "
"vs reg SM0=0x%04X/%u SM1=0x%04X/%u -> restored\r\n",
(unsigned)u16EscAddrReceiveMbx, (unsigned)u16ReceiveMbxSize,
(unsigned)u16EscAddrSendMbx, (unsigned)u16SendMbxSize,
(unsigned)sm0Start, (unsigned)sm0Len,
(unsigned)sm1Start, (unsigned)sm1Len);
u16EscAddrReceiveMbx = sm0Start;
u16ReceiveMbxSize = sm0Len;
u16EscAddrSendMbx = sm1Start;
u16SendMbxSize = sm1Len;
}
}
if (al < 4U /* STATE_SAFEOP */ || nPdOutputSize == 0U) {
return 0xFFFFU; /* 以下仅 SAFEOP+ 适用 */
}
evt = SWAPWORD(HW_GetALEventRegister());
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
actual = SWAPWORD(actual);
/* (1) 0x204 缺位自愈 */
if ((UINT16)(actual & u16ALEventMask) != u16ALEventMask) {
const UINT16 before = actual;
HW_SetALEventMask(u16ALEventMask);
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
actual = SWAPWORD(actual);
printf("[ECAT][AEM] WARN: AL Event Mask lost bits, actual=0x%04X expect=0x%04X -> restored 0x%04X\r\n",
(unsigned)before, (unsigned)u16ALEventMask, (unsigned)actual);
}
/* (2) SM2 事件楔死自愈:仅 SAFEOP + 标志未置位 + bit10 挂起时触发
* (OP 态下 LRW 每周期置位/清除 bit10 属正常,不能误报) */
if (al == 4U /* SAFEOP */ && (evt & 0x0400U) != 0U && bEcatFirstOutputsReceived == FALSE) {
HW_EscReadWord(wdState, ESC_PD_WD_STATE);
wdState = SWAPWORD(wdState);
bEcatFirstOutputsReceived = TRUE;
HW_EscReadWord(dummy, nEscAddrOutputData);
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
printf("[ECAT][AEM] WARN: SM2 event stuck evt=0x%04X, PDI_Isr path wedged "
"(bEscInt=%u bDcSync=%u mask_exp=0x%04X mask_act=0x%04X wd=0x%04X) "
"-> flag set + event acked\r\n",
(unsigned)evt,
(unsigned)(bEscIntEnabled ? 1U : 0U),
(unsigned)(bDcSyncActive ? 1U : 0U),
(unsigned)u16ALEventMask, (unsigned)actual, (unsigned)wdState);
}
return actual;
}
+35 -7
View File
@@ -143,9 +143,25 @@
#define DC_SUPPORTED 1
#endif
/** 看门狗由定时器中断检查。 */
/** 看门狗由定时器中断检查。
* 1.5.47:1→0。ECAT_TIMER_INT=1 要求端口提供调用 ECAT_CheckTimer() 的 1ms 硬件
* 定时器 ISR——本工程从未实现,导致 ECAT_CheckTimer 在整个固件里零调用者:
* (a) EsmTimeoutCounter 永不递减 → 任何 pending 状态迁移(SAFEOP→OP 等)永不
* 超时,从站会无限期忽略一切 AL Control(实测"进 PREOP 失败: AL=SAFEOP
* code=0x0000"的卡死就是它);
* (b) DC_CheckWatchdog/PLL 计数器(i16WaitForPllRunningCnt)不前进 → 若 ESC
* 残留 DC 激活位(0x0981,上个 DC 会话遗留、跨重连不清),从站按 DC 同步
* 模式进 SAFEOP→OP,OP 等待 PLL 序列永远不满足 → SAFEOP+0x0000 永久挂起
* (00:27 复测日志: 等待期轨迹恒 SAFEOP+0x0000,LRW 每 7ms 也救不回)。
* 改 0 后 MainLoop 按 DWT CYCCNT 轮询(hw_gd32exmc.cpp 的 HW_GetTimerValue/
* HW_ClearTimerValue 本就是为此写的死代码),每 1ms 调一次 ECAT_CheckTimer。 */
#ifndef ECAT_TIMER_INT
#define ECAT_TIMER_INT 1
#define ECAT_TIMER_INT 0
#endif
/** 1.5.47:DWT CYCCNT @180MHz 的每毫秒 tick 数(ECAT_TIMER_INT=0 时 MainLoop 用)。 */
#ifndef ECAT_TIMER_INC_P_MS
#define ECAT_TIMER_INC_P_MS 180000U
#endif
#ifndef INTERRUPTS_SUPPORTED
@@ -572,18 +588,30 @@
/*-----------------------------------------------------------------------------------------
------
------ PDO Size (需与 DCU_ESI.xml / gen_eeprom.py 的 PDO 尺寸一致: 4×64bit=32B)
------ PDO Size (需与 DCU_ESI.xml / gen_eeprom.py / dc_app.c 三处一致)
------
------ 1.5.56 扩到 DPRAM 硬上限:64B/384B → 384B/640B。
------ 输出 384B = 48×64bit:兼容单帧区 48B + CANFD 批量发送区 336B
------ 输入 640B = 80×64bit:桥状态 32B + 电机窗口 288B + 长帧 40B + 批量接收区 280B
------ 上限依据:邮箱占 0x1000-0x13FF,过程数据区仅 0x1400-0x1FFF=3072B,
------ SM2/SM3 均 3-buffer 实占 3×size ⇒ out+in ≤ 1024B。384+640 恰好用满。
------ 推导与实测(SII 余量/EXMC 耗时)见 comm/ethercat_pdo_size.h。
------
------ 这两个宏是**编译期硬门限**,必须先于其它改动生效:
------ - ecatappl.c:157 用它们定义 PD 暂存区 aPdOutputData/aPdInputData
------ - ecatslv.c:509/574 在 SM 校验时拿 SMLength 与之比较,超限直接返回
------ ALSTATUSCODE_INVALIDSMINCFG/OUTCFG,表现为 PREOP→SAFEOP 失败
------ 忘改这里会在进 SAFEOP 时报 AL 错误,而不是静默截断。
-----------------------------------------------------------------------------------------*/
/** 最大输出 PDO 字节数(RxPDO,主站→从站)。 */
/** 最大输出 PDO 字节数(RxPDO,主站→从站)。1.5.56: 0x40 → 0x180 = 384B */
#ifndef MAX_PD_OUTPUT_SIZE
#define MAX_PD_OUTPUT_SIZE 0x20
#define MAX_PD_OUTPUT_SIZE 0x180
#endif
/** 最大输入 PDO 字节数(TxPDO,从站→主站)。 */
/** 最大输入 PDO 字节数(TxPDO,从站→主站)。1.5.56: 0x140 → 0x280 = 640B */
#ifndef MAX_PD_INPUT_SIZE
#define MAX_PD_INPUT_SIZE 0x20
#define MAX_PD_INPUT_SIZE 0x280
#endif
/*-----------------------------------------------------------------------------------------
+44 -10
View File
@@ -54,17 +54,44 @@ PRODUCT_CODE = 0x00009252
REVISION_NO = 0x00010000
SERIAL_NO = 0x00000001
# Mailbox configuration (ESC process RAM: mailbox 0x1000-0x13FF, PDO 0x1400-0x14BF)
# Mailbox configuration (ESC process RAM: mailbox 0x1000-0x13FF, PDO 0x1400-0x1FFF)
MBX_RX_OFFSET = 0x1000 # SM0: Master -> Slave (receive mailbox)
MBX_RX_SIZE = 256
MBX_TX_OFFSET = 0x1200 # SM1: Slave -> Master (send mailbox)
MBX_TX_SIZE = 256
# Process Data configuration
#
# 1.5.56 扩到 DPRAM 硬上限 64/384 -> 384/640(需要一周期合并多条 CANFD 帧)。
# 必须与 comm/ethercat_pdo_size.h 的 ECAT_PDO_OUT_BYTES / ECAT_PDO_IN_BYTES 一致
# ——固件侧有 _Static_assert 守着,但 EEPROM 这一侧无法编译期校验,只能靠这里对齐。
#
# ★ 为何 out+in 最大只能是 1024 ★
# 邮箱 SM0/SM1 各 256B 固定占 0x1000-0x13FF,过程数据区只剩 0x1400-0x1FFF=3072B。
# SM2/SM3 都是 3-buffer 模式(control 0x64 / 0x20,bit1=0),实际占用 3×size:
# SM2 384B -> 0x1400-0x187F (384×3 = 1152B)
# SM3 640B -> 0x1880-0x1FFF (640×3 = 1920B) ← 恰好用满最后一字节
# (384+640)×3 = 3072 = 过程数据区全长,一字节不剩也一字节不越界。
# SSC 在 ecatslv.c:708-721 按 (start + size*buffer) 做重叠检查,撞了会在
# PREOP->SAFEOP 返回 ALSTATUSCODE_INVALIDSMOUTCFG,不是静默出错。
# 要再更大只能压邮箱(拖慢 SDO/FoE)或改 1-buffer(失去读写并发保护),见
# comm/ethercat_pdo_size.h 的完整推导。
PD_OUT_OFFSET = 0x1400 # SM2: RxPDO (Master -> Slave outputs)
PD_OUT_SIZE = 32
PD_IN_OFFSET = 0x1480 # SM3: TxPDO (Slave -> Master inputs)
PD_IN_SIZE = 32
PD_OUT_SIZE = 384 # 48×64bit
PD_IN_OFFSET = 0x1880 # SM3: TxPDO (Slave -> Master inputs)
PD_IN_SIZE = 640 # 80×64bit
# 3-buffer 占用与 DPRAM 上限自检:改尺寸/偏移时立刻在生成阶段拦住,
# 而不是等烧进 EEPROM、冷启动后再看 AL 错误码。
_PD_BUFFERS = 3
assert PD_OUT_OFFSET >= MBX_TX_OFFSET + MBX_TX_SIZE, \
"过程数据区起点压到了邮箱上(SSC ecatslv.c:708 会报 INVALIDSMINCFG)"
assert PD_OUT_OFFSET + PD_OUT_SIZE * _PD_BUFFERS <= PD_IN_OFFSET, \
"SM2 的 3 个缓冲会盖住 SM3,请把 PD_IN_OFFSET 往后挪"
assert PD_IN_OFFSET + PD_IN_SIZE * _PD_BUFFERS - 1 <= 0x1FFF, \
"SM3 超出 LAN9252 4KB DPRAM (0x1FFF)"
assert PD_OUT_SIZE % 8 == 0 and PD_IN_SIZE % 8 == 0, \
"PDO 尺寸必须是 8 的整数倍(映射条目是 64bit)"
# PDI type: LAN9252 HBI 16-bit indexed mode (DS Table 14-2: 0x8C=HBI 8-bit,
# 0x8D=HBI 16-bit). 0x05 is a reserved value on LAN9252 and disables the HBI PDI.
@@ -214,17 +241,24 @@ for start, length, control, enable, sm_type in sm_entries:
sm_data.append(sm_type)
append_category(41, bytes(sm_data))
def append_pdo(cat_type, pdo_index, sm_index, obj_index):
def append_pdo(cat_type, pdo_index, sm_index, obj_index, pd_size):
"""Table 24/25: header [index(2)][nEntry(1)][SyncM(1)][Sync(1)][NameIdx(1)]
[Flags(2)] + entries [idx(2)][sub(1)][name(1)][dtype(1)][bitlen(1)][flags(2)]."""
[Flags(2)] + entries [idx(2)][sub(1)][name(1)][dtype(1)][bitlen(1)][flags(2)].
nEntry 由过程数据长度推导(每条目 UNSIGNED64 = 8B),须与 dc_app.c 的
PDO_OUT_ENTRIES / PDO_IN_ENTRIES 一致;写死 4 会在扩容后让主站按 32B 建映射,
与从站 APPL_GenerateMapping 报的长度不符,SAFEOP 阶段报 AL 0x001A/0x0011。"""
n_entry = pd_size // 8
if not 1 <= n_entry <= 255:
raise SystemExit(f"PDO 条目数 {n_entry} 超出 SII 单字节 nEntry 范围")
data = bytearray()
data.extend(struct.pack('<H', pdo_index))
data.append(4) # nEntry: 0x7000/0x6000:01..04 (dc_app.c)
data.append(n_entry) # nEntry: 0x7000/0x6000:01..n (dc_app.c)
data.append(sm_index) # SyncM
data.append(0) # DC Synchronization
data.append(0) # NameIdx
data.extend(struct.pack('<H', 0)) # Flags
for sub in range(1, 5):
for sub in range(1, n_entry + 1):
data.extend(struct.pack('<H', obj_index))
data.append(sub) # SubIndex
data.append(0) # Entry NameIdx
@@ -233,8 +267,8 @@ def append_pdo(cat_type, pdo_index, sm_index, obj_index):
data.extend(struct.pack('<H', 0)) # Flags
append_category(cat_type, bytes(data))
append_pdo(51, 0x1600, 2, 0x7000) # RXPDO: outputs (SM2)
append_pdo(50, 0x1A00, 3, 0x6000) # TXPDO: inputs (SM3)
append_pdo(51, 0x1600, 2, 0x7000, PD_OUT_SIZE) # RXPDO: outputs (SM2)
append_pdo(50, 0x1A00, 3, 0x6000, PD_IN_SIZE) # TXPDO: inputs (SM3)
# DC (60) is "for future use" per Table 19 and has no defined payload;
# DC configuration comes from DCU_ESI.xml <Dc> - omitted on purpose.