5 Commits

Author SHA1 Message Date
47ad4c70de Merge branch 'can' into develop 2025-09-03 19:08:55 +02:00
c4aa3a1450 1.3 baseplane 2025-08-29 21:11:04 +02:00
f0d89417b6 Merge branch 'premerge' into develop 2025-08-29 21:09:20 +02:00
a8e17cda3b wip min 2025-08-29 17:14:15 +02:00
a38d704498 new case and can sensor board 2025-08-25 10:15:36 +02:00
42 changed files with 47550 additions and 2233 deletions

View File

View File

BIN
board/Body1.3mf Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"board": { "board": {
"active_layer": 4, "active_layer": 0,
"active_layer_preset": "All Layers", "active_layer_preset": "All Layers",
"auto_track_width": false, "auto_track_width": false,
"hidden_netclasses": [], "hidden_netclasses": [],

View File

@@ -472,6 +472,7 @@
"single_global_label": "ignore", "single_global_label": "ignore",
"unannotated": "error", "unannotated": "error",
"unconnected_wire_endpoint": "warning", "unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error", "unit_value_mismatch": "error",
"unresolved_variable": "error", "unresolved_variable": "error",
"wire_dangling": "error" "wire_dangling": "error"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-battery-charging" viewBox="0 0 16 16">
<path d="M9.585 2.568a.5.5 0 0 1 .226.58L8.677 6.832h1.99a.5.5 0 0 1 .364.843l-5.334 5.667a.5.5 0 0 1-.842-.49L5.99 9.167H4a.5.5 0 0 1-.364-.843l5.333-5.667a.5.5 0 0 1 .616-.09z"/>
<path d="M2 4h4.332l-.94 1H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h2.38l-.308 1H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"/>
<path d="M2 6h2.45L2.908 7.639A1.5 1.5 0 0 0 3.313 10H2zm8.595-2-.308 1H12a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9.276l-.942 1H12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"/>
<path d="M12 10h-1.783l1.542-1.639q.146-.156.241-.34zm0-3.354V6h-.646a1.5 1.5 0 0 1 .646.646M16 8a1.5 1.5 0 0 1-1.5 1.5v-3A1.5 1.5 0 0 1 16 8"/>
</svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@@ -182,40 +182,40 @@
) )
(pad "9" thru_hole rect (pad "9" thru_hole rect
(at 39.5 22.5) (at 39.5 22.5)
(size 0.85 0.85) (size 1.7 1.7)
(drill 0.5) (drill 1)
(layers "*.Cu" "*.Mask") (layers "*.Cu" "*.Mask")
(remove_unused_layers no) (remove_unused_layers no)
(uuid "f01565c2-eadd-4451-9dea-2ebe28d872f0") (uuid "f01565c2-eadd-4451-9dea-2ebe28d872f0")
) )
(pad "10" thru_hole rect (pad "10" thru_hole rect
(at -0.5 15) (at -0.5 15)
(size 0.85 0.85) (size 1.7 1.7)
(drill 0.5) (drill 1)
(layers "*.Cu" "*.Mask") (layers "*.Cu" "*.Mask")
(remove_unused_layers no) (remove_unused_layers no)
(uuid "56ec7c50-069f-4f46-9b83-ac18e4928930") (uuid "56ec7c50-069f-4f46-9b83-ac18e4928930")
) )
(pad "11" thru_hole rect (pad "11" thru_hole rect
(at -43 22.5) (at -43 22.5)
(size 0.85 0.85) (size 1.7 1.7)
(drill 0.5) (drill 1)
(layers "*.Cu" "*.Mask") (layers "*.Cu" "*.Mask")
(remove_unused_layers no) (remove_unused_layers no)
(uuid "f423be21-13b8-46de-8e19-e48325411a29") (uuid "f423be21-13b8-46de-8e19-e48325411a29")
) )
(pad "12" thru_hole rect (pad "12" thru_hole rect
(at -0.5 -12) (at -0.5 -12)
(size 0.85 0.85) (size 1.7 1.7)
(drill 0.5) (drill 1)
(layers "*.Cu" "*.Mask") (layers "*.Cu" "*.Mask")
(remove_unused_layers no) (remove_unused_layers no)
(uuid "536f0038-06c5-45e5-b1c8-898a364f6ec4") (uuid "536f0038-06c5-45e5-b1c8-898a364f6ec4")
) )
(pad "13" thru_hole rect (pad "13" thru_hole rect
(at 39.5 -16) (at 39.5 -16)
(size 0.85 0.85) (size 1.7 1.7)
(drill 0.5) (drill 1)
(layers "*.Cu" "*.Mask") (layers "*.Cu" "*.Mask")
(remove_unused_layers no) (remove_unused_layers no)
(uuid "3e0cdbaa-8219-48c4-bb0a-fd4f0e78a390") (uuid "3e0cdbaa-8219-48c4-bb0a-fd4f0e78a390")

View File

@@ -0,0 +1,208 @@
(footprint "Sensor"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "REF**"
(at 0 -0.5 0)
(unlocked yes)
(layer "F.SilkS")
(uuid "f71e9c26-7923-4e5c-828e-153927862740")
(effects
(font
(size 1 1)
(thickness 0.1)
)
)
)
(property "Value" "Sensor"
(at 0 1 0)
(unlocked yes)
(layer "F.Fab")
(uuid "d40c7203-0c06-49e1-8672-dbd216694fc8")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "6720cb18-0687-4d55-a6ad-3ccf0819eac2")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "43905e6e-773d-4d5e-8d72-57c092c3495a")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(attr smd)
(fp_line
(start -45 -18)
(end 41 -18)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "e63ec799-95c4-407e-acc9-9c7e6d3e330c")
)
(fp_line
(start -45 24)
(end -45 -18)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "a66c286b-432b-493d-9619-2dd4fbfdb21c")
)
(fp_line
(start -44 24)
(end -45 24)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "71526e68-71d4-4b13-ab74-482657a06849")
)
(fp_line
(start 41 -18)
(end 41 24)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "55554342-bbff-4d1b-b931-67fb7eaa5895")
)
(fp_line
(start 41 24)
(end -44 24)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "2dc0ee59-36f6-407e-821a-c6acfe3ea887")
)
(fp_text user "${REFERENCE}"
(at 0 2.5 0)
(unlocked yes)
(layer "F.Fab")
(uuid "befc0725-b201-4f81-b0dc-b327becad9ba")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole rect
(at -42.4 -15.3)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "5538ac99-6e48-4111-a3df-8ff33be72ff3")
)
(pad "2" thru_hole circle
(at -42.4 -12.76)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "0c1d4a6c-7871-46ae-a262-a8223571975e")
)
(pad "3" thru_hole circle
(at -42.4 -10.22)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "70df4148-0415-45a2-8365-0977fcda45a1")
)
(pad "4" thru_hole circle
(at -42.4 -7.68)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "179dd332-2ab2-4917-91ec-35f232b45ce3")
)
(pad "5" thru_hole circle
(at -42.4 -5.14)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "17bc8f3a-4727-4e31-90f4-8252dff5ad1c")
)
(pad "6" thru_hole circle
(at -42.4 -2.6)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "eee06414-e977-45a8-a5e0-618644284d45")
)
(pad "7" thru_hole rect
(at -0.5 -12)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "536f0038-06c5-45e5-b1c8-898a364f6ec4")
)
(pad "8" thru_hole rect
(at 39.5 -16)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "3e0cdbaa-8219-48c4-bb0a-fd4f0e78a390")
)
(pad "9" thru_hole rect
(at 39.5 22.5)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "f01565c2-eadd-4451-9dea-2ebe28d872f0")
)
(pad "10" thru_hole rect
(at -0.5 15)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "56ec7c50-069f-4f46-9b83-ac18e4928930")
)
(pad "11" thru_hole rect
(at -43 22.5)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "f423be21-13b8-46de-8e19-e48325411a29")
)
(embedded_fonts no)
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
{
"board": {
"active_layer": 6,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"locked_item_shadows",
"conflict_shadows",
"shapes"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 1
},
"git": {
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "Sensors.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11
],
"col_widths": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"symbols": true,
"text": true,
"wires": true
}
}
}

View File

@@ -0,0 +1,674 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "ignore",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "warning",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.15,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.25,
"min_track_width": 0.0,
"min_via_annular_width": 0.05,
"min_via_diameter": 0.25,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.1,
0.2,
0.5
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
},
{
"diameter": 0.3,
"drill": 0.25
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "ignore",
"power_pin_not_driven": "ignore",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "Sensors.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.15,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "Sensors.step",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": false,
"label": "LCSC_PART_NUMBER",
"name": "LCSC_PART_NUMBER",
"show": true
},
{
"group_by": false,
"label": "Sim.Device",
"name": "Sim.Device",
"show": false
},
{
"group_by": false,
"label": "Sim.Pins",
"name": "Sim.Pins",
"show": false
},
{
"group_by": false,
"label": "Sim.Type",
"name": "Sim.Type",
"show": false
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"46346c04-8bed-48b4-837b-9342dd403232",
"Root"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
(sym_lib_table
(version 7)
(lib (name "Modules")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/board/modules/Modules.kicad_sym")(options "")(descr ""))
)

View File

@@ -0,0 +1,14 @@
[build]
target = "riscv32imc-unknown-none-elf"
[target."riscv32imc-unknown-none-elf"]
rustflags = [
# "-C", "link-arg=-Tlink.x",
]
# runner = "riscv64-unknown-elf-gdb -q -x openocd.gdb"
# runner = "riscv-none-embed-gdb -q -x openocd.gdb"
# runner = "gdb -q -x openocd.gdb"
# runner = "wlink -v flash"
runner = "wlink -v flash --enable-sdi-print --watch-serial --erase"
# runner = "wlink -v flash"

View File

@@ -0,0 +1,7 @@
target extended-remote :3333
set remotetimeout 2000
#symbol-file target/riscv32imc-unknown-none-elf/release/ch32v203-examples
file target/riscv32imc-unknown-none-elf/release/bms
monitor reset halt

View File

@@ -0,0 +1 @@
target

View File

@@ -0,0 +1,43 @@
[package]
name = "bms"
version = "0.1.0"
edition = "2021"
[dependencies]
ch32-hal = { git = "https://github.com/ch32-rs/ch32-hal", features = [
"ch32v203c8t6",
"memory-x",
"embassy",
"rt",
"time-driver-tim2",
], default-features = false }
embassy-executor = { version = "0.7.0", features = [
"arch-riscv32",
"executor-thread",
] }
embassy-time = { version = "0.4.0" }
embassy-usb = { version = "0.3.0" }
embassy-futures = { version = "0.1.0" }
# This is okay because we should automatically use whatever ch32-hal uses
qingke-rt = "*"
qingke = "*"
panic-halt = "1.0"
embedded-hal = "1.0.0"
heapless = "0.8.0"
micromath = { version = "2.1.0", features = ["num-traits"] }
embedded-can = "0.4.1"
[profile.dev]
#lto = true
opt-level = 1
[profile.release]
strip = false # symbols are not flashed to the microcontroller, so don't strip them.
#lto = true
debug = true
opt-level = "z" # Optimize for size.

View File

@@ -0,0 +1,72 @@
# ch32v203-bms
A simple battery management controller software.
## CAN bus and hardware address
This firmware exposes a CAN interface on the CH32V203 and uses 4 hardware address pins to allow up to 16 sensors on the same bus.
- CAN pins (default mapping):
- CAN RX: PA11
- CAN TX: PA12
- Address select pins (with internal pull-ups):
- A0: PA0
- A1: PA1
- A2: PA2
- A3: PA3
Wire each address pin to GND to set its corresponding bit to 1. The 4-bit address range is 0..15. The nodes CAN Standard ID is `0x100 | addr`, i.e. 0x100..0x10F. The CAN acceptance filter is configured to only accept frames with the nodes own ID.
Adjust the pins above if your PCB routes CAN or address lines to different pads.
## 555 timer (software) emulation mode
To save the BOM cost of a classic NE555 in simple oscillator applications, this firmware implements a minimal 555-like Schmitt trigger using the MCUs ADC and a GPIO, approximating the behavior when the capacitor is charged/discharged via Q through a resistor, and the combined Trigger/Threshold senses the capacitor node.
- Pins used:
- Q output: PB2
- Combined Trigger/Threshold (ADC input): PA0
- Wiring:
- PB2 (Q) -> series resistor R -> capacitor node
- Capacitor node -> capacitor to GND
- Capacitor node -> PA0 (ADC input)
- Behavior:
- When ADC(PA0) <= ~1/3 Vref, PB2 is driven High.
- When ADC(PA0) >= ~2/3 Vref, PB2 is driven Low.
- Hysteresis avoids chatter; the actual charge/discharge dynamics follow your chosen R and C.
- Notes:
- Use an appropriate resistor from PB2 to the capacitor to set oscillation frequency. Start with 10k..100k and adjust with C.
- Ensure PA0 is routed to the capacitor node and left high impedance (no strong pull-ups/downs) so the ADC can sense the analog voltage.
- PB2 drives the on-board LED (if present), so the LED might blink at the oscillation frequency.
This mode is implemented in `src/main.rs` using `hal::adc::Adc::convert(&mut pin, SampleTime::...)` to take periodic samples and a simple state machine to toggle the Q output based on ~1/3 and ~2/3 Vref thresholds.
## Building
``` sh
cargo build --release
```
## Flash
``` sh
wchisp config reset
wchip wchisp flash target/riscv32imc-unknown-none-elf/release/bms
```
## Debugging
For debugging purposes a container file is provided together with wrapper scripts to start the containerized `openocd` and `riscv-gdb` transparently. The wrapper scripts assume that `podman` is setup.
Starting Debug server
```
./bin/openocd
```
Connecting with gdb for interactive debugging
```
./bin/gdb -f target/riscv32imc-unknown-none-elf/release/bms
```

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_NAME="localhost/wch-dev-tools:latest"
CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")"
pushd "$CONTAINER_TOOLS_BASEDIR"
podman build -t "$CONTAINER_NAME" -f "../wch-tools.Containerfile" .
popd

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_IMAGE="localhost/wch-dev-tools:latest"
CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")"
function _fatal {
echo -e "\e[31mERROR\e[0m $(</dev/stdin)$*" 1>&2
exit 1
}
declare -a PODMAN_ARGS=(
"--rm" "-i" "--log-driver=none"
"--network=host"
"--pid=host"
"-v" "$PWD:$PWD:rw"
"-w" "$PWD"
)
[[ -t 1 ]] && PODMAN_ARGS+=("-t")
if ! podman image exists "$CONTAINER_IMAGE"; then
#attempt to build container
"$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 ||
_fatal "faild to build local image, cannot continue! … please ensure you have an internet connection"
fi
podman run "${PODMAN_ARGS[@]}" --entrypoint riscv-none-elf-gdb-py3 "$CONTAINER_IMAGE" "$@"

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_IMAGE="localhost/wch-dev-tools:latest"
CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")"
function _fatal {
echo -e "\e[31mERROR\e[0m $(</dev/stdin)$*" 1>&2
exit 1
}
declare -a PODMAN_ARGS=(
"--rm" "-i" "--log-driver=none"
"--network=host"
"-v" "$PWD:$PWD:rw"
"-w" "$PWD"
)
for device in /dev/bus/usb/*/*; do
if udevadm info "$device" | grep -q "ID_VENDOR=wch.cn" && \
udevadm info "$device" | grep -q "ID_MODEL=WCH-Link"; then
DEBUGGER_DEV_PATH="$device"
break
fi
done
if [[ -z "${DEBUGGER_DEV_PATH:-}" ]]; then
echo "Could not find hardware debugger … Exiting!" 1>&2
exit 1
else
# add jlink to podman device
PODMAN_ARGS+=("--device=$DEBUGGER_DEV_PATH")
fi
[[ -t 1 ]] && PODMAN_ARGS+=("-t")
if ! podman image exists "$CONTAINER_IMAGE"; then
#attempt to build container
"$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 ||
_fatal "faild to build local image, cannot continue! … please ensure you have an internet connection"
fi
podman run "${PODMAN_ARGS[@]}" --entrypoint openocd "$CONTAINER_IMAGE" "$@"

View File

@@ -0,0 +1,11 @@
fn main() {
// println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
// println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_dir = std::path::PathBuf::from(out_dir);
std::fs::write(out_dir.join("memory.x"), include_bytes!("memory.x")).unwrap();
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rerun-if-changed=memory.x");
}

View File

@@ -0,0 +1,125 @@
/* CH32V203c8t6 */
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 64K /* BANK_1 */
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
REGION_ALIAS("REGION_TEXT", FLASH);
REGION_ALIAS("REGION_RODATA", FLASH);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);
/* fault handlers */
PROVIDE(InstructionMisaligned = ExceptionHandler);
PROVIDE(InstructionFault = ExceptionHandler);
PROVIDE(IllegalInstruction = ExceptionHandler);
PROVIDE(Breakpoint = ExceptionHandler);
PROVIDE(LoadMisaligned = ExceptionHandler);
PROVIDE(LoadFault = ExceptionHandler);
PROVIDE(StoreMisaligned = ExceptionHandler);
PROVIDE(StoreFault = ExceptionHandler);;
PROVIDE(UserEnvCall = ExceptionHandler);
PROVIDE(SupervisorEnvCall = ExceptionHandler);
PROVIDE(MachineEnvCall = ExceptionHandler);
PROVIDE(InstructionPageFault = ExceptionHandler);
PROVIDE(LoadPageFault = ExceptionHandler);
PROVIDE(StorePageFault = ExceptionHandler);
/* core interrupt handlers */
PROVIDE(NonMaskableInt = DefaultHandler);
PROVIDE(Software = DefaultHandler);
/* external interrupt handlers */
PROVIDE(WWDG = DefaultHandler);
PROVIDE(PVD = DefaultHandler);
PROVIDE(TAMPER = DefaultHandler);
PROVIDE(RTC = DefaultHandler);
PROVIDE(FLASH = DefaultHandler);
PROVIDE(RCC = DefaultHandler);
PROVIDE(EXTI0 = DefaultHandler);
PROVIDE(EXTI1 = DefaultHandler);
PROVIDE(EXTI2 = DefaultHandler);
PROVIDE(EXTI3 = DefaultHandler);
PROVIDE(EXTI4 = DefaultHandler);
PROVIDE(DMA1_CHANNEL1 = DefaultHandler);
PROVIDE(DMA1_CHANNEL2 = DefaultHandler);
PROVIDE(DMA1_CHANNEL3 = DefaultHandler);
PROVIDE(DMA1_CHANNEL4 = DefaultHandler);
PROVIDE(DMA1_CHANNEL5 = DefaultHandler);
PROVIDE(DMA1_CHANNEL6 = DefaultHandler);
PROVIDE(DMA1_CHANNEL7 = DefaultHandler);
PROVIDE(ADC = DefaultHandler);
PROVIDE(USB_HP_CAN1_TX = DefaultHandler);
/*PROVIDE(USB_LP_CAN1_RX0 = DefaultHandler);*/
PROVIDE(CAN1_RX1 = DefaultHandler);
PROVIDE(CAN1_SCE = DefaultHandler);
PROVIDE(EXTI9_5 = DefaultHandler);
PROVIDE(TIM1_BRK = DefaultHandler);
PROVIDE(TIM1_UP_ = DefaultHandler);
PROVIDE(TIM1_TRG_COM = DefaultHandler);
PROVIDE(TIM1_CC = DefaultHandler);
PROVIDE(TIM2 = DefaultHandler);
PROVIDE(TIM3 = DefaultHandler);
PROVIDE(TIM4 = DefaultHandler);
PROVIDE(I2C1_EV = DefaultHandler);
PROVIDE(I2C1_ER = DefaultHandler);
PROVIDE(I2C2_EV = DefaultHandler);
PROVIDE(I2C2_ER = DefaultHandler);
PROVIDE(SPI1 = DefaultHandler);
PROVIDE(SPI2 = DefaultHandler);
PROVIDE(USART1 = DefaultHandler);
PROVIDE(USART2 = DefaultHandler);
PROVIDE(USART3 = DefaultHandler);
PROVIDE(EXTI15_10 = DefaultHandler);
PROVIDE(RTCALARM = DefaultHandler);
PROVIDE(USBWAKE_UP = DefaultHandler);
PROVIDE(TIM8_BRK = DefaultHandler);
PROVIDE(TIM8_UP_ = DefaultHandler);
PROVIDE(TIM8_TRG_COM = DefaultHandler);
PROVIDE(TIM8_CC = DefaultHandler);
PROVIDE(RNG = DefaultHandler);
PROVIDE(FSMC = DefaultHandler);
PROVIDE(SDIO = DefaultHandler);
PROVIDE(TIM5 = DefaultHandler);
PROVIDE(SPI3 = DefaultHandler);
PROVIDE(UART4 = DefaultHandler);
PROVIDE(UART5 = DefaultHandler);
PROVIDE(TIM6 = DefaultHandler);
PROVIDE(TIM7 = DefaultHandler);
PROVIDE(DMA2_CHANNEL1 = DefaultHandler);
PROVIDE(DMA2_CHANNEL2 = DefaultHandler);
PROVIDE(DMA2_CHANNEL3 = DefaultHandler);
PROVIDE(DMA2_CHANNEL4 = DefaultHandler);
PROVIDE(DMA2_CHANNEL5 = DefaultHandler);
PROVIDE(ETH = DefaultHandler);
PROVIDE(ETH_WKUP = DefaultHandler);
PROVIDE(CAN2_TX = DefaultHandler);
PROVIDE(CAN2_RX0 = DefaultHandler);
PROVIDE(CAN2_RX1 = DefaultHandler);
PROVIDE(CAN2_SCE = DefaultHandler);
PROVIDE(OTG_FS = DefaultHandler);
PROVIDE(USBHSWAKEUP = DefaultHandler);
PROVIDE(USBHS = DefaultHandler);
PROVIDE(DVP = DefaultHandler);
PROVIDE(UART6 = DefaultHandler);
PROVIDE(UART7 = DefaultHandler);
PROVIDE(UART8 = DefaultHandler);
PROVIDE(TIM9_BRK = DefaultHandler);
PROVIDE(TIM9_UP_ = DefaultHandler);
PROVIDE(TIM9_TRG_COM = DefaultHandler);
PROVIDE(TIM9_CC = DefaultHandler);
PROVIDE(TIM10_BRK = DefaultHandler);
PROVIDE(TIM10_UP_ = DefaultHandler);
PROVIDE(TIM10_TRG_COM = DefaultHandler);
PROVIDE(TIM10_CC = DefaultHandler);
PROVIDE(DMA2_CHANNEL6 = DefaultHandler);
PROVIDE(DMA2_CHANNEL7 = DefaultHandler);
PROVIDE(DMA2_CHANNEL8 = DefaultHandler);
PROVIDE(DMA2_CHANNEL9 = DefaultHandler);
PROVIDE(DMA2_CHANNEL10 = DefaultHandler);
PROVIDE(DMA2_CHANNEL11 = DefaultHandler);

View File

@@ -0,0 +1,17 @@
set _CHIPNAME ch32v203
set _TARGETNAME $_CHIPNAME.cpu
#bindto 0.0.0.0
adapter driver wlinke
adapter speed 6000
transport select sdi
sdi newtap $_CHIPNAME cpu -irlen 5 --expected-id 0x00001
target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME
$_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1
set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME wch_rsicv 0x00000000 0 0 0 $_TARGETNAME.0
init

View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View File

@@ -0,0 +1,61 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)]
// Simple 555-like oscillator implemented in firmware.
// - Q output: PB2 (also drives the on-board LED if present)
// - Combined Trigger/Threshold analog input: PA0 (capacitor node)
// Wiring suggestion:
// Q (PB2) --[R]--+-- C -- GND
// |
// PA0 (ADC input)
// The firmware toggles Q high when PA0 <= 1/3 Vref and low when PA0 >= 2/3 Vref.
use embassy_executor::Spawner;
use embassy_time::Timer;
use hal::gpio::{Level, Output};
use {ch32_hal as hal, panic_halt as _};
use hal::adc::{Adc, SampleTime};
#[embassy_executor::main(entry = "qingke_rt::entry")]
async fn main(_spawner: Spawner) -> ! {
let p = hal::init(Default::default());
// Q output on PB2
let mut q = Output::new(p.PB2, Level::Low, Default::default());
// ADC on PA0 for combined Trigger/Threshold input
let mut adc = Adc::new(p.ADC1, Default::default());
let mut trig_thres = p.PA0; // analog-capable pin used as ADC channel
// ADC characteristics: assume 12-bit if HAL doesn't expose it.
// If the HAL provides a method to query resolution, prefer that.
let full_scale: u16 = 4095; // 12-bit default
let thr_low: u16 = (full_scale as u32 / 3) as u16; // ~1/3 Vref
let thr_high: u16 = ((full_scale as u32 * 2) / 3) as u16; // ~2/3 Vref
// Start with Q low. State variable to avoid redundant toggles.
let mut q_high = false;
q.set_low();
loop {
// Read capacitor node voltage via ADC
let sample: u16 = adc.convert(&mut trig_thres, SampleTime::CYCLES239_5);
// Implement Schmitt trigger behavior like NE555 using thresholds
if !q_high && sample <= thr_low {
// Trigger: voltage fell below 1/3 Vref -> set output high
q.set_high();
q_high = true;
} else if q_high && sample >= thr_high {
// Threshold: voltage rose above 2/3 Vref -> set output low
q.set_low();
q_high = false;
}
// Small delay to reduce CPU usage; adjust for responsiveness/noise
Timer::after_micros(200).await;
}
}

View File

@@ -0,0 +1,27 @@
FROM debian:bookworm
RUN apt update -y && apt upgrade -y && apt install git libjaylink-dev libusb-1.0-0 unzip curl libhidapi-hidraw0 xz-utils -y
RUN cd /root && \
curl -L -o mrs-toolchain.tar.xz "https://github.com/ch32-riscv-ug/MounRiver_Studio_Community_miror/releases/download/1.92-toolchain/MRS_Toolchain_Linux_x64_V1.92.tar.xz" && \
mkdir mrs-toolchain && \
tar -xvf mrs-toolchain.tar.xz -C mrs-toolchain --strip-components=1 && \
mv mrs-toolchain/OpenOCD/bin/openocd /usr/local/bin && \
mv mrs-toolchain/OpenOCD/share/openocd /usr/local/share && \
# mv mrs-toolchain/RISC-V_Embedded_GCC12/bin/riscv-none-elf-gdb /usr/local/bin && \ # both toolchains in MRS are to old to work with emacs dape
# mv mrs-toolchain/RISC-V_Embedded_GCC12/libexec /usr/local && \ # both toolchains in MRS are to old to work with emacs dape
rm -rf mrs-toolchain mrs-toolchain.tar.xz && \
# Use up to date xpack toolchains for gdb
curl -L -o xpack-riscv-toolchain.tar.gz "https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v14.2.0-3/xpack-riscv-none-elf-gcc-14.2.0-3-linux-x64.tar.gz" && \
mkdir xpack-toolchain && \
tar -xvf xpack-riscv-toolchain.tar.gz -C xpack-toolchain --strip-components=1 && \
mv xpack-toolchain/bin/* /usr/local/bin && \
mv xpack-toolchain/lib/ /usr/local && \
mv xpack-toolchain/lib64/ /usr/local && \
mv xpack-toolchain/libexec /usr/local && \
mv xpack-toolchain/riscv-none-elf /usr/local && \
rm -rf xpack-toolchain xpack-riscv-toolchain.tar.gz
RUN mkdir -p /root/.config/gdb && echo "set auto-load safe-path /" >> /root/.config/gdb/gdbinit
ENTRYPOINT [ "/usr/bin/bash" ]

View File

@@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "Sensor")(type "KiCad")(uri "${KIPRJMOD}/Sensor.pretty")(options "")(descr ""))
)

BIN
case/PlantCtrl Case v9.f3z Normal file

Binary file not shown.

View File

@@ -58,7 +58,7 @@ pub struct TankConfig {
pub tank_warn_percent: u8, pub tank_warn_percent: u8,
pub tank_empty_percent: u8, pub tank_empty_percent: u8,
pub tank_full_percent: u8, pub tank_full_percent: u8,
pub ml_per_pulse: f32 pub ml_per_pulse: f32,
} }
impl Default for TankConfig { impl Default for TankConfig {
fn default() -> Self { fn default() -> Self {
@@ -111,6 +111,7 @@ pub struct PlantControllerConfig {
pub struct PlantConfig { pub struct PlantConfig {
pub mode: PlantWateringMode, pub mode: PlantWateringMode,
pub target_moisture: f32, pub target_moisture: f32,
pub min_moisture: f32,
pub pump_time_s: u16, pub pump_time_s: u16,
pub pump_limit_ml: u16, pub pump_limit_ml: u16,
pub pump_cooldown_min: u16, pub pump_cooldown_min: u16,
@@ -131,6 +132,7 @@ impl Default for PlantConfig {
Self { Self {
mode: PlantWateringMode::OFF, mode: PlantWateringMode::OFF,
target_moisture: 40., target_moisture: 40.,
min_moisture: 30.,
pump_time_s: 30, pump_time_s: 30,
pump_limit_ml: 5000, pump_limit_ml: 5000,
pump_cooldown_min: 60, pump_cooldown_min: 60,

View File

@@ -23,6 +23,7 @@ use bq34z100::Bq34z100g1Driver;
use ds323x::{DateTimeAccess, Ds323x}; use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
use embedded_hal_bus::i2c::MutexDevice; use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::pcnt::PCNT1;
use esp_idf_hal::{ use esp_idf_hal::{
adc::ADC1, adc::ADC1,
delay::Delay, delay::Delay,
@@ -54,7 +55,6 @@ use esp_idf_hal::pcnt::PCNT1;
//Only support for 8 right now! //Only support for 8 right now!
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
const TANK_MULTI_SAMPLE: usize = 11; const TANK_MULTI_SAMPLE: usize = 11;
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c); pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c);

View File

@@ -136,7 +136,7 @@ pub(crate) fn create_v3(
peripherals.gpio5, peripherals.gpio5,
tank_power_pin, tank_power_pin,
flow_sensor_pin, flow_sensor_pin,
peripherals.pcnt1 peripherals.pcnt1,
)?; )?;
let mut signal_counter = PcntDriver::new( let mut signal_counter = PcntDriver::new(

View File

@@ -159,7 +159,7 @@ pub(crate) fn create_v4(
peripherals.gpio5, peripherals.gpio5,
tank_power_pin, tank_power_pin,
flow_sensor_pin, flow_sensor_pin,
peripherals.pcnt1 peripherals.pcnt1,
)?; )?;
let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34);

View File

@@ -6,7 +6,9 @@ use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; use esp_idf_hal::adc::{attenuation, Resolution, ADC1};
use esp_idf_hal::delay::Delay; use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull}; use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull};
use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1}; use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1,
};
use esp_idf_sys::EspError; use esp_idf_sys::EspError;
use one_wire_bus::OneWire; use one_wire_bus::OneWire;
@@ -25,7 +27,7 @@ impl<'a> TankSensor<'a> {
gpio5: Gpio5, gpio5: Gpio5,
tank_power_pin: AnyIOPin, tank_power_pin: AnyIOPin,
flow_sensor_pin: AnyIOPin, flow_sensor_pin: AnyIOPin,
pcnt1: PCNT1 pcnt1: PCNT1,
) -> anyhow::Result<TankSensor<'a>> { ) -> anyhow::Result<TankSensor<'a>> {
let mut one_wire_pin = let mut one_wire_pin =
PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin"); PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin");
@@ -73,7 +75,6 @@ impl<'a> TankSensor<'a> {
}, },
)?; )?;
Ok(TankSensor { Ok(TankSensor {
one_wire_bus, one_wire_bus,
tank_channel, tank_channel,

View File

@@ -591,16 +591,31 @@ pub fn do_secure_pump(
let mut first_error = true; let mut first_error = true;
let mut pump_time_s = 0; let mut pump_time_s = 0;
if !dry_run { if !dry_run {
board.board_hal.get_tank_sensor().unwrap().reset_flow_meter(); board
board.board_hal.get_tank_sensor().unwrap().start_flow_meter(); .board_hal
.get_tank_sensor()
.unwrap()
.reset_flow_meter();
board
.board_hal
.get_tank_sensor()
.unwrap()
.start_flow_meter();
board.board_hal.pump(plant_id, true)?; board.board_hal.pump(plant_id, true)?;
Delay::new_default().delay_ms(10); Delay::new_default().delay_ms(10);
for step in 0..plant_config.pump_time_s as usize { for step in 0..plant_config.pump_time_s as usize {
let flow_value = board.board_hal.get_tank_sensor().unwrap().get_flow_meter_value(); let flow_value = board
.board_hal
.get_tank_sensor()
.unwrap()
.get_flow_meter_value();
flow_collector[step] = flow_value; flow_collector[step] = flow_value;
let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
println!("Flow value is {} ml, limit is {} ml raw sensor {}", flow_value_ml, plant_config.pump_limit_ml, flow_value); println!(
"Flow value is {} ml, limit is {} ml raw sensor {}",
flow_value_ml, plant_config.pump_limit_ml, flow_value
);
if flow_value_ml > plant_config.pump_limit_ml as f32 { if flow_value_ml > plant_config.pump_limit_ml as f32 {
println!("Flow value is reached, stopping"); println!("Flow value is reached, stopping");
break; break;
@@ -672,9 +687,16 @@ pub fn do_secure_pump(
} }
} }
board.board_hal.get_tank_sensor().unwrap().stop_flow_meter(); board.board_hal.get_tank_sensor().unwrap().stop_flow_meter();
let final_flow_value = board.board_hal.get_tank_sensor().unwrap().get_flow_meter_value(); let final_flow_value = board
.board_hal
.get_tank_sensor()
.unwrap()
.get_flow_meter_value();
let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
println!("Final flow value is {} with {} ml", final_flow_value, flow_value_ml); println!(
"Final flow value is {} with {} ml",
final_flow_value, flow_value_ml
);
current_collector.sort(); current_collector.sort();
Ok(PumpResult { Ok(PumpResult {
median_current_ma: current_collector[current_collector.len() / 2], median_current_ma: current_collector[current_collector.len() / 2],

View File

@@ -78,6 +78,7 @@ impl PumpState {
pub enum PlantWateringMode { pub enum PlantWateringMode {
OFF, OFF,
TargetMoisture, TargetMoisture,
MinMoisture,
TimerOnly, TimerOnly,
} }
@@ -235,6 +236,30 @@ impl PlantState {
false false
} }
} }
PlantWateringMode::MinMoisture => {
let (moisture_percent, _) = self.plant_moisture();
if let Some(moisture_percent) = moisture_percent {
if self.pump_in_timeout(plant_conf, current_time) {
false
} else if !in_time_range(
current_time,
plant_conf.pump_hour_start,
plant_conf.pump_hour_end,
) {
false
} else if (true) {
//if not cooldown min and below max
true
} else if (true) {
//if below min disable cooldown min
true
} else {
false
}
} else {
false
}
}
PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time), PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time),
} }
} }
@@ -268,7 +293,9 @@ impl PlantState {
.map(|t| t.with_timezone(&current_time.timezone())), .map(|t| t.with_timezone(&current_time.timezone())),
next_pump: if matches!( next_pump: if matches!(
plant_conf.mode, plant_conf.mode,
PlantWateringMode::TimerOnly | PlantWateringMode::TargetMoisture PlantWateringMode::TimerOnly
| PlantWateringMode::TargetMoisture
| PlantWateringMode::MinMoisture
) { ) {
self.pump.previous_pump.and_then(|last_pump| { self.pump.previous_pump.and_then(|last_pump| {
last_pump last_pump

View File

@@ -105,6 +105,7 @@ export interface PlantControllerConfig {
export interface PlantConfig { export interface PlantConfig {
mode: string, mode: string,
target_moisture: number, target_moisture: number,
min_moisture: number,
pump_time_s: number, pump_time_s: number,
pump_cooldown_min: number, pump_cooldown_min: number,
pump_hour_start: number, pump_hour_start: number,

View File

@@ -55,6 +55,7 @@
<select class="plantvalue" id="plant_${plantId}_mode"> <select class="plantvalue" id="plant_${plantId}_mode">
<option value="OFF">Off</option> <option value="OFF">Off</option>
<option value="TargetMoisture">Target</option> <option value="TargetMoisture">Target</option>
<option value="MinMoisture">Min Moisture</option>
<option value="TimerOnly">Timer</option> <option value="TimerOnly">Timer</option>
</select> </select>
@@ -63,6 +64,10 @@
<div class="plantkey">Target Moisture:</div> <div class="plantkey">Target Moisture:</div>
<input class="plantvalue" id="plant_${plantId}_target_moisture" type="number" min="0" max="100" placeholder="0"> <input class="plantvalue" id="plant_${plantId}_target_moisture" type="number" min="0" max="100" placeholder="0">
</div> </div>
<div class="flexcontainer plantMinEnabledOnly_${plantId}">
<div class="plantkey">Minimum Moisture:</div>
<input class="plantvalue" id="plant_${plantId}_min_moisture" type="number" min="0" max="100" placeholder="0">
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}"> <div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Pump Time (s):</div> <div class="plantkey">Pump Time (s):</div>
<input class="plantvalue" id="plant_${plantId}_pump_time_s" type="number" min="0" max="600" placeholder="30"> <input class="plantvalue" id="plant_${plantId}_pump_time_s" type="number" min="0" max="600" placeholder="30">

View File

@@ -59,6 +59,7 @@ export class PlantView {
private readonly header: HTMLElement; private readonly header: HTMLElement;
private readonly testButton: HTMLButtonElement; private readonly testButton: HTMLButtonElement;
private readonly targetMoisture: HTMLInputElement; private readonly targetMoisture: HTMLInputElement;
private readonly minMoisture: HTMLInputElement;
private readonly pumpTimeS: HTMLInputElement; private readonly pumpTimeS: HTMLInputElement;
private readonly pumpCooldown: HTMLInputElement; private readonly pumpCooldown: HTMLInputElement;
private readonly pumpHourStart: HTMLSelectElement; private readonly pumpHourStart: HTMLSelectElement;
@@ -118,6 +119,11 @@ export class PlantView {
controller.configChanged() controller.configChanged()
} }
this.minMoisture = document.getElementById("plant_" + plantId + "_min_moisture")! as HTMLInputElement;
this.minMoisture.onchange = function () {
controller.configChanged()
}
this.pumpTimeS = document.getElementById("plant_" + plantId + "_pump_time_s") as HTMLInputElement; this.pumpTimeS = document.getElementById("plant_" + plantId + "_pump_time_s") as HTMLInputElement;
this.pumpTimeS.onchange = function () { this.pumpTimeS.onchange = function () {
controller.configChanged() controller.configChanged()
@@ -203,13 +209,15 @@ export class PlantView {
let sensorOnly = document.getElementsByClassName("plantSensorEnabledOnly_"+ this.plantId) let sensorOnly = document.getElementsByClassName("plantSensorEnabledOnly_"+ this.plantId)
let pumpOnly = document.getElementsByClassName("plantPumpEnabledOnly_"+ this.plantId) let pumpOnly = document.getElementsByClassName("plantPumpEnabledOnly_"+ this.plantId)
let targetOnly = document.getElementsByClassName("plantTargetEnabledOnly_"+ this.plantId) let targetOnly = document.getElementsByClassName("plantTargetEnabledOnly_"+ this.plantId)
let minOnly = document.getElementsByClassName("plantMinEnabledOnly_"+ this.plantId)
console.log("updateVisibility plantConfig: " + plantConfig.mode) console.log("updateVisibility plantConfig: " + plantConfig.mode)
let showSensor = plantConfig.sensor_a || plantConfig.sensor_b let showSensor = plantConfig.sensor_a || plantConfig.sensor_b
let showPump = plantConfig.mode !== "OFF" let showPump = plantConfig.mode !== "OFF"
let showTarget = plantConfig.mode === "TargetMoisture" let showTarget = plantConfig.mode === "TargetMoisture"
let showMin = plantConfig.mode === "MinMoisture"
console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget) console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget + " min " + showMin)
for (const element of Array.from(sensorOnly)) { for (const element of Array.from(sensorOnly)) {
if (showSensor) { if (showSensor) {
@@ -234,6 +242,14 @@ export class PlantView {
element.classList.add("plantHidden_" + this.plantId) element.classList.add("plantHidden_" + this.plantId)
} }
} }
for (const element of Array.from(minOnly)) {
if (showMin) {
element.classList.remove("plantHidden_" + this.plantId)
} else {
element.classList.add("plantHidden_" + this.plantId)
}
}
} }
setTestResult(result: PumpTestResult) { setTestResult(result: PumpTestResult) {
@@ -255,6 +271,7 @@ export class PlantView {
setConfig(plantConfig: PlantConfig) { setConfig(plantConfig: PlantConfig) {
this.mode.value = plantConfig.mode; this.mode.value = plantConfig.mode;
this.targetMoisture.value = plantConfig.target_moisture.toString(); this.targetMoisture.value = plantConfig.target_moisture.toString();
this.minMoisture.value = plantConfig.min_moisture?.toString() || "";
this.pumpTimeS.value = plantConfig.pump_time_s.toString(); this.pumpTimeS.value = plantConfig.pump_time_s.toString();
this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString(); this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString();
this.pumpHourStart.value = plantConfig.pump_hour_start.toString(); this.pumpHourStart.value = plantConfig.pump_hour_start.toString();
@@ -280,6 +297,7 @@ export class PlantView {
let conv: PlantConfig = { let conv: PlantConfig = {
mode: this.mode.value, mode: this.mode.value,
target_moisture: this.targetMoisture.valueAsNumber, target_moisture: this.targetMoisture.valueAsNumber,
min_moisture: this.minMoisture.valueAsNumber,
pump_time_s: this.pumpTimeS.valueAsNumber, pump_time_s: this.pumpTimeS.valueAsNumber,
pump_cooldown_min: this.pumpCooldown.valueAsNumber, pump_cooldown_min: this.pumpCooldown.valueAsNumber,
pump_hour_start: +this.pumpHourStart.value, pump_hour_start: +this.pumpHourStart.value,