Merge branch 'master' of ssh://git.sixteenmillimeter.com/16mm/contact_printer
This commit is contained in:
commit
b927db828c
|
@ -1 +1,2 @@
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
bin/
|
|
@ -1,9 +1,12 @@
|
||||||
[submodule "scad/common"]
|
[submodule "scad/common"]
|
||||||
path = scad/common
|
path = scad/common
|
||||||
url = https://git.sixteenmillimeter.com/modules/common.git
|
url = https://git.sixteenmillimeter.com/modules/common.git
|
||||||
|
ignore = dirty
|
||||||
[submodule "scad/takeup"]
|
[submodule "scad/takeup"]
|
||||||
path = scad/takeup
|
path = scad/takeup
|
||||||
url = https://git.sixteenmillimeter.com/modules/takeup.git
|
url = https://git.sixteenmillimeter.com/modules/takeup.git
|
||||||
|
ignore = dirty
|
||||||
[submodule "scad/sprocketed_roller"]
|
[submodule "scad/sprocketed_roller"]
|
||||||
path = scad/sprocketed_roller
|
path = scad/sprocketed_roller
|
||||||
url = https://git.sixteenmillimeter.com/modules/sprocketed_roller.git
|
url = https://git.sixteenmillimeter.com/modules/sprocketed_roller.git
|
||||||
|
ignore = dirty
|
64
README.md
64
README.md
|
@ -1,17 +1,67 @@
|
||||||
# contact printer
|
# contact printer
|
||||||
|
|
||||||
A desktop 16mm contact printer made for CNC/laser cutting and 3D printing
|
A desktop 16mm contact printer and film transport platform made using 3D printing and aluminum extrusion.
|
||||||
|
|
||||||
|
## [Project Home: git.sixteenmillimeter.com/16mm/contact_printer](https://git.sixteenmillimeter.com/16mm/contact_printer)
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
Developed with support from [Filmwerkplaats at WORM](https://worm.org/spaces/filmwerkplaats/) and [SPECTRAL](http://www.spectral-cinematics.eu/).
|
||||||
|
|
||||||
|
Special thanks to [Esther Urlus](https://estherurlus.hotglue.me/), [Hrvoje Spudić](https://hrvojespudic.net/) and [Nan Wang](https://nanwang.org/).
|
||||||
|
|
||||||
|
Magnetic clutch design inspired by Clyde Shaffer's [Shaffer Linear Processor](https://clydeshaffer.com/slp/).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [Project Home: git.sixteenmillimeter.com/16mm/contact_printer](https://git.sixteenmillimeter.com/16mm/contact_printer)
|
## Description
|
||||||
|
|
||||||
|
This contact printer is designed to make 16mm prints using affordable electronics and 3D printing.
|
||||||
|
It is *not* intended as a replacement for professional printers like the Bell & Howell Model C.
|
||||||
|
This project is for DIY filmmakers, independent artists and small film labs to have a machine that can produce reasonable-quality work prints with a small footprint on short lengths of film easily.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
[Contact](https://vimeo.com/flashfra/contact?share=git) the first print made on the prototype during a residency at [Filmwerkplaats](https://filmwerkplaats.org/).
|
||||||
|
Soundtrack applied by the opt_snd soundtrack recorder system by [Hrvoje Spudić](https://hrvojespudic.net/optical-sound-2).
|
||||||
|
|
||||||
|
# Bill of Materials
|
||||||
|
|
||||||
|
Below are estimates based on per-unit costs found online.
|
||||||
|
Actual price of a full build may be higher.
|
||||||
|
|
||||||
|
<!-- bom -->
|
||||||
|
|
||||||
|
| Part | Qty | Cost (USD) | Minumum |
|
||||||
|
|-------------------------------------|------|------------|---------------------------------------------|
|
||||||
|
| M4 hex bolt 40mm | 4 | $2.44 | [10 for $6.09](https://amzn.to/4ikpYL8) |
|
||||||
|
| M3 sliding t slot nut | 25 | $1.43 | [105 for $5.99](https://amzn.to/48GRrSU) |
|
||||||
|
| M3 hex cap bolt 8mm | 25 | $2.25 | [100 for $8.99](https://amzn.to/3YEvWNB) |
|
||||||
|
| M3 hex cap bolt 6mm | 8 | $0.59 | [100 for $7.26](https://amzn.to/3AwiZxo) |
|
||||||
|
| M3 hex cap bolt 12mm | 1 | $0.09 | [100 for $8.36](https://amzn.to/48CGa5Y) |
|
||||||
|
| L298N Motor driver module | 1 | $2.88 | [4 for $11.49](https://amzn.to/4ellssy) |
|
||||||
|
| ESP32 GPIO breakout board | 1 | $6.00 | [2 for $11.99](https://amzn.to/3UFjpbO) |
|
||||||
|
| ESP32 Dev board | 1 | $6.67 | [3 for $19.99](https://amzn.to/3NXCvGj) |
|
||||||
|
| 608-RS Ball Bearing | 1 | $0.18 | [100 for $17.79](https://amzn.to/4fKxDA7) |
|
||||||
|
| 250RPM DC geared motor | 2 | $29.98 | [1 for $14.99](https://amzn.to/3NWkcRL) |
|
||||||
|
| 2020 Aluminum extrusion mm | 1880 | $11.41 | [12200 for $73.99](https://amzn.to/418OicC) |
|
||||||
|
| 100RPM DC geared motor with encoder | 1 | $16.19 | [1 for $16.19](https://amzn.to/3UF707G) |
|
||||||
|
|---------|------|--------|---------|
|
||||||
|
| TOTAL | 1950 | $80.11 | $203.12 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- /bom -->
|
||||||
|
|
||||||
### Mirrors
|
### Mirrors
|
||||||
|
|
||||||
* [github.com/sixteenmillimeter/contact_printer](https://github.com/sixteenmillimeter/contact_printer)
|
* [github.com/sixteenmillimeter/contact_printer](https://github.com/sixteenmillimeter/contact_printer)
|
||||||
* [gitlab.com/16mm/contact_printer](https://gitlab.com/16mm/contact_printer)
|
* [gitlab.com/16mm/contact_printer](https://gitlab.com/16mm/contact_printer)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.2.2
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
.env
|
|
@ -0,0 +1,35 @@
|
||||||
|
# contact printer dev server
|
||||||
|
|
||||||
|
The purpose of this project is to host a dev server that development branches of the contact printer can post reports to after running.
|
||||||
|
This can be used for profiling the operation with different settings, hardware and conditions.
|
||||||
|
Reports will log data as it is needed without a specific goal from the outset, largely looking at speed of motors over time compared to PWM and can be used to improve performance or documentation.
|
||||||
|
|
||||||
|
The server has no frontend and simply accepts a post from the ESP32 running the contact printer and inserts the data into the SQLite database.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Install the node dependencies for running the server.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --omit=dev
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like to develop the server, install the complete set of dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node dist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
If you make changes to the Typescript in source, recompile the server code.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run compile
|
||||||
|
```
|
|
@ -0,0 +1 @@
|
||||||
|
PORT=9999
|
|
@ -0,0 +1,32 @@
|
||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
require("dotenv/config");
|
||||||
|
const express_1 = __importDefault(require("express"));
|
||||||
|
const body_parser_1 = __importDefault(require("body-parser"));
|
||||||
|
const log_1 = require("./log");
|
||||||
|
const PORT = typeof process.env['PORT'] !== 'undefined' ? parseInt(process.env['PORT'], 10) : 9999;
|
||||||
|
let db;
|
||||||
|
const app = (0, express_1.default)();
|
||||||
|
const log = (0, log_1.createLog)('server');
|
||||||
|
app.use(body_parser_1.default.json());
|
||||||
|
app.use(body_parser_1.default.urlencoded({ extended: true }));
|
||||||
|
async function setup() {
|
||||||
|
//db = new Database('./data.sqlite');
|
||||||
|
}
|
||||||
|
async function report(req, res, next) {
|
||||||
|
//
|
||||||
|
log.info(`Added record`);
|
||||||
|
res.json({ success: true });
|
||||||
|
}
|
||||||
|
app.post('/', report);
|
||||||
|
async function main() {
|
||||||
|
await setup();
|
||||||
|
app.listen(PORT, async () => {
|
||||||
|
log.info(`contact_printer_dev_server running on port ${PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
main();
|
||||||
|
//# sourceMappingURL=index.js.map
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,yBAAuB;AACvB,sDAA8B;AAK9B,8DAAqC;AAGrC,+BAAiC;AAGjC,MAAM,IAAI,GAAY,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAE5G,IAAI,EAAa,CAAC;AAClB,MAAM,GAAG,GAAa,IAAA,iBAAO,GAAE,CAAC;AAChC,MAAM,GAAG,GAAY,IAAA,eAAS,EAAC,QAAQ,CAAC,CAAC;AAEzC,GAAG,CAAC,GAAG,CAAC,qBAAU,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3B,GAAG,CAAC,GAAG,CAAC,qBAAU,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAGnD,KAAK,UAAU,KAAK;IACnB,qCAAqC;AACtC,CAAC;AAED,KAAK,UAAU,MAAM,CAAE,GAAa,EAAE,GAAa,EAAE,IAAmB;IACvE,EAAE;IACF,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACzB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAG,IAAI,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAGtB,KAAK,UAAU,IAAI;IAClB,MAAM,KAAK,EAAE,CAAC;IACd,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QAC3B,GAAG,CAAC,IAAI,CAAC,8CAA8C,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC"}
|
|
@ -0,0 +1,50 @@
|
||||||
|
'use strict';
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.createLog = void 0;
|
||||||
|
/** @module log */
|
||||||
|
/** Wrapper for winston that tags streams and optionally writes files with a simple interface. */
|
||||||
|
/** Module now also supports optional papertrail integration, other services to follow */
|
||||||
|
const winston_1 = require("winston");
|
||||||
|
const { SPLAT } = require('triple-beam');
|
||||||
|
const { isObject } = require('lodash');
|
||||||
|
const APP_NAME = process.env.APP_NAME || 'default';
|
||||||
|
let winstonPapertrail;
|
||||||
|
function formatObject(param) {
|
||||||
|
if (isObject(param)) {
|
||||||
|
return JSON.stringify(param);
|
||||||
|
}
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
const all = (0, winston_1.format)((info) => {
|
||||||
|
const splat = info[SPLAT] || [];
|
||||||
|
const message = formatObject(info.message);
|
||||||
|
const rest = splat.map(formatObject).join(' ');
|
||||||
|
info.message = `${message} ${rest}`;
|
||||||
|
return info;
|
||||||
|
});
|
||||||
|
const myFormat = winston_1.format.printf(({ level, message, label, timestamp }) => {
|
||||||
|
return `${timestamp} [${label}] ${level}: ${message}`;
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Returns a winston logger configured to service
|
||||||
|
*
|
||||||
|
* @param {string} label Label appearing on logger
|
||||||
|
* @param {string} filename Optional file to write log to
|
||||||
|
*
|
||||||
|
* @returns {object} Winston logger
|
||||||
|
*/
|
||||||
|
function createLog(label, filename = null) {
|
||||||
|
const tports = [new (winston_1.transports.Console)()];
|
||||||
|
const fmat = winston_1.format.combine(all(), winston_1.format.label({ label }), winston_1.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), winston_1.format.colorize(), myFormat);
|
||||||
|
let papertrailOpts;
|
||||||
|
if (filename !== null) {
|
||||||
|
tports.push(new (winston_1.transports.File)({ filename }));
|
||||||
|
}
|
||||||
|
return (0, winston_1.createLogger)({
|
||||||
|
format: fmat,
|
||||||
|
transports: tports
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.createLog = createLog;
|
||||||
|
module.exports = { createLog };
|
||||||
|
//# sourceMappingURL=log.js.map
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAEZ,kBAAkB;AAClB,iGAAiG;AACjG,yFAAyF;AAEzF,qCAA2D;AAC3D,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AACzC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvC,MAAM,QAAQ,GAAY,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,SAAS,CAAC;AAE5D,IAAI,iBAAiB,CAAC;AAEtB,SAAS,YAAY,CAAE,KAAW;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,GAAG,GAAG,IAAA,gBAAM,EAAC,CAAC,IAAU,EAAE,EAAE;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,GAAG,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC;IACpC,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,gBAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAQ,EAAE,EAAE;IAC5E,OAAO,GAAG,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,OAAO,EAAE,CAAC;AACxD,CAAC,CAAC,CAAC;AAEH;;;;;;;EAOE;AACF,SAAgB,SAAS,CAAE,KAAc,EAAE,WAAoB,IAAI;IAC/D,MAAM,MAAM,GAAW,CAAE,IAAI,CAAC,oBAAU,CAAC,OAAO,CAAC,EAAE,CAAE,CAAC;IACtD,MAAM,IAAI,GAAS,gBAAM,CAAC,OAAO,CAC7B,GAAG,EAAE,EACL,gBAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,EACvB,gBAAM,CAAC,SAAS,CAAC,EAAC,MAAM,EAAE,yBAAyB,EAAC,CAAC,EACrD,gBAAM,CAAC,QAAQ,EAAE,EACjB,QAAQ,CACX,CAAC;IACF,IAAI,cAAoB,CAAC;IAEzB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAE,IAAI,CAAC,oBAAU,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAE,CAAC;IACvD,CAAC;IAED,OAAO,IAAA,sBAAY,EAAC;QAChB,MAAM,EAAG,IAAI;QACb,UAAU,EAAG,MAAM;KACtB,CAAC,CAAC;AACP,CAAC;AAnBD,8BAmBC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,SAAS,EAAE,CAAC"}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "contact_printer_dev_server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Server for receiving data from contact printer for profiling performance.",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"compile": "./node_modules/.bin/tsc -p tsconfig.json",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git.sixteenmillimeter.com/16mm/contact_printer.git"
|
||||||
|
},
|
||||||
|
"author": "mmcwilliams",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/multer": "^1.4.11",
|
||||||
|
"@types/node": "^20.11.20",
|
||||||
|
"@types/uuid": "^9.0.8",
|
||||||
|
"@types/winston": "^2.4.4",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"triple-beam": "^1.4.1",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS queue (
|
||||||
|
id TEXT(36) PRIMARY KEY,
|
||||||
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'dotenv/config';
|
||||||
|
import express from 'express';
|
||||||
|
import { Express, Request, Response, NextFunction } from 'express'
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { Database } from 'sqlite3';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import multer from 'multer';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { createLog } from './log'
|
||||||
|
import type { Logger } from 'winston';
|
||||||
|
|
||||||
|
const PORT : number = typeof process.env['PORT'] !== 'undefined' ? parseInt(process.env['PORT'], 10) : 9999;
|
||||||
|
|
||||||
|
let db : Database;
|
||||||
|
const app : Express = express();
|
||||||
|
const log : Logger = createLog('server');
|
||||||
|
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
|
||||||
|
async function setup () {
|
||||||
|
//db = new Database('./data.sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function report (req : Request, res: Response, next : NextFunction) {
|
||||||
|
//
|
||||||
|
log.info(`Added record`);
|
||||||
|
res.json({ success : true });
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post('/', report);
|
||||||
|
|
||||||
|
|
||||||
|
async function main () {
|
||||||
|
await setup();
|
||||||
|
app.listen(PORT, async () => {
|
||||||
|
log.info(`contact_printer_dev_server running on port ${PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -0,0 +1,63 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/** @module log */
|
||||||
|
/** Wrapper for winston that tags streams and optionally writes files with a simple interface. */
|
||||||
|
/** Module now also supports optional papertrail integration, other services to follow */
|
||||||
|
|
||||||
|
import { format, transports, createLogger } from 'winston';
|
||||||
|
const { SPLAT } = require('triple-beam');
|
||||||
|
const { isObject } = require('lodash');
|
||||||
|
|
||||||
|
const APP_NAME : string = process.env.APP_NAME || 'default';
|
||||||
|
|
||||||
|
let winstonPapertrail;
|
||||||
|
|
||||||
|
function formatObject (param : any) {
|
||||||
|
if (isObject(param)) {
|
||||||
|
return JSON.stringify(param);
|
||||||
|
}
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
|
const all = format((info : any) => {
|
||||||
|
const splat = info[SPLAT] || [];
|
||||||
|
const message = formatObject(info.message);
|
||||||
|
const rest = splat.map(formatObject).join(' ');
|
||||||
|
info.message = `${message} ${rest}`;
|
||||||
|
return info;
|
||||||
|
});
|
||||||
|
|
||||||
|
const myFormat = format.printf(({ level, message, label, timestamp } : any) => {
|
||||||
|
return `${timestamp} [${label}] ${level}: ${message}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a winston logger configured to service
|
||||||
|
*
|
||||||
|
* @param {string} label Label appearing on logger
|
||||||
|
* @param {string} filename Optional file to write log to
|
||||||
|
*
|
||||||
|
* @returns {object} Winston logger
|
||||||
|
*/
|
||||||
|
export function createLog (label : string, filename : string = null) {
|
||||||
|
const tports : any[] = [ new (transports.Console)() ];
|
||||||
|
const fmat : any = format.combine(
|
||||||
|
all(),
|
||||||
|
format.label({ label }),
|
||||||
|
format.timestamp({format: 'YYYY-MM-DD HH:mm:ss.SSS'}),
|
||||||
|
format.colorize(),
|
||||||
|
myFormat,
|
||||||
|
);
|
||||||
|
let papertrailOpts : any;
|
||||||
|
|
||||||
|
if (filename !== null) {
|
||||||
|
tports.push( new (transports.File)({ filename }) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLogger({
|
||||||
|
format : fmat,
|
||||||
|
transports : tports
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createLog };
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"target": "ES2020",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"removeComments" : false,
|
||||||
|
"baseUrl" : "dist",
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"rootDir" : "./src/",
|
||||||
|
"paths" : {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude" : [
|
||||||
|
"./dist"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
module,quantity,part,part_id,description
|
||||||
|
contact_printer,1040,2020 Aluminum extrusion mm,N/A,Sides and central frame 4x 260mm
|
||||||
|
contact_printer,840,2020 Aluminum extrusion mm,N/A,Top and bottom frame 2x 420mm
|
||||||
|
electronics_panel,4,M3 hex cap bolt 6mm,N/A,Attach the GPIO breakout board to the panel
|
||||||
|
electronics_panel,1,ESP32 GPIO breakout board,N/A,To make the ESP32 dev board easier to wire
|
||||||
|
electronics_panel,6,M3 sliding t slot nut,N/A,Attach the frame to the electronics_panel
|
||||||
|
electronics_panel,6,M3 hex cap bolt 8mm,N/A,Attach the electronics_panel to the frame
|
||||||
|
electronics_panel,1,L298N Motor driver module,N/A,Control the 3 motors using 2 channels
|
||||||
|
electronics_panel,1,ESP32 Dev board,N/A,Control the contact_printer
|
||||||
|
sprocketed_roller_invert_solid,1,608-RS Ball Bearing,608-RS,Reduces wobble in the rollers spin
|
||||||
|
sprocketed_roller_invert_solid,1,M3 hex cap bolt 12mm,N/A,Attaches the sprocketed_roller to the geared motor
|
||||||
|
takeup_panel_stock,6,M3 sliding t slot nut,N/A,Attach the frame to the takeup_panel_stock
|
||||||
|
takeup_panel_stock,6,M3 hex cap bolt 8mm,N/A,Attach the takeup_panel_stock to the frame
|
||||||
|
takeup_panel_stock,1,250RPM DC geared motor,JSX40-370,Drive the takeup of the stock pathway
|
||||||
|
takeup_panel_picture,7,M3 sliding t slot nut,N/A,Attach the frame to the takeup_panel_picture
|
||||||
|
takeup_panel_picture,7,M3 hex cap bolt 8mm,N/A,Attach the takeup_panel_picture to the frame
|
||||||
|
takeup_panel_picture,1,250RPM DC geared motor,JSX40-370,Drive the takeup of the picture pathway
|
||||||
|
panel,4,M4 hex bolt 40mm,N/A,Attach the lamp to the panel
|
||||||
|
panel,1,100RPM DC geared motor with encoder,N/A,Drive the sprocketed_roller
|
||||||
|
panel,6,M3 sliding t slot nut,N/A,Attach aluminum extrusions to panel
|
||||||
|
panel,6,M3 hex cap bolt 8mm,N/A,Attach panel to aluminum extrusions
|
||||||
|
panel,4,M3 hex cap bolt 6mm,N/A,Attach encoder motor to panel
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
quantity,part,part_id,price
|
||||||
|
4,"M4 hex bolt 40mm",N/A,244
|
||||||
|
25,"M3 sliding t slot nut",N/A,143
|
||||||
|
25,"M3 hex cap bolt 8mm",N/A,225
|
||||||
|
8,"M3 hex cap bolt 6mm",N/A,59
|
||||||
|
1,"M3 hex cap bolt 12mm",N/A,9
|
||||||
|
1,"L298N Motor driver module",N/A,288
|
||||||
|
1,"ESP32 GPIO breakout board",N/A,600
|
||||||
|
1,"ESP32 Dev board",N/A,667
|
||||||
|
1,"608-RS Ball Bearing",608-RS,18
|
||||||
|
2,"250RPM DC geared motor",JSX40-370,2998
|
||||||
|
1880,"2020 Aluminum extrusion mm",N/A,1141
|
||||||
|
1,"100RPM DC geared motor with encoder",N/A,1619
|
||||||
|
1950,TOTAL,N/A,8011
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
part,part_id,price,quantity,url
|
||||||
|
M3 hex nut,N/A,999,300,https://amzn.to/4hAnwjc
|
||||||
|
M3 hex cap bolt 6mm,N/A,726,100,https://amzn.to/3AwiZxo
|
||||||
|
M3 hex cap bolt 8mm,N/A,899,100,https://amzn.to/3YEvWNB
|
||||||
|
M3 hex cap bolt 12mm,N/A,836,100,https://amzn.to/48CGa5Y
|
||||||
|
M3 sliding t slot nut,N/A,599,105,https://amzn.to/48GRrSU
|
||||||
|
2020 Aluminum extrusion mm,N/A,7399,12200,https://amzn.to/418OicC
|
||||||
|
100RPM DC geared motor with encoder,N/A,1619,1,https://amzn.to/3UF707G
|
||||||
|
250RPM DC geared motor,JSX40-370,1499,1,https://amzn.to/3NWkcRL
|
||||||
|
ESP32 Dev board,N/A,1999,3,https://amzn.to/3NXCvGj
|
||||||
|
L298N Motor driver module,N/A,1149,4,https://amzn.to/4ellssy
|
||||||
|
ESP32 GPIO breakout board,N/A,1199,2,https://amzn.to/3UFjpbO
|
||||||
|
M4 hex bolt 40mm,N/A,609,10,https://amzn.to/4ikpYL8
|
||||||
|
608-RS Ball Bearing,608-RS,1779,100,https://amzn.to/4fKxDA7
|
|
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
@ -1,94 +1,153 @@
|
||||||
#include "ContactPrinter.h"
|
#include "ContactPrinter.h"
|
||||||
|
|
||||||
ContactPrinter::ContactPrinter () {
|
ContactPrinter::ContactPrinter () {
|
||||||
SetDriveSpeed(drive_speed);
|
|
||||||
SetSpeedTakeup(takeup_speed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactPrinter::Setup () {
|
void ContactPrinter::Setup () {
|
||||||
pinMode(drive_pin, OUTPUT);
|
|
||||||
pinMode(takeup_picture_pin_cw, OUTPUT);
|
|
||||||
pinMode(takeup_picture_pin_ccw, OUTPUT);
|
|
||||||
pinMode(takeup_stock_pin_cw, OUTPUT);
|
|
||||||
pinMode(takeup_stock_pin_ccw, OUTPUT);
|
|
||||||
|
|
||||||
digitalWrite(drive_pin, LOW);
|
pinMode(takeup_pin_dir_a, OUTPUT);
|
||||||
digitalWrite(takeup_picture_pin_cw, LOW);
|
pinMode(takeup_pin_dir_b, OUTPUT);
|
||||||
digitalWrite(takeup_picture_pin_ccw, LOW);
|
|
||||||
digitalWrite(takeup_stock_pin_cw, LOW);
|
pinMode(start_button_pin, INPUT_PULLUP);
|
||||||
digitalWrite(takeup_stock_pin_ccw, LOW);
|
|
||||||
|
drive_motor.Setup();
|
||||||
|
lamp.Setup();
|
||||||
|
|
||||||
|
ledcSetup(takeup_pwm_channel, pwm_frequency, pwm_resolution);
|
||||||
|
Serial.print("Attaching pin ");
|
||||||
|
Serial.print(takeup_pin_enable);
|
||||||
|
Serial.print(" to ledc channel ");
|
||||||
|
Serial.print(takeup_pwm_channel);
|
||||||
|
Serial.println(" for takeup");
|
||||||
|
ledcAttachPin(takeup_pin_enable, takeup_pwm_channel);
|
||||||
|
ledcWrite(takeup_pwm_channel, takeup_pwm_duty_cycle);
|
||||||
|
|
||||||
|
digitalWrite(takeup_pin_dir_a, LOW);
|
||||||
|
digitalWrite(takeup_pin_dir_b, LOW);
|
||||||
|
|
||||||
|
SetupTakeup();
|
||||||
|
SetupDrive();
|
||||||
|
start_time = millis();
|
||||||
|
//lamp.On();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactPrinter::Start () {
|
void ContactPrinter::Start () {
|
||||||
RampTakeup(0, takeup_pwm, takeup_ramp_time);
|
Serial.println("Start()");
|
||||||
delay(100);
|
drive_motor.Start();
|
||||||
analogWrite(drive_pin, drive_pwm);
|
StartTakeup();
|
||||||
|
run_time = timer;
|
||||||
|
running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactPrinter::Stop () {
|
void ContactPrinter::Stop () {
|
||||||
analogWrite(drive_pin, 0);
|
Serial.println("Stop()");
|
||||||
delay(100);
|
lamp.Off();
|
||||||
RampTakeup(takeup_pwm, 0, takeup_ramp_time);
|
drive_motor.Stop();
|
||||||
|
StopTakeup();
|
||||||
|
run_time = timer;
|
||||||
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContactPrinter::SetDirectionTakeup(bool dir) {
|
||||||
|
takeup_dir = dir;
|
||||||
|
}
|
||||||
|
|
||||||
void ContactPrinter::SetSpeedTakeup(float speed) {
|
void ContactPrinter::SetSpeedTakeup(float speed) {
|
||||||
takeup_speed = speed;
|
takeup_speed = speed;
|
||||||
takeup_pwm = round(speed * 255);
|
takeup_pwm_duty_cycle = floor(speed * pwm_maximum);
|
||||||
|
Serial.print("Set takeup motors PWM = ");
|
||||||
|
Serial.print(takeup_pwm_duty_cycle);
|
||||||
|
Serial.print(" / ");
|
||||||
|
Serial.println(pwm_maximum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactPrinter::SetSpeedDrive(float speed) {
|
void ContactPrinter::StartTakeup () {
|
||||||
drive_speed = speed;
|
ledcWrite(takeup_pwm_channel, takeup_pwm_duty_cycle);
|
||||||
drive_pwm = round(speed * 255);
|
if (takeup_dir) {
|
||||||
|
digitalWrite(takeup_pin_dir_a, LOW);
|
||||||
|
digitalWrite(takeup_pin_dir_b, HIGH);
|
||||||
|
} else {
|
||||||
|
digitalWrite(takeup_pin_dir_a, HIGH);
|
||||||
|
digitalWrite(takeup_pin_dir_b, LOW);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactPrinter::SetDirectionStock(bool clockwise) {
|
void ContactPrinter::StopTakeup() {
|
||||||
takeup_stock_cw = clockwise;
|
digitalWrite(takeup_pin_dir_a, LOW);
|
||||||
|
digitalWrite(takeup_pin_dir_b, LOW);
|
||||||
|
ledcWrite(takeup_pwm_channel, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactPrinter::SetDirectionPicture(bool clockwise) {
|
void ContactPrinter::SetupTakeup () {
|
||||||
takeup_picture_cw = clockwise;
|
SetDirectionTakeup(true);
|
||||||
|
SetSpeedTakeup(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactPrinter::SetupDrive() {
|
||||||
|
//drive_motor.SetSpeed(speed);
|
||||||
|
//drive_motor.SetPWM(247);
|
||||||
|
drive_motor.SetLoad(load);
|
||||||
|
drive_motor.SetFPS(18.0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//linear
|
//linear
|
||||||
void ContactPrinter::RampTakeup(uint16_t start, uint16_t end, uint16_t time) {
|
void ContactPrinter::RampTakeup(uint16_t start_pwm, uint16_t end_pwm, uint16_t time) {
|
||||||
uint16_t steps = abs(start - end);
|
takeup_ramp_steps = abs(start_pwm - end_pwm);
|
||||||
uint16_t step = round(time / steps);
|
takeup_ramp_step = round(time / takeup_ramp_steps);
|
||||||
uint16_t pwm = start;
|
takeup_pwm_duty_cycle = start_pwm;
|
||||||
uint8_t takeup_picture_pin;
|
takeup_ramp_dir = end_pwm < start_pwm;
|
||||||
uint8_t takeup_stock_pin;
|
takeup_ramp_current_step = 0;
|
||||||
bool dir = end < start;
|
takeup_ramping = true;
|
||||||
|
|
||||||
if (takeup_picture_cw) {
|
for (uint16_t i = 0; i < takeup_ramp_steps; i++) {
|
||||||
takeup_picture_pin = takeup_picture_pin_cw;
|
if (takeup_pwm_duty_cycle <= 0 || takeup_pwm_duty_cycle >= pwm_maximum) {
|
||||||
analogWrite(takeup_picture_pin_ccw, 0);
|
|
||||||
} else {
|
|
||||||
takeup_picture_pin = takeup_picture_pin_ccw;
|
|
||||||
analogWrite(takeup_picture_pin_cw, 0);
|
|
||||||
}
|
|
||||||
if (takeup_stock_cw) {
|
|
||||||
takeup_stock_pin = takeup_stock_pin_cw;
|
|
||||||
analogWrite(takeup_stock_pin_ccw, 0);
|
|
||||||
} else {
|
|
||||||
takeup_stock_pin = takeup_stock_pin_cw;
|
|
||||||
analogWrite(takeup_stock_pin_cw, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < steps; i++) {
|
|
||||||
if (pwm <= 0 || pwm >= 256) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
analogWrite(takeup_picture_pin, pwm);
|
ledcWrite(takeup_pwm_channel, takeup_pwm_duty_cycle);
|
||||||
analogWrite(takeup_stock_pin, pwm);
|
delay(takeup_ramp_step);
|
||||||
delay(step);
|
if (takeup_ramp_dir) {
|
||||||
if (dir) {
|
takeup_pwm_duty_cycle++;
|
||||||
pwm++;
|
|
||||||
} else {
|
} else {
|
||||||
pwm--;
|
takeup_pwm_duty_cycle--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
takeup_ramping = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ContactPrinter::ButtonLoop () {
|
||||||
|
if (!running && timer >= run_time + button_delay && digitalRead(start_button_pin) == LOW) {
|
||||||
|
Start();
|
||||||
|
} else if (running && timer >= run_time + button_delay && digitalRead(start_button_pin) == LOW) {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContactPrinter::IsRunning () {
|
bool ContactPrinter::IsRunning () {
|
||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContactPrinter::Loop () {
|
||||||
|
int32_t frame;
|
||||||
|
timer = millis();
|
||||||
|
if (initialized) {
|
||||||
|
ButtonLoop();
|
||||||
|
if (running) {
|
||||||
|
drive_motor.Loop();
|
||||||
|
frame = drive_motor.GetFrames();
|
||||||
|
if (!lamp.IsOn() && start_lamp > 0 && frame >= start_lamp) {
|
||||||
|
lamp.On();
|
||||||
|
}
|
||||||
|
if (lamp.IsOn() && stop_lamp > 0 && frame >= stop_lamp) {
|
||||||
|
lamp.Off();
|
||||||
|
}
|
||||||
|
if (stop_after > 0 && frame >= stop_after) {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (timer >= start_time + 100) {
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,47 +2,91 @@
|
||||||
#define CONTACT_PRINTER
|
#define CONTACT_PRINTER
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "DriveMotor.h"
|
||||||
|
#include "Lamp.h"
|
||||||
|
#include "WebGUI.h"
|
||||||
|
|
||||||
class ContactPrinter {
|
class ContactPrinter {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
//use default drive motor pins
|
||||||
|
DriveMotor drive_motor;
|
||||||
|
Lamp lamp;
|
||||||
|
|
||||||
const uint16_t serial_delay = 5;
|
const uint16_t serial_delay = 5;
|
||||||
const uint16_t baud = 57600;
|
const uint16_t baud = 115200;
|
||||||
const uint8_t drive_pin = 7;
|
|
||||||
const uint8_t takeup_picture_pin_cw = 8;
|
|
||||||
const uint8_t takeup_picture_pin_ccw = 9;
|
|
||||||
const uint8_t takeup_stock_pin_cw = 10;
|
|
||||||
const uint8_t takeup_stock_pin_ccw = 11;
|
|
||||||
|
|
||||||
volatile float drive_speed = 1f;
|
/* PINS */
|
||||||
volatile float takeup_speed = 1f;
|
const uint8_t takeup_pin_enable = 21;
|
||||||
|
const uint8_t takeup_pin_dir_a = 22;
|
||||||
|
const uint8_t takeup_pin_dir_b = 23;
|
||||||
|
|
||||||
volatile uint16_t drive_pwm;
|
const uint8_t start_button_pin = 15;
|
||||||
volatile uint16_t takeup_pwm;
|
|
||||||
|
|
||||||
volatile bool takeup_picture_cw = false;
|
/* MOTOR PWM */
|
||||||
volatile bool takeup_picture_ccw = true;
|
const uint32_t pwm_frequency = 5000;
|
||||||
|
const uint8_t takeup_pwm_channel = 1;
|
||||||
|
const uint8_t pwm_resolution = 8;
|
||||||
|
const uint16_t pwm_maximum = 255; //8 = 255, 10 = 1024, 16 = 65535
|
||||||
|
|
||||||
volatile uint16_t takeup_ramp_time = 500;
|
/* BUTTONS */
|
||||||
|
const uint16_t button_delay = 500;
|
||||||
|
|
||||||
|
/* MEMORY */
|
||||||
|
|
||||||
|
volatile long timer = 0;
|
||||||
|
volatile long start_time = 0;
|
||||||
|
volatile long run_time = 0;
|
||||||
|
|
||||||
|
volatile float drive_speed = 1.0; //CHANGE
|
||||||
|
volatile float takeup_speed = 1.0; //percentage of max PWM
|
||||||
|
|
||||||
|
volatile uint16_t takeup_pwm_duty_cycle = 0;
|
||||||
|
volatile uint16_t takeup_ramp_steps = 0; //# of steps
|
||||||
|
volatile uint16_t takeup_ramp_step = 0; //length of step (ms)
|
||||||
|
volatile boolean takeup_ramp_dir = true; //true = up, false = down
|
||||||
|
volatile uint16_t takeup_ramp_time = 500; //default ramp time (ms)
|
||||||
|
volatile long takeup_ramp_start = 0; //time to start ramping
|
||||||
|
volatile long takeup_ramp_current_step = 0;
|
||||||
|
volatile long takeup_ramp_next_step_start = 0;
|
||||||
|
volatile boolean takeup_ramping = false;
|
||||||
|
|
||||||
|
volatile uint8_t load = 2; //0 = no load, 1 = single thread, 2 = dual thread
|
||||||
|
|
||||||
|
volatile uint32_t start_lamp = 24;
|
||||||
|
volatile uint32_t stop_lamp = -1;
|
||||||
|
volatile uint32_t stop_after = -1;
|
||||||
|
|
||||||
|
volatile bool takeup_dir = true;
|
||||||
|
volatile bool initialized = false;
|
||||||
volatile bool running = false;
|
volatile bool running = false;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ContactPrinter();
|
ContactPrinter();
|
||||||
|
|
||||||
void Setup();
|
void Setup();
|
||||||
|
void Loop();
|
||||||
void Start();
|
void Start();
|
||||||
void Stop();
|
void Stop();
|
||||||
void SetSpeedTakeup(float speed);
|
void SetSpeedTakeup(float speed); //percent
|
||||||
void SetSpeedDrive(float speed);
|
void SetupDrive();
|
||||||
void SetDirectionStock(bool clockwise);
|
void SetupTakeup();
|
||||||
void SetDirectionPicture(bool clockwise);
|
void SetDirectionTakeup(bool dir);
|
||||||
|
|
||||||
|
void StartTakeup();
|
||||||
|
void StopTakeup();
|
||||||
|
void EnableTakeup();
|
||||||
|
|
||||||
void RampTakeup(uint16_t start, uint16_t end, uint16_t time);
|
void RampTakeup(uint16_t start, uint16_t end, uint16_t time);
|
||||||
|
void RampTakeupLoop();
|
||||||
|
|
||||||
|
void ButtonLoop();
|
||||||
|
|
||||||
bool IsRunning ();
|
bool IsRunning ();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
#include "DriveMotor.h"
|
||||||
|
|
||||||
|
DriveMotor::DriveMotor () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
void DriveMotor::Setup () {
|
||||||
|
pinMode(enable_pin, OUTPUT);
|
||||||
|
pinMode(forward_pin, OUTPUT);
|
||||||
|
pinMode(backward_pin, OUTPUT);
|
||||||
|
|
||||||
|
pinMode(encoder_a_pin, INPUT);
|
||||||
|
pinMode(encoder_b_pin, INPUT);
|
||||||
|
|
||||||
|
ledcSetup(pwm_channel, pwm_frequency, pwm_resolution);
|
||||||
|
Serial.print("Attaching pin ");
|
||||||
|
Serial.print(enable_pin);
|
||||||
|
Serial.print(" to ledc channel ");
|
||||||
|
Serial.print(pwm_channel);
|
||||||
|
Serial.println(" for drive");
|
||||||
|
ledcAttachPin(enable_pin, pwm_channel);
|
||||||
|
ledcWrite(pwm_channel, pwm_duty_cycle);
|
||||||
|
|
||||||
|
digitalWrite(forward_pin, LOW);
|
||||||
|
digitalWrite(backward_pin, LOW);
|
||||||
|
|
||||||
|
attachInterrupt(digitalPinToInterrupt(encoder_b_pin), ReadEncoder, RISING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::Start() {
|
||||||
|
pulses = 0;
|
||||||
|
fps = 0.0;
|
||||||
|
fps_max = -1.0;
|
||||||
|
fps_min = 100000.0;
|
||||||
|
fps_avg = -1.0;
|
||||||
|
rpm = 0.0;
|
||||||
|
rpm_max = -1.0;
|
||||||
|
rpm_min = 100000.0;
|
||||||
|
rpm_avg = -1.0;
|
||||||
|
start_frame = frames;
|
||||||
|
start_rotation = rotations;
|
||||||
|
start_time = millis();
|
||||||
|
ledcWrite(pwm_channel, pwm_duty_cycle);
|
||||||
|
digitalWrite(forward_pin, HIGH);
|
||||||
|
digitalWrite(backward_pin, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::Stop() {;
|
||||||
|
digitalWrite(forward_pin, LOW);
|
||||||
|
digitalWrite(backward_pin, LOW);
|
||||||
|
ledcWrite(pwm_channel, 0);
|
||||||
|
Report();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::SetLoad(uint8_t loadInt) {
|
||||||
|
load = loadInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::SetSpeed(float speed) {
|
||||||
|
pwm_duty_cycle = floor(pwm_maximum * speed);
|
||||||
|
Serial.print("Set drive motor PWM = ");
|
||||||
|
Serial.print(pwm_duty_cycle);
|
||||||
|
Serial.print(" / ");
|
||||||
|
Serial.println(pwm_maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::SetPWM(uint32_t pwm) {
|
||||||
|
pwm_duty_cycle = pwm;
|
||||||
|
Serial.print("Set drive motor PWM = ");
|
||||||
|
Serial.print(pwm_duty_cycle);
|
||||||
|
Serial.print(" / ");
|
||||||
|
Serial.println(pwm_maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::SetFPS(float fps) {
|
||||||
|
target_fps = fps;
|
||||||
|
Serial.print("FPS = ");
|
||||||
|
Serial.println(fps);
|
||||||
|
SetPWM(EstimatePWMFromFPS(fps));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t DriveMotor::pulses = 0;
|
||||||
|
|
||||||
|
void DriveMotor::ReadEncoder () {
|
||||||
|
int b = digitalRead(encoder_b_pin);
|
||||||
|
if (b > 0) {
|
||||||
|
pulses++;
|
||||||
|
} else {
|
||||||
|
pulses--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriveMotor::Loop () {
|
||||||
|
timer = millis();
|
||||||
|
//monitor speed
|
||||||
|
frames = (int32_t) floor((float) pulses / pulses_per_frame);
|
||||||
|
if (frames != last_frame) {
|
||||||
|
last_frame = frames;
|
||||||
|
fps = CalculateFPS(timer - start_time, frames - start_frame);
|
||||||
|
if (fps < fps_min) { fps_min = fps; }
|
||||||
|
if (fps > fps_max) { fps_max = fps; }
|
||||||
|
if (fps_avg < 0.0) {
|
||||||
|
fps_avg = fps;
|
||||||
|
} else {
|
||||||
|
fps_avg = (fps_avg + fps) / 2.0;
|
||||||
|
}
|
||||||
|
Serial.print("Frame: ");
|
||||||
|
Serial.println(frames);
|
||||||
|
}
|
||||||
|
rotations = (int32_t) floor((float) pulses / (float) pulses_per_rotation);
|
||||||
|
if (rotations != last_rotation) {
|
||||||
|
last_rotation = rotations;
|
||||||
|
//correction
|
||||||
|
frames = rotations * frames_per_rotation;
|
||||||
|
rpm = CalculateRPM(timer - start_time, rotations - start_rotation);
|
||||||
|
if (rpm < rpm_min) { rpm_min = rpm; }
|
||||||
|
if (rpm > rpm_max) { rpm_max = rpm; }
|
||||||
|
if (rpm_avg < 0.0) {
|
||||||
|
rpm_avg = rpm;
|
||||||
|
} else {
|
||||||
|
rpm_avg = (rpm_avg + rpm) / 2.0;
|
||||||
|
}
|
||||||
|
Serial.print("Rotation: ");
|
||||||
|
Serial.println(rotations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DriveMotor::Report () {
|
||||||
|
fps = CalculateFPS(timer - start_time, frames - start_frame);
|
||||||
|
rpm = CalculateRPM(timer - start_time, rotations - start_rotation);
|
||||||
|
Serial.print("RPM ");
|
||||||
|
Serial.println(rpm);
|
||||||
|
Serial.print("RPM avg: ");
|
||||||
|
Serial.println(rpm_avg);
|
||||||
|
Serial.print("RPM min: ");
|
||||||
|
Serial.println(rpm_min);
|
||||||
|
Serial.print("RPM max: ");
|
||||||
|
Serial.println(rpm_max);
|
||||||
|
|
||||||
|
Serial.print("FPS ");
|
||||||
|
Serial.println(fps);
|
||||||
|
Serial.print("FPS avg: ");
|
||||||
|
Serial.println(fps_avg);
|
||||||
|
Serial.print("FPS min: ");
|
||||||
|
Serial.println(fps_min);
|
||||||
|
Serial.print("FPS max: ");
|
||||||
|
Serial.println(fps_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper methods */
|
||||||
|
|
||||||
|
float DriveMotor::CalculateFPS (long time_length, uint32_t frames) {
|
||||||
|
return 1000.0 / ((float) time_length / (float) frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
float DriveMotor::CalculateRPM (long time_length, uint32_t rotations) {
|
||||||
|
return 60000.0 / ((float) time_length / (float) rotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t DriveMotor::GetFrames () {
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t DriveMotor::GetRotations () {
|
||||||
|
return rotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
float DriveMotor::FloatMap (float x, float in_min, float in_max, float out_min, float out_max) {
|
||||||
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t DriveMotor::EstimatePWMFromFPS (float fps) {
|
||||||
|
float min, max;
|
||||||
|
float max_pwm = pwm_range[0];
|
||||||
|
float min_pwm = pwm_range[1];
|
||||||
|
switch (load) {
|
||||||
|
case 0 :
|
||||||
|
max = load_none[0];
|
||||||
|
min = load_none[1];
|
||||||
|
break;
|
||||||
|
case 1 :
|
||||||
|
max = load_one[0];
|
||||||
|
min = load_one[1];
|
||||||
|
break;
|
||||||
|
case 2 :
|
||||||
|
max = load_two[0];
|
||||||
|
min = load_two[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (uint16_t) floor(FloatMap(fps, min, max, (float) min_pwm, (float) max_pwm));
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
#ifndef DRIVE_MOTOR
|
||||||
|
#define DRIVE_MOTOR
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class DriveMotor {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
//defaults are for EPS32 dev board
|
||||||
|
static const uint8_t enable_pin = 26;
|
||||||
|
static const uint8_t forward_pin = 27; //Clockwise
|
||||||
|
static const uint8_t backward_pin = 14; //Counter-clockwise
|
||||||
|
static const uint8_t encoder_a_pin = 33;
|
||||||
|
static const uint8_t encoder_b_pin = 25;
|
||||||
|
|
||||||
|
const uint32_t pwm_frequency = 5000;
|
||||||
|
const uint8_t pwm_channel = 0;
|
||||||
|
const uint8_t pwm_resolution = 8;
|
||||||
|
const uint16_t pwm_maximum = 255; //8 = 255, 10 = 1024, 16 = 65535
|
||||||
|
|
||||||
|
const uint8_t ppr = 11;
|
||||||
|
const float ratio = 187.0 / 3.0;
|
||||||
|
const uint32_t pulses_per_rotation = (int) round((float) ppr * ratio);
|
||||||
|
const uint8_t frames_per_rotation = 18;
|
||||||
|
const float pulses_per_frame = (float) pulses_per_rotation / (float) frames_per_rotation;
|
||||||
|
|
||||||
|
//pwm ranges for mapping to estimated fps
|
||||||
|
const uint16_t pwm_range[2] = { 255, 210 };
|
||||||
|
const float load_none[2] = { 25.4, 16.75 };
|
||||||
|
const float load_one [2] = { 24.8, 16.5 };
|
||||||
|
const float load_two [2] = { 21.9, 13.4 };
|
||||||
|
|
||||||
|
volatile uint8_t load = 2;
|
||||||
|
|
||||||
|
volatile uint16_t pwm_duty_cycle = 0;
|
||||||
|
|
||||||
|
static int32_t pulses;
|
||||||
|
|
||||||
|
//state
|
||||||
|
volatile long timer = 0;
|
||||||
|
volatile long start_time = 0;
|
||||||
|
volatile int32_t start_rotation = 0;
|
||||||
|
volatile int32_t start_frame = 0;
|
||||||
|
|
||||||
|
//measure
|
||||||
|
volatile float rpm = 0.0;
|
||||||
|
volatile float rpm_max = -1.0;
|
||||||
|
volatile float rpm_min = 100000.0;
|
||||||
|
volatile float rpm_avg = -1.0;
|
||||||
|
volatile int32_t rotations = 0;
|
||||||
|
volatile int32_t last_rotation = 0;
|
||||||
|
|
||||||
|
volatile float fps = 0.0;
|
||||||
|
volatile float fps_max = -1.0;
|
||||||
|
volatile float fps_min = 100000.0;
|
||||||
|
volatile float fps_avg = -1.0;
|
||||||
|
volatile int32_t frames = 0;
|
||||||
|
volatile int32_t last_frame = 0;
|
||||||
|
|
||||||
|
//target
|
||||||
|
volatile float target_fps = 0.0;
|
||||||
|
volatile float target_rpm = 0.0;
|
||||||
|
|
||||||
|
volatile float test[1000];
|
||||||
|
|
||||||
|
|
||||||
|
float CalculateFPS (long time_length, uint32_t frames);
|
||||||
|
float CalculateRPM (long time_length, uint32_t rotations);
|
||||||
|
float FloatMap(float x, float in_min, float in_max, float out_min, float out_max);
|
||||||
|
uint16_t EstimatePWMFromFPS(float);
|
||||||
|
void Report();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
DriveMotor();
|
||||||
|
void Setup();
|
||||||
|
void Loop();
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
void SetLoad(uint8_t loadInt);
|
||||||
|
void SetSpeed(float speed);
|
||||||
|
void SetPWM(uint32_t pwm);
|
||||||
|
void SetFPS(float fps);
|
||||||
|
|
||||||
|
int32_t GetFrames();
|
||||||
|
int32_t GetRotations();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
static void ReadEncoder();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,26 @@
|
||||||
|
#include "Lamp.h"
|
||||||
|
|
||||||
|
Lamp::Lamp () {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lamp::Setup() {
|
||||||
|
pinMode(lamp_pin_a, OUTPUT);
|
||||||
|
digitalWrite(lamp_pin_a, LOW);
|
||||||
|
Serial.print("Simple white LED lamp on pin: ");
|
||||||
|
Serial.println(lamp_pin_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lamp::On () {
|
||||||
|
digitalWrite(lamp_pin_a, HIGH);
|
||||||
|
on = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lamp::Off () {
|
||||||
|
digitalWrite(lamp_pin_a, LOW);
|
||||||
|
on = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean Lamp::IsOn () {
|
||||||
|
return on;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef LAMP
|
||||||
|
#define LAMP
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class Lamp {
|
||||||
|
private:
|
||||||
|
const uint8_t lamp_pin_a = 32;
|
||||||
|
volatile boolean on = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Lamp();
|
||||||
|
void Setup();
|
||||||
|
void Loop();
|
||||||
|
void On();
|
||||||
|
void Off();
|
||||||
|
boolean IsOn();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,26 @@
|
||||||
|
#include "WebGUI.h"
|
||||||
|
|
||||||
|
void WebGUI::Setup () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebGUI::Loop () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebGUI::HandleNotFound() {
|
||||||
|
String res = "404 Not Found\n\n";
|
||||||
|
res += "URI: ";
|
||||||
|
res += server.uri();
|
||||||
|
res += "\nMethod: ";
|
||||||
|
res += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
res += "\nArguments: ";
|
||||||
|
res += server.args();
|
||||||
|
res += "\n";
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
res += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
server.send(404, "text/plain", res);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef WEBGUI
|
||||||
|
#define WEBGUI
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WiFiAP.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
class WebGUI {
|
||||||
|
private:
|
||||||
|
const char *AP_SSID = "contact_printer";
|
||||||
|
const char *DEFAULT_PASSWORD = "contact_printer";
|
||||||
|
const char *MDNS_NAME = "contact_printer";
|
||||||
|
//IPAddress wifiIP;
|
||||||
|
//String IP;
|
||||||
|
//DynamicJsonDocument postJSON(1024);
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void HandleNotFound();
|
||||||
|
|
||||||
|
public:
|
||||||
|
WebGUI();
|
||||||
|
void Setup();
|
||||||
|
void Loop();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,4 +1,38 @@
|
||||||
#include "ContactPrinter.h";
|
#include "ContactPrinter.h";
|
||||||
|
|
||||||
void setup () {}
|
#define VERSION "0.2.2"
|
||||||
void loop () {}
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Target Board: ESP32 Dev Kit
|
||||||
|
*
|
||||||
|
* Pins
|
||||||
|
*
|
||||||
|
* 21 Takeup Picture Enable - set duty rate
|
||||||
|
* 22 Takeup Direction A - Stock Clockwise, Picture Counter Clockwise
|
||||||
|
* 23 Takeup Direction B - Stoc k Counter Clockwise, Picture Clockwise
|
||||||
|
*
|
||||||
|
* 26 Drive Enable
|
||||||
|
* 27 Drive Forward (Clockwise)
|
||||||
|
* 14 Drive Backward (Counter Clockwise)
|
||||||
|
* 33 Drive Encoder A
|
||||||
|
* 25 Drive Encoder B
|
||||||
|
*
|
||||||
|
* 15 Start Button
|
||||||
|
*
|
||||||
|
* 32 Lamp
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
ContactPrinter contact_printer;
|
||||||
|
|
||||||
|
void setup () {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.print("contact_printer v");
|
||||||
|
Serial.println(VERSION);
|
||||||
|
contact_printer.Setup();
|
||||||
|
}
|
||||||
|
void loop () {
|
||||||
|
contact_printer.Loop();
|
||||||
|
}
|
||||||
|
|
|
@ -6,15 +6,20 @@
|
||||||
*********/
|
*********/
|
||||||
|
|
||||||
// Motor A
|
// Motor A
|
||||||
int motor1Pin1 = 27;
|
//int motor1Pin1 = 14;
|
||||||
int motor1Pin2 = 26;
|
//int motor1Pin2 = 27;
|
||||||
int enable1Pin = 14;
|
//int enable1Pin = 26;
|
||||||
|
|
||||||
|
// Motor B
|
||||||
|
int motor1Pin1 = 5;
|
||||||
|
int motor1Pin2 = 18;
|
||||||
|
int enable1Pin = 19;
|
||||||
|
|
||||||
// Setting PWM properties
|
// Setting PWM properties
|
||||||
const int freq = 30000;
|
const int freq = 30000;
|
||||||
const int pwmChannel = 0;
|
const int pwmChannel = 0;
|
||||||
const int resolution = 8;
|
const int resolution = 8;
|
||||||
int dutyCycle = 200;
|
int dutyCycle = 250;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
// sets the pins as outputs:
|
// sets the pins as outputs:
|
||||||
|
@ -28,6 +33,8 @@ void setup() {
|
||||||
// attach the channel to the GPIO to be controlled
|
// attach the channel to the GPIO to be controlled
|
||||||
ledcAttachPin(enable1Pin, pwmChannel);
|
ledcAttachPin(enable1Pin, pwmChannel);
|
||||||
|
|
||||||
|
ledcWrite(pwmChannel, dutyCycle);
|
||||||
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
// testing
|
// testing
|
||||||
|
@ -59,15 +66,4 @@ void loop() {
|
||||||
digitalWrite(motor1Pin2, LOW);
|
digitalWrite(motor1Pin2, LOW);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
||||||
// Move DC motor forward with increasing speed
|
|
||||||
digitalWrite(motor1Pin1, HIGH);
|
|
||||||
digitalWrite(motor1Pin2, LOW);
|
|
||||||
while (dutyCycle <= 255){
|
|
||||||
ledcWrite(pwmChannel, dutyCycle);
|
|
||||||
Serial.print("Forward with duty cycle: ");
|
|
||||||
Serial.println(dutyCycle);
|
|
||||||
dutyCycle = dutyCycle + 5;
|
|
||||||
delay(500);
|
|
||||||
}
|
|
||||||
dutyCycle = 200;
|
|
||||||
}
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
|
||||||
|
#define PIN 15
|
||||||
|
#define NUM 1
|
||||||
|
|
||||||
|
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM, PIN, NEO_GRB + NEO_KHZ800);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
pixels.begin();
|
||||||
|
pixels.setBrightness(255);
|
||||||
|
pixels.fill(0xFF0000);
|
||||||
|
pixels.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop () {
|
||||||
|
pixels.show();
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/** ______________
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* | TIP120 |
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* --------------
|
||||||
|
* || || ||
|
||||||
|
* || || ||
|
||||||
|
* || || ||
|
||||||
|
* || || ||
|
||||||
|
* 2.2K
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
* \/
|
||||||
|
* 8 pin
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
int signalPin = 8;
|
||||||
|
|
||||||
|
void setup () {
|
||||||
|
pinMode(signalPin, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop () {
|
||||||
|
digitalWrite(signalPin, HIGH);
|
||||||
|
delay(1000);
|
||||||
|
digitalWrite(signalPin, LOW);
|
||||||
|
delay(2000);
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
/***
|
||||||
|
*
|
||||||
|
* 100RPM 12VDC Worm Gear Motor w/ encoder
|
||||||
|
* SKU - JGY-370
|
||||||
|
* DC12V100RPM - SKU-GS00127-05
|
||||||
|
*
|
||||||
|
* Gear ratio: 40:1(?) 60:1(?)
|
||||||
|
* 11 PPR Encoder - (!)
|
||||||
|
*
|
||||||
|
* Red——Motor power + (exchange can control rotating and reversing)
|
||||||
|
* Black——Coding power- negative (3.3-5V) polarity cannot be wrong
|
||||||
|
* Yellow——Signal feedback
|
||||||
|
* Green——Signal feedback
|
||||||
|
* Blue——Coding power + positive(3.3-5V)polarity cannot be wrong
|
||||||
|
* White——Motor power - (exchange can control rotating and
|
||||||
|
*
|
||||||
|
***/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <util/atomic.h> // For the ATOMIC_BLOCK macro
|
||||||
|
|
||||||
|
#define ENCA 2 // YELLOW
|
||||||
|
#define ENCB 3 // WHITE
|
||||||
|
#define MOTORA 10
|
||||||
|
#define MOTORB 11
|
||||||
|
|
||||||
|
volatile int32_t posi = 0; // specify posi as volatile
|
||||||
|
const long maxTime = 100000;
|
||||||
|
const int32_t maxRotations = 10;
|
||||||
|
const int ppr = 11;
|
||||||
|
const float ratio = 187.0 / 3.0;
|
||||||
|
const int maxPulses = (int) round((float) ppr * ratio);
|
||||||
|
const int speed = 255;
|
||||||
|
const int framesPerRotation = 18;
|
||||||
|
const float pulsesPerFrame = (float) maxPulses / (float) framesPerRotation;
|
||||||
|
volatile long start = 0;
|
||||||
|
volatile long total;
|
||||||
|
volatile bool done = false;
|
||||||
|
volatile bool stop = false;
|
||||||
|
volatile int32_t incoming;
|
||||||
|
volatile float rpm;
|
||||||
|
volatile float fps;
|
||||||
|
volatile float fpsMax = -1.0;
|
||||||
|
volatile float fpsMin = 100000.0;
|
||||||
|
volatile int32_t rotations = 0;
|
||||||
|
volatile int32_t lastRotationPosition = 0;
|
||||||
|
volatile int32_t lastFramePosition = 0;
|
||||||
|
volatile int32_t frames = 0;
|
||||||
|
|
||||||
|
float calculateFPS (long timeLength, int frames) {
|
||||||
|
return 1000.0 / ((float) timeLength / (float) frames);
|
||||||
|
}
|
||||||
|
float calculateRPM (long rotationLength) {
|
||||||
|
return 60000.0 / (float) (rotationLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(57600);
|
||||||
|
Serial.flush();
|
||||||
|
pinMode(ENCA, INPUT);
|
||||||
|
pinMode(ENCB, INPUT);
|
||||||
|
pinMode(MOTORA, OUTPUT);
|
||||||
|
pinMode(MOTORB, OUTPUT);
|
||||||
|
|
||||||
|
attachInterrupt(digitalPinToInterrupt(ENCA), readEncoder,RISING);
|
||||||
|
|
||||||
|
Serial.println("Connected");
|
||||||
|
Serial.print("PPR: ");
|
||||||
|
Serial.println(ppr);
|
||||||
|
Serial.print("Ratio: ");
|
||||||
|
Serial.println(ratio);
|
||||||
|
Serial.print("Pulses: ");
|
||||||
|
Serial.println(maxPulses);
|
||||||
|
Serial.print("Frames per Rotation: ");
|
||||||
|
Serial.println(framesPerRotation);
|
||||||
|
Serial.print("Pulses per Frame: ");
|
||||||
|
Serial.println(pulsesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
int32_t pos;
|
||||||
|
// Read the position in an atomic block to avoid a potential
|
||||||
|
// misread if the interrupt coincides with this code running
|
||||||
|
// see: https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/
|
||||||
|
|
||||||
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||||
|
pos = posi;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotations = (int) floor((float) pos / (float) maxPulses);
|
||||||
|
if (rotations != lastRotationPosition) {
|
||||||
|
lastRotationPosition = rotations;
|
||||||
|
frames = rotations * framesPerRotation;
|
||||||
|
Serial.print("Rotations: ");
|
||||||
|
Serial.print(rotations);
|
||||||
|
Serial.print(" = ");
|
||||||
|
Serial.println(millis() - start);
|
||||||
|
}
|
||||||
|
frames = (int) floor((float) pos / pulsesPerFrame);
|
||||||
|
if (frames != lastFramePosition) {
|
||||||
|
lastFramePosition = frames;
|
||||||
|
total = millis() - start;
|
||||||
|
fps = calculateFPS(total, frames);
|
||||||
|
if (fps < 10000.0 && fps > fpsMax) {
|
||||||
|
fpsMax = fps;
|
||||||
|
}
|
||||||
|
if (fps < fpsMin) {
|
||||||
|
fpsMin = fps;
|
||||||
|
}
|
||||||
|
//Serial.print("Frames: ");
|
||||||
|
//Serial.print(frames);
|
||||||
|
//Serial.print(" = ");
|
||||||
|
//Serial.println(total);
|
||||||
|
//Serial.println(fps);
|
||||||
|
//Serial.print("@");
|
||||||
|
//Serial.println(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs(pos) >= maxPulses * maxRotations) {
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
if (start == -1) {
|
||||||
|
delay(1000);
|
||||||
|
analogWrite(MOTORA, speed);
|
||||||
|
digitalWrite(MOTORB, LOW);
|
||||||
|
start = millis();
|
||||||
|
rotations = 0;
|
||||||
|
} else if (stop && !done) {
|
||||||
|
digitalWrite(MOTORA, LOW);
|
||||||
|
digitalWrite(MOTORB, LOW);
|
||||||
|
//Serial.print("Final: ");
|
||||||
|
//Serial.println(pos);
|
||||||
|
Serial.print("Time: ");
|
||||||
|
total = millis() - start;
|
||||||
|
Serial.println(total);
|
||||||
|
rpm = calculateRPM(total) * (float) (rotations + 1);
|
||||||
|
fps = calculateFPS(total, frames);
|
||||||
|
Serial.print("RPM: ");
|
||||||
|
Serial.println(rpm);
|
||||||
|
Serial.print("FPS: ");
|
||||||
|
Serial.println(fps);
|
||||||
|
Serial.print(" Min: ");
|
||||||
|
Serial.println(fpsMin);
|
||||||
|
Serial.print(" Max: ");
|
||||||
|
Serial.println(fpsMax);
|
||||||
|
Serial.print("Rotations: ");
|
||||||
|
Serial.println(rotations);
|
||||||
|
Serial.print("Frames: ");
|
||||||
|
Serial.println(frames);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
if (Serial.available() > 0) {
|
||||||
|
incoming = Serial.read();
|
||||||
|
start = -1;
|
||||||
|
posi = 0;
|
||||||
|
done = false;
|
||||||
|
stop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void readEncoder(){
|
||||||
|
int b = digitalRead(ENCB);
|
||||||
|
if(b > 0){
|
||||||
|
posi++;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
posi--;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
/***
|
|
||||||
*
|
|
||||||
* 100RPM 12VDC Worm Gear Motor w/ encoder
|
|
||||||
* SKU - JGY-370
|
|
||||||
* DC12V100RPM - SKU-GS00127-05
|
|
||||||
*
|
|
||||||
* Gear ratio: 40:1(?) 60:1(?)
|
|
||||||
* 11 PPR Encoder - (!)
|
|
||||||
*
|
|
||||||
* Red——Motor power + (exchange can control rotating and reversing)
|
|
||||||
* Black——Coding power- negative (3.3-5V) polarity cannot be wrong
|
|
||||||
* Yellow——Signal feedback
|
|
||||||
* Green——Signal feedback
|
|
||||||
* Blue——Coding power + positive(3.3-5V)polarity cannot be wrong
|
|
||||||
* White——Motor power - (exchange can control rotating and
|
|
||||||
*
|
|
||||||
***/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include <util/atomic.h> // For the ATOMIC_BLOCK macro
|
|
||||||
|
|
||||||
#define ENCA 2 // YELLOW
|
|
||||||
#define ENCB 3 // WHITE
|
|
||||||
#define MOTORA 10
|
|
||||||
#define MOTORB 11
|
|
||||||
|
|
||||||
volatile int posi = 0; // specify posi as volatile
|
|
||||||
const long maxTime = 10000;
|
|
||||||
const int ppr = 11;
|
|
||||||
const float ratio = 62.0;
|
|
||||||
const int maxPulses = (int) round((float) ppr * ratio);
|
|
||||||
const int speed = 40;
|
|
||||||
volatile long start = 0;
|
|
||||||
volatile bool done = false;
|
|
||||||
volatile bool stop = false;
|
|
||||||
volatile int incoming;
|
|
||||||
volatile float rpm;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(57600);
|
|
||||||
Serial.flush();
|
|
||||||
pinMode(ENCA, INPUT);
|
|
||||||
pinMode(ENCB, INPUT);
|
|
||||||
pinMode(MOTORA, OUTPUT);
|
|
||||||
pinMode(MOTORB, OUTPUT);
|
|
||||||
|
|
||||||
attachInterrupt(digitalPinToInterrupt(ENCA), readEncoder,RISING);
|
|
||||||
|
|
||||||
Serial.println("Connected");
|
|
||||||
Serial.print("PPR: ");
|
|
||||||
Serial.println(ppr);
|
|
||||||
Serial.print("Ratio: ");
|
|
||||||
Serial.println(ratio);
|
|
||||||
Serial.print("Pulses: ");
|
|
||||||
Serial.println(maxPulses);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
int pos;
|
|
||||||
if (Serial.available() > 0) {
|
|
||||||
incoming = Serial.read();
|
|
||||||
start = -1;
|
|
||||||
posi = 0;
|
|
||||||
done = false;
|
|
||||||
stop = false;
|
|
||||||
}
|
|
||||||
// Read the position in an atomic block to avoid a potential
|
|
||||||
// misread if the interrupt coincides with this code running
|
|
||||||
// see: https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/
|
|
||||||
|
|
||||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
|
||||||
pos = posi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs(pos) == maxPulses) {
|
|
||||||
stop = true;
|
|
||||||
}
|
|
||||||
if (start == -1) {
|
|
||||||
delay(1000);
|
|
||||||
analogWrite(MOTORA, speed);
|
|
||||||
digitalWrite(MOTORB, LOW);
|
|
||||||
start = millis();
|
|
||||||
} else if ((stop || millis() - start >= maxTime) && !done) {
|
|
||||||
digitalWrite(MOTORA, LOW);
|
|
||||||
digitalWrite(MOTORB, LOW);
|
|
||||||
Serial.print("Final: ");
|
|
||||||
Serial.println(pos);
|
|
||||||
Serial.print("Time: ");
|
|
||||||
Serial.println(millis() - start);
|
|
||||||
rpm = (float) 60000 / (float) (millis() - start);
|
|
||||||
Serial.print("RPM: ");
|
|
||||||
Serial.println(rpm);
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void readEncoder(){
|
|
||||||
int b = digitalRead(ENCB);
|
|
||||||
if(b > 0){
|
|
||||||
posi++;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
posi--;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +1,22 @@
|
||||||
2020_tslot_insert
|
2020_tslot_insert
|
||||||
bearing_post_nut
|
bearing_post_nut
|
||||||
|
blank
|
||||||
corner_foot
|
corner_foot
|
||||||
daylight_spool_insert_reinforced
|
daylight_spool_insert_reinforced
|
||||||
daylight_spool_insert_reinforced_nut
|
daylight_spool_insert_reinforced_nut
|
||||||
|
electronics_panel
|
||||||
feed_panel_motor_mount
|
feed_panel_motor_mount
|
||||||
feed_panel_picture
|
feed_panel_picture
|
||||||
feed_panel_stock
|
feed_panel_stock
|
||||||
|
filter_carrier
|
||||||
full_gate
|
full_gate
|
||||||
|
gate_carrier
|
||||||
gate_holder
|
gate_holder
|
||||||
idle_roller_half_a
|
idle_roller_half_a
|
||||||
idle_roller_half_b
|
idle_roller_half_b
|
||||||
lamp_LEDs
|
|
||||||
lamp_cover
|
lamp_cover
|
||||||
lamp_dual
|
lamp_dual
|
||||||
|
lamp_LEDs
|
||||||
lamp_single
|
lamp_single
|
||||||
magnetic_coupling
|
magnetic_coupling
|
||||||
motor_controller_panel
|
motor_controller_panel
|
||||||
|
@ -22,6 +26,9 @@ slip_coupling
|
||||||
sound_gate
|
sound_gate
|
||||||
sprocketed_roller
|
sprocketed_roller
|
||||||
sprocketed_roller_invert
|
sprocketed_roller_invert
|
||||||
|
sprocketed_roller_invert_solid
|
||||||
|
sprocketed_roller_solid
|
||||||
|
sprocketed_wheel
|
||||||
super_gate
|
super_gate
|
||||||
takeup_panel_picture
|
takeup_panel_picture
|
||||||
takeup_panel_picture_motor_mount
|
takeup_panel_picture_motor_mount
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef LINEAR_REGRESSION
|
||||||
|
#define LINEAR_REGRESSION
|
||||||
|
|
||||||
|
class LinearRegression {
|
||||||
|
private:
|
||||||
|
uint32_t i;
|
||||||
|
public:
|
||||||
|
LinearRegression();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,493 @@
|
||||||
|
Connected
|
||||||
|
11
|
||||||
|
Ratio: 62.00
|
||||||
|
Pulses: 682
|
||||||
|
Frames per Rotation: 18
|
||||||
|
Pulses per Frame: 37.89
|
||||||
|
Connected
|
||||||
|
PPR: 11
|
||||||
|
Ratio: 62.00
|
||||||
|
Pulses: 682
|
||||||
|
Frames per Rotation: 18
|
||||||
|
Pulses per Frame: 37.89
|
||||||
|
Time: 7124
|
||||||
|
RPM: 92.64
|
||||||
|
FPS: 39577.78
|
||||||
|
Rotations: 11
|
||||||
|
Frames: 180
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Connected
|
||||||
|
PPR: 11
|
||||||
|
Ratio: 62.00
|
||||||
|
Pulses: 682
|
||||||
|
Frames per Rotation: 18
|
||||||
|
Pulses per Frame: 37.89
|
||||||
|
Connected
|
||||||
|
PPR: 11
|
||||||
|
Ratio: 62.00
|
||||||
|
Pulses: 682
|
||||||
|
Frames per Rotation: 18
|
||||||
|
Pulses per Frame: 37.89
|
||||||
|
Frames: 20 = 0
|
||||||
|
Frames: 21 = 12
|
||||||
|
Frames: 22 = 60
|
||||||
|
Frames: 23 = 107
|
||||||
|
Frames: 24 = 156
|
||||||
|
Frames: 25 = 203
|
||||||
|
Frames: 26 = 250
|
||||||
|
Frames: 27 = 296
|
||||||
|
Frames: 28 = 343
|
||||||
|
Frames: 29 = 389
|
||||||
|
Frames: 30 = 435
|
||||||
|
Frames: 31 = 480
|
||||||
|
Frames: 32 = 526
|
||||||
|
Frames: 33 = 572
|
||||||
|
Frames: 34 = 618
|
||||||
|
Frames: 35 = 664
|
||||||
|
Frames: 36 = 710
|
||||||
|
Frames: 37 = 757
|
||||||
|
Frames: 38 = 804
|
||||||
|
Frames: 39 = 853
|
||||||
|
Frames: 40 = 901
|
||||||
|
Frames: 41 = 950
|
||||||
|
Frames: 42 = 998
|
||||||
|
Frames: 43 = 1046
|
||||||
|
Frames: 44 = 1094
|
||||||
|
Frames: 45 = 1139
|
||||||
|
Frames: 46 = 1186
|
||||||
|
Frames: 47 = 1232
|
||||||
|
Frames: 48 = 1277
|
||||||
|
Frames: 49 = 1324
|
||||||
|
Frames: 50 = 1369
|
||||||
|
Frames: 51 = 1415
|
||||||
|
Frames: 52 = 1461
|
||||||
|
Frames: 53 = 1507
|
||||||
|
Frames: 54 = 1552
|
||||||
|
Frames: 55 = 1599
|
||||||
|
Frames: 56 = 1646
|
||||||
|
Frames: 57 = 1693
|
||||||
|
Frames: 58 = 1741
|
||||||
|
Frames: 59 = 1789
|
||||||
|
Frames: 60 = 1838
|
||||||
|
Frames: 61 = 1886
|
||||||
|
Frames: 62 = 1934
|
||||||
|
Frames: 63 = 1979
|
||||||
|
Frames: 64 = 2026
|
||||||
|
Frames: 65 = 2072
|
||||||
|
Frames: 66 = 2117
|
||||||
|
Frames: 67 = 2163
|
||||||
|
Frames: 68 = 2208
|
||||||
|
Frames: 69 = 2254
|
||||||
|
Frames: 70 = 2300
|
||||||
|
Frames: 71 = 2347
|
||||||
|
Frames: 72 = 2392
|
||||||
|
Frames: 73 = 2440
|
||||||
|
Frames: 74 = 2487
|
||||||
|
Frames: 75 = 2535
|
||||||
|
Frames: 76 = 2582
|
||||||
|
Frames: 77 = 2631
|
||||||
|
Frames: 78 = 2679
|
||||||
|
Frames: 79 = 2727
|
||||||
|
Frames: 80 = 2775
|
||||||
|
Frames: 81 = 2821
|
||||||
|
Frames: 82 = 2868
|
||||||
|
Frames: 83 = 2914
|
||||||
|
Frames: 84 = 2959
|
||||||
|
Frames: 85 = 3005
|
||||||
|
Frames: 86 = 3051
|
||||||
|
Frames: 87 = 3096
|
||||||
|
Frames: 88 = 3142
|
||||||
|
Frames: 89 = 3188
|
||||||
|
Frames: 90 = 3234
|
||||||
|
Frames: 91 = 3281
|
||||||
|
Frames: 92 = 3329
|
||||||
|
Frames: 93 = 3377
|
||||||
|
Frames: 94 = 3424
|
||||||
|
Frames: 95 = 3472
|
||||||
|
Frames: 96 = 3521
|
||||||
|
Frames: 97 = 3569
|
||||||
|
Frames: 98 = 3616
|
||||||
|
Frames: 99 = 3662
|
||||||
|
Frames: 100 = 3708
|
||||||
|
Frames: 101 = 3755
|
||||||
|
Frames: 102 = 3801
|
||||||
|
Frames: 103 = 3846
|
||||||
|
Frames: 104 = 3892
|
||||||
|
Frames: 105 = 3938
|
||||||
|
Frames: 106 = 3983
|
||||||
|
Frames: 107 = 4029
|
||||||
|
Frames: 108 = 4074
|
||||||
|
Frames: 109 = 4121
|
||||||
|
Frames: 110 = 4169
|
||||||
|
Frames: 111 = 4216
|
||||||
|
Frames: 112 = 4264
|
||||||
|
Frames: 113 = 4313
|
||||||
|
Frames: 114 = 4361
|
||||||
|
Frames: 115 = 4409
|
||||||
|
Frames: 116 = 4457
|
||||||
|
Frames: 117 = 4503
|
||||||
|
Frames: 118 = 4549
|
||||||
|
Frames: 119 = 4595
|
||||||
|
Frames: 120 = 4641
|
||||||
|
Frames: 121 = 4686
|
||||||
|
Frames: 122 = 4732
|
||||||
|
Frames: 123 = 4779
|
||||||
|
Frames: 124 = 4824
|
||||||
|
Frames: 125 = 4870
|
||||||
|
Frames: 126 = 4915
|
||||||
|
Frames: 127 = 4962
|
||||||
|
Frames: 128 = 5009
|
||||||
|
Frames: 129 = 5057
|
||||||
|
Frames: 130 = 5105
|
||||||
|
Frames: 131 = 5153
|
||||||
|
Frames: 132 = 5201
|
||||||
|
Frames: 133 = 5250
|
||||||
|
Frames: 134 = 5298
|
||||||
|
Frames: 135 = 5344
|
||||||
|
Frames: 136 = 5390
|
||||||
|
Frames: 137 = 5436
|
||||||
|
Frames: 138 = 5482
|
||||||
|
Frames: 139 = 5527
|
||||||
|
Frames: 140 = 5573
|
||||||
|
Frames: 141 = 5619
|
||||||
|
Frames: 142 = 5665
|
||||||
|
Frames: 143 = 5710
|
||||||
|
Frames: 144 = 5755
|
||||||
|
Frames: 145 = 5803
|
||||||
|
Frames: 146 = 5850
|
||||||
|
Frames: 147 = 5897
|
||||||
|
Frames: 148 = 5945
|
||||||
|
Frames: 149 = 5992
|
||||||
|
Frames: 150 = 6040
|
||||||
|
Frames: 151 = 6089
|
||||||
|
Frames: 152 = 6136
|
||||||
|
Frames: 153 = 6182
|
||||||
|
Frames: 154 = 6230
|
||||||
|
Frames: 155 = 6276
|
||||||
|
Frames: 156 = 6321
|
||||||
|
Frames: 157 = 6367
|
||||||
|
Frames: 158 = 6413
|
||||||
|
Frames: 159 = 6458
|
||||||
|
Frames: 160 = 6504
|
||||||
|
Frames: 161 = 6550
|
||||||
|
Frames: 162 = 6595
|
||||||
|
Frames: 163 = 6642
|
||||||
|
Frames: 164 = 6689
|
||||||
|
Frames: 165 = 6737
|
||||||
|
Frames: 166 = 6785
|
||||||
|
Frames: 167 = 6834
|
||||||
|
Frames: 168 = 6882
|
||||||
|
Frames: 169 = 6930
|
||||||
|
Frames: 170 = 6978
|
||||||
|
Frames: 171 = 7023
|
||||||
|
Frames: 172 = 7070
|
||||||
|
Frames: 173 = 7116
|
||||||
|
Frames: 174 = 7162
|
||||||
|
Frames: 175 = 7207
|
||||||
|
Frames: 176 = 7254
|
||||||
|
Frames: 177 = 7299
|
||||||
|
Frames: 178 = 7345
|
||||||
|
Frames: 179 = 7391
|
||||||
|
Frames: 180 = 7435
|
||||||
|
Time: 7436
|
||||||
|
RPM: 88.76
|
||||||
|
FPS: 24.21
|
||||||
|
Min: 24.21
|
||||||
|
Max: 1750.00
|
||||||
|
Rotations: 10
|
||||||
|
Frames: 180
|
||||||
|
-------------------------------
|
||||||
|
FPS avg
|
||||||
|
Connected
|
||||||
|
PPR: 11
|
||||||
|
Ratio: 62.00
|
||||||
|
Pulses: 682
|
||||||
|
Frames per Rotation: 18
|
||||||
|
Pulses per Frame: 37.89
|
||||||
|
Frames: 20 = 20000.00
|
||||||
|
Frames: 21 = 1500.00
|
||||||
|
Frames: 22 = 354.84
|
||||||
|
Frames: 23 = 209.09
|
||||||
|
Frames: 24 = 151.90
|
||||||
|
Frames: 25 = 120.77
|
||||||
|
Frames: 26 = 101.96
|
||||||
|
Frames: 27 = 89.70
|
||||||
|
Frames: 28 = 80.46
|
||||||
|
Frames: 29 = 73.60
|
||||||
|
Frames: 30 = 68.18
|
||||||
|
Frames: 31 = 63.92
|
||||||
|
Frames: 32 = 60.26
|
||||||
|
Frames: 33 = 57.19
|
||||||
|
Frames: 34 = 54.57
|
||||||
|
Frames: 35 = 52.24
|
||||||
|
Frames: 36 = 50.35
|
||||||
|
Frames: 37 = 48.49
|
||||||
|
Frames: 38 = 46.86
|
||||||
|
Frames: 39 = 45.45
|
||||||
|
Frames: 40 = 44.15
|
||||||
|
Frames: 41 = 42.93
|
||||||
|
Frames: 42 = 41.87
|
||||||
|
Frames: 43 = 40.87
|
||||||
|
Frames: 44 = 40.04
|
||||||
|
Frames: 45 = 39.27
|
||||||
|
Frames: 46 = 38.56
|
||||||
|
Frames: 47 = 37.93
|
||||||
|
Frames: 48 = 37.35
|
||||||
|
Frames: 49 = 36.81
|
||||||
|
Frames: 50 = 36.34
|
||||||
|
Frames: 51 = 35.86
|
||||||
|
Frames: 52 = 35.42
|
||||||
|
Frames: 53 = 35.01
|
||||||
|
Frames: 54 = 34.64
|
||||||
|
Frames: 55 = 34.25
|
||||||
|
Frames: 56 = 33.88
|
||||||
|
Frames: 57 = 33.51
|
||||||
|
Frames: 58 = 33.16
|
||||||
|
Frames: 59 = 32.83
|
||||||
|
Frames: 60 = 32.52
|
||||||
|
Frames: 61 = 32.21
|
||||||
|
Frames: 62 = 31.93
|
||||||
|
Frames: 63 = 31.69
|
||||||
|
Frames: 64 = 31.45
|
||||||
|
Frames: 65 = 31.22
|
||||||
|
Frames: 66 = 31.02
|
||||||
|
Frames: 67 = 30.83
|
||||||
|
Frames: 68 = 30.64
|
||||||
|
Frames: 69 = 30.46
|
||||||
|
Frames: 70 = 30.29
|
||||||
|
Frames: 71 = 30.12
|
||||||
|
Frames: 72 = 29.98
|
||||||
|
Frames: 73 = 29.81
|
||||||
|
Frames: 74 = 29.65
|
||||||
|
Frames: 75 = 29.48
|
||||||
|
Frames: 76 = 29.33
|
||||||
|
Frames: 77 = 29.17
|
||||||
|
Frames: 78 = 29.02
|
||||||
|
Frames: 79 = 28.87
|
||||||
|
Frames: 80 = 28.74
|
||||||
|
Frames: 81 = 28.62
|
||||||
|
Frames: 82 = 28.50
|
||||||
|
Frames: 83 = 28.40
|
||||||
|
Frames: 84 = 28.30
|
||||||
|
Frames: 85 = 28.20
|
||||||
|
Frames: 86 = 28.10
|
||||||
|
Frames: 87 = 28.01
|
||||||
|
Frames: 88 = 27.91
|
||||||
|
Frames: 89 = 27.83
|
||||||
|
Frames: 90 = 27.74
|
||||||
|
Frames: 91 = 27.66
|
||||||
|
Frames: 92 = 27.56
|
||||||
|
Frames: 93 = 27.47
|
||||||
|
Frames: 94 = 27.38
|
||||||
|
Frames: 95 = 27.29
|
||||||
|
Frames: 96 = 27.20
|
||||||
|
Frames: 97 = 27.10
|
||||||
|
Frames: 98 = 27.02
|
||||||
|
Frames: 99 = 26.95
|
||||||
|
Frames: 100 = 26.88
|
||||||
|
Frames: 101 = 26.81
|
||||||
|
Frames: 102 = 26.76
|
||||||
|
Frames: 103 = 26.70
|
||||||
|
Frames: 104 = 26.64
|
||||||
|
Frames: 105 = 26.59
|
||||||
|
Frames: 106 = 26.53
|
||||||
|
Frames: 107 = 26.48
|
||||||
|
Frames: 108 = 26.44
|
||||||
|
Frames: 109 = 26.37
|
||||||
|
Frames: 110 = 26.32
|
||||||
|
Frames: 111 = 26.26
|
||||||
|
Frames: 112 = 26.20
|
||||||
|
Frames: 113 = 26.15
|
||||||
|
Frames: 114 = 26.09
|
||||||
|
Frames: 115 = 26.03
|
||||||
|
Frames: 116 = 25.97
|
||||||
|
Frames: 117 = 25.92
|
||||||
|
Frames: 118 = 25.88
|
||||||
|
Frames: 119 = 25.83
|
||||||
|
Frames: 120 = 25.80
|
||||||
|
Frames: 121 = 25.76
|
||||||
|
Frames: 122 = 25.72
|
||||||
|
Frames: 123 = 25.68
|
||||||
|
Frames: 124 = 25.65
|
||||||
|
Frames: 125 = 25.61
|
||||||
|
Frames: 126 = 25.58
|
||||||
|
Frames: 127 = 25.54
|
||||||
|
Frames: 128 = 25.50
|
||||||
|
Frames: 129 = 25.46
|
||||||
|
Frames: 130 = 25.42
|
||||||
|
Frames: 131 = 25.38
|
||||||
|
Frames: 132 = 25.34
|
||||||
|
Frames: 133 = 25.29
|
||||||
|
Frames: 134 = 25.25
|
||||||
|
Frames: 135 = 25.22
|
||||||
|
Frames: 136 = 25.19
|
||||||
|
Frames: 137 = 25.16
|
||||||
|
Frames: 138 = 25.13
|
||||||
|
Frames: 139 = 25.10
|
||||||
|
Frames: 140 = 25.07
|
||||||
|
Frames: 141 = 25.05
|
||||||
|
Frames: 142 = 25.02
|
||||||
|
Frames: 143 = 25.00
|
||||||
|
Frames: 144 = 24.97
|
||||||
|
Frames: 145 = 24.95
|
||||||
|
Frames: 146 = 24.92
|
||||||
|
Frames: 147 = 24.89
|
||||||
|
Frames: 148 = 24.86
|
||||||
|
Frames: 149 = 24.83
|
||||||
|
Frames: 150 = 24.79
|
||||||
|
Frames: 151 = 24.76
|
||||||
|
Frames: 152 = 24.73
|
||||||
|
Frames: 153 = 24.70
|
||||||
|
Frames: 154 = 24.68
|
||||||
|
Frames: 155 = 24.65
|
||||||
|
Frames: 156 = 24.63
|
||||||
|
Frames: 157 = 24.61
|
||||||
|
Frames: 158 = 24.60
|
||||||
|
Frames: 159 = 24.57
|
||||||
|
Frames: 160 = 24.55
|
||||||
|
Frames: 161 = 24.53
|
||||||
|
Frames: 162 = 24.52
|
||||||
|
Frames: 163 = 24.50
|
||||||
|
Frames: 164 = 24.48
|
||||||
|
Frames: 165 = 24.45
|
||||||
|
Frames: 166 = 24.43
|
||||||
|
Frames: 167 = 24.40
|
||||||
|
Frames: 168 = 24.38
|
||||||
|
Frames: 169 = 24.36
|
||||||
|
Frames: 170 = 24.33
|
||||||
|
Frames: 171 = 24.31
|
||||||
|
Frames: 172 = 24.29
|
||||||
|
Frames: 173 = 24.27
|
||||||
|
Frames: 174 = 24.25
|
||||||
|
Frames: 175 = 24.24
|
||||||
|
Frames: 176 = 24.23
|
||||||
|
Frames: 177 = 24.21
|
||||||
|
Frames: 178 = 24.20
|
||||||
|
Frames: 179 = 24.18
|
||||||
|
Frames: 180 = 24.17
|
||||||
|
Time: 7448
|
||||||
|
RPM: 88.61
|
||||||
|
FPS: 24.17
|
||||||
|
Min: 24.17
|
||||||
|
Max: 1500.00
|
||||||
|
Rotations: 10
|
||||||
|
Frames: 180
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
switch to 187/3
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Connected
|
||||||
|
PPR: 11
|
||||||
|
Ratio: 62.33
|
||||||
|
Pulses: 686
|
||||||
|
Frames per Rotation: 18
|
||||||
|
Pulses per Frame: 38.11
|
||||||
|
Rotations: 1 = 0
|
||||||
|
Rotations: 2 = 470
|
||||||
|
Rotations: 3 = 1189
|
||||||
|
Rotations: 4 = 1910
|
||||||
|
Rotations: 5 = 2631
|
||||||
|
Rotations: 6 = 3352
|
||||||
|
Rotations: 7 = 4071
|
||||||
|
Rotations: 8 = 4791
|
||||||
|
Rotations: 9 = 5511
|
||||||
|
Rotations: 10 = 6232
|
||||||
|
Rotations: 11 = 6950
|
||||||
|
Rotations: 12 = 7670
|
||||||
|
Rotations: 13 = 8388
|
||||||
|
Rotations: 14 = 9109
|
||||||
|
Rotations: 15 = 9828
|
||||||
|
Rotations: 16 = 10548
|
||||||
|
Rotations: 17 = 11267
|
||||||
|
Rotations: 18 = 11988
|
||||||
|
Rotations: 19 = 12707
|
||||||
|
Rotations: 20 = 13427
|
||||||
|
Rotations: 21 = 14145
|
||||||
|
Rotations: 22 = 14866
|
||||||
|
Rotations: 23 = 15585
|
||||||
|
Rotations: 24 = 16304
|
||||||
|
Rotations: 25 = 17021
|
||||||
|
Rotations: 26 = 17742
|
||||||
|
Rotations: 27 = 18461
|
||||||
|
Rotations: 28 = 19180
|
||||||
|
Rotations: 29 = 19898
|
||||||
|
Rotations: 30 = 20619
|
||||||
|
Rotations: 31 = 21336
|
||||||
|
Rotations: 32 = 22053
|
||||||
|
Rotations: 33 = 22772
|
||||||
|
Rotations: 34 = 23493
|
||||||
|
Rotations: 35 = 24212
|
||||||
|
Rotations: 36 = 24931
|
||||||
|
Rotations: 37 = 25651
|
||||||
|
Rotations: 38 = 26371
|
||||||
|
Rotations: 39 = 27088
|
||||||
|
Rotations: 40 = 27806
|
||||||
|
Rotations: 41 = 28527
|
||||||
|
Rotations: 42 = 29244
|
||||||
|
Rotations: 43 = 29963
|
||||||
|
Rotations: 44 = 30681
|
||||||
|
Rotations: 45 = 31401
|
||||||
|
Rotations: 46 = 32118
|
||||||
|
Rotations: 47 = 32837
|
||||||
|
Rotations: 48 = 33556
|
||||||
|
Rotations: 49 = 34277
|
||||||
|
Rotations: 50 = 34995
|
||||||
|
Rotations: 51 = 35712
|
||||||
|
Rotations: 52 = 36431
|
||||||
|
Rotations: 53 = 37149
|
||||||
|
Rotations: 54 = 37867
|
||||||
|
Rotations: 55 = 38586
|
||||||
|
Rotations: 56 = 39306
|
||||||
|
Rotations: 57 = 40024
|
||||||
|
Rotations: 58 = 40741
|
||||||
|
Rotations: 59 = 41461
|
||||||
|
Rotations: 60 = 42182
|
||||||
|
Rotations: 61 = 42899
|
||||||
|
Rotations: 62 = 43617
|
||||||
|
Rotations: 63 = 44338
|
||||||
|
Rotations: 64 = 45057
|
||||||
|
Rotations: 65 = 45775
|
||||||
|
Rotations: 66 = 46492
|
||||||
|
Rotations: 67 = 47213
|
||||||
|
Rotations: 68 = 47934
|
||||||
|
Rotations: 69 = 48655
|
||||||
|
Rotations: 70 = 49379
|
||||||
|
Rotations: 71 = 50100
|
||||||
|
Rotations: 72 = 50819
|
||||||
|
Rotations: 73 = 51538
|
||||||
|
Rotations: 74 = 52258
|
||||||
|
Rotations: 75 = 52976
|
||||||
|
Rotations: 76 = 53694
|
||||||
|
Rotations: 77 = 54414
|
||||||
|
Rotations: 78 = 55132
|
||||||
|
Rotations: 79 = 55849
|
||||||
|
Rotations: 80 = 56568
|
||||||
|
Rotations: 81 = 57286
|
||||||
|
Rotations: 82 = 58004
|
||||||
|
Rotations: 83 = 58723
|
||||||
|
Rotations: 84 = 59443
|
||||||
|
Rotations: 85 = 60161
|
||||||
|
Rotations: 86 = 60879
|
||||||
|
Rotations: 87 = 61600
|
||||||
|
Rotations: 88 = 62318
|
||||||
|
Rotations: 89 = 63035
|
||||||
|
Rotations: 90 = 63754
|
||||||
|
Rotations: 91 = 64471
|
||||||
|
Rotations: 92 = 65187
|
||||||
|
Rotations: 93 = 65903
|
||||||
|
Rotations: 94 = 66623
|
||||||
|
Rotations: 95 = 67340
|
||||||
|
Rotations: 96 = 68057
|
||||||
|
Rotations: 97 = 68774
|
||||||
|
Rotations: 98 = 69492
|
||||||
|
Rotations: 99 = 70209
|
||||||
|
Rotations: 100 = 70926
|
||||||
|
Time: 70927
|
||||||
|
RPM: 85.44
|
||||||
|
FPS: 25.38
|
||||||
|
Min: 25.38
|
||||||
|
Max: 961.54
|
||||||
|
Rotations: 100
|
||||||
|
Frames: 1800
|
|
@ -0,0 +1,90 @@
|
||||||
|
## Report on Residency at Filmwerkplaats
|
||||||
|
|
||||||
|
Matthew McWilliams
|
||||||
|
|
||||||
|
01/03/2024
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
### Dates
|
||||||
|
|
||||||
|
18/02/2024 to 25/02/2024
|
||||||
|
|
||||||
|
### Artist Biography
|
||||||
|
|
||||||
|
Matt McWilliams is an artist and inventor working on free, open-source and open-hardware tools for analog filmmakers and photographers.
|
||||||
|
He works as a software developer in robotics research in the greater Boston area.
|
||||||
|
His website, [sixteenmillimeter.com](https://sixteenmillimeter.com), hosts various models for 3D printing as well as software and design documents for machines for making analog cinema that are all freely-available to use and modify under [The MIT License](https://opensource.org/license/mit).
|
||||||
|
|
||||||
|
### Project Description
|
||||||
|
|
||||||
|
The purpose of this project is to develop a free, open-source and open-hardware desktop contact printer for 16mm film to allow artists the ability to make prints of their 16mm films from negatives and other sources that would otherwise be cost and time prohibitive to do at a small scale.
|
||||||
|
|
||||||
|
Contact printers are an essential piece of film lab equipment that performs a simple but important service for filmmakers.
|
||||||
|
By taking two (or more) pieces of film--one of them developed and the other undeveloped--and sandwiching them together at the emulsion (the "contact") a light projected behind the developed film will impart a negative of the image on the undeveloped film.
|
||||||
|
In the simple case of having a stand of developed negative black and white 16mm film, one can pair it with a piece of undeveloped black and white 16mm print stock and produce a positive image that can be used for projection.
|
||||||
|
|
||||||
|
This project aims to leverage advances in 3D printing, cheap-but-reliable geared DC motors and open-platform microcontrollers to build a small, affordable and reproducible contact printer which can be used as-is or adapted and modified to fit the purposes of individuals and groups who are working with particular analog production techniques.
|
||||||
|
|
||||||
|
### Relevancy and Quality of the Project
|
||||||
|
|
||||||
|
Many artist-run film labs and individual filmmakers who work with small gauge analog film do not have access to large-footprint commercial machines and lack the space and maintenance resources to keep them.
|
||||||
|
|
||||||
|
Since information about commercially-developed equipment is guarded, expensive or even lost to time, starting a project from the principles of free, open-source software (FOSS) gives it a better chance to exist in the open where others can freely access it and improve upon its development without the risk of violating patents or copyright.
|
||||||
|
|
||||||
|
By designing a desktop-scale contact printer, in the spirit of the Uhler Cine Printer, artists who make short films can utilize it for making tests, work prints and even short release-quality prints without the need to work with large amounts of film at a time.
|
||||||
|
Filmmakers who work with hand-processed film in a small darkroom can process and then print on a machine not much larger than a laptop.
|
||||||
|
|
||||||
|
### Developed Activities
|
||||||
|
|
||||||
|
The work completed during the residency addressed practical limitations in the current design and established a list of improvements that will be made in the project.
|
||||||
|
|
||||||
|
Issues with the overall tension on the film as it advances across the drive gear and past the lamp head were resolved by adding thin spacers between the takeup and feed spindles and the magnetic clutches which allow them to tension the film without potentially snapping it.
|
||||||
|
Improving the tension of the film allows the film to be contact printed without the frame lines pulling up or down and improves image stability.
|
||||||
|
|
||||||
|
The optimal operating speed of the contact printing process was determined through a series of tests under different loads (no film, one film and two films) and utilizing performance assessment code written as part of the residency.
|
||||||
|
By testing the drive motor of the contact printer at different drive speeds, I was able to establish minimum and maximum operating speeds that can be predicted and monitored by the software.
|
||||||
|
18 frames per second was settled on as the speed most likely to be stabilized while transporting both print stock and negative with the "100RPM" motor that was selected for the drive sprocketed roller.
|
||||||
|
Since a single strip of film, likely the print stock, is capable of running at 24 or 25 fps, this means additional capabilities are possible in future developments; namely that the contact printer could be used with the optical soundtrack recorder being developed in parallel by Hrvoje Spudić.
|
||||||
|
The 18 frame sprocketed roller, driven stably at 60RPM with two strips of film, can also be run at or above 24fps to record sound to stock in real-time.
|
||||||
|
|
||||||
|
Tests with Kodak 3302 black and white print stock and 3383 color print stock established a baseline for exposure that will be used to improve the lamp design.
|
||||||
|
Currently employing three standard 5mm cool white LED bulbs powered with 5V DC and with 330 Ohm resistance each, we know that a standard 216 diffusion gel and a .6 ND filter will produce a proper gray card density from a LAD test negative on black and white print stock (Kodak 3302).
|
||||||
|
Similarly, we were able to approximate the exposure and filters required to print from color negative onto color print stock although further testing and development will be needed.
|
||||||
|
|
||||||
|
Immediate next tasks are to address the lamp design, user interface issues and motor speed stability.
|
||||||
|
|
||||||
|
The lamp should be expanded from the current 3 LED design for increased exposure headroom and from solid color modules to RGB-controllable ones.
|
||||||
|
This will improve color printing and remove the need for as many physical color filters which ultimately reduce the amount of light needed to make accurate exposures on color print stock.
|
||||||
|
The lamp should also be made focus-able (the LED "bulb") and the diffusion.
|
||||||
|
Additionally, larger filters should be accepted, possibly allowing for the Bolex filter holder to be used.
|
||||||
|
All of this, as well as adding the ability to move the gate closer to the film
|
||||||
|
|
||||||
|
Since the behavior of the contact printer can only currently be altered with code changes, a UI beyond a start/stop button must be implemented.
|
||||||
|
Though the aims of this design are simple, contact printing analog film has many variables and being able to adjust them is one of the main advantages of using DIY projects such as these.
|
||||||
|
Exposing functionality to artists will make this tool more suitable for release-quality printing, providing the ability to do shot-to-shot timing and A/B roll printing.
|
||||||
|
|
||||||
|
Another area of potential improvement is the overall stability of the drive roller and therefore the resulting print.
|
||||||
|
A bearing of larger diameter or height could improve the rotational stability of the drive roller.
|
||||||
|
Also a redesign of the gate to put contact against the film could both improve the sharpness of the image and of the selective areas to be printed.
|
||||||
|
|
||||||
|
### Artist's Feedback
|
||||||
|
|
||||||
|
The amount of knowledge, expertise, capacity for experimentation and encouragement to work was unique and indispensable.
|
||||||
|
The only thing I would request or suggest is for more time at the residency, though the limitation is entirely with my schedule.
|
||||||
|
Having the ability to change code, strike print tests and process them without leaving the darkroom made progress possible at a rate previously not available to the project.
|
||||||
|
Without the support and resources provided by this residency, months or years would have been spent trying to discover what was during those seven days.
|
||||||
|
|
||||||
|
### Images
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|

|
|
@ -1 +1 @@
|
||||||
Subproject commit b499c2810117b5ca57014d5cb4219cf68b4c5c0f
|
Subproject commit e2eeb27f173d739a174c0d147bcb62a16859e2d9
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
||||||
|
include <../contact_printer.scad>;
|
||||||
|
|
||||||
|
PART="blank";
|
||||||
|
|
||||||
|
translate([0, -30, 4]) sprocketed_roller_invert_solid();
|
||||||
|
lamp_single([0, 10, 0 + 1]);
|
||||||
|
lamp_single_assembly([0, 10, 0 + 1]);
|
|
@ -0,0 +1,77 @@
|
||||||
|
//OpenSCAD representation of small gauge film formats
|
||||||
|
//Website: https://git.sixteenmillimeter.com/16mm/filmless.git
|
||||||
|
|
||||||
|
module rounded_cube (cube_arr = [1, 1, 1], d = 0, center = false) {
|
||||||
|
off_x = 0;
|
||||||
|
off_y = 0;
|
||||||
|
r = d/2;
|
||||||
|
union () {
|
||||||
|
cube([cube_arr[0] - d, cube_arr[1], cube_arr[2]], center = center);
|
||||||
|
cube([cube_arr[0], cube_arr[1] - d, cube_arr[2]], center = center);
|
||||||
|
translate ([1 * (cube_arr[0] / 2) - r , 1 * (cube_arr[1] / 2)- r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||||
|
translate ([-1 * (cube_arr[0] / 2) + r, -1 * (cube_arr[1] / 2) + r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||||
|
translate ([1 * (cube_arr[0] / 2) - r, -1 * (cube_arr[1] / 2) + r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||||
|
translate ([-1 * (cube_arr[0] / 2) + r, 1 * (cube_arr[1] / 2)- r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module 16mm_perf () {
|
||||||
|
$fn = 10;
|
||||||
|
PERF_W = 1.829;
|
||||||
|
PERF_H = 1.27;
|
||||||
|
rounded_cube([PERF_W, PERF_H, 2], d = .5, center = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
module 16mm_film (frames = 10, double = false, long = true) {
|
||||||
|
THICKNESS = 0.0047 * 25.4;
|
||||||
|
WIDTH = 16;
|
||||||
|
SPACING_LONG = 7.62; //long pitch (projection)
|
||||||
|
SPACING_SHORT = 7.605; //short pitch
|
||||||
|
PERF_OFFSET = (1.829 / 2) + .85;
|
||||||
|
difference () {
|
||||||
|
if (long) {
|
||||||
|
cube([WIDTH, SPACING_LONG * frames, THICKNESS], center = true);
|
||||||
|
} else {
|
||||||
|
cube([WIDTH, SPACING_SHORT * frames, THICKNESS], center = true);
|
||||||
|
}
|
||||||
|
if (long) {
|
||||||
|
OFFSET = (SPACING_LONG * frames) / 2;
|
||||||
|
for (i = [0 : frames]) {
|
||||||
|
translate([8 - PERF_OFFSET, -OFFSET + SPACING_LONG * i, 0]) 16mm_perf();
|
||||||
|
}
|
||||||
|
if (double) {
|
||||||
|
for (i = [0 : frames]) {
|
||||||
|
translate([-8 + PERF_OFFSET, -OFFSET + SPACING_LONG * i, 0]) 16mm_perf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OFFSET = (SPACING_SHORT * frames) / 2;
|
||||||
|
for (i = [0 : frames]) {
|
||||||
|
translate([8 - PERF_OFFSET, -OFFSET + SPACING_LONG * i, 0]) 16mm_perf();
|
||||||
|
}
|
||||||
|
if (double) {
|
||||||
|
for (i = [0 : frames]) {
|
||||||
|
translate([-8 + PERF_OFFSET, -OFFSET + SPACING_LONG * i, 0]) 16mm_perf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module film_sheet () {
|
||||||
|
STRIPS = 12;
|
||||||
|
FRAMES = 33;
|
||||||
|
PERFS = "single";
|
||||||
|
PITCH = "long";
|
||||||
|
projection() for (i = [0:STRIPS - 1]) {
|
||||||
|
translate([16.01 * i, 0, 0]) {
|
||||||
|
16mm_film(FRAMES, PERFS == "double", PITCH == "long");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo("STRIP LENGTH", (PITCH == "long" ? 7.62 : 7.605) * FRAMES, "mm");
|
||||||
|
echo("PAGE WIDTH", 16 * STRIPS, "mm");
|
||||||
|
echo("PITCH", PITCH);
|
||||||
|
echo("PERFS", PERFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
//film_sheet();
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
|
||||||
AT = 25.4 * 0.22;
|
AT = 25.4 * 0.22;
|
||||||
|
|
||||||
|
|
||||||
|
@ -685,7 +685,7 @@ module sprocketed_roller_gear_cap () {
|
||||||
}
|
}
|
||||||
translate([0, 0, -30]) contact_printer_roller();
|
translate([0, 0, -30]) contact_printer_roller();
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
include <./common/common.scad>
|
include <../common/common.scad>
|
||||||
|
|
||||||
in = 25.4;
|
in = 25.4;
|
||||||
$fn = 100;
|
$fn = 100;
|
|
@ -1,7 +1,7 @@
|
||||||
$fn = 80;
|
$fn = 80;
|
||||||
|
|
||||||
include <./common/common.scad>;
|
include <../common/common.scad>;
|
||||||
include <./16mm_sprocketed_roller_var.scad>;
|
include <../16mm_sprocketed_roller_var.scad>;
|
||||||
|
|
||||||
BRACE_L = 24;
|
BRACE_L = 24;
|
||||||
PLATE_L = 47;
|
PLATE_L = 47;
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4482a471bb357e93e9cabe9efaecba0370a3a0bd
|
Subproject commit e108b53ced5fd86dd283baaa6c911964d54ecf40
|
|
@ -1 +1 @@
|
||||||
Subproject commit 74133c800b4a4267cd3bf82b567beb0121bfb7d0
|
Subproject commit dbb3eca85a46c0474c7e68e7febf43db508b53a8
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ "${1}" == "" ]]; then
|
||||||
|
echo "Please use a .scad file as first argument"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FILENAME=$(basename "${1}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
EXTENSION="${FILENAME##*.}"
|
||||||
|
|
||||||
|
if [[ "${EXTENSION}" != "scad" ]]; then
|
||||||
|
echo "Please use a .scad file as first argument, not .${EXTENSION}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${1}" ]]; then
|
||||||
|
echo "File ${1} does not exist"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p hardware
|
||||||
|
|
||||||
|
NAME="${FILENAME%.*}"
|
||||||
|
DESTINATION="./hardware/${NAME}_BOM.csv"
|
||||||
|
TOTAL="./hardware/${NAME}_BOM_total.csv"
|
||||||
|
|
||||||
|
touch "${DESTINATION}"
|
||||||
|
touch "${TOTAL}"
|
||||||
|
|
||||||
|
DESTINATION=$(realpath "${DESTINATION}")
|
||||||
|
TOTAL=$(realpath "${TOTAL}")
|
||||||
|
PRICES=$(realpath "./hardware/prices.csv")
|
||||||
|
MODULE=""
|
||||||
|
|
||||||
|
echo "module,quantity,part,part_id,description" > "${DESTINATION}"
|
||||||
|
|
||||||
|
tac "${1}" | while read line; do
|
||||||
|
module=$(echo "${line}" | grep 'module ' | grep '(' | grep ')')
|
||||||
|
if [[ "${module}" != "" ]]; then
|
||||||
|
MODULE=$(echo "${module}" | xargs | awk '{print $2}' | awk -F'{' '{print $1}')
|
||||||
|
fi
|
||||||
|
bom=$(echo "${line}" | grep '//' | grep 'BOM' | awk -F'BOM:' '{print $2}'| xargs)
|
||||||
|
if [[ "${bom}" != "" ]]; then
|
||||||
|
QUANTITY=$(echo "${bom}" | awk -F',' '{print $1}' | xargs)
|
||||||
|
PART=$(echo "${bom}" | awk -F',' '{print $2}' | xargs)
|
||||||
|
ID=$(echo "${bom}" | awk -F',' '{print $3}' | xargs)
|
||||||
|
DESCRIPTION=$(echo "${bom}" | awk -F',' '{print $4}' | xargs)
|
||||||
|
echo "[${MODULE}] ${QUANTITY}x ${PART} (${ID})"
|
||||||
|
echo "${MODULE},${QUANTITY},${PART},${ID},${DESCRIPTION}" >> "${DESTINATION}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "quantity,part,part_id,price" > "${TOTAL}"
|
||||||
|
sqlite3 :memory: -cmd '.mode csv' -cmd ".import ${DESTINATION} bom" -cmd ".import ${PRICES} prices"\
|
||||||
|
'SELECT SUM(quantity),part,part_id, CAST( CEIL( CAST(SUM(quantity) AS FLOAT) * (SELECT CAST(prices.price AS FLOAT) / CAST(prices.quantity AS FLOAT) FROM prices WHERE prices.part = bom.part LIMIT 1) ) AS INTEGER) as price FROM bom GROUP BY part ORDER BY part DESC;' >> "${TOTAL}"
|
||||||
|
|
||||||
|
sqlite3 :memory: -cmd '.mode csv' -cmd ".import ${TOTAL} bom" -cmd ".import ${PRICES} prices" -cmd '.mode markdown' \
|
||||||
|
"SELECT part as Part, quantity as Qty, \
|
||||||
|
printf('$%.2f', CAST(price AS FLOAT) / 100) as 'Cost (USD)', \
|
||||||
|
printf( '[%s for $%.2f](%s)', (SELECT prices.quantity FROM prices WHERE prices.part = bom.part), (SELECT CAST(prices.price AS FLOAT) / 100 FROM prices WHERE prices.part = bom.part), (SELECT prices.url FROM prices WHERE prices.part = bom.part)) as 'Minumum' \
|
||||||
|
FROM bom ORDER BY part DESC;"
|
||||||
|
|
||||||
|
sqlite3 :memory: -cmd '.mode csv' -cmd ".import ${TOTAL} bom" -cmd ".import ${PRICES} prices" -cmd '.mode markdown' \
|
||||||
|
"SELECT 'TOTAL', SUM(quantity) AS qty, \
|
||||||
|
printf('$%.2f', CAST(SUM(price) AS FLOAT) / 100) as total, \
|
||||||
|
printf('$%.2f', ( SELECT CAST( SUM(price) AS FLOAT) / 100 FROM prices WHERE prices.part IN ( SELECT bom.part FROM bom ) ) ) as min \
|
||||||
|
FROM bom;" | grep -v 'qty'
|
||||||
|
|
||||||
|
sqlite3 :memory: -cmd '.mode csv' -cmd ".import ${TOTAL} bom"\
|
||||||
|
"SELECT SUM(quantity), 'TOTAL', 'N/A', SUM(price) FROM bom;" | tr -d '"' >> "${TOTAL}"
|
||||||
|
|
||||||
|
NONEFOUND=$(sqlite3 :memory: -cmd '.mode csv' -cmd ".import ${DESTINATION} bom" -cmd ".import ${PRICES} prices" -cmd '.mode column' -cmd '.headers off' \
|
||||||
|
'SELECT DISTINCT part FROM bom WHERE part NOT IN (SELECT part FROM prices) ORDER BY part;')
|
||||||
|
|
||||||
|
if [[ "${NONEFOUND}" != "" ]]; then
|
||||||
|
echo "No price found for the following parts:"
|
||||||
|
echo "${NONEFOUND}"
|
||||||
|
fi
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FQBN=esp32:esp32:esp32 #ESP32
|
||||||
|
|
||||||
|
INO="./ino/contact_printer"
|
||||||
|
INOFILE="${INO}/contact_printer.ino"
|
||||||
|
OUTPUT="./bin"
|
||||||
|
|
||||||
|
mkdir -p "${OUTPUT}"
|
||||||
|
|
||||||
|
#esp32
|
||||||
|
arduino-cli compile --fqbn ${FQBN} --output-dir "${OUTPUT}" "${INO}" || echo 'Compile failed' && exit 1
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
WIDTH=2400
|
||||||
|
HEIGHT=2000
|
||||||
|
SCHEME=DeepOcean
|
||||||
|
IMG=img/contact_printer.png
|
||||||
|
|
||||||
|
# setting PART equal to a non-existent module will render the debug layout
|
||||||
|
|
||||||
|
openscad -o ${IMG} --enable manifold --colorscheme ${SCHEME} --imgsize ${WIDTH},${HEIGHT} -D "PART=\"DEBUGxxxxxxx\"" scad/contact_printer.scad
|
||||||
|
convert ${IMG} -resize 1200x1000 -gravity center -extent 1000x700 ${IMG}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd ./notes
|
||||||
|
pandoc --wrap=preserve ./residency_report.md -o ./residency_report.pdf
|
|
@ -12,8 +12,10 @@ cat scad/contact_printer.scad | grep "PART ==" | awk -F'==' '{print $2}' | awk -
|
||||||
|
|
||||||
while read m; do
|
while read m; do
|
||||||
echo "Rendering $m..."
|
echo "Rendering $m..."
|
||||||
openscad -o "stl/contact_printer_$m.stl" -D "PART=\"$m\"" scad/contact_printer.scad
|
openscad --export-format=asciistl --enable manifold -o "stl/contact_printer_$m.stl" -D "PART=\"$m\"" scad/contact_printer.scad
|
||||||
python scad/common/c14n_stl.py "stl/contact_printer_$m.stl"
|
python scad/common/c14n_stl.py "stl/contact_printer_$m.stl"
|
||||||
done < models.txt
|
done < models.txt
|
||||||
|
|
||||||
|
bash scripts/bom.sh "./scad/contact_printer.scad"
|
||||||
|
|
||||||
#run client tests?
|
#run client tests?
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Updating all git submodules"
|
||||||
|
|
||||||
|
git submodule update --recursive scad/common
|
||||||
|
git submodule update --recursive scad/takeup
|
||||||
|
git submodule update --recursive scad/sprocketed_roller
|
|
@ -0,0 +1,29 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
VERSION_FILE="./VERSION.txt"
|
||||||
|
SOURCE_FILE="./ino/contact_printer/contact_printer.ino"
|
||||||
|
CURRENT=`cat "${VERSION_FILE}"`
|
||||||
|
DATESTR=`date +"%Y%m%d"`
|
||||||
|
IFS="."
|
||||||
|
read -ra VERSION <<< "${CURRENT}"
|
||||||
|
IFS=" "
|
||||||
|
|
||||||
|
if [[ "${1}" == "major" ]]; then
|
||||||
|
let "VERSION[0]=${VERSION[0]}+1"
|
||||||
|
let "VERSION[1]=0"
|
||||||
|
let "VERSION[2]=0"
|
||||||
|
elif [[ "${1}" == "minor" ]]; then
|
||||||
|
let "VERSION[1]=${VERSION[1]}+1"
|
||||||
|
let "VERSION[2]=0"
|
||||||
|
else
|
||||||
|
let "VERSION[2]=${VERSION[2]}+1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
V="${VERSION[0]}.${VERSION[1]}.${VERSION[2]}"
|
||||||
|
|
||||||
|
#echo "{ \"version\" : \"$V\", \"bin\" : \"/bin/contact_printer.bin\", \"date\" : $DATESTR }" > ./ota.json
|
||||||
|
VERSION_UPDATE=`sed "s/.*define VERSION.*/ #define VERSION \"${V}\"/" "${SOURCE_FILE}"`
|
||||||
|
echo "${VERSION_UPDATE}" > "${SOURCE_FILE}"
|
||||||
|
|
||||||
|
echo $V > "${VERSION_FILE}"
|
||||||
|
echo $V
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue