diff --git a/package-lock.json b/package-lock.json index 2e973bb..0c405c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "pug": "^3.0.3" }, "devDependencies": { - "nodemon": "^3.0.1" + "nodemon": "^3.1.14" } }, "node_modules/@babel/helper-string-parser": { @@ -361,12 +361,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -1373,15 +1367,15 @@ } }, "node_modules/nodemon": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", - "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "dev": true, "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -1400,14 +1394,25 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/nodemon/node_modules/debug": { @@ -1428,15 +1433,18 @@ } }, "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/nodemon/node_modules/ms": { diff --git a/package.json b/package.json index 89462a8..a57619e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,6 @@ "pug": "^3.0.3" }, "devDependencies": { - "nodemon": "^3.0.1" + "nodemon": "^3.1.14" } } diff --git a/projects/hmc20.md b/projects/hmc20.md new file mode 100644 index 0000000..f4c694c --- /dev/null +++ b/projects/hmc20.md @@ -0,0 +1,117 @@ +--- +title: Human Machine Controller HMC-20 +author: Sam Lefebvre +image: /projects/hmc20/HMC-20_console_600x370.jpg +link: https://www.tindie.com/products/saleconix/hmc-20-human-machine-controller-interface/ +tags: [electronics, STM32, display] +status: finished +date: 2026-05-17 +--- + +The HMC-20 is a flexible, low-cost, multi-purpose HMI interface board. It can host an add-on module for dedicated interfaces. + +## General description + +The HMC-20 **is a multi-purpose HMI board** with the following characteristics: + +* 7-inch capacitive touch panel, 1000 cd/m2 +* Optional resistive touch panel +* Keypad interface with 4x5 buttons +* User application board connector with command interpreter +* PWM-controlled backlight +* 5 digital inputs, 5 digital outputs + PWM +* 24-bit RGB interface +* Internal fonts, soft fonts, and external font ROM chip +* RTC with battery +* SD card for images, layouts, and firmware updates +* USB-C with serial port for control +* Acoustic feedback +* Saving parameters and settings with on board flash + +It contains a range of predefined widgets that can be configured with a JSON files loaded from an SD card. Each property can be bound to events or changed with a command through the **command interpreter interface**. This allows you to develop your own board and connect an FTDI cable to the connector to evaluate your application before having the application board. + +![HMC-20](/projects/hmc20/20240312_165421_blur_noback.png) + +## Operation + +The USB-C connector provides a SD drive and serial port on the PC, with a menu to dump and load layouts (applications), clear memory, set the clock, rotate the display, change brightness, and perform firmware updates. It comes with a bootloader (MCUboot) that provides a robuust firmware update procedure. + +```text +HMI PANEL 1.2.1+0 +---- +Build May 5 2026 23:34:10 +Total flash size: 64Mb +Index [##..................] 8,91% +Storage [##..................] 10,85% +www.saleconix.be | info@saleconix.be +2026 All rights reserved. + +Settings +---- +Time: 01/01/1970 00:00:00 +Display size WxH: 800x480 +Debug port speed: virtual +Command port speed: 115200Bd +LCD bits per pixel: 8 +Display orientation: 0 degrees +Backlight brightness: 100% +Current app selected: appgraphchinese + +Menu +---- +s: Settings menu +i: Info menu +h: Help menu +1: Dump current layout to json +2: Load application from memory +3: Clear selected application +4: Store application from SD card +5: Set clock +6: Set orientation +7: Set brightness +8: Clear data for all applications +9: Format flash memory +10: Erase full flash memory +11: Firmware update +12: Reboot device +``` + +## JSON applications + +Layouts are described in JSON format. Each widget belongs to a page. Visual widgets have a render priority which defines the visible order. Each property can have a default value and can be updated by the command interpreter port. Some parameters are stored permanently in memory like PIN codes, etc. + +```json +{ + "buttons": [ + { + "name": "B0", + "Xloc": 72, + "Yloc": 80, + "width": 15, + "height": 17, + "background": "image", + "image": "s2.bmp", + "backcolor": 0, + "fontcolor": 65535 + } + ], + "functions": [], + "pages": [], + "pincodes": [] +} +``` + +## Dual-board solution + +To enable maximum flexibility, two boards can be connected to each other. The base board HMC-20 provides the core graphical functionality, supplies power, and can also run independently. The add-on board UAB-23 contains the specific interfaces for a given application. Both boards communicate through serial command interpreter with an open protocol. The SD card can be accessed from the application board to upload layouts and firmware images without removing the SD card. For robuust appications, the card can be replaced by a chip, however this requires a board layout update. + +![HMC-20](/projects/hmc20/Afbeelding2.jpg) + +## Downloads + +Datasheet [HMC-20_V12\.pdf](/projects/hmc20/HMC-20_Datasheet_V12.pdf) + +Firmware [1.1.0+0\.bin](/projects/hmc20/hmc20_1.1.0+0.bin) + +For extra information, demos, orders, collaborations, or project ideas, email info@saleconix.be. + diff --git a/projects/hmc20/20240312_165421_blur_noback.png b/projects/hmc20/20240312_165421_blur_noback.png new file mode 100644 index 0000000..b4528d8 Binary files /dev/null and b/projects/hmc20/20240312_165421_blur_noback.png differ diff --git a/projects/hmc20/Afbeelding2.jpg b/projects/hmc20/Afbeelding2.jpg new file mode 100644 index 0000000..aa62e11 Binary files /dev/null and b/projects/hmc20/Afbeelding2.jpg differ diff --git a/projects/hmc20/HMC-20_Datasheet_V12.pdf b/projects/hmc20/HMC-20_Datasheet_V12.pdf new file mode 100644 index 0000000..a8c1917 Binary files /dev/null and b/projects/hmc20/HMC-20_Datasheet_V12.pdf differ diff --git a/projects/hmc20/HMC-20_console_600x370.jpg b/projects/hmc20/HMC-20_console_600x370.jpg new file mode 100644 index 0000000..6a005f1 Binary files /dev/null and b/projects/hmc20/HMC-20_console_600x370.jpg differ diff --git a/projects/hmc20/hmc20_1.1.0+0.bin b/projects/hmc20/hmc20_1.1.0+0.bin new file mode 100644 index 0000000..bbc6bc1 Binary files /dev/null and b/projects/hmc20/hmc20_1.1.0+0.bin differ diff --git a/public/css/style.css b/public/css/style.css index 9b50581..1565b32 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -72,6 +72,23 @@ code,highlight { margin: 0 2px; font-size: .95rem } + +pre { + background: #1b1f23; + border: 1px solid rgba(255,168,106,0.3); + display: inline-block; + max-width: 100%; + padding: 12px; + overflow-x: auto; +} + +pre code { + background: transparent; + color: inherit; + padding: 0; + margin: 0; +} + colored { color: var(--accent); } @@ -86,9 +103,8 @@ colored { .event-description img { max-width: 100%; height: auto; - max-height: 400px; + max-height: 650px; margin: 10px 0; - border: 1px solid var(--accent); } /* Event card hover effect */ @@ -225,8 +241,13 @@ a.active { } .project-detail-image { + width: auto; + max-width: 100%; height: auto; - max-height: 400px; + max-height: 650px; + object-fit: contain; + margin-left: auto; + margin-right: auto; } .project-meta { diff --git a/views/project-detail.pug b/views/project-detail.pug index 361f1ed..e750a74 100644 --- a/views/project-detail.pug +++ b/views/project-detail.pug @@ -35,7 +35,7 @@ html(lang="en") div(style="height:200px;") script. - const markdown = `#{project.description.replace(/`/g, '\\`').replace(/\n/g, '\\n')}`; + const markdown = !{JSON.stringify(project.description).replace(/