Buffer topics (#601)
* Local commit * Additional fill for the topics * Block ignores * Remove pins buffer pages * Remove number format page from pins * Add buffer topic links to summary
This commit is contained in:
parent
b673108852
commit
2aa2cb9a79
@ -298,6 +298,18 @@
|
|||||||
* [SRS BitBot](/pkg/srs/pxt-bitbot)
|
* [SRS BitBot](/pkg/srs/pxt-bitbot)
|
||||||
* [BlockyTalkyBLE](/pkg/LaboratoryForPlayfulComputation/pxt-blockytalkybluetooth/)
|
* [BlockyTalkyBLE](/pkg/LaboratoryForPlayfulComputation/pxt-blockytalkybluetooth/)
|
||||||
|
|
||||||
|
## #types
|
||||||
|
|
||||||
|
* [Types](/types)
|
||||||
|
* [Number](/types/number)
|
||||||
|
* [String](/types/string)
|
||||||
|
* [Boolean](/types/boolean)
|
||||||
|
* [Array](/types/array)
|
||||||
|
* [Function](/types/function)
|
||||||
|
* [Buffer](/types/buffer)
|
||||||
|
* [Using buffers](/types/buffer/using-buffers)
|
||||||
|
* [Number format](/types/buffer/number-format)
|
||||||
|
|
||||||
## #other
|
## #other
|
||||||
|
|
||||||
* [Hardware](/device)
|
* [Hardware](/device)
|
||||||
|
6
docs/types.md
Normal file
6
docs/types.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# @extends
|
||||||
|
|
||||||
|
## Buffer #custom
|
||||||
|
|
||||||
|
* **[Buffer](types/buffer)**: temporary part of memory used to transfer data between your program and devices
|
||||||
|
|
121
docs/types/buffer.md
Normal file
121
docs/types/buffer.md
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# Buffer
|
||||||
|
|
||||||
|
Memory used as a temporary location for data in a program is commonly called a _buffer_. It's a transfer stop for data coming from some source like a network or pins operation. A program can read the data from the buffer when it's ready to work with it. Also, programs put their data in a buffer to make it ready for a transfer to a device or to somewhere else in memory on the @boardname@.
|
||||||
|
|
||||||
|
## Create a new buffer
|
||||||
|
|
||||||
|
Sending and receiving data over the pins might need a buffer if you use your own form of data. The **Pins** category contains a function to create buffers for this purpose.
|
||||||
|
|
||||||
|
A buffer is created with the **createBuffer** function by choosing a buffer size as a number of bytes.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let bufr = pins.createBuffer(16);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get number value from a buffer
|
||||||
|
|
||||||
|
You can get a number value from a particular place in a buffer using **getNumber**. To do it, you have to say how big each of your number values are. The size of the number values is set using a [NumberFormat](/types/buffer/number-format#number-format-types) type. You use an _offset_ value which is the position in the buffer where the number value you want is at.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let num = bufr.getNumber(NumberFormat.Int8LE, 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Put a number value into a buffer
|
||||||
|
A number is placed in a buffer with **setNumber**. You use a number format like with **getNumber**. A new value to goes into the buffer at the position you select.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let val = 15;
|
||||||
|
bufr.setNumber(NumberFormat.Int8LE, 5, val);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get the length of the buffer
|
||||||
|
|
||||||
|
The buffer **length** property tells how big the buffer is in size as number of bytes.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let bufrLength = bufr.length;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fill the buffer
|
||||||
|
|
||||||
|
You can fill an entire buffer so that every byte in the buffer has the same value. You use the
|
||||||
|
**fill** function to initialize or reset the buffer contents to a default value. Typically the number `0`.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
bufr.fill(0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Make two buffers from one
|
||||||
|
|
||||||
|
A new smaller buffer is created from an original larger one using the **slice** function. You tell what position you want to start from in the original buffer and how many bytes from that position you want to copy.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let newBufr = bufr.slice(32, 64);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shift the buffer contents
|
||||||
|
|
||||||
|
The contents of a buffer are shifted left or right by some amount of bytes with the **shift** function. A positive shift number moves the data to the left (to a lower position in the buffer). A negative number moves the data to the right (to a higher position in the buffer). Any data that is moved past the first or last position in the buffer is lost. Locations in the buffer where data is shifted out of are filled with `0` values.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
bufr.shift(8);
|
||||||
|
```
|
||||||
|
Here's an example of shifting a buffer to the left (higher positions to lower positions). The original buffer contains:
|
||||||
|
|
||||||
|
|||||||||
|
||||||
|
|-|-|-|-|-|-|-|-|
|
||||||
|
|0|1|1|1|0|1|0|0|
|
||||||
|
|1|0|1|0|1|0|1|0|
|
||||||
|
|1|1|0|0|1|1|0|0|
|
||||||
|
|1|1|1|0|1|1|1|0|
|
||||||
|
|1|1|1|0|1|1|1|1|
|
||||||
|
<br/>
|
||||||
|
After shifting by 2 bytes using **shift(2)**...
|
||||||
|
|
||||||
|
|||||||||
|
||||||
|
|-|-|-|-|-|-|-|-|
|
||||||
|
|1|1|0|0|1|1|0|0|
|
||||||
|
|1|1|1|0|1|1|1|0|
|
||||||
|
|1|1|1|0|1|1|1|1|
|
||||||
|
|0|0|0|0|0|0|0|0|
|
||||||
|
|0|0|0|0|0|0|0|0|
|
||||||
|
|
||||||
|
## Rotate the buffer contents
|
||||||
|
|
||||||
|
The contents of a buffer are rotated left or right by some amount of bytes with the **rotate** function. A positive rotate number rotates the data to the left (to a lower position in the buffer). A negative number moves the data to the right (to a higher position in the buffer). Any data that is moved past the first or last position in the buffer is placed back in the buffer at the location where the rotation ended.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
bufr.rotate(6);
|
||||||
|
```
|
||||||
|
Here's an example of rotating a buffer to the left (higher positions to lower positions). The original buffer contains:
|
||||||
|
|
||||||
|
|||||||||
|
||||||
|
|-|-|-|-|-|-|-|-|
|
||||||
|
|0|1|1|1|0|1|0|0|
|
||||||
|
|1|0|1|0|1|0|1|0|
|
||||||
|
|1|1|0|0|1|1|0|0|
|
||||||
|
|1|1|1|0|1|1|1|0|
|
||||||
|
|1|1|1|0|1|1|1|1|
|
||||||
|
<br/>
|
||||||
|
After shifting by 3 bytes using **rotate(3)**...
|
||||||
|
|
||||||
|
|||||||||
|
||||||
|
|-|-|-|-|-|-|-|-|
|
||||||
|
|1|1|1|0|1|1|1|0|
|
||||||
|
|1|1|1|0|1|1|1|1|
|
||||||
|
|0|1|1|1|0|1|0|0|
|
||||||
|
|1|0|1|0|1|0|1|0|
|
||||||
|
|1|1|0|0|1|1|0|0|
|
||||||
|
|
||||||
|
## Copy one buffer into another
|
||||||
|
|
||||||
|
The contents of another buffer are copied to a location in the current buffer using the **write** function. You say where (what position) in the current buffer you want to write the contents to and include the other buffer as the second parameter.
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let sourceBufr = serial.readBuffer(32);
|
||||||
|
bufr.write(128, sourceBufr);
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
[using buffers](/types/buffer/using-buffers), [number format](/types/buffer/number-format)
|
73
docs/types/buffer/number-format.md
Normal file
73
docs/types/buffer/number-format.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Number format
|
||||||
|
|
||||||
|
Numbers get stored at some place in memory. They have a certain memory size (spaces in memory) and an arrangement for their bytes.
|
||||||
|
|
||||||
|
## Digital numbers
|
||||||
|
|
||||||
|
The data stored in memory is just a lot of tiny electronic connections in the circuits of the @boardname@. A number is the most basic type of data and other types of data are made using numbers (number are even used to act as text characters in memory). A number is made with combination of electronic connections that are either on and off. One connection is called a _bit_ and this is used as a single digit for computer numbers. A single bit can mean one of two numbers, 0 or 1. Two bits can mean one of four numbers, 00, 01, 10, or 11. We use decimal numbers normally so we give the decimal names to the computer numbers. So, for the two bit numbers, their decimal names are: 00 = 0, 01 = 1, 10 = 2, and 11 = 3. When you see numbers with ones and zeros, they are called _binary_ numbers.
|
||||||
|
|
||||||
|
## The 'byte'
|
||||||
|
|
||||||
|
The smallest amount of bits normally used to store a number in a computer is a combination of 8 bits. A group of 8 bits is called a _byte_. A byte looks like `10010010`. This binary number, for example, represents the number `162` in decimal.
|
||||||
|
|
||||||
|
The maximum absolute value for a number contained in a byte is `255`. You couldn't store the decimal number of `2552` in one byte, you need to use two bytes together. So, `2552` as a binary number is `00001001 11111000` using two bytes.
|
||||||
|
|
||||||
|
## Number formats on the @boardname@
|
||||||
|
|
||||||
|
Numbers are stored in memory in different ways. They can use one or more bytes, have a positive or negative value, and have their bytes switched around.
|
||||||
|
|
||||||
|
### Signed numbers
|
||||||
|
|
||||||
|
If you want a number that can have a positive value or a negative value, then you use a _signed_ number. This means that one bit of all the bits than make up the number value is treated as a plus sign or a minus sign. A bit value of `0` means a plus and a bit value of `1` means minus. Using one of the bits of the number for a sign means that the maximum possible value for a number gets reduced by about half.
|
||||||
|
|
||||||
|
As an example, the decimal numbers of `1` and `-1` are `00000001` and `11111111` in binary. You see that the bit on the left of the second binary number is a `1`. That bit is used as the minus sign (-) for the `-1`.
|
||||||
|
|
||||||
|
### ~hint
|
||||||
|
**Two's complement**
|
||||||
|
|
||||||
|
You might think that the binary number for a decimal `-1` would be `10000001` if the left bit is a `1` for the minus sign. The binary number for a decimal `-1` is actually `11111111`. That's because computers and electronic devices use a special trick when working with negative numbers. The trick is called _two's complement_.
|
||||||
|
|
||||||
|
Making a negative number from a positive number is a two step process. The first step is the _one's complement_ of the number. This step switches all the bits in the number that are `1` to `0` and all the bits that are `0` to `1` (invert the bits). Then, the twos's complement step adds the value of `1` to the one's complement value. Here's how it works:
|
||||||
|
|
||||||
|
Start with the positive binary number for a decimal `1` which is `00000001`.
|
||||||
|
1. The **one's complement** switches the bits, the binary number is now `11111110`.
|
||||||
|
2. The **two's complement** adds a binary `1` to the one's complement value.
|
||||||
|
|
||||||
|
>`11111110` + `00000001` = `11111111`
|
||||||
|
### ~
|
||||||
|
|
||||||
|
### Unsigned numbers
|
||||||
|
|
||||||
|
Signed numbers use all of their bits for the value itself and are always positive values.
|
||||||
|
|
||||||
|
### Big end and little end (endian)
|
||||||
|
|
||||||
|
Earlier you saw that the decimal number `2552` needs two bytes in memory. The order in which these two bytes are placed in memory is called _endian_ order or _endianness_. Funny word, right? This comes from the idea that the byte with the larger part of the value is called the big end and the byte with smaller part of the value is called the little end.
|
||||||
|
|
||||||
|
For `2552` its binary number uses two bytes which are: `00001001 11111000`. The two parts (ends) of this number are:
|
||||||
|
|
||||||
|
* Big end: `00001001`
|
||||||
|
* Little end: `11111000`
|
||||||
|
|
||||||
|
If the big end of the number is stored in memory first, before the little end, the number is a _big endian_ number. Then, of course, if the little end of the number is stored in memory first, before the big end, the number is a _little endian_ number.
|
||||||
|
|
||||||
|
### Number format types
|
||||||
|
|
||||||
|
Sometimes you need to have your program tell what type of numbers it will store in memory. This often necessary when you use [pin](/reference/pins) operations with a [buffer](/types/buffer).
|
||||||
|
|
||||||
|
The formats for numbers stored on the @boardname@ are:
|
||||||
|
|
||||||
|
* `Int8LE`: one byte, signed, little endian
|
||||||
|
* `UInt8LE`: one byte, unsigned, litte endian
|
||||||
|
* `Int8BE`: one byte, signed, big endian
|
||||||
|
* `UInt8BE`: one byte, unsigned, big endian
|
||||||
|
* `Int16LE`: two bytes, signed, little endian
|
||||||
|
* `UInt16LE`: two bytes, unsigned, little endian
|
||||||
|
* `Int16BE`: two bytes, signed, big endian
|
||||||
|
* `UInt16BE`: two bytes, unsigned, big endian
|
||||||
|
* `Int32LE`: four bytes, unsigned, little endian
|
||||||
|
* `Int32BE`: four bytes, unsigned, big endian
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
[buffer](/types/buffer)
|
114
docs/types/buffer/using-buffers.md
Normal file
114
docs/types/buffer/using-buffers.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Using buffers
|
||||||
|
|
||||||
|
Memory used as temporary location for data in a program is commonly called a _buffer_. It's a transfer stop for data coming from some source like a network or pins operation. A program can read the data from the buffer when it's ready to work with it. Also, programs put their data in a buffer to make it ready for a transfer to a device or to somewhere else in memory on the @boardname@.
|
||||||
|
|
||||||
|
## Multiple data types in one place
|
||||||
|
|
||||||
|
Much of the time you read and write [numbers](/types/number) and [strings](/types/string) to and from connected devices or to and from a part on the @boardname@. Sometimes, though, you need to transfer data that is more complicated just these simple types. Your data could be a combination of numbers and strings together, or just a lot of numbers together as a chunk. An example of where you might do this is if you have a external display module or a camera connected to the @boardname@. A buffer is kind of like an [array](/types/array) but it can have data of any type at any place in it.
|
||||||
|
|
||||||
|
## Get a buffer
|
||||||
|
|
||||||
|
Maybe we want to use a super accurate clock with our @boardname@. We can get an external Real Time Clock (RTC) device and connect it to the I2C pins. The time values returned from the RTC device come to us as a sequence of numbers (each number is one _byte_ of data, which means we use the number format of `UInt8LE`). The current time is read from the RTC in a buffer as 7 numbers of a time data (7 bytes). Here's an example of a function we can create to read the time and return the data in a buffer:
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
const rtcAddress = 104;
|
||||||
|
|
||||||
|
function getTimeData() {
|
||||||
|
pins.i2cWriteNumber(rtcAddress, 0, NumberFormat.UInt8LE, false)
|
||||||
|
return pins.i2cReadBuffer(rtcAddress, pins.sizeOf(NumberFormat.UInt8LE) * 7)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The command to read the time uses just one number so we use **i2cWriteNumber** to send this command. The time data that the RTC gives us comes back as a buffer from **i2cReadBuffer**.
|
||||||
|
|
||||||
|
## Reading from a buffer
|
||||||
|
|
||||||
|
Data items in buffers are located at _offsets_ inside the buffer. Like an _index_ in an [array](/types/array), an offset is the position number of some particular data in the buffer we want to access.
|
||||||
|
|
||||||
|
In the previous example, our **getTimeData** function returned the current time to us in a buffer. The time data is 7 numbers all together. Remember, for this device the numbers are bytes which means we use `UInt8LE` as the format. The time is formatted where the first number is seconds, the next number is minutes, and so on. So, if we wanted to know what the current day is, we have to look at the 6th number. This is how we get the 6th number from the the buffer:
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let timeBuf = getTimeData();
|
||||||
|
let day = timeBuf.getNumber(NumberFormat.Int8LE, 6);
|
||||||
|
```
|
||||||
|
|
||||||
|
If we want to decode the data further, we can convert the day number to a name:
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
let timeBuf = getTimeData();
|
||||||
|
let day = timeBuf.getNumber(NumberFormat.Int8LE, 6);
|
||||||
|
basic.showString(dayNames[day]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## ~hint
|
||||||
|
**RTC module**
|
||||||
|
|
||||||
|
Do you want to use a real RTC module and read accurate time data just like in the example? You can get a [precision RTC breakout](https://www.adafruit.com/product/3013) that connects on I2C.
|
||||||
|
## ~
|
||||||
|
|
||||||
|
## Writing to a buffer
|
||||||
|
|
||||||
|
To write to a buffer, you need to use an existing buffer or create one. There is a function that is part of **Pins** that lets you create one.
|
||||||
|
|
||||||
|
Let's say you want to see how quickly it gets dark in the evening over a period of 4 hours. You set the @boardname@ next to the window with the room lights off and record a light level reading once every minute.
|
||||||
|
|
||||||
|
You could simply save the light measurements in an array like this:
|
||||||
|
|
||||||
|
```blocks
|
||||||
|
let darkness: number[] = []
|
||||||
|
input.onButtonPressed(Button.A, () => {
|
||||||
|
for (let i = 0; i < 60 * 4; i++) {
|
||||||
|
darkness.push(input.lightLevel())
|
||||||
|
basic.pause(60000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This will save the light level measurements but we'd like to upload the data later to a laptop computer using the serial port. Also, we want the data to be compact and in the form of a file. We can use a buffer to do it.
|
||||||
|
|
||||||
|
The **Pins** category has a special function called **createBuffer**. It makes a buffer that holds data with a size specified in bytes. Here it is:
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let darkness = pins.createBuffer(60 * 4);
|
||||||
|
```
|
||||||
|
|
||||||
|
The code in blocks for recording the light level is modified to make our file data:
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let darkness = pins.createBuffer(60 * 4);
|
||||||
|
input.onButtonPressed(Button.A, () => {
|
||||||
|
for (let i = 0; i < 60 * 4; i++) {
|
||||||
|
darkness.setNumber(NumberFormat.UInt8LE, i, input.lightLevel())
|
||||||
|
basic.pause(60000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Later, we can upload the file to the laptop computer by pressing the **B** button:
|
||||||
|
|
||||||
|
```typescript-ignore
|
||||||
|
let dataReady = false;
|
||||||
|
let darkness = pins.createBuffer(60 * 4);
|
||||||
|
input.onButtonPressed(Button.A, () => {
|
||||||
|
for (let i = 0; i < 60 * 4; i++) {
|
||||||
|
darkness.setNumber(NumberFormat.UInt8LE, i, input.lightLevel())
|
||||||
|
basic.pause(60000)
|
||||||
|
}
|
||||||
|
dataReady = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onButtonPressed(Button.B, () => {
|
||||||
|
if (dataReady) {
|
||||||
|
serial.writeLine("Transferring file: DARKNESS, Length: " + darkness.length + " bytes...");
|
||||||
|
serial.writeBuffer(darkness)
|
||||||
|
darkness.fill(0);
|
||||||
|
dataReady = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
[buffer](/types/buffer), [number format](types/buffer/number-format),
|
||||||
|
[serial read buffer](/reference/serial/read-buffer), [serial write buffer](/reference/serial/write-buffer)
|
Loading…
Reference in New Issue
Block a user