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,iVBORw0KGgoAAAANSUhEUgAAAKgAAAHkCAYAAACqpmQXAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEpoAABKaATLVu+gAAD2nSURBVHhe7Z0JfFxV2f/POXcmSdOFsm8FSzOZTNpSCoW/4lpQVJY2k2JQQFkFX19EQQVBUevOIqjggvDygsjLYqWZtCwCCggKyFoKbSYz0wUoW1naQpckM/ec/++59840aZNmaZI5M32+/Zzec85dM/d3n/Oc5Z4rhTB7CyE2IDCMbYxGMOP8OMPYhhmnghjDWAkV8bCg8r0gbR1Tp87eM5sNf0tLM00YN6SEetmEQtemlsx7MtiEKVvMOCsFWlsfv0EYcRqiyuA/XGRv0OrF6eT0Q4SYq/0spnywTKC1sfhDWMz0UwVcqHCtFKad4rjWUViORwjTyjzYZnUmOR0VPhZq+WCJQA+oO2VaSL63CNHAWJoOGRInpl5safbTPTOhpilSHXb/Y4TeJcgSUokfpZYm5gZJpqSxQKAT6+PnhY34VZDUjhg/PZm86YUg3U+OrqyNVa5GBH8LMPLxdFvzh704U8IUuRb/gVjDt/PixJOyNJ1MOMJZc+CkKfErJ01qOtDbaBtMnfql+pqps34TmRw+GfuOl1L6Qpfm8Nq6xue8OFPSFM2C1tScNEOFNz7tJYxaoKVwlNDHeunNmJDsOLG19d47grRHdErjR41rHoHfKQOfwMMY/WxVWF/ekQvd7lWulL4uvXTBV/y1TOlRRAuqwhue8mPyeSVzFV3EmZNCvktyQ1zmTOXtkyd/7pP+Kvir9XM+AHE+iijEaWijNYhnaZ2U6pDOnPP9nDGXeMLV6ux9950zgaJMaVIUgdbVxf/j6Qu18nSy+WAt1Gf9NeJ0FNXhVLJ513Syha5tFWVmde4+WhIh7bZ5ESNeo20yycQu2KdChcTn/Ww5Jbth9VVYpildPU4voyVTmhShiJ+ramOLXIrpbLhWVXQ0ooi/HNfQDrFSE1I3amNxz5RCiJ5RpDQtIcqupbtHbV18I/6iUQh3pVsTs/LbhsPimKUvJO71NurClClNu3S6nb/DuXcLsoB8H/+tNVK/IHPqb+l0c6uf3xNzVbT++RPwG34BF0k+82ic8BVpxL06J3+7bFkzVdzoms/FYhZC/po3Ifo2SoBn3az8S367noAv/T341DPx5FXi2O8ok/taKnXXq8FqDxz/eCy+hlCHY+L0KoWy5XeZtsRfvA0Cauvjv8dxaoNkN4yRt2Xamv83SIpoXeOlONKMIAkkfltD512ZE+qOFa3zX/Lzh5MiFPHR2HN/pyV+7A3Lls3LCO181Fsh9Dv+cmu2UmIvSGXepCWOHfMyjPkrLTo7RY/NVVnTOQV7fQHRT20OphHhdGnkVcIxS6L18b9MnB6ndtdu7H/gsTvjQfsHhHAbxNmArEkIe+JaD8UFf1+FDbkwwaXL7+C/oxDy54FYzem4zmuwXTpSN3s28raitn72xyGKnyL6KRzpYyhz4lqF9/fX+kRjcWpSo7+T2o/3RsmxF479cWx7Bx7YS2ibAGhXfBXLLn/r5qCUOQLLAhDneVh02cbM9vY34rKQcZO1scY5tN1wM+ICxQ/oNcRXhar8BnnZcbO3NAo/7PYBoexHS1er+2mZbmtpoiVuViUttwTi2qx9Ix7FTbkIovkF9oCgvY4BiWM2hdvF/RMmNHWx7nNVZTZ8OyL5ToV12P5qb38j/wfHWou8/SdOPC04b+E879M2WP99nOfBIG8cfOdb6uvn0KiyLsxV+E3yzW89jjarq5s9Dcf5vpeQohXnPgH++9lI4Xq8vB/UTG3wfpOu4GIew28yr2vQWiwMVufJX/NyRL6CP+EbyPIeeCyr8DddhxKowk8PH8XwQb0//MUXb/dq8OnkPXfSElJwotH4oPvXI7GGJ3AQh+Ir2qZRkepjRActJk+OoyjuHUjoqUxry2VwJb4LV2OOycmpyM74a8VhVWNyuEE+0frnyGJ+2k+J1VI7h8Af/oa3f1vzWdlR4gAc8JKVK28ikReACdtA26TbEj/Fecgy5UUx1jWaXIACsM6nYHEIiR0i/KOf2x0t1Zex8O6hNubLOPc8+O/X4zxwmTzCKiu3snQwElemWhMndA1bugNdeDOVTFyXbmu+Gr9LE3amCiqxa2dnriaIDxsjKtApUxqPDKKeaPLkQrkLaWmUOIz8xq7B2wD0ls4HWI4PUr6RsGBdujthHf5Ny6w2l3oZ/SSTSSxTRtF4AA/c9LOCKFnqM4MoxeemUncuD5IeKxcl1uKG/ixI9gYMnvlbEBdayA8EUfKNx2Dh7W+U+TFu0tsU74GPBMsN++21bvPDraXnRnlIeVAQGxqkyPvLJlsRohaUYWVEBdrpavLF6I/0aud5Vrx41xXCcU9GtCDAgYIdTVZmL8q0NheERORMlXdOCHgfL2MAtLXNJ3Hnr3VSUAyjBJCB34yThs1dQXTgGFloAsONKPjgnbksPbB0vW07Ve/5Wy+zZwK3yLzy8MMP5/w4XBxXrgyixK7BsoAR+kj4rmdH6ho+F5k8+2DPnegDemjgj8cRPc7PkXeuXDLvDT8+fIyoQHFnPSuhjOwmUCK9ZOGtqJkrqp13DcFqr9beUzofUGSqla13XxasLqDcdV63qTYi5GUMnEIzVU7pPSdNaqLu1J38HJFd9mLFa0F8QESjcw7DH0P+IoHCQz9AEc9nlOLbXq6Q337mmeu8Nt6t8US1O8VQZK+nZZ5N43Ob09IUxinkwcN6DrkNUsp5Uqtn4U4k4c8eGqzeksOphOp0s++jtKDKZiUO8C9l3DP81cPLiAoUZZonEle5G72MESCT+WDQiO8lBwM1OxUIhzvoNYQ8m4SY5zWZ9QcIaXfc7GUIbxmlqUjOW7frk8kW70FSOaqkCaqQ3Q+fr1frPHHiSqqgeCO68Ld1G8G1c3tVwZripPkKItwgz6V4GOEuIwyVDnlXqxb+7N14+PIP3rYx4qPY/rFIZPg7QUbWgkq/GUhp6T35Xamp+dy5uHGantauIVjdzQel/7pukw+RyXN+Seu7su++j+7sRaiRZXDsGyyF6VBvh0KVXduMx0UiR/sDVPoHVeKoOSrf7vo2RHvxvnutPYcSkSkNk3GhJyGaM8r9JuX1RlABwwMCjCCftUBHx3rUsn1gLQvWNJVsORqlzREIszLJlo9K7VVyXvbXij1UOBcU3914nEqobPv4UcKVk2FF893OU2VI/zqIDxsjKlClXK/5Bzcl6mUExCY3/AI/ztWIkoxISAj4KbxQIMinjYjCetrDi0itv1Uba5jvrQ6oHjvmoiA64M6IA6bO3hMLb9AKLuyNTGb+q0uWzKMbXijWTaiCavv9g2rkRp6Aa/2MI8y0dDK8VybZfGnBf8wp+IPenxeS2nkx/+Bh+5976wGu47FILP6PIPWuvxT+Qxigw9V03R4wra8E0a3wG/xlofaupOnWxtoVeiCo0yLXMZ4qjp1+rtdGOqyMqEBznW5QkzZj/aWPq6UnIuOYP6fbPD8UocUL3gbAz0t4aRJjfr2Xj32kMTfSOvzgjUI0ec1NBCyIV+PG+oFWZmQop8in9dwSI6ltNHhAjAgEApMoJVVotnIgqJcqiBZACdJOTUHwl+/3i/Qt3QPdrXWjN3B+79gQa743Z++u7Z0hoQs9QPi7nw2iPYL1XcXda2dJntGjN0DzxncpfGMyrIyoQDOZe4MbQB7hTO/GR6ON1N4HZDazpCWIb5ue3Ml0W8sZyPee7MiUXKHLDr+g1wukc535CkmP4IrGR6PHT6qtbayP1DU2wHKRv3aqt06INfCbC81UcPnI2ns3CeYN2zbcSpWMmprGPWgZjTVehUrFawMs/vGkdi7E9R4BIR/VLRhxQ7AFTii/gYfCG3eghSyMUXBy6vyZM2eG6MEw2vitJajE5ZRKUIT+JlzXWV16xST1BhlpqPWEcKVxvYraFoSo14xCzZTZkQ43dz129VwIXCv5s8PKiArUQ/pPaTS20z9piYfQ6+3Bne6lyWIgD6nXVyyka7zByvVTm6h7EfoyZvPD0QtGnAG/b5lwzFKIgm5qviH+LQihcfmShXlfTbS1LXga2/8wSAL5BVQanlJh8yYt8Tedj8yKysox3SovfUHXCOv6cKq15e9dA/6CQksCdSgkk80pL54V12LhtZHSOV99Y/w6PBhvYPvpfp64Jt9nLpX+CLa5Ltwu1uDhW4sAH9bciTWe2GCNr2hrW7iC4ltwWGU2/C4F5ao0tssbkde1crz26+FkxAUq3fFeGyL8UE9EWmi/N0Xm2/S6AXH1ZC89trp2bby2Q7Isi2mZy2XvpiWK5wW03Iqc8xb97ye6QTX/53DmH+msnAo/0XuYukK9QVLKExHN9zYVgDBewjNxZuCvUtprScDfnPfdBgT81sI1ooJZiNMgE6nVMYh6I7dANQLV7Dvh2vxqwl5r85aULD31AOV9Vqqte92U5Fvjws5DBeq7lO5CjwbD+9uEuVoZfejy1jvz5x02yLqM+IBl/+kVlfjRns20JWZQRcBbYcRvcONpkIJHbV3jOjhJVEy68DU9lyBS17ARwhgFeW5ML00Umnwikxt+JrX0fuRs+9pRonrMaWEd+gOlsW+vKt9nxqzqUKcu9NWHNlTr5cvn+X3Z/YSKPukq1M6pHU2tyFu4PFSsVmx0dumsdt+lXqYgu99Q0f3662P3c91wFhW1rdqQgYxMnj1duM7+cL7fD4VCi/Bw5MVYwD/OuMlahfYzOucIR7wyYY/3XujayJ+H+tnfVxu7NqkJ9f7um7bsvh1eivROUjR6xiSj3vWKLalCn5KuPkNLTc0rBAyeyMEND+eNZ1U4PO2FF+Z57YQ779y00257ZrveZLJODhTuWdSQkE8kky9/vDa2n2etUHu6oq21ZdiLImY4KNKI+lTqf5fDH/L6ofEk/32DcS6EKvMVG1QsC+I0VaPMUXlxEmvWzFuH4iUGa5v376hIU7S5MuYfsF6H18b299sHhXmPxVnaFMWC5onE4m/gArw2OwdWMgkhTjmo6aPZTZ3TstnqO1esuM1r2O+N3epmj91JmpMq5LjnW1v/7wkhZoRr6yBOaaiZSaNoLzQ3MaWIBa8d19bHyUn3RIoa6p8zrYl+NTVtyYEHzm5qz6p8o7OucMKj4IcNqlLC2IIlEzdE6+KLIM5gWJjUWpivLksmrvPT26amBhWUkHwBFSe/uUTK9anW5m4dAUypYolAifrps4/KtVPD8+ZhHYi9a4T6tcnue3kmc02hHTMWO3aOFqFfGiEnIlnYXqvcb5ctvWvzYGWmxLFIoHlq6uKXKCF/BD+y3xU4Y8xDmbYWejXZb65iygQLBbqZuWpS/QvXK6HnQIG4RgJ1fSNhOGWH0e7j77vOiW9u441IptTx7nv+5jOMbfAMy4zlsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq5He1y4Zxl74nSTGVvidJMZyWKCM1bBAGathgTJWwwJlrIYFylgNC5SxGhYoYzUsUMZqWKCM1bBAGathgTJWwwJlrIYFylgNC5SxGhYoYzUsUMZqWKCM1bBAGathgTJWwwJlrIYFylgNC5SxGhYoYzUsUMZqWKCM1bBAGathgTJWI/ff/6IZ4epkkGQYe8hujAlZGzv+ASncnYM8hrEGI5w1tODpFxlL4ekXGcthgTJWwwJlrEb6Pqh8L0iPKNHorN20DB2BaIVwzROZTGKZv8YnUj/nEGXMgUbKtyuU888lS+atD1b1STQ65zCtTFwKPUYK9ezG9aG/VI3p/LiQco9Ma+LPwWYeB0ydvWfIleemW1u+jyRP2m8NRfRBo7HGU41yVkppviWFOUk64una+viFtG7ChKZRtbHG+VLrf2hpPi+Me0mnm10xqf74Wm/nPojEGi8yyn1ECL27EeodI8xpo8bkboZQR0sjrpk48bSqYFOPcM45WRh5JKIsTssoikCj0caDIJr/gR7OTicTH0q3JY51s+GJxohFtL5qTPanWHegNLmpsHbHpJMth0stjq0wbtY7wDaITJ59MAT/Mygtnkm2nJ1JNv8Y5zhCO+7F2fad7oEETcWotZ8NNvcwQjdhcZufYixj5JuZYCl/j/BokOzGlClNFbWx+PsIpwVZAyISi1+JfZ8IkluB895QG2soiLFmasN+2L6Tivkgi7GG4hXxM3Dyx4N4N9p1LorFGKmdXkW2TaSoRzH+YpDaCinFrfh/1j4zZlV7aVd8DosHV7y44E1KM3ZRHIEaWSW0WhekuqGkHk1LxzE9ru8LaYw2yvTqCqSWhh/GYl31euc4Sksjm6SQXLxbSrEs6EooY/8g3g2ZC71Fy5x0e1zfN3K5MZKscC/McyHIO2BJP0/FOzIO0rn2Zn8dYxtFsqDiSfw/B/7mGD8jT5OTSt25ApHl0pWn+HldaXKCSK9AnP+QQsysq5t9aJDlEYnMmRBEUbn3LObRypWnQah3ZzL3FqWZjemborSDTpweHx9uF8/g5Gu0EZcrR7wjtPkUiv59U22JU1BpOR6b3YHweziNC43WO0kpz0JN/tepVOJe7yC9IyN18dsgvGPwINyAR/B1HPcjRpowtQgE2wicI4W/fT9sd1KqtYUtqJUUqZK0clFibdYJf8QI8RgEcpHW4lLEq92w+R6tTycTd0ppqCloojHmShTJ/2WMSKRS4ftpfR+YTNv0k7A8HeKshEhr8CDcVR3eSE1JBeCqftsI+Xud7bwnyGLshEczMbbCo5kYy2GBMlbDAmWspqijmQL2QqDBGxoB1+MFitPDQ0P+B9Vg3wVqT6Vj5QeC0JLOQXlvIGxCYKzEqx8VrZIUQqAGda/LsRd2QzjAjw4Y6pGKINB5emNfhH38KGMfxRMoWbB6P9onFQiT/Gi/GYUw0Y/2yU4ILFIrKZ5AaxBIpP2FrCG5Av2FLOdAoJFMW/RqMcWnOM1M5G+S35f3CfvDBgSyiv1hDwTyLQcCjWTa3Y8yNlEMgZK1es2PDgiqyPXHypGQ+/1qSBcG8sAwI0QxBDpY3kHYxY8OCwOx0swIUQyBDrelGuzxqUmLZ1ixjFKyoMRAKlYDhZq03vajjC0UQ6B0zsEIjXzX/ryWMdi/qRKh048ytlAMgb6CQCPZBwo1NbX70W1CPU+DKar7HAzNjDzFEKiLQBaUGuD7y64Ia/1on5AvSdsPBOoOfdWPMpZRtK7OOoT+WK2xCAO1uHRcOn5/oPZPbgO1kuL1JOWhHiWqnPQGCXOw3ZD5vv7e/j4qPej8A7W2zIhhxqGoJYEWdTQTCYjaN/OjjMgFyFtWKnb7nE2kD+gB8F5lBl2PT+d7OVgyVlJ8C8ow24Bf+WAshwXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVSGNMEGUYC/EFasb5KYaxCTOOi3jGaligjNWwQBmrYYEyVsMCZayGBcpYDQuUsRoWKGM1LFDGaligjNWwQBmrYYEyVsMCZayGBcpYDQuUsRoWKGM1LFDGaligjNWwQBmrYYEyVsMCZayGBcpYDQuUsRoWKGM1LFDGaligjNWwQBmr8SYPk1KMQ/S9IK8UGYuwix8V3mRTCCGELGUAehClH/Wg+BsI7V6KsRQzrpQFuivCTgguwvsI7yIMhL0Qqvyot28pP6BlSmkKdAJCGOEdhKG6ZhI7zfC3AWE1ZTA2YMaV0vSLuyNMQqjwUsPDaIQahJ29FFNkSkOglQgRBPIzR4rdEGoRuvqtzIhjv0DJkn3Aj444JE6y2CP5YDDdsFug+yKQJSs2tlzHDoi9MyyT1VyP8LaXKi6vItDvRLV+ZoSxUaB7I1ANfZ2XsgOq2ecQWKQjjG0CJQFQ4zlZT9vIW/PxwZIZAWwS6BgEup41XspOqPeJeqyoHZYZCSyqJEWDZSlQStdawthTi6d2TsePlgTVCHv4UWb4sKOrk242+XWveanS4QCElQj0hA8506Z9enR7+9g9dahzlMqZtanUXfT7DMu57MUOgdKNXuFHSwpqyN8f4SUvNQRMqj++1tHul4U0s3F4ciO61hGoVeNRhFv33WvtvIcffphaFcqc4guURhNRb9HrXqr0oJ6m5X508EycHh8fajeXSyFPR5KGCfZFRmrx9VQqcW+QLlOKL9CJCFRMlirkN9PoqkFb0Wi08SAjzULY4/2CrH6D8v7KTHL6hULM1UFWmcHf6txeaCzqoH/DurrZ04wy/xyMOAn4GN+KxJ67NkiWJcUUKNWCy2HsZSfCgNtFJ01q2kkLdReiNOh60MAtOCta33hOkCw7iilQqr1v9KMlDfXV7+NH+49T2XlpH5YTxbb8JYr/ixB/zs/qGbhpl0Uic8jVKDu4iB8aBjRuNBaLTxRGnhkkeyOZTjZfkGltuUxKeXmQ1xujRUh/J4iXFcUUaDm16Q3ob8kJcSoWfbkFhYqP1qbPShCekFMmTjwt/45V2VAsgdIo+R32jUopzGeD6FAyrmLUmo8G8bKhWALdE6GcXk4bQEVpLn5zeVCQ6EoW+Y9g+XcKxsgnvFxCyjfxv5cfhB6b5oxWBwfRsqFY7aDUA/OyHy0LRiHQC3d9DrCm2rtTkV0bJDcjxf+mWxN9+aUe5MO6PfS+wc+4KpNMfCtIlgHcDjpUbEIgkfZJKNTes6U13ivP/SKXC/e4LfzQshsGyBZ06OjX3zRjxtnh9zasJv97S+OwArX1K4Ux3mwo2ph3M20tf6V4NHr8JKHcT1Gc0MIcSu2fQbIABPqjVDIxN0iWAWxBhwpqZupXTf6ZZ67LYsNMkOzKATAWv8W6P1KAWH8Y5Ast3UPz+d66HsRJaCOXBNGygQU6NJD/2e8iGmp+OIgOJW4uFKLRTmVFsQRK7XqlNEC5L2g869YVn16AU3VrEB1K/r5yyTx6JaWsKJZAaXgdNTWVC/Sw9XtEUSbZ/E9Y0ceCZG/sV1c3+1DyP6USM4O83pH650GsrCiWQGkUUNnVOAeElv+N/6n9tDd20lI9ZZS7DCb3q0Fej6CSe0u6dQG1oZYd7IMODQP+HVOp5ueNNOcHye1ALumsFOcGibKDBbr9UPGenyh3QGRaW34vjLwQUVTOB8WLJic/u3JRot/+b6lRTIHSj5qfFbmUoWFuNORuUKTbmq+AFZyN6IBee0FF62a3M/zRTGb+qiCrLCn2Kx9D8k5PkRmS11ZQIRprpPoaTCm1cdKLhD3Rgdt1L1yDK5YlW/qqZJUBxX8nqVTf6MxD3Zv47QQN5hgyIpNnTRFuaLpQegJcgCpp5BqUda0m2/6fTObeYtynIlH8iRvIxRjU+ziW0JulY4aE4nd1UtthqTY30eBg/krIcFNkC0qUqhWl6XqYYcWOwSJkRanBul/D1SyBPuhAc5gyw4wNAiWokkHD1UoBmvmD5q23eZrI8sGCIj4P3Xj6BIzt1AVLZtixazwoTYZFxeaA3zEfQajddsgmC2P6xiaBEtS7RP6ojXNvUkXuLQSuuY8gtgmUoBfPyO+gjynYAn11hBrI6ZugzEhikQ+6JcX8iFdXaJ5OmqaHGXGK35PUFzTBw3B/n7M3SJQxBBtLmR0E+wWahwZk0BffRgqy3Da5GDsopSNQghryqRmKGsmHC3oIyGKX0/tSJYwdc9QPFPqeEomUeqBeCZbbA7kP+akLaVxnhx9lik9pCjQPvYtOvU+0JN5F6O/fsCsCCZ32pfbXsh70W7qUtkC3hEbn5z+dTRWbLb+CQcW2588gUDclNxlZT2n5oMwOB099w1gOC5SxGhYoYzUsUMZqWKCM1ZRTM9MAaHJisc7JWSErOtaHl65aNY9mSC4K9GUOZ/S7E5YtWdDTnKG9Eos1RnM5ubG8J27YAWvxNbGGD9fGsotdIa+QRlxQNSa7KBKLfzpYPeKERq35hnJVG30WMcjqkWh01m7RWOM3g6TQwsySIf2xIFm27FAWFFZnV1eYxUKZOemlLf+hvEjk6EqlnN1Sqbtepc9h601jXwlVrftgpRN6fMmSeZ2TpszaX+nwPhurs4tfe2Zh4ct4EBS9+jF2zJi9nqdZk2lq7w0bVu/rOOG3O9zcgdj/Bey/3t+6d2pj8UVSyAeN1Ll0awvN0+RB34vf0DnqIOPoVcuXLHwlWt8QN0b9BMKkbyx5yFxHOlc5pjrUIdxMZh4Nphb19XP2xt/TgXO/O2VK0y65nFsvpUkmk80l+JJfefUk9UmkLv5V/LEfyiSbCze5K7Cki6X3Ap9ca3LmIunIOXiEPymFeQ6/0tEmFz6KhFAba7zW79wwb+F4h4yr3uPI999/p8Yo904jxBtKitfws34w2z5+2sqVN7Vj+xZse3s6mbgtOJVHZPLsg6VWl7uOe6aTc/6Vbps+kb5cTA+Fo52/SS0eMlJOlcr8WLviLNynI7Bbwt9bfBDn+CPdNwiwEcf+HGXW1sX/bqT4qZQyLIz5iTDifmTP0tKcU3rT5exgRbySslYJk6Q4LNeJ0Vj8aQqRutk0eVeea9PJ5iYhOt4yuKnjRu8xK5VsuRg3eoF0sifV1DdORQF7EARxUjrZ8g3kv/L+xjePCvYdI3IdjanWxJcg1FcqqtccRpkQ2S9CUm097bdWX4SYboeFfBnV1ZeikxeRAIXjhs41wlyfakucg2uZqTs7/qWMM1dIsRLn/QoFnPc+lASyunIDCfYTNLdTTU3jHtimJpOc/ggszy+U0aem2xI/0EJe7Eh5Hh271LBZoNS3TuNAaWwmBXoniNKDfqkOgnsDgvAmiYDw/toezh6F9JNSOHRcD0eIZ7ylE54Iazr2/Y2r/y9aH/8LLOVBEN07ytAIe7kP5Xn5eMpdbfxKlhRv5OdOgtVdbYzjdSFnWuc/0do6f4vZ65ocHP9EpWVtpL7hOxDcJqPFF2kNLGKt0arV2wwnwDF7HWG1ePH9G7DJfa5QDTJkjsdxcE1zNa61Tkv1M7pGJc1ZsKbLgl1KChsFSoM6ahFoGB3NGkdvUVKgoXWUJl+KZvXwbv5AgCW5BzfwhJqpDfuR3/jyC3evkUb2+BJcZ9gr6jelWqd/ARbxBFiyhkxb4hYt9BtQ38uU5+e3HLesbcFDwW5dQBVsG0Ri2U9isRJ/5DOwpMtRXN8MwTbsM2NWNS70bdyYwkNDYlZK49Q9v1mgjboFxf/nEU5wpLklyH7d5MR3/GtMHO+VAiWIbT5oXpxeMdwHNLiYKiHrvFQ/8WvC+gL81fPw178Hs3oyfoLvk/jIBw0JMTuZTHjTKcINuBG/TTV+owex3SThmPnppQc/VRt7/iH8cC9CqM9D8AdpV/1CKTnGOO6f0q2JDwb73iqk+r906/y7EV+IrFshlIIPGq2L34xi/KF0W8uNQRbt8zf4jjfhQVopjL4d7sh1ZLmFK24Kh8OPdrrZl3DdV0hXtKB4P80o8xJNgjtz5szQq6+PX47reRvnOISOBdflbBz/dCnVLTjWTli3KtXacrN3opLBPh+URrP3R5wEDS6mcZ0D+htSyearIK4Zxui/w2KlpHaOJnHSOsfI89rbxxe+lIGbfQbKyj/g4e0QSt9N4qTic9zo3T8FYd4FE9kphb6R2iLXj+182fizJXvA/7vKZJ0ng8RPs064u5V1xJ9GVW5CcbwZ7PMNLXMvkEsgcuKTJCq4JVelUol7qUUAlbCPo6he5bqh10zI/ClkUCKAhx9+OAd7/TmnSw0/lUxcp4z5Ch6uDTjOovXV2vsoWMlBFpSU6qeKCvmc9DmXgQCN8RSI5YtdFpQGGw90rnXv6fKjTDliYyVpoGzvO0mMxdhUSRrsB2Zpggd6vaPPVziot2fd+re+pKQJwTlY3VkpHh7IFzKoycYo8fP0ksSiIKtHotHGg7SjPwK/cC38wIVtbQuG7PUSar/FHZuaSTZ/L8jaJrWxxuNQWTqiND/TXR4N9fTyW7++k7lu3er9pTSXaWl2ht09KtwuXzxg6ux+f/EOz/LOxpXbnMc0Emu8SCvzZ4izGkI6RKMmH6waElARG4W/gb4N2i+k1KOVETsFyZLDJgtK7X6D+VoGNeL3a8a5SCReI0LiHlgTbwpFr1tQiOtQi/9LJHL0OBmqOJE+WqCEnger9xptQ/3zjsk1KKOehbAvxq/1A1ivx+vqZh2wYUPVG11HQtEII1eYR93OcHT58nle89eUKU0V1KdPy45crok+jKBc9e9UqvlftJ56sTor3EepTbaubvY+WjlT0q3ND0Doh7tSvhbS7pG4QWM6wtlbaJtIXeMZUplp6daE1zMUiTV8BqXBdFz3f/B3eb1VNL5AOFUn41xVUotNUsiPpNoSX6Z1pYVdFpQ+hjWYKW4GNMkCHkZn/wOP3TkanXOYkSICQTxHRb8KVT6IdS4V/VrKf0BQY2jghWPcB4VR62EVT8buh/tHwU8nnXlVo7OzgqSHK/QRWPNAXpwEiZOWnW52IY4/XWi5wihzNYRGx6MLurCqo8KbvNcoOdloQ59IJMtxQkjoe0mcuKaaylx4AeV3pbau8QIpxJdQTVyE5fcidfETKB8P2k1KmA9LXCRSP/I2LlFsEii1aw50lmXq9hzYJ2CM2LsyW5EwStMHXS9e3npneu36tz6JmtY618jnsEzhpr6SzWY/5hp9PMRxZ7qt+VpYrDOx/fPBUUSuM/zJTFv4ziDpAUu1NwRFs/N1IxZrOBBr90onmy8gay2VuABiPTtY3TtG/CGdTFyDc5+PeDQaPW6L6X/MudqYG2DV3zbSNOOYn6eeKFzHZ8aO2eOrtC+2KXx3vhSxzQddjdDfvnZqu6V20H5/p51Akb4KQvmENvpYJHHz5ipHij1xU3dzjGyiAMvzrNYkVLM7fL5Cwz32LQy3863kPPoo7makWYUL6tJF6eNKtRcEVPganRYuHbPPNl9Y+Pz5cGr5lpSK/O3N4LqVkkfTNSut6OH+a/W68C7Y713qyvU2UZuvuRSxTaDkB9MP2tcXNPZCoJs16E8QUv85xPRObf3io2FNk9BAR6qt+WKEiyhkMoll+HmWY5sZtP3E6XESVGGKcvJBvX7zrrjuvXDpZ8KXPDTIoUrT4TkhUxD9tAkTmrwKltTqo1h4g1Jw/LXGkf7UO0b8P28ZkD+3X5Eze23YUNmtlQOqbYPwb8tfM3WlVlY6q1FB28Ub2UTbaOl1fZYqNtbiqdmHPo9I3Z5kjagSRE1JVLxRmnqOqNnGq8RsD9rIG6TQZ9HgZdzUpyKx+JPURx6Nxf9Ng5vDTuhOI8zE2lj8gfAm8VfEC+ckH7R6vXNckPSgQc+wuKei5t6MfZ6ojTW0YsvjV7TOfwn7Xj9qTPY5v19enqlD2it6jVHXQ0XXRmOND8I9qPcOlMeYg7H9Y6GcegxinUsVMlwvdOmjlDgfYr8N57kF2/0tUh8/k3xeXMMvVNj8G3n3w6qP5KyAQ49FXZ3bgq5vKB4mSeMmgziYq7qmJ01q2ikWi0+kwRdBlgfVrrGQvgWc610HVaLy8Z6IROZMyFvMPPmKV5AsQC0IvoWeq/JWubYu/isSXCTStDut9zYEVKGj95iCpKBrPaB+zgfo2oMsDzqef41NzpbXUTrw1DfWkhdokNxB2cFG1JcSHRXZH1eHN94eJHdc2IIy9sIWlLEcFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWsxqYBy0MBPXDUlRhG2PIrH5RHg4sLo5MY2yn9ycOojzn/yga9PEe9DjTFzJbizEMDomkkFA1ypm0lAo0Q6j5sjrGE0hUojdChD82SRdxizqMBQ3M1kWBpXKk3hSFjC6U3WITGTQ7X14/pRTQaztfvl+iY4aZ0BEqDhUmYZDWHG/otpiD0+81JZrgoDYHSAGWag2mkoTGgpT3Yt+SxW6BU66bXg2lZLOi1kqgfZUYeewVKtfPJfrToUE2f3AtuMx5x7BQoibPwcppF0APDIh1R7BNovli3lViwZEYE+wRqszgJai+lGaCZEcEugVIb5HC0bw419FtRbxQz7Njzyge1c1JPjjePkeVQjxu5IqXwMJU8tgh0d4RS6makL474s4Eww4oNffHUGE4TbvX6LSBLodlOiDXBckiYMqVpl06387O4K4dIIfbBvQkJLd4z0iSllo+kUomnsBn5ZTsAdgwWId9zMPOC2gD1cq3wo9tHMLcoTYfThLCtzomMkeLSTGv4pq0mLys7ii9QGrv5LkKpWc881AVLQ/sG9K2mLZCR+oYLpfHm8ez/WAMpntRKnzzQz3iXFsUXaClbzzzbYUWbnGhd9kZYxC8FGQPlbanVManUfCr2y5Di1uKpFlwKtfa+oK7QQVEby/5qO8RJ7GaUvpumggzSZUcxBUqjhbZ7CkULoFdIqBViQETrG47B4lw/tV3srmUI/mjvM+2VMsX8owZteSyDJtwd4NjRJscYeVWQGALMxyN1i7zvxZcbxRRoOTWVDOhvqY3lGrAY2m5dKS4IYmVFMQW6w34hDhVT72scQwmKo0Nrpszua+r0kqNYAqX3fuiDCeXCgNwVKcVHguiQ4rjOsBy3mBSrmWmwnz20FRpLQCVCn78hTd8drlpb+PhXF56Fn3CxkqbPksVodTDu3OVBcjPS/Czd2nJJkCoDitcOWm4CpWF4NMKpz6+O0Nc3VNj08G0nc3U62fKNILFNotFZuxnlbDV2wQjzu0yy5WtBsgzgCWyHCupyJJH2SVVVqMfvFkkpt/q4Qu+EenyZD4ampL+J1BNsQYeO/v5NsrYu/i68VnILtuQ9OLN99q/DFaCX+Xrqr/+6/3W5coEt6FBBYulvrxg1SXkf8eqBcVi5c18B2/U4mASm5ukgWjawQIcGsoYDKIHkVh+GHQJezyRDTwbxsqFYAqXRPz0VcaUKfXyr3/5fdpS5GeaOvqg3dBjx+3IcfldMgcLv3TFZuSgBccpfBsmh4DXjdlwdxMuKYhbx5dIXTwz4bxk3ZvfLsddQFMlaSnNWJnPvSFZyRwz2QYeGAQuUPpedVWHqk9+eAccG/76Zam25J0iXHcUUKCqkZfGA0Kj6d/zowFi5ZN4bOis/Ann/K8gaCJukkKen2xK/CdJlSTEFQm2G5fBmJH0tedDF67JlzavTreGZRorzkezfm61G3KOMPjiVbP5TkFO2FPuVjyF76ayIDNlrK9OmfXr0xo7RcSn1cbgfhyKLuk/p09trkG5FkfMvCPm2Za3NL9L25U/xZxbZBaGUm5vorQCewGHYKH5PEr3RSSItVegtzHJ4r8pabKikUJvoTn60pKABG9v7AQemD2wQKNWAizHF9/ZAvxuNXmr3UszwUWQfNA8VlTQaqFTgacFHBHtGM9HMImSNSqHCVG6vq1iNLQIl6KZTUW9zrZhKmhDC0A70YHrHkiK+KzTNtk0PTh5b584vY4rfDtob9MGCfr1CMUKwOIuCvQIlaGID6kUpNvR6RSlV4MoIuwVKFOsrc3noRTbqLWKKgv0CJXZDGOnilSpC9DUPm3+XHYDSEChBlSYSzB5eanihHqIP+FGmuJSOQPOQP0jF/nB8Mpu+Gz9SX1Rm+oUdc9QPBqpV50VK102DTgYDHYNeeKMR8TQ+labzZqyhdAXaFbL++RFRXnGAQPMb0XeX6Asc5B7QYBSyvhSn9flXNKhzoKd5khgrKA+B9gQJkLpNSZQk1vUI2/OhA6YolJ4PyuxQ8NQ3jOV4RTzDWAsX8Yy9cBHPWA4LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq2GBMlbDAmWshgXKWA0LlLEaFihjNSxQxmpYoIzVsEAZq/Hei6+pO/mHIbmJJuRiGGvImVGbPIFGoqd8XYbeY4EyVmFy4zbxxA2MxfDEDYzlsEAZq2GBMlbDAmWshgXKWE25TgE+IKLR4/YVKnQsxfFrvC117pFUauHb3sqASCz+afxGh2CD1dWVG+5YvPh+mgPfIxqdtZtQzhyKayPWtm8IL1y1at6Izn1/wNTZezquOlEYOc5IMX9Za/OLwaoShmvxPso51QjxGYjLwSP7caHUC5H6OYfQqkjk6MraWHyhFOJrSuq3sX7Kpo7q87398ijnC1qKOEXxsH9x1JjsHV7+CEHiDOXU48qInBTmZccIPEzlAVtQAAHeaYz+U6ZtwQIvXdd4gZD6kHSy5cRorOEXEO9+iH/R27gHsP+NUpqHUq0tN9fVza7TUj2UTib2mTKlaZcOnT1FarGLkfLeTLL58Uis8XCljZtKJZ6MRJp2V+HOo2k/Ok401niWzrXfkcnc+15t/RxYdPdwPBgprP8zVpvauobTpZFLjCNmpVvDc4WY59J+kbo4XdtxmbbEFyidB9cy1ijnFCPMXlrrB5e1LXiI8iP18TOrwxtvp1IgGo3/P+0olWmd/0SkrvHkkNSLXSk+3xHKXanXVLePGp07VSg9QRvZvCyZeAYP7DgZqjxVSLGHkXp+ZumC57yTDQtsQT0gwBnK6GeCpJBKL4FZ3GfixNOq8Pj+tw6Ji4NVPUL7a+nvb6Q6Dv//s6amcY9ON/soLMAmWIBFsGzNNVMb9lNSHIIi+Hhvx1D2fNz4H1O0rm7WARDShRDn+9G6xkvhLMyB4bjPGPlf0fp4A4kNx7kCNv67OFY6L07CEXoxtj0K23lWnCAh4UF51GgTghF6Uknnz5HJs6bMmHF2WBpx2eLFH/ZcEIj9K0Jr72O5eMiu0EJcjodi5VhdnR01Nns/snc1Wj2F6z5m/wOP3RnifEjCmBktn5Za3Uf7DSc7vEDJf1TCVKZSd70aZJHKJkF0Kysq1h2K1KplL7a84q/YmgkTmkbhhtZL43wTlvQBCHp2LmTOU2FxgZHmZljeP6aTzfOx6fOOK+q0FqtgTfeCdR2DH/8T2Bengr1WzhmwStdDqBOx3ykQ5s3alR/AyjHKyKVaiYMRX7++2j0pb3HztLUtWIwjNKIwvBRW8HrKU6HKc2Bt/5ZuS/wmk2xZCME/Klxnynvtq6fgPK1CzKXP89BHeg4RrnjmgPo59PnH0dmQOQ3H/5+szh0njFmVbmv+WaatuSXdmvhJVTb8Dey7COd6FQ/xYbj0J71jDCM7vEC1UjNw4wvWE6BEFqdCNvONom8tmW26PlVj3YOw/yIj1O9CUp0CMX5ixYsL3sR+R0olu1qYA1xlVkJ8q2AB90LR/2VYzxuQ/xbEsT/O9/lspbjOKPVJ5KXgfB2slMlWOuGZyWRzCsKfgfxbX3tm4UY62JakWxc8glLgMFjBpkgkXoNrOtIV7ubzSzkJPupKk6OKnnmWsmCV94HgJmQyieUws3T8u/xrx++ixSelUH+jeB4c89N4CNZoLWuNUQ+kkxUNwaphY4cXqELNHFbMEyhZNVjBa5F+A1ZjgTI5FPVyKt1wWk/FLMI0iueB9Z0BwT0GH+7Z1tb5rwfZdDeVzIoKinpFrxRvLluyIKNDehWyJqCYPUG47f+H478OcfwSVvv6lYsSa3HEShT1L2ZaE7+GJbtjyZJ53mce6TqF0U9QvCtk+ciK+ymzG65dV1aG34SYlJTSOz/83k9AWKNSqelP41rrILyXhGhy4AJcgdWLaEfyubEsHF9KPQp5YYp7rRzYHtFK+D+3wtf9A/zpf3Z1M4aLHV6guJFTtZCn1sYan+90c0/gTr1qch2fo1VtbQtXwOL9QIbEExDuItxQ3JTQWH9PH4gJPqV8OkgWkMr8Chb4Nux3P4rr801WnUj5sFCrsdckhPnwNztgKeFamEnjxuz+a1qvs7IFex+F/RYg3Betb/QqPjjGwV395DyO1h8bNSabjMbi/8b13YPrPwWiXg+f8Rr4in+gY0CUv9DSgd+LYl2KJ/E3fzMS6/wnrn0D1nnXjv0OQSj8HdqoW3DOn2L/h40K/QFihMEXv5dG34Hfah4qdA+STxpsPnzwaKb+0ORQpSNIbAmMVs/AIldMmtRE3wndgrldDIMX3/IYkipZ1MQVpEHXfbpD54nFGncNkgWoQjRxepw+CdkN+ltoHaJ03uDcWx+fjrulCOma6NoQ7fXvHjr4U4iM1XAzE2M53FA/jFDlBf4h+ZYfxu8L/889l2rb/tr+EambPVtKdSV82jNSS1sepaJcS/N73LbJKGQ34qBndu3WhM94PO7qB9OtLRcGWYIa/aXWjam2xJeDrBKBLeiwUj06+0s4UO+kk4lpRuhLUCf9WbCqX0Sjx8dQ4/42otW601lBea4wNxktqafqQGnEn5Ux3ToR6HxCyw8FSYDat9GXaqX+J8goKVigw4Q3gESaObn28dRTZIxjWrHc21vZD7yeI+XeYJQ6DzVtlcnMXxWLNRyIVZPSbc1/9DZSohU19W7HDEvVBsvqNYsR0VjuiyghW6krM8gqKVigw4RRzlHwnR5ZufKmdkorLfbBYnM76baRWqobXSN/Aus3zhjpNaznhDoWor8bUarZCu2KvY0w3Y4ZtMWOnjbt06Opxq2F+a6r9Hf9taUHC3SYgNWbhqJ4cZCErtRhWHRrx4S/eCfCA7X1DX8IsjyisYaLhZHPLWtr/htuEPXwePt5xxTyBYoTUprDjPTbMbsC9aY7O0dNkk7VV2A976UOgmBVycECHSaMlDtJJdYGSaTFCdKVfw2SHtkqcWZHOHvCqPAm8jMLoOLzBXIPorH406gMXQJlfqGurvFIHGWcNHINbeO3Y8rZISObvZ26ACG35aScDvGeI133p0F2ScK1+GEiUt/w38qIWcZV3xQhc5IU4sBUa6Iw2qi/wMKmpM4dQYNZauvil0F9ewhXXm6U+CYs6CZUlr4ebFogEmv8AW7sl2F+fptuTVweZJcgXIsfPrKdNxij/iUccwlZvbAKnxCsGQBzoXGxMD/SyrgdP4N1fEM4+ntK6f+kk9PP8zbbAqn0fRByIrtp/NVBVunCPUmMvbAFLRtqYg0fDqJlBfugg2Kuqql79hOOkjQETQhXvZVKNT9PURpcUZULfwJP//pU68EPYlt/YPAgmDilaa+KbC6CY/8ryOqRaH3DMahYfS3TmjgmyPL31Z1Tg6TQ2YrnM5l5bwXJLsxVdXXPz8xmQ88sXz5vXZBpCWxBB0U0uuhQaPMaY2QTBS29piBBr1RUZMNPwGmagfxvR2LPbZcPGHazNxtl7qNRRUHWVsycOTOkjbxUKNOtJQD7/gjX8NX8NTpOxy7Bqm5E6587Q0tzT6gyd3SQZRVsQQcB1dCxqMy0tvzKz/GprY8/it/zp5lky32xWHyia8Qj6baE975PHho8XOmEXuh0s59xhHwmmQwtq613Pyulbk8tTfwj2Myzirgnp+H2TDTKPT2zdOGSYFU3ovWN5whtJqfaEucEWR6RWPypXJU4yh8E3TM0QLtTZ5/EORJKmGwq2fLDYJUlsAUdFFLIGVKIXUlE+XGisVhjFIu9SZyU7pBUAe8OdX+iFn4TxHkrjnKIK8wD0XqK6w9BZLfQG5a0HVlFWL2f54TzPRiPNrgQ9d4BtoDGeuKBOA/V+27CIosLwU0IbZJH1U5u+GCQvRUQ53ewuAlCeBxWqsdzFBsW6GAw4u9Q3/tCy2NlqGopBLFLTpqPIf/RYAsRFm6tkSIdJD1cGToU2zgm13HCuNG7U/fjBG3EH9Ktie+jHHsKazyxr3p9/FnSiH8vb70zjfOkpOpZPKF2cQkelGu3nGSio2N9lRbqN3iSanCNv4rWxX8XrCoQicyZgGtp2vR++BrHmBQeiMnBKqtggQ6CdDJxG4r3y/xi1azL5dx63OwahGXBJoiKmbCS/wySHvixZ0C0N9B77+vXv0Giez6TTDzsrZRistupFtMIfFjNHxkpR3mvH2vxIRxsK4HSe1IQ59FhJ3xNkFWAjp9JNl9KwUj1TSPNkcGqAsrRP8fi3erRuR+6Up2NK64ly+2vtQcW6HYwqf74Wiz2cN1NL5APh1/Te0WDRAaL9EVXyj9RugAqU0oJr0aOWjdVrLy491qFEeFly5pXh8K5i7HdrVqY37nSzIP1pEkbtrJuMiQux//fX7JkXmeQ1SPKuEej+H4sSHrUxOL0oByshTiXzoGHCW6GeO3118cVRkHZAgt0gNTGGm5DZegGev/cMS5NrPBlsljGkc3CyNNrY/GrQxXZf6D2+aMVrfNfCnYLMDPanaw3MgkWkt6i9OJVbhhx6b2bboQ5yWQ7fkCzeFAIq9Bd2AT+7eZ3hmrrZ38ci12D9+27QcegChKu8zeR+ji9RHeUyYYvClZ74EBXGqO/lz8HBTwgS43q2dctJizQAbJpfcUZSqv/NVLfuGl9eApNakD56SWJRbmQOwN1o3lCu59Nt7Xc6O3QBS3lsS+/cLc32APivmrDaPcvFCfRwtJ9zVTqdTmlPkaCp3yC3tB0hEFFJ9+eOlcZo66kottPd4ceCqVzcVjeBTInvwV35MNbtn8qo7+en+Ynjxtyv9pZKXx3wya4q7O0iNTP+RAsJPmPOwD8VidjNdwOylgOC5SxGhYoYzUsUMZqWKCM1bBAGavJD7ejl/8LX61gGDsQo/8/mqTE1EXUZPQAAAAASUVORK5CYII=",
+ "bpp": 32
+ },
{
"name": "DCM220",
"image": "data:image/png;base64,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",
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);