Gyro tutorials (#930)

* gyro tutorials

* tutorials

* fix gyro simulator

* images

* updated image

* fix svg errors
This commit is contained in:
Peli de Halleux 2019-09-30 22:43:50 -07:00 committed by GitHub
parent cb816c91ad
commit 80b9c715b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 175 additions and 8 deletions

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 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", "description": "Use the color sensor to follow line or detect colors",
"url":"/tutorials/color-sensor", "url":"/tutorials/color-sensor",
"imageUrl":"/static/tutorials/what-color.png" "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", "name": "Ultrasonic Sensor",
"description": "Use the ultrasonic sensor to detect obstacles", "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.

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

@ -0,0 +1,19 @@
# 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": "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"
}]
```

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

@ -1,6 +1,6 @@
# calibrate # 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 ```sig
sensors.gyro2.calibrate() sensors.gyro2.calibrate()

View File

@ -73,7 +73,7 @@ namespace sensors {
} }
/** /**
* 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. * Must be called when the sensor is completely still.
*/ */
//% help=sensors/gyro/calibrate //% help=sensors/gyro/calibrate
@ -95,6 +95,19 @@ namespace sensors {
// give time for robot to settle // give time for robot to settle
pause(700); 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.calibrating = false;
return;
}
// calibrating // calibrating
brick.setStatusLight(StatusLight.OrangePulse); brick.setStatusLight(StatusLight.OrangePulse);

View File

@ -1,5 +1,5 @@
namespace pxsim { namespace pxsim {
const enum GyroSensorMode { export const enum GyroSensorMode {
None = -1, None = -1,
Angle = 0, Angle = 0,
Rate = 1, Rate = 1,
@ -20,6 +20,7 @@ namespace pxsim {
} }
setAngle(angle: number) { setAngle(angle: number) {
angle = angle | 0;
if (this.angle != angle) { if (this.angle != angle) {
this.angle = angle; this.angle = angle;
this.setChangedState(); this.setChangedState();
@ -27,6 +28,7 @@ namespace pxsim {
} }
setRate(rate: number) { setRate(rate: number) {
rate = rate | 0;
if (this.rate != rate) { if (this.rate != rate) {
this.rate = rate; this.rate = rate;
this.setChangedState(); this.setChangedState();

View File

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

View File

@ -1,13 +1,16 @@
namespace pxsim.visuals { namespace pxsim.visuals {
const MAX_RATE = 40;
const MAX_ANGLE = 360;
export class RotationSliderControl extends ControlView<GyroSensorNode> { export class RotationSliderControl extends ControlView<GyroSensorNode> {
private group: SVGGElement; private group: SVGGElement;
private slider: SVGGElement; private slider: SVGGElement;
private text: SVGTextElement;
private static SLIDER_WIDTH = 70; private static SLIDER_WIDTH = 70;
private static SLIDER_HEIGHT = 78; //private static SLIDER_HEIGHT = 78;
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
this.group = svg.elt("g") as SVGGElement; this.group = svg.elt("g") as SVGGElement;
@ -23,6 +26,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': 13, 'style': 'fill: #f12a21' });
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' }); pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' });
this.text = pxsim.svg.child(this.group, "text", {
'x': RotationSliderControl.SLIDER_WIDTH / 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", { const dragSurface = svg.child(this.group, "rect", {
x: 0, x: 0,
y: 0, y: 0,
@ -61,7 +72,17 @@ namespace pxsim.visuals {
return; return;
} }
const node = this.state; const node = this.state;
const percentage = node.getValue(); let percentage = 50;
if (node.getMode() == GyroSensorMode.Rate) {
const rate = node.getValue();
this.text.textContent = `${rate}°/s`
// cap rate at 40deg/s
percentage = 50 + Math.sign(rate) * Math.min(MAX_RATE, Math.abs(rate)) / MAX_RATE * 50;
} else { //angle
const angle = node.getValue();
this.text.textContent = `${angle}°`
percentage = 50 + Math.sign(angle) * Math.min(MAX_ANGLE, Math.abs(angle)) / MAX_ANGLE * 50;
}
const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100; const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100;
const y = Math.abs((percentage - 50) / 50) * 10; const y = Math.abs((percentage - 50) / 50) * 10;
this.slider.setAttribute("transform", `translate(${x}, ${y})`); this.slider.setAttribute("transform", `translate(${x}, ${y})`);
@ -73,8 +94,14 @@ namespace pxsim.visuals {
const bBox = this.content.getBoundingClientRect(); const bBox = this.content.getBoundingClientRect();
let t = Math.max(0, Math.min(1, (width + bBox.left / this.scaleFactor - cur.x / this.scaleFactor) / width)) 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; const state = this.state;
state.setRate((1 - t) * (100)); if (state.getMode() == GyroSensorMode.Rate) {
state.setRate(MAX_RATE * t);
} else {
state.setAngle(MAX_ANGLE * t)
}
} }
} }

View File

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