diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aef66172..6219df302 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,6 @@ set(header_eez_apps_psu src/eez/apps/psu/channel_dispatcher.h src/eez/apps/psu/conf.h src/eez/apps/psu/conf_advanced.h - src/eez/apps/psu/conf_channel.h src/eez/apps/psu/conf_user.h src/eez/apps/psu/datetime.h src/eez/apps/psu/debug.h diff --git a/images/Module DCP405b.png b/images/Module DCP405b.png index 7a74e0599..0dd4ad14b 100644 Binary files a/images/Module DCP405b.png and b/images/Module DCP405b.png differ diff --git a/modular-psu-firmware.eez-project b/modular-psu-firmware.eez-project index 81d252a93..6f817a017 100644 --- a/modular-psu-firmware.eez-project +++ b/modular-psu-firmware.eez-project @@ -1464,15 +1464,6 @@ "simulator" ] }, - { - "name": "slot_module_type", - "type": "enum", - "enumItems": "[\"None\", \"DCP505\", \"DCP405\"]", - "defaultValue": "1", - "usedIn": [ - "simulator" - ] - }, { "name": "simulator.loadState", "description": "", @@ -3140,6 +3131,7 @@ "type": "MultilineText", "style": { "inheritFrom": "default_S", + "alignHorizontal": "left", "color": "status_warning", "padding": 0 }, @@ -3157,6 +3149,7 @@ "type": "MultilineText", "style": { "inheritFrom": "default_S", + "alignHorizontal": "left", "padding": 0 }, "activeStyle": { @@ -8727,21 +8720,18 @@ "height": 28, "widgets": [ { - "type": "Text", + "type": "Rectangle", "style": { - "inheritFrom": "default_S", - "padding": 0 + "inheritFrom": "default" }, "activeStyle": { - "inheritFrom": "default", - "padding": 0 + "inheritFrom": "default" }, - "action": "", "left": 0, "top": 0, "width": 234, "height": 28, - "text": "Advanced options" + "invertColors": true }, { "type": "Text", @@ -15179,7 +15169,7 @@ "top": 0, "width": 166, "height": 32, - "text": "Display value #1:" + "text": "YT view value #1:" }, { "type": "DisplayData", @@ -15217,7 +15207,7 @@ "top": 36, "width": 166, "height": 32, - "text": "Display value #2:" + "text": "YT view Display value #2:" }, { "type": "DisplayData", @@ -15401,7 +15391,7 @@ "top": 0, "width": 321, "height": 32, - "text": "Set over-power protection" + "text": "Display view settings" } ] }, @@ -25866,9 +25856,8 @@ { "type": "Text", "style": { - "inheritFrom": "option_toggle_L_center", + "inheritFrom": "default", "font": "Oswald17", - "alignHorizontal": "center", "backgroundColor": "Background", "padding": "0 0 0 6" }, @@ -25885,7 +25874,7 @@ { "type": "Text", "style": { - "inheritFrom": "option_toggle_L_center", + "inheritFrom": "default", "font": "Oswald17", "alignHorizontal": "left", "padding": "0 0 0 " @@ -25936,7 +25925,6 @@ "style": { "inheritFrom": "option_toggle_L_center", "font": "Oswald17", - "alignHorizontal": "center", "padding": "0 0 0 6" }, "activeStyle": { @@ -25954,7 +25942,6 @@ "style": { "inheritFrom": "option_toggle_L_center", "font": "Oswald17", - "alignHorizontal": "left", "padding": "0 0 0 6" }, "activeStyle": { @@ -25991,39 +25978,41 @@ ] }, { - "type": "Container", + "type": "Select", "style": { "inheritFrom": "default" }, "activeStyle": { "inheritFrom": "default" }, - "action": "set_coupling_parallel", + "data": "is_coupling_parallel_allowed", "left": 5, "top": 120, "width": 189, "height": 36, "widgets": [ { - "type": "Select", + "type": "Container", "style": { "inheritFrom": "default" }, "activeStyle": { "inheritFrom": "default" }, - "data": "is_coupling_type_parallel", + "data": "", + "action": "", "left": 0, "top": 0, - "width": 38, + "width": 189, "height": 36, "widgets": [ { "type": "Text", "style": { - "inheritFrom": "option_toggle_L_center", + "inheritFrom": "default", "font": "Oswald17", "alignHorizontal": "center", + "backgroundColor": "Background", "padding": "0 0 0 6" }, "activeStyle": { @@ -26039,40 +26028,107 @@ { "type": "Text", "style": { - "inheritFrom": "option_toggle_L_center", + "inheritFrom": "default", "font": "Oswald17", "alignHorizontal": "left", - "padding": "0 0 0 6" + "padding": "0 0 0 " }, "activeStyle": { "inheritFrom": "default" }, "action": "", - "left": 0, + "left": 38, "top": 0, - "width": 38, + "width": 150, "height": 36, - "text": "\\u008e" + "text": "Parallel" } ] }, { - "type": "Text", + "type": "Container", "style": { - "inheritFrom": "option_toggle_L_center", - "font": "Oswald17", - "alignHorizontal": "left", - "padding": "0 0 0 " + "inheritFrom": "default" }, "activeStyle": { "inheritFrom": "default" }, - "action": "", - "left": 38, + "action": "set_coupling_parallel", + "left": 0, "top": 0, - "width": 150, + "width": 189, "height": 36, - "text": "Parallel" + "widgets": [ + { + "type": "Select", + "style": { + "inheritFrom": "default" + }, + "activeStyle": { + "inheritFrom": "default" + }, + "data": "is_coupling_type_parallel", + "left": 0, + "top": 0, + "width": 38, + "height": 36, + "widgets": [ + { + "type": "Text", + "style": { + "inheritFrom": "option_toggle_L_center", + "font": "Oswald17", + "alignHorizontal": "center", + "padding": "0 0 0 6" + }, + "activeStyle": { + "inheritFrom": "default" + }, + "action": "", + "left": 0, + "top": 0, + "width": 38, + "height": 36, + "text": "\\u008d" + }, + { + "type": "Text", + "style": { + "inheritFrom": "option_toggle_L_center", + "font": "Oswald17", + "padding": "0 0 0 6" + }, + "activeStyle": { + "inheritFrom": "default" + }, + "action": "", + "left": 0, + "top": 0, + "width": 38, + "height": 36, + "text": "\\u008e" + } + ] + }, + { + "type": "Text", + "style": { + "inheritFrom": "option_toggle_L_center", + "font": "Oswald17", + "alignHorizontal": "left", + "padding": "0 0 0 " + }, + "activeStyle": { + "inheritFrom": "default" + }, + "action": "", + "left": 38, + "top": 0, + "width": 150, + "height": 36, + "text": "Parallel" + } + ] } ] }, @@ -26109,7 +26165,6 @@ "style": { "inheritFrom": "option_toggle_L_center", "font": "Oswald17", - "alignHorizontal": "center", "padding": "0 0 0 6" }, "activeStyle": { @@ -26127,7 +26182,6 @@ "style": { "inheritFrom": "option_toggle_L_center", "font": "Oswald17", - "alignHorizontal": "left", "padding": "0 0 0 6" }, "activeStyle": { @@ -26195,7 +26249,6 @@ "style": { "inheritFrom": "option_toggle_L_center", "font": "Oswald17", - "alignHorizontal": "center", "padding": "0 0 0 6" }, "activeStyle": { @@ -26213,7 +26266,6 @@ "style": { "inheritFrom": "option_toggle_L_center", "font": "Oswald17", - "alignHorizontal": "left", "padding": "0 0 0 6" }, "activeStyle": { @@ -28521,7 +28573,7 @@ "left": 236, "top": 56, "width": 236, - "height": 84, + "height": 176, "itemWidget": { "type": "Container", "style": { @@ -28761,8 +28813,8 @@ "padding": 0 }, "action": "standBy", - "left": 16, - "top": 16, + "left": 10, + "top": 10, "width": 188, "height": 32, "text": "Standby" @@ -28779,8 +28831,8 @@ "padding": 0 }, "action": "reset", - "left": 16, - "top": 48, + "left": 10, + "top": 46, "width": 188, "height": 32, "text": "Soft reset (*RST)" @@ -28797,8 +28849,8 @@ "padding": 0 }, "action": "hard_reset", - "left": 16, - "top": 80, + "left": 10, + "top": 82, "width": 188, "height": 32, "text": "Hard reset" @@ -28815,8 +28867,8 @@ "padding": 0 }, "action": "turnDisplayOff", - "left": 16, - "top": 130, + "left": 10, + "top": 124, "width": 188, "height": 32, "text": "Display off" @@ -28833,8 +28885,8 @@ "inheritFrom": "default", "padding": 0 }, - "left": 16, - "top": 119, + "left": 10, + "top": 118, "width": 188, "height": 2 }, @@ -28850,8 +28902,8 @@ "padding": 0 }, "action": "show_previous_page", - "left": 16, - "top": 162, + "left": 10, + "top": 160, "width": 188, "height": 32, "text": "Cancel" @@ -28859,8 +28911,8 @@ ], "left": 130, "top": 31, - "width": 220, - "height": 210 + "width": 212, + "height": 202 }, { "name": "entering_standby", @@ -48644,6 +48696,322 @@ "width": 168, "height": 484 }, + { + "name": "dcp405b_front_panel", + "style": "", + "widgets": [ + { + "type": "Container", + "style": { + "inheritFrom": "default", + "color": "#f2f2f2", + "backgroundColor": "#ffffff", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 168, + "height": 484, + "widgets": [ + { + "type": "Bitmap", + "style": { + "inheritFrom": "default", + "backgroundColor": "#ffffff", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 168, + "height": 484, + "bitmap": "DCP405B" + }, + { + "type": "Select", + "style": { + "inheritFrom": "default", + "backgroundColor": "#ffffff", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "data": "simulator.loadState", + "action": "simulator_load", + "left": 4, + "top": 44, + "width": 160, + "height": 28, + "widgets": [ + { + "type": "Text", + "style": { + "inheritFrom": "default", + "font": "Oswald14", + "alignHorizontal": "center", + "color": "#9b9b00", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 4, + "borderColor": "#808080", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "data": "", + "action": "", + "left": 0, + "top": 0, + "width": 160, + "height": 28, + "text": "Load Off" + }, + { + "type": "Text", + "style": { + "inheritFrom": "edit_S", + "font": "Oswald14", + "alignHorizontal": "center", + "color": "#9b9b00", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 4, + "borderColor": "#808080", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "data": "simulator.load", + "action": "", + "left": 0, + "top": 0, + "width": 160, + "height": 28, + "text": "" + } + ] + }, + { + "type": "Select", + "style": { + "inheritFrom": "default", + "backgroundColor": "#ffffff", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "data": "channel.isCC", + "left": 26, + "top": 102, + "width": 20, + "height": 20, + "widgets": [ + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#ffffff", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + }, + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#ff040b", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + } + ] + }, + { + "type": "Select", + "style": { + "inheritFrom": "default", + "backgroundColor": "#ffffff", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "data": "channel.isCV", + "left": 50, + "top": 102, + "width": 20, + "height": 20, + "widgets": [ + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#ffffff", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + }, + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#00ff00", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + } + ] + }, + { + "type": "Select", + "style": { + "inheritFrom": "default", + "backgroundColor": "#ffffff", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "data": "channel.activeCoupledLed", + "left": 31, + "top": 328, + "width": 20, + "height": 20, + "widgets": [ + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#ffffff", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + }, + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#00ff00", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + }, + { + "type": "Rectangle", + "style": { + "inheritFrom": "default", + "color": "#ff040b", + "backgroundColor": "#ffffff", + "borderSize": 2, + "borderRadius": 8, + "borderColor": "#404040", + "padding": 0 + }, + "activeStyle": { + "inheritFrom": "default", + "padding": 0 + }, + "left": 0, + "top": 0, + "width": 20, + "height": 20 + } + ] + } + ] + } + ], + "usedIn": [ + "simulator" + ], + "left": 0, + "top": 0, + "width": 168, + "height": 484 + }, { "name": "dcm220_front_panel", "widgets": [ @@ -50299,16 +50667,6 @@ "borderSize": 0, "padding": 0 }, - { - "name": "mon_dac", - "inheritFrom": "default", - "font": "Oswald24", - "alignHorizontal": "center", - "alignVertical": "center", - "color": "status_warning", - "borderSize": 0, - "padding": 0 - }, { "name": "mon_value", "inheritFrom": "default" @@ -433028,6 +433386,11 @@ "image": "data:image/png;base64,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", "bpp": 32 }, + { + "name": "DCP405B", + "image": "data:image/png;base64,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", + "bpp": 32 + }, { "name": "DCM220", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKgAAAHkCAYAAACqpmQXAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAStwAAErcBqc337wAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7L15fFxXef//fs7MSLK8J7GdON41m6zESRBbgIATQiBNbI2ciKasgVDTQlu2ttAflBooLXyhBUopEKCEAiGNsDVyEgLZCDtNsOM4kTWaGTtOsLMvjldJM/c+vz/ujDSWtVvS3JHPm5fJzJ1zz3nm6jNnP88joGcBR7BY/MfMIHAE5GC5LbFYTkQx5TbBYhmOYLkNGJlNJhJ58J1q3FchcqYa9lapu7Wzc+ud5bbMMvkI6By/NfENazas7+7Rm4y4M0CGTSsuTx87VnPBH/940+NTZJ5lytA5vhLoqlWJD5gq/ZIgpapURbsFOaKijiBBlNlAVem9IuSqg4H4Qw9t3jO1VlsmD/8I1ETqEwdVmekpUxXkR5lU8q3D3bRo0WUz586buV1Fo30XRXdkOtsvmFxzLVODDwS6cOH6RXNPM0/2XQi4/y/TsfWjY80nHF+/QzDnAShyNJtqmzmBZlrKQpkFunLl6xcFq2cXxZnLpM6vjcW2L8wFg2uDpub36YduHKm5lnD8qislqG7m4cBPY7HABa5039+fX7Jq2LstPqe8ApVIPOEAonCwJui+qydvfszAUVHI/XLmoa0fGnhzJJ54Djjt+Bw5UGUOLO3Nzz2IiCAcyHQm50/id7BMKjqnbPOg0XjiIJ4Y80YCV/XkzWY8sSrIMRQFIGc+GIk3/3fpveF4cy9FcSq9QK7wel7OnfvC0cNzFxTfR+NNP5qir2SZBMoi0LqGq69RmAWQSYVmqjo/A0DZkU0lTSbVVpvpShoCzmbvDn1X8d5IfP03BQ2BcvCF2bMyXcnqTCpZVRU4djqAqgRnznnh34yRt3lZyjWwyS5IVChlaeILTbsR5FvpVNt7I/GEC5BJJU+Y9IzEmh0VNdnCZ9F4okehyqVq4+7Uzd8qTbuyYd0/B53Ax1GcTFcyGIknDgBzgX2ZVHLpoLbEmt6FyCsHXO5B2Kcqv8qm2n4Phdp8EKLRdWe4JvgeQS8BWQx6VNEdCjfsTrX/FmDF+Yl5oR42ocwAUOSR7sPBr+zb13pssDzDq9dfgCvXCRICQMzWTOeW24qf153TtNTk5J0icomiixV6Rbgfw1czHckdpXktO/eK+dW9oXcAl2FYiaIiPAz6rXRn+10Dy66v33BWTt2/BV4rMA/lCRHa3XzPf2azt/cM9RwmB50z5StJ0TVXX6S9eQNoOtW2ccWKxDwFBHUGS6+iefHmPAVQVyUgogwUJ0DuxZrPBmflPo54LcOB50JL5p2eOwQsGdIgkX8AIicWDIISjiceMq65Lp3ecv/AJOF40zpFvi/o3L6bAEFeJvCecH3indnO5PdD3VwOfKCvSJTamfl5wMcG5rl27drg/ifN1uNsVvcC4DaA6OqmizTPHQg12lceoJyLw9vD8aZ12VT7zwBW1m9YHsy52xBOLzEPVVaDtETrm/863dn2tWIx0ehV8bw6vxI4o//5EFa4iGBVczh8+eunWqRT3vRpb/4WAIR7APbuTb7oVZsSGCx90cAlS1pqvNt0yKWl2trclwt5OwDPPNN6WAr903BD06eGuK2YXw64HrhelaTAC4UPz1Xj/DIcb76w9KZw/YZXCrIZr4YGoRPly4p+C+jyTOV8ABENldx6FEBFr127du0JFcS+J+deTr84jwKg9N3vKi8FqQGeBX4GlP5wQiD/XHxjXK0HTgcOC/JzQX4OuMXvraqfhZa+564B53t44nRBvijwXpCd3teTVxOqet8Qz3DSmHqBFv6gmc7kG4qXtPDQwvHmfYOkF4C5cwkAqAyuz2i05eVq2FjI8Qd99xv5CoC48pERTOvJpJLvzaSS7812JZtDgdAyUb7vfSQ1gn6v9I8p6v4XeMIR5TtnLzqwJtOV/FA21b4xkzp/tSDXovq7QZ7A1sKLRY8/Pu8NAz8V5NrCi/uA/SfebrajfFDzPUsyqeSbMqnkyxH9Rv/9rCkKP6AmjfDPTm9oSTrVdkk61XYJKqUzInPj8Xyd9/w2vAzl5YVcvpdJtf1dOpW83hFzdTGxURl24WQymPImvrBS1Pd/ANUBeV2vo78S9OxIPKGeZvtSC0BvPncwEktAoQb1+q1akmOurybMpNr7BlWZXed9NBLf8bcoY5q47+hoPdzYuPG6g0eebgRWA5FoNHdZOs3t0Wji5QoXeMbpk0ePVP31vffem++/e5ObTvG9IbK+BVgP1Krh7cDtxQ+i0XVnKFwJgMuNCO8feHM21fYL4Bel1wTzC0X/ovBW7733Xgcgnd68B/jH4zII6i8p6Uzl84WulXFf239V+zbi7OncnInEExkgonC+9yNtHbQ7NhlMaQ26YsU1K7xXkiu93tHR9uvQjOClJcIU71/JnKgU/kfJlf40xevPnjg5v8llnGzbdn0OlRv7Lhh9LYAGWFu8pCI/GWqwMxiqeliEWwtvm2Kx9bP78wq8Fa+/nc+H3JtGnSfaUPLmPoYZ1Gle+tIK+uSSJQceBXBhcfG6C+kBtxUXTALRaPeUzitPaQ1aXd1ziQuI0Dvws10P/PhuBvnBROPNvS4aIj9zbjb7w4PFGYDBRvyTgUK6WJCLKfwRdWXfb8LlkTHnqfK/oG8Gal0xVwE3FD66tvDfux95eOtTkXhixLzC4cvnoLy774Lw9aFTbzJidvxN/2BJvlWs+QVdVPxOQXUPH3+fHCpq3pjgaXj93ylhSmvQnLoLAXSIEftQTIASh6xRRsIY94V+OzRYyK2v1hMjLwxy27Dkuuf+BDhYyOttAJGGxPmIN6hS4YejzUuCNV+mv/a7JZNKDrkwEanf8bcl/cyOXM+8fynJaW7xleOY/PF3akmF4laP1raJYEprUFMrv6fbm0wf+NnixetqZ84OPoG4c0ol2TeVEjzyYmmN4vVVj0MRact0tl01SNHj1rgqC0vePlf4JoeLmlfcs8aa5969N3SH481JQd+BcHE4vGEJrntt4eOj1SbUNpp8wvHmjxUXMRTS5EPXDZU2Wt/UrMq/Ft4+rwH3zXv33tDdn0JfLD6mQMAd2F/vE2VPUJ8ejW0TxZTWoIfco/cDiFJTen3Vqpa5M+cEjiA65yS0JKhuiNYnThz5nhSmb/AgojsBVNz+Zl0lPq5cxf3fvpcB91qU4gi5vaOj9fBQ9xUJ1yeuE7RYA+5zxFyWzbY+M1jaWKz5ElVuxPt7H1ExV2Q72ncNSPZc8YUG+mvTAsV5UeeRh6unrHmHKRboUzvv8E6PyvHlBkK5p7xX2pNJJQOZVFKK/yjMYxp153jvpW/VqfSfI+4HAVRZvCq2vjiipa7uGm8FSRnzYKmuYX0Y9G2FtzknwM8KFpeOov9kZf2G5UNkMeSvbXbtojspikL04xRFIGbE5j0ca7palG8W8n8aRy57pHPLo4OljUY3vMwVTXpzp9otools55bfn2ip9P2w1TEl+2k3GeCcwptHpnIED+VYiy+02SvXrI/1XROvCcmk2mtgcCFVV3e70D/OH8iezq1fQbgTIID5SvG6CXW3ey/0hDnWoYhG150RjiXeZpzAvVCYnhL9zu6H2/8IUPgDP1BIPiOo7k/C9RsKy6WbTLh+w0ui9Yn/DccTXxyqjG3brs8Bhb0GUmxRnplTe8Ydw9kWi214tYj8EAgAzxl135DJtHUOlnZl/YblatyfALPxlkOvGmx5E0Ac8/P+N3pNY+PGEEA49uC1eJP9KLQPZ9tkMPWH5kR+D3phMCe/BhbAxpDyNAKD/jJFPFEePLjYAW/zwFBZHwwdbZ7TW3sYoXTl5nwAp2fmpSNYNqvYry2dXS385+5c9/zjtvypca8T1/wKT8CrRd3fReKJI7AjhFKlgFH+a7gCXXVvMmI29uWJ3lwQ7tD3iPNXIMWptNNdMQ+eMNoX/Wyms/0TAdV30t88V6nKbYPMDGzJpJJXpdNb7o/EEr9CuAi48NCRpx6LxhP7FX1JwbgDRvNfGs62yWDKa9BM6rHXAaByBrQE1q5NF9cuB7VFXe/63r1HCn84TzR1DW++eGDaeT21zYW8FSAST1xLYQ1/z54bM0OYNJTTClV4CJW/zHSF3nj8gAKyu7Y+oGJeK7Ct5PJM+s9K7dIg3wJwkf4flfS/3t1V/Uug5LDf8c279i/u992jQy2lHXejzCm+GDEtzOm7zTFvAR70ypYzFRrxnt8TEtD16fStE9y/H5kyHDvelkOWPo9yWjSe++O99967uFBzSbi++fvZzra3F1OurEt8BKGwvOj1fYxwnwsXGid3F1Cyfr/JuLLjfwDUaKH51f8GwYiUTKccT1DM5Q5O3+S1or3iBF7M54N79+xpfXG4b5Lt3LIdeFmkIXGeuPpSF043ap530O27U8ntFARWbUJtObf3Cldxug9X/bI/h1ZH3KsuIuCsUdzns51bj1saVZGrDG5E8ybVd9EJ/bWEer89nF3i6i6AgLr/nodfBszg+xwA8gT7+q7Z7JZ9a9eufen+p+ZfrGiDUQKI7jl6qOqnY1mMmEjKst1u8eKNtTPnPH0EwIV3hWCFA/9UsCgnLo+pyNmg3gYRlQfTXW3nF+8vbM8rtPbyuIgaVTmz8LFmUkkTqW96GJUGVN1MV/uQfyCLnynTjvrHH7/+qKr7HQAD31V1vgd4E8xKSIW6PnGKpEvFCfDk/hkLVcUt9BQXq8qZhbbQzfeccVZ09fp/Q70lvUAVx91rqSzKttM827X1PcWpDVcCe0yV+6lMKhRUuBHVR1S4M5PqqUl3tsUG3nvo0I+ezXa1BaqDukGEXaKkA8qfZlJtAal55pPqmg8DoPqF1EPtD03xV7NMIGU/dhyub3pSVBYBBJB/T6XaRtoWNyTRWFOnijdxrirt2a62kRezLT7GB+fiAcL1iS5RogoYyAdNzct37brpgRFvLLAitu6jIQn8K8XN5a5+Optu/6fJstcyVfhEoAB10cRGY/hm3wXF1QD3dsuxln0dP3t+YPqVa9bHgr2Bn4CuKl4TId97rCayd+9Ne6fGasvk4iOBFgnHEj8V0TeeuErYP31+IuLmlU8+0tX22Uk2zzKl+FCgRWKxN7/a1d6bETkT0QGDOUWQvCPSKbng64faJGGpdHRO8f8sFh9SRs8iFstosAK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+BorUIuvsQK1+Bqpi/7pdwOBnpnlNsRiGYjjVB8JOu68pMjTVeU2xmIZiOPO6wV0TrkNsVgGR+fYPqjF11iBWnyNFajF11iBWnyNFajF11iBWnyNFajF11iBWnyNFajF11iBWnyNFajF1wTLbcAUEAAWF147QAhwgUPAgXIZZRkd01Ggc4H5gBb+ucDjeOIcmG554bUU0v2xcI/FJ4i3m0kOltuQCWA5Xm15AHh+HPcbYGnhv88B0+GZVDg6h2mw3W4psIqJ7U+fXsjTbuQuK5Ut0Ll4IprMzdZnASsnMX/LsFSuQJcAZ05RWSEgBtRMUXmWPipToKuAWWUqd3YZyj2FqTyBhilvTbYMqKTnVeFUlkDrgBnlNgI4G3/YcQpQOQJdjL+a11i5DTg1qIxDc8X+5qGyWnE8GSBSbiNOBSpBoGfhrQT5CRdvMv+Mchsy3fH7UudiYH+5jRiC5/FG9s9OVIYrGlrODLq9G4wrr0SoU2+hoFuEx1D+DzfQlk5v3jNR5VUCfl/qXAHsLbMNw1GNt+p0UjX8yvoNywOqnxb0rXjLtcOgt4lrPp5Otz14MmVWBv7ug54FPFluI0agh5Oc9grHmt8dVPdhQd/BiOIEkCvU6P3hePMn8Ta5TGv8LNAaoLvcRoyCF4DTxnNjON78SRH9DmNfeAgJ+qlIPPFd2OTnv+FJ49cvFwJ6y23EKHkBb1/AmIjWN71H0E+dZNnvDMd3/PNJ5uFr/CrQs/Hv4Ggw3LEkjkavWqUqXx4h2TFgDyNs+xP4aCy24dVjKb+S8KtAK408Y9hVpcb5Z0bYyueil2ZSyTrNh8IjZGdccb442rIrDb8KtNJ2te8HFo0mYTR65dnAm0dKF8I8C5DNtj7DiM9DXjlda1G/CrQSGd2I2gSbGdVofWy4xm2Z6Dz9gF8FWmk16KhR9JWTlPHk5Ftm/LiSFAJy5TZishBk5SC/vqOKXGpc81TxwszZp/+x7x43UNIPdWer0STeIkYpI/VVKxI/CrQWOFpuIyYLF2YP0hd4Mptq+91Q9wxc3gzXJzpFTxBoJexKGzN+bOK7mcbHK2SwH59y2rJzr5g/mvtXrLi2RpSlg+R7eALM8x1+rEF78Na4pyUiPKbKK46/yLzqXOj5SDzRdymAxFKptjRAJJ5w6RuEDe5rQuGxybG4vPixBoXKXGMelc0Kf5iMwlXl/snIt9z4VaDTdhQfUElORr4Kmycj33LjV4FWWg26EHhmNAkLzfYdI6VzXe/c05IlLaM5/9S1uyt452jKrzT82AetRGqBp0eb2Kj7cVfMJQzz/NXolkg8sQdyZzLiD1b+P2gd6HtqWuDXGvQZRrl0WIl0dW39g6D/MkKyVcClwDnDJ9MfZlJtWybINN/hV4EexauVKoFa4MhYb0qnLvgU6A9Prmj5Za57/ntOLg9/41eBgucuccLXrCeBRYyy/3k8m9xM6oJ3gHyRcQ0K9aZjh4Nv2rv3hkrY1D1u/H4maTnwaLmNGIYA3tGUfSeTSV1s/cVGzBeBl4yYWNgtyCfSnW03nUyZlYHO8btAl+EdSMuX25AhqAN2T1BeEqlffxFqrgZeWch7PsqLiDwmqverMW1nL3r+Z/fee69fn8cEUxmeRVaV24AhmMk0Hsj5A3+f6izyArCg3EYMwtnAUyOmspwUlSLQWvy1gWQl8Ei5jTgVqASBgjdQWoE/7F2M96OZtntWfYbv+6ClrKa8y6CLsP6YppDKGCSVIng7x0NlKHsRnpsby5RReQIt4k3BTB0RpumOdX9TuQIFbwfRZE9BzcUTpx/6vqcglS1Q8FZywnirORPJDDzx2ya9rPh/JWm01OCJVPGWHce70rIAz5FXN/DExJhmGT/TR6ClLKF/n2UOT2hD+U6aQ39fVvCc0U7Lw2eVic6ZjhuWSzduBPFWfBSvHxmgv3Y1eI65/LwZxVLhfVDLtKYy1uItpzBWoBZfYwVq8TVWoBZfYwVq8TVWoBZfYwVq8TVWoBZfYwVq8TVWoBZfYwVq8TXTcbNIHysaWs4MOb2XCCakDvdlMm2d5bJl5TnrFwVy5g0i6oob3J5Ob04VP4vUb7hCXS34onI121XVNl291Y2VaVuDhmOJt4Xc3K8VeYkLywnwjXB94u1lsSXe9MZg3mwXaFRMgxrn9nC8+WPg+ZxH3aSBRgONRmgMhw9P64pjLPh9P2gt3tGO5xkhZmUpq+qvigQ1/0tc56Xp9K19MT+XLGmZsW9f67FYrPkSY6TTUb2yO9T748ceuu2FcDyxFpVVGnR+ubtja9a7Y5OJ1j/wJtQswM3d5eW1yUTrH7hEXN2lEniDwd2eSrU/BNDQ0DIrEHhRd+68o8/b3dq1a4P7n5qXEkeuTafbfg1etDk1wY5cIBTnyMwDoZoDz2dSyQn35hcOX15NaEaDcdRJp4MPV16t7M/9oDV4Z89dPLeGe4F59J+Lf5YRxBrQfJOL/DhbIk6AfftajwE4op93VXMId1cdC50WqW/6B5QFavQeccyWSEPiHZmO5I5IfMf3XZUnEd0lJnBXOLzhDUfnbntejwR+5Bq5X1x+7gp3hBuaXp/taN/V6/Z+QZ3ao8BHimU+/vjpYYxjiuIESKdv3R+NN28PuU5jDu4GlWj0qsL5qt6D6fQtz47mQa1YcW1NVe2BV+PKElfVMSoPpdNtOyl4yzPBmneoutergUi9c2Wmk9tGk2883nSuY6jN7Gq/D9AV5yfmVfXquQENZDs7t5xw0qChoaWqx8m9yogGQ6Zqe0dH6/OjKWc0+E2gC/GCsu4ZcP0A/eEtFuEdZvsjQ6CwQsSLehGJN71FkA8rnK4qn8l2tf23l0Y/le1s/9nixnW1M48E3iGuc05PtevU5KoWqOu+JRZb96IrXNAbzL0aoCoXqpeAexXwLaCq2oTe3NHZejgSb1oiDhcBu9xe80+BQP643fuO5M4yYk5wz6jo8+pqYTe/hAg4nwNQDfwK+OpwD2nt2rXBx5+c9wnlwIfVZTYoIqCiROKJXUbdd3Z1bf2D4pqiGwFVZ1TduWXnXjHfyclOXFhxfmL+3h3JA1Xd8kmFD+Vxvwr8TWn6yOqmV/Q6uW8LnKMq9Dq5vxnJ/rHgJ4GegVdrjuTK8Cm8c0NLhkorah4V1RhAJtV+I3BjJN78BSPMK6YxbvBRgFmHqs5U49SoCXy2OhdAUdQ1d7lGl6LMq86FPteXr9GOwsuejo5W72iIyotqvAHO7t1tJ7gBN27gSdDBDvUtQaT4I+tNdyZHDDDrscnsf3LHFmBdycWif1EBVrsiHwH+bHT5HU+1G1peePni3h3JA17muqxQTF+omyVLWmbUzsp/Vl39GybRj6tfBGrwasXRujI8jCfSmQzi3VjUbMY4fwivXv+17K6tDwyX0dGj5okZsxynKhD6SJ/ogFUN65YZh95sKvS+0r7b4sZ1Q/YVFzeuq6167nS31KnsnDkLsgePPJ2PRhOXp9PJ2wHC8eYLQc8i3/17mDsmTynR+I4Pab84X1Tlr3B6Wo/ODQZmHw6+UoVP6Bjj15ciLisKat9bcnkZgKr0CbR2du9bVeVDQA/CNpSXj7fM4fCLQJdzYrM+Ek8yhBOvdHrznkg88X5xze2ReNMfQA4K7sUI7xuYdt++1mORWPPnep3cPZFY4ucIS8Xl++mO5O2RWKItEs/fIzT9VpGoC/9yDGfIqaqZR82/ac2B4/qg27Zdn6uLNb8vYNzvRuLN94moquoFInpdJnt7T0NDS1WvQyhan7i5eE/IhP5isH5cQ0NLVc7p/dv+Zlveme1qay9Jcg9wz6pVLXMHsy8ca24xwl8qnA7uQ0b1M11dW7sAGhs3hg4eeXqz9sf8XBqJJ4qRQ84BEOGj0XhiXjqVvN51zTMi7g/FDX7SFXetiE6KQP0yih+vJ+Vh71uypGVGzWznPCFfpbncjmz29oMA8XhzdObMBY9s23Z9nwOwunOalpLTcBDd29V1S5/ow+FEnQbcZeIEM9nsln2AhBua6rMd7bvAm98M9aqTTt/y7Mpz1i8KdFe7hRjvx7FixbU1wRkHV6s6NT2Hqx4oDtgAwg1Nq9WRGQAiRrOda3bAphNqwWi0+TVq9FcACulsKhkb7uFE4k3vBflG4e2DwHkDkjxbFQjFOjpan1927hXzq3Oh5xjB95Uo3093Jd9Rei0ca363iH6n8PZvMqnkBPVB/TOKH2/grmKIwEHvL4jg9wOvF0MMlrL74fY/MsjAK5tN7ub4rocWxQnwyMNbnxrs9UAKzf72wT4rzc9j8KAdKiUuwpVhuy6DcJ7CQwa5S1WvRlgKnNGbz18HfOGxh257IRq9cqma4HeANyL6WXGC/00gv0pV7gRymqfeBNxRh9uZCCp9ov4YnheQUwTt96wnY/SLr9zdfTj0inSq7cNqTF9QUDG6pvg6nb51P+L5CVBXdg6Isrw/m03u7uraemjc5o+DShfoTKZx6O6BiPQPfkTGFqZHRb9S7FZ4XQjPP4AqC48rQ70RuwT0UQAXKYzq+wdIU4lfmvgJ9fm5uHFd7czDwfercY2oHHUCTvuejltG/YAj8cSNiPlhpnPLsBPbdfGmVxmR1+OiBre1OOCoO6dpqcnJBhUWqcovd3e1/fRkv1OB/gl8l+XDpBuBTS4kuoFZqDdFFIk1/52I/qkW/e678u1oPNGjKmeCguqaSLzpd0dmuq9/fNstU1Yp+KUG7WHsgbuEIebfZh+WpQgfwDV7UOYGnMC2eDyxYvRZq4w0sR2JJT5gkG+qK/tBnnVEvgMQXr3+ApOXX6vIDFQ6jLAxUr/+taMve2hcMf2RkoVXNzS0zJqIfL383CUKjfRXFud47/XsQnnzQF4269DUBljzSw36JN70RnYM9wwfAkb0YDaVbAWIxBMXOXAhsLeurnmhCepGEZ3pGvcH2V23dADEYuvXuJi3gqZURRBv3BWJNNe7Ls+VTsLX1TUvRPSTRt1zu7q2Pl64/A0AccwXQDdlu5LfLVw/yWhy/WQ7A/dH4u5evGXfub1u7iuw6c9LR/yRSHM9AT0/k0r+aCx5Z1LtH6iLNd9uRG8vnSGIxpvvUfRiVN+d6Wr/7kj5TDR+ESh4I+jRxh06G6+5G270X+WtbzsxhWgAfhcOX15NUO8S0X9y1TwvriRXnrP+NeZYQF3R20X0w4pERbVJi8IK8LlAQO8E/rOYcaDKfam60lEizgItAST3mp5QvmVsX320tDqqzZ8Q0R8AoLw7HN/xMkjcKqIuKueBXi7oncCYBAoQEJYqINDXHVJ0OYAEju+D1p3TtFQc3gIgykv7P5FLw/VNtUBvtvOCrww2XTYW/CTQHrylyyieR7rBRovVwFK85c7hR5PKAlecfxNYKyJ/meps2xuON70R9CBwyIgbcpFMyJG1GnRrFW7LdLb/L0Aknji/qP1Mqi3BgB+CqixE5IWBRS479+gccqHQYw/VTtq8crar7YfheHOdoJsAETgXOBft78arSmqo+4dDVZcigBQFusnAjiUAeQ0cJ9BA3lyiaGEZuPTx6HpRWQ8Qi23/364uBvyIx4afBAqehLfxxAAAIABJREFUSNN4m0ZOw/vmhR81BuhltN0AYX82lWyORhMvd1VvbmzcuPngkadPQ5npijZ6ifQXOMH7xTjNgj7Xf7P2luR0Qi1tVB5zjUYGXn/sodteiMQTh+PxfF0qxQlzrRNFNtX26fDq9beIa/5C4UKDLlDkRaBDRFrTqfNuhiRi2KUudwjqqnGPW6kTpU2Fs0T058VrCk8I3KUuPwNYs+a3M7p7Z9yjSG6GMcctiLjG2SmuuWsYM485Tu65YT4fLdPPu10stj4WqU/0TX5H4omfh2NNV8di69dE4omOxsaNxSAMAkg43rQuEm/+BSArzk/Mi9QndoXjTesAotGr4uFwy3GBxMLhy6sj8cQj0fqmvhWVaDRxOSCR+qavh2OJm4plxOPN0bqG9WEs42CaerfLmZCLltZ8+i2MvLura+tOQX988MgzD0TrE8lIPPG7aPTKxXNnLvopuIciscT20DF+jLJbMAqgxvl/EsxdU5p/Nnt7jxp3g6p8MBJPZCLxRBrhzwB1eqo+JqJ68MjTeyPx5ocd3O8F81phG4V9xfSrQQsMnFvte9/YuDEUja47Id5RybTNcWkHyauPxY3rahsaWqoGXl+7dm1wzZrLZo7ZaksJlR9EwTKtmaZNvGX6YAVq8TVWoBZfYwVq8TVWoBZfYwVq8TVWoBZfYwVq8TVWoBZfYwVq8TVWoBZf47f9oBPFfLxQ29Af6TiP55gsj3eWyS35TPF29J/U7m/LxDOdBDqT4olE7zjIWDyVGDxnZAYvxvz+4ZNbppCK3800C1gFx5/vPgmq8Hw+LZmg/CzjprK32wkTK8yBVAERvKMnlrJQuQKdjXe4bkIdPgzBGXjHfC1TTmUKdAHeseOppBqIMzU/CEsflSfQM/EEWg4EWI2dmptCKkugi5i8/uZYiJfbgFOHyhHoXGAwP+/lwOANniyTTmWcSTJ4zfoJ4U/KhItny+JyG3IqUAkCrWNsTsWmgsN4z66m3IZMd/y+kjSPMUSYm2L24c3DjjX4w7DE44kVDlyIyiqB2a5wVNDHNKD3negqfPrjlyAKQzFoFA8fcRpek39gpITDs8lE6x9oUZWPAC8bOp2mVPgqud7vZLO395xcmZWAzvGzQOfiNaMneJHzGSdVi8Zi62OumO/i+S8dFQppRd+1O9X+2/GWWxn4e5A0H/+LE7xADtXjuTEcT6x1Mb9nDOIEEIga5OfhWPNbx1NuJeFngVYK4xrR18UTjQK3UBKecYxUiej3il74pit+FeiQcTh9ypjiPC1uXFdr4Ca8nVgnQ0CQ74fDG6btziu/CjRIIUxKhVB0sjsqZh0J/D39IQdPlrkm4P7LBOXlO/wq0PFGnisX+xllM79mzWUzFT44kYWr8JZYbN3KiczTL/hVoJVGnlHOKR/tqb0Cb4ZiIgmomHGF3/Y7fhVopdWgMEqbxbB2cgqXScm33PhxJckA09dltlI/2FVBP+/KKCb8VWoE/pYTB1gNE2Ge3/CjQKd7/M35g1zbm061/8NoM4jEExcCbzz+qk7Loyl+bOId/GnXhCCDz06MdaJ/kE0qUkmzHqPGjzXoMeB0YCJi7PgOV3haTuytLo7EE79npOBkAEo18KoTrgtDxqqvZPwo0DHNKfqIUdlsVB9U5PJBPnrFSZWi7BjV/RXGtG1Ky8CoRvEuZqJCcw8oXYcNHV6p+FWglVaDzgKOjCZhNnXer4DMBJf/opOr2jLBefoCvwq00jiDUfeZN7mqfHoiC1fk3/fsaX1xIvP0C34V6GE85wzTkmxX8oeCTlRT/zD57s9PUF6+w68CfQ6vVqoEip7zxoKGAlVvBR1X2OwSntE8iem8u96vAoXK6YcuYxze8Do6Wp/PBaouFtg2nkIVHtWArs1mk7vHc3+l4GeB7sf741cC49o7sLej9cne7nmvQfQLQO+IN3i4ony/N5S74FQ4ROfnM0kwCacmJ5izgWcYvbiGZFXDumXBfPB9KtqC970H8oQobY6Rr+/ubHv4ZMurDPx9aA48F4iLgb1ltmMwDF4Nv3eiMw6HWxZIVe8qUWY7ylHJBx7LZrdU0gmDCaIyXN+cDcwotxGDYN3fTDr+PtVZZD8TdzxioliE17RbJh/f16Dg7RnwS401h6n3T3qKUhlNfJFayu/peIYPbDiFqCyBgre6NNgId6rKnpYH0/xL5QkUvM26MaZ2DnchtlkvA5Up0CIRBj8+MdGEsZE+ykRlCxS89fowEJqEvBfhdScqZcl1GlL5Ai2yBK9/WDtBea3CO7xnKSs6x49HPsZDcZVlIf2BFg4wOr+dpWEQpZBXbqINtIyP6SLQIk+XvJ6HNyXk4gWPLW6JC9C/uUPpDyRbic4iTgWmRRNvmZZUxlKn5RTGCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb7GCtTia6xALb5mugWTHYoavFDd84Ae4Aiji4RsKTPTUaDVwJkl7xVPlIeAPXitxlxgKf0huAGOcny0ZIsPEC/asRwstyETwFnADDwx7h/H/bX0x5p/opCPpazoHKZBOO5FwCo8cU4UZwF1QGgC87SMmcoWaC2eMGdOYhnLgbMnMX/LsFSuQM8AlkxRWTOBNUBgisqz9FGZAl0GzC9DuVG82QDLlFF5Al0BzCpj+avwuhaWKaGyBHoWk9vfHC3LsYOnKULnVMpK0kL6J9jLzaN4I3zLFFAJAp2B1/d7vtyGlLAbr7m3TDKVINAlwGPlNmIAOeBFoFK6RxWL35c6FwJPlduIIXgOrxadsFW4JUtaZtTOzr1RXS5EZBXoPOAgwh/VlfvzM/S2vTuSp9QeAr8vda4A9pbZhuGYWfh3Umv4y869Yn5NLvj3irwfmD1M0h5V+b6G3E/vfrj9jydTZmWgc/ws0AV4Gzj8MDAajlV4m1DGRaR+wxWizrcVOXPk1H0cEtEPpzvbvz3ecisDf4/iZ+J/cYLXFx2u1huSaDyxEXXbxyhOgNmq8q1orPlz4ym3kvCrQAOAU24jRslzeEuvYyJa39Ss8HVOYglVRT8arm/60HjvrwT8KtClwL5yGzEG3LEkrq/fcJaqfJsJeP6i8rlYbP2ak83Hr/hVoOBtNK4UXMZQE+bV3QScNlwaRd+USSVF3MBIiwJVrph/G23ZlYZfBVpJ4gSvth/VtrxodN0ZwLUjpQtiHgFIpzfvYeTncWl49foLRlN+pWEFOjGM3t6ASQBVE22AOOaaic7TD/hVoNMWVXn1pGRseNWk5Ftm/LiSFATy5TZi0hDCg9S3Paq8wxjt229wsNbpGySK6GXF164yw8D1J0xNKbHJMbi8+FGgtVTG/Oe4UGWunHh5f7YrefNQ96Q72+8qfR+uTzwgyuUDks2dEAN9hh+b+F68o8PTEhGODXJ5bkNDy2j7pYKyYJDrg+Vb8fixBu2GQf8A0wJ1eVxOrEJP73VyT0Tiib6NIEadS7u6bnkEIFKfyKJ95/dn4p1kHYBU0rzxqPGjQMGfNftIDNJyD5poG5AY5KPTKJkbFQn279pXVo2Uv3r5TjsqUQgVjcHdOin5irZNRr7lxgp0YjidUe747+raulPgtyOly5t8CGCUfdPHQib0k9GUX2n4tYkfVXPpI+YAj4w2saPuJ4yYuxnme4ob+G4knnig18mN3LyrfKqjo7V31NZWEH7dDzobbyT/bLkNGSUrGOPG6mgs8TUV3neyBatwe7YzeQWVt/o2Cvy7H/QQlXPepwZv5mFMzJ618IPAHSdTsMJD+WrewrQUp4dfBQreQ6+Epv4s4Mmx3rRt2/U5zfesB24cT6GC/Lw6EFo73c8o+bWJB0+cy/H3mSTB28V0UnOQkXjizxT+VbzvOxLPqehns50XfAU2jWkfauXh7zNJ4PXtHmOMG4KnkJV4P6CTbmIbGlqqcm5vs6q0AK/geOdoz6DcL0bbQqbqpo6O1sMnW15l4H+BgieCUY+Qp5AZeCtek3JmPxy+vLp3hql1X6jt3revdVouY45MZfhmWkB5vNmNRLTcBkx//DuKL+UZvENpfpqzXQacAufSfYHva9Ai9eU2oMBCvJUjy6RTGU18EQPEy2zDaXjTSpYpobIECl4zX0955kcXcnx4G8ukU3kCBU+cUabWme0KbLNeBipToEXOZvIDKdQCEabxDn9/U9kCBW8dPMwIThDGgcFzCmab9LJSGRP1o2Eunkh7GV+UuSKz8PqaDp6rb0tZmT4CLRLieA8fT+O5cByOxYX7BO806TOTY5pl7Ew/gQ5kAV4/0uCdtc/jdQvyhWsungfnabnZt/LROX5anZkMbG1Y4VTCUqflFMYK1OJrrEAtvsYK1OJrrEAtvsYK1OJrrEAtvsYK1OJrrEAtvsYK1OJrrEAtvmZar8UvblxXO+OwvEJEqshXbc9mW8u2Nr9kScuM6pk9rwwSEBEeTKXanit+Fo83nZtD+tws7k4ltzON/S2NhWlbg0ajictnHgk8aMT8uYg0STB3TyTevKEctoTrN7xyxqxcyohsdI2+1UF3RuOJjeA5aHCQ7QY2Fv8tWdJSUw47/UglbLcbc1iacHjDEgm6f8CRizOZtk7v6iYTjW47LZ2+5dnw6vUXBByyDlxcHay+p6Oj9XA0elUcnJXg3J9O39Ln9jHSkDhfHV3k9lb9fs+e1hcBCddvuIDcsSzBqgvJBzqy2S3D+GbaZCLxBx8APp5Jtd0KEI1eFVfjbNN8aEU+P/NQqObA85lUsnbMT2YU1J3TtDSYU6era+vjk5H/5OLP/aAGz4mWFv4Vo34oXjzMxxnB3WG4PvFBVFdnU+0bB/08nrhflGMYMuTlixiuRPS1CL9CebsjgQ17OjdnIvWJL6MsQGUXoteI61x8eDZHZx0xuxV5CKVDRK8RdV/V1XXLI+F44quiHM10JT9aLKuuYX3Y5M09ma7kslIbIrHEL1X0s/nu+b8I1bzwQk8ovxjAHFpwbO/eG0btzjEavSruiLsCnB43xK5HHt76VN9n8eZ3KnoDACKXZTrb7hxNnivPWb8o0B0MFX94jY0bQ4cOPbc0FDJPD/QLtWpVy9xAdc95aOAMhSfId2/PZm/vGa39w+O//aDzC//2DJNmKZBjOJeHqmERyYLnOU7gIwCuyuezXW2tAAhfyXQmN4fDl1eLVH+wKhBa2dHR2huNJ44ada+NRq/8L1V9YybVvhrQaKx5thswbwb3BkVmaT701my29ZlIfaIagpcC3zJu4Eu5gB4XRlwcswTDEyfYKPIUwsLCm6rqXOhmAK158W5gpDjwEok3bVTkHxRnuddPM5g8Gokl7gmIvC+Vaksrbk3xhLaqO6puQzh8+RzJm8cJumbZuVec9thDt71w6PAzn1GjH+1xnP8E/hogvHpdg7iBz0HuTagJUgxDEqx+Nlyf+HC2M/n90ZQ3En4S6Dy83e7DiRM8lzPz8UKxPDVYAoPsx/VcGWZSyR8BP4rEm79ghKXFNOIGOgCMqT1bceb1OrnbIvEEChi0VQOmTlQWReKJOwAURdTcV3C0d6xkwPWcK+4s6Av8ehwq8qyonhhPXvTMgCv7C32Xnkwq+YYRvncf0VjiWwrXDeIcQBBe74r+I/D20eZXSjBYtdzxWrHDjz102wsAGF3peWvV/uh3GvgG8JpBsjhDlBvq4k27d6faR/TFP6I9J5vBBLIAyIwy7Qt4Z48G9W7sGmeruIFfxuOJL6RSyb3DZVRTc+ipY7213bnueetKm9ZwOFFHiOczncnLKBlRL25cd3xf0UWHG2pKrjsjwaracP2GV2Y7t/weINzQtBqHumAweF/PGBvDaH3Te1S5rvC2B5V/zBu5OeRIQE3+lSD/4LqEhs1kGPKwoiD8vcVrqroMBFHp9+SnBBDuU1e+ibAbeJ2gm/CqbGOEBKMIFjESfhHockauOQeynyFcM2Z33dIRiTV/yhH9v3C8qR3kEOjVrvLxgWl37rzjSKS+6TuhmgPtkXjiVoUVoHdkU8mfReKJ+6L1if9V5VeixB3hv4/hdA5lUCSe+A9ROZruavtYny3Z23si8cSHRd1kpL6pTVxx1WG9qnygo6P1cDh8eTVUh6Kx5r5mPZ8L/mthQDaAloCruU+U1Jx/kelqu6EkwZ7Gxo2tB3qeGvS4dKR+/WtR2QhyBqI7c6bq3/d2tD5ZzDsSz30J5LyCc+v5kXjim4VbV3v/0bdG4oneTCq5OQBvSXUmH6X/x/uLSCxxKcJFAOrKcX3u8eIXgQreUd+xMuRcYaar7T/q6ppvkhDnozpHNPi1TKEJNi7vP3rU9B0rznS2/31dPNEYEImIwwPpdOh3AJlU8q3RaPOrJcASUed3u1Nbt8Mmia5+4Ko+wzVwgxvI5wAcCXw16J74PTKp5I9WNLT8POQ6jSpuTS4Q+kxRGNns7T3R1U2XoNrnHGL+/PmDnkSNRnON2u+FeW8mlfzewDTbtl2fYxDPewbzIVV9HcWpRZU3hpzcnzY0tDR0dLQebmhgbq/DX/c/Uj0bGDDIlCuAF4HNg7VMKuSKPx4jMiGzBn4ZxS9jfI5gi24QT4lJ7Wh98/tV9T+9d/LjTKqtZbj0kXjTe0G+0XdB+SOivwG5jIKzC4G/TqeS/wnegoGj8iWE16N82RV+EBBdpipbAEdcc2EgwL7Ozi0nDPoaGlpO63V694PUAIgrF6XTbb8+uW9cGf5Bh+MonqfjUwPVvhimMnb/pNt7qnLnZVLtf4a4zcWLLvKy4utUqv0hhNpCAfftTiW3Oa56QRqUx9PpLfcPJk6AXrf3s33iRH+aTrf9Zoz2DUqlC7SWkR0zTBu0ZDjmomMaCCm6qTgqz3RW/4ZCl0rQgYFplxfyfxTAiFkOoKJDtnDhWOJtqLy38PZZUb2OCWrV/NIHHe8PZdD7Ghpaqnrc3NuNEkD0md5q+flYwrVE4s3fEKOt6V3Ju4dLF483R114nSvqOgH31uIk+bJzr5hf1Vt1qaCLReXedLrtwbF9rSHpW78fZUSQIWh1IHEMmIV6Gih0B66m4I8qgHwhEk8cpVCOINFIPHFLrnteS+lsR3R14vXq8m3PJA6LyxVd6YlbtfJLDZoDRhsvvZRB7c/ne1aK8i+u6HyFy0Ld8vCKhpYxOAJz57quDrv0GI41vzuP3qHKQnFZHsyZn4In2upc6GERvUhEAhj9r3C8+cKxfKkhMe4DJe9e6c0ATBTmAuBSCs9U4VWF95FCggXAFTLr+bnFOyKrm16hLkm8lb4eEW1Op5P3TZxN/qlB9+N5qcuO4Z6VDDewEp7LdrZ/HiASS0Sq3Pxa4KY1ay6beTRXe41RZrh5kywu562s37A86GqTC+nSqDfR6JVni5iDXV1bDxWvLTv3ivmS0887EnjVntTmDEA4fPlnARz4ggpfyHYmv1xI/qWGhpZxz0uWUmWqf59zep9U5ExggQSqPw18tDRNff2Gs3LqrMmm2n82lrxnVB35SHfvzN8Vlkb3iht4PYAa5wfAhSr6MQ1wY7GVCK9e14ArP8FzuJZTtCXT2X7XBHzN4/CLQMFbuhztaH4hXrjEITeRqBJYdu4V82t6qpa5omFxzA5oCRzrzd2N6P8oPE/QvXvF+YlXAASPub9Sw78btAmVK0BvBFAT+KYrchdQFBxVudDLBNJ7Ojf3LSwU1p8F9FJyofeUmjJRgV4LS7GfBb4KgPD3kXjiAlRuRVRVZU1e3WsM/BoYk0B37rzjSDjWHBABQR4propF4glvFUzlgd0PJ/sGZuKaH9Pv9vK3CKvD9U2r+z5HdEbo6Nd27rzjyMl8Zz8JtLgJIYLn+nCwP6rg+e18gRHCXwucXZML3apGXyrou9LpzalYrPkSB44YJ/hTAIy7M9Str1ehFuGeYq0XiSf6HOP2hPJvN4cWHBenSFTPxsgJe0sbGlrm9zq5mmy24TloHf03HwPpVPJrkfqmBlT+onDpDYi+AUDEG5e4asYV+c6ILvN26PQNiAR0KQjGDfRVHA0NLbN6nVxpvIDXicrrBubX0zPrN8BJjeb9JFDwRJrB85wcpH9HE/T3N/cwmhGi8Fi6M/nqcDxxmcBXYNNNDg+eJcgixN0IIMpux0hXQPWNrrdLqoD27djpW48uxZh9qrpi4OWOjtYXIvHEsZX1O5c+0jlp/kU109n+l9H6plvUE+mFeGF6jgA7VbQ13z3v6wAKewW6AEcZMHGu/B/CShV2FC+5SLege0T01wANDS0ze93cIyi9pQsbHR0NRyOxHT8RYeAMAP15oblgbiJC9VS0h+VBicXWxyL1iV3F95H6xP9F65v+JBrd8LJIPLGNAUEYwrGmq8P1iZ+At4E4Ek9sC8eb1gGsaGg5c+D6e0NDy6xIPPFkNJq4vC+P+g0vAYjEEz+M1Dd9vVhGXV3zwnB4w2S7Kp+m+G+73aQgKt92lT/Pprc0h2PN28Pxpl8J0gHExM2/tbdn/q3BmgMfjcSbbgWZBfStrIXyue8EjwbupKQP2tHRejgWa36LK3pDpD7xKEo16r4AvCko5m/z6v4oEm/ahcp+RM/C4eqp/9bTA78sdU4wm0x9/c5FxVWPtWvXBp94Ys7C4q7y+voNZ7lufsGhWZp9fNstR4v3xOMPhgOB4N4joVwtB+Z17917Q3dDQ8tpL77IscHjZW4ydQ3bV7nCodKNwuDVnFpl5u7pPHf39I9KPFlUfhAFy7Sm8tfiLdMcK1CLr7ECtfgaK1CLr7ECtfgaK1CLr7ECtfgaK1CLr7ECtfgaK1CLr7ECtfia6bqbqRiW28Xb9pbHOzfTW3hf9JRX5Cg28KwvmU4CDeIdGQHoocS30CiYBawovD4MPDt0UssUU/G7marxjoFMiC8gYDbegbyFE5SfZdxU/na7JdDvUnGCmQ00AnNHSmiZLCpXoDVAjPGdpR8ri/H6s5YppzIFOo/+/uJUMQvv3L5lSqk8gZ4BnFWmskNAfMRUlgmksgQ6n/I3tQava2GZEipHoDOZuFH6yVKFN8q3TDqVcybpTMbn4HYy6MXzajIGZ2SW8VIJAq1jED/0ZeZFvPnX6bTQ4Uv8/oBn4q3s+PFc+aN4CwRjDf4wLLHY+tmOBF6CUifobBE56qKPBpFtpfE9TxX87rhh0CgePmIBcIx+x2fjxovAEfgI6BvxaueBuMAvRfQ/0p3tSU4Jv/z+DIVYZBae/3m/b+I4qR9Rff2Gs/KqXwNtHjm1h8BvRd13d3Vt7RpvuZWBvwdJZ+B/cYLn631cXaVotPm8vOr9YxEneN6PXTH3h+OJy8ZTbiXhZ4FWCo/hLYeOiXA4UadG7yjEIxoPswVtj0abBwtHOG3wq0DPgkECsPqXMT3HtWvXBiXIzZz0jimpUaM3NzS0nDZy2srErwKtxtvTWSkMEtd1aB5/ct4HgJdMUNln5fK5z0xQXr7Dr4Ok5TBpHoong2q8pdihQ4QXCIcvr5Zg9WNM7H7TnqCYlUMF2apc/D1IqiR6GO3Wv0DojUz8ZujqHM41E5ynL7ACnWJE5PWTkq8rl05GvuXGjytJhvFFPq4MVBoG7bEK/y3uiZFDBuKKO0uQ93DiZP65E2Kfz/CjQKd7/M3TB15QeDTbmbxutBlE4oko8IbjLgpnnLxp/sM28VONnLhEKTrGv8Pg6afl0qcfa9AjeMc6hg3UVcGceKRZWBqJJ35MSbDYoRChVpXXDryulbHqNmb8KFClMmv2Uc2FKuyUgc2zx1Wjun+IelJg52jurzQqUQgVjt45KbmK/nQy8i03fhXomFZmfMAMvG13IzJ35qJ7gHHF0hyGo8Zxb57gPH2BFejEsAh4ejQJt227Pqei/zqRhSt8PZ2+ZVq66/GrQI/iTTdNS7KdVd8EfjdB2e2tDoQ2TVBevsOvAn2ayjmUVvSWNwZaHaPu1SgnGw34sBp3Q0dH60nv6PcrfhVoJbEUxi60rq6tjwdELmX8Z5qeM2relN219YFx3l8R+FmgT1AZtWiAcR7qS6Xa0lWB0MtE+AFjq4Xvyotp7Ora8pvxlFtJ+HW7XZEJPzU5wZyJF7r7pJdmw6vXX4Ab+KCgTQzqUU+7wdwuRr+W3pW8+2TLqwz8fWgOPC928/Hv7vowkJ3IDAuhw1erMatQmavoYaPuoz09pz28d+8N3RNZlv+pDNc3y/Ecd/mNMP7uIk0DKmPDctFBgp9YCBzCnw4lphWVIFDwROoXh121eCtHT5XbkFME3zfxReZQfg93Iaz7xSmkMvqgpcxn8nzSj8QMIFKmsk9RKk+g4AU3mGp33POZerfjlgoVKHj7WGN43u8mm+V4m0EsU07lCrTIYjz/oZPBPLzZg6mIJGIZlMoXKHgzESsK/yZiVuJ0PGFOW3cylYP/V5LGyjI8kQreBo78KO87A69vC965oOnyPCocnePHM0knQ6kf+yX0B4x18JrqXMnnxdpW8UQ5LTf8VjrTTaClTPSxCksZqJSVJMspihWoxddYgVp8jRWoxddYgVp8jRWoxddYgVp8jRWoxddYgVp8jRWoxddYgVp8jRWoxddYgVp8jRWoxddYgVp8jRWoxddYgVp8jRWoxddIOHb13Ubyg/ijtFjKi6vBF4PHeqIfqanZFRg5ucUytfQePaeHaXAu3jJtqQz/oJZTGCtQi6+xArX4GitQi6+xArX4GitQi6+Zzr6Zxk04nKgzIV3pqsk5Insf6dzy6HjzisXWv1Qx70p3Jd8/4PpsBy4WCQSNOnd2dW09dPKW97OyfsPyEE5EXOPmgvnsno5bHhv5ruMJNzRum6iiAAAR8ElEQVStFkf+LpNKvmsibRsLtgYdBAnwXUX+zKi+OajurZF4cyu0jGsxwzHmNWp4vvRaNHrl2Y6YP4iR16B6kYr5q4mxvJ+gut9Ulbe5RtcHHfN/kXjzhjFn4sqFCC9OtG1jwdagJ7DJIDvWzAgdvXznzjuOrF27Nrj/iXn3R+K5RCbF5nA8cVk2FbobWp14vDmaCzju7o6tWWgJRKO5ywio5AK6LaByerajfZeBRmDzcUWY4DsMclu6M/n3xUvR1U0XiaM7urq2HgqHL6+WUM1rM51td0ZXN11UEzy2/Vhv7ZuMmidL43NGVzdd5KqsCGHu6uzcMjAaX2OuhsjeHckD4XgiD3ousCUaTbzcGNmdSrU9Fw63LDDGWZFOb7kfIFy/4SUG5xw3J7/JZpO7BRq1EDY8HG++UIPuvt0Pt59shOYxYWvQAUQiD8ZA9u3ceccRgHvvvTevor8RiC5Z0jJD4AfQ6gI46D9IXs6HTSYSzyU1wNWu8ifBvNkuDhcBKDQ6Ad12fCnyuKJvqDunqS9iibrmQ64EXgdAoGY9qu/1rsv3untn/FjQNa64W+Px5igg4Xjz91xH3o/K2Xl1/6U093g8sQI47Do5icXWvxS4TAPuDwHUcH0u4MwHMKH8m1XcqwGi8aZPibr/qspCCfKlQlaNGN0Wrm/6kKCf1qNVU+7Y19agA1CjjSDHCUqQM12V7TWznfNw2UkhMrFCYwD305H4gwkUk0klrwOIxhMXiOq2hoaWWTmnd/7AWiedavufSH1imcnL9ki86QOZVPuNqD4MUg/citF3qfLlaPTKsxUW4wb/JJ3enIrEEhe7qvMi9c2XirI03ZW8ZLDv4CAvAQ1W50I3u8JyQdPVVD+2YsW1NXDg7N0dW3cDqNKo8NN4vDmaR6+ZO3PhOdu2XZ8DvtjYuDF08MjTdThynQjz5sxc+CfbUtfnBitvMrE16AAEGkXpE+iK8xPzgIs15N4Jzkswuh2grq55ocCirq5b9gJXisjNAIsb19UC9Y6TeyiXy58P7BikGM10Jj+D0StBvh0OX14tRjsQ6uPxpnONsjCbSt7pSuilwG3p9OZUwbhYTc3RDlTXueL+eKjvoNAI+pVMKvmGTCoZA7O0x+m9uKrq4LnAw14SBPR1RgN/cOFKEW0viBOAg91PNwD7DPqWgMrnSz+bSqxAT6TRFGrQxY3rakPdfBvlf3Y/3P5HQc5R16QAAiE+hic+RXWRijwLMOuI+aDCI9ns7T0q2qjCcbWxV4t55DXwJJDLZmflHUwHuPUO8gVEPwaooI0q+mvoa7afLnQ9zgA5BNDYuDGE55O/D0EbXfW6FeHw5bMFd2EQ84iKcw6iKYBIPHENMC+d3vyIC6cX84NNprFxY0hdGkF/8f+3d/bxUVVnHv89596ZTBIgpZVXA4Zk7p07DISXqZ9+dF1MW5cWkeQOMuu2uuoCWlyxpVrtan1JldKPdqG7dmm31N2Pu1a7bSiZoFYXBFfbWl4MQSDJvCVEQcJLFQgJJHPvPc/+MRlMQkSwvEQz37+Sc+7znHPPfeY5957zPPcC9IhN/C/ndITPgqyB9qJSgDCNIb+h+81IXodSx6BYIub6DgAw035BvEAzQlUMnsTIGB+9AZYPaUbFLxl0NSNtHAQECb0N1J1zZLnXb/5O95krVXZeZsJioMrxCDUO0DQArfHGmle6Dw9Sty4n/bCV1ku8kRgP60ZoRVvHwfWBQLjv16CnCRILdZ/536S6NzOLH0aj1XFi2g+mmV6jYhWArwHYBoABfg2MO3QjtEIztm84dmzfyHTfxbZ41PUfJDHGa1TMOS9D/hFk70F7USlZlM+QUghVyvYcNWd3fX1VKlObjKo/8BrW67B5D+BqE6KTASARq16m66HXiOQhVXUfOk5dLgAQREvbj7l633/Gpt6l6zuCUOUwsuUjifjzfwaALqS8RNTsdLmWZI5lle8t8IxOAIC06PduN70BAPHGmqd8vvItLGikW7gf7tlHAFDA11ggt4s4pSju3fX1Ve0AEI9HXvL5ym9whCo78qxoznHXiPR5RdYZRqjMBhflKK6l9dHI+yWB8hX5SmcrsM6x1PAst3XiosQMf9o+Q/OJJBAID0k59h8ZWJSMVv/pYvdn4PDp+wzNJ5LOTjsPLp7fFI3UfvTRg4tB70ENwyxygPsBgJgOS+KNyWhkXaY+GLzd1dZ+aAGDy4joOEn+93g8siVTX1o6M7+zK+8JJqgAd5KkZ3vWXwjGT5493G257iVwKSAarc6Ch1panu68kH04P2Qj6iGJZhMhj6R4CkLWEfCkZph3Aekn7raOg+sZCArGCgBrWMFXesp3WrlXsECpBFaBsZ0FNni9sy5oGk2O5foNQG1s07eY0WjbHfTRUp8MBv0Uz4wgEa/v3u7bqvlD74P5RwB+onqO3keEg4lo9W09RF7oKS+BoGD+Q1O0phbANs0wfyyEMnTCpPJcxRb3geAn8Da2Uo9xjmucsMXfJWKRpUBY0X3Wz23LdU9zc9VRr99cIAgt8YbIBt1vmsy4iQiOJOfRZMPz9boRukWy7CCirwmmlbFY9cbuLhCAGSlX6m/fib54GEATAAQCYbfl2IsZ8kvMopVd8tGmXTV79Inml6VDo5Ox6mfLysrUva3Df5aMVd/mnVg+TUhxOROmEqMpHo0sT7fJJhEdthT1gZb6wEHd2L6QQbMBvCfYeTAWW7vvfF6fQe9BAQQlyZP3fhJo5fSXjonAi+DQD04nLEBByWIbAOhG6GYA0VTqkvcUW6wncK0tXPMhaRqp7lsBAERhAPD6UiEmzFdVuwioFMR40BEc9/pCNzLzQrZdd0iJ/yOpPAoADL6JiO6W4OWqqvZc/GdmVOdY7kix/3otU2jZ1k+ZeZyt8gICv604tBwAWKICQA4AtLYOm0gkJ6W10EwGKglYR+ys0XyhRQye5wixhCGrhZRDdaPuESZMdVLqzQw+7JyHIJe+DGoPWlgYzgWsy5INnmimTAEmAdxoGOZlDpAfj1fvOJ2OtAeWkzXD/B6D97At5qm5R24kRm0iWvMcAGi+0BsgHud0fHav8BwZBQBEdCeAaocwRvfXXSolbW/aVbNHM8zHwPRLoVoLGZhDhCeQ9pLTBcsvJmJrT+lP4ZgjN727v2CJwrxJm1hxLdnqISbnS4noVC9QKb3+ua+A5bUAwODppOI/AUCCggx6E+j+oRH/KNFYEwFAmmHeT9K+cnf8hXcBvF1cHC5gN90DSQ+o7tTdAF3tAN84JxfiNAxqA/UMdaYQox6octIllUJy3WKw+JkguMEs0b3v3h/jJ88eDgsFiWjNiJ7HeY2KLxDhZNQRCS5lRk1Ly9OdmmFCn2h+GQ4aJNEBAsYy082k8He93rmFYKmS4NcllPbjeda/7qt9/rjPN2eCBPbF+jFOIB3QAuCfNZ85gqS4AYq9GYzNQKUEAMGyFIwGoFIQtvuHeUbWAwCDygmoAdI/NFtxfxMAJvjnjgdLO542TgCA6rE+zxI7SMgGCeX9grxLll6I7c9BPsU709O7QZWi2H+9phl1z4LpUDI25Vf5+SN2g3DC6zcXACCfr3ys7jfNntIexzUdQB36GDExdUimSwGgxKi4khnTTrS7q7ur9zFjGVhZLkjuE8QLiHAg0VCz2fHYFgg5qRPD/5BsXLNpaDspAOBABAHa2rf3Y4Nz8nTdnAWAiovDBUS4CiQ3S8ZxQIwFQCUloZFMWCIVubKk5K1LANi1tats3V9xAxGuVcC14yfPHg5Abamv2g8AQnEkgM+Uls7MLywM5+r63MuZpQWCEm+s2ZBsXLOtvX2/p29/zgeD2kAJ9DkAszVj+06FnX8j0KvJmGseUClra1dZEjyPmBdqhtkiSfkVQ/YKPGaHxzPxK331CjhPEuEa3TDfJKIHBcs5e/dWneiu3gqgNh7/bbNkSjJoiKXIbwLA7l1rD4CwyuU5sknzma87JCrTCqmQgNf6tuN2FDcTFmuG2Szc1u8l8ep4Y82vC/JHvQzIfZph1ikurgbTfcmGtXVNTVP+DPBWzTB3SKYpAK8fPfpoo6fLpQF4OaO3O/rq2eOpvG25Q6wNAOckGnP+SBIxr2Fu0Xzm644QC87RZfgosm8WyTJQya6DZhngDFoDDQTC7u7YzbPG652VEwiE3X3Lg8HbXR9XZ18MwyzSDHPnx82F+rQwKA3Ua4T+KeWk3srvUDZrhrkafeIpP4yiols9mmGuJdVTm7KtA5rfPJlT5PVXfLet48COvA5lk2aYv+2rUzPMnboemtKjiDTD3FLiK/9if21JwuMAln2wwnDh8foq5mmGaXl95k391QcCYbdmmFHNMJ87X30YdAaafuqV17GdmpqIRkoBlHonlk89E1lVPeAWLB9MRKsnMeQtYNwJACW+0FeJqYLt1NRkNDIFQMDrnzutpywzkpLkyYV0r88MAzjSFFv7at92up/8xyWikf/prx+lpTPzgUoBpL12zyDobugv9eTeiXMCJOhbAMUUyH6Xt7qk9Y9EeIuBSX9JW6dj0K2DssDdgrAsnnypK11CrezgM2cim0y+1Aag+2KJSwncAACC+NsALUue1IlWQXJ4T1kBikkiLwCUlZWp7x5AJQS+3k8zJEArGPRt9Fq+qhSa8dYOYt50ogtBzbcdJCp+3NZxcJHLA1+JYc5sikZqdT10FSu8ijvQoRnm2wByEtHIHM0w4yyckOz07FXc1qtQMD9RH+kvHQXFxeECkqmnFNAtErxpzJi2hlis9zGBQPizXY51h0txXZFyrJb0rci59/aDyoOmd45wRerE8I0flPI4qDirVFqfr3wsEe6Tgh/o9l5XWp0FHyw3EcYJSb10SiAORgkA7Ns//B/A2NKfgXh9oa8DaO4bF6rrO3WAfQR6LhGLTCPCZcwIJqKRv2KmNQpoelHRrR4W/Aw5ytxkNHI5iA+BunOiGCtJqncpbusZInriw4wTqBTCZT3NoEcchfMYHOveCOhFyrYeIsZP6+ur3gdhb7FfFp/NGJ4pg8pA8/O7NBDeyYSiTZhUPgrAkEyWIwDo/tCdumG+qRvmm4ZRMbmvDl2/7lJJ4gVmWpJsWFun5B71Arw3o7OkJDQSjIJodEqyp5wCihGxt7AwnMvge6XKD/XVXVgYzgXxwzaJ+/vWSXI+T4TqWKx6YyAQHsKAmus+8QAAJuLRDjtJ1XP4bwDakUmyY0nDMyknbtX1DMALALTEG6v7vXUAAM2//XsCFEu5rK3k4Cpiqu/7QFjsv14D4e9JcEL3V1wD4JiQlv90Y/9xGVRTvCPUPGLZnvnfZSs3grkKPabSeGP1SgAr+5Mv8YcmSebnFKYlmWgixeE8COrIHENueSOA1ZltxpNtO0qcFFmSOyR1J4NW9/cChNx86x4Gre7vVTsEBCXwvwBgWfZUCPwpk7sPYDpbOduEO3UbSMYAoCgQHk2ONdtR+F4ASDnWDwFsADDhdGPEwBVEPNJjua4BUMyE97rsVHn6nNIoLB8nxu/AYgYAMEkLgvwA1p5O98dhUBloSu2K5ViuYt1fEQIwlJkXCciyM5EtMcygYF7HwJMO8DmvLxS2c3m93eaKKW6rSDNCc5k4H4xFJO1T8tWTyapDmmHmArRQplxf6Fvv988dY7O8RWFn+od0IUgC/wUATBzMJOz5fOVjHaC9ubnqqNcXagWk6TUqvkKOtRjAsaZdNXu8fnMJmIcNyx81p639YJPPV+6zhEsq7PwkEY18tVc/GyPXZv7WDHOLJJrfFI3sOlnmL58ByRPisanBzI9QN0K3ScZVZzKOZ8ugmuLf2fniYSZxHUuaAUahpbhmnGk8I4GGENMvBFOuAIIgngYAzc1VR5nEdWD+a5I03lZcV/cMsuitA9+XTEuam6tOed+RDbmUiB//sJeIseAXTwZ5EDeTgt8AAJFwg/EYABSOOfxrYlED0NWC5XeI+GGvd1YOGAW57hMLa2tXWQy6i1kZQcJiAC+e/pw5Mm7U4WjPMpY0kljc3nOGcBTnVQKfshpxjshudV5sdD00RTPM2sG+KH8q2a3OAUH7UDshWJZdzEX5AUzWg2YZqGQ9aJYBTtZAswxosgaaZUCTNdAsAxoVQP5p8sKyZLmY5P8/mxQO4etPdeIAAAAASUVORK5CYII=", diff --git a/src/eez/apps/psu/calibration.cpp b/src/eez/apps/psu/calibration.cpp index a92f21e2f..8b266a329 100644 --- a/src/eez/apps/psu/calibration.cpp +++ b/src/eez/apps/psu/calibration.cpp @@ -56,14 +56,14 @@ void Value::reset() { max_set = false; min_dac = voltOrCurr - ? g_channel->params->U_CAL_VAL_MIN - : (currentRange == 0 ? g_channel->params->I_CAL_VAL_MIN : g_channel->params->I_CAL_VAL_MIN / 100); + ? g_channel->params.U_CAL_VAL_MIN + : (currentRange == 0 ? g_channel->params.I_CAL_VAL_MIN : g_channel->params.I_CAL_VAL_MIN / 100); mid_dac = voltOrCurr - ? g_channel->params->U_CAL_VAL_MID - : (currentRange == 0 ? g_channel->params->I_CAL_VAL_MID : g_channel->params->I_CAL_VAL_MID / 100); + ? g_channel->params.U_CAL_VAL_MID + : (currentRange == 0 ? g_channel->params.I_CAL_VAL_MID : g_channel->params.I_CAL_VAL_MID / 100); max_dac = voltOrCurr - ? g_channel->params->U_CAL_VAL_MAX - : (currentRange == 0 ? g_channel->params->I_CAL_VAL_MAX : g_channel->params->I_CAL_VAL_MAX / 100); + ? g_channel->params.U_CAL_VAL_MAX + : (currentRange == 0 ? g_channel->params.I_CAL_VAL_MAX : g_channel->params.I_CAL_VAL_MAX / 100); } float Value::getLevelValue() { @@ -91,10 +91,10 @@ void Value::setLevel(int8_t value) { void Value::setLevelValue() { if (voltOrCurr) { g_channel->setVoltage(getLevelValue()); - g_channel->setCurrent(g_channel->params->I_VOLT_CAL); + g_channel->setCurrent(g_channel->params.I_VOLT_CAL); } else { g_channel->setCurrent(getLevelValue()); - g_channel->setVoltage(g_channel->params->U_CURR_CAL); + g_channel->setVoltage(g_channel->params.U_CURR_CAL); } } @@ -117,16 +117,16 @@ void Value::setDacValue(float value) { bool Value::checkRange(float dac, float data, float adc) { float range; if (voltOrCurr) { - range = g_channel->params->U_CAL_VAL_MAX - g_channel->params->U_CAL_VAL_MIN; + range = g_channel->params.U_CAL_VAL_MAX - g_channel->params.U_CAL_VAL_MIN; } else { - range = g_channel->params->I_CAL_VAL_MAX - g_channel->params->I_CAL_VAL_MIN; + range = g_channel->params.I_CAL_VAL_MAX - g_channel->params.I_CAL_VAL_MIN; if (currentRange == 1) { // range /= 5; // 500mA range /= 25; // 50mA } } - float allowedDiff = range * g_channel->params->CALIBRATION_DATA_TOLERANCE_PERCENT / 100; + float allowedDiff = range * g_channel->params.CALIBRATION_DATA_TOLERANCE_PERCENT / 100; float diff; diff = fabsf(dac - data); @@ -135,7 +135,7 @@ bool Value::checkRange(float dac, float data, float adc) { return false; } - if (g_slots[g_channel->slotIndex].moduleType != MODULE_TYPE_DCM220) { + if (g_slots[g_channel->slotIndex].moduleInfo->moduleType != MODULE_TYPE_DCM220) { diff = fabsf(dac - adc); if (diff > allowedDiff) { DebugTrace("ADC check failed: level=%f, adc=%f, diff=%f, allowedDiff=%f", dac, adc, diff, allowedDiff); @@ -165,12 +165,12 @@ void Value::setData(float dac, float data, float adc) { if (min_set && max_set) { if (voltOrCurr) { - minPossible = g_channel->params->U_MIN; - maxPossible = g_channel->params->U_MAX; + minPossible = g_channel->params.U_MIN; + maxPossible = g_channel->params.U_MAX; } else { if (currentRange == 0) { - minPossible = g_channel->params->I_MIN; - maxPossible = g_channel->params->I_MAX; + minPossible = g_channel->params.I_MIN; + maxPossible = g_channel->params.I_MAX; } } DebugTrace("ADC=%f", min_adc); @@ -180,7 +180,7 @@ void Value::setData(float dac, float data, float adc) { bool Value::checkMid() { float mid = remap(mid_dac, min_dac, min_val, max_dac, max_val); - float allowedDiff = g_channel->params->CALIBRATION_MID_TOLERANCE_PERCENT * (max_val - min_val) / 100.0f; + float allowedDiff = g_channel->params.CALIBRATION_MID_TOLERANCE_PERCENT * (max_val - min_val) / 100.0f; float diff = fabsf(mid - mid_val); if (diff <= allowedDiff) { diff --git a/src/eez/apps/psu/channel.cpp b/src/eez/apps/psu/channel.cpp index 9e9532f25..1e969b779 100644 --- a/src/eez/apps/psu/channel.cpp +++ b/src/eez/apps/psu/channel.cpp @@ -44,41 +44,6 @@ namespace psu { //////////////////////////////////////////////////////////////////////////////// -static const char *CH_BOARD_NAMES[] = { "None", "DCP505", "DCP405", "DCP405", "DCM220" }; -static const char *CH_REVISION_NAMES[] = { "None", "R1B3", "R1B1", "R2B5", "R1B1" }; -static const char *CH_BOARD_AND_REVISION_NAMES[] = { "None", "DCP505_R1B3", "DCP405_R1B1", "DCP405_R2B5", "DCM220_R1B1" }; - -static uint16_t CH_BOARD_REVISION_FEATURES[] = { - // CH_BOARD_REVISION_NONE - 0, - - // CH_BOARD_REVISION_DCP505_R1B3 - CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE | CH_FEATURE_DPROG | - CH_FEATURE_RPROG | CH_FEATURE_RPOL, - - // CH_BOARD_REVISION_DCP405_R1B1 - CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE | CH_FEATURE_DPROG | - CH_FEATURE_RPROG | CH_FEATURE_RPOL, - - // CH_BOARD_REVISION_DCP405_R2B5 - CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE | CH_FEATURE_DPROG | - CH_FEATURE_RPROG | CH_FEATURE_RPOL, - - // CH_BOARD_REVISION_DCM220_R1B1 - CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE -}; - -static ChannelParams CH_BOARD_REVISION_PARAMS[] = { - // VOLTAGE_GND_OFFSET // CURRENT_GND_OFFSET // CALIBRATION_DATA_TOLERANCE_PERCENT // CALIBRATION_MID_TOLERANCE_PERCENT - { CH_PARAMS_NONE, 0, 0, 10.0f, 1.0f }, // CH_BOARD_REVISION_NONE - { CH_PARAMS_50V_5A, 1.05f, 0.11f, 10.0f, 1.0f }, // CH_BOARD_REVISION_DCP505_R1B3 - { CH_PARAMS_40V_5A, 0.86f, 0.11f, 10.0f, 1.0f }, // CH_BOARD_REVISION_DCP405_R1B1 - { CH_PARAMS_40V_5A, 0.86f, 0.11f, 10.0f, 1.0f }, // CH_BOARD_REVISION_DCP405_R2B5 - { CH_PARAMS_20V_4A, 0, 0, 15.0f, 2.0f } // CH_BOARD_REVISION_DCM220_R1B1 -}; - -//////////////////////////////////////////////////////////////////////////////// - int CH_NUM = 0; Channel Channel::g_channels[CH_MAX]; @@ -217,22 +182,27 @@ float Channel::Simulator::getVoltProgExt() { //////////////////////////////////////////////////////////////////////////////// -void Channel::set(uint8_t slotIndex_, uint8_t subchannelIndex_, uint8_t boardRevision_) { +void Channel::set(uint8_t slotIndex_, uint8_t subchannelIndex_) { + auto slot = g_slots[slotIndex_]; + slotIndex = slotIndex_; - boardRevision = boardRevision_; subchannelIndex = subchannelIndex_; - channelInterface = g_modules[g_slots[slotIndex].moduleType].channelInterfaces ? g_modules[g_slots[slotIndex].moduleType].channelInterfaces[slotIndex] : nullptr; + channelInterface = slot.moduleInfo->channelInterfaces ? slot.moduleInfo->channelInterfaces[slotIndex] : nullptr; + + if (!channelInterface) { + return; + } - params = &CH_BOARD_REVISION_PARAMS[boardRevision]; + channelInterface->getParams(subchannelIndex, params); - u.min = roundChannelValue(UNIT_VOLT, params->U_MIN); - u.max = roundChannelValue(UNIT_VOLT, params->U_MAX); - u.def = roundChannelValue(UNIT_VOLT, params->U_DEF); + u.min = roundChannelValue(UNIT_VOLT, params.U_MIN); + u.max = roundChannelValue(UNIT_VOLT, params.U_MAX); + u.def = roundChannelValue(UNIT_VOLT, params.U_DEF); - i.min = roundChannelValue(UNIT_AMPER, params->I_MIN); - i.max = roundChannelValue(UNIT_AMPER, params->I_MAX); - i.def = roundChannelValue(UNIT_AMPER, params->I_DEF); + i.min = roundChannelValue(UNIT_AMPER, params.I_MIN); + i.max = roundChannelValue(UNIT_AMPER, params.I_MAX); + i.def = roundChannelValue(UNIT_AMPER, params.I_DEF); #ifdef EEZ_PLATFORM_SIMULATOR simulator.load_enabled = true; @@ -383,7 +353,7 @@ void Channel::onPowerDown() { outputEnable(false); doRemoteSensingEnable(false); - if (getFeatures() & CH_FEATURE_RPROG) { + if (params.features & CH_FEATURE_RPROG) { doRemoteProgrammingEnable(false); } @@ -430,7 +400,7 @@ void Channel::reset() { // [SOUR[n]]:VOLT:SENS INTernal doRemoteSensingEnable(false); - if (getFeatures() & CH_FEATURE_RPROG) { + if (params.features & CH_FEATURE_RPROG) { // [SOUR[n]]:VOLT:PROG INTernal doRemoteProgrammingEnable(false); } @@ -448,11 +418,11 @@ void Channel::reset() { // [SOUR[n]]:CURR:STEP // [SOUR[n]]:VOLT // [SOUR[n]]:VOLT:STEP -> set all to default - u.init(params->U_MIN, params->U_DEF_STEP, u.max); - i.init(params->I_MIN, params->I_DEF_STEP, i.max); + u.init(params.U_MIN, params.U_DEF_STEP, u.max); + i.init(params.I_MIN, params.I_DEF_STEP, i.max); maxCurrentLimitCause = MAX_CURRENT_LIMIT_CAUSE_NONE; - p_limit = roundChannelValue(UNIT_WATT, params->PTOT); + p_limit = roundChannelValue(UNIT_WATT, params.PTOT); resetHistory(); @@ -464,8 +434,8 @@ void Channel::reset() { flags.currentTriggerMode = TRIGGER_MODE_FIXED; flags.triggerOutputState = 1; flags.triggerOnListStop = TRIGGER_ON_LIST_STOP_OUTPUT_OFF; - trigger::setVoltage(*this, params->U_MIN); - trigger::setCurrent(*this, params->I_MIN); + trigger::setVoltage(*this, params.U_MIN); + trigger::setCurrent(*this, params.I_MIN); list::resetChannelList(*this); #ifdef EEZ_PLATFORM_SIMULATOR @@ -491,43 +461,43 @@ void Channel::clearCalibrationConf() { cal_conf.flags.i_cal_params_exists_range_high = 0; cal_conf.flags.i_cal_params_exists_range_low = 0; - cal_conf.u.min.dac = cal_conf.u.min.val = cal_conf.u.min.adc = params->U_CAL_VAL_MIN; - cal_conf.u.mid.dac = cal_conf.u.mid.val = cal_conf.u.mid.adc = (params->U_CAL_VAL_MIN + params->U_CAL_VAL_MAX) / 2; - cal_conf.u.max.dac = cal_conf.u.max.val = cal_conf.u.max.adc = params->U_CAL_VAL_MAX; - cal_conf.u.minPossible = params->U_MIN; - cal_conf.u.maxPossible = params->U_MAX; + cal_conf.u.min.dac = cal_conf.u.min.val = cal_conf.u.min.adc = params.U_CAL_VAL_MIN; + cal_conf.u.mid.dac = cal_conf.u.mid.val = cal_conf.u.mid.adc = (params.U_CAL_VAL_MIN + params.U_CAL_VAL_MAX) / 2; + cal_conf.u.max.dac = cal_conf.u.max.val = cal_conf.u.max.adc = params.U_CAL_VAL_MAX; + cal_conf.u.minPossible = params.U_MIN; + cal_conf.u.maxPossible = params.U_MAX; - cal_conf.i[0].min.dac = cal_conf.i[0].min.val = cal_conf.i[0].min.adc = params->I_CAL_VAL_MIN; - cal_conf.i[0].mid.dac = cal_conf.i[0].mid.val = cal_conf.i[0].mid.adc = (params->I_CAL_VAL_MIN + params->I_CAL_VAL_MAX) / 2; - cal_conf.i[0].max.dac = cal_conf.i[0].max.val = cal_conf.i[0].max.adc = params->I_CAL_VAL_MAX; - cal_conf.i[0].minPossible = params->I_MIN; - cal_conf.i[0].maxPossible = params->I_MAX; + cal_conf.i[0].min.dac = cal_conf.i[0].min.val = cal_conf.i[0].min.adc = params.I_CAL_VAL_MIN; + cal_conf.i[0].mid.dac = cal_conf.i[0].mid.val = cal_conf.i[0].mid.adc = (params.I_CAL_VAL_MIN + params.I_CAL_VAL_MAX) / 2; + cal_conf.i[0].max.dac = cal_conf.i[0].max.val = cal_conf.i[0].max.adc = params.I_CAL_VAL_MAX; + cal_conf.i[0].minPossible = params.I_MIN; + cal_conf.i[0].maxPossible = params.I_MAX; - cal_conf.i[1].min.dac = cal_conf.i[1].min.val = cal_conf.i[1].min.adc = params->I_CAL_VAL_MIN / 100; - cal_conf.i[1].mid.dac = cal_conf.i[1].mid.val = cal_conf.i[1].mid.adc = (params->I_CAL_VAL_MIN + params->I_CAL_VAL_MAX) / 2 / 100; - cal_conf.i[1].max.dac = cal_conf.i[1].max.val = cal_conf.i[1].max.adc = params->I_CAL_VAL_MAX / 100; - cal_conf.i[1].minPossible = params->I_MIN; - cal_conf.i[1].maxPossible = params->I_MAX / 100; + cal_conf.i[1].min.dac = cal_conf.i[1].min.val = cal_conf.i[1].min.adc = params.I_CAL_VAL_MIN / 100; + cal_conf.i[1].mid.dac = cal_conf.i[1].mid.val = cal_conf.i[1].mid.adc = (params.I_CAL_VAL_MIN + params.I_CAL_VAL_MAX) / 2 / 100; + cal_conf.i[1].max.dac = cal_conf.i[1].max.val = cal_conf.i[1].max.adc = params.I_CAL_VAL_MAX / 100; + cal_conf.i[1].minPossible = params.I_MIN; + cal_conf.i[1].maxPossible = params.I_MAX / 100; strcpy(cal_conf.calibration_date, ""); strcpy(cal_conf.calibration_remark, CALIBRATION_REMARK_INIT); } void Channel::clearProtectionConf() { - prot_conf.flags.u_state = params->OVP_DEFAULT_STATE; - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + prot_conf.flags.u_state = params.OVP_DEFAULT_STATE; + if (params.features & CH_FEATURE_HW_OVP) { prot_conf.flags.u_type = 1; // HW } else { prot_conf.flags.u_type = 0; // SW } - prot_conf.flags.i_state = params->OCP_DEFAULT_STATE; - prot_conf.flags.p_state = params->OPP_DEFAULT_STATE; + prot_conf.flags.i_state = params.OCP_DEFAULT_STATE; + prot_conf.flags.p_state = params.OPP_DEFAULT_STATE; - prot_conf.u_delay = params->OVP_DEFAULT_DELAY; + prot_conf.u_delay = params.OVP_DEFAULT_DELAY; prot_conf.u_level = u.max; - prot_conf.i_delay = params->OCP_DEFAULT_DELAY; - prot_conf.p_delay = params->OPP_DEFAULT_DELAY; - prot_conf.p_level = params->OPP_DEFAULT_LEVEL; + prot_conf.i_delay = params.OCP_DEFAULT_DELAY; + prot_conf.p_delay = params.OPP_DEFAULT_DELAY; + prot_conf.p_level = params.OPP_DEFAULT_LEVEL; temperature::sensors[temp_sensor::CH1 + channelIndex].prot_conf.state = OTP_CH_DEFAULT_STATE; temperature::sensors[temp_sensor::CH1 + channelIndex].prot_conf.level = OTP_CH_DEFAULT_LEVEL; @@ -545,7 +515,7 @@ bool Channel::test() { outputEnable(false); doRemoteSensingEnable(false); - if (getFeatures() & CH_FEATURE_RPROG) { + if (params.features & CH_FEATURE_RPROG) { doRemoteProgrammingEnable(false); } @@ -558,7 +528,7 @@ bool Channel::test() { } bool Channel::isInstalled() { - return boardRevision != CH_BOARD_REVISION_NONE; + return channelInterface != nullptr; } bool Channel::isPowerOk() { @@ -591,7 +561,7 @@ void Channel::tick(uint32_t tick_usec) { channelInterface->tick(subchannelIndex, tick_usec); - if (getFeatures() & CH_FEATURE_RPOL) { + if (params.features & CH_FEATURE_RPOL) { unsigned rpol = 0; rpol = channelInterface->getRPol(subchannelIndex); @@ -642,7 +612,7 @@ float Channel::getValuePrecision(Unit unit, float value) const { } float Channel::getVoltageResolution() const { - float precision = params->U_RESOLUTION; // 5 mV; + float precision = params.U_RESOLUTION; // 5 mV; if (calibration::isEnabled()) { precision /= 10; @@ -652,11 +622,11 @@ float Channel::getVoltageResolution() const { } float Channel::getCurrentResolution(float value) const { - float precision = params->I_RESOLUTION; // 0.5mA + float precision = params.I_RESOLUTION; // 0.5mA if (hasSupportForCurrentDualRange()) { if ((!isNaN(value) && value <= 0.05f && isMicroAmperAllowed()) || flags.currentCurrentRange == CURRENT_RANGE_LOW) { - precision = params->I_LOW_RESOLUTION; // 5uA + precision = params.I_LOW_RESOLUTION; // 5uA } } @@ -668,7 +638,7 @@ float Channel::getCurrentResolution(float value) const { } float Channel::getPowerResolution() const { - return params->P_RESOLUTION; // 1 mW; + return params.P_RESOLUTION; // 1 mW; } bool Channel::isMicroAmperAllowed() const { @@ -869,7 +839,7 @@ void Channel::update() { setCurrent(i.set); doOutputEnable(flags.outputEnabled); doRemoteSensingEnable(flags.senseEnabled); - if (getFeatures() & CH_FEATURE_RPROG) { + if (params.features & CH_FEATURE_RPROG) { doRemoteProgrammingEnable(flags.rprogEnabled); } @@ -892,37 +862,37 @@ void Channel::doCalibrationEnable(bool enable) { flags._calEnabled = enable; if (enable) { - u.min = roundChannelValue(UNIT_VOLT, MAX(cal_conf.u.minPossible, params->U_MIN)); + u.min = roundChannelValue(UNIT_VOLT, MAX(cal_conf.u.minPossible, params.U_MIN)); if (u.limit < u.min) u.limit = u.min; if (u.set < u.min) setVoltage(u.min); - u.max = roundChannelValue(UNIT_VOLT, MIN(cal_conf.u.maxPossible, params->U_MAX)); + u.max = roundChannelValue(UNIT_VOLT, MIN(cal_conf.u.maxPossible, params.U_MAX)); if (u.limit > u.max) u.limit = u.max; if (u.set > u.max) setVoltage(u.max); - i.min = roundChannelValue(UNIT_AMPER, MAX(cal_conf.i[0].minPossible, params->I_MIN)); - if (i.min < params->I_MIN) - i.min = params->I_MIN; + i.min = roundChannelValue(UNIT_AMPER, MAX(cal_conf.i[0].minPossible, params.I_MIN)); + if (i.min < params.I_MIN) + i.min = params.I_MIN; if (i.limit < i.min) i.limit = i.min; if (i.set < i.min) setCurrent(i.min); - i.max = roundChannelValue(UNIT_AMPER, MIN(cal_conf.i[0].maxPossible, params->I_MAX)); + i.max = roundChannelValue(UNIT_AMPER, MIN(cal_conf.i[0].maxPossible, params.I_MAX)); if (i.limit > i.max) i.limit = i.max; if (i.set > i.max) setCurrent(i.max); } else { - u.min = roundChannelValue(UNIT_VOLT, params->U_MIN); - u.max = roundChannelValue(UNIT_VOLT, params->U_MAX); + u.min = roundChannelValue(UNIT_VOLT, params.U_MIN); + u.max = roundChannelValue(UNIT_VOLT, params.U_MAX); - i.min = roundChannelValue(UNIT_AMPER, params->I_MIN); - i.max = roundChannelValue(UNIT_AMPER, params->I_MAX); + i.min = roundChannelValue(UNIT_AMPER, params.I_MIN); + i.max = roundChannelValue(UNIT_AMPER, params.I_MAX); } u.def = roundChannelValue(UNIT_VOLT, u.min); @@ -993,7 +963,7 @@ float Channel::getCalibratedVoltage(float value) { } #if !defined(EEZ_PLATFORM_SIMULATOR) - value += params->VOLTAGE_GND_OFFSET; + value += params.VOLTAGE_GND_OFFSET; #endif return value; @@ -1007,8 +977,8 @@ void Channel::doSetVoltage(float value) { prot_conf.u_level = u.set; } - if (params->U_MAX != params->U_MAX_CONF) { - value = remap(value, 0, 0, params->U_MAX_CONF, params->U_MAX); + if (params.U_MAX != params.U_MAX_CONF) { + value = remap(value, 0, 0, params.U_MAX_CONF, params.U_MAX); } value = getCalibratedVoltage(value); @@ -1040,8 +1010,8 @@ void Channel::doSetCurrent(float value) { i.set = value; i.mon_dac = 0; - if (params->I_MAX != params->I_MAX_CONF) { - value = remap(value, 0, 0, params->I_MAX_CONF, params->I_MAX); + if (params.I_MAX != params.I_MAX_CONF) { + value = remap(value, 0, 0, params.I_MAX_CONF, params.I_MAX); } if (isCurrentCalibrationEnabled()) { @@ -1130,22 +1100,6 @@ const char *Channel::getCvModeStr() { return "UR"; } -const char *Channel::getBoardName() { - return CH_BOARD_NAMES[boardRevision]; -} - -const char *Channel::getRevisionName() { - return CH_REVISION_NAMES[boardRevision]; -} - -const char *Channel::getBoardAndRevisionName() { - return CH_BOARD_AND_REVISION_NAMES[boardRevision]; -} - -uint16_t Channel::getFeatures() { - return CH_BOARD_REVISION_FEATURES[boardRevision]; -} - float Channel::getVoltageLimit() const { return u.limit; } @@ -1227,7 +1181,7 @@ float Channel::getPowerLimit() const { } float Channel::getPowerMaxLimit() const { - return params->PTOT; + return params.PTOT; } void Channel::setPowerLimit(float limit) { @@ -1306,13 +1260,12 @@ float Channel::getDualRangeGndOffset() { #ifdef EEZ_PLATFORM_SIMULATOR return 0; #else - return flags.currentCurrentRange == CURRENT_RANGE_LOW ? (params->CURRENT_GND_OFFSET / 100) : params->CURRENT_GND_OFFSET; + return flags.currentCurrentRange == CURRENT_RANGE_LOW ? (params.CURRENT_GND_OFFSET / 100) : params.CURRENT_GND_OFFSET; #endif } bool Channel::hasSupportForCurrentDualRange() const { - return (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) && - (boardRevision == CH_BOARD_REVISION_DCP405_R1B1 || boardRevision == CH_BOARD_REVISION_DCP405_R2B5); + return params.features & CH_FEATURE_CURRENT_DUAL_RANGE ? true : false; } void Channel::setCurrentRangeSelectionMode(CurrentRangeSelectionMode mode) { @@ -1342,7 +1295,7 @@ void Channel::enableAutoSelectCurrentRange(bool enable) { } float Channel::getDualRangeMax() { - return flags.currentCurrentRange == CURRENT_RANGE_LOW ? (params->I_MAX / 100) : params->I_MAX; + return flags.currentCurrentRange == CURRENT_RANGE_LOW ? (params.I_MAX / 100) : params.I_MAX; } void Channel::setCurrentRange(uint8_t currentCurrentRange) { diff --git a/src/eez/apps/psu/channel.h b/src/eez/apps/psu/channel.h index 2537c187a..b7a7b6a9f 100644 --- a/src/eez/apps/psu/channel.h +++ b/src/eez/apps/psu/channel.h @@ -44,16 +44,6 @@ struct Value; enum DisplayValue { DISPLAY_VALUE_VOLTAGE, DISPLAY_VALUE_CURRENT, DISPLAY_VALUE_POWER }; -enum ChannelFeatures { - CH_FEATURE_VOLT = (1 << 1), - CH_FEATURE_CURRENT = (1 << 2), - CH_FEATURE_POWER = (1 << 3), - CH_FEATURE_OE = (1 << 4), - CH_FEATURE_DPROG = (1 << 5), - CH_FEATURE_RPROG = (1 << 7), - CH_FEATURE_RPOL = (1 << 8) -}; - enum TriggerMode { TRIGGER_MODE_FIXED, TRIGGER_MODE_LIST, TRIGGER_MODE_STEP }; enum TriggerOnListStop { @@ -88,65 +78,6 @@ struct ProtectionValue { uint32_t alarm_started; }; -struct ChannelParams { - float U_MIN; - float U_DEF; - float U_MAX; - float U_MAX_CONF; - float U_MIN_STEP; - float U_DEF_STEP; - float U_MAX_STEP; - float U_CAL_VAL_MIN; - float U_CAL_VAL_MID; - float U_CAL_VAL_MAX; - float U_CURR_CAL; - bool OVP_DEFAULT_STATE; - float OVP_MIN_DELAY; - float OVP_DEFAULT_DELAY; - float OVP_MAX_DELAY; - float I_MIN; - float I_DEF; - float I_MAX; - float I_MAX_CONF; - float I_MIN_STEP; - float I_DEF_STEP; - float I_MAX_STEP; - float I_CAL_VAL_MIN; - float I_CAL_VAL_MID; - float I_CAL_VAL_MAX; - float I_VOLT_CAL; - bool OCP_DEFAULT_STATE; - float OCP_MIN_DELAY; - float OCP_DEFAULT_DELAY; - float OCP_MAX_DELAY; - bool OPP_DEFAULT_STATE; - float OPP_MIN_DELAY; - float OPP_DEFAULT_DELAY; - float OPP_MAX_DELAY; - float OPP_MIN_LEVEL; - float OPP_DEFAULT_LEVEL; - float OPP_MAX_LEVEL; - float SOA_VIN; - float SOA_PREG_CURR; - float SOA_POSTREG_PTOT; - float PTOT; - float U_RESOLUTION; - float I_RESOLUTION; - float I_LOW_RESOLUTION; - float P_RESOLUTION; - - float VOLTAGE_GND_OFFSET; // [V], (1375 / 65535) * (40V | 50V) - float CURRENT_GND_OFFSET; // [A] - - /// Maximum difference, in percentage, between ADC - /// and real value during calibration. - float CALIBRATION_DATA_TOLERANCE_PERCENT; - - /// Maximum difference, in percentage, between calculated mid value - /// and real mid value during calibration. - float CALIBRATION_MID_TOLERANCE_PERCENT; -}; - /// PSU channel. class Channel { friend class DigitalAnalogConverter; @@ -348,8 +279,6 @@ class Channel { // Slot index. Starts from 0. uint8_t slotIndex; - uint8_t boardRevision; - /// Channel index. Starts from 0. uint8_t channelIndex; @@ -358,7 +287,7 @@ class Channel { ChannelInterface *channelInterface; - ChannelParams *params; + ChannelParams params; Flags flags; @@ -380,7 +309,7 @@ class Channel { Simulator simulator; #endif // EEZ_PLATFORM_SIMULATOR - void set(uint8_t slotIndex, uint8_t subchannelIndex, uint8_t boardRevision); + void set(uint8_t slotIndex, uint8_t subchannelIndex); void setChannelIndex(uint8_t channelIndex); @@ -508,18 +437,6 @@ class Channel { /// Returns "CC", "CV" or "UR" const char *getCvModeStr(); - /// Returns name of the board of this channel. - const char *getBoardName(); - - /// Returns name of the revison of this channel. - const char *getRevisionName(); - - /// Returns name of the board and revison of this channel. - const char *getBoardAndRevisionName(); - - /// Returns features present (check ChannelFeatures) in board revision of this channel. - uint16_t getFeatures(); - /// Returns currently set voltage limit float getVoltageLimit() const; diff --git a/src/eez/apps/psu/channel_dispatcher.cpp b/src/eez/apps/psu/channel_dispatcher.cpp index a8fc525fd..94ab77f82 100644 --- a/src/eez/apps/psu/channel_dispatcher.cpp +++ b/src/eez/apps/psu/channel_dispatcher.cpp @@ -85,14 +85,7 @@ bool isCouplingTypeAllowed(CouplingType couplingType, int *err) { } if (couplingType == COUPLING_TYPE_PARALLEL || couplingType == COUPLING_TYPE_SERIES) { - if (g_slots[0].moduleType != g_slots[1].moduleType) { - if (err) { - *err = SCPI_ERROR_HARDWARE_MISSING; - } - return false; - } - - if (g_slots[0].moduleType != MODULE_TYPE_DCP405 && g_slots[0].moduleType != MODULE_TYPE_DCP505) { + if (!(Channel::get(0).params.features & CH_FEATURE_COUPLING) || !(Channel::get(1).params.features & CH_FEATURE_COUPLING)) { if (err) { *err = SCPI_ERROR_HARDWARE_MISSING; } @@ -116,7 +109,7 @@ bool setCouplingType(CouplingType couplingType, int *err) { channel.outputEnable(false); channel.remoteSensingEnable(false); - if (channel.getFeatures() & CH_FEATURE_RPROG) { + if (channel.params.features & CH_FEATURE_RPROG) { channel.remoteProgrammingEnable(false); } @@ -825,9 +818,9 @@ float getPowerMinLimit(const Channel &channel) { float getPowerMaxLimit(const Channel &channel) { if (channel.channelIndex < 2 && (g_couplingType == COUPLING_TYPE_SERIES || g_couplingType == COUPLING_TYPE_PARALLEL)) { - return 2 * MIN(Channel::get(0).params->PTOT, Channel::get(1).params->PTOT); + return 2 * MIN(Channel::get(0).params.PTOT, Channel::get(1).params.PTOT); } - return channel.params->PTOT; + return channel.params.PTOT; } float getPowerDefaultLimit(const Channel &channel) { @@ -867,23 +860,23 @@ float getOppLevel(Channel &channel) { float getOppMinLevel(Channel &channel) { if (channel.channelIndex < 2 && (g_couplingType == COUPLING_TYPE_SERIES || g_couplingType == COUPLING_TYPE_PARALLEL)) { - return 2 * MAX(Channel::get(0).params->OPP_MIN_LEVEL, Channel::get(1).params->OPP_MIN_LEVEL); + return 2 * MAX(Channel::get(0).params.OPP_MIN_LEVEL, Channel::get(1).params.OPP_MIN_LEVEL); } - return channel.params->OPP_MIN_LEVEL; + return channel.params.OPP_MIN_LEVEL; } float getOppMaxLevel(Channel &channel) { if (channel.channelIndex < 2 && (g_couplingType == COUPLING_TYPE_SERIES || g_couplingType == COUPLING_TYPE_PARALLEL)) { - return 2 * MIN(Channel::get(0).params->OPP_MAX_LEVEL, Channel::get(1).params->OPP_MAX_LEVEL); + return 2 * MIN(Channel::get(0).params.OPP_MAX_LEVEL, Channel::get(1).params.OPP_MAX_LEVEL); } - return channel.params->OPP_MAX_LEVEL; + return channel.params.OPP_MAX_LEVEL; } float getOppDefaultLevel(Channel &channel) { if (channel.channelIndex < 2 && (g_couplingType == COUPLING_TYPE_SERIES || g_couplingType == COUPLING_TYPE_PARALLEL)) { - return Channel::get(0).params->OPP_DEFAULT_LEVEL + Channel::get(1).params->OPP_DEFAULT_LEVEL; + return Channel::get(0).params.OPP_DEFAULT_LEVEL + Channel::get(1).params.OPP_DEFAULT_LEVEL; } - return channel.params->OPP_DEFAULT_LEVEL; + return channel.params.OPP_DEFAULT_LEVEL; } void setOppParameters(Channel &channel, int state, float level, float delay) { diff --git a/src/eez/apps/psu/conf.h b/src/eez/apps/psu/conf.h index 10160e41d..a4b3d55c4 100644 --- a/src/eez/apps/psu/conf.h +++ b/src/eez/apps/psu/conf.h @@ -46,7 +46,7 @@ option. #define MIN_POWER_UP_DELAY 5 /// Default calibration password. -#define CALIBRATION_PASSWORD_DEFAULT "eezpsu" +#define CALIBRATION_PASSWORD_DEFAULT "eezbb3" /// Is OTP enabled by default? #define OTP_AUX_DEFAULT_STATE 1 diff --git a/src/eez/apps/psu/conf_channel.h b/src/eez/apps/psu/conf_channel.h deleted file mode 100644 index 4d8a0ae47..000000000 --- a/src/eez/apps/psu/conf_channel.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * EEZ Modular Firmware - * Copyright (C) 2015-present, Envox d.o.o. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - - -#define CH_BOARD_REVISION_NONE 0 - -#define CH_BOARD_REVISION_DCP505_R1B3 1 - -#define CH_BOARD_REVISION_DCP405_R1B1 2 -#define CH_BOARD_REVISION_DCP405_R2B5 3 - -#define CH_BOARD_REVISION_DCM220_R1B1 4 - - -// OVP_DEFAULT_STATE, OVP_MIN_DELAY, OVP_DEFAULT_DELAY, OVP_MAX_DELAY -#define CH_PARAMS_OVP false, 0.0f, 0.005f, 10.0f - -// U_MIN, U_DEF, U_MAX, U_MAX_CONF, U_MIN_STEP, U_DEF_STEP, U_MAX_STEP, U_CAL_VAL_MIN, U_CAL_VAL_MID, U_CAL_VAL_MAX, U_CURR_CAL -#define CH_PARAMS_U_50V 0.0f, 0.0f, 50.0f, 50.0f, 0.01f, 0.1f, 5.0f, 0.15f, 24.1f, 48.0f, 25.0f, CH_PARAMS_OVP -// U_MIN, U_DEF, U_MAX, U_MAX_CONF, U_MIN_STEP, U_DEF_STEP, U_MAX_STEP, U_CAL_VAL_MIN, U_CAL_VAL_MID, U_CAL_VAL_MAX, U_CURR_CAL -#define CH_PARAMS_U_40V 0.0f, 0.0f, 40.0f, 40.0f, 0.01f, 0.1f, 5.0f, 0.15f, 20.0f, 38.0f, 20.0f, CH_PARAMS_OVP -// U_MIN, U_DEF, U_MAX, U_MAX_CONF, U_MIN_STEP, U_DEF_STEP, U_MAX_STEP, U_CAL_VAL_MIN, U_CAL_VAL_MID, U_CAL_VAL_MAX, U_CURR_CAL -#define CH_PARAMS_U_20V 0.0f, 0.0f, 20.0f, 20.0f, 0.01f, 0.1f, 5.0f, 0.75f, 10.0f, 18.0f, 20.0f, CH_PARAMS_OVP - -// OCP_DEFAULT_STATE, OCP_MIN_DELAY, OCP_DEFAULT_DELAY, OCP_MAX_DELAY -#define CH_PARAMS_OCP false, 0.0f, 0.02f, 10.0f - -// I_MIN, I_DEF, I_MAX, I_MAX_CONF, I_MIN_STEP, I_DEF_STEP, I_MAX_STEP, I_CAL_VAL_MIN, I_CAL_VAL_MID, I_CAL_VAL_MAX, I_VOLT_CAL -#define CH_PARAMS_I_5A 0.0f, 0.0f, 5.0f, 5.0f, 0.01f, 0.01f, 1.0f, 0.05f, 2.425f, 4.8f, 0.1f, CH_PARAMS_OCP -// I_MIN, I_DEF, I_MAX, I_MAX_CONF, I_MIN_STEP, I_DEF_STEP, I_MAX_STEP, I_CAL_VAL_MIN, I_CAL_VAL_MID, I_CAL_VAL_MAX, I_VOLT_CAL -#define CH_PARAMS_I_4A 0.0f, 0.0f, 4.0f, 4.0f, 0.01f, 0.01f, 1.0f, 0.05f, 1.95f, 3.8f, 0.5f, CH_PARAMS_OCP - -// OPP_MIN_DELAY, OPP_DEFAULT_DELAY, OPP_MAX_DELAY -#define CH_PARAMS_OPP_DELAY 1.0f, 10.0f, 300.0f - -// OPP_DEFAULT_STATE, OPP_MIN_LEVEL, SOA_VIN, OPP_DEFAULT_LEVEL, SOA_PREG_CURR, OPP_MAX_LEVEL, SOA_POSTREG_PTOT, PTOT, U_RESOLUTION, I_RESOLUTION, I_LOW_RESOLUTION, P_RESOLUTION -#define CH_PARAMS_NONE CH_PARAMS_U_50V, CH_PARAMS_I_5A, true, CH_PARAMS_OPP_DELAY, 0.0f, 160.0f, 155.0f, 58.0f, 5.0f, 25.0f, 155.0f, 0, 0, 0, 0 -// OPP_DEFAULT_STATE, OPP_MIN_LEVEL, SOA_VIN, OPP_DEFAULT_LEVEL, SOA_PREG_CURR, OPP_MAX_LEVEL, SOA_POSTREG_PTOT, PTOT, U_RESOLUTION, I_RESOLUTION, I_LOW_RESOLUTION, P_RESOLUTION -#define CH_PARAMS_50V_5A CH_PARAMS_U_50V, CH_PARAMS_I_5A, true, CH_PARAMS_OPP_DELAY, 0.0f, 160.0f, 155.0f, 58.0f, 5.0f, 25.0f, 155.0f, 0.005f, 0.0005f, 0.000005f, 0.001f -// OPP_DEFAULT_STATE, OPP_MIN_LEVEL, SOA_VIN, OPP_DEFAULT_LEVEL, SOA_PREG_CURR, OPP_MAX_LEVEL, SOA_POSTREG_PTOT, PTOT, U_RESOLUTION, I_RESOLUTION, I_LOW_RESOLUTION, P_RESOLUTION -#define CH_PARAMS_40V_5A CH_PARAMS_U_40V, CH_PARAMS_I_5A, true, CH_PARAMS_OPP_DELAY, 0.0f, 160.0f, 155.0f, 58.0f, 5.0f, 25.0f, 155.0f, 0.005f, 0.0005f, 0.000005f, 0.001f -// OPP_DEFAULT_STATE, OPP_MIN_LEVEL, SOA_VIN, OPP_DEFAULT_LEVEL, SOA_PREG_CURR, OPP_MAX_LEVEL, SOA_POSTREG_PTOT, PTOT, U_RESOLUTION, I_RESOLUTION, I_LOW_RESOLUTION, P_RESOLUTION -#define CH_PARAMS_20V_4A CH_PARAMS_U_20V, CH_PARAMS_I_4A, true, CH_PARAMS_OPP_DELAY, 0.0f, 70.0f, 80.0f, 58.0f, 5.0f, 25.0f, 80.0f, 0.01f, 0.02f, 0, 0.001f diff --git a/src/eez/apps/psu/gui/calibration.cpp b/src/eez/apps/psu/gui/calibration.cpp index 7766e7b6f..96e5c964a 100644 --- a/src/eez/apps/psu/gui/calibration.cpp +++ b/src/eez/apps/psu/gui/calibration.cpp @@ -57,7 +57,7 @@ void onStartPasswordOk() { g_channel->prot_conf.flags.i_state = 0; g_channel->prot_conf.flags.p_state = 0; - if (g_channel->getFeatures() & CH_FEATURE_RPROG) { + if (g_channel->params.features & CH_FEATURE_RPROG) { g_channel->remoteProgrammingEnable(false); } @@ -288,9 +288,9 @@ void finishStop() { g_channel->outputEnable(false); - g_channel->prot_conf.flags.u_state = g_channel->params->OVP_DEFAULT_STATE; - g_channel->prot_conf.flags.i_state = g_channel->params->OCP_DEFAULT_STATE; - g_channel->prot_conf.flags.p_state = g_channel->params->OPP_DEFAULT_STATE; + g_channel->prot_conf.flags.u_state = g_channel->params.OVP_DEFAULT_STATE; + g_channel->prot_conf.flags.i_state = g_channel->params.OCP_DEFAULT_STATE; + g_channel->prot_conf.flags.p_state = g_channel->params.OPP_DEFAULT_STATE; (*g_stopCallback)(); } diff --git a/src/eez/apps/psu/gui/data.cpp b/src/eez/apps/psu/gui/data.cpp index 2e2e497eb..c7b9a93a2 100644 --- a/src/eez/apps/psu/gui/data.cpp +++ b/src/eez/apps/psu/gui/data.cpp @@ -173,6 +173,7 @@ EnumItem g_dstRuleEnumDefinition[] = { { datetime::DST_RULE_OFF, "Off" }, #if defined(EEZ_PLATFORM_SIMULATOR) EnumItem g_moduleTypeEnumDefinition[] = { { MODULE_TYPE_NONE, "None" }, { MODULE_TYPE_DCP405, "DCP405" }, + { MODULE_TYPE_DCP405B, "DCP405B" }, { MODULE_TYPE_DCM220, "DCM220" }, { 0, 0 } }; #endif @@ -319,7 +320,7 @@ bool compare_CHANNEL_BOARD_INFO_LABEL_value(const Value &a, const Value &b) { } void CHANNEL_BOARD_INFO_LABEL_value_to_text(const Value &value, char *text, int count) { - snprintf(text, count - 1, "CH%d board:", value.getInt()); + snprintf(text, count - 1, "CH%d board:", value.getInt() + 1); text[count - 1] = 0; } @@ -602,7 +603,7 @@ void CHANNEL_TITLE_value_to_text(const Value &value, char *text, int count) { if (channel.flags.trackingEnabled) { snprintf(text, count - 1, "\xA2 #%d", channel.channelIndex + 1); } else { - snprintf(text, count - 1, "%s #%d", channel.getBoardName(), channel.channelIndex + 1); + snprintf(text, count - 1, "%s #%d", g_slots[channel.slotIndex].moduleInfo->moduleName, channel.channelIndex + 1); } } @@ -637,13 +638,16 @@ bool compare_CHANNEL_LONG_TITLE_value(const Value &a, const Value &b) { } void CHANNEL_LONG_TITLE_value_to_text(const Value &value, char *text, int count) { - Channel &channel = Channel::get(value.getInt()); + auto &channel = Channel::get(value.getInt()); + auto &slot = g_slots[channel.slotIndex]; if (channel.flags.trackingEnabled) { - snprintf(text, count - 1, "\xA2 %s #%d: %dV/%dA, %s", channel.getBoardName(), channel.channelIndex + 1, - (int)floor(channel.params->U_MAX), (int)floor(channel.params->I_MAX), channel.getRevisionName()); + snprintf(text, count - 1, "\xA2 %s #%d: %dV/%dA, R%dB%d", slot.moduleInfo->moduleName, slot.channelIndex + 1, + (int)floor(channel.params.U_MAX), (int)floor(channel.params.I_MAX), + (int)(slot.moduleRevision >> 8), (int)(slot.moduleRevision & 0xFF)); } else { - snprintf(text, count - 1, "%s #%d: %dV/%dA, %s", channel.getBoardName(), channel.channelIndex + 1, - (int)floor(channel.params->U_MAX), (int)floor(channel.params->I_MAX), channel.getRevisionName()); + snprintf(text, count - 1, "%s #%d: %dV/%dA, R%dB%d", g_slots[channel.slotIndex].moduleInfo->moduleName, slot.channelIndex + 1, + (int)floor(channel.params.U_MAX), (int)floor(channel.params.I_MAX), + (int)(slot.moduleRevision >> 8), (int)(slot.moduleRevision & 0xFF)); } } @@ -1126,7 +1130,7 @@ int getDefaultView(int channelIndex) { Channel &channel = Channel::get(channelIndex); if (channel.isInstalled()) { if (channel.isOk()) { - int numChannels = g_modules[g_slots[channel.slotIndex].moduleType].numChannels; + int numChannels = g_slots[channel.slotIndex].moduleInfo->numChannels; if (numChannels == 1) { if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_SERIES && channel.channelIndex == 1) { if (persist_conf::devConf.flags.channelsViewMode == CHANNELS_VIEW_MODE_NUMERIC || persist_conf::devConf.flags.channelsViewMode == CHANNELS_VIEW_MODE_VERT_BAR) { @@ -1215,7 +1219,7 @@ void data_slot_max_view(data::DataOperationEnum operation, data::Cursor &cursor, Channel &channel = Channel::get(cursor.i); if (channel.isInstalled()) { if (channel.isOk()) { - int numChannels = g_modules[g_slots[channel.slotIndex].moduleType].numChannels; + int numChannels = g_slots[channel.slotIndex].moduleInfo->numChannels; if (numChannels == 1) { if (persist_conf::devConf.flags.channelsViewModeInMax == CHANNELS_VIEW_MODE_IN_MAX_NUMERIC) { value = channel.isOutputEnabled() ? PAGE_ID_SLOT_MAX_1CH_NUM_ON : PAGE_ID_SLOT_MAX_1CH_NUM_OFF; @@ -1244,7 +1248,7 @@ int getMinView(int channelIndex) { Channel &channel = Channel::get(channelIndex); if (channel.isInstalled()) { if (channel.isOk()) { - int numChannels = g_modules[g_slots[channel.slotIndex].moduleType].numChannels; + int numChannels = g_slots[channel.slotIndex].moduleInfo->numChannels; if (numChannels == 1) { if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_SERIES && channel.channelIndex == 1) { return PAGE_ID_SLOT_MIN_1CH_COUPLED_SERIES; @@ -1296,7 +1300,7 @@ int getMicroView(int channelIndex) { Channel &channel = Channel::get(channelIndex); if (channel.isInstalled()) { if (channel.isOk()) { - int numChannels = g_modules[g_slots[channel.slotIndex].moduleType].numChannels; + int numChannels = g_slots[channel.slotIndex].moduleInfo->numChannels; if (numChannels == 1) { if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_SERIES && channel.channelIndex == 1) { return PAGE_ID_SLOT_MICRO_1CH_COUPLED_SERIES; @@ -1974,7 +1978,7 @@ void data_channel_protection_ovp_type(data::DataOperationEnum operation, data::C if (operation == data::DATA_OPERATION_GET) { int iChannel = cursor.i >= 0 ? cursor.i : (g_channel ? g_channel->channelIndex : 0); Channel &channel = Channel::get(iChannel); - if (g_slots[channel.slotIndex].moduleType == MODULE_TYPE_DCP405) { + if (channel.params.features & CH_FEATURE_HW_OVP) { ChSettingsProtectionSetPage *page = (ChSettingsProtectionSetPage *)getPage(PAGE_ID_CH_SETTINGS_PROT_OVP); if (page) { value = page->type ? 0 : 1; @@ -2012,9 +2016,9 @@ void data_channel_protection_ovp_delay(data::DataOperationEnum operation, data:: } } } else if (operation == data::DATA_OPERATION_GET_MIN) { - value = MakeValue(channel.params->OVP_MIN_DELAY, UNIT_SECOND); + value = MakeValue(channel.params.OVP_MIN_DELAY, UNIT_SECOND); } else if (operation == data::DATA_OPERATION_GET_MAX) { - value = MakeValue(channel.params->OVP_MAX_DELAY, UNIT_SECOND); + value = MakeValue(channel.params.OVP_MAX_DELAY, UNIT_SECOND); } else if (operation == data::DATA_OPERATION_SET) { channel_dispatcher::setOvpParameters(channel, channel.prot_conf.flags.u_state ? 1 : 0, channel.prot_conf.flags.u_type ? 1 : 0, channel_dispatcher::getUProtectionLevel(channel), value.getFloat()); } @@ -2069,9 +2073,9 @@ void data_channel_protection_ocp_delay(data::DataOperationEnum operation, data:: } } } else if (operation == data::DATA_OPERATION_GET_MIN) { - value = MakeValue(channel.params->OCP_MIN_DELAY, UNIT_SECOND); + value = MakeValue(channel.params.OCP_MIN_DELAY, UNIT_SECOND); } else if (operation == data::DATA_OPERATION_GET_MAX) { - value = MakeValue(channel.params->OCP_MAX_DELAY, UNIT_SECOND); + value = MakeValue(channel.params.OCP_MAX_DELAY, UNIT_SECOND); } else if (operation == data::DATA_OPERATION_SET) { channel_dispatcher::setOcpParameters(channel, channel.prot_conf.flags.i_state ? 1 : 0, value.getFloat()); } @@ -2156,9 +2160,9 @@ void data_channel_protection_opp_delay(data::DataOperationEnum operation, data:: } } } else if (operation == data::DATA_OPERATION_GET_MIN) { - value = MakeValue(channel.params->OPP_MIN_DELAY, UNIT_SECOND); + value = MakeValue(channel.params.OPP_MIN_DELAY, UNIT_SECOND); } else if (operation == data::DATA_OPERATION_GET_MAX) { - value = MakeValue(channel.params->OPP_MAX_DELAY, UNIT_SECOND); + value = MakeValue(channel.params.OPP_MAX_DELAY, UNIT_SECOND); } else if (operation == data::DATA_OPERATION_SET) { channel_dispatcher::setOppParameters(channel, channel.prot_conf.flags.p_state ? 1 : 0, channel_dispatcher::getPowerProtectionLevel(channel), value.getFloat()); } @@ -2326,7 +2330,7 @@ void data_channel_has_advanced_options(data::DataOperationEnum operation, data:: if (operation == data::DATA_OPERATION_GET) { int iChannel = cursor.i >= 0 ? cursor.i : (g_channel ? g_channel->channelIndex : 0); Channel &channel = Channel::get(iChannel); - value = g_slots[channel.slotIndex].moduleType == MODULE_TYPE_DCP405 ? 1 : 0; + value = channel.params.features & (CH_FEATURE_DPROG | CH_FEATURE_RPROG | CH_FEATURE_RPOL) ? 1 : 0; } } @@ -2340,7 +2344,7 @@ void data_channel_rsense_status(data::DataOperationEnum operation, data::Cursor void data_channel_rprog_installed(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value) { if (operation == data::DATA_OPERATION_GET) { - value = g_channel->getFeatures() & CH_FEATURE_RPROG ? 1 : 0; + value = g_channel->params.features & CH_FEATURE_RPROG ? 1 : 0; } } @@ -2356,7 +2360,7 @@ void data_channel_dprog_installed(data::DataOperationEnum operation, data::Curso if (operation == data::DATA_OPERATION_GET) { int iChannel = cursor.i >= 0 ? cursor.i : (g_channel ? g_channel->channelIndex : 0); Channel &channel = Channel::get(iChannel); - value = channel.getFeatures() & CH_FEATURE_DPROG ? 1 : 0; + value = channel.params.features & CH_FEATURE_DPROG ? 1 : 0; } } @@ -2710,7 +2714,7 @@ void data_sys_fan_speed(data::DataOperationEnum operation, data::Cursor &cursor, void data_channel_board_info_label(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value) { if (operation == data::DATA_OPERATION_GET) { if (cursor.i >= 0 && cursor.i < CH_NUM) { - value = data::Value(cursor.i + 1, VALUE_TYPE_CHANNEL_BOARD_INFO_LABEL); + value = data::Value(cursor.i, VALUE_TYPE_CHANNEL_BOARD_INFO_LABEL); } } } @@ -2718,7 +2722,7 @@ void data_channel_board_info_label(data::DataOperationEnum operation, data::Curs void data_channel_board_info_revision(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value) { if (operation == data::DATA_OPERATION_GET) { if (cursor.i >= 0 && cursor.i < CH_NUM) { - value = data::Value(Channel::get(cursor.i).getBoardAndRevisionName()); + value = Value((int)Channel::get(cursor.i).slotIndex, VALUE_TYPE_SLOT_INFO); } } } diff --git a/src/eez/apps/psu/gui/page_ch_settings_adv.cpp b/src/eez/apps/psu/gui/page_ch_settings_adv.cpp index 90671c482..89c70fd3c 100644 --- a/src/eez/apps/psu/gui/page_ch_settings_adv.cpp +++ b/src/eez/apps/psu/gui/page_ch_settings_adv.cpp @@ -56,14 +56,13 @@ void ChSettingsAdvOptionsPage::toggleDprog() { //////////////////////////////////////////////////////////////////////////////// -void ChSettingsAdvRangesPage::onModeSet(uint8_t value) { +void ChSettingsAdvRangesPage::onModeSet(uint16_t value) { popPage(); g_channel->setCurrentRangeSelectionMode((CurrentRangeSelectionMode)value); } void ChSettingsAdvRangesPage::selectMode() { - pushSelectFromEnumPage(g_channelCurrentRangeSelectionModeEnumDefinition, - g_channel->getCurrentRangeSelectionMode(), 0, onModeSet); + pushSelectFromEnumPage(g_channelCurrentRangeSelectionModeEnumDefinition, g_channel->getCurrentRangeSelectionMode(), 0, onModeSet); } void ChSettingsAdvRangesPage::toggleAutoRanging() { @@ -78,36 +77,34 @@ void ChSettingsAdvViewPage::pageAlloc() { origYTViewRate = ytViewRate = g_channel->ytViewRate; } -bool ChSettingsAdvViewPage::isDisabledDisplayValue1(uint8_t value) { +bool ChSettingsAdvViewPage::isDisabledDisplayValue1(uint16_t value) { ChSettingsAdvViewPage *page = (ChSettingsAdvViewPage *)getPreviousPage(); return value == page->displayValue2; } -void ChSettingsAdvViewPage::onDisplayValue1Set(uint8_t value) { +void ChSettingsAdvViewPage::onDisplayValue1Set(uint16_t value) { popPage(); ChSettingsAdvViewPage *page = (ChSettingsAdvViewPage *)getActivePage(); - page->displayValue1 = value; + page->displayValue1 = (uint8_t)value; } void ChSettingsAdvViewPage::editDisplayValue1() { - pushSelectFromEnumPage(g_channelDisplayValueEnumDefinition, displayValue1, - isDisabledDisplayValue1, onDisplayValue1Set); + pushSelectFromEnumPage(g_channelDisplayValueEnumDefinition, displayValue1, isDisabledDisplayValue1, onDisplayValue1Set); } -bool ChSettingsAdvViewPage::isDisabledDisplayValue2(uint8_t value) { +bool ChSettingsAdvViewPage::isDisabledDisplayValue2(uint16_t value) { ChSettingsAdvViewPage *page = (ChSettingsAdvViewPage *)getPreviousPage(); return value == page->displayValue1; } -void ChSettingsAdvViewPage::onDisplayValue2Set(uint8_t value) { +void ChSettingsAdvViewPage::onDisplayValue2Set(uint16_t value) { popPage(); ChSettingsAdvViewPage *page = (ChSettingsAdvViewPage *)getActivePage(); - page->displayValue2 = value; + page->displayValue2 = (uint8_t)value; } void ChSettingsAdvViewPage::editDisplayValue2() { - pushSelectFromEnumPage(g_channelDisplayValueEnumDefinition, displayValue2, - isDisabledDisplayValue2, onDisplayValue2Set); + pushSelectFromEnumPage(g_channelDisplayValueEnumDefinition, displayValue2, isDisabledDisplayValue2, onDisplayValue2Set); } void ChSettingsAdvViewPage::onYTViewRateSet(float value) { diff --git a/src/eez/apps/psu/gui/page_ch_settings_adv.h b/src/eez/apps/psu/gui/page_ch_settings_adv.h index 8aca45901..555a0f66f 100644 --- a/src/eez/apps/psu/gui/page_ch_settings_adv.h +++ b/src/eez/apps/psu/gui/page_ch_settings_adv.h @@ -38,7 +38,7 @@ class ChSettingsAdvRangesPage : public Page { void toggleAutoRanging(); private: - static void onModeSet(uint8_t value); + static void onModeSet(uint16_t value); }; class ChSettingsAdvViewPage : public SetPage { @@ -62,10 +62,10 @@ class ChSettingsAdvViewPage : public SetPage { uint8_t origDisplayValue2; float origYTViewRate; - static bool isDisabledDisplayValue1(uint8_t value); - static void onDisplayValue1Set(uint8_t value); - static bool isDisabledDisplayValue2(uint8_t value); - static void onDisplayValue2Set(uint8_t value); + static bool isDisabledDisplayValue1(uint16_t value); + static void onDisplayValue1Set(uint16_t value); + static bool isDisabledDisplayValue2(uint16_t value); + static void onDisplayValue2Set(uint16_t value); static void onYTViewRateSet(float value); }; diff --git a/src/eez/apps/psu/gui/page_ch_settings_protection.cpp b/src/eez/apps/psu/gui/page_ch_settings_protection.cpp index 2b94c5070..9277df95e 100644 --- a/src/eez/apps/psu/gui/page_ch_settings_protection.cpp +++ b/src/eez/apps/psu/gui/page_ch_settings_protection.cpp @@ -174,9 +174,9 @@ void ChSettingsOvpProtectionPage::pageAlloc() { defLevel = channel_dispatcher::getUMax(*g_channel); origDelay = delay = MakeValue(g_channel->prot_conf.u_delay, UNIT_SECOND); - minDelay = g_channel->params->OVP_MIN_DELAY; - maxDelay = g_channel->params->OVP_MAX_DELAY; - defaultDelay = g_channel->params->OVP_DEFAULT_DELAY; + minDelay = g_channel->params.OVP_MIN_DELAY; + maxDelay = g_channel->params.OVP_MAX_DELAY; + defaultDelay = g_channel->params.OVP_DEFAULT_DELAY; } void ChSettingsOvpProtectionPage::onSetParamsOk() { @@ -209,9 +209,9 @@ void ChSettingsOcpProtectionPage::pageAlloc() { origLevel = level = 0; origDelay = delay = MakeValue(g_channel->prot_conf.i_delay, UNIT_SECOND); - minDelay = g_channel->params->OCP_MIN_DELAY; - maxDelay = g_channel->params->OCP_MAX_DELAY; - defaultDelay = g_channel->params->OCP_DEFAULT_DELAY; + minDelay = g_channel->params.OCP_MIN_DELAY; + maxDelay = g_channel->params.OCP_MAX_DELAY; + defaultDelay = g_channel->params.OCP_DEFAULT_DELAY; } void ChSettingsOcpProtectionPage::onSetParamsOk() { @@ -246,9 +246,9 @@ void ChSettingsOppProtectionPage::pageAlloc() { defLevel = channel_dispatcher::getOppDefaultLevel(*g_channel); origDelay = delay = MakeValue(g_channel->prot_conf.p_delay, UNIT_SECOND); - minDelay = g_channel->params->OPP_MIN_DELAY; - maxDelay = g_channel->params->OPP_MAX_DELAY; - defaultDelay = g_channel->params->OPP_DEFAULT_DELAY; + minDelay = g_channel->params.OPP_MIN_DELAY; + maxDelay = g_channel->params.OPP_MAX_DELAY; + defaultDelay = g_channel->params.OPP_DEFAULT_DELAY; } void ChSettingsOppProtectionPage::onSetParamsOk() { diff --git a/src/eez/apps/psu/gui/page_ch_settings_trigger.cpp b/src/eez/apps/psu/gui/page_ch_settings_trigger.cpp index 4d41f8bf3..d82014d65 100644 --- a/src/eez/apps/psu/gui/page_ch_settings_trigger.cpp +++ b/src/eez/apps/psu/gui/page_ch_settings_trigger.cpp @@ -51,12 +51,12 @@ void ChSettingsTriggerPage::onFinishTriggerModeSet() { profile::save(); } -void ChSettingsTriggerPage::onTriggerModeSet(uint8_t value) { +void ChSettingsTriggerPage::onTriggerModeSet(uint16_t value) { popPage(); if (channel_dispatcher::getVoltageTriggerMode(*g_channel) != value || channel_dispatcher::getCurrentTriggerMode(*g_channel) != value) { - g_newTriggerMode = value; + g_newTriggerMode = (uint8_t)value; if (trigger::isInitiated() || list::isActive()) { yesNoDialog(PAGE_ID_YES_NO, "Trigger is active. Are you sure?", onFinishTriggerModeSet, @@ -68,9 +68,7 @@ void ChSettingsTriggerPage::onTriggerModeSet(uint8_t value) { } void ChSettingsTriggerPage::editTriggerMode() { - pushSelectFromEnumPage(g_channelTriggerModeEnumDefinition, - channel_dispatcher::getVoltageTriggerMode(*g_channel), 0, - onTriggerModeSet); + pushSelectFromEnumPage(g_channelTriggerModeEnumDefinition, channel_dispatcher::getVoltageTriggerMode(*g_channel), 0, onTriggerModeSet); } void ChSettingsTriggerPage::onVoltageTriggerValueSet(float value) { @@ -127,16 +125,14 @@ void ChSettingsTriggerPage::toggleOutputState() { profile::save(); } -void ChSettingsTriggerPage::onTriggerOnListStopSet(uint8_t value) { +void ChSettingsTriggerPage::onTriggerOnListStopSet(uint16_t value) { popPage(); channel_dispatcher::setTriggerOnListStop(*g_channel, (TriggerOnListStop)value); profile::save(); } void ChSettingsTriggerPage::editTriggerOnListStop() { - pushSelectFromEnumPage(g_channelTriggerOnListStopEnumDefinition, - channel_dispatcher::getTriggerOnListStop(*g_channel), 0, - onTriggerOnListStopSet); + pushSelectFromEnumPage(g_channelTriggerOnListStopEnumDefinition, channel_dispatcher::getTriggerOnListStop(*g_channel), 0, onTriggerOnListStopSet); } void ChSettingsTriggerPage::onListCountSet(float value) { diff --git a/src/eez/apps/psu/gui/page_ch_settings_trigger.h b/src/eez/apps/psu/gui/page_ch_settings_trigger.h index fa766fb25..57d38cf6e 100644 --- a/src/eez/apps/psu/gui/page_ch_settings_trigger.h +++ b/src/eez/apps/psu/gui/page_ch_settings_trigger.h @@ -44,9 +44,9 @@ class ChSettingsTriggerPage : public Page { private: static void onFinishTriggerModeSet(); - static void onTriggerModeSet(uint8_t value); + static void onTriggerModeSet(uint16_t value); - static void onTriggerOnListStopSet(uint8_t value); + static void onTriggerOnListStopSet(uint16_t value); static void onVoltageTriggerValueSet(float value); static void onCurrentTriggerValueSet(float value); diff --git a/src/eez/apps/psu/gui/page_sys_settings.cpp b/src/eez/apps/psu/gui/page_sys_settings.cpp index 57c567310..bcd70f202 100644 --- a/src/eez/apps/psu/gui/page_sys_settings.cpp +++ b/src/eez/apps/psu/gui/page_sys_settings.cpp @@ -150,7 +150,7 @@ void SysSettingsDateTimePage::edit() { } } -void SysSettingsDateTimePage::onDstRuleSet(uint8_t value) { +void SysSettingsDateTimePage::onDstRuleSet(uint16_t value) { popPage(); SysSettingsDateTimePage *page = (SysSettingsDateTimePage *)getActivePage(); page->dstRule = (datetime::DstRule)value; @@ -641,7 +641,7 @@ void SysSettingsTriggerPage::pageAlloc() { trigger::isContinuousInitializationEnabled(); } -void SysSettingsTriggerPage::onTriggerSourceSet(uint8_t value) { +void SysSettingsTriggerPage::onTriggerSourceSet(uint16_t value) { popPage(); SysSettingsTriggerPage *page = (SysSettingsTriggerPage *)getActivePage(); page->m_source = (trigger::Source)value; @@ -717,7 +717,7 @@ void SysSettingsIOPinsPage::togglePolarity() { m_polarity[i] = m_polarity[i] == io_pins::POLARITY_NEGATIVE ? io_pins::POLARITY_POSITIVE : io_pins::POLARITY_NEGATIVE; } -void SysSettingsIOPinsPage::onFunctionSet(uint8_t value) { +void SysSettingsIOPinsPage::onFunctionSet(uint16_t value) { popPage(); SysSettingsIOPinsPage *page = (SysSettingsIOPinsPage *)getActivePage(); page->m_function[page->pinNumber] = (io_pins::Function)value; @@ -767,7 +767,7 @@ void SysSettingsSerialPage::toggle() { m_enabled = !m_enabled; } -void SysSettingsSerialPage::onParitySet(uint8_t value) { +void SysSettingsSerialPage::onParitySet(uint16_t value) { popPage(); SysSettingsSerialPage *page = (SysSettingsSerialPage *)getActivePage(); page->m_parity = (serial::Parity)value; diff --git a/src/eez/apps/psu/gui/page_sys_settings.h b/src/eez/apps/psu/gui/page_sys_settings.h index af6cf846b..709caded6 100644 --- a/src/eez/apps/psu/gui/page_sys_settings.h +++ b/src/eez/apps/psu/gui/page_sys_settings.h @@ -57,7 +57,7 @@ class SysSettingsDateTimePage : public SetPage { int16_t origTimeZone; datetime::DstRule origDstRule; - static void onDstRuleSet(uint8_t value); + static void onDstRuleSet(uint16_t value); #if OPTION_ETHERNET enum { @@ -256,7 +256,7 @@ class SysSettingsTriggerPage : public SetPage { float m_delayOrig; bool m_initiateContinuouslyOrig; - static void onTriggerSourceSet(uint8_t value); + static void onTriggerSourceSet(uint16_t value); static void onDelaySet(float value); }; @@ -279,7 +279,7 @@ class SysSettingsIOPinsPage : public SetPage { io_pins::Polarity m_polarityOrig[NUM_IO_PINS]; io_pins::Function m_functionOrig[NUM_IO_PINS]; - static void onFunctionSet(uint8_t value); + static void onFunctionSet(uint16_t value); }; class SysSettingsSerialPage : public SetPage { @@ -301,7 +301,7 @@ class SysSettingsSerialPage : public SetPage { uint8_t m_baudIndexOrig; serial::Parity m_parityOrig; - static void onParitySet(uint8_t value); + static void onParitySet(uint16_t value); }; class SysSettingsTrackingPage : public SetPage { diff --git a/src/eez/apps/psu/gui/psu.cpp b/src/eez/apps/psu/gui/psu.cpp index 888a3fb57..f70db0b73 100644 --- a/src/eez/apps/psu/gui/psu.cpp +++ b/src/eez/apps/psu/gui/psu.cpp @@ -596,9 +596,9 @@ void onSetPowerTripDelay(float delay) { void changePowerTripDelay(int iChannel) { g_iChannelSetValue = iChannel; Channel &channel = Channel::get(iChannel); - float minDelay = channel.params->OPP_MIN_DELAY; - float maxDelay = channel.params->OPP_MAX_DELAY; - float defaultDelay = channel.params->OPP_DEFAULT_DELAY; + float minDelay = channel.params.OPP_MIN_DELAY; + float maxDelay = channel.params.OPP_MAX_DELAY; + float defaultDelay = channel.params.OPP_DEFAULT_DELAY; changeValue(channel, MakeValue(channel.prot_conf.p_delay, UNIT_SECOND), minDelay, maxDelay, defaultDelay, onSetPowerTripDelay); diff --git a/src/eez/apps/psu/init.cpp b/src/eez/apps/psu/init.cpp index 4aa8d9e5e..b96f00ef1 100644 --- a/src/eez/apps/psu/init.cpp +++ b/src/eez/apps/psu/init.cpp @@ -102,7 +102,7 @@ void oneIter() { } bool measureAllAdcValuesOnChannel(int channelIndex) { - if (g_slots[Channel::get(channelIndex).slotIndex].moduleType == MODULE_TYPE_NONE) { + if (g_slots[Channel::get(channelIndex).slotIndex].moduleInfo->moduleType == MODULE_TYPE_NONE) { return true; } diff --git a/src/eez/apps/psu/profile.cpp b/src/eez/apps/psu/profile.cpp index 2a624a603..57e71ba4d 100644 --- a/src/eez/apps/psu/profile.cpp +++ b/src/eez/apps/psu/profile.cpp @@ -190,7 +190,7 @@ void recallChannelsFromProfile(Parameters *profile, int location) { channel.flags.outputEnabled = channel.isTripped() ? 0 : profile->channels[i].flags.output_enabled; channel.flags.senseEnabled = profile->channels[i].flags.sense_enabled; - if (channel.getFeatures() & CH_FEATURE_RPROG) { + if (channel.params.features & CH_FEATURE_RPROG) { channel.flags.rprogEnabled = profile->channels[i].flags.rprog_enabled; } else { channel.flags.rprogEnabled = 0; @@ -244,14 +244,15 @@ void fillProfile(Parameters *pProfile) { for (int i = 0; i < CH_MAX; ++i) { Channel &channel = Channel::get(i); if (i < CH_NUM && channel.isInstalled()) { - profile.channels[i].flags.moduleType = g_slots[channel.slotIndex].moduleType; + profile.channels[i].moduleType = g_slots[channel.slotIndex].moduleInfo->moduleType; + profile.channels[i].moduleRevision = g_slots[channel.slotIndex].moduleRevision; profile.channels[i].flags.parameters_are_valid = 1; profile.channels[i].flags.output_enabled = channel.flags.outputEnabled; profile.channels[i].flags.sense_enabled = channel.flags.senseEnabled; - if (channel.getFeatures() & CH_FEATURE_RPROG) { + if (channel.params.features & CH_FEATURE_RPROG) { profile.channels[i].flags.rprog_enabled = channel.flags.rprogEnabled; } else { @@ -381,7 +382,7 @@ void tick() { bool checkProfileModuleMatch(Parameters *profile) { for (int i = 0; i < CH_NUM; ++i) { Channel &channel = Channel::get(i); - if (profile->channels[i].flags.parameters_are_valid && profile->channels[i].flags.moduleType != g_slots[channel.slotIndex].moduleType) { + if (profile->channels[i].flags.parameters_are_valid && profile->channels[i].moduleType != g_slots[channel.slotIndex].moduleInfo->moduleType) { return false; // mismatch } } diff --git a/src/eez/apps/psu/profile.h b/src/eez/apps/psu/profile.h index 888daaf78..c496cc0d3 100644 --- a/src/eez/apps/psu/profile.h +++ b/src/eez/apps/psu/profile.h @@ -29,7 +29,6 @@ namespace profile { /// Channel binary flags stored in profile. struct ChannelFlags { - unsigned moduleType: 8; unsigned output_enabled : 1; unsigned sense_enabled : 1; unsigned u_state : 1; @@ -52,6 +51,8 @@ struct ChannelFlags { /// Channel parameters stored in profile. struct ChannelParameters { + uint16_t moduleType; + uint16_t moduleRevision; ChannelFlags flags; float u_set; float u_step; @@ -93,7 +94,7 @@ struct Parameters { temperature::ProtectionConfiguration temp_prot[temp_sensor::MAX_NUM_TEMP_SENSORS]; }; -static const uint16_t PROFILE_VERSION = 10; +static const uint16_t PROFILE_VERSION = 11; // auto save support extern bool g_profileDirty; diff --git a/src/eez/apps/psu/psu.cpp b/src/eez/apps/psu/psu.cpp index bfbbef227..3af7995d0 100644 --- a/src/eez/apps/psu/psu.cpp +++ b/src/eez/apps/psu/psu.cpp @@ -223,34 +223,27 @@ void init() { int channelIndex = 0; for (uint8_t slotIndex = 0; slotIndex < NUM_SLOTS; slotIndex++) { - uint16_t value; - if (!bp3c::eeprom::read(slotIndex, (uint8_t *)&value, 2, (uint16_t)0)) { - g_slots[slotIndex].moduleType = MODULE_TYPE_NONE; - g_slots[slotIndex].boardRevision = CH_BOARD_REVISION_NONE; - } else if (value == 405) { - g_slots[slotIndex].moduleType = MODULE_TYPE_DCP405; - g_slots[slotIndex].boardRevision = CH_BOARD_REVISION_DCP405_R1B1; - } else if (value == 406) { - g_slots[slotIndex].moduleType = MODULE_TYPE_DCP405; - g_slots[slotIndex].boardRevision = CH_BOARD_REVISION_DCP405_R2B5; - } else if (value == 505) { - g_slots[slotIndex].moduleType = MODULE_TYPE_DCP505; - g_slots[slotIndex].boardRevision = CH_BOARD_REVISION_DCP505_R1B3; - } else if (value == 220) { - g_slots[slotIndex].moduleType = MODULE_TYPE_DCM220; - g_slots[slotIndex].boardRevision = CH_BOARD_REVISION_DCM220_R1B1; - } else { - g_slots[slotIndex].moduleType = MODULE_TYPE_NONE; - g_slots[slotIndex].boardRevision = CH_BOARD_REVISION_NONE; + auto& slot = g_slots[slotIndex]; + + static const uint16_t ADDRESS = 0; + uint16_t value[2]; + if (!bp3c::eeprom::read(slotIndex, (uint8_t *)&value, sizeof(value), ADDRESS)) { + value[0] = MODULE_TYPE_NONE; + value[1] = 0; } - g_slots[slotIndex].channelIndex = channelIndex; + uint16_t moduleType = value[0]; + uint16_t moduleRevision = value[1]; + + slot.moduleInfo = getModuleInfo(moduleType); + slot.moduleRevision = moduleRevision; + slot.channelIndex = channelIndex; - for (uint8_t subchannelIndex = 0; subchannelIndex < g_modules[g_slots[slotIndex].moduleType].numChannels; subchannelIndex++) { - Channel::get(channelIndex++).set(slotIndex, subchannelIndex, g_slots[slotIndex].boardRevision); + for (uint8_t subchannelIndex = 0; subchannelIndex < slot.moduleInfo->numChannels; subchannelIndex++) { + Channel::get(channelIndex++).set(slotIndex, subchannelIndex); } - if (g_slots[slotIndex].moduleType != MODULE_TYPE_NONE) { + if (slot.moduleInfo->moduleType != MODULE_TYPE_NONE) { persist_conf::loadModuleConf(slotIndex); ontime::g_moduleCounters[slotIndex].init(); } @@ -616,7 +609,7 @@ bool powerUp() { ontime::g_mcuCounter.start(); for (int slotIndex = 0; slotIndex < NUM_SLOTS; slotIndex++) { - if (g_slots[slotIndex].moduleType != MODULE_TYPE_NONE) { + if (g_slots[slotIndex].moduleInfo->moduleType != MODULE_TYPE_NONE) { ontime::g_moduleCounters[slotIndex].start(); } } @@ -684,7 +677,7 @@ void powerDown() { ontime::g_mcuCounter.stop(); for (int slotIndex = 0; slotIndex < NUM_SLOTS; slotIndex++) { - if (g_slots[slotIndex].moduleType != MODULE_TYPE_NONE) { + if (g_slots[slotIndex].moduleInfo->moduleType != MODULE_TYPE_NONE) { ontime::g_moduleCounters[slotIndex].stop(); } } @@ -824,7 +817,7 @@ void onProtectionTripped() { void tick_onTimeCounters(uint32_t tickCount) { ontime::g_mcuCounter.tick(tickCount); for (int slotIndex = 0; slotIndex < NUM_SLOTS; slotIndex++) { - if (g_slots[slotIndex].moduleType != MODULE_TYPE_NONE) { + if (g_slots[slotIndex].moduleInfo->moduleType != MODULE_TYPE_NONE) { ontime::g_moduleCounters[slotIndex].tick(tickCount); } } diff --git a/src/eez/apps/psu/psu.h b/src/eez/apps/psu/psu.h index 30d395a25..633edb1a8 100644 --- a/src/eez/apps/psu/psu.h +++ b/src/eez/apps/psu/psu.h @@ -26,7 +26,6 @@ #include #include -#include #include #include diff --git a/src/eez/apps/psu/scpi/debug.cpp b/src/eez/apps/psu/scpi/debug.cpp index 3592b3559..ce9f042bd 100644 --- a/src/eez/apps/psu/scpi/debug.cpp +++ b/src/eez/apps/psu/scpi/debug.cpp @@ -32,10 +32,6 @@ #include #include -#if defined(EEZ_PLATFORM_STM32) -#include -#endif - extern "C" { #include "py/compile.h" #include "py/runtime.h" @@ -459,307 +455,11 @@ scpi_result_t scpi_cmd_debugPythonQ(scpi_t *context) { #endif // DEBUG } -#if defined(EEZ_PLATFORM_STM32) -#define BUFFER_SIZE 10 - -static uint8_t g_output[BUFFER_SIZE]; -static uint8_t g_input[BUFFER_SIZE]; - -static bool g_synchronized; - -#define SPI_SLAVE_SYNBYTE 0x53 -#define SPI_MASTER_SYNBYTE 0xAC - -void specDelayUs(uint32_t microseconds) { - while (microseconds--) { - // 216 NOP's - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - __ASM volatile ("NOP"); - } -} - -void masterSynchro(void) { - uint8_t txackbytes = SPI_MASTER_SYNBYTE, rxackbytes = 0x00; - do { - taskENTER_CRITICAL(); - spi::selectA(2); - spi::transfer(2, &txackbytes, &rxackbytes, 1); - spi::deselectA(2); - taskEXIT_CRITICAL(); - } while(rxackbytes != SPI_SLAVE_SYNBYTE); - - while (HAL_GPIO_ReadPin(SPI5_IRQ_GPIO_Port, SPI5_IRQ_Pin) != GPIO_PIN_SET); -} -#endif - scpi_result_t scpi_cmd_debugDcm220Q(scpi_t *context) { #if defined(EEZ_PLATFORM_STM32) - int32_t cmd; - if (!SCPI_ParamInt(context, &cmd, TRUE)) { - return SCPI_RES_ERR; - } - - int32_t param; - int mandatoryParam = cmd == 14 || cmd == 15 || cmd == 24 || cmd == 25; - if (!SCPI_ParamInt(context, ¶m, mandatoryParam)) { - if (mandatoryParam || SCPI_ParamErrorOccurred(context)) { - return SCPI_RES_ERR; - } - } - - g_output[0] = (uint8_t)(cmd | 0x40); - g_output[1] = (uint8_t)(param & 0xFF); - g_output[2] = (uint8_t)((param >> 8) & 0xFF); - - for (int i = 3; i < BUFFER_SIZE; i++) { - g_output[i] = 0xFF; - } - - if (!g_synchronized) { - spi::init(2, spi::CHIP_DCM220); - masterSynchro(); - g_synchronized = true; - } - - if (cmd == 31) { - HAL_GPIO_WritePin(OE_SYNC_GPIO_Port, OE_SYNC_Pin, GPIO_PIN_RESET); - } - - taskENTER_CRITICAL(); - spi::selectA(2); - spi::transfer(2, g_output, g_input, BUFFER_SIZE); - spi::deselectA(2); - taskEXIT_CRITICAL(); - - if (cmd == 31) { - HAL_GPIO_WritePin(OE_SYNC_GPIO_Port, OE_SYNC_Pin, GPIO_PIN_SET); - } - char text[100]; - - sprintf(text, "0x%02X, 0x%02X, U_MON_1=%d, I_MON_1=%d, U_MON_2=%d, I_MON_2=%d", - (int)g_input[0], (int)g_input[1], - (int)(uint16_t)(g_input[2] | (g_input[3] << 8)), - (int)(uint16_t)(g_input[4] | (g_input[5] << 8)), - (int)(uint16_t)(g_input[6] | (g_input[7] << 8)), - (int)(uint16_t)(g_input[8] | (g_input[9] << 8))); - + sprintf(text, "TODO"); SCPI_ResultText(context, text); - return SCPI_RES_OK; #else SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); diff --git a/src/eez/apps/psu/scpi/diag.cpp b/src/eez/apps/psu/scpi/diag.cpp index 9ed00f363..0f4056a6e 100644 --- a/src/eez/apps/psu/scpi/diag.cpp +++ b/src/eez/apps/psu/scpi/diag.cpp @@ -430,7 +430,7 @@ scpi_result_t scpi_cmd_diagnosticInformationRegsQ(scpi_t *context) { for (int i = 0; i < CH_MAX; i++) { Channel& channel = Channel::get(i); if (channel.isInstalled()) { - if (g_slots[channel.slotIndex].moduleType == MODULE_TYPE_DCP405) { + if (g_slots[channel.slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCP405 || g_slots[channel.slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCP405B) { sprintf(buffer + strlen(buffer), "CH%d:\n", i + 1); sprintf(buffer + strlen(buffer), "\tIOEXP:\n"); diff --git a/src/eez/apps/psu/scpi/outp.cpp b/src/eez/apps/psu/scpi/outp.cpp index 690217844..43950612b 100644 --- a/src/eez/apps/psu/scpi/outp.cpp +++ b/src/eez/apps/psu/scpi/outp.cpp @@ -215,7 +215,7 @@ scpi_result_t scpi_cmd_outputDprog(scpi_t *context) { return SCPI_RES_ERR; } - if (channel->getFeatures() & CH_FEATURE_DPROG) { + if (channel->params.features & CH_FEATURE_DPROG) { channel->setDprogState((DprogState)dprogState); } else { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); @@ -231,7 +231,7 @@ scpi_result_t scpi_cmd_outputDprogQ(scpi_t *context) { return SCPI_RES_ERR; } - if (channel->getFeatures() & CH_FEATURE_DPROG) { + if (channel->params.features & CH_FEATURE_DPROG) { resultChoiceName(context, dprogStateChoice, channel->getDprogState()); } else { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); diff --git a/src/eez/apps/psu/scpi/simu.cpp b/src/eez/apps/psu/scpi/simu.cpp index 4b0c5ae39..a36af3827 100644 --- a/src/eez/apps/psu/scpi/simu.cpp +++ b/src/eez/apps/psu/scpi/simu.cpp @@ -179,7 +179,7 @@ scpi_result_t scpi_cmd_simulatorVoltageProgramExternal(scpi_t *context) { return SCPI_RES_ERR; } - if (!(channel->getFeatures() & CH_FEATURE_RPROG)) { + if (!(channel->params.features & CH_FEATURE_RPROG)) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } @@ -201,7 +201,7 @@ scpi_result_t scpi_cmd_simulatorVoltageProgramExternalQ(scpi_t *context) { return SCPI_RES_ERR; } - if (!(channel->getFeatures() & CH_FEATURE_RPROG)) { + if (!(channel->params.features & CH_FEATURE_RPROG)) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } @@ -256,7 +256,7 @@ scpi_result_t scpi_cmd_simulatorRpol(scpi_t *context) { return SCPI_RES_ERR; } - if (channel->getFeatures() & CH_FEATURE_RPOL) { + if (channel->params.features & CH_FEATURE_RPOL) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } @@ -278,7 +278,7 @@ scpi_result_t scpi_cmd_simulatorRpolQ(scpi_t *context) { return SCPI_RES_ERR; } - if (channel->getFeatures() & CH_FEATURE_RPOL) { + if (channel->params.features & CH_FEATURE_RPOL) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } diff --git a/src/eez/apps/psu/scpi/sour.cpp b/src/eez/apps/psu/scpi/sour.cpp index d376a4619..7e5039747 100644 --- a/src/eez/apps/psu/scpi/sour.cpp +++ b/src/eez/apps/psu/scpi/sour.cpp @@ -254,7 +254,7 @@ scpi_result_t scpi_cmd_sourceCurrentLevelImmediateStepIncrement(scpi_t *context) return SCPI_RES_ERR; } - return set_step(context, &channel->i, channel->params->I_MIN_STEP, channel->params->I_MAX_STEP, channel->params->I_DEF_STEP, SCPI_UNIT_AMPER); + return set_step(context, &channel->i, channel->params.I_MIN_STEP, channel->params.I_MAX_STEP, channel->params.I_DEF_STEP, SCPI_UNIT_AMPER); } scpi_result_t scpi_cmd_sourceCurrentLevelImmediateStepIncrementQ(scpi_t *context) { @@ -264,7 +264,7 @@ scpi_result_t scpi_cmd_sourceCurrentLevelImmediateStepIncrementQ(scpi_t *context return SCPI_RES_ERR; } - return get_source_value(context, *channel, UNIT_AMPER, channel->i.step, channel->params->I_DEF_STEP); + return get_source_value(context, *channel, UNIT_AMPER, channel->i.step, channel->params.I_DEF_STEP); } scpi_result_t scpi_cmd_sourceVoltageLevelImmediateStepIncrement(scpi_t *context) { @@ -274,7 +274,7 @@ scpi_result_t scpi_cmd_sourceVoltageLevelImmediateStepIncrement(scpi_t *context) return SCPI_RES_ERR; } - return set_step(context, &channel->u, channel->params->U_MIN_STEP, channel->params->U_MAX_STEP, channel->params->U_DEF_STEP, SCPI_UNIT_VOLT); + return set_step(context, &channel->u, channel->params.U_MIN_STEP, channel->params.U_MAX_STEP, channel->params.U_DEF_STEP, SCPI_UNIT_VOLT); } scpi_result_t scpi_cmd_sourceVoltageLevelImmediateStepIncrementQ(scpi_t *context) { @@ -284,7 +284,7 @@ scpi_result_t scpi_cmd_sourceVoltageLevelImmediateStepIncrementQ(scpi_t *context return SCPI_RES_ERR; } - return get_source_value(context, *channel, UNIT_VOLT, channel->u.step, channel->params->U_DEF_STEP); + return get_source_value(context, *channel, UNIT_VOLT, channel->u.step, channel->params.U_DEF_STEP); } //////////////////////////////////////////////////////////////////////////////// @@ -297,7 +297,7 @@ scpi_result_t scpi_cmd_sourceCurrentProtectionDelayTime(scpi_t *context) { } float delay; - if (!get_duration_param(context, delay, channel->params->OCP_MIN_DELAY, channel->params->OCP_MAX_DELAY, channel->params->OCP_DEFAULT_DELAY)) { + if (!get_duration_param(context, delay, channel->params.OCP_MIN_DELAY, channel->params.OCP_MAX_DELAY, channel->params.OCP_DEFAULT_DELAY)) { return SCPI_RES_ERR; } @@ -396,7 +396,7 @@ scpi_result_t scpi_cmd_sourcePowerProtectionDelayTime(scpi_t *context) { } float delay; - if (!get_duration_param(context, delay, channel->params->OPP_MIN_DELAY, channel->params->OPP_MAX_DELAY, channel->params->OPP_DEFAULT_DELAY)) { + if (!get_duration_param(context, delay, channel->params.OPP_MIN_DELAY, channel->params.OPP_MAX_DELAY, channel->params.OPP_DEFAULT_DELAY)) { return SCPI_RES_ERR; } @@ -495,7 +495,7 @@ scpi_result_t scpi_cmd_sourceVoltageProtectionDelayTime(scpi_t *context) { } float delay; - if (!get_duration_param(context, delay, channel->params->OVP_MIN_DELAY, channel->params->OVP_MAX_DELAY, channel->params->OVP_DEFAULT_DELAY)) { + if (!get_duration_param(context, delay, channel->params.OVP_MIN_DELAY, channel->params.OVP_MAX_DELAY, channel->params.OVP_DEFAULT_DELAY)) { return SCPI_RES_ERR; } @@ -565,7 +565,7 @@ scpi_result_t scpi_cmd_sourceVoltageProtectionType(scpi_t *context) { return SCPI_RES_ERR; } - if (g_slots[channel->slotIndex].moduleType != MODULE_TYPE_DCP405) { + if (!(channel->params.features & CH_FEATURE_HW_OVP)) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } @@ -586,7 +586,7 @@ scpi_result_t scpi_cmd_sourceVoltageProtectionTypeQ(scpi_t *context) { return SCPI_RES_ERR; } - if (g_slots[channel->slotIndex].moduleType != MODULE_TYPE_DCP405) { + if (!(channel->params.features & CH_FEATURE_HW_OVP)) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } @@ -651,7 +651,7 @@ scpi_result_t scpi_cmd_sourceVoltageProgramSource(scpi_t *context) { return SCPI_RES_ERR; } - if (!(channel->getFeatures() & CH_FEATURE_RPROG)) { + if (!(channel->params.features & CH_FEATURE_RPROG)) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } @@ -683,7 +683,7 @@ scpi_result_t scpi_cmd_sourceVoltageProgramSourceQ(scpi_t *context) { return SCPI_RES_ERR; } - if (!(channel->getFeatures() & CH_FEATURE_RPROG)) { + if (!(channel->params.features & CH_FEATURE_RPROG)) { SCPI_ErrorPush(context, SCPI_ERROR_HARDWARE_MISSING); return SCPI_RES_ERR; } diff --git a/src/eez/apps/psu/scpi/syst.cpp b/src/eez/apps/psu/scpi/syst.cpp index b17ea33af..3b43a1012 100644 --- a/src/eez/apps/psu/scpi/syst.cpp +++ b/src/eez/apps/psu/scpi/syst.cpp @@ -461,7 +461,7 @@ scpi_result_t scpi_cmd_systemChannelInformationPowerQ(scpi_t *context) { return SCPI_RES_ERR; } - SCPI_ResultFloat(context, channel->params->PTOT); + SCPI_ResultFloat(context, channel->params.PTOT); return SCPI_RES_OK; } @@ -473,7 +473,7 @@ scpi_result_t scpi_cmd_systemChannelInformationProgramQ(scpi_t *context) { return SCPI_RES_ERR; } - uint16_t features = channel->getFeatures(); + uint16_t features = channel->params.features; char strFeatures[64] = { 0 }; @@ -565,13 +565,19 @@ scpi_result_t scpi_cmd_systemChannelInformationOntimeLastQ(scpi_t *context) { } scpi_result_t scpi_cmd_systemChannelModelQ(scpi_t *context) { - // TODO migrate to generic firmware Channel *channel = param_channel(context, false, true); if (!channel) { return SCPI_RES_ERR; } - SCPI_ResultText(context, channel->getBoardAndRevisionName()); + if (channel->isInstalled()) { + auto &slot = g_slots[channel->slotIndex]; + char text[100]; + sprintf(text, "%s_R%dB%d", slot.moduleInfo->moduleName, (int)(slot.moduleRevision >> 8), (int)(slot.moduleRevision & 0xFF)); + SCPI_ResultText(context, text); + } else { + SCPI_ResultText(context, "None"); + } return SCPI_RES_OK; } diff --git a/src/eez/apps/psu/temp_sensor.cpp b/src/eez/apps/psu/temp_sensor.cpp index 51748eb31..a7444dfe8 100644 --- a/src/eez/apps/psu/temp_sensor.cpp +++ b/src/eez/apps/psu/temp_sensor.cpp @@ -62,10 +62,7 @@ bool TempSensor::isInstalled() { } else if (type >= CH1 && type <= CH6) { int channelIndex = type - CH1; int slotIndex = Channel::get(channelIndex).slotIndex; - return - g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405 || - g_slots[slotIndex].moduleType == MODULE_TYPE_DCP505 || - g_slots[slotIndex].moduleType == MODULE_TYPE_DCM220; + return g_slots[slotIndex].moduleInfo->moduleType != MODULE_TYPE_NONE; } else { return false; } @@ -100,16 +97,17 @@ float TempSensor::doRead() { if (type >= CH1 && type <= CH6) { int channelIndex = type - CH1; int slotIndex = Channel::get(channelIndex).slotIndex; + auto &slot = g_slots[slotIndex]; - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405 && Channel::get(slotIndex).boardRevision == CH_BOARD_REVISION_DCP405_R2B5) { + if ((slot.moduleInfo->moduleType == MODULE_TYPE_DCP405 && slot.moduleRevision >= MODULE_REVISION_DCP405_R1B1) || slot.moduleInfo->moduleType == MODULE_TYPE_DCP405B) { return drivers::tc77::readTemperature(slotIndex); } - if ((g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405 && Channel::get(slotIndex).boardRevision == CH_BOARD_REVISION_DCP405_R1B1) || g_slots[slotIndex].moduleType == MODULE_TYPE_DCP505) { + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP405 || slot.moduleInfo->moduleType == MODULE_TYPE_DCP505) { return drivers::tmp1075::readTemperature(slotIndex); } - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCM220) { + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCM220) { return dcm220::readTemperature(channelIndex); } } diff --git a/src/eez/gui/action_impl.cpp b/src/eez/gui/action_impl.cpp index 5c0c5b9da..3cf4d4aed 100644 --- a/src/eez/gui/action_impl.cpp +++ b/src/eez/gui/action_impl.cpp @@ -934,9 +934,9 @@ void themesEnumDefinition(data::DataOperationEnum operation, data::Cursor &curso } -void onSetSelectedThemeIndex(uint8_t value) { +void onSetSelectedThemeIndex(uint16_t value) { popPage(); - persist_conf::devConf2.selectedThemeIndex = value; + persist_conf::devConf2.selectedThemeIndex = (uint8_t)value; persist_conf::saveDevice2(); mcu::display::onThemeChanged(); refreshScreen(); @@ -1021,7 +1021,7 @@ void action_user_switch_clicked() { static int g_slotIndex; -void onSetModuleType(uint8_t moduleType) { +void onSetModuleType(uint16_t moduleType) { popPage(); bp3c::eeprom::writeModuleType(g_slotIndex, moduleType); @@ -1035,7 +1035,7 @@ void onSetModuleType(uint8_t moduleType) { void selectSlot(int slotIndex) { g_slotIndex = slotIndex; - pushSelectFromEnumPage(g_moduleTypeEnumDefinition, g_slots[slotIndex].moduleType, NULL, onSetModuleType); + pushSelectFromEnumPage(g_moduleTypeEnumDefinition, g_slots[slotIndex].moduleInfo->moduleType, NULL, onSetModuleType); } void action_front_panel_select_slot1() { diff --git a/src/eez/gui/app_context.cpp b/src/eez/gui/app_context.cpp index fabb67305..c28b56caf 100644 --- a/src/eez/gui/app_context.cpp +++ b/src/eez/gui/app_context.cpp @@ -326,15 +326,15 @@ void AppContext::showPageOnNextIter(int pageId) { m_pageIdToSetOnNextIter = pageId; } -void AppContext::pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint8_t currentValue, - bool (*disabledCallback)(uint8_t value), - void (*onSet)(uint8_t)) { +void AppContext::pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint16_t currentValue, + bool (*disabledCallback)(uint16_t value), + void (*onSet)(uint16_t)) { m_selectFromEnumPage.init(enumDefinition, currentValue, disabledCallback, onSet); pushPage(INTERNAL_PAGE_ID_SELECT_FROM_ENUM, &m_selectFromEnumPage); } void AppContext::pushSelectFromEnumPage(void (*enumDefinitionFunc)(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value), - uint8_t currentValue, bool (*disabledCallback)(uint8_t value), void (*onSet)(uint8_t)) { + uint16_t currentValue, bool (*disabledCallback)(uint16_t value), void (*onSet)(uint16_t)) { m_selectFromEnumPage.init(enumDefinitionFunc, currentValue, disabledCallback, onSet); pushPage(INTERNAL_PAGE_ID_SELECT_FROM_ENUM, &m_selectFromEnumPage); } diff --git a/src/eez/gui/app_context.h b/src/eez/gui/app_context.h index 4b28c73c6..265e3f11d 100644 --- a/src/eez/gui/app_context.h +++ b/src/eez/gui/app_context.h @@ -82,10 +82,10 @@ class AppContext { int getPreviousPageId(); Page *getPreviousPage(); - void pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint8_t currentValue, - bool (*disabledCallback)(uint8_t value), void (*onSet)(uint8_t)); + void pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint16_t currentValue, + bool (*disabledCallback)(uint16_t value), void (*onSet)(uint16_t)); void pushSelectFromEnumPage(void (*enumDefinitionFunc)(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value), - uint8_t currentValue, bool (*disabledCallback)(uint8_t value), void (*onSet)(uint8_t)); + uint16_t currentValue, bool (*disabledCallback)(uint16_t value), void (*onSet)(uint16_t)); void replacePage(int pageId, Page *page = nullptr); diff --git a/src/eez/gui/data.cpp b/src/eez/gui/data.cpp index cbf1fb059..00b9e1c6c 100644 --- a/src/eez/gui/data.cpp +++ b/src/eez/gui/data.cpp @@ -194,15 +194,32 @@ bool compare_SLOT_INFO_value(const Value &a, const Value &b) { void SLOT_INFO_value_to_text(const Value &value, char *text, int count) { int slotIndex = value.getInt(); - psu::Channel &channel = psu::Channel::get(slotIndex); + auto &slot = g_slots[slotIndex]; + psu::Channel &channel = psu::Channel::get(slot.channelIndex); if (channel.isInstalled()) { - snprintf(text, count - 1, "%s %s", channel.getBoardName(), channel.getRevisionName()); + snprintf(text, count - 1, "%s R%dB%d", slot.moduleInfo->moduleName, (int)(slot.moduleRevision >> 8), (int)(slot.moduleRevision & 0xFF)); } else { strncpy(text, "Not installed", count - 1); } text[count - 1] = 0; } +bool compare_SLOT_INFO2_value(const Value &a, const Value &b) { + return a.getInt() == b.getInt(); +} + +void SLOT_INFO2_value_to_text(const Value &value, char *text, int count) { + int slotIndex = value.getInt(); + auto &slot = g_slots[slotIndex]; + psu::Channel &channel = psu::Channel::get(slot.channelIndex); + if (channel.isInstalled()) { + snprintf(text, count - 1, "%s_R%dB%d", slot.moduleInfo->moduleName, (int)(slot.moduleRevision >> 8), (int)(slot.moduleRevision & 0xFF)); + } else { + strncpy(text, "None", count - 1); + } + text[count - 1] = 0; +} + bool compare_TEST_RESULT_value(const Value &a, const Value &b) { return a.getInt() == b.getInt(); } @@ -232,7 +249,7 @@ static CompareValueFunction g_compareBuiltInValueFunctions[] = { compare_NONE_value, compare_INT_value, compare_FLOAT_value, compare_STR_value, compare_ENUM_value, compare_SCPI_ERROR_value, compare_PERCENTAGE_value, compare_SIZE_value, compare_POINTER_value, - compare_PAGE_INFO_value, compare_MASTER_INFO_value, compare_SLOT_INFO_value, + compare_PAGE_INFO_value, compare_MASTER_INFO_value, compare_SLOT_INFO_value, compare_SLOT_INFO2_value, compare_TEST_RESULT_value }; @@ -240,7 +257,7 @@ static ValueToTextFunction g_builtInValueToTextFunctions[] = { NONE_value_to_text, INT_value_to_text, FLOAT_value_to_text, STR_value_to_text, ENUM_value_to_text, SCPI_ERROR_value_to_text, PERCENTAGE_value_to_text, SIZE_value_to_text, POINTER_value_to_text, - PAGE_INFO_value_to_text, MASTER_INFO_value_to_text, SLOT_INFO_value_to_text, + PAGE_INFO_value_to_text, MASTER_INFO_value_to_text, SLOT_INFO_value_to_text, SLOT_INFO2_value_to_text, TEST_RESULT_value_to_text }; @@ -516,14 +533,6 @@ void data_slots(DataOperationEnum operation, Cursor &cursor, Value &value) { } } -void data_slot_module_type(DataOperationEnum operation, Cursor &cursor, Value &value) { - if (operation == data::DATA_OPERATION_GET) { - int slotIndex = cursor.i; - assert(slotIndex >= 0 && slotIndex < CH_MAX); - value = g_slots[slotIndex].moduleType; - } -} - #endif void data_selected_theme(DataOperationEnum operation, Cursor &cursor, Value &value) { diff --git a/src/eez/gui/data.h b/src/eez/gui/data.h index 5b2702209..11220bd67 100644 --- a/src/eez/gui/data.h +++ b/src/eez/gui/data.h @@ -37,6 +37,7 @@ enum BuiltInValueType { VALUE_TYPE_PAGE_INFO, VALUE_TYPE_MASTER_INFO, VALUE_TYPE_SLOT_INFO, + VALUE_TYPE_SLOT_INFO2, VALUE_TYPE_TEST_RESULT, VALUE_TYPE_USER, }; @@ -50,7 +51,7 @@ struct Style; namespace data { struct EnumItem { - uint8_t value; + uint16_t value; const char *menuLabel; const char *widgetLabel; }; @@ -58,8 +59,8 @@ struct EnumItem { extern const data::EnumItem *g_enumDefinitions[]; struct EnumValue { - uint8_t enumValue; - uint8_t enumDefinition; + uint16_t enumValue; + uint16_t enumDefinition; }; struct PairOfUint8Value { diff --git a/src/eez/gui/gui.cpp b/src/eez/gui/gui.cpp index e2f672d0f..fed068c5e 100644 --- a/src/eez/gui/gui.cpp +++ b/src/eez/gui/gui.cpp @@ -73,7 +73,6 @@ bool onSystemStateChanged() { if (eez::g_systemState == eez::SystemState::BOOTING) { if (eez::g_systemStatePhase == 0) { g_guiTaskHandle = osThreadCreate(osThread(g_guiTask), nullptr); - } else if (eez::g_systemStatePhase == 1) { } } @@ -182,13 +181,13 @@ bool isPageActiveOrOnStack(int pageId) { return g_appContext->isPageActiveOrOnStack(pageId); } -void pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint8_t currentValue, - bool (*disabledCallback)(uint8_t value), void (*onSet)(uint8_t)) { +void pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint16_t currentValue, + bool (*disabledCallback)(uint16_t value), void (*onSet)(uint16_t)) { g_appContext->pushSelectFromEnumPage(enumDefinition, currentValue, disabledCallback, onSet); } void pushSelectFromEnumPage(void(*enumDefinitionFunc)(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value), - uint8_t currentValue, bool(*disabledCallback)(uint8_t value), void(*onSet)(uint8_t)) { + uint16_t currentValue, bool(*disabledCallback)(uint16_t value), void(*onSet)(uint16_t)) { g_appContext->pushSelectFromEnumPage(enumDefinitionFunc, currentValue, disabledCallback, onSet); } @@ -640,4 +639,4 @@ void animateRects(Buffer startBuffer, int numRects, float duration) { } // namespace gui } // namespace eez -#endif \ No newline at end of file +#endif diff --git a/src/eez/gui/gui.h b/src/eez/gui/gui.h index 537f6ad57..c410adabb 100644 --- a/src/eez/gui/gui.h +++ b/src/eez/gui/gui.h @@ -60,10 +60,10 @@ int getPreviousPageId(); Page *getPreviousPage(); Page *getPage(int pageId); bool isPageActiveOrOnStack(int pageId); -void pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint8_t currentValue, - bool (*disabledCallback)(uint8_t value), void (*onSet)(uint8_t)); +void pushSelectFromEnumPage(const data::EnumItem *enumDefinition, uint16_t currentValue, + bool (*disabledCallback)(uint16_t value), void (*onSet)(uint16_t)); void pushSelectFromEnumPage(void(*enumDefinitionFunc)(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value), - uint8_t currentValue, bool(*disabledCallback)(uint8_t value), void(*onSet)(uint8_t)); + uint16_t currentValue, bool(*disabledCallback)(uint16_t value), void(*onSet)(uint16_t)); bool isPageInternal(int pageId); void executeAction(int actionId); diff --git a/src/eez/gui/page.cpp b/src/eez/gui/page.cpp index 63c29ebc1..273a360be 100644 --- a/src/eez/gui/page.cpp +++ b/src/eez/gui/page.cpp @@ -316,8 +316,8 @@ void ToastMessagePage::executeAction() { //////////////////////////////////////////////////////////////////////////////// -void SelectFromEnumPage::init(const data::EnumItem *enumDefinition_, uint8_t currentValue_, - bool (*disabledCallback_)(uint8_t value), void (*onSet_)(uint8_t)) +void SelectFromEnumPage::init(const data::EnumItem *enumDefinition_, uint16_t currentValue_, + bool (*disabledCallback_)(uint16_t value), void (*onSet_)(uint16_t)) { enumDefinition = enumDefinition_; enumDefinitionFunc = NULL; @@ -329,7 +329,7 @@ void SelectFromEnumPage::init(const data::EnumItem *enumDefinition_, uint8_t cur } void SelectFromEnumPage::init(void (*enumDefinitionFunc_)(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value), - uint8_t currentValue_, bool (*disabledCallback_)(uint8_t value), void (*onSet_)(uint8_t)) + uint16_t currentValue_, bool (*disabledCallback_)(uint16_t value), void (*onSet_)(uint16_t)) { enumDefinition = NULL; enumDefinitionFunc = enumDefinitionFunc_; @@ -380,7 +380,7 @@ void SelectFromEnumPage::init() { findPagePosition(); } -uint8_t SelectFromEnumPage::getValue(int i) { +uint16_t SelectFromEnumPage::getValue(int i) { if (enumDefinitionFunc) { data::Value value; data::Cursor cursor(i); diff --git a/src/eez/gui/page.h b/src/eez/gui/page.h index c7f33fefa..0bcf28986 100644 --- a/src/eez/gui/page.h +++ b/src/eez/gui/page.h @@ -110,10 +110,10 @@ class ToastMessagePage : public InternalPage { class SelectFromEnumPage : public InternalPage { public: - void init(const data::EnumItem *enumDefinition_, uint8_t currentValue_, - bool (*disabledCallback_)(uint8_t value), void (*onSet_)(uint8_t)); + void init(const data::EnumItem *enumDefinition_, uint16_t currentValue_, + bool (*disabledCallback_)(uint16_t value), void (*onSet_)(uint16_t)); void init(void (*enumDefinitionFunc)(data::DataOperationEnum operation, data::Cursor &cursor, data::Value &value), - uint8_t currentValue_, bool (*disabledCallback_)(uint8_t value), void (*onSet_)(uint8_t)); + uint16_t currentValue_, bool (*disabledCallback_)(uint16_t value), void (*onSet_)(uint16_t)); void init(); @@ -133,12 +133,12 @@ class SelectFromEnumPage : public InternalPage { bool dirty; - uint8_t getValue(int i); + uint16_t getValue(int i); const char *getLabel(int i); - uint8_t currentValue; - bool (*disabledCallback)(uint8_t value); - void (*onSet)(uint8_t); + uint16_t currentValue; + bool (*disabledCallback)(uint16_t value); + void (*onSet)(uint16_t); void findPagePosition(); diff --git a/src/eez/index.cpp b/src/eez/index.cpp index 5811bdd49..ed2b607c5 100644 --- a/src/eez/index.cpp +++ b/src/eez/index.cpp @@ -129,45 +129,64 @@ OnSystemStateChangedCallback g_onSystemStateChangedCallbacks[] = { psu::onSystemStateChanged, }; -int g_numOnSystemStateChangedCallbacks = - sizeof(g_onSystemStateChangedCallbacks) / sizeof(OnSystemStateChangedCallback); +int g_numOnSystemStateChangedCallbacks = sizeof(g_onSystemStateChangedCallbacks) / sizeof(OnSystemStateChangedCallback); -ModuleInfo g_modules[] = { +static ModuleInfo g_modules[] = { { + MODULE_TYPE_NONE, + "None", 0, - CH_BOARD_REVISION_NONE, 1, nullptr }, { - 406, - CH_BOARD_REVISION_DCP405_R2B5, + MODULE_TYPE_DCP405, + "DCP405", + MODULE_REVISION_DCP405_R2B7, 1, dcpX05::g_channelInterfaces }, { - 220, - CH_BOARD_REVISION_DCM220_R1B1, - 2, - dcm220::g_channelInterfaces + MODULE_TYPE_DCP405B, + "DCP405B", + MODULE_REVISION_DCP405B_R2B7, + 1, + dcpX05::g_channelInterfaces }, { - 505, - CH_BOARD_REVISION_DCP505_R1B3, + MODULE_TYPE_DCP505, + "DCP505", + MODULE_REVISION_DCP505_R1B3, 1, dcpX05::g_channelInterfaces }, + { + MODULE_TYPE_DCM220, + "DCM220", + MODULE_REVISION_DCM220_R2B4, + 2, + dcm220::g_channelInterfaces + } }; +ModuleInfo *getModuleInfo(uint16_t moduleType) { + for (size_t i = 0; i < sizeof(g_modules) / sizeof(ModuleInfo); i++) { + if (g_modules[i].moduleType == moduleType) { + return &g_modules[i]; + } + } + return &g_modules[0]; +} + SlotInfo g_slots[NUM_SLOTS] = { { - MODULE_TYPE_NONE + &g_modules[0] }, { - MODULE_TYPE_NONE + &g_modules[0] }, { - MODULE_TYPE_NONE + &g_modules[0] } }; diff --git a/src/eez/index.h b/src/eez/index.h index 7f2b8029b..cf43d658f 100644 --- a/src/eez/index.h +++ b/src/eez/index.h @@ -53,11 +53,95 @@ enum DprogState { DPROG_STATE_AUTO = 2 }; +enum ChannelFeatures { + CH_FEATURE_VOLT = (1 << 0), + CH_FEATURE_CURRENT = (1 << 1), + CH_FEATURE_POWER = (1 << 2), + CH_FEATURE_OE = (1 << 3), + CH_FEATURE_DPROG = (1 << 4), + CH_FEATURE_RPROG = (1 << 5), + CH_FEATURE_RPOL = (1 << 6), + CH_FEATURE_CURRENT_DUAL_RANGE = (1 << 7), + CH_FEATURE_HW_OVP = (1 << 8), + CH_FEATURE_COUPLING = (1 << 9) +}; + +struct ChannelParams { + float U_MIN; + float U_DEF; + float U_MAX; + float U_MAX_CONF; + + float U_MIN_STEP; + float U_DEF_STEP; + float U_MAX_STEP; + + float U_CAL_VAL_MIN; + float U_CAL_VAL_MID; + float U_CAL_VAL_MAX; + float U_CURR_CAL; + + float I_MIN; + float I_DEF; + float I_MAX; + + float I_MAX_CONF; + float I_MIN_STEP; + float I_DEF_STEP; + float I_MAX_STEP; + + float I_CAL_VAL_MIN; + float I_CAL_VAL_MID; + float I_CAL_VAL_MAX; + float I_VOLT_CAL; + + bool OVP_DEFAULT_STATE; + float OVP_MIN_DELAY; + float OVP_DEFAULT_DELAY; + float OVP_MAX_DELAY; + + bool OCP_DEFAULT_STATE; + float OCP_MIN_DELAY; + float OCP_DEFAULT_DELAY; + float OCP_MAX_DELAY; + + bool OPP_DEFAULT_STATE; + float OPP_MIN_DELAY; + float OPP_DEFAULT_DELAY; + float OPP_MAX_DELAY; + float OPP_MIN_LEVEL; + float OPP_DEFAULT_LEVEL; + float OPP_MAX_LEVEL; + + float PTOT; + + float U_RESOLUTION; + float I_RESOLUTION; + float I_LOW_RESOLUTION; + float P_RESOLUTION; + + float VOLTAGE_GND_OFFSET; // [V], (1375 / 65535) * (40V | 50V) + float CURRENT_GND_OFFSET; // [A] + + /// Maximum difference, in percentage, between ADC + /// and real value during calibration. + float CALIBRATION_DATA_TOLERANCE_PERCENT; + + /// Maximum difference, in percentage, between calculated mid value + /// and real mid value during calibration. + float CALIBRATION_MID_TOLERANCE_PERCENT; + + /// Returns features present (check ChannelFeatures) in board revision of this channel. + uint32_t features; +}; + struct ChannelInterface { int slotIndex; ChannelInterface(int slotIndex); + virtual void getParams(int subchannelIndex, ChannelParams ¶ms) = 0; + virtual void init(int subchannelIndex) = 0; virtual void reset(int subchannelIndex) = 0; virtual void test(int subchannelIndex) = 0; @@ -108,23 +192,35 @@ struct ChannelInterface { #endif }; -static const uint8_t MODULE_TYPE_NONE = 0; -static const uint8_t MODULE_TYPE_DCP405 = 1; -static const uint8_t MODULE_TYPE_DCM220 = 2; -static const uint8_t MODULE_TYPE_DCP505 = 3; +static const uint16_t MODULE_TYPE_NONE = 0; +static const uint16_t MODULE_TYPE_DCP405 = 405; +static const uint16_t MODULE_TYPE_DCP405B = 406; +static const uint16_t MODULE_TYPE_DCP505 = 505; +static const uint16_t MODULE_TYPE_DCM220 = 220; + +static const uint16_t MODULE_REVISION_DCP405_R1B1 = 0x0101; +static const uint16_t MODULE_REVISION_DCP405_R2B5 = 0x0205; +static const uint16_t MODULE_REVISION_DCP405_R2B7 = 0x0207; + +static const uint16_t MODULE_REVISION_DCP405B_R2B7 = 0x0207; + +static const uint16_t MODULE_REVISION_DCP505_R1B3 = 0x0103; + +static const uint16_t MODULE_REVISION_DCM220_R2B4 = 0x0204; struct ModuleInfo { - uint16_t moduleId; - uint8_t lasestBoardRevision; // TODO should be lasestModuleRevision + uint16_t moduleType; + const char *moduleName; + uint16_t latestModuleRevision; uint8_t numChannels; ChannelInterface **channelInterfaces; }; -extern ModuleInfo g_modules[]; +ModuleInfo *getModuleInfo(uint16_t moduleType); struct SlotInfo { - uint8_t moduleType; // MODULE_TYPE_... - uint8_t boardRevision; // TODO should be moduleRevision; + ModuleInfo *moduleInfo; + uint16_t moduleRevision; uint8_t channelIndex; }; diff --git a/src/eez/modules/aux_ps/fan.cpp b/src/eez/modules/aux_ps/fan.cpp index f3d1c69d0..0f2746196 100644 --- a/src/eez/modules/aux_ps/fan.cpp +++ b/src/eez/modules/aux_ps/fan.cpp @@ -328,8 +328,8 @@ void getIMonMax(float &iMonMax, float &iMax) { iMonMax = channel.i.mon; } } - if (channel.params->I_MAX > iMax) { - iMax = channel.params->I_MAX; + if (channel.params.I_MAX > iMax) { + iMax = channel.params.I_MAX; } } } diff --git a/src/eez/modules/bp3c/eeprom.cpp b/src/eez/modules/bp3c/eeprom.cpp index 5ad273c5d..bd2139315 100644 --- a/src/eez/modules/bp3c/eeprom.cpp +++ b/src/eez/modules/bp3c/eeprom.cpp @@ -223,12 +223,12 @@ bool test() { return g_testResult != TEST_FAILED; } -void writeModuleType(uint8_t slotIndex, uint8_t moduleType) { - uint8_t buffer[] = { - (uint8_t)(g_modules[moduleType].moduleId & 0xFF), - (uint8_t)((g_modules[moduleType].moduleId >> 8) & 0xFF), +void writeModuleType(uint8_t slotIndex, uint16_t moduleType) { + uint16_t buffer[] = { + moduleType, + getModuleInfo(moduleType)->latestModuleRevision }; - write(slotIndex, buffer, 2, 0); + write(slotIndex, (uint8_t *)buffer, 4, 0); } } // namespace eeprom diff --git a/src/eez/modules/bp3c/eeprom.h b/src/eez/modules/bp3c/eeprom.h index 6671b558e..8d806aa9a 100644 --- a/src/eez/modules/bp3c/eeprom.h +++ b/src/eez/modules/bp3c/eeprom.h @@ -107,7 +107,7 @@ extern TestResult g_testResult; bool read(uint8_t slotIndex, uint8_t *buffer, uint16_t buffer_size, uint16_t address); bool write(uint8_t slotIndex, const uint8_t *buffer, uint16_t buffer_size, uint16_t address); -void writeModuleType(uint8_t slotIndex, uint8_t moduleType); +void writeModuleType(uint8_t slotIndex, uint16_t moduleType); } // namespace eeprom } // namespace bp3c diff --git a/src/eez/modules/dcm220/channel.cpp b/src/eez/modules/dcm220/channel.cpp index 7f98603cc..1cad38c7f 100644 --- a/src/eez/modules/dcm220/channel.cpp +++ b/src/eez/modules/dcm220/channel.cpp @@ -148,6 +148,70 @@ struct Channel : ChannelInterface { #endif } + void getParams(int subchannelIndex, ChannelParams ¶ms) { + params.U_MIN = 0.0f; + params.U_DEF = 0.0f; + params.U_MAX = 20.0f; + params.U_MAX_CONF = 20.0f; + + params.U_MIN_STEP = 0.01f; + params.U_DEF_STEP = 0.1f; + params.U_MAX_STEP = 5.0f; + + params.U_CAL_VAL_MIN = 0.75f; + params.U_CAL_VAL_MID = 10.0f; + params.U_CAL_VAL_MAX = 18.0f; + params.U_CURR_CAL = 20.0f; + + params.I_MIN = 0.0f; + params.I_DEF = 0.0f; + params.I_MAX = 4.0f; + params.I_MAX_CONF = 4.0f; + + params.I_MIN_STEP = 0.01f; + params.I_DEF_STEP = 0.01f; + params.I_MAX_STEP = 1.0f; + + params.I_CAL_VAL_MIN = 0.05f; + params.I_CAL_VAL_MID = 1.95f; + params.I_CAL_VAL_MAX = 3.8f; + params.I_VOLT_CAL = 0.5f; + + params.OVP_DEFAULT_STATE = false; + params.OVP_MIN_DELAY = 0.0f; + params.OVP_DEFAULT_DELAY = 0.005f; + params.OVP_MAX_DELAY = 10.0f; + + params.OCP_DEFAULT_STATE = false; + params.OCP_MIN_DELAY = 0.0f; + params.OCP_DEFAULT_DELAY = 0.02f; + params.OCP_MAX_DELAY = 10.0f; + + params.OPP_DEFAULT_STATE = true; + params.OPP_MIN_DELAY = 1.0f; + params.OPP_DEFAULT_DELAY = 10.0f; + params.OPP_MAX_DELAY = 300.0f; + params.OPP_MIN_LEVEL = 0.0f; + params.OPP_DEFAULT_LEVEL = 80.0f; + params.OPP_MAX_LEVEL = 80.0f; + + params.PTOT = 80.0f; + + params.U_RESOLUTION = 0.01f; + params.I_RESOLUTION = 0.02f; + params.I_LOW_RESOLUTION = 0; + params.P_RESOLUTION = 0.001f; + + params.VOLTAGE_GND_OFFSET = 0; + params.CURRENT_GND_OFFSET = 0; + + params.CALIBRATION_DATA_TOLERANCE_PERCENT = 15.0f; + + params.CALIBRATION_MID_TOLERANCE_PERCENT = 2.0f; + + params.features = CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE; + } + #if defined(EEZ_PLATFORM_STM32) bool isCrcOk; @@ -297,11 +361,11 @@ struct Channel : ChannelInterface { int offset = subchannelIndex * 2; uint16_t uMonAdc = inputSetValues[offset]; - float uMon = remap(uMonAdc, (float)ADC_MIN, channel.params->U_MIN, (float)ADC_MAX, channel.params->U_MAX); + float uMon = remap(uMonAdc, (float)ADC_MIN, channel.params.U_MIN, (float)ADC_MAX, channel.params.U_MAX); channel.onAdcData(ADC_DATA_TYPE_U_MON, uMon); uint16_t iMonAdc = inputSetValues[offset + 1]; - float iMon = remap(iMonAdc, (float)ADC_MIN, channel.params->I_MIN, (float)ADC_MAX, channel.params->I_MAX); + float iMon = remap(iMonAdc, (float)ADC_MIN, channel.params.I_MIN, (float)ADC_MAX, channel.params.I_MAX); channel.onAdcData(ADC_DATA_TYPE_I_MON, iMon); } #endif @@ -397,14 +461,14 @@ struct Channel : ChannelInterface { #if defined(EEZ_PLATFORM_SIMULATOR) psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex, subchannelIndex); - uSet[subchannelIndex] = remap(clamp((float)value, (float)DAC_MIN, (float)DAC_MAX), (float)DAC_MIN, channel.params->U_MIN, (float)DAC_MAX, channel.params->U_MAX); + uSet[subchannelIndex] = remap(clamp((float)value, (float)DAC_MIN, (float)DAC_MAX), (float)DAC_MIN, channel.params.U_MIN, (float)DAC_MAX, channel.params.U_MAX); #endif } void setDacVoltageFloat(int subchannelIndex, float value) { #if defined(EEZ_PLATFORM_STM32) psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex, subchannelIndex); - value = remap(value, channel.params->U_MIN, (float)DAC_MIN, channel.params->U_MAX, (float)DAC_MAX); + value = remap(value, channel.params.U_MIN, (float)DAC_MIN, channel.params.U_MAX, (float)DAC_MAX); uSet[subchannelIndex] = (uint16_t)clamp(round(value), DAC_MIN, DAC_MAX); #endif @@ -421,14 +485,14 @@ struct Channel : ChannelInterface { #if defined(EEZ_PLATFORM_SIMULATOR) psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex, subchannelIndex); - uSet[subchannelIndex] = remap(clamp((float)value, (float)DAC_MIN, (float)DAC_MAX), (float)DAC_MIN, channel.params->I_MIN, (float)DAC_MAX, channel.params->I_MAX); + uSet[subchannelIndex] = remap(clamp((float)value, (float)DAC_MIN, (float)DAC_MAX), (float)DAC_MIN, channel.params.I_MIN, (float)DAC_MAX, channel.params.I_MAX); #endif } void setDacCurrentFloat(int subchannelIndex, float value) { #if defined(EEZ_PLATFORM_STM32) psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex, subchannelIndex); - value = remap(value, channel.params->I_MIN, (float)DAC_MIN, channel.params->I_MAX, (float)DAC_MAX); + value = remap(value, channel.params.I_MIN, (float)DAC_MIN, channel.params.I_MAX, (float)DAC_MAX); iSet[subchannelIndex] = (uint16_t)clamp(round(value), DAC_MIN, DAC_MAX); #endif diff --git a/src/eez/modules/dcpX05/adc.cpp b/src/eez/modules/dcpX05/adc.cpp index 46867f2e3..2641cc652 100644 --- a/src/eez/modules/dcpX05/adc.cpp +++ b/src/eez/modules/dcpX05/adc.cpp @@ -34,17 +34,17 @@ namespace psu { #if defined(EEZ_PLATFORM_STM32) float remapAdcDataToVoltage(Channel& channel, AdcDataType adcDataType, int16_t adcData) { - float value = remap((float)adcData, (float)AnalogDigitalConverter::ADC_MIN, channel.params->U_MIN, (float)AnalogDigitalConverter::ADC_MAX, channel.params->U_MAX_CONF); + float value = remap((float)adcData, (float)AnalogDigitalConverter::ADC_MIN, channel.params.U_MIN, (float)AnalogDigitalConverter::ADC_MAX, channel.params.U_MAX_CONF); #if !defined(EEZ_PLATFORM_SIMULATOR) if (adcDataType == ADC_DATA_TYPE_U_MON || !channel.flags.rprogEnabled) { - value -= channel.params->VOLTAGE_GND_OFFSET; + value -= channel.params.VOLTAGE_GND_OFFSET; } #endif return value; } float remapAdcDataToCurrent(Channel& channel, AdcDataType adcDataType, int16_t adcData) { - float value = remap((float)adcData, (float)AnalogDigitalConverter::ADC_MIN, channel.params->I_MIN, (float)AnalogDigitalConverter::ADC_MAX, channel.getDualRangeMax()); + float value = remap((float)adcData, (float)AnalogDigitalConverter::ADC_MIN, channel.params.I_MIN, (float)AnalogDigitalConverter::ADC_MAX, channel.getDualRangeMax()); #if !defined(EEZ_PLATFORM_SIMULATOR) value -= channel.getDualRangeGndOffset(); #endif diff --git a/src/eez/modules/dcpX05/channel.cpp b/src/eez/modules/dcpX05/channel.cpp index 91998e6ac..601e31e22 100644 --- a/src/eez/modules/dcpX05/channel.cpp +++ b/src/eez/modules/dcpX05/channel.cpp @@ -64,6 +64,108 @@ struct Channel : ChannelInterface { Channel(int slotIndex_) : ChannelInterface(slotIndex_), uSet(0) {} + void getParams(int subchannelIndex, ChannelParams ¶ms) { + auto slot = g_slots[slotIndex]; + + params.U_MIN = 0.0f; + params.U_DEF = 0.0f; + + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP505) { + params.U_MAX = 50.0f; + params.U_MAX_CONF = 50.0f; + + params.U_CAL_VAL_MIN = 0.15f; + params.U_CAL_VAL_MID = 24.1f; + params.U_CAL_VAL_MAX = 48.0f; + params.U_CURR_CAL = 25.0f; + } else { + params.U_MAX = 40.0f; + params.U_MAX_CONF = 40.0f; + + params.U_CAL_VAL_MIN = 0.15f; + params.U_CAL_VAL_MID = 20.0f; + params.U_CAL_VAL_MAX = 38.0f; + params.U_CURR_CAL = 20.0f; + } + + params.U_MIN_STEP = 0.01f; + params.U_DEF_STEP = 0.1f; + params.U_MAX_STEP = 5.0f; + + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP505) { + params.U_CAL_VAL_MIN = 0.15f; + params.U_CAL_VAL_MID = 24.1f; + params.U_CAL_VAL_MAX = 48.0f; + params.U_CURR_CAL = 25.0f; + } else { + params.U_CAL_VAL_MIN = 0.15f; + params.U_CAL_VAL_MID = 20.0f; + params.U_CAL_VAL_MAX = 38.0f; + params.U_CURR_CAL = 20.0f; + } + + params.I_MIN = 0.0f; + params.I_DEF = 0.0f; + params.I_MAX = 5.0f; + params.I_MAX_CONF = 5.0f; + params.I_MIN_STEP = 0.01f; + params.I_DEF_STEP = 0.01f; + params.I_MAX_STEP = 1.0f; + params.I_CAL_VAL_MIN = 0.05f; + params.I_CAL_VAL_MID = 2.425f; + params.I_CAL_VAL_MAX = 4.8f; + params.I_VOLT_CAL = 0.1f; + + params.OVP_DEFAULT_STATE = false; + params.OVP_MIN_DELAY = 0.0f; + params.OVP_DEFAULT_DELAY = 0.005f; + params.OVP_MAX_DELAY = 10.0f; + + params.OCP_DEFAULT_STATE = false; + params.OCP_MIN_DELAY = 0.0f; + params.OCP_DEFAULT_DELAY = 0.02f; + params.OCP_MAX_DELAY = 10.0f; + + params.OPP_DEFAULT_STATE = true; + params.OPP_MIN_DELAY = 1.0f; + params.OPP_DEFAULT_DELAY = 10.0f; + params.OPP_MAX_DELAY = 300.0f; + params.OPP_MIN_LEVEL = 0.0f; + params.OPP_DEFAULT_LEVEL = 155.0f; + params.OPP_MAX_LEVEL = 160.0f; + + params.PTOT = 155.0f; + + params.U_RESOLUTION = 0.005f; + params.I_RESOLUTION = 0.0005f; + params.I_LOW_RESOLUTION = 0.000005f; + params.P_RESOLUTION = 0.001f; + + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP505) { + params.VOLTAGE_GND_OFFSET = 1.05f; + } else { + params.VOLTAGE_GND_OFFSET = 0.86f; + } + params.CURRENT_GND_OFFSET = 0.11f; + + params.CALIBRATION_DATA_TOLERANCE_PERCENT = 10.0f; + + params.CALIBRATION_MID_TOLERANCE_PERCENT = 1.0f; + + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP505) { + params.features = CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE | + CH_FEATURE_DPROG | CH_FEATURE_RPROG | CH_FEATURE_RPOL; + } else if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP405) { + params.features = CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE | + CH_FEATURE_DPROG | CH_FEATURE_RPROG | CH_FEATURE_RPOL | + CH_FEATURE_CURRENT_DUAL_RANGE | CH_FEATURE_HW_OVP | CH_FEATURE_COUPLING; + } else { + // DCP405B + params.features = CH_FEATURE_VOLT | CH_FEATURE_CURRENT | CH_FEATURE_POWER | CH_FEATURE_OE | + CH_FEATURE_CURRENT_DUAL_RANGE | CH_FEATURE_COUPLING; + } + } + void init(int subchannelIndex) { ioexp.slotIndex = slotIndex; adc.slotIndex = slotIndex; @@ -138,45 +240,47 @@ struct Channel : ChannelInterface { #endif } - if (dprogState == DPROG_STATE_AUTO) { - // turn off DP after delay - if (delayed_dp_off && (micros() - delayed_dp_off_start) >= DP_OFF_DELAY_PERIOD * 1000000L) { - delayed_dp_off = false; - setDpEnable(false); - } - } - - /// Output power is monitored and if its go below DP_NEG_LEV - /// that is negative value in Watts (default -1 W), - /// and that condition lasts more then DP_NEG_DELAY seconds (default 5 s), - /// down-programmer circuit has to be switched off. - if (channel.isOutputEnabled()) { - if (channel.u.mon_last * channel.i.mon_last >= DP_NEG_LEV || tickCount < dpNegMonitoringTime) { - dpNegMonitoringTime = tickCount; - } else { - if (tickCount - dpNegMonitoringTime > DP_NEG_DELAY * 1000000UL) { - if (dpOn) { - // DebugTrace("CH%d, neg. P, DP off: %f", channel.channelIndex + 1, channel.u.mon_last * channel.i.mon_last); - dpNegMonitoringTime = tickCount; - generateError(SCPI_ERROR_CH1_DOWN_PROGRAMMER_SWITCHED_OFF + channel.channelIndex); - setDpEnable(false); - } else { - // DebugTrace("CH%d, neg. P, output off: %f", channel.channelIndex + 1, channel.u.mon_last * channel.i.mon_last); - generateError(SCPI_ERROR_CH1_OUTPUT_FAULT_DETECTED - channel.channelIndex); - channel_dispatcher::outputEnable(channel, false); + if (channel.params.features & CH_FEATURE_DPROG) { + if (dprogState == DPROG_STATE_AUTO) { + // turn off DP after delay + if (delayed_dp_off && (micros() - delayed_dp_off_start) >= DP_OFF_DELAY_PERIOD * 1000000L) { + delayed_dp_off = false; + setDpEnable(false); + } + } + + /// Output power is monitored and if its go below DP_NEG_LEV + /// that is negative value in Watts (default -1 W), + /// and that condition lasts more then DP_NEG_DELAY seconds (default 5 s), + /// down-programmer circuit has to be switched off. + if (channel.isOutputEnabled()) { + if (channel.u.mon_last * channel.i.mon_last >= DP_NEG_LEV || tickCount < dpNegMonitoringTime) { + dpNegMonitoringTime = tickCount; + } else { + if (tickCount - dpNegMonitoringTime > DP_NEG_DELAY * 1000000UL) { + if (dpOn) { + // DebugTrace("CH%d, neg. P, DP off: %f", channel.channelIndex + 1, channel.u.mon_last * channel.i.mon_last); + dpNegMonitoringTime = tickCount; + generateError(SCPI_ERROR_CH1_DOWN_PROGRAMMER_SWITCHED_OFF + channel.channelIndex); + setDpEnable(false); + } else { + // DebugTrace("CH%d, neg. P, output off: %f", channel.channelIndex + 1, channel.u.mon_last * channel.i.mon_last); + generateError(SCPI_ERROR_CH1_OUTPUT_FAULT_DETECTED - channel.channelIndex); + channel_dispatcher::outputEnable(channel, false); + } + } else if (tickCount - dpNegMonitoringTime > 500 * 1000UL) { + if (dpOn && channel.channelIndex < 2) { + if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_SERIES) { + psu::Channel &channel2 = psu::Channel::get(channel.channelIndex == 0 ? 1 : 0); + voltageBalancing(channel2); + dpNegMonitoringTime = tickCount; + } else if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_PARALLEL) { + psu::Channel &channel2 = psu::Channel::get(channel.channelIndex == 0 ? 1 : 0); + currentBalancing(channel2); + dpNegMonitoringTime = tickCount; + } + } } - } else if (tickCount - dpNegMonitoringTime > 500 * 1000UL) { - if (dpOn && channel.channelIndex < 2) { - if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_SERIES) { - psu::Channel &channel2 = psu::Channel::get(channel.channelIndex == 0 ? 1 : 0); - voltageBalancing(channel2); - dpNegMonitoringTime = tickCount; - } else if (channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_PARALLEL) { - psu::Channel &channel2 = psu::Channel::get(channel.channelIndex == 0 ? 1 : 0); - currentBalancing(channel2); - dpNegMonitoringTime = tickCount; - } - } } } } @@ -195,11 +299,12 @@ struct Channel : ChannelInterface { } } - if (fallingEdge) { - fallingEdge = channel.u.mon_last > channel.u.set * (1.0f + CONF_OVP_PERCENTAGE / 100.0f); - } + if (channel.params.features & CH_FEATURE_HW_OVP) { + if (fallingEdge) { + fallingEdge = channel.u.mon_last > channel.u.set * (1.0f + CONF_OVP_PERCENTAGE / 100.0f); + } - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + // HW OVP handling if (channel.isOutputEnabled()) { if (!fallingEdge && channel.isHwOvpEnabled() && !ioexp.testBit(IOExpander::DCP405_IO_BIT_OUT_OVP_ENABLE)) { if (isSwOvpAtStart) { @@ -300,14 +405,17 @@ struct Channel : ChannelInterface { } void setDpEnable(bool enable) { - // DP bit is active low - ioexp.changeBit(IOExpander::IO_BIT_OUT_DP_ENABLE, !enable); + psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex); + if (channel.params.features & CH_FEATURE_DPROG) { + // DP bit is active low + ioexp.changeBit(IOExpander::IO_BIT_OUT_DP_ENABLE, !enable); - setOperBits(OPER_ISUM_DP_OFF, !enable); - dpOn = enable; + setOperBits(OPER_ISUM_DP_OFF, !enable); + dpOn = enable; - if (enable) { - dpNegMonitoringTime = micros(); + if (enable) { + dpNegMonitoringTime = micros(); + } } } @@ -315,12 +423,14 @@ struct Channel : ChannelInterface { psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex); if (enable) { - if (dprogState == DPROG_STATE_AUTO) { - // enable DP - dpNegMonitoringTime = micros(); - delayed_dp_off = false; - setDpEnable(true); - } + if (channel.params.features & CH_FEATURE_DPROG) { + if (dprogState == DPROG_STATE_AUTO) { + // enable DP + dpNegMonitoringTime = micros(); + delayed_dp_off = false; + setDpEnable(true); + } + } dac.setVoltage(uSet); @@ -351,14 +461,16 @@ struct Channel : ChannelInterface { setCurrentRange(subchannelIndex); - if (dprogState == DPROG_STATE_AUTO) { - // turn off DP after some delay - delayed_dp_off = true; - delayed_dp_off_start = micros(); - } + if (channel.params.features & CH_FEATURE_DPROG) { + if (dprogState == DPROG_STATE_AUTO) { + // turn off DP after some delay + delayed_dp_off = true; + delayed_dp_off_start = micros(); + } + } } - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + if (channel.params.features & CH_FEATURE_COUPLING) { if (channel.channelIndex == 0 && channel_dispatcher::getCouplingType() == channel_dispatcher::COUPLING_TYPE_PARALLEL) { ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_OE_UNCOUPLED_LED, false); ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_OE_COUPLED_LED, enable); @@ -395,18 +507,20 @@ struct Channel : ChannelInterface { } void setDprogState(DprogState dprogState_) { - dprogState = dprogState_; - - if (dprogState == DPROG_STATE_OFF) { - setDpEnable(false); - } else if (dprogState == DPROG_STATE_ON) { - setDpEnable(true); - } else { - psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex); - setDpEnable(channel.isOutputEnabled()); - } + psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex); + if (channel.params.features & CH_FEATURE_DPROG) { + dprogState = dprogState_; + + if (dprogState == DPROG_STATE_OFF) { + setDpEnable(false); + } else if (dprogState == DPROG_STATE_ON) { + setDpEnable(true); + } else { + setDpEnable(channel.isOutputEnabled()); + } - delayed_dp_off = false; + delayed_dp_off = false; + } } void setRemoteSense(int subchannelIndex, bool enable) { @@ -465,13 +579,14 @@ struct Channel : ChannelInterface { } void setCurrentRange(int subchannelIndex) { - psu::Channel &channel = psu::Channel::getBySlotIndex(slotIndex); + auto &channel = psu::Channel::getBySlotIndex(slotIndex); + auto &slot = g_slots[slotIndex]; if (!channel.hasSupportForCurrentDualRange()) { return; } - if (channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1) { + if (slot.moduleRevision == MODULE_REVISION_DCP405_R1B1) { ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_500MA, false); } @@ -480,14 +595,14 @@ struct Channel : ChannelInterface { // 5A // DebugTrace("CH%d: Switched to 5A range", channel.channelIndex + 1); ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_5A, true); - ioexp.changeBit(channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1 ? + ioexp.changeBit(slot.moduleRevision == MODULE_REVISION_DCP405_R1B1 ? IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_50MA : IOExpander::DCP405_R2B5_IO_BIT_OUT_CURRENT_RANGE_50MA, false); // calculateNegligibleAdcDiffForCurrent(); } else { // 50mA // DebugTrace("CH%d: Switched to 50mA range", channel.channelIndex + 1); - ioexp.changeBit(channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1 ? + ioexp.changeBit(slot.moduleRevision == MODULE_REVISION_DCP405_R1B1 ? IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_50MA : IOExpander::DCP405_R2B5_IO_BIT_OUT_CURRENT_RANGE_50MA, true); ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_5A, false); @@ -497,7 +612,7 @@ struct Channel : ChannelInterface { adc.start(ADC_DATA_TYPE_U_MON); } else { ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_5A, true); - ioexp.changeBit(channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1 ? + ioexp.changeBit(slot.moduleRevision == MODULE_REVISION_DCP405_R1B1 ? IOExpander::DCP405_IO_BIT_OUT_CURRENT_RANGE_50MA : IOExpander::DCP405_R2B5_IO_BIT_OUT_CURRENT_RANGE_50MA, false); } diff --git a/src/eez/modules/dcpX05/dac.cpp b/src/eez/modules/dcpX05/dac.cpp index b946ec370..b77845104 100644 --- a/src/eez/modules/dcpX05/dac.cpp +++ b/src/eez/modules/dcpX05/dac.cpp @@ -77,7 +77,7 @@ bool DigitalAnalogConverter::test(IOExpander &ioexp, AnalogDigitalConverter &adc channel.calibrationEnableNoEvent(false); // disable OE on channel - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + if (channel.params.features & CH_FEATURE_HW_OVP) { // OVP has to be disabled before OE deactivation ioexp.changeBit(IOExpander::DCP405_IO_BIT_OUT_OVP_ENABLE, false); } @@ -150,7 +150,7 @@ void DigitalAnalogConverter::setVoltage(float value) { Channel &channel = Channel::getBySlotIndex(slotIndex); #if defined(EEZ_PLATFORM_STM32) - set(DATA_BUFFER_B, remap(value, channel.params->U_MIN, (float)DAC_MIN, channel.params->U_MAX, (float)DAC_MAX)); + set(DATA_BUFFER_B, remap(value, channel.params.U_MIN, (float)DAC_MIN, channel.params.U_MAX, (float)DAC_MAX)); #endif #if defined(EEZ_PLATFORM_SIMULATOR) @@ -165,7 +165,7 @@ void DigitalAnalogConverter::setDacVoltage(uint16_t value) { #if defined(EEZ_PLATFORM_SIMULATOR) Channel &channel = Channel::getBySlotIndex(slotIndex); - g_uSet[channel.channelIndex] = remap(value, (float)DAC_MIN, channel.params->U_MIN, (float)DAC_MAX, channel.params->U_MAX); + g_uSet[channel.channelIndex] = remap(value, (float)DAC_MIN, channel.params.U_MIN, (float)DAC_MAX, channel.params.U_MAX); #endif } @@ -173,7 +173,7 @@ void DigitalAnalogConverter::setCurrent(float value) { Channel &channel = Channel::getBySlotIndex(slotIndex); #if defined(EEZ_PLATFORM_STM32) - set(DATA_BUFFER_A, remap(value, channel.params->I_MIN, (float)DAC_MIN, channel.getDualRangeMax(), (float)DAC_MAX)); + set(DATA_BUFFER_A, remap(value, channel.params.I_MIN, (float)DAC_MIN, channel.getDualRangeMax(), (float)DAC_MAX)); #endif #if defined(EEZ_PLATFORM_SIMULATOR) @@ -188,7 +188,7 @@ void DigitalAnalogConverter::setDacCurrent(uint16_t value) { #if defined(EEZ_PLATFORM_SIMULATOR) Channel &channel = Channel::getBySlotIndex(slotIndex); - g_iSet[channel.channelIndex] = remap(value, (float)DAC_MIN, channel.params->I_MIN, (float)DAC_MAX, channel.getDualRangeMax()); + g_iSet[channel.channelIndex] = remap(value, (float)DAC_MIN, channel.params.I_MIN, (float)DAC_MAX, channel.getDualRangeMax()); #endif } diff --git a/src/eez/modules/dcpX05/ioexp.cpp b/src/eez/modules/dcpX05/ioexp.cpp index 8b95bb49d..6bb991c2f 100644 --- a/src/eez/modules/dcpX05/ioexp.cpp +++ b/src/eez/modules/dcpX05/ioexp.cpp @@ -59,18 +59,27 @@ static const uint8_t DCP505_REG_VALUE_IODIRA = 0B00011111; // pins 0, 1, 2, 3, static const uint8_t DCP505_REG_VALUE_IODIRB = 0B11110000; // static const uint8_t DCP405_REG_VALUE_IODIRA = 0B00011111; // pins 0, 1, 2, 3 and 4 are inputs (set to 1) +static const uint8_t DCP405_R2B5_REG_VALUE_IODIRA = 0B00111111; // pins 0, 1, 2, 3, 4 and 5 are inputs (set to 1) static const uint8_t DCP405_REG_VALUE_IODIRB = 0B00000000; // -static const uint8_t DCP405_R2B5_REG_VALUE_IODIRA = 0B00111111; // pins 0, 1, 2, 3, 4 and 5 are inputs (set to 1) +static const uint8_t DCP405B_REG_VALUE_IODIRA = 0B00011111; // pins 0, 1, 2, 3 and 4 are inputs (set to 1) +static const uint8_t DCP405B_REG_VALUE_IODIRB = 0B00000000; // static const uint8_t REG_VALUE_IPOLA = 0B00000000; // no pin is inverted static const uint8_t REG_VALUE_IPOLB = 0B00000000; // no pin is inverted -static const uint8_t REG_VALUE_GPINTENA = 0B00100000; // enable interrupt for HW OVP Fault + +static const uint8_t REG_VALUE_GPINTENA = 0B00000000; // no interrupts +static const uint8_t DCP405_REG_VALUE_GPINTENA = 0B00100000; // enable interrupt for HW OVP Fault static const uint8_t REG_VALUE_GPINTENB = 0B00000000; // no interrupts -static const uint8_t REG_VALUE_DEFVALA = 0B00100000; // default value for HW OVP Fault is 1 + +static const uint8_t REG_VALUE_DEFVALA = 0B00000000; // +static const uint8_t DCP405_REG_VALUE_DEFVALA = 0B00100000; // default value for HW OVP Fault is 1 static const uint8_t REG_VALUE_DEFVALB = 0B00000000; // -static const uint8_t REG_VALUE_INTCONA = 0B00100000; // compare HW OVP Fault value with default value + +static const uint8_t REG_VALUE_INTCONA = 0B00000000; +static const uint8_t DCP405_REG_VALUE_INTCONA = 0B00100000; // compare HW OVP Fault value with default value static const uint8_t REG_VALUE_INTCONB = 0B00000000; // + static const uint8_t REG_VALUE_IOCON = 0B00100000; // sequential operation disabled, hw addressing disabled static const uint8_t REG_VALUE_GPPUA = 0B00100001; // pull up with 100K static const uint8_t REG_VALUE_GPPUB = 0B00000000; // @@ -81,6 +90,9 @@ static const uint8_t DCP505_REG_VALUE_GPIOB = 0B00000001; // DP is OFF static const uint8_t DCP405_REG_VALUE_GPIOA = 0B00000000; // static const uint8_t DCP405_REG_VALUE_GPIOB = 0B00000001; // DP is OFF +static const uint8_t DCP405B_REG_VALUE_GPIOA = 0B00000000; // +static const uint8_t DCP405B_REG_VALUE_GPIOB = 0B00000000; // + static const uint8_t REG_VALUES[] = { REG_IODIRA, DCP505_REG_VALUE_IODIRA, 1, REG_IODIRB, DCP505_REG_VALUE_IODIRB, 1, @@ -98,31 +110,94 @@ static const uint8_t REG_VALUES[] = { REG_GPIOA, DCP505_REG_VALUE_GPIOA, 0, REG_GPIOB, DCP505_REG_VALUE_GPIOB, 0, }; + +static const int REG_IODIRA_INDEX = 0; +static const int REG_IODIRB_INDEX = 1; #endif //////////////////////////////////////////////////////////////////////////////// +#if defined(EEZ_PLATFORM_STM32) +uint8_t IOExpander::getRegValue(int i) { + uint8_t reg = REG_VALUES[3 * i]; + uint8_t value = REG_VALUES[3 * i + 1]; + + auto &slot = g_slots[slotIndex]; + + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP405) { + if (reg == REG_IODIRA) { + if (slot.moduleRevision < MODULE_REVISION_DCP405_R2B5) { + value = DCP405_REG_VALUE_IODIRA; + } else { + value = DCP405_R2B5_REG_VALUE_IODIRA; + } + } else if (reg == REG_IODIRB) { + value = DCP405_REG_VALUE_IODIRB; + } else if (reg == REG_GPIOA) { + value = DCP405_REG_VALUE_GPIOA; + } else if (reg == REG_GPIOB) { + value = DCP405_REG_VALUE_GPIOB; + } else if (reg == REG_GPINTENA) { + value = DCP405_REG_VALUE_GPINTENA; + } else if (reg == REG_DEFVALA) { + value = DCP405_REG_VALUE_DEFVALA; + } else if (reg == REG_INTCONA) { + value = DCP405_REG_VALUE_INTCONA; + } + } else if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP405B) { + if (reg == REG_IODIRA) { + value = DCP405B_REG_VALUE_IODIRA; + } else if (reg == REG_IODIRB) { + value = DCP405B_REG_VALUE_IODIRB; + } else if (reg == REG_GPIOA) { + value = DCP405B_REG_VALUE_GPIOA; + } else if (reg == REG_GPIOB) { + value = DCP405B_REG_VALUE_GPIOB; + } + } + + return value; +} +#endif + void IOExpander::init() { #if defined(EEZ_PLATFORM_STM32) - Channel &channel = Channel::getBySlotIndex(slotIndex); + auto &slot = g_slots[slotIndex]; - gpioOutputPinsMask = - (1 << IO_BIT_OUT_DP_ENABLE) | - (1 << IO_BIT_OUT_OUTPUT_ENABLE) | - (1 << IO_BIT_OUT_REMOTE_SENSE) | - (1 << IO_BIT_OUT_REMOTE_PROGRAMMING); - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { - gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_CURRENT_RANGE_500MA; - gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_CURRENT_RANGE_5A; - gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OVP_ENABLE; - gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OE_UNCOUPLED_LED; - gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OE_COUPLED_LED; - if (channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1) { + gpioOutputPinsMask = 0; + + if (g_slots[slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCP405) { + gpioOutputPinsMask |= 1 << IO_BIT_OUT_DP_ENABLE; + gpioOutputPinsMask |= 1 << IO_BIT_OUT_OUTPUT_ENABLE; + gpioOutputPinsMask |= 1 << IO_BIT_OUT_REMOTE_SENSE; + gpioOutputPinsMask |= 1 << IO_BIT_OUT_REMOTE_PROGRAMMING; + + if (slot.moduleRevision < MODULE_REVISION_DCP405_R2B5) { gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_CURRENT_RANGE_50MA; + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_CURRENT_RANGE_500MA; } else { gpioOutputPinsMask |= 1 << DCP405_R2B5_IO_BIT_OUT_CURRENT_RANGE_50MA; } + + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_CURRENT_RANGE_5A; + + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OVP_ENABLE; + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OE_UNCOUPLED_LED; + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OE_COUPLED_LED; + } else if (g_slots[slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCP405B) { + gpioOutputPinsMask |= 1 << IO_BIT_OUT_OUTPUT_ENABLE; + + gpioOutputPinsMask |= 1 << DCP405_R2B5_IO_BIT_OUT_CURRENT_RANGE_50MA; + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_CURRENT_RANGE_5A; + + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OE_UNCOUPLED_LED; + gpioOutputPinsMask |= 1 << DCP405_IO_BIT_OUT_OE_COUPLED_LED; } else { + // DCP505 + gpioOutputPinsMask |= 1 << IO_BIT_OUT_DP_ENABLE; + gpioOutputPinsMask |= 1 << IO_BIT_OUT_OUTPUT_ENABLE; + gpioOutputPinsMask |= 1 << IO_BIT_OUT_REMOTE_SENSE; + gpioOutputPinsMask |= 1 << IO_BIT_OUT_REMOTE_PROGRAMMING; gpioOutputPinsMask |= 1 << DCP505_IO_BIT_OUT_OVP_ENABLE; gpioOutputPinsMask |= 1 << DCP505_IO_BIT_OUT_OE_UNCOUPLED_LED; gpioOutputPinsMask |= 1 << DCP505_IO_BIT_OUT_OE_COUPLED_LED; @@ -131,24 +206,7 @@ void IOExpander::init() { const uint8_t N_REGS = sizeof(REG_VALUES) / 3; for (int i = 0; i < N_REGS; i++) { uint8_t reg = REG_VALUES[3 * i]; - uint8_t value = REG_VALUES[3 * i + 1]; - - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { - if (reg == REG_IODIRA) { - if (channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1) { - value = DCP405_REG_VALUE_IODIRA; - } else { - value = DCP405_R2B5_REG_VALUE_IODIRA; - } - } else if (reg == REG_IODIRB) { - value = DCP405_REG_VALUE_IODIRB; - } else if (reg == REG_GPIOA) { - value = DCP405_REG_VALUE_GPIOA; - } else if (reg == REG_GPIOB) { - value = DCP405_REG_VALUE_GPIOB; - } - } - + uint8_t value = getRegValue(i); write(reg, value); } #endif @@ -161,6 +219,7 @@ void IOExpander::init() { bool IOExpander::test() { #if defined(EEZ_PLATFORM_STM32) Channel &channel = Channel::getBySlotIndex(slotIndex); + auto &slot = g_slots[slotIndex]; channel.flags.powerOk = 1; @@ -169,23 +228,9 @@ bool IOExpander::test() { if (REG_VALUES[3 * i + 2]) { uint8_t reg = REG_VALUES[3 * i]; uint8_t value = read(reg); - - uint8_t expectedValue = REG_VALUES[3 * i + 1]; - - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { - if (reg == REG_IODIRA) { - if (channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1) { - expectedValue = DCP405_REG_VALUE_IODIRA; - } else { - expectedValue = DCP405_R2B5_REG_VALUE_IODIRA; - } - } else if (reg == REG_IODIRB) { - expectedValue = DCP405_REG_VALUE_IODIRB; - } - } - + uint8_t expectedValue = getRegValue(i); if (value != expectedValue) { - DebugTrace("Ch%d IO expander reg check failure: reg=%d, expected=%d, got=%d", channel.channelIndex + 1, (int)REG_VALUES[3 * i], (int)expectedValue, (int)value); + DebugTrace("Ch%d IO expander reg check failure: reg=%d, expected=%d, got=%d", slot.channelIndex + 1, (int)REG_VALUES[3 * i], (int)expectedValue, (int)value); g_testResult = TEST_FAILED; break; @@ -196,7 +241,7 @@ bool IOExpander::test() { readGpio(); if (g_testResult == TEST_FAILED) { - generateError(SCPI_ERROR_CH1_IOEXP_TEST_FAILED + channel.channelIndex); + generateError(SCPI_ERROR_CH1_IOEXP_TEST_FAILED + slot.channelIndex); channel.flags.powerOk = 0; } else { g_testResult = TEST_OK; @@ -204,8 +249,8 @@ bool IOExpander::test() { #if !CONF_SKIP_PWRGOOD_TEST channel.flags.powerOk = testBit(IO_BIT_IN_PWRGOOD); if (!channel.flags.powerOk) { - DebugTrace("Ch%d power fault", channel.channelIndex + 1); - generateError(SCPI_ERROR_CH1_FAULT_DETECTED - channel.channelIndex); + DebugTrace("Ch%d power fault", slot.channelIndex + 1); + generateError(SCPI_ERROR_CH1_FAULT_DETECTED + slot.channelIndex); } #endif } @@ -222,24 +267,10 @@ bool IOExpander::test() { #if defined(EEZ_PLATFORM_STM32) void IOExpander::reinit() { - Channel &channel = Channel::getBySlotIndex(slotIndex); - const uint8_t N_REGS = sizeof(REG_VALUES) / 3; for (int i = 0; i < N_REGS; i++) { uint8_t reg = REG_VALUES[3 * i]; - uint8_t value = REG_VALUES[3 * i + 1]; - - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { - if (reg == REG_IODIRA) { - if (channel.boardRevision == CH_BOARD_REVISION_DCP405_R1B1) { - value = DCP405_REG_VALUE_IODIRA; - } else { - value = DCP405_R2B5_REG_VALUE_IODIRA; - } - } else if (reg == REG_IODIRB) { - value = DCP405_REG_VALUE_IODIRB; - } - } + uint8_t value = getRegValue(i); if (reg == REG_GPIOA) { value = (uint8_t)gpioWritten; @@ -254,16 +285,16 @@ void IOExpander::reinit() { void IOExpander::tick(uint32_t tick_usec) { #if defined(EEZ_PLATFORM_STM32) - Channel &channel = Channel::getBySlotIndex(slotIndex); + auto &slot = g_slots[slotIndex]; readGpio(); uint8_t iodira = read(REG_IODIRA); if (iodira == 0xFF || (gpio & gpioOutputPinsMask) != gpioWritten) { if (iodira == 0xFF) { - event_queue::pushEvent(event_queue::EVENT_ERROR_CH1_IOEXP_RESET_DETECTED + channel.channelIndex); + event_queue::pushEvent(event_queue::EVENT_ERROR_CH1_IOEXP_RESET_DETECTED + slot.channelIndex); } else { - event_queue::pushEvent(event_queue::EVENT_ERROR_CH1_IOEXP_FAULT_MATCH_DETECTED + channel.channelIndex); + event_queue::pushEvent(event_queue::EVENT_ERROR_CH1_IOEXP_FAULT_MATCH_DETECTED + slot.channelIndex); } reinit(); @@ -281,7 +312,7 @@ void IOExpander::tick(uint32_t tick_usec) { gpio &= ~(1 << IOExpander::IO_BIT_IN_PWRGOOD); } - if (channel.getFeatures() & CH_FEATURE_RPOL) { + if (channel.params.features & CH_FEATURE_RPOL) { if (!simulator::getRPol(channel.channelIndex)) { gpio |= 1 << IOExpander::IO_BIT_IN_RPOL; } else { @@ -307,9 +338,9 @@ void IOExpander::tick(uint32_t tick_usec) { int IOExpander::getBitDirection(int bit) { uint8_t dir; if (bit < 8) { - dir = g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405 ? DCP405_REG_VALUE_IODIRA : DCP505_REG_VALUE_IODIRA; + dir = getRegValue(REG_IODIRA_INDEX); } else { - dir = g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405 ? DCP405_REG_VALUE_IODIRB : DCP505_REG_VALUE_IODIRB; + dir = getRegValue(REG_IODIRB_INDEX); bit -= 8; } return dir & (1 << bit) ? 1 : 0; @@ -323,7 +354,8 @@ bool IOExpander::testBit(int io_bit) { #if defined(EEZ_PLATFORM_STM32) bool IOExpander::isAdcReady() { // ready = !HAL_GPIO_ReadPin(SPI2_IRQ_GPIO_Port, SPI2_IRQ_Pin); - return !testBit(g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405 ? DCP405_IO_BIT_IN_ADC_DRDY : DCP505_IO_BIT_IN_ADC_DRDY); + auto &slot = g_slots[slotIndex]; + return !testBit(slot.moduleInfo->moduleType == MODULE_TYPE_DCP405 || slot.moduleInfo->moduleType == MODULE_TYPE_DCP405B ? DCP405_IO_BIT_IN_ADC_DRDY : DCP505_IO_BIT_IN_ADC_DRDY); } #endif diff --git a/src/eez/modules/dcpX05/ioexp.h b/src/eez/modules/dcpX05/ioexp.h index ba2157c12..a96e45feb 100644 --- a/src/eez/modules/dcpX05/ioexp.h +++ b/src/eez/modules/dcpX05/ioexp.h @@ -51,9 +51,11 @@ class IOExpander { static const uint8_t DCP405_IO_BIT_IN_ADC_DRDY = 4; static const uint8_t DCP405_IO_BIT_OUT_CURRENT_RANGE_50MA = 5; - static const uint8_t DCP405_R2B5_IO_BIT_IN_OVP_FAULT = 5; // active low static const uint8_t DCP405_IO_BIT_OUT_CURRENT_RANGE_500MA = 6; + + static const uint8_t DCP405_R2B5_IO_BIT_IN_OVP_FAULT = 5; // active low static const uint8_t DCP405_R2B5_IO_BIT_OUT_CURRENT_RANGE_50MA = 6; + static const uint8_t DCP405_IO_BIT_OUT_CURRENT_RANGE_5A = 7; static const uint8_t DCP405_IO_BIT_OUT_OVP_ENABLE = 12; @@ -86,6 +88,8 @@ class IOExpander { uint16_t gpioWritten; uint16_t gpioOutputPinsMask; + uint8_t getRegValue(int i); + void reinit(); void readGpio(); uint8_t read(uint8_t reg); diff --git a/src/eez/platform/simulator/front_panel.cpp b/src/eez/platform/simulator/front_panel.cpp index f4035ff13..ac11fd90a 100644 --- a/src/eez/platform/simulator/front_panel.cpp +++ b/src/eez/platform/simulator/front_panel.cpp @@ -68,9 +68,11 @@ void data_main_app_view(DataOperationEnum operation, Cursor &cursor, Value &valu int getSlotView(int channelIndex) { int slotIndex = psu::Channel::get(channelIndex).slotIndex; - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + if (g_slots[slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCP405) { return PAGE_ID_DCP405_FRONT_PANEL; - } else if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCM220) { + } else if (g_slots[slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCP405B) { + return PAGE_ID_DCP405B_FRONT_PANEL; + } else if (g_slots[slotIndex].moduleInfo->moduleType == MODULE_TYPE_DCM220) { return PAGE_ID_DCM220_FRONT_PANEL; } else { return PAGE_ID_FRONT_PANEL_EMPTY_SLOT; diff --git a/src/eez/platform/stm32/spi.cpp b/src/eez/platform/stm32/spi.cpp index 781f6d650..41c1deec4 100644 --- a/src/eez/platform/stm32/spi.cpp +++ b/src/eez/platform/stm32/spi.cpp @@ -85,7 +85,9 @@ void deselectB(uint8_t slotIndex) { void select(uint8_t slotIndex, int chip) { taskENTER_CRITICAL(); - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCM220) { + auto &slot = g_slots[slotIndex]; + + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCM220) { selectA(slotIndex); return; } @@ -96,7 +98,7 @@ void select(uint8_t slotIndex, int chip) { // __HAL_SPI_ENABLE(spiHandle[slotIndex]); - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP405 || slot.moduleInfo->moduleType == MODULE_TYPE_DCP405B) { if (chip == CHIP_DAC) { // 00 selectA(slotIndex); @@ -134,9 +136,10 @@ void select(uint8_t slotIndex, int chip) { } void deselect(uint8_t slotIndex) { - if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCM220) { + auto &slot = g_slots[slotIndex]; + if (slot.moduleInfo->moduleType == MODULE_TYPE_DCM220) { deselectA(slotIndex); - } else if (g_slots[slotIndex].moduleType == MODULE_TYPE_DCP405) { + } else if (slot.moduleInfo->moduleType == MODULE_TYPE_DCP405 || slot.moduleInfo->moduleType == MODULE_TYPE_DCP405B) { // 01 ADC deselectA(slotIndex); selectB(slotIndex);