Compare commits

...

23 Commits

Author SHA1 Message Date
540a097198 driving base sim 2019-10-01 22:17:47 -07:00
93cfb76f68 add city shaper 2019-10-01 22:02:56 -07:00
8bc3fdc8ba 1.2.21 2019-10-01 15:56:22 -07:00
e8a1e73cf5 1.2.20 2019-10-01 14:54:42 -07:00
e9b2b239ad More gyro lessons (#933)
* updated pauseuntil

* updated youtube link

* updated docs
2019-10-01 14:54:17 -07:00
5ad2288a9f updated docs 2019-10-01 13:38:33 -07:00
92d13ef343 updated bluetooth info 2019-10-01 13:36:53 -07:00
471ca5d915 1.2.19 2019-10-01 13:28:01 -07:00
f745079728 Don't reset threashold when resetting color mode (#932)
* don't reset threshold when changing modes

* updated docs
2019-10-01 13:27:37 -07:00
d179f18ef3 added gyro movies 2019-10-01 11:05:44 -07:00
805fc6c787 1.2.18 2019-10-01 10:12:21 -07:00
374bbb8304 Drift-compensated angle in gyro (#931)
* compute angle based on undrifted rate

* add is calibrating function

* fix integrator

* added example

* docs

* poll faster
2019-10-01 10:11:58 -07:00
25452efc92 1.2.17 2019-09-30 22:44:09 -07:00
80b9c715b2 Gyro tutorials (#930)
* gyro tutorials

* tutorials

* fix gyro simulator

* images

* updated image

* fix svg errors
2019-09-30 22:43:50 -07:00
cb816c91ad updated drift 2019-09-30 14:33:57 -07:00
3012068986 1.2.16 2019-09-30 13:44:27 -07:00
4c9c7d6a69 add delay to fix mac deployment (#929) 2019-09-30 13:43:12 -07:00
ad3652c290 bump master to 1.2.15 2019-09-30 13:03:48 -07:00
d8971829e3 1.2.15 2019-09-30 11:13:45 -07:00
8ca4558fc2 web serial cleanup (#928)
* updated ticks

* fix enable and compile

* add debug mode

* don't try to change UI while downloading

* cleanup

* don't double open

* use cu

* add delay before reconnecting

* support for 200 ragne in field turn ratio

* updated docs

* fix docs
2019-09-30 11:13:28 -07:00
d85b5c5129 updated info for macs (#925) 2019-09-30 10:30:21 -07:00
95b1c6a50f 1.2.14 2019-09-29 23:09:07 -07:00
4dc2872286 Better bt download flow (#927)
* round the drif

* restart compile automatically

* add settle
2019-09-29 23:08:46 -07:00
35 changed files with 540 additions and 124 deletions

View File

@ -20,8 +20,8 @@ https://youtu.be/VIq8-6Egtqs
## Supported browsers
* Chrome desktop, version 77 and higher, Windows 10
* [Edge Insider desktop](https://www.microsoftedgeinsider.com), version 77 and higher, Windows 10
* Chrome desktop, version 77 and higher, Windows 10 or Mac OS.
* [Edge Insider desktop](https://www.microsoftedgeinsider.com), version 77 and higher, Windows 10 or Mac OS.
To make sure your browser is up to date, go to the '...' menu, click "Help" then "About".
@ -38,10 +38,7 @@ Next you need to enable the experimental features (this may change in the future
## Download over Bluetooth
* go to the **beta** editor https://makecode.mindstorms.com/beta
This feature is not yet released so make sure to use the beta editor.
* go to https://makecode.mindstorms.com/
* click on **Download** to start a file download as usual
* on the download dialog, you should see a **Bluetooth** button. Click on the
**Bluetooth** button to enable the mode.
@ -51,8 +48,9 @@ This feature is not yet released so make sure to use the beta editor.
## Choosing the correct serial port
Unfortunately, the browser dialog does not make it easy to select which serial port is the brick.
On Windows, it typically reads "Standard Serial over Bluetooth" and you may
have multiple of those if you've paired different bricks.
* On Windows, choose ``Standard Serial over Bluetooth``. There might be multiple of those but only one works. Try your luck! Once you know the COM port number, remember it for the next time around.
* On Mac OS, choose ``cu.BRICKNAME-SerialPort``
## Known issues

View File

@ -1,3 +1,3 @@
{
"appref": "v1.1.20"
"appref": "v1.2.18"
}

View File

@ -61,6 +61,12 @@ motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations);
motors.largeBC.stop();
```
### ~ hint
The **turn ratio range is -200, 200** unlike LabView who used -100,100.
### ~
## Tank
The **tank** blocks control the speed of two motors. These are commonly used for a differential drive robot. The blocks can also specify the duration, angle, or number of rotations.

View File

@ -22,7 +22,7 @@ If you decide to use a **value** of rotation distance, you need to choose a type
## Parameters
* **turnRatio**: a [number](/types/number) that is the percentage of speed of the drive motor. The follower motor runs at this speed. A negative number steers to the left and a positive number steers to the right. This is a number between `-100` and `100`.
* **turnRatio**: a [number](/types/number) that is the percentage of speed of the drive motor. The follower motor runs at this speed. A negative number steers to the left and a positive number steers to the right. This is a number between `-200` and `200`.
* **speed**: a [number](/types/number) that is the percentage of full speed. A negative value runs the motors in the reverse direction. This is the speed that the drive motor runs at.
* **value**: the [number](/types/number) of movement units to rotate for. A value of `0` means run the motor continuously.
* **unit**: the movement unit of rotation. This can be `milliseconds`, `seconds`, `degrees`, or `rotations`. If the number for **value** is `0`, this parameter isn't used.

BIN
docs/static/tutorials/calibrate-gyro.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/static/tutorials/drifter.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/static/tutorials/turn-with-gyro.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -25,6 +25,11 @@ Step by step guides to coding your @boardname@.
"description": "Use the color sensor to follow line or detect colors",
"url":"/tutorials/color-sensor",
"imageUrl":"/static/tutorials/what-color.png"
}, {
"name": "Gyro",
"description": "Drive straight or turn more precisely with the gyro",
"url":"/tutorials/gyro",
"imageUrl":"/static/tutorials/calibrate-gyro.png"
}, {
"name": "Ultrasonic Sensor",
"description": "Use the ultrasonic sensor to detect obstacles",

View File

@ -0,0 +1,37 @@
# Calibrate Gyro
## Introduction @fullscreen
The gyroscope is a very useful sensor in the EV3 system. It detects the rotation rate
which can be very useful to correct the trajectory of the robot and do precise turns.
However, the sensor can be imprecise and subject to drifting. It is recommend to
calibrate your sensor at least once after starting your brick. You don't have to
recalibrate on every run.
* [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
* [EV3 Driving Base with Gyro](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-gyro-sensor-driving-base-a521f8ebe355c281c006418395309e15.pdf)
## Step 1 Show ports
Add the ``||brick:show ports||`` to see the status of the gyroscope.
```blocks
brick.showPorts()
```
## Step 2 Calibration
Add a ``||sensors:calibrate gyro||`` block to calibrate the gyro. The block
detects if the sensor is present and does a full reset of the sensor if necessary.
```blocks
brick.showPorts()
sensors.gyro2.calibrate()
```
## Step 3 Download and run @fullscreen
Download this program to your brick and press the ENTER button.

View File

@ -24,6 +24,18 @@
"name": "Bluetooth download (beta)",
"description": "EXPERIMENTAL! Learn how to download code via Bluetooth.",
"youTubeId": "VIq8-6Egtqs"
}, {
"name": "Turn with Gyro",
"description": "Use the gyro for precise turns.",
"youTubeId": "I7ncuXAfBwk"
}, {
"name": "Moving with Gyro",
"description": "Use the gyro for correct the robot trajectory.",
"youTubeId": "ufiOPvW37xc"
}, {
"name": "Line following with 1 color sensor",
"description": "Simple line following using the color sensor.",
"youTubeId": "_LeduyKQVjg"
}]
```

34
docs/tutorials/drifter.md Normal file
View File

@ -0,0 +1,34 @@
# Drifter
Use this program to try out the gyro sensor and the effect of drifting.
```typescript
// this loop shows the rate, angle and drift of the robot
forever(() => {
brick.showValue("rate", sensors.gyro2.rate(), 1)
brick.showValue("angle", sensors.gyro2.angle(), 2)
brick.showValue("drift", sensors.gyro2.drift(), 3)
})
// this loop shows wheter the sensor is calibrating
forever(() => {
brick.showString(sensors.gyro2.isCalibrating() ? "calibrating..." : "", 4)
})
// instructions on how to use the buttons
brick.showString("ENTER: calibrate", 7)
brick.showString(" (reset+drift)", 8)
brick.showString("LEFT: reset", 9)
brick.showString("RIGHT: compute drift", 10)
// enter -> calibrate
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
sensors.gyro2.calibrate()
})
// right -> compute drift
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
sensors.gyro2.computeDrift()
})
// left -> reset
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
sensors.gyro2.reset()
})
```

31
docs/tutorials/gyro.md Normal file
View File

@ -0,0 +1,31 @@
# Gyro tutorials
## Tutorials
```codecard
[{
"name": "Calibrate",
"description": "Make sure you gyro sensor is ready to use",
"cardType": "tutorial",
"url":"/tutorials/calibrate-gyro",
"imageUrl":"/static/tutorials/calibrate-gyro.png"
}, {
"name": "Turn",
"description": "Use the gyro to turn precisely",
"cardType": "tutorial",
"url":"/tutorials/turn-with-gyro",
"imageUrl":"/static/tutorials/turn-with-gyro.png"
}, {
"name": "Move Straight",
"description": "Use the gyro to correct the trajectory of the robot",
"cardType": "tutorial",
"url":"/tutorials/move-straight-with-gyro",
"imageUrl":"/static/tutorials/move-straight-with-gyro.png"
}, {
"name": "Drifter",
"description": "Explore how the gyro is drifting",
"cardType": "example",
"url":"/tutorials/drifter",
"imageUrl":"/static/tutorials/drifter.png"
}]
```

View File

@ -0,0 +1,61 @@
# Move Straight With Gyro
## Introduction @fullscreen
Rotating using a wheel is not precise. The wheel can slip or the motors
can be slightly different.
With the help of the gyro you can detect and correct deviations in your trajectory.
* [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
* [EV3 Driving Base with Gyro](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-gyro-sensor-driving-base-a521f8ebe355c281c006418395309e15.pdf)
## Step 1 Calibration
Add a ``||sensors:calibrate gyro||`` block in a ``||brick:on button enter pressed||`` block so that you can manually start a calibration process. Run the calibration
at least once after connecting the gyro.
```blocks
brick.showPorts()
sensors.gyro2.calibrate()
```
## Step 2 Compute the error
Make a new **error** variable and drag the ``||sensors:gyro rate||``
and multiply it by -1. Since the rate shows the rotation rate, we will
counter it by negating it.
```blocks
let error = 0
brick.showPorts()
sensors.gyro2.calibrate()
while (true) {
error = sensors.gyro2.rate() * -1
}
```
## Step 3 Steer with feedback
Drag a ``||motors:steer motors||`` block under the variable and pass
the **error** variable into the turn ratio section.
If the robot is turning right, the gyro will report a positive rotation rate
and the turn ratio will be negative which will the turn the robot left!
```blocks
let error = 0
brick.showPorts()
sensors.gyro2.calibrate()
while (true) {
error = sensors.gyro2.rate() * -1
motors.largeBC.steer(error, 50)
}
```
## Step 4 Run it!
Download to your brick and test out if the robot is going straight.
This kind of technique is called a proportional controller;
it corrects the inputs (motor speed) with a feedback proportional to the output (rotation rate).

View File

@ -0,0 +1,43 @@
# Turn With Gyro
## Introduction @fullscreen
Use the gyro to measure how much the robot is turning, regardless if your wheels are slipping.
## Step 1 Calibrate
Add the ``||sensors:calibrate gyro||`` block to make sure your gyro is ready to use.
```blocks
sensors.gyro2.calibrate()
```
## Step 2 Turn
Use motor blocks to make the robot turn. Don't go too fast!
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 20)
```
## Step 3 Pause for turn
Use the ``||sensors:pause until rotated||`` block to wait until the desired amount of rotation has occured.
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 20)
sensors.gyro2.pauseUntilRotated(90)
```
## Step 4 Stop
Stop the motors!
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 20)
sensors.gyro2.pauseUntilRotated(90)
motors.stopAll()
```

View File

@ -64,7 +64,6 @@ class WebSerialPackageIO implements pxt.HF2.PacketIO {
private _writer: any;
constructor(private port: SerialPort, private options: SerialOptions) {
this.openAsync();
}
async readSerialAsync() {
@ -173,7 +172,7 @@ export function initAsync(): Promise<void> {
}
if (WebSerialPackageIO.isSupported())
pxt.tickEvent("bluetooth.supported");
pxt.tickEvent("webserial.supported");
return Promise.resolve();
}
@ -274,7 +273,7 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
if (!useHID) return saveUF2Async()
pxt.tickEvent("bluetooth.flash");
pxt.tickEvent("webserial.flash");
let w: Ev3Wrapper;
return initHidAsync()
.then(w_ => {
@ -289,14 +288,16 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
header: lf("Bluetooth download failed..."),
htmlBody:
`<ul>
<li>${lf("Make sure to stop your program on the EV3.")}</li>
<li>${lf("Make sure to stop your program or exit portview on the EV3.")}</li>
<li>${lf("Check your battery level.")}</li>
<li>${lf("Close EV3 LabView or other MakeCode editor tabs.")}
</ul>`,
hasCloseIcon: true,
hideCancel: true,
hideAgree: false,
agreeLbl: lf("Try again"),
}).then(() => w.disconnectAsync())
.then(() => Promise.delay(1000))
.then(() => w.reconnectAsync());
// nothing we can do
@ -308,12 +309,13 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
.then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
.then(() => w.flashAsync(rbfPath, rbfBIN))
.then(() => w.runAsync(rbfPath))
.then(() => Promise.delay(500))
.then(() => {
pxt.tickEvent("bluetooth.success");
pxt.tickEvent("webserial.success");
return w.disconnectAsync()
//return Promise.delay(1000).then(() => w.dmesgAsync())
}).catch(e => {
pxt.tickEvent("bluetooth.fail");
pxt.tickEvent("webserial.fail");
useHID = false;
useWebSerial = false;
// if we failed to initalize, tell the user to retry

View File

@ -5,7 +5,15 @@ import { deployCoreAsync, initAsync, canUseWebSerial, enableWebSerialAsync, setC
let bluetoothDialogShown = false;
pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
const projectView = opts.projectView;
pxt.debug('loading pxt-ev3 target extensions...')
function enableWebSerialAndCompileAsync() {
return enableWebSerialAsync()
.then(() => Promise.delay(500))
.then(() => projectView.compile());
}
const res: pxt.editor.ExtensionResult = {
deployCoreAsync,
showUploadInstructionsAsync: (fn: string, url: string, confirmAsync: (options: any) => Promise<number>) => {
@ -83,7 +91,7 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
onclick: () => {
pxt.tickEvent("bluetooth.enable");
if (bluetoothDialogShown) {
enableWebSerialAsync().done();
enableWebSerialAndCompileAsync().done();
} else {
bluetoothDialogShown = true;
confirmAsync({
@ -98,20 +106,13 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
}],
htmlBody: `<p>
${lf("You will be prompted to select a serial port.")}
${lf("On Windows, look for 'Standard Serial over Bluetooth link'.")}
${pxt.BrowserUtils.isWindows()
? lf("Look for 'Standard Serial over Bluetooth link'.")
: lf("Loop for 'cu.EV3-SerialPort'.")}
${lf("If you have paired multiple EV3, you might have to try out multiple ports until you find the correct one.")}
</p>
`
}).then(() => enableWebSerialAsync())
.then(() => confirmAsync({
header: lf("Bluetooth is ready!"),
hasCloseIcon: true,
hideCancel: true,
htmlBody: `<p>
${lf("Click Download again to send your code to the EV3 over Bluetooth.")}
</p>
`
}))
}).then(() => enableWebSerialAndCompileAsync())
}
}
} : undefined, downloadAgain ? {

View File

@ -1,8 +1,12 @@
/**
* See https://www.lego.com/cdn/cs/set/assets/blt6879b00ae6951482/LEGO_MINDSTORMS_EV3_Communication_Developer_Kit.pdf
* https://github.com/mindboards/ev3sources/blob/master/lms2012/lms2012/source/bytecodes.h#L146
*/
import HF2 = pxt.HF2
import U = pxt.U
function log(msg: string) {
pxt.debug("EWRAP: " + msg)
pxt.log("serial: " + msg)
}
export interface DirEntry {
@ -13,13 +17,14 @@ export interface DirEntry {
const runTemplate = "C00882010084XX0060640301606400"
const usbMagic = 0x3d3f
const DIRECT_COMMAND_NO_REPLY = 0x80
export class Ev3Wrapper {
msgs = new U.PromiseBuffer<Uint8Array>()
private cmdSeq = U.randomUint32() & 0xffff;
private lock = new U.PromiseQueue();
isStreaming = false;
dataDump = false;
dataDump = /talkdbg=1/.test(window.location.href);
constructor(public io: pxt.HF2.PacketIO) {
io.onData = buf => {
@ -89,7 +94,7 @@ export class Ev3Wrapper {
runAsync(path: string) {
let codeHex = runTemplate.replace("XX", U.toHex(U.stringToUint8Array(path)))
let code = U.fromHex(codeHex)
let pkt = this.allocCore(2 + code.length, 0)
let pkt = this.allocCore(2 + code.length, DIRECT_COMMAND_NO_REPLY)
HF2.write16(pkt, 5, 0x0800)
U.memcpy(pkt, 7, code)
log(`run ${path}`)

View File

@ -77,8 +77,6 @@ export class FieldSpeed extends Blockly.FieldSlider implements Blockly.FieldCust
setReadout_(readout: Element, value: string) {
let x = parseFloat(value) || 0;
// snap on multiple of 5
x = Math.round(x / 5) * 5;
this.updateSpeed(x);
// Update reporter
this.reporter.textContent = `${x}%`;

View File

@ -23,7 +23,7 @@ export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.Field
* @constructor
*/
constructor(value_: any, params: FieldTurnRatioOptions, opt_validator?: Function) {
super(String(value_), '-100', '100', null, '10', 'TurnRatio', opt_validator);
super(String(value_), '-200', '200', null, '10', 'TurnRatio', opt_validator);
this.params = params;
(this as any).sliderColor_ = '#a8aaa8';
}
@ -76,26 +76,26 @@ export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.Field
if (!this.path_) {
return;
}
let v = goog.math.clamp(parseFloat(this.getText()), -100, 100);
let v = goog.math.clamp(parseFloat(this.getText()), -200, 200);
if (isNaN(v)) {
v = 0;
}
const x = goog.math.clamp(parseFloat(this.getText()), -100, 100) / 100;
const theta = x * Math.PI / 2;
const cx = FieldTurnRatio.HALF;
const cy = FieldTurnRatio.HALF - 14;
const gamma = Math.PI - 2 * theta;
const r = FieldTurnRatio.RADIUS;
const alpha = 0.2 + Math.abs(x) * 0.5;
const x1 = 0;
const x = v / 100;
const nx = Math.max(-1, Math.min(1, x));
const theta = Math.max(nx) * Math.PI / 2;
const r = FieldTurnRatio.RADIUS - 6;
let cx = FieldTurnRatio.HALF;
const cy = FieldTurnRatio.HALF - 22;
if (Math.abs(x) > 1) {
cx -= (x - (x > 0 ? 1 : -1)) * r / 2; // move center of circle
}
const alpha = 0.2 + Math.abs(nx) * 0.5;
const y1 = r * alpha;
const y2 = r * Math.sin(Math.PI / 2 - theta);
const x2 = r * Math.cos(Math.PI / 2 - theta);
const y3 = y2 - r * alpha * Math.cos(2 * theta);
const x3 = x2 - r * alpha * Math.sin(2 * theta);
const d = `M ${cx} ${cy} C ${cx} ${cy - y1} ${cx + x3} ${cy - y3} ${cx + x2} ${cy - y2}`;
this.path_.setAttribute('d', d);

View File

@ -73,13 +73,7 @@ namespace sensors {
}
setMode(m: ColorSensorMode) {
if (m == ColorSensorMode.AmbientLightIntensity) {
this.thresholdDetector.setLowThreshold(5);
this.thresholdDetector.setHighThreshold(20);
} else {
this.thresholdDetector.setLowThreshold(20);
this.thresholdDetector.setHighThreshold(80);
}
// don't change threshold after initialization
this._setMode(m)
}

View File

@ -87,7 +87,7 @@ namespace sensors.internal {
this.devType = DAL.DEVICE_TYPE_NONE
this.iicid = ''
this.sensors = []
this.poller = new Poller(50, () => this.query(), (prev, curr) => this.update(prev, curr));
this.poller = new Poller(25, () => this.query(), (prev, curr) => this.update(prev, curr));
}
poke() {
@ -121,7 +121,7 @@ namespace sensors.internal {
powerMM = control.mmap("/dev/lms_power", 2, 0)
devPoller = new Poller(500, () => { return hashDevices(); },
devPoller = new Poller(250, () => { return hashDevices(); },
(prev, curr) => {
detectDevices();
});
@ -814,10 +814,10 @@ namespace sensors {
export class ThresholdDetector {
public id: number;
public min: number;
public max: number;
public lowThreshold: number;
public highThreshold: number;
private min: number;
private max: number;
private lowThreshold: number;
private highThreshold: number;
public level: number;
public state: ThresholdState;

26
libs/core/integrator.ts Normal file
View File

@ -0,0 +1,26 @@
namespace control {
export class EulerIntegrator {
public value: number;
private t: number;
private v: number;
constructor() {
this.reset();
}
public integrate(derivative: number): void {
let now = control.millis();
let dt = (now -this.t) / 1000.0;
this.value += dt * (this.v + derivative) / 2;
this.t = now;
this.v = derivative;
}
public reset() {
this.value = 0;
this.v = 0;
this.t = control.millis();
}
}
}

View File

@ -264,8 +264,9 @@ namespace motors {
// allow 500ms for robot to settle
if (this._brake && this._brakeSettleTime > 0)
pause(this._brakeSettleTime);
else
pause(1); // give a tiny breather
else {
pause(1);
}
}
protected pauseOnRun(stepsOrTime: number) {
@ -275,7 +276,6 @@ namespace motors {
// allow robot to settle
this.settle();
} else {
// give a breather to the event system in tight loops
pause(1);
}
}

View File

@ -25,7 +25,8 @@
"dal.d.ts",
"icons.jres",
"ns.ts",
"platform.h"
"platform.h",
"integrator.ts"
],
"testFiles": [
"test.ts"

View File

@ -1,6 +1,6 @@
# calibrate
Reset the zero reference for the gyro to current position of the brick.
Detects if the gyro is drifting and performs a full reset if needed.
```sig
sensors.gyro2.calibrate()

View File

@ -0,0 +1,21 @@
# Pause Until Rotated
Pauses the program until the gyro sensors detect that the desired amount of rotation
has been acheived.
```
sensors.gyro2.pauseUntilRotated(90)
```
## Example
This program performs a square turn left, then right.
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 10)
sensors.gyro2.pauseUntilRotated(90)
motors.largeBC.steer(-200, 10)
sensors.gyro2.pauseUntilRotated(-90)
motors.largeBC.stop()
```

View File

@ -7,12 +7,15 @@ const enum GyroSensorMode {
namespace sensors {
//% fixedInstances
export class GyroSensor extends internal.UartSensor {
private calibrating: boolean;
private _calibrating: boolean;
private _drift: number;
private _angle: control.EulerIntegrator;
constructor(port: number) {
super(port)
this.calibrating = false;
this._calibrating = false;
this._drift = 0;
this._angle = new control.EulerIntegrator();
this._setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Rate);
}
@ -21,13 +24,17 @@ namespace sensors {
}
_query(): number {
return this.getNumber(NumberFormat.Int16LE, 0);
const v = this.getNumber(NumberFormat.Int16LE, 0);
this._angle.integrate(v - this._drift);
return v;
}
setMode(m: GyroSensorMode) {
if (m == GyroSensorMode.Rate && this.mode != m)
this._drift = 0;
this._setMode(m)
// decrecated
}
isCalibrating(): boolean {
return this._calibrating;
}
/**
@ -40,15 +47,14 @@ namespace sensors {
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=64
//% weight=64 blockGap=8
//% group="Gyro Sensor"
angle(): number {
this.poke();
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
if (this._calibrating)
pauseUntil(() => !this._calibrating, 2000);
this.setMode(GyroSensorMode.Angle);
return this._query();
return Math.round(this._angle.value);
}
/**
@ -65,15 +71,13 @@ namespace sensors {
//% group="Gyro Sensor"
rate(): number {
this.poke();
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.setMode(GyroSensorMode.Rate);
if (this._calibrating)
pauseUntil(() => !this._calibrating, 2000);
return this._query() - this._drift;
}
/**
* Forces a calibration of the with light progress indicators.
* Detects if calibration is necessary and performs a full reset, drift computation.
* Must be called when the sensor is completely still.
*/
//% help=sensors/gyro/calibrate
@ -85,16 +89,30 @@ namespace sensors {
//% weight=51 blockGap=8
//% group="Gyro Sensor"
calibrate(): void {
if (this.calibrating) return; // already in calibration mode
if (this._calibrating) return; // already in calibration mode
const statusLight = brick.statusLight(); // save current status light
brick.setStatusLight(StatusLight.Orange);
this.calibrating = true;
this._calibrating = true;
// may be triggered by a button click,
// give time for robot to settle
pause(700);
// compute drift
this.computeDriftNoCalibration();
if (Math.abs(this.drift()) < 0.1) {
// no drift, skipping calibration
brick.setStatusLight(StatusLight.Green); // success
pause(1000);
brick.setStatusLight(statusLight); // resture previous light
// and we're done
this._angle.reset();
this._calibrating = false;
return;
}
// calibrating
brick.setStatusLight(StatusLight.OrangePulse);
@ -103,22 +121,22 @@ namespace sensors {
// wait till sensor is live
pauseUntil(() => this.isActive(), 7000);
// mode toggling
this.setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Angle);
this._setMode(GyroSensorMode.Rate);
this._setMode(GyroSensorMode.Angle);
this._setMode(GyroSensorMode.Rate);
// check sensor is ready
if (!this.isActive()) {
brick.setStatusLight(StatusLight.RedFlash); // didn't work
pause(2000);
brick.setStatusLight(statusLight); // restore previous light
this.calibrating = false;
this._angle.reset();
this._calibrating = false;
return;
}
// switch to rate mode
this.computeDriftNoCalibration();
// switch back to the desired mode
this.setMode(this.mode);
// and done
brick.setStatusLight(StatusLight.Green); // success
@ -126,7 +144,8 @@ namespace sensors {
brick.setStatusLight(statusLight); // resture previous light
// and we're done
this.calibrating = false;
this._angle.reset();
this._calibrating = false;
}
/**
@ -141,13 +160,37 @@ namespace sensors {
//% weight=50 blockGap=8
//% group="Gyro Sensor"
reset(): void {
if (this.calibrating) return; // already in calibration mode
if (this._calibrating) return; // already in calibration mode
this._calibrating = true;
const statusLight = brick.statusLight(); // save current status light
brick.setStatusLight(StatusLight.Orange);
this.calibrating = true;
// send a reset command
super.reset();
this._drift = 0;
this._angle.reset();
pauseUntil(() => this.isActive(), 7000);
// check sensor is ready
if (!this.isActive()) {
brick.setStatusLight(StatusLight.RedFlash); // didn't work
pause(2000);
brick.setStatusLight(statusLight); // restore previous light
this._angle.reset();
this._calibrating = false;
return;
}
this._setMode(GyroSensorMode.Rate);
// and done
this.calibrating = false;
brick.setStatusLight(StatusLight.Green); // success
pause(1000);
brick.setStatusLight(statusLight); // resture previous light
// and done
this._angle.reset();
this._calibrating = false;
}
/**
@ -177,38 +220,54 @@ namespace sensors {
//% weight=10 blockGap=8
//% group="Gyro Sensor"
computeDrift() {
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
if (this._calibrating)
pauseUntil(() => !this._calibrating, 2000);
pause(1000); // let the robot settle
this.computeDriftNoCalibration();
}
/**
* Pauses the program until the gyro detected
* that the angle changed by the desired amount of degrees.
* @param degrees the degrees to turn
*/
//% help=sensors/gyro/pause-until-rotated
//% block="pause until **gyro** %this|rotated %degrees=rotationPicker|degrees"
//% blockId=gyroPauseUntilRotated
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% degrees.defl=90
//% weight=63
//% group="Gyro Sensor"
pauseUntilRotated(degrees: number, timeOut?: number): void {
let a = this.angle();
const end = a + degrees;
const direction = (end - a) > 0 ? 1 : -1;
pauseUntil(() => (end - this.angle()) * direction <= 0, timeOut);
}
private computeDriftNoCalibration() {
// clear drift
this.setMode(GyroSensorMode.Rate);
this._drift = 0;
const n = 100;
const n = 10;
let d = 0;
for (let i = 0; i < n; ++i) {
d += this._query();
pause(4);
pause(20);
}
this._drift = d / n;
this._angle.reset();
}
_info(): string {
if (this.calibrating)
if (this._calibrating)
return "cal...";
switch (this.mode) {
case GyroSensorMode.Angle:
return `${this._query()}>`;
case GyroSensorMode.Rate:
let r = `${this._query()}>/s`;
if (this._drift)
r += `-${this._drift | 0}`;
return r;
}
return "";
let r = `${this._query()}r`;
if (this._drift != 0)
r += `-${this._drift | 0}`;
return r;
}
}
@ -223,4 +282,17 @@ namespace sensors {
//% fixedInstance whenUsed block="4" jres=icons.port4
export const gyro4: GyroSensor = new GyroSensor(4)
/**
* Get the rotation angle field editor
* @param degrees angle in degrees, eg: 90
*/
//% blockId=rotationPicker block="%degrees"
//% blockHidden=true shim=TD_ID
//% colorSecondary="#FFFFFF"
//% degrees.fieldEditor="numberdropdown" degrees.fieldOptions.decompileLiterals=true
//% degrees.fieldOptions.data='[["30", 30], ["45", 45], ["60", 60], ["90", 90], ["180", 180], ["-30", -30], ["-45", -45], ["-60", -60], ["-90", -90], ["-180", -180]]'
export function __rotationPicker(degrees: number): number {
return degrees;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "pxt-ev3",
"version": "1.2.13",
"version": "1.2.21",
"description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode",
"private": false,
"keywords": [
@ -40,7 +40,7 @@
},
"dependencies": {
"pxt-common-packages": "0.23.61",
"pxt-core": "4.0.10"
"pxt-core": "4.0.11"
},
"scripts": {
"test": "node node_modules/pxt-core/built/pxt.js travis"

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -1,5 +1,5 @@
namespace pxsim {
const enum GyroSensorMode {
export const enum GyroSensorMode {
None = -1,
Angle = 0,
Rate = 1,
@ -8,7 +8,6 @@ namespace pxsim {
export class GyroSensorNode extends UartSensorNode {
id = NodeType.GyroSensor;
private angle: number = 0;
private rate: number = 0;
constructor(port: number) {
@ -19,23 +18,20 @@ namespace pxsim {
return DAL.DEVICE_TYPE_GYRO;
}
setAngle(angle: number) {
if (this.angle != angle) {
this.angle = angle;
this.setChangedState();
}
}
setRate(rate: number) {
rate = rate | 0;
if (this.rate != rate) {
this.rate = rate;
this.setChangedState();
}
}
getRate() {
return this.rate;
}
getValue() {
return this.mode == GyroSensorMode.Angle ? this.angle :
this.mode == GyroSensorMode.Rate ? this.rate : 0;
return this.getRate();
}
}
}

View File

@ -49,11 +49,13 @@ namespace pxsim.visuals {
}
private updateDimensions(width: number, height: number, strict?: boolean) {
width = Math.max(0, width);
height = Math.max(0, height);
if (this.content) {
const currentWidth = this.getInnerWidth();
const currentHeight = this.getInnerHeight();
const newHeight = currentHeight / currentWidth * width;
const newWidth = currentWidth / currentHeight * height;
const newHeight = Math.max(0, currentHeight / currentWidth * width);
const newWidth = Math.max(0, currentWidth / currentHeight * height);
if (strict) {
this.content.setAttribute('width', `${width}`);
this.content.setAttribute('height', `${height}`);

View File

@ -1,13 +1,15 @@
namespace pxsim.visuals {
const MAX_RATE = 40;
export class RotationSliderControl extends ControlView<GyroSensorNode> {
private group: SVGGElement;
private slider: SVGGElement;
private rateText: SVGTextElement;
private static SLIDER_WIDTH = 70;
private static SLIDER_HEIGHT = 78;
//private static SLIDER_HEIGHT = 78;
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
this.group = svg.elt("g") as SVGGElement;
@ -23,6 +25,14 @@ namespace pxsim.visuals {
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 13, 'style': 'fill: #f12a21' });
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' });
this.rateText = pxsim.svg.child(this.group, "text", {
'x': this.getInnerWidth() / 2,
'y': RotationSliderControl.SLIDER_WIDTH * 1.2,
'text-anchor': 'middle', 'dominant-baseline': 'middle',
'style': 'font-size: 16px',
'class': 'sim-text inverted number'
}) as SVGTextElement;
const dragSurface = svg.child(this.group, "rect", {
x: 0,
y: 0,
@ -61,7 +71,10 @@ namespace pxsim.visuals {
return;
}
const node = this.state;
const percentage = node.getValue();
const rate = node.getRate();
this.rateText.textContent = `${rate}°/s`
// cap rate at 40deg/s
const percentage = 50 + Math.sign(rate) * Math.min(MAX_RATE, Math.abs(rate)) / MAX_RATE * 50;
const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100;
const y = Math.abs((percentage - 50) / 50) * 10;
this.slider.setAttribute("transform", `translate(${x}, ${y})`);
@ -73,8 +86,10 @@ namespace pxsim.visuals {
const bBox = this.content.getBoundingClientRect();
let t = Math.max(0, Math.min(1, (width + bBox.left / this.scaleFactor - cur.x / this.scaleFactor) / width))
t = -(t - 0.5) * 2; // [-1,1]
const state = this.state;
state.setRate((1 - t) * (100));
state.setRate(MAX_RATE * t);
}
}

View File

@ -0,0 +1,55 @@
namespace pxsim {
export class RobotGameTable {
readonly ctx: CanvasRenderingContext2D;
readonly data: ImageData;
cx: number; // cm
cy: number; // cm
angle: number; // radians
cwidth: number; // cm
constructor(public canvas: HTMLCanvasElement, public scale: number) {
this.ctx = this.canvas.getContext("2d");
this.data = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
this.cx = this.width / 2;
this.cy = 0;
}
/**
* Gets the width in cm
*/
get width() {
return this.canvas.width * this.scale;
}
/**
* gets the height in cm
*/
get height() {
return this.canvas.height * this.scale;
}
color(): number {
// compute color sensor position from center;
// todo
const px = Math.max(0, Math.min(this.data.width, (this.cx ) / this.scale));
const py = Math.max(0, Math.min(this.data.height, (this.cy ) / this.scale));
// get color
const i = px * this.data.width + py;
let c =
(this.data.data[i] << 16) | (this.data.data[i + 1] << 8) | (this.data.data[i + 2]);
// map color to known color
return c;
}
intensity(): number {
const c = this.color();
return ((c >> 16 & 0xff) + (c >> 8 & 0xff) + (c & 0xff)) / 3;
}
ultrasonicDistance() {
// obstacles?
}
}
}

View File

@ -18,6 +18,7 @@
"Touch Sensor Tutorials": "tutorials/touch-sensor",
"Color Sensor Tutorials": "tutorials/color-sensor",
"Ultrasonic Sensor Tutorials": "tutorials/ultrasonic-sensor",
"Gyro Tutorials": "tutorials/gyro",
"Infrared Sensor Tutorials": "tutorials/infrared-sensor",
"FLL / City Shaper": "tutorials/city-shaper",
"Design Engineering": "design-engineering",