start of plantsensor
This commit is contained in:
		
							
								
								
									
										10
									
								
								bootloader/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								bootloader/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| # ESP-IDF build artifacts | ||||
| build/ | ||||
| .sdkconfig* | ||||
| CMakeFiles/ | ||||
| CMakeCache.txt | ||||
| cmake-build-*/ | ||||
| *.log | ||||
| *.bin | ||||
| *.elf | ||||
| *.map | ||||
							
								
								
									
										8
									
								
								bootloader/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								bootloader/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Editor-based HTTP Client requests | ||||
| /httpRequests/ | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
							
								
								
									
										8
									
								
								bootloader/.idea/bootloader.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								bootloader/.idea/bootloader.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <module type="EMPTY_MODULE" version="4"> | ||||
|   <component name="NewModuleRootManager"> | ||||
|     <content url="file://$MODULE_DIR$" /> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|   </component> | ||||
| </module> | ||||
							
								
								
									
										8
									
								
								bootloader/.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								bootloader/.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ProjectModuleManager"> | ||||
|     <modules> | ||||
|       <module fileurl="file://$PROJECT_DIR$/.idea/bootloader.iml" filepath="$PROJECT_DIR$/.idea/bootloader.iml" /> | ||||
|     </modules> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										7
									
								
								bootloader/.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								bootloader/.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> | ||||
|     <mapping directory="$PROJECT_DIR$/../website/themes/blowfish" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										2214
									
								
								bootloader/sdkconfig.old
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2214
									
								
								bootloader/sdkconfig.old
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								rust/src/webserver/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								rust/src/webserver/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| index.html.gz | ||||
| bundle.js.gz | ||||
							
								
								
									
										4
									
								
								rust/src_webpack/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								rust/src_webpack/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| index.html.gz | ||||
| bundle.js.gz | ||||
| index.html | ||||
| bundle.js | ||||
							
								
								
									
										14
									
								
								rust_can_sensor/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								rust_can_sensor/.cargo/config.toml
									
									
									
									
									
										Normal 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" | ||||
							
								
								
									
										32
									
								
								rust_can_sensor/.doomrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								rust_can_sensor/.doomrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| ;;; .doomrc --- doom runtime config -*- mode: emacs-lisp; lexical-binding: t; -*- | ||||
| ;;; Commentary: | ||||
| ;;; Code: | ||||
| (require 'doom) ; be silent, byte-compiler | ||||
|  | ||||
| (after! dape | ||||
|         (add-to-list | ||||
|         'dape-configs | ||||
|         `(gdb-dap-openocd | ||||
|         ensure (lambda (config) | ||||
|                 (dape-ensure-command config) | ||||
|                 (let* ((default-directory | ||||
|                         (or (dape-config-get config 'command-cwd) | ||||
|                                 default-directory)) | ||||
|                         (command (dape-config-get config 'command)) | ||||
|                         (output (shell-command-to-string (format "%s --version" command))) | ||||
|                         (version (save-match-data | ||||
|                                         (when (string-match "GNU gdb \\(?:(.*) \\)?\\([0-9.]+\\)" output) | ||||
|                                         (string-to-number (match-string 1 output)))))) | ||||
|                         (unless (>= version 14.1) | ||||
|                         (user-error "Requires gdb version >= 14.1")))) | ||||
|         modes () | ||||
|         command-cwd dape-command-cwd | ||||
|         command "gdb" | ||||
|         command-args ("--interpreter=dap") | ||||
|         :request nil | ||||
|         :program nil | ||||
|         :args [] | ||||
|         :stopAtBeginningOfMainSubprogram nil)) | ||||
| ) | ||||
|  | ||||
| ;;; .doomrc ends here | ||||
							
								
								
									
										7
									
								
								rust_can_sensor/.gdbinit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								rust_can_sensor/.gdbinit
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										2
									
								
								rust_can_sensor/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								rust_can_sensor/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| target | ||||
| vendor | ||||
							
								
								
									
										8
									
								
								rust_can_sensor/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								rust_can_sensor/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Editor-based HTTP Client requests | ||||
| /httpRequests/ | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
							
								
								
									
										11
									
								
								rust_can_sensor/.idea/ch32-sensor.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								rust_can_sensor/.idea/ch32-sensor.iml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <module type="EMPTY_MODULE" version="4"> | ||||
|   <component name="NewModuleRootManager"> | ||||
|     <content url="file://$MODULE_DIR$"> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> | ||||
|       <excludeFolder url="file://$MODULE_DIR$/target" /> | ||||
|     </content> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|   </component> | ||||
| </module> | ||||
							
								
								
									
										8
									
								
								rust_can_sensor/.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								rust_can_sensor/.idea/modules.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ProjectModuleManager"> | ||||
|     <modules> | ||||
|       <module fileurl="file://$PROJECT_DIR$/.idea/ch32-sensor.iml" filepath="$PROJECT_DIR$/.idea/ch32-sensor.iml" /> | ||||
|     </modules> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										7
									
								
								rust_can_sensor/.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								rust_can_sensor/.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> | ||||
|     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										47
									
								
								rust_can_sensor/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								rust_can_sensor/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| [package] | ||||
| name = "bms" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| ch32-hal = { path = "/home/empire/workspace/ch32-hal/", features = [ | ||||
|     "ch32v203c8t6", | ||||
|     "memory-x", | ||||
|     "embassy", | ||||
|     "rt", | ||||
|     "time-driver-tim2", | ||||
|  | ||||
| ], default-features = false } | ||||
|  | ||||
|  | ||||
|  | ||||
| embassy-executor = { version = "0.7.0", features = [ | ||||
|     # "arch-riscv32", | ||||
|     "arch-spin", # TODO: Required for USBD to connect properl | ||||
|     "executor-thread", | ||||
| ] } | ||||
|  | ||||
| #embassy-time = { version = "0.3.2" } | ||||
| embassy-usb = { version = "0.3.0" } | ||||
| embassy-futures = { version = "0.1.0" } | ||||
| embassy-sync = { version = "0.6.0" } | ||||
|  | ||||
| # This is okay because we should automatically use whatever ch32-hal uses | ||||
| qingke-rt = "*" | ||||
| qingke = "*" | ||||
|  | ||||
| panic-halt = "1.0" | ||||
|  | ||||
|  | ||||
| heapless = { version = "0.8.0", features = ["portable-atomic-critical-section"] } | ||||
| embassy-time = { version = "0.4.0" } | ||||
|  | ||||
| [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 = false | ||||
| opt-level = "z" # Optimize for size. | ||||
							
								
								
									
										111
									
								
								rust_can_sensor/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								rust_can_sensor/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| # ch32v203-bms | ||||
|  | ||||
| A simple battery management controller software. | ||||
|  | ||||
| ## WeActStudio BluePill Plus CH32 Pin Labels | ||||
|  | ||||
| The BluePill Plus CH32 board from WeActStudio uses standard MCU port naming printed on the PCB silkscreen: | ||||
|  | ||||
| - PAx: GPIO Port A pins, labeled PA0 .. PA15 | ||||
| - PBx: GPIO Port B pins, labeled PB0 .. PB15 | ||||
| - PCx: GPIO Port C pins, commonly PC13 .. PC15 are broken out | ||||
| - Other labels typically present: 3V3, 5V, G (GND), NRST (reset), BOOT (BOOT0), and SWD/RVSWD pads for programming/debug (SWCLK/SWDIO or similar). | ||||
|  | ||||
| For the exact header layout and picture of the silkscreen labels, please refer to the official WeActStudio documentation: | ||||
|  | ||||
| - https://github.com/WeActStudio/WeActStudio.BluePill-Plus-CH32 | ||||
|  | ||||
| Pins used by this firmware (as referenced in `src/main.rs`): | ||||
|  | ||||
| - PA1: ADC analog input (combined Trigger/Threshold in the 555-timer example) | ||||
| - PB0: Digital output (Q in the 555-timer example) | ||||
|  | ||||
| If you need to map a label to code, use the same letter+number as in the silkscreen. For example, `p.PA1` in code corresponds to pin labeled "PA1" on the PCB header, and `p.PB0` corresponds to "PB0". | ||||
|  | ||||
| ## Building | ||||
|  | ||||
| ``` sh | ||||
| cargo build --release | ||||
| ``` | ||||
|  | ||||
| ## USB CDC Console (optional) | ||||
|  | ||||
| This project includes an optional software USB CDC-ACM device stack using embassy-usb. It runs on the CH32V203’s USB device peripheral but implements the protocol fully in software (no built-in USB class firmware is required). | ||||
|  | ||||
| How to enable: | ||||
| - Build with the `usb-cdc` feature: `cargo build --release --features usb-cdc` | ||||
| - Wire the MCU’s USB pins to a USB connector: | ||||
|   - D+ (PA12) | ||||
|   - D− (PA11) | ||||
|   - GND and 5V (as appropriate for your board; ensure you have a data-capable cable) | ||||
|  | ||||
| After flashing and powering via USB, your OS should enumerate a virtual serial port (e.g., /dev/ttyACM0 on Linux, COMx on Windows, /dev/tty.usbmodem* on macOS). Open it with any terminal program (baud setting is ignored by CDC but 115200 is fine). | ||||
|  | ||||
| Example: | ||||
| - Linux: `screen /dev/ttyACM0 115200` | ||||
| - macOS: `screen /dev/tty.usbmodemXXXX 115200` | ||||
| - Windows: Use PuTTY on the shown COM port. | ||||
|  | ||||
| Notes: | ||||
| - The firmware currently implements an echo console: bytes you type are echoed back. You can extend it to print logs or interact with your application. | ||||
| - If you don’t see a device, ensure D+ (PA12) and D− (PA11) are connected and the cable supports data. | ||||
|  | ||||
| ## Flash | ||||
|  | ||||
| You can flash the built ELF using wchisp (WCH ISP tool): | ||||
|  | ||||
| ``` sh | ||||
| wchisp flash target/riscv32imc-unknown-none-elf/release/bms | ||||
| # or, if using a wrapper on your system/container, the command may be: | ||||
| # wchip wchisp flash target/riscv32imc-unknown-none-elf/release/bms | ||||
| ``` | ||||
|  | ||||
| ## Unlock / Remove MCU Protection (fix "checksum error") | ||||
|  | ||||
| Some CH32 devices ship with flash protection enabled. When protected, tools may report a checksum error and refuse to program. You can clear the protection by performing a full chip erase with wchisp. This will erase all flash contents. | ||||
|  | ||||
| Steps: | ||||
|  | ||||
| - Ensure WCH-Link/WCH-LinkE is connected to the target and your OS has permissions to access it. | ||||
| - Verify connection and current status: | ||||
|  | ||||
| ``` sh | ||||
| wchisp info | ||||
| ``` | ||||
|  | ||||
| - Mass erase the chip (this also clears protection/lock bits): | ||||
|  | ||||
| ``` sh | ||||
| * start the device (while pressing boot) | ||||
| wchisp erase | ||||
| * powercycle the device (while pressing boot) | ||||
| wchisp config reset | ||||
| * powercycle the device again (while pressing boot), flash should not work | ||||
| ``` | ||||
|  | ||||
| - Flash your firmware again: | ||||
|  | ||||
| ``` sh | ||||
| wchisp flash target/riscv32imc-unknown-none-elf/release/bms | ||||
| ``` | ||||
|  | ||||
| Notes: | ||||
| - The target used in this project is CH32V203C8T6; wchisp detects it automatically with WCH-Link. | ||||
| - If your wchisp version differs, run `wchisp --help`, `wchisp erase --help`, or consult the tool's README for the exact flag name. | ||||
| - If the tool still reports protection, look for commands named `unprotect` or `protect --off` in `wchisp --help`. The mass/chip erase is the typical way to clear protection. | ||||
|  | ||||
| ## 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 | ||||
| ``` | ||||
							
								
								
									
										10
									
								
								rust_can_sensor/bin/build-wch-tools-container.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								rust_can_sensor/bin/build-wch-tools-container.sh
									
									
									
									
									
										Executable 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 | ||||
							
								
								
									
										29
									
								
								rust_can_sensor/bin/gdb
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										29
									
								
								rust_can_sensor/bin/gdb
									
									
									
									
									
										Executable 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" "$@" | ||||
							
								
								
									
										44
									
								
								rust_can_sensor/bin/openocd
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										44
									
								
								rust_can_sensor/bin/openocd
									
									
									
									
									
										Executable 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" "$@" | ||||
							
								
								
									
										11
									
								
								rust_can_sensor/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								rust_can_sensor/build.rs
									
									
									
									
									
										Normal 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"); | ||||
| } | ||||
							
								
								
									
										125
									
								
								rust_can_sensor/memory.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								rust_can_sensor/memory.x
									
									
									
									
									
										Normal 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); | ||||
							
								
								
									
										17
									
								
								rust_can_sensor/openocd.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								rust_can_sensor/openocd.cfg
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										2
									
								
								rust_can_sensor/rust-toolchain.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								rust_can_sensor/rust-toolchain.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| [toolchain] | ||||
| channel = "nightly" | ||||
							
								
								
									
										207
									
								
								rust_can_sensor/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								rust_can_sensor/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| #![no_std] | ||||
| #![no_main] | ||||
|  | ||||
| use core::fmt::Write as _; | ||||
| use ch32_hal::gpio::{Level, Output, Speed}; | ||||
| use ch32_hal::adc::{Adc, SampleTime, ADC_MAX}; | ||||
| use ch32_hal::peripherals::USBD; | ||||
| // use ch32_hal::delay::Delay; | ||||
| use embassy_executor::{Spawner, task}; | ||||
| use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||||
| use embassy_usb::{Builder, UsbDevice}; | ||||
| use embassy_futures::yield_now; | ||||
| use hal::usbd::{Driver}; | ||||
| use hal::{bind_interrupts}; | ||||
| use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||||
| use embassy_sync::channel::{Channel, TrySendError}; | ||||
| use embassy_time::{Timer, Instant, Duration}; | ||||
| use heapless::String; | ||||
| use {ch32_hal as hal, panic_halt as _}; | ||||
|  | ||||
| bind_interrupts!(struct Irqs { | ||||
|     USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>; | ||||
| }); | ||||
|  | ||||
| // 'static storage for USB descriptors and state so we can spawn tasks | ||||
| static mut USB_CONFIG_DESCRIPTOR: [u8; 256] = [0; 256]; | ||||
| static mut USB_BOS_DESCRIPTOR: [u8; 256] = [0; 256]; | ||||
| static mut USB_CONTROL_BUF: [u8; 64] = [0; 64]; | ||||
| static mut CDC_STATE: core::mem::MaybeUninit<State<'static>> = core::mem::MaybeUninit::uninit(); | ||||
| static mut USB_DEVICE: core::mem::MaybeUninit<UsbDevice<'static, Driver<'static, hal::peripherals::USBD>>> = core::mem::MaybeUninit::uninit(); | ||||
| static mut CDC_CLASS: core::mem::MaybeUninit<CdcAcmClass<'static, Driver<'static, hal::peripherals::USBD>>> = core::mem::MaybeUninit::uninit(); | ||||
|  | ||||
| static LOG_CH: Channel<CriticalSectionRawMutex, heapless::String<128>, 8> = Channel::new(); | ||||
|  | ||||
| #[embassy_executor::main(entry = "qingke_rt::entry")] | ||||
| async fn main(spawner: Spawner) { | ||||
|     let p = hal::init(hal::Config { | ||||
|         rcc: hal::rcc::Config::SYSCLK_FREQ_144MHZ_HSI, | ||||
|         ..Default::default() | ||||
|     }); | ||||
|  | ||||
|  | ||||
|  | ||||
|     // Build driver and USB stack using 'static buffers | ||||
|     let driver = Driver::new(p.USBD, Irqs, p.PA12, p.PA11); | ||||
|  | ||||
|     let mut config = embassy_usb::Config::new(0xC0DE, 0xCAFE); | ||||
|     config.manufacturer = Some("Embassy"); | ||||
|     config.product = Some("USB-serial example"); | ||||
|     config.serial_number = Some("12345678"); | ||||
|     config.max_power = 100; | ||||
|     config.max_packet_size_0 = 64; | ||||
|  | ||||
|     // Windows compatibility requires these; CDC-ACM | ||||
|     config.device_class = 0x02; | ||||
|     config.device_sub_class = 0x02; | ||||
|     config.device_protocol = 0x00; | ||||
|     config.composite_with_iads = false; | ||||
|  | ||||
|     let usb = unsafe { | ||||
|         let mut builder = Builder::new( | ||||
|             driver, | ||||
|             config, | ||||
|             &mut USB_CONFIG_DESCRIPTOR, | ||||
|             &mut USB_BOS_DESCRIPTOR, | ||||
|             &mut [], // no msos descriptors | ||||
|             &mut USB_CONTROL_BUF, | ||||
|         ); | ||||
|  | ||||
|         // Initialize CDC state and create CDC-ACM class | ||||
|         CDC_STATE.write(State::new()); | ||||
|         let class = { | ||||
|             let state_ref: &mut State<'static> = CDC_STATE.assume_init_mut(); | ||||
|             CdcAcmClass::new(&mut builder, state_ref, 64) | ||||
|         }; | ||||
|         CDC_CLASS.write(class); | ||||
|  | ||||
|         // Build USB device | ||||
|         let dev = builder.build(); | ||||
|         USB_DEVICE.write(dev); | ||||
|  | ||||
|         USB_DEVICE.assume_init_mut() | ||||
|     }; | ||||
|  | ||||
|     // Create GPIO for 555 Q output (PB0) | ||||
|     let q_out = Output::new(p.PB0, Level::Low, Speed::Low); | ||||
|     // Built-in LED on PB2 mirrors Q state | ||||
|     let led = Output::new(p.PB2, Level::Low, Speed::Low); | ||||
|  | ||||
|     // Create ADC on ADC1 and use PA1 as analog input (Threshold/Trigger) | ||||
|     let adc = Adc::new(p.ADC1, Default::default()); | ||||
|     let ain = p.PA1; | ||||
|  | ||||
|     // Spawn independent tasks using 'static references | ||||
|     unsafe { | ||||
|         let class = CDC_CLASS.assume_init_mut(); | ||||
|         spawner.spawn(usb_task(usb)).unwrap(); | ||||
|         spawner.spawn(usb_writer(class)).unwrap(); | ||||
|         // move Q output, LED, ADC and analog input into worker task | ||||
|         spawner.spawn(worker(q_out, led, adc, ain)).unwrap(); | ||||
|     } | ||||
|  | ||||
|     // Prevent main from exiting | ||||
|     core::future::pending::<()>().await; | ||||
| } | ||||
|  | ||||
| #[task] | ||||
| async fn worker( | ||||
|     mut q: Output<'static>, | ||||
|     mut led: Output<'static>, | ||||
|     mut adc: Adc<'static, hal::peripherals::ADC1>, | ||||
|     mut ain: hal::peripherals::PA1, | ||||
| ) { | ||||
|     // 555 emulation state: Q initially Low | ||||
|     let mut q_high = false; | ||||
|     let low_th: u16 = (ADC_MAX as u16) / 3;       // ~1/3 Vref | ||||
|     let high_th: u16 = ((ADC_MAX as u32 * 2) / 3) as u16; // ~2/3 Vref | ||||
|  | ||||
|     loop { | ||||
|         // Count rising edges of Q in a 100 ms window | ||||
|         let start = Instant::now(); | ||||
|         let mut pulses: u32 = 0; | ||||
|         let mut last_q = q_high; | ||||
|  | ||||
|         while Instant::now().checked_duration_since(start).unwrap_or(Duration::from_millis(0)) | ||||
|             < Duration::from_millis(1000) | ||||
|         { | ||||
|             // Sample the analog input (Threshold/Trigger on A1) | ||||
|             let val: u16 = adc.convert(&mut ain, SampleTime::CYCLES28_5); | ||||
|  | ||||
|             // 555 core behavior: | ||||
|             // - If input <= 1/3 Vref => set Q high (trigger) | ||||
|             // - If input >= 2/3 Vref => set Q low (threshold) | ||||
|             // - Otherwise keep previous Q state (hysteresis) | ||||
|             if val <= low_th { | ||||
|                 q_high = true; | ||||
|             } else if val >= high_th { | ||||
|                 q_high = false; | ||||
|             } | ||||
|  | ||||
|             // Drive output pin accordingly | ||||
|             if q_high { | ||||
|                 q.set_high(); | ||||
|                 led.set_high(); | ||||
|             } else { | ||||
|                 q.set_low(); | ||||
|                 led.set_low(); | ||||
|             } | ||||
|  | ||||
|             // Count rising edges | ||||
|             if !last_q && q_high { | ||||
|                 pulses = pulses.saturating_add(1); | ||||
|             } | ||||
|             last_q = q_high; | ||||
|  | ||||
|             // Yield to allow USB and other tasks to run | ||||
|             yield_now().await; | ||||
|         } | ||||
|  | ||||
|         // Compute frequency from 100 ms window | ||||
|         let freq_hz = pulses; // pulses per 0.1s => Hz | ||||
|  | ||||
|         let mut msg: heapless::String<128> = heapless::String::new(); | ||||
|         let _ = write!( | ||||
|             &mut msg, | ||||
|             "555 window=100ms pulses={} freq={} Hz (A1->Q on PB0)\r\n", | ||||
|             pulses, freq_hz | ||||
|         ); | ||||
|         log(msg); | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn log(message: heapless::String<128>) { | ||||
|     match LOG_CH.try_send(message) { | ||||
|         Ok(_) => {} | ||||
|         Err(_) => {} | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[task] | ||||
| async fn usb_task(usb: &'static mut UsbDevice<'static, Driver<'static, hal::peripherals::USBD>>) { | ||||
|     usb.run().await; | ||||
| } | ||||
|  | ||||
| #[task] | ||||
| async fn usb_writer( | ||||
|     class: &'static mut CdcAcmClass<'static, Driver<'static, hal::peripherals::USBD>> | ||||
| ) { | ||||
|     loop { | ||||
|  | ||||
|         class.wait_connection().await; | ||||
|         printer(class).await; | ||||
|     } | ||||
| } | ||||
|  | ||||
| async fn printer(class: &mut CdcAcmClass<'static, Driver<'static, USBD>>) { | ||||
|     loop { | ||||
|         let msg = LOG_CH.receive().await; | ||||
|         match class.write_packet(msg.as_bytes()).await { | ||||
|             Ok(_) => {} | ||||
|             Err(_) => { | ||||
|                 // Disconnected or endpoint disabled | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								rust_can_sensor/wch-tools.Containerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								rust_can_sensor/wch-tools.Containerfile
									
									
									
									
									
										Normal 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" ] | ||||
		Reference in New Issue
	
	Block a user