tasmota lcd display price
The display driver is able to display predefined setups of text or user defined text. To display text using DisplayText set DisplayMode to 0, or set DisplayMode to 1 for the HT16K33 dot-matrix display.
To use the seven-segment-specific TM1637, TM1638 and MAX7219 Display- commands, set DisplayMode to 0. Parameter LCD Display OLED Display TFT Display 7-segment Display (TM163x and MAX7219) 0 DisplayText DisplayText DisplayText All TM163x Display- functions
The DisplayText command is used to display text as well as graphics and graphs on LCD, OLED and e-Paper displays (EPD). The command argument is a string that is printed on the display at the current position. The string can be prefixed by embedded control commands enclosed in brackets [].
In order to use the DisplayText command the DisplayMode must be set to 0 (or optional 1 on LCD displays) or other modes must be disabled before compilation with #undef USE_DISPLAY_MODES1TO5.
In the list below p stands for parameter and may be a number from 1 to n digits. On monochrome graphic displays things are drawn into a local frame buffer and sent to the display either via the d command or automatically at the end of the command.
Pfilename: = display an rgb 16-bit color (or jpg on ESP32) image when file system is present, Scripteditor contains a converter to convert jpg to special RGB16 pictures See ScriptEditor Ffilename: = load RAM font file when file system is present. the font is selected with font Nr. 5, these fonts are special binary versions of GFX fonts of any type. they end with .fnt. an initial collection is found in Folder BinFonts
Draw up to 16 GFX buttons to switch real Tasmota devices such as relays or draw Sliders to dimm e.g. a lamp Button number + 256 - a virtual touch toggle button is created (MQTT => TBT)
When a file system is present you may define displaytext batch files. If a file named "display.bat" is present in the file system this batch file is executed. The file may contain any number of diplaytext cmds, one at a line. You may have comment lines beginning with a ;
E-Paper displays have 2 operating modes: full update and partial update. While full update delivers a clean and sharp picture, it has the disadvantage of taking several seconds for the screen update and shows severe flickering during update. Partial update is quite fast (300 ms) with no flickering but there is the possibility that erased content is still slightly visible. It is therefore useful to perform a full update in regular intervals (e.g., each hour) to fully refresh the display.
The data sheets of the TFT and OLED displays mention burn-in effects when a static display is shown for extended periods of time. You may want to consider turning on the display on demand only.
The EPD font contains 95 characters starting from code 32, while the classic GFX font contains 256 characters ranging from 0 to 255. Custom characters above 127 can be displayed. To display these characters, you must specify an escape sequence (standard octal escapes do not work). The ~character followed by a hex byte can define any character code.
The I2C address must be specified using DisplayAddress XX, e.g., 60. The model must be specified with DisplayModel, e.g., 2 for SSD1306. To permanently turn the display on set DisplayDimmer 100. Display rotation can be permanently set using DisplayRotate X (x = 0..3).
E-Paper displays are connected via software 3-wire SPI (CS, SCLK, MOSI). DC should be connected to GND , Reset to 3.3 V and busy may be left unconnected. The jumper on the circuit board of the display must be set to 3-wire SPI.
Waveshare has two kinds of display controllers: with partial update and without partial update. The 2.9 inch driver is for partial update and should also support other Waveshare partial update models with modified WIDTH and HEIGHT parameters. The 4.2 inch driver is a hack which makes the full update display behave like a partial update and should probably work with other full update displays.
In black and white displays, a local RAM buffer must be allocated before calling the driver. This must be set to zero on character or TFT color displays.
Universal Display Driver or uDisplay is a way to define your display settings using a simple text file and easily add it to Tasmota. uDisplay is DisplayModel 17. It supports I2C and hardware or software SPI (3 or 4 wire).
Initial register setup for the display controller. (IC marks that the controller is using command mode even with command parameters) All values are in hex. On SPI the first value is the command, then the number of arguments and the the arguments itself. Bi7 7 on the number of arguments set indicate a wait of 150 ms. On I2C all hex values are sent to I2C.
bit 2: enable async DMA, 0 wait for DMA to complete before returning, 4 run DMA async in the background. This later mode is only valid if the SPI bus is not shared between the display and any other SPI device like SD Card Reader.
# Scripter is the nost convenient way to edit and develop a uDisplay driver. On every scripter save the display is reinitialized and you immediately see results of your changes.
There are also many variants of each display available and not all variants may be supported. #define directive Description USE_DISPLAY Enable display support. Also requires at least one of the following compilation directives
The display driver is able to display predefined setups of text or user defined text. To display text using DisplayText set DisplayMode to 0, or set DisplayMode to 1 for the HT16K33 dot-matrix display.
In order to use the DisplayText command the DisplayMode must be set to 0 (or optional 1 on LCD displays) or other modes must be disabled before compilation with #undef USE_DISPLAY_MODES1TO5.
E-Paper displays have 2 operating modes: full update and partial update. While full update delivers a clean and sharp picture, it has the disadvantage of taking several seconds for the screen update and shows severe flickering during update. Partial update is quite fast (300 ms) with no flickering but there is the possibility that erased content is still slightly visible. It is therefore useful to perform a full update in regular intervals (e.g., each hour) to fully refresh the display.
The data sheets of the TFT and OLED displays mention burn-in effects when a static display is shown for extended periods of time. You may want to consider turning on the display on demand only.
The EPD font contains 95 characters starting from code 32, while the classic GFX font contains 256 characters ranging from 0 to 255. Custom characters above 127 can be displayed. To display these characters, you must specify an escape sequence (standard octal escapes do not work). The ~character followed by a hex byte can define any character code.
The I2C address must be specified using DisplayAddress XX, e.g., 60. The model must be spedified with DisplayModel, e.g., 2 for SSD1306. To permanently turn the display on set DisplayDimmer 100. Display rotation can be permanently set using DisplayRotate X (x = 0..3).
Waveshare has two kinds of display controllers: with partial update and without partial update. The 2.9 inch driver is for partial update and should also support other Waveshare partial update models with modified WIDTH and HEIGHT parameters. The 4.2 inch driver is a hack which makes the full update display behave like a partial update and should probably work with other full update displays.
In black and white displays, a local RAM buffer must be allocated before calling the driver. This must be set to zero on character or TFT color displays.
Remark: To use the 400x300 e-Paper display, the Arduino library 2.4 or later must be used because it leaves much more RAM available than prior versions. This display requires 15k of RAM!
This first week of Feb 2021 I received lots of gadgets and some more displays from Banggood including an old favourite – the ILI9341 – good, cheap LCD display – which I’ve always supported in ESP-GO – but now I want to run on an ESP8266 (again) this time using Tasmota. Here’s the ILI9341 display first: 2.4 Inch 240*320 Color HD LCD TFT Screen SPI Serial Display Module ILI9341
The setup for the display wasn’t QUITE as indicated on the Tasmota site – it seems that displays are not yet a high priority there, but with help I managed to get it running – here’s the template for Tasmota-display standard build – no need to compile a special. Note that the display does NOT have a CS pin but this has to be defined in Tasmota for the display to be recognised. I hooked RESET to RST and the backlight to pin marked BLK on the display. It SEEMS that Tasmota display support is a bit primitive as yet.
Note the ILI9341 CS and DC settings – the SPI versions don’t work – see above and this works. Also, CS is not used on these boards but needs to be defined in Tasmota or it won’t recognise the display. In the end, easier done than written about so here we go – 4 lines and some coloured text. The software also does boxes and circles.
I noted in an earlier Tasmota-display.bin development update some severe font issues and I spent days talking to the author of some of the Tasmota displays – Gerhard Mutz – he recently included a 7-segment font – and thanks to a little encouragement these will work on ESP8266 (that’s what I’m running my tests on to be sure).
I’ve also learned about the Tasmota file system and it is now possible to run PNG files (losing the transparency) through an editor to convert them into .RGB files so that icons can be easily added to Tasmota-displays – I expect this is in the tasmota-display.bin file but certainly in a custom Tasmota file – also font 5 onwards are optionally added RAM fonts. In essence we can have a small number of fonts including 7-segment as well as a range of colour icons available to use in Tasmota for the ILI9341. For me it has been a goal to get this facility for many months. The file system makes it possible to store the icons in FLASH and checking earlier I noted not far short of 2MB available for this purpose.
Lots of fonts are available here but beware they take up RAM. “displaybatch” lets you run files stored in the file system (which has to be enabled depending on your tasmota build). I run it with display.bat (the file who’s contents can init the display on powerup – currently not 100% perfect at powerup) or other .bat files – you need a leading slash before the file name. Example: displaybatch /display2.bat
Given a graphic in the root of the Tasmota file system and also a file called display.bat (with for example [z] in it to clear the display) then running displaybatch /display.ini produces the following display: (the virus image wasn’t my idea:-) )
With success on the ILI9341 board from Banggood, I turned to a model JYC150-7P SPI SSD1351 OLED board. The board has a link set for 4-wire SPI, I changed the link over to 3-wire SPI. Sadly no matter what combinations I tried – despite help from a couple of guys on the Tasmota displays DISCORD channel, we still got no-where. I found this document – which seemed to clear up what to do with the SPI DC wire, reset and CS – but.. nothing but black from the display.
In the meantime I tried a very old SSD1351 (2013 ILSoft Ltd) display board. Here are the settings I used after changing the link (cutting) on THAT board to set to SPI 3-wire mode with DC grounded, CS wired to Tasmota SSD1351 CS (see Tasmota config page) and RST to ESP8266 RESET. This board has SCK and SDI which go to the Tasmota SPI CLK and SPI MOSI :
displaytext [z][x0y0h128x0y127h128x0y0v128x127y0v128][x5y3C31s1f1]Working[x5y23C30735]Color is purple[x5y43C4032]Green text[x5y63C63488]red text[x5y83C64800]Orange text
No other settings in Tasmota-displays.bin.gz were used to achieve the above. See this page for the full colour codes (indexed did not work – I used the full colour codes).
I should take this opportunity to correct some colour coding on the Tasmota page as relates to this and similar displays. The page refers to green as code “8”. As we are looking at a display that uses 5 bits, 6 bits and 5 bits for R,G,B respectively, full green is in fact 64*63 i.e. 1984.
Once again this display works well but feature support for displays in Tasmota still leaves something to be desired – more fonts would be good and not just scaled up. In the example to the right above, I’m using codes which may look awkward at first – but are merely text – with commands embedded in square brackets – z for CLEAR SCREEN, x and y for POSITIONING, f for FONT, C for COLOUR, r for RECTANGLE and R for FILLED RECTANGLE:
Another great gadget – see photo below: You’ll see a white dolphin I picked up at a seaside market in Spain early summer 2020 – they are widely available – they come with a wooden base + USB lead and have a bright, warm, single-colour (white) display – but the SHARK came from Banggood (described as “Shark 3D Night Light 7 Colors Changing LED Touch Switch USB Table Lamp“), has a solid-looking black plastic base, USB lead and has full RGB including touch button options for any single colour or slowly cycling through the colours. I love it and so does my wife. The shark base in the photo above is sitting on a USB battery power pack I happened to have handy.
I just wrote about the 10-pack of GeekCreit Wireless IOT Modules – or as most of us know them – nodeMCU ESP8266 boards – at a very reasonable €23 for 10 – i.e. 2.30 each inc post. So what better to test them with than the freely-available tasmota-displays.bin and a nice OLED display.
But using the little 0.96″ displays is nothing new and though cheap they are a little on the small size. I was pleased therefore to note that Banggood stock a larger version at 1.3″ (I’ve no idea why we still use inches for the size of these). I’ve been using the SSD1306-based 0.96″ displays for years and you’ll find umpteen references to them in here.
The larger, white 1.3″ OLED displays however use a different chip – the SSH1106 – this particular version has the same resolution as the higher of the two smaller displays – i.e. 128*64
Having already tested one of my nodeMCU boards with the SSD1306 I was happy my hardware worked – and changed the Tasmota commands slightly to cater for the new display.
Disaster – and that’s why it has taken me a couple of weeks to write this article, planned originally for Christmas week. I wasted a lot of time on the Tasmota forums trying to find out why my new display – when presented with some text, would only show every other line of pixels. Antonio (Mr Shark) and I spent AGES trying to figure it out then he remembered he’d had a similar display and his worked.
The penny dropped… so I sent an email to Banggood who rapidly shipped off a pair of replacements. They turned up in deepest Spain this morning and here I am. They work, PERFECTLY – so it looks like, after all that, I simply had a duff display (looking now I can see a minor crack in the bottom left of the display which may have resulted in a disconnected internal wire) – nothing wrong with the Tasmota driver at all.
If you check the link in this article to the displays – note that for some reason Banggood show the GND and VCC positions to be the same as the SSD1306 displays – in FACT they are reversed (and marked accordingly) on the displays I have (i.e. vcc on the outside).
To drive these OLED displays, assuming for now you are working in the Tasmota webUI – is easy. But take note – the displays use 4 wires in total – 3v3,gND, SCL and SDA (i2c). The Tasmota (display version bin file) settings for this are easy – simply set GPIO4 (D2) to SDA in the webUI and GPIO5 (D1) to SCL using the “generic (18)” template. This as it happens is a choice, you don’t HAVE to use GPIO4 and 5, I just tried moving SCL to GPIO12 and it worked just as well.
Then wire the display to the ESP8266 board. (for reference these boards sit at i2c address 0x78 by default – that is how Tasmota knows the board is connected. In addition, a couple of commands are needed and these are non-volatile.
There are a ton of commands and settings for displays and it all gets confusing very quickly – the ones I’ve given you above are all you need to get started. That should force a reset of the board and give you a blank screen on the display. Next you will want to send some text…again in this case in the console:
Of course it doesn’t stop there – you can set one of several fonts, clear the screen, change orientation and so much more as well as xy position the cursor but that gets confusing fast so I thought I’d get the basics out of the way – I got bogged down with all the commands through trying to do too much at once. Ok, got a working display?
“displaytext” supports commands in square brackets which can be used for control – for example [z] clears the screen, [x0y8] sets the cursor down and left etc. To go further – I direct you back to the massive Tasmota commands page (display section) for more. You have a small choice of fonts, can draw rectangles etc filled or un-filled and more.
If you were sending commands via MQTT, in this example to a device I’m calling “display1”, you might send the displaytext command as a topic – payload pair:
There’s an important point here, these displays don’t last forever, so when I put a message up, sfter some time, maybe a couple of minutes depending on the job, I turn it off – this can be done with a POWER command or simply [z] to clear the display.
Until recently I was running the SSD1306 display on my home control Raspberry Pi, but now that I have this, I can combine the display and maybe a pair of SSD PSU control relays onto one nodeMCU board (I’m thinking power to the Pi and control of a pair of SSDs for cloning, all handled in Node-Red but that’s another article). Oh, another non-volatile command DISPLAYROTATE (0,1,2 or 3). Of course, it is up to you not to put too many characters on a line.
I’ll shortly expand this as I’m expecting more displays in the next week or so including colour LCD and OLED displays (the larger ones tend to use SPI hence more GPIOs).
I never had to use one, as I turned a Sonoff Basic into a Sonoff TH equivalent by adding a DHT11 sensor to it and flashing the switch with Tasmota. This turned the $5 into a smart controller for my DIY Smart Heating System. I don’t think I will replace the current setup with Sonoff TH Elite (even though I could), as I already have a ZigBee based thermostat lined up for this role.
There is no denying that the Elite series of the Sonoff devices have really handsome looks! DIN rail mountable by default, Sonoff TH Elite sports uniform white body with minimal prints. The surface is dominated by an LCD display that refreshes the data every 5 sec. While a display highlight would be nice, it’s unlikely you will ever take on electrical work in the dark, so I will let this one slide.
The display will show you the current temperature and humidity of the attached probe, so you don’t have to reach for your phone to troubleshoot things. I only wish that display had more information present like network details, schedule etc as in reality, it’s easier to pull out the eWeLink app and access the data than move to the physical location of your Sonoff TH Elite.
The new interface in the eWeLink app brings features designed specifically for the Sonoff TH Elite. The switch interface has 2 modes – manual, where the sensor data is gathered and displayed in graphs, but the temperature and humidity values have no influence over the relay and the Auto mode in which the state of the relay is driven by the policies set in the app. You will be able to add up to 8 policies for temperature and humidity control where you can control the internal relay of the Sonoff TH Elite with values (higher/lower) from the probe. The schedule for these policies can be also defined so they don’t apply 24/7.
On top of that, the eWeLink app brings the LAN mode for local control, power-on state and inching. Fans of the imperial units will find the toggle for these and units will be converted in the app as well as on the LCD display.
I mentioned that the button on Sonoff TH Elite can change the mode of the relay from Auto to Manual (double click), change is reflected on the unit itself by a barely visible LED. An icon on the LCD would be nice to make the change of mode more visible. A single click on the button changes the relay state. What’s a little bit annoying is the fact that after enabling Auto mode, going to manual mode in the eWeLink app doesn’t override it. You have to go to Auto mode again and disable it.
By default, the LCD screen shows the temperature and humidity data. If your probe only reads one, the other value will be blanked out. These are updated every 5 sec and sent to the eWeLink cloud. Data from sensors is stored in the app but at 1h intervals. There are no aggregation settings to control that and even though the sensor delivers new results every 5 sec, there are no options to retain that information. It can only be used to trigger policies.
The relay on my model (THR320D) was rated for 20A and the dry contact one for 3A. Extra dev pads are exposed on the other side probably to access the LCD IC – so it’s a matter of time before we going to see Tasmota running on it.
I don’t have any information about support for Sonoff DIY mode. It would be nice to see for anyone looking to integrate these with DIY automation without flashing with Tasmota or using eWeLink API.
I’m somewhat impressed with Sonoff TH Elite. It looks nice and brings interesting options to the table including new probes, dry contact relay and an LCD display to make monitoring as simple as possible. As Sonoff TH Elite still keeps the price low, if wanted to use an all-in-one thermostatic switch – this could be a no-brainer. Let me know what you think about the Elite series from Sonoff in this Reddit thread.
This clever utility uses a security loophole to trick your Tuya device into thinking that it’s installing an updated version of itself, when in fact it’s replacing itself with Tasmota. This means you can do the conversion without any electrical connection to the device: you don’t need a serial connection, and you don’t need to open the case.
If you only had one device to convert, you can say “N” (or just press ENTER) and Tuya-Convert will exit. You’re now ready to follow the normal Tasmota setup and configure your device.
Tuya-Convert installs a very basic Tasmota binary that will allow your device to connect to WiFi, but it may not be the specific Tasmota build that your device requires for its features to work.
Power up your device, and then follow the usual Tasmota setup process to connect your phone or tablet to its network. Then you can open its web interface, and if necessary you can install a different build of Tasmota that suits your device.
The best place to get information about specific devices is the Tasmota Device Templates Repository. Look up your device there to find out what Tasmota build you need to install, and how it should be configured.
Along 3 years I have been trying several leg mechanism, at first I decided to do a simple desing with tibial motor where placed on femur joint.This design had several problems, like it wasn"t very robust and the most importat is that having the motor (with big mass) that far from the rotating axis, caused that in some movements it generate unwanted dynamics to the robot body, making controlability worse.New version have both motors of femur/tibial limb at coxa frame, this ends with a very simple setup and at the same time, the heaviest masses of the mechanism are centered to the rotating axis of coxa limb, so even though the leg do fast movements, inertias won"t be strong enough to affect the hole robot mass, achieving more agility.Inverse Kinematics of the mechanismAfter building it I notice that this mechanism was very special for another reason, at the domain the leg normally moves, it acts as a diferential mecanism, this means that torque is almost all the time shared between both motor of the longer limbs. That was an improvent since with the old mechanism tibial motor had to hold most of the weight and it was more forced than the one for femur.To visualize this, for the same movement, we can see how tibial motor must travel more arc of angel that the one on the new version.In order to solve this mechanism, just some trigonometry is needed. Combining both cosine and sine laws, we can obtain desired angle (the one between femur and tibia) with respect to the angle the motor must achieve.Observing these equations, with can notice that this angle (the one between femur and tibia) depends on both servos angles, which means both motors are contributing to the movement of the tibia.Calibration of servosAnother useful thing to do if we want to control servo precisely is to print a calibration tool for our set up. As shown in the image below, in order to know where real angles are located, angle protactor is placer just in the origin of the rotating joint, and choosing 2 know angles we can match PWM signal to the real angles we want to manipulate simply doing a lineal relation between angles and PWM pulse length.Then a simple program in the serial console can be wrtten to let the user move the motor to the desired angle. This way the calibration process is only about placing motor at certain position and everything is done and we won"t need to manually introduce random values that can be a very tedious task.With this I have achieved very good calibrations on motors, which cause the robot to be very simetrial making the hole system more predictable. Also the calibration procedure now is very easy to do, as all calculations are done automatically. Check Section 1 for the example code for calibration.More about this can be seen in the video below, where all the building process is shown as well as the new leg in action.SECTION 1:In the example code below, you can see how calibration protocol works, it is just a function called calibrationSecuence() which do all the work until calibration is finished. So you only need to call it one time to enter calibration loop, for example by sending a "c" character thought the serial console.Also some useful function are used, like moving motor directly with analogWrite functions which all the calculations involved, this is a good point since no interrupts are used.This code also have the feature to calibrate the potentiometer coming from each motor.#define MAX_PULSE 2500 #define MIN_PULSE 560 /*---------------SERVO PIN DEFINITION------------------------*/ int m1 = 6;//FR int m2 = 5; int m3 = 4; int m4 = 28;//FL int m5 = 29; int m6 = 36; int m7 = 3;//BR int m8 = 2; int m9 = 1; int m10 = 7;//BL int m11 = 24; int m12 = 25; int m13 = 0;//BODY /*----------------- CALIBRATION PARAMETERS OF EACH SERVO -----------------*/ double lowLim[13] = {50, 30, 30, 50, 30, 30, 50, 30, 30, 50, 30, 30, 70}; double highLim[13] = {130, 150, 150, 130, 150, 150, 130, 150, 150, 130, 150, 150, 110}; double a[13] = { -1.08333, -1.06667, -1.07778, //FR -1.03333, 0.97778, 1.01111, //FL 1.03333, 1.05556, 1.07778, //BR 1.07500, -1.07778, -1.00000, //BL 1.06250 }; double b[13] = {179.0, 192.0, 194.5, //FR 193.0, 5.5, -7.5, //FL 7.0, -17.0, -16.0, //BR -13.5, 191.5, 157.0, //BL -0.875 }; double ae[13] = {0.20292, 0.20317, 0.19904 , 0.21256, -0.22492, -0.21321, -0.21047, -0.20355, -0.20095, -0.20265, 0.19904, 0.20337, -0.20226 }; double be[13] = { -18.59717, -5.70512, -2.51697, -5.75856, 197.29411, 202.72169, 185.96931, 204.11902, 199.38663, 197.89534, -5.33768, -32.23424, 187.48058 }; /*--------Corresponding angles you want to meassure at in your system-----------*/ double x1[13] = {120, 135, 90, 60, 135 , 90, 120, 135, 90, 60, 135, 90, 110}; //this will be the first angle you will meassure double x2[13] = {60, 90, 135, 120, 90, 135, 60, 90, 135, 120, 90, 135, 70};//this will be the second angle you will meassure for calibration /*--------You can define a motor tag for each servo--------*/ String motorTag[13] = {"FR coxa", "FR femur", "FR tibia", "FL coxa", "FL femur", "FL tibia", "BR coxa", "BR femur", "BR tibia", "BL coxa", "BL femur", "BL tibia", "Body angle" }; double ang1[13] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; double ang2[13] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; float xi[500]; float yi[500]; float fineAngle; float fineL; float fineH; int motorPin; int motor = 0; float calibrationAngle; float res = 1.0; float ares = 0.5; float bres = 1.0; float cres = 4.0; float rawAngle; float orawAngle; char cm; char answer; bool interp = false; bool question = true; bool swing = false; int i; double eang; int freq = 100; // PWM frecuency can be choosen here. void connectServos() { analogWriteFrequency(m1, freq); //FR coxa digitalWrite(m1, LOW); pinMode(m1, OUTPUT); analogWriteFrequency(m2, freq); //femur digitalWrite(m2, LOW); pinMode(m2, OUTPUT); analogWriteFrequency(m3, freq); //tibia digitalWrite(m3, LOW); pinMode(m3, OUTPUT); analogWriteFrequency(m4, freq); //FL coxa digitalWrite(m4, LOW); pinMode(m4, OUTPUT); analogWriteFrequency(m5, freq); //femur digitalWrite(m5, LOW); pinMode(m5, OUTPUT); analogWriteFrequency(m6, freq); //tibia digitalWrite(m6, LOW); pinMode(m6, OUTPUT); analogWriteFrequency(m7, freq); //FR coxa digitalWrite(m7, LOW); pinMode(m7, OUTPUT); analogWriteFrequency(m8, freq); //femur digitalWrite(m8, LOW); pinMode(m8, OUTPUT); analogWriteFrequency(m9, freq); //tibia digitalWrite(m9, LOW); pinMode(m9, OUTPUT); analogWriteFrequency(m10, freq); //FR coxa digitalWrite(m10, LOW); pinMode(m10, OUTPUT); analogWriteFrequency(m11, freq); //femur digitalWrite(m11, LOW); pinMode(m11, OUTPUT); analogWriteFrequency(m12, freq); //tibia digitalWrite(m12, LOW); pinMode(m12, OUTPUT); analogWriteFrequency(m13, freq); //body digitalWrite(m13, LOW); pinMode(m13, OUTPUT); } void servoWrite(int pin , double angle) { float T = 1000000.0f / freq; float usec = float(MAX_PULSE - MIN_PULSE) * (angle / 180.0) + (float)MIN_PULSE; uint32_t duty = int(usec / T * 4096.0f); analogWrite(pin , duty); } double checkLimits(double angle , double lowLim , double highLim) { if ( angle >= highLim ) { angle = highLim; } if ( angle <= lowLim ) { angle = lowLim; } return angle; } int motorInfo(int i) { enc1 , enc2 , enc3 , enc4 , enc5 , enc6 , enc7 , enc8 , enc9 , enc10 , enc11 , enc12 , enc13 = readEncoders(); if (i == 0) { rawAngle = enc1; motorPin = m1; } else if (i == 1) { rawAngle = enc2; motorPin = m2; } else if (i == 2) { rawAngle = enc3; motorPin = m3; } else if (i == 3) { rawAngle = enc4; motorPin = m4; } else if (i == 4) { rawAngle = enc5; motorPin = m5; } else if (i == 5) { rawAngle = enc6; motorPin = m6; } else if (i == 6) { rawAngle = enc7; motorPin = m7; } else if (i == 7) { rawAngle = enc8; motorPin = m8; } else if (i == 8) { rawAngle = enc9; motorPin = m9; } else if (i == 9) { rawAngle = enc10; motorPin = m10; } else if (i == 10) { rawAngle = enc11; motorPin = m11; } else if (i == 11) { rawAngle = enc12; motorPin = m12; } else if (i == 12) { rawAngle = enc13; motorPin = m13; } return rawAngle , motorPin; } void moveServos(double angleBody , struct vector anglesServoFR , struct vector anglesServoFL , struct vector anglesServoBR , struct vector anglesServoBL) { //FR anglesServoFR.tetta = checkLimits(anglesServoFR.tetta , lowLim[0] , highLim[0]); fineAngle = a[0] * anglesServoFR.tetta + b[0]; servoWrite(m1 , fineAngle); anglesServoFR.alpha = checkLimits(anglesServoFR.alpha , lowLim[1] , highLim[1]); fineAngle = a[1] * anglesServoFR.alpha + b[1]; servoWrite(m2 , fineAngle); anglesServoFR.gamma = checkLimits(anglesServoFR.gamma , lowLim[2] , highLim[2]); fineAngle = a[2] * anglesServoFR.gamma + b[2]; servoWrite(m3 , fineAngle); //FL anglesServoFL.tetta = checkLimits(anglesServoFL.tetta , lowLim[3] , highLim[3]); fineAngle = a[3] * anglesServoFL.tetta + b[3]; servoWrite(m4 , fineAngle); anglesServoFL.alpha = checkLimits(anglesServoFL.alpha , lowLim[4] , highLim[4]); fineAngle = a[4] * anglesServoFL.alpha + b[4]; servoWrite(m5 , fineAngle); anglesServoFL.gamma = checkLimits(anglesServoFL.gamma , lowLim[5] , highLim[5]); fineAngle = a[5] * anglesServoFL.gamma + b[5]; servoWrite(m6 , fineAngle); //BR anglesServoBR.tetta = checkLimits(anglesServoBR.tetta , lowLim[6] , highLim[6]); fineAngle = a[6] * anglesServoBR.tetta + b[6]; servoWrite(m7 , fineAngle); anglesServoBR.alpha = checkLimits(anglesServoBR.alpha , lowLim[7] , highLim[7]); fineAngle = a[7] * anglesServoBR.alpha + b[7]; servoWrite(m8 , fineAngle); anglesServoBR.gamma = checkLimits(anglesServoBR.gamma , lowLim[8] , highLim[8]); fineAngle = a[8] * anglesServoBR.gamma + b[8]; servoWrite(m9 , fineAngle); //BL anglesServoBL.tetta = checkLimits(anglesServoBL.tetta , lowLim[9] , highLim[9]); fineAngle = a[9] * anglesServoBL.tetta + b[9]; servoWrite(m10 , fineAngle); anglesServoBL.alpha = checkLimits(anglesServoBL.alpha , lowLim[10] , highLim[10]); fineAngle = a[10] * anglesServoBL.alpha + b[10]; servoWrite(m11 , fineAngle); anglesServoBL.gamma = checkLimits(anglesServoBL.gamma , lowLim[11] , highLim[11]); fineAngle = a[11] * anglesServoBL.gamma + b[11]; servoWrite(m12 , fineAngle); //BODY angleBody = checkLimits(angleBody , lowLim[12] , highLim[12]); fineAngle = a[12] * angleBody + b[12]; servoWrite(m13 , fineAngle); } double readEncoderAngles() { enc1 , enc2 , enc3 , enc4 , enc5 , enc6 , enc7 , enc8 , enc9 , enc10 , enc11 , enc12 , enc13 = readEncoders(); eang1 = ae[0] * enc1 + be[0]; eang2 = ae[1] * enc2 + be[1]; eang3 = ae[2] * enc3 + be[2]; eang4 = ae[3] * enc4 + be[3]; eang5 = ae[4] * enc5 + be[4]; eang6 = ae[5] * enc6 + be[5]; eang7 = ae[6] * enc7 + be[6]; eang8 = ae[7] * enc8 + be[7]; eang9 = ae[8] * enc9 + be[8]; eang10 = ae[9] * enc10 + be[9]; eang11 = ae[10] * enc11 + be[10]; eang12 = ae[11] * enc12 + be[11]; eang13 = ae[12] * enc13 + be[12]; return eang1 , eang2 , eang3 , eang4 , eang5 , eang6 , eang7 , eang8 , eang9 , eang10 , eang11 , eang12 , eang13; } void calibrationSecuence( ) { //set servos at their middle position at firstt for (int i = 0; i <= 12; i++) { rawAngle , motorPin = motorInfo(i); servoWrite(motorPin , 90); } // sensorOffset0 = calibrateContacts(); Serial.println(" "); Serial.println("_________________________________SERVO CALIBRATION ROUTINE_________________________________"); Serial.println("___________________________________________________________________________________________"); Serial.println("(*) Don"t send several caracter at the same time."); delay(500); Serial.println(" "); Serial.println("Keyboard: "x"-> EXIT CALIBRATION. "c"-> ENTER CALIBRATION."); Serial.println(" "i"-> PRINT INFORMATION. "); Serial.println(" "); Serial.println(" "n"-> CHANGE MOTOR (+). "b" -> CHANGE MOTOR (-)."); Serial.println(" "m"-> START CALIBRATION."); Serial.println(" "q"-> STOP CALIBRATION."); Serial.println(" "); Serial.println(" "r"-> CHANGE RESOLUTION."); Serial.println(" "p"-> ADD ANGLE. "o"-> SUBTRACT ANGLE. "); Serial.println(" "s"-> SAVE ANGLE."); delay(500); Serial.println(" "); Serial.println("---------------------------------------------------------------------------------------------------"); Serial.print("SELECTED MOTOR: "); Serial.print(motorTag[motor]); Serial.print(". SELECTED RESOLUTION: "); Serial.println(res); while (CAL == true) { if (Serial.available() > 0) { cm = Serial.read(); if (cm == "x") { Serial.println("Closing CALIBRATION program..."); CAL = false; secuence = false; startDisplay(PAGE); angleBody = 90; anglesIKFR.tetta = 0.0; anglesIKFR.alpha = -45.0; anglesIKFR.gamma = 90.0; anglesIKFL.tetta = 0.0; anglesIKFL.alpha = -45.0; anglesIKFL.gamma = 90.0; anglesIKBR.tetta = 0.0; anglesIKBR.alpha = 45.0; anglesIKBR.gamma = -90.0; anglesIKBL.tetta = 0.0; anglesIKBL.alpha = 45.0; anglesIKBL.gamma = -90.0; } else if (cm == "i") { // + Serial.println(" "); Serial.println("---------------------------------------------------------------------------------------------------"); Serial.println("---------------------------------------------------------------------------------------------------"); Serial.println("(*) Don"t send several caracter at the same time."); delay(500); Serial.println(" "); Serial.println("Keyboard: "x"-> EXIT CALIBRATION. "c"-> ENTER CALIBRATION."); Serial.println(" "i"-> PRINT INFORMATION. "); Serial.println(" "); Serial.println(" "n"-> CHANGE MOTOR (+). "b" -> CHANGE MOTOR (-)."); Serial.println(" "m"-> START CALIBRATION."); Serial.println(" "q"-> STOP CALIBRATION."); Serial.println(" "); Serial.println(" "r"-> CHANGE RESOLUTION."); Serial.println(" "p"-> ADD ANGLE. "o"-> SUBTRACT ANGLE. "s"-> SAVE ANGLE."); Serial.println(" "); delay(500); Serial.println(" "); Serial.println("---------------------------------------------------------------------------------------------------"); Serial.println(" "); Serial.print("SELECTED MOTOR: "); Serial.print(motorTag[motor]); Serial.print(". SELECTED RESOLUTION: "); Serial.println(res); Serial.println("Actual parameters of the motor: "); Serial.print("High limit: "); Serial.print(highLim[motor]); Serial.print(" Low limit: "); Serial.print(lowLim[motor]); Serial.print(" Angle 1: "); Serial.print(ang1[motor]); Serial.print(" Angle 2: "); Serial.println(ang2[motor]); Serial.println("---------------------------------------------------------------------------------------------------"); } else if (cm == "m") { // + secuence = true; } else if (cm == "s") { // + } else if (cm == "n") { // + motor++; if (motor >= 13) { motor = 0; } Serial.print("SELECTED MOTOR: "); Serial.println(motorTag[motor]); } else if (cm == "b") { // + motor--; if (motor < 0) { motor = 13 - 1; } Serial.print("SELECTED MOTOR: "); Serial.println(motorTag[motor]); } else if (cm == "r") { // + if (res == ares) { res = bres; } else if (res == bres) { res = cres; } else if (res == cres) { res = ares; } Serial.print("SELECTED RESOLUTION: "); Serial.println(res); } } if (secuence == true) { Serial.print("Starting secuence for motor: "); Serial.println(motorTag[motor]); for (int i = 0; i <= 30; i++) { delay(20); Serial.print("."); } Serial.println("."); while (question == true) { unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 100000) { previousMicros = currentMicros; if (Serial.available() > 0) { answer = Serial.read(); if (answer == "y") { question = false; interp = true; secuence = true; } else if (answer == "n") { question = false; interp = false; secuence = true; } else { Serial.println("Please, select Yes(y) or No(n)."); } } } } answer = "t"; question = true; if (interp == false) { Serial.println("___"); Serial.println(" | Place motor at 1ts position and save angle"); Serial.println(" | This position can be the higher one"); rawAngle , motorPin = motorInfo(motor); calibrationAngle = 90; //start calibration at aproximate middle position of the servo. while (secuence == true) { /* find first calibration angle */ if (Serial.available() > 0) { cm = Serial.read(); if (cm == "p") { // + Serial.print(" | +"); Serial.print(res); Serial.print(" : "); calibrationAngle = calibrationAngle + res; servoWrite(motorPin , calibrationAngle); Serial.println(calibrationAngle); } else if (cm == "o") { // - Serial.print(" | -"); Serial.print(res); Serial.print(" : "); calibrationAngle = calibrationAngle - res; servoWrite(motorPin , calibrationAngle); Serial.println(calibrationAngle); } else if (cm == "r") { // + if (res == ares) { res = bres; } else if (res == bres) { res = cres; } else if (res == cres) { res = ares; } Serial.print("SELECTED RESOLUTION: "); Serial.println(res); } else if (cm == "q") { // quit secuence secuence = false; Serial.println(" | Calibration interrupted!!"); } else if (cm == "s") { // save angle ang1[motor] = calibrationAngle; secuence = false; Serial.print(" | Angle saved at "); Serial.println(calibrationAngle); } } } if (cm == "q") { Serial.println(" |"); } else { secuence = true; Serial.println("___"); Serial.println(" | Place motor at 2nd position and save angle"); Serial.println(" | This position can be the lower one"); } while (secuence == true) { /* find second calibration angle */ if (Serial.available() > 0) { cm = Serial.read(); if (cm == "p") { // + Serial.print(" | +"); Serial.print(res); Serial.print(" : "); calibrationAngle = calibrationAngle + res; servoWrite(motorPin , calibrationAngle); Serial.println(calibrationAngle); } else if (cm == "o") { // - Serial.print(" | -"); Serial.print(res); Serial.print(" : "); calibrationAngle = calibrationAngle - res; servoWrite(motorPin , calibrationAngle); Serial.println(calibrationAngle); } else if (cm == "r") { // + if (res == ares) { res = bres; } else if (res == bres) { res = cres; } else if (res == cres) { res = ares; } Serial.print("SELECTED RESOLUTION: "); Serial.println(res); } else if (cm == "q") { // quit secuence secuence = false; Serial.println(" | Calibration interrupted!!"); } else if (cm == "s") { // save angle ang2[motor] = calibrationAngle; secuence = false; Serial.print(" | Angle saved at "); Serial.println(calibrationAngle); } } } /*--------------------start calibration calculations------------------*/ if (cm == "q") { Serial.println("___|"); Serial.println("Calibration finished unespected."); Serial.println(" Select another motor."); Serial.print("SELECTED MOTOR: "); Serial.print(motorTag[motor]); Serial.print(". SELECTED RESOLUTION: "); Serial.println(res); } else { Serial.println("___"); Serial.println(" |___"); Serial.print( " | | Interpolating for motor: "); Serial.println(motorTag[motor]); secuence = true; //real angle is calculated interpolating both angles to a linear relation. a[motor] = (ang2[motor] - ang1[motor]) / (x2[motor] - x1[motor]); b[motor] = ang1[motor] - x1[motor] * (ang2[motor] - ang1[motor]) / (x2[motor] - x1[motor]); Serial.println(" | |"); } interp = true; } /*---------------------------make swing movement to interpolate motor encoder-----*/ if (interp == true and secuence == true) { delay(200); double x; int k = 0; int stp = 180; swing = true; i = 0; orawAngle , motorPin = motorInfo(motor); previousMicros = 0; while (swing == true) { // FIRST unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x2[motor]; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 3) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } swing = true; i = 0; while (swing == true) { // moving unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x2[motor] + float(i) * (x1[motor] - x2[motor]) / stp; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 6) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } swing = true; i = 0; while (swing == true) { // SECOND unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x1[motor]; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 3) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } swing = true; i = 0; while (swing == true) { // moving unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x1[motor] + float(i) * (x2[motor] - x1[motor]) / stp; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 6) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } swing = true; i = 0; while (swing == true) { // FIRST unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x2[motor]; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 3) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } swing = true; i = 0; while (swing == true) { // moving unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x2[motor] + float(i) * (x1[motor] - x2[motor]) / stp; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 6) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } swing = true; i = 0; while (swing == true) { // SECOND unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x1[motor]; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); if ((i % 3) == 0) { yi[k+1] = x; xi[k] = rawAngle; Serial.print(" | | Real ang: "); Serial.print(x); Serial.print(" -> Servo ang: "); Serial.print(calibrationAngle); Serial.print(" Enc: "); Serial.println(rawAngle); k++; } if (i >= stp) { swing = false; } i++; } } Serial.println(" | | Interpolation finished!"); /*-------Calculate linear interpolation of the encoder from 60 meassures done in swing------*/ double sx = 0; double sy = 0; double sx2 = 0; double sy2 = 0; double sxy = 0; double xmean = 0; double ymean = 0; int n = 300; for (int i = 0 ; i < n ; i++) { sx += xi[i+10]; sy += yi[i+10]; sx2 += xi[i+10] * xi[i+10]; sy2 += yi[i+10] * yi[i+10]; sxy += xi[i+10] * yi[i+10]; } ae[motor] = (n * sxy - sx * sy) / (n * sx2 - sx * sx); //sxy / sx2; // be[motor] = (sy - ae[motor] * sx) / n; //ymean - ae[motor] * xmean; Serial.println(" | | Moving back to ZERO position."); // turn the motor back to middle position swing = true; i = 0; while (swing == true) { unsigned long currentMicros = micros(); if (currentMicros - previousMicros >= 10000) { // save the last time you blinked the LED previousMicros = currentMicros; x = x1[motor] + float(i) * (90 - x1[motor]) / 60; calibrationAngle = a[motor] * x + b[motor]; servoWrite(motorPin , calibrationAngle); rawAngle , motorPin = motorInfo(motor); eang = ae[motor] * rawAngle + be[motor]; if ((i % 4) == 0) { Serial.print(" | | Servo ang: "); Serial.print(calibrationAngle); Serial.print(" -> Real ang: "); Serial.print(x); Serial.print(" -> Encoder ang: "); Serial.println(eang); } if (i >= 60) { swing = false; } i++; } } Serial.println("___|___|"); Serial.println(" | "); Serial.println("___"); Serial.println(" | Calibration finished satisfactory. Results data:"); Serial.print(" | HIGH lim: "); Serial.print(highLim[motor]); Serial.print(" LOW lim: "); Serial.println(lowLim[motor]); Serial.print(" | angle 1: "); Serial.print(ang1[motor]); Serial.print(" angle 2 "); Serial.println(ang2[motor]); Serial.print(" | Regression Motor a: "); Serial.print(a[motor], 5); Serial.print(" b: "); Serial.println(b[motor], 5); Serial.print(" | Regression Encoder a: "); Serial.print(ae[motor], 5); Serial.print(" b: "); Serial.println(be[motor], 5); Serial.println(" |"); Serial.println(" | ______________________________________________________________"); Serial.println(" | | |"); Serial.println(" | | This code won"t be able to save the updated parameters |"); Serial.println(" | | once the robot is shutted down. |"); Serial.println(" | | |"); Serial.println(" | | Please, write down the results |"); Serial.println(" | | and save them in the definition of each variable. |"); Serial.println(" | |_____________________________________________________________|"); Serial.println(" |"); Serial.println("___|"); Serial.println(" Select another motor."); Serial.print("SELECTED MOTOR: "); Serial.print(motorTag[motor]); Serial.print(". SELECTED RESOLUTION: "); Serial.println(res); } interp = false; secuence = false; } } SAFE = false; Serial.println("Calibration killed"); } // END OF CALIBRATION
If you would like to build a web server displaying readings from multiple sensors, follow the next tutorial: ESP32 Plot Sensor Readings in Charts (Multiple DS18B20 Sensors)