chips which operate at logic levels of 3.3v will usually have a VCC input power level of 3.3V.
Chips which operate at logic levels of 5.0v will usually have a VCC input power level of 5.0V.
The Teensy operates with an input 5V on the USB and VIN because there is a 3.3V regulator which regulates that down to 3.3V operating level.
The L298N is a 5V part but its logic is an INPUT so the 3.3V output of the Teensy can work with it because the low threshold level which is still considered a high level is around 2.5V and that’s below 3.3V. If the L298N had an OUTPUT which was 5V level you would likely kill the Teensy connecting that OUTPUT to a Teensy INPUT.
If I take the jumper off my driver board, it will take 5v INPUT. And if the teensy is hooked into USB power, the 5v pin on the teensy functions as a 5v out, correct? So, could I use this configuration to test the teensy and motor using only USB power? I believe this is the setup that was correctly reversing the motor for me before… (minus taking the driver board jumper out–which maybe doesn’t matter if there is no external power anyway?)
My thought is to verify and debug the software using only usb power, for simplicity. The motor runs slow, but it works. And then re-wire to test the power of the motor (which has to wait for me to finish building the mechanism…) And by re-wiring I mean, putting the jumper back on the driver board, and cutting the teensy power trace.
But-- If I am careful never to plug into USB and 12v at the same time, do I actually need to cut the power trace?
The 5V jumper on the L298N connects the onboard 5V regulator to the logic side of the board AND it provides a pin with 5V on it which could be used for ______ something needing 5V. You do not have to use that 5V output of the L298N but you can leave the jump on to let it power the onboard logic and therefore you would not need to provide 5V to the L298N. But you will need to connect some power to the L298N to power the motors and the onboard 5V regulator.
On the Teensy side, just power the board with USB like you normally would do and just connect your logic outputs and a ground wire to the L298N.
The motors should work well if you provide enough voltage/power to the motor power input pins on the L298N and the Teensy is fine to handle toggling the inputs on the L298N to make the motors so fast/slow/forwards/backwards.
After you figure out what you want to use, you can decide on how all the voltages are going to be used to power the Teensy and the L298N(either just motors or motors and 5V logic).
If you remove the jumper on the L298N board that connects the voltage regulator to the L298N logic supply, then you can use 5V on USB to supply both the teensy and the L298N logic. It will not power the motors; that’s separate 12V.
If you cut the trace, then the teensy will always be powered from the 12V through the 5V regulator on the L298N board, even if you are connected to USB.
If you don’t cut the trace, then don’t connect the jumper on the L298N board that supplies 5V from 12V.
If that isn’t clear, ask more questions before attaching power.
Why not just let the L298N power its own logic and just use the Teensy to drive the control inputs?
You don’t need to cut the Teensy 5V isolation trace, you can just power it off USB while debugging and when you are ready for operation, remove USB power, connect up 5V to VIN and go to town. If you need to remind yourself, put a piece of tap over the USB port and write yourself a note to remove the VIN power or verify it is unpowered before connecting USB? Or am I missing something?
This makes sense to me. It’s easy enough to pull that 5v wire out, until I need to run it without USB… I will want to keep a ground wire running between the two boards, though, correct?
Sorry for my ignorance… it just gives me pause, connecting the grounds between two different power sources…
It seems like most of the software I’ll need is here: GitHub - misan/dcservo: Position control of DC motors. I have never tried to upload multiple files to the teensy. And I’m not sure what the non-ino files do either. So time to try to get up to speed on that…
oh, I guess I should first figure out a test for the encoders.
I’ll need to check out the DCServo code but IIRC the ProMini doesn’t have enough timers to use interrupts on inputs(Step/Dir) and encoders(ChanA/ChanB). I don’t know if the accuracy is needed for your task but I really liked how the originator described in detail his task in the Instructable( 3 AXIS CNC PLOTTER FROM DC MOTORS AND OPTICAL ENCODERS : 15 Steps (with Pictures) - Instructables ) and more importantly explains in “Step 10: ARDUINO CODE” the use of interrupts and expected performance with or without using interrupts:
Best Performance: Both signals connect to interrupt pins.
Good Performance: First signal connects to an interrupt pin, second to a non-interrupt pin.
Low Performance: Both signals connect to non-interrupt pins.
My hack of his code required I find that Arduino Hat and instead of I2C to control the HAT, directly control an L298N. There’s lots of good stuff in his Instructable explanation of the project no matter it not being the same as yours or my project.
I’m going to need a more robust mounting solution for the motor, but it does work. A more powerful motor allows for more string tension, but this one can manage pretty well. I also need a stronger spring. The mechanism is pretty noisy, but I’m hoping that a delrin nut will help with that. I had to use the brass nut, as I got the wrong tap from ebay. Another one is coming, but it will be a few weeks from China.
Reading up on the processor Frank used, the STM32L432K, it looks like it supports arduino out of the box:
so that part is maybe simpler than I thought. It says the IO is equivalent to the nano, would that include the interrupt options?
Looks like the teensy supports interrupts on most pins… but is there still a total limit? Looks like I should not use pins 0 and 1 for serial, as those do not support interrupts. But there are several other serial options on the board that do.
So I am trying to tease out what’s going on. I don’t see any interrupt logic, although it’s mentioned in the code comments… EDIT: found it. I was expecting a method…
but it looks like this code uses a single motor control line. Easy enough to change, but I wonder how that is wired… it’s a different motor driver chip, for sure…
int pwmPin = 16;
int motor1pin1 = 17;
int motor1pin2 = 18;
int encoderPin1 = 8;
int encoderPin2 = 9;
int value = 0;
volatile long encoderPos = 0;
void setup() {
// put your setup code here, to run once:
pinMode(motor1pin1, OUTPUT);
pinMode(motor1pin2, OUTPUT);
pinMode(encoderPin1, INPUT);
pinMode(encoderPin2 , INPUT);
Serial.begin(9600); // Other baud rates can be used...
Serial.println("setting up");
attachInterrupt(encoderPin1, doEncoderMotor, CHANGE); // encoderA pin on interrupt
}
void loop() {
// put your main code here, to run repeatedly:
//value = analogRead(wiperPin);
//Controlling speed (0 = off and 255 = max speed):
analogWrite(pwmPin, 255); //ENA pin
//Controlling spin direction of motors:
digitalWrite(motor1pin1, HIGH);
digitalWrite(motor1pin2, LOW);
delay(80);
digitalWrite(motor1pin1, LOW);
digitalWrite(motor1pin2, LOW);
delay(1000);
digitalWrite(motor1pin1, LOW);
digitalWrite(motor1pin2, HIGH);
delay(80);
digitalWrite(motor1pin1, LOW);
digitalWrite(motor1pin2, LOW);
delay(1000);
}
void doEncoderMotor(){
if (digitalRead(encoderPin1) == HIGH) { // found a low-to-high on channel A
if (digitalRead(encoderPin2) == LOW) { // check channel B to see which way encoder is turning
encoderPos = encoderPos - 1; // CCW
}
else {
encoderPos = encoderPos + 1; // CW
}
}
else // found a high-to-low on channel A
{
if (digitalRead(encoderPin2) == LOW) { // check channel B to see which way encoder is turning
encoderPos = encoderPos + 1; // CW
}
else {
encoderPos = encoderPos - 1; // CCW
}
}
Serial.print(value);
value = value + 1;
Serial.print("-Encoder Value: ");
Serial.println(encoderPos);
}
This is all working and writing to the serial monitor (cool!) but encoderPos never really moves:
I wonder whether the author of the code you were referencing didn’t need it because both pins were on the same port so it was actually firing on every change on the port. I find that code hard to reason about.
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) (recommended) attachInterrupt(interrupt, ISR, mode) (not recommended) attachInterrupt(pin, ISR, mode) (Not recommended. Additionally, this syntax only works on Arduino SAMD Boards, Uno WiFi Rev2, Due, and 101.)
Note that it recommends digitalPinToInterrupt() because there are fewer interrupts than pins; interrupts are per port not per pin. You might print out (or look up) the values of digitalPinToInterrupt(encoderPin1) and digitalPinToInterrupt(encoderPin2) — if they are different, it would matter, but they might have been the same on the board being used by the author of the reference.
Also, that doEncoderMotor() handler won’t notice missed values. There is a general approach to gray code decoders that handle different lengths (here, it’s just length 2) that is clearer.
I have not tested this, but I’d expect something like this to be easier to reason about:
void doEncoderMotor() {
static int previous = 0;
int gray = (digitalRead(encoderPin2)<<1) | digitalRead(encoderPin1);
int cur = gray ^ gray >> 1; // convert to binary
if abs(cur-previous) > 1 {
Serial.println("Lost encoder step");
}
// cur-previous == 0 does not imply missing 4 steps; there could be other interrupts on the same port
encoderPos += cur-previous;
previous = cur
}
Pin reading vs. Port reading
Note that the two digital reads are separated in time. As long as the interrupt handler is fast enough relative to motor speed, you will be fine. If I were an embedded engineer working on this for production, I’d be ensuring that both pins for each encoder were on adjacent bits on the same processor IO port, and I’d be doing a single port read and masking out the pins. In pseudo-code, if they were the third and fourth pins on portB, it would be something like gray = portB & 0xc >> 2. This is something that Arduino’s abstraction away from ports can hide, by pretending that all the pins are individually addressable from code.
The ports are still accessible, but it does make code less portable across Arduino board implementations; you need to look up the documentation for the exact implementation you are using. Here’s an article covering original arduino hardware, that won’t apply directly to any particular teensy:
You’d have to look into how to do the same for the teensy.
If you eventually choose to use the port read method, you’ll need to look into the documentation the particular Teensy you are using to find out how to do whole port reads, and you’ll need to do that before deciding on the final pins to use.
I’ll dig in here. I never did get bit-shifting, I’ll try again…
THANKS!!
Edit: wow, Gray code is like binary on acid! I am seriously tripping out…
Edit: I said that BEFORE I read about it as a traversal of vertices of a tesseract, or the image of it on the number line. It always amazes me when a search for a practical solution yields something this beautiful.
Edit: Reading about ports: Well, I didn’t expect so little code portability, but the process of figuring this out for teensy will no doubt help with whatever processor I end up using. I liked the solution I pulled the code from, which uses the STM32, in that it is extremely compact. But I don’t necessarily need to be THAT compact…
Well, you just saw where the Processing / Wiring abstraction breaks down. It’s amazing how well it does work, but sometimes you can see through the cracks that the hardware differs from one implementation to another.
I was wrong to try to use arithmetic. That was silly of me. But I do think that it would be more robust comparing old to new values instead of assuming that it owns the interrupts.
Again I’m not testing anything, just writing off the cuff without thinking through, but something like this?
void doEncoderMotor() {
static int prev_gray = 0;
int gray = (digitalRead(encoderPin2)<<1) | digitalRead(encoderPin1);
if gray == prev_gray {
// spurious interrupt?
return
}
if gray == (~prev_gray | 0x3) {
// off by two positions; missed a step or just booted and read the default
if encoderPos == 0 {
// probably happened to boot at this reading
// ignore possibility of a lost encoder step at exactly position 0
prev_gray = gray;
}
// Otherwise, if cur and previous are inverse, we have missed an intermediate step
Serial.println("Lost encoder step");
// here you have to decide whether you want an error
return
}
if ( !(prev_gray & 1) && (gray & 1) ) {
if (gray & 2) {
encoderPos--;
} else {
encoderPos++;
}
} else {
if (gray & 2) {
encoderPos++;
} else {
encoderPos--;
}
}
prev_gray = gray;
}
well, if the logic is solid I can probably get it to compile. But understanding the logic is a whole other thing… I was trying to find info on how to attach the interrupts on the teensy, since it seems like it’s dependent on the hardware.
I read the attach interrupt page on the teensy, and I really can’t follow it–the use example is too dissimilar, and I couldn’t figure out how to read the table. But then I found this: https://www.pjrc.com/teensy/td_libs_Encoder.html
Using the example, I wrote this code:
#include <Encoder.h>
int pwmPin = 16;
int motor1pin1 = 17;
int motor1pin2 = 18;
int encoderPin1 = 8;
int encoderPin2 = 9;
Encoder enc(encoderPin1,encoderPin2);
void setup() {
// put your setup code here, to run once:
pinMode(motor1pin1, OUTPUT);
pinMode(motor1pin2, OUTPUT);
Serial.begin(9600); // Other baud rates can be used...
Serial.println("encoder test");
//attachInterrupt(encoderPin1, doEncoderMotor, CHANGE); // encoderA pin on interrupt
}
long encPosition = -999;
void loop() {
// put your main code here, to run repeatedly:
long newEnc;
newEnc = enc.read();
if (newEnc != encPosition) {
Serial.print("position = ");
Serial.print(newEnc);
Serial.println();
encPosition = newEnc;
}
//value = analogRead(wiperPin);
//Controlling speed (0 = off and 255 = max speed):
analogWrite(pwmPin, 255); //ENA pin
//Controlling spin direction of motors:
digitalWrite(motor1pin1, HIGH);
digitalWrite(motor1pin2, LOW);
delay(100);
digitalWrite(motor1pin1, LOW);
digitalWrite(motor1pin2, LOW);
delay(1000);
digitalWrite(motor1pin1, LOW);
digitalWrite(motor1pin2, HIGH);
delay(100);
digitalWrite(motor1pin1, LOW);
digitalWrite(motor1pin2, LOW);
delay(1000);
}
But I am only getting values of 0 or 1 in the serial output. I’m wondering if the encoder on this motor even works, or if it works the way anybody expects an encoder to work. Maybe I should buy a motor and encoder from polulu. I need to try their stronger motor anyway.
EDIT: it occurs to me I should test that the encoder is putting out square waves. Any thoughts about how to do that without an oscilloscope? I don’t seem to have frequency on my multi-meter.
EDIT: wondering if the documentation on the encoder is wrong. It sure seems strange that the black wire is +.
okay, tried to measure the outputs of the encoder, and one of them broke off. I think these were the last two JST connectors I did before I learned not to squeeze the crimper all the way, but only the minimum until it releases…
Sucess!!! Wonderful values in the serial monitor. So now on to PID, I guess…
This is starting to feel like something that might eventually work