micropython-ulab/docs/ulab-pid.ipynb

800 lines
28 KiB
Text

{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2021-01-13T18:54:58.722373Z",
"start_time": "2021-01-13T18:54:57.178438Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"source": [
"%pylab inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Notebook magic"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2021-01-13T18:57:41.555892Z",
"start_time": "2021-01-13T18:57:41.551121Z"
}
},
"outputs": [],
"source": [
"from IPython.core.magic import Magics, magics_class, line_cell_magic\n",
"from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n",
"from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n",
"import subprocess\n",
"import os"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2021-01-13T18:57:42.313231Z",
"start_time": "2021-01-13T18:57:42.288402Z"
}
},
"outputs": [],
"source": [
"@magics_class\n",
"class PyboardMagic(Magics):\n",
" @cell_magic\n",
" @magic_arguments()\n",
" @argument('-skip')\n",
" @argument('-unix')\n",
" @argument('-pyboard')\n",
" @argument('-file')\n",
" @argument('-data')\n",
" @argument('-time')\n",
" @argument('-memory')\n",
" def micropython(self, line='', cell=None):\n",
" args = parse_argstring(self.micropython, line)\n",
" if args.skip: # doesn't care about the cell's content\n",
" print('skipped execution')\n",
" return None # do not parse the rest\n",
" if args.unix: # tests the code on the unix port. Note that this works on unix only\n",
" with open('/dev/shm/micropython.py', 'w') as fout:\n",
" fout.write(cell)\n",
" proc = subprocess.Popen([\"../micropython/ports/unix/build-2/micropython-2\", \"/dev/shm/micropython.py\"], \n",
" stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
" print(proc.stdout.read().decode(\"utf-8\"))\n",
" print(proc.stderr.read().decode(\"utf-8\"))\n",
" return None\n",
" if args.file: # can be used to copy the cell content onto the pyboard's flash\n",
" spaces = \" \"\n",
" try:\n",
" with open(args.file, 'w') as fout:\n",
" fout.write(cell.replace('\\t', spaces))\n",
" printf('written cell to {}'.format(args.file))\n",
" except:\n",
" print('Failed to write to disc!')\n",
" return None # do not parse the rest\n",
" if args.data: # can be used to load data from the pyboard directly into kernel space\n",
" message = pyb.exec(cell)\n",
" if len(message) == 0:\n",
" print('pyboard >>>')\n",
" else:\n",
" print(message.decode('utf-8'))\n",
" # register new variable in user namespace\n",
" self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n",
" \n",
" if args.time: # measures the time of executions\n",
" pyb.exec('import utime')\n",
" message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n",
" \"\\nprint('execution time: {:d} us'.format(delta))\")\n",
" print(message.decode('utf-8'))\n",
" \n",
" if args.memory: # prints out memory information \n",
" message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n",
" print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n",
" message = pyb.exec(cell)\n",
" print(\">>> \", message.decode('utf-8'))\n",
" message = pyb.exec('print(mem_info())')\n",
" print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n",
"\n",
" if args.pyboard:\n",
" message = pyb.exec(cell)\n",
" print(message.decode('utf-8'))\n",
"\n",
"ip = get_ipython()\n",
"ip.register_magics(PyboardMagic)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## pyboard"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-07T07:35:35.126401Z",
"start_time": "2020-05-07T07:35:35.105824Z"
}
},
"outputs": [],
"source": [
"import pyboard\n",
"pyb = pyboard.Pyboard('/dev/ttyACM0')\n",
"pyb.enter_raw_repl()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-19T19:11:18.145548Z",
"start_time": "2020-05-19T19:11:18.137468Z"
}
},
"outputs": [],
"source": [
"pyb.exit_raw_repl()\n",
"pyb.close()"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-07T07:35:38.725924Z",
"start_time": "2020-05-07T07:35:38.645488Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"%%micropython -pyboard 1\n",
"\n",
"import utime\n",
"import ulab as np\n",
"\n",
"def timeit(n=1000):\n",
" def wrapper(f, *args, **kwargs):\n",
" func_name = str(f).split(' ')[1]\n",
" def new_func(*args, **kwargs):\n",
" run_times = np.zeros(n, dtype=np.uint16)\n",
" for i in range(n):\n",
" t = utime.ticks_us()\n",
" result = f(*args, **kwargs)\n",
" run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n",
" print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n",
" print('\\tbest: %d us'%np.min(run_times))\n",
" print('\\tworst: %d us'%np.max(run_times))\n",
" print('\\taverage: %d us'%np.mean(run_times))\n",
" print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n",
" return result\n",
" return new_func\n",
" return wrapper\n",
"\n",
"def timeit(f, *args, **kwargs):\n",
" func_name = str(f).split(' ')[1]\n",
" def new_func(*args, **kwargs):\n",
" t = utime.ticks_us()\n",
" result = f(*args, **kwargs)\n",
" print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n",
" return result\n",
" return new_func"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"__END_OF_DEFS__"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# PID module\n",
"\n",
"`ulab` provides a module with a generic and fast implementation of the proportional-integral-derivative ([PID](https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller)) controller. While it is relatively easy to write a PID controller in `python`, the problem with that approach is execution speed: even in thermal applications, where the time scales are not particularly short, running more than one controller simultanously might be difficult, but if the goal is e.g. to control an object mechanically, the task will prove outright impossible. The reason for this slowness of the code execution is the number of floating point operations, where the type-agnostic nature of `python` requires constant type conversion.\n",
"\n",
"`ulab`'s `PID` module implements the controller in C, and for very time-critical applications, it allows the user to take data direclty from the measurement\n",
"device (most probably an analogue-to-digital converter), if it supports the buffer protocol. Likewise, data can be output directly to the physical device (in most cases a digital-to-analogue converter), if it can take data from a buffer. This direct access to the device data eliminates the need for data conversion in `python`, and reduces the number of objects that must be passed to the controller loop. In fact, in most cases the controller can be run without moving any data whatsoever, simply by calling a method of the `PID` instance without arguments."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Background\n",
"\n",
"In general, running a controller loop requires the following steps:\n",
"\n",
"1. Define desired set point\n",
"1. Measure the current value of the quantity that is to be controlled\n",
"1. Calculate the error, the difference between set point and current value\n",
"1. Based on the error's value, calculate the output; this is where the control loop itself is defined\n",
"1. Place the calculated output on a physical output device\n",
"1. Repeat indefinitely\n",
"\n",
"While the description above seems innocuous, steps 2-5 might require considerable resources, when implemented in an interpreted language."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Measuring the value of a physical quantity\n",
"\n",
"The quantity that a physical device can measure is not necessarily the quantity that we are interested in, which means that the measured quantity has to undergo some conversion steps before it can be used in the controller loop. \n",
"\n",
"Take the example of temperature measurements: while there are digital thermometers that will output the temperature directly, in most cases, the temperature is only inferred from a voltage. In the simplest case, a thermistor will form half of a voltage divider connected to a reference voltage $U_{ref}$, and the potential $U_{meas}$ is measured at mid-point. It is obvious that even if the thermistors temperature-resistance relationship $R_{th}(T)$ were to be linear, the measured potential will not be (it does not matter, whether the thermistor is in the upper or lower half of the divider):\n",
"\n",
"\\begin{equation}\n",
"U_{meas} = U_{ref}\\frac{R}{R + R_{th}(T)}\n",
"\\end{equation}\n",
"\n",
"Most thermistors will have a highly non-linear characteristic curve, thus, converting the potential values to temperature becomes non-trivial, and in the most general case, the conversion must be calculated from the expression above, by inverting $R_{th}(T)$. We can note, however, that any well-behaved function can be expanded in a Taylor series around a point, where the series converges, hence the temperature can be expressed as \n",
"\n",
"\\begin{equation}\n",
"T = f(U_{meas}) = \n",
"f(U_0) + \n",
"\\left. \\frac{df}{dU} \\right|_{U_0} (U_{meas} - U_0) + \n",
"\\left. \\frac{1}{2}\\frac{d^2f}{dU^2} \\right|_{U_0} (U_{meas} - U_0)^2 + \n",
"\\left. \\frac{1}{6}\\frac{d^3f}{dU^3} \\right|_{U_0} (U_{meas} - U_0)^3 + ...\n",
"\\end{equation}\n",
"where the differentials are evaluated at $U_0$.\n",
"\n",
"\n",
"The coefficients of the expansion are determined by the parameters of the physical system in question, as well as $U_0$. Also note that higher-order expansions are inherently more accurate, but take more time to calculate. Usually, a second-order expansion, or even a linear one is enough, if the expected deviations from $U_0$ are not too large."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Converting the digital control values to physical control signals\n",
"\n",
"The comments on converting measured values to physically relevant quantities apply, when we want to convert calculated values to physially relevant control signals, i.e., when at the end of the control loop, we want to use the calculated signal to influence the physical drive mechanism: the calculated digital value will be converted to a current, potential etc. by means of a digital-to-analogue converter, and some analogue circuitry that takes an analogue potential as input, and generates a current, or another potential as output, or turns a valve. The relationship between the input potential and the physical control quantity is not necessarily the identity, or even linear, thus, in the general case, we have to express the conversion function as \n",
"\n",
"\\begin{equation}\n",
"I_{control} = g(U_{control}) = \n",
"g(U^*) + \n",
"\\left. \\frac{dg}{dU} \\right|_{U^*} (U_{control} - U^*) + \n",
"\\left. \\frac{1}{2}\\frac{d^2g}{dU^2} \\right|_{U^*} (U_{control} - U^*)^2 + \n",
"\\left. \\frac{1}{6}\\frac{d^3g}{dU^3} \\right|_{U^*} (U_{control} - U^*)^3 + ...\n",
"\\end{equation}\n",
"where the control output is expanded around the potential $U^*$. \n",
"\n",
"The coefficients of the expansion are determined by the parameters of the physical system in question, as well as $U^*$. Also note that higher-order expansions are inherently more accurate, but take more time to calculate. Usually, a second-order expansion, or even a linear one is enough, if the expected deviations from $U^*$ are not too large."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Input-output buffers\n",
"\n",
"Conversion between analogue and digital values works not with floating point numbers, which are most probably the results of the controller algorithm, but with integers that are transmitted to/from the converter. A typical transmission sequence will, in general, contain not only the data, but some auxiliary bits, like status, channel number, gain, etc., as shown below. The input/output string $S$ consists of $n + N + m$ bits, where the *D*s are the data bits with the digital values, while the *A*s are the auxiliary bits. The data are embedded in a string, whose auxiliary bits are fixed, and do not change from transmission to transmission.\n",
"\n",
"\\begin{equation}\n",
"S = \\overbrace{A_n A_{n - 1} ... A_1 A_0}^{\\mathrm{auxiliary\\ bits}}\n",
"\\overbrace{D_N D_{N - 1} ... D_1 D_0}^{\\mathrm{data\\ bits}} \n",
"\\overbrace{A_m A_{m - 1} ... A_1 A_0}^{\\mathrm{auxiliary\\ bits}}\n",
"\\end{equation}\n",
"\n",
"In order to make data exchange between the control algorithm and the peripheral devices transparent and seamless, the `PID` module implements input/output buffers, which not only place the relevant bits at the correct positions in pre-defined strings automatically, but also take care of the conversion of data bits to floating point numbers and vica versa.\n",
"\n",
"Since we are not interested in the sequences $A_n A_{n - 1} ... A_1 A_0$, and $A_m A_{m - 1} ... A_1 A_0$, the data can be recovered by shifting its bits to the right by so many places that $D_0$ reaches a byte boundary, and then masking the result by $2^N - 1$ (this is equivalent to $N$ 1s). In the code, $n$ will be referred to as the `offset`, while $N$ as the `bitdepth`. The exact values should be taken from the data sheets of the ADC or DAC that are used as the interface devices."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Input-output data converters\n",
"\n",
"We have seen above how the numbers in the control algorithm can be interfaced with the outside world. To facilitate the convension, `ulab`'s `PID` module defines input-output data converters, which hold an input/output buffer as described in the previous section, and the coefficients of the Taylor series expansions, i.e., all information required to turn a string of bits into a floating point number that is the true represenation of the value that we are interested in."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Methods \n",
"\n",
"The module implements a class with the following methods\n",
"\n",
"1. [bitdepth](#bitdepth)\n",
"1. [offset](#offset)\n",
"1. [buffer](#buffer)\n",
"1. [evaluate](#evaluate)\n",
"1. [parameters](#parameters)\n",
"1. [reset](#reset)\n",
"1. [series](#series)\n",
"1. [step](#step)\n",
"1. [float_step](#float_step)\n",
"1. [value](#value)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Instantiating the PID object\n",
"\n",
"The controller can be instantiated by importing the `PID` module, and calling the constructor without arguments. At this point, the object will hold all values required for the operation of the PID loop, and some of these can be displayed by simply printing the controller object: "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2021-01-13T19:06:54.640444Z",
"start_time": "2021-01-13T19:06:54.623467Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"PID object at 0x7fb58e2053a0\n",
"value: 0.000000\n",
"last time: 0.000000\n",
"steps: 0\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"print(pid)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `value` is the current value of the controller's output, `last time` is when the controller's `step` method was called last. Time is measured from the moment of the first step. `steps` is the number of steps the controller has taken since its start."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### bitdepth\n",
"\n",
"This method *gets* or *sets* the bit depth of the input's ADC or the output's DAC. Note that the method does not communicate with the ADC or DAC, it simply changes how the bits in the input and output buffers are interpreted. The method takes 1 or 2 arguments. The first one is the strings `in` or `out`. If this is the only argument supplied (i.e., the method is a *getter*), the method returns the currently active bitdepth setting for that buffer. If the method is called with arguments (i.e., when it is a *setter*), this argument must be an unsigned integer, signifying the number of bits taken by the data in the buffer. "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"initial bit depth: 0\n",
"new bit depth: 12\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"print('initial bit depth: ', pid.bitdepth('in'))\n",
"pid.bitdepth('in', 12)\n",
"print('new bit depth: ', pid.bitdepth('in'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### offset\n",
"\n",
"This method *gets* or *sets* the offset of the input's ADC or the output's DAC stream. Note that the method does not communicate with the ADC or DAC, it simply changes how the bits in the input and output buffers are interpreted. The method takes 1 or 2 arguments. The first one is the strings `in` or `out`. If this is the only argument supplied (i.e., the method is a *getter*), the method returns the currently active offset setting for that buffer. If the method is called with arguments (i.e., when it is a *setter*), this argument must be an unsigned integer, signifying the offset expressed in bits. "
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"offset: 176\n",
"new offset: 12\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"print('offset: ', pid.offset('in'))\n",
"pid.offset('in', 12)\n",
"print('new offset: ', pid.offset('in'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### reset\n",
"\n",
"This method takes no arguments, and resets the controller, i.e., zeroes the time, the number of steps, and the integral part."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"initial state of controller\n",
"PID object at 0x7f0ad62053a0\n",
"value: 0.000000\n",
"last time: 0.000000\n",
"steps: 0\n",
"\n",
"stepping the controller 10 times\n",
"\n",
"new state of controller:\n",
"PID object at 0x7f0ad62053a0\n",
"value: 0.000000\n",
"last time: 0.000000\n",
"steps: 10\n",
"\n",
"resetting controller\n",
"\n",
"new state of controller:\n",
"PID object at 0x7f0ad62053a0\n",
"value: 0.000000\n",
"last time: 0.000000\n",
"steps: 0\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"print('initial state of controller')\n",
"print(pid)\n",
"\n",
"print()\n",
"print('stepping the controller 10 times')\n",
"for _ in range(10):\n",
" pid.step()\n",
"\n",
"print()\n",
"print('new state of controller:')\n",
"print(pid)\n",
"\n",
"print()\n",
"print('resetting controller')\n",
"pid.reset()\n",
"\n",
"print()\n",
"print('new state of controller:')\n",
"print(pid)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### step\n",
"\n",
"This method takes no arguments, and steps the controller, i.e., takes and converts the data in the inpput buffer, calculates the three terms of the controller's output, and places the data on the output buffer. At the same time, the `value`, `steps`, and `integral` attributes of the controller are updated. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### float_step"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"P: 1.0\n",
"2.0\n",
"0.75\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"pid.parameters('P', 1.0)\n",
"pid.parameters('D', 1.0)\n",
"print('P: ', pid.parameters('P'))\n",
"\n",
"print(pid.float_step(1.0))\n",
"print(pid.float_step(2.0, 0.2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### parameters\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"P: 0.0\n",
"I: 0.0\n",
"D: 0.0\n",
"\n",
"P: 1.0\n",
"I: 2.0\n",
"D: 3.0\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"print('P: ', pid.parameters('P'))\n",
"print('I: ', pid.parameters('I'))\n",
"print('D: ', pid.parameters('D'))\n",
"\n",
"print()\n",
"pid.parameters('P', 1.0)\n",
"pid.parameters('I', 2.0)\n",
"pid.parameters('D', 3.0)\n",
"print('P: ', pid.parameters('P'))\n",
"print('I: ', pid.parameters('I'))\n",
"print('D: ', pid.parameters('D'))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Converting input/output data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### series\n",
"\n",
"The `series` method is again a *getter/setter*, and takes 1 or 3 arguments. The first one is the strings `in` or `out`. If this is the only argument supplied (i.e., the method is a *getter*), the method returns a tuple with the point, around which the function is expanded in a Taylor series, as well as the coefficients of the expansion.\n",
"\n",
"If 3 arguments are supplied, the method becomes a *setter*. The second argument must be a floating point number on which the expansion is centred, while the third argument can be a generic iterable holding the floating point coefficients of the expansion starting with the highest power."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Taylor series, input: (0.0, (1.0, 0.0))\n",
"Taylor series, output: (10.0, (1.0, 2.0, 3.0))\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"# y = x, default\n",
"print('Taylor series, input: ', pid.series('in'))\n",
"\n",
"# y = (x - 10) * (x - 10) + 2 * (x - 10) + 3\n",
"pid.series('out', 10.0, (1, 2, 3))\n",
"print('Taylor series, output: ', pid.series('out'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### evaluate\n",
"\n",
"This is a convenience method, which can be used to evaluate the Taylor series at a particular point. Internally, it calls the same function that is called by the controller, whenever it needs to convert its input or output. The method takes two arguments, the designator of the buffer (`in/out`), and the point at which the Taylor expansion is to be evaluated."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Taylor series, input: (0.0, (1.0, 0.0))\n",
"value at x = 10.0: 10.0\n",
"\n",
"Taylor series, input: (0.0, (2.0, 0.0, 10.0))\n",
"value at x = 10.0: 210.0\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"from ulab import PID\n",
"\n",
"pid = PID.PID()\n",
"\n",
"# y = x, default\n",
"print('Taylor series, input: ', pid.series('in'))\n",
"print('value at x = 10.0: ', pid.evaluate('in', 10.0))\n",
"\n",
"print()\n",
"\n",
"# y = 2 * x * x + 10.0\n",
"pid.series('in', 0.0, (2, 0, 10))\n",
"print('Taylor series, input: ', pid.series('in'))\n",
"\n",
"print('value at x = 10.0: ', pid.evaluate('in', 10.0))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "382.797px"
},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}