(自用复现记录) 流程图: 初始化: 【checkMagnetPresence()通过返回值确定磁块位置的准确距离→【ReadRawAngle()读取当前绝对位置角度值信息】→ 进入循环(每个循环都很耗时,所以速度不能超过特定值): 【ReadRawAngle()函数】→【correctAngle()函数,角度值可以计算→【checkQuadrant()函数,通过每次读取的象限,确定是否转过一周】→【refreshDisplay()函数】 、这个项目用的是IIC通信总线,显示屏和编码器连接同一接口。
// 编码器测量旋转角值并显示在显示屏上 // AS5600编码器 #include <Wire.h> //This is for i2C #include <SSD1306Ascii.h> //i2C OLED库 #include <SSD1306AsciiWire.h> //i2C OLED库,GitHub开源可下载 // i2C OLED #define I2C_ADDRESS 0x3C #define RST_PIN -1 SSD1306AsciiWire oled; float OLEDTimer = 0; ///屏幕刷新时间 //I2C pins: //STM32: SDA: PB7 SCL: PB6 //Arduino: SDA: A4 SCL: A5 //--------------------------------------------------------------------------- //Magnetic sensor things int magnetStatus = 0; ///磁块三种状态 (MD, ML, MH) int lowbyte; //raw angle 7:0 word highbyte; //raw angle 7:0 and 11:8 int rawAngle; //final raw angle float degAngle; //raw angle in degrees (360/4096 * [value between 0-4095]) int quadrantNumber, previousquadrantNumber; //quadrant IDs float numberofTurns = 0; //number of turns float correctedAngle = 0; //tared angle - based on the startup value float startAngle = 0; //starting angle float totalAngle = 0; //total absolute angular displacement float previoustotalAngle = 0; //for the display printing void setup() {
Serial./span>begin(115200); //start serial - tip: don't use serial if you don't need it (speed considerations) Wire.begin(); //start i2C Wire.setClock(800000L); //fast clock checkMagnetPresence(); //check the magnet (blocks until magnet is found)校准磁极 ReadRawAngle(); //make a reading so the degAngle gets updated startAngle = degAngle; //update startAngle with degAngle - for taring //------------------------------------------------------------------------------ //OLED 部分 #if RST_PIN >= 0 oled.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN); #else // RST_PIN >= 0 oled.begin(&Adafruit128x32, I2C_ADDRESS); #endif // RST_PIN >= 0 oled.setFont(Adafruit5x7); oled.clear(); //clear display oled.set2X(); //double-line font size - better to read it oled.println("Welcome!"); //print a welcome message oled.println("AS5600"); //print a welcome message delay(3000); OLEDTimer = millis(); //start the timer } void loop() {
ReadRawAngle(); //ask the value from the sensor correctAngle(); //tare the value checkQuadrant(); //check quadrant, check rotations, check absolute angular position refreshDisplay(); //delay(100); //wait a little - adjust it for "better resolution" } // 读取编码器的绝对位置的数值 void ReadRawAngle() {
//7:0 - bits Wire.beginTransmission(0x36); //connect to the sensor Wire.write(0x0D); //figure 21 - register map: Raw angle (7:0) Wire.endTransmission(); //end transmission Wire.requestFrom(0x36, 1); //request from the sensor while(Wire.available() == 0); //wait until it becomes available lowbyte = Wire.read(); //Reading the data after the request //11:8 - 4 bits Wire.beginTransmission(0x36); Wire.write(0x0C); //figure 21 - register map: Raw angle (11:8) Wire.endTransmission(); Wire.requestFrom(0x36, 1); while(Wire.available() == 0); highbyte = Wire.read(); //4 bits have to be shifted to its proper place as we want to build a 12-bit number highbyte = highbyte << 8; //shifting to left //What is happening here is the following: The variable is being shifted by 8 bits to the left: //Initial value: 00000000|00001111 (word = 16 bits or 2 bytes) //Left shifting by eight bits: 00001111|00000000 so, the high byte is filled in //Finally, we combine (bitwise OR) the two numbers: //High: 00001111|00000000 //Low: 00000000|00001111 // ----------------- //H|L: 00001111|00001111 rawAngle = highbyte | lowbyte; //int is 16 bits (as well as the word) //We need to calculate the angle: //12 bit -> 4096 different levels: 360° is divided into 4096 equal parts: //360/4096 = 0.087890625 //Multiply the output of the encoder with 0.087890625 degAngle = rawAngle * 0.087890625; Serial.println(degAngle); //Serial.print("Deg angle: "); //Serial.println(degAngle, 2); //absolute position of the encoder within the 0-360 circle } // 校对编码器的数值,因为编码器是0~360°的位置绝对值,需要计算其的圈数进行累加 void correctAngle() {
//recalculate angle correctedAngle = degAngle - startAngle; //this tares the position 没变动则为0;变动的数值 if(correctedAngle < 0) //if the calculated angle is negative, we need to "normalize" it {
correctedAngle = correctedAngle + 360; //correction for negative numbers (i.e. -15 becomes +345) } else {
//do nothing } //Serial.print("Corrected angle: "); //Serial.println(correctedAngle, 2); //print the corrected/tared angle } // 用相数的变化来计算旋转的圈数,用于校对 void checkQuadrant() {
/* //Quadrants: 4 | 1 ---|--- 3 | 2 */ //Quadrant 1 if(correctedAngle >= 0 && correctedAngle <=90) {
quadrantNumber = 1; } //Quadrant 2 if(correctedAngle > 90 && correctedAngle <=180) {
quadrantNumber = 2; } //Quadrant 3 if(correctedAngle > 180 && correctedAngle <=270) {
quadrantNumber = 3; } //Quadrant 4 if(correctedAngle > 270 && correctedAngle <360) {
quadrantNumber = 4; } //Serial.print("Quadrant: "); //Serial.println(quadrantNumber); //print our position "quadrant-wise" if(quadrantNumber != previousquadrantNumber) //if we changed quadrant {
if(quadrantNumber == 1 && previousquadrantNumber == 4) {
numberofTurns++; // 4 --> 1 transition: CW rotation } if(quadrantNumber == 4 && previousquadrantNumber == 1) {
numberofTurns--; // 1 --> 4 transition: CCW rotation } //this could be done between every quadrants so one can count every 1/4th of transition previousquadrantNumber = quadrantNumber; //update to the current quadrant } //Serial.print("Turns: "); //Serial.println(numberofTurns,0); //number of turns in absolute terms (can be negative which indicates CCW turns) //after we have the corrected angle and the turns, we can calculate the total absolute position totalAngle = (numberofTurns*360) + correctedAngle; //number of turns (+/-) plus the actual angle within the 0-360 range //Serial.print("Total angle: "); //Serial.println(totalAngle, 2); //absolute position of the motor expressed in degree angles, 2 digits } // 初始时对MCU进行锁定,确定其位置进行标定 void checkMagnetPresence() {
//This function runs in the setup() and it locks the MCU until the magnet is not positioned properly // 他的函数在setup()中运行,它锁定MCU,直到磁铁没有正确定位 while((magnetStatus & 32) != 32) //while the magnet is not adjusted to the proper distance - 32: MD = 1 // 磁铁位置是否调整到合适的位置。 {
magnetStatus = 0; //reset reading Wire.beginTransmission(0x36); //connect to the sensor Wire.write(0x0B); //figure 21 - register map: Status: MD ML MH Wire.endTransmission(); //end transmission Wire.requestFrom(0x36, 1); //request from the sensor while(Wire.available() == 0); //wait until it becomes available magnetStatus = Wire.read(); //Reading the data after the request //Serial.print("Magnet status: "); //Serial.println(magnetStatus, BIN); //print it in binary so you can compare it to the table (fig 21) } //Status register output: 0 0 MD ML MH 0 0 0 //MH: Too label magnet - 100111 - DEC: 39 //ML: Too weak magnet - 10111 - DEC: 23 //MD: OK magnet - 110111 - DEC: 55 //Serial.println("Magnet found!"); //delay(1000); } void refreshDisplay() {
if (millis() - OLEDTimer > 100) //chech if we will update at every 100 ms {
if(totalAngle != previoustotalAngle) //if there's a change in the position* {
oled.clear(); //delete the content of the display oled.println(totalAngle); //print the new absolute position OLEDTimer = millis(); //reset timer previoustotalAngle = totalAngle; //update the previous value } } else {
//skip } //*idea: you can define a certain tolerance for the angle so the screen will not flicker //when there is a 0.08 change in the angle (sometimes the sensor reads uncertain values) }
二、AS5600利用数码轮同步转动
1、解释 旋转脉冲编码器,步进电机同步旋转到对应的位置,而编码器起到测量反馈的作用。
2、代码二
#include <Wire.h> //This is for i2C
#include <SSD1306Ascii.h> //i2C OLED
#include <SSD1306AsciiWire.h> //i2C OLED
// i2C OLED
#define I2C_ADDRESS 0x3C
#define RST_PIN -1
SSD1306AsciiWire oled;
float OLEDTimer = 0; //Timer for the screen refresh
//I2C pins:
//STM32: SDA: PB7 SCL: PB6
//Arduino: SDA: A4 SCL: A5
//---------------------------------------------------------------------------
//Magnetic sensor things
int magnetStatus = 0; //value of the status register (MD, ML, MH)
int lowbyte; //raw angle 7:0
word highbyte; //raw angle 7:0 and 11:8
int rawAngle; //final raw angle
float degAngle; //raw angle in degrees (360/4096 * [value between 0-4095])
int quadrantNumber, previousquadrantNumber; //quadrant IDs
float numberofTurns = 0; //number of turns
float correctedAngle = 0; //tared angle - based on the startup value
float startAngle = 0; //starting angle
float totalAngle = 0; //total absolute angular displacement
float previoustotalAngle = 0; //for the display printing
float encoderTimer = 0;
//---------------------------------------------------------------------------
int pinA = PB10; // Pin A of the encoder
int pinB = PB11; // Pin B of the encoder
//CNC Decoder behavior
// CNC编译器特性
//CW rotation: output of B is half square wave delayed from output of A
// CW旋转模式下:B输出比A的输出延迟半个方波
//CCW rotation: output of A is half square wave delayed from output of B
// CCW旋转模式下:A输出比B的输出延迟半个方波
//The pulse generator's output can be DIRECTLY wired to the step and dir pins.
//脉冲发生器的输出可以直接连接到step和dir引脚。
//This means that the microcontroller can be omitted!!! - of course there won't be any feedback then
//这意味着可以省略微控制器!! 当然那时不会有任何反馈
volatile int numberofclicks = 0; //Stores the number of click done by the encoder. 1 turn = 100 clicks
// 存储编码器完成的点击次数。 1回合= 100次点击
int previous_numberofclicks = 0; //Stores the "previous" number of clicks. Helps us to see if the encoder was moved
// 存储“以前”的点击次数。 帮助我们确定编码器是否被移动过
//--Stepper motor related 步进电机相关----------------------------------------------------------
#include <AccelStepper.h>
AccelStepper stepper(1, PA9, PA8);// pulses/steps 9; Direction 8
const int stepperEnablePin = PB12; //enable/disable pin for the stepper motor driver
//remember that for Arduino, you don't need the "PA" and "PB" prefixes. Just use 1,2,3...etc.
void setup()
{
pinMode(pinA, INPUT_PULLUP); //A terminal of the CNC wheel
pinMode(pinB, INPUT_PULLUP); //B terminal of the CNC wheel
attachInterrupt(digitalPinToInterrupt(pinA), pinAInterrupt, RISING); //pin A is an interrupt 低电平变高电平触发,中断函数被触发pinAInterrupt()。
Serial.begin(115200); //start serial - tip: don't use serial if you don't need it (speed considerations)
Wire.begin(); //start i2C
Wire.setClock(800000L); //fast clock
//General remark on i2C: it seems that the i2C interferes with the attachInterrupt() in some way causing
//strange readings if the i2C-related hardware is read too often (in every loop iteration).
// 注意的bug: 似乎i2C以某种方式干扰了attachInterrupt()导致;
// 如果i2c相关的硬件被读得太频繁(在每个循环迭代中),则会出现奇怪的读数。
checkMagnetPresence(); //check the magnet (blocks until magnet is found)
ReadRawAngle(); //make a reading so the degAngle gets updated
startAngle = degAngle; //update startAngle with degAngle - for taring
//------------------------------------------------------------------------------
//OLED part
#if RST_PIN >= 0
oled.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
#else // RST_PIN >= 0
oled.begin(&Adafruit128x32, I2C_ADDRESS);
#endif // RST_PIN >= 0
oled.setFont(Adafruit5x7);
oled.clear(); //clear display
oled.set2X(); //double-line font size - better to read it 建立两行
oled.println("Welcome!"); //print a welcome message
oled.println("AS5600"); //print a welcome message
//Stepper setup---------------------------------------------------------
stepper.setSpeed(1000); //SPEED = Steps / second
stepper.setMaxSpeed(1000); //SPEED = Steps / second
stepper.setAcceleration(5000); //ACCELERATION = Steps /(second)^2
pinMode(stepperEnablePin, OUTPUT); //enable/disable pin is defined as an output
digitalWrite(stepperEnablePin, LOW); //enable motor current
//disabling the current can prevent the driver and the motor running hot
//on the other hand, it can lead to inaccuracies because the motor is not held at place when it is not under power
delay(2000);
OLEDTimer = millis(); //start the timer
encoderTimer = millis(); //start encoder timer
}
void loop()
{
if(millis()- encoderTimer > 125) //125 ms will be able to make 8 readings in a sec which is enough for 60 RPM
//60转每分钟
{
ReadRawAngle(); //ask the value from the sensor
correctAngle(); //tare the value
checkQuadrant(); //check quadrant, check rotations, check absolute angular position
encoderTimer = millis();
/*A little brainstorm on determining the required delay * The above 3 functions require about 300-310 us to finish * They mess up the interrupt of the CNC encoder due to the i2C communication * Therefore it is not good if they are called very often * We want to detect at least every rotations of the shaft * I say (arbitrarily), that we need to detect at least 2 angles in each quadrants, so in 1 turn of the shaft, there are 8 readings * 我说(随意地),我们需要在每个象限中检测至少两个角度,所以在轴的一圈中,有8个读数 * 8 readings per turn can be converted into readings per second based on the expected highest speed * 每轮8个读数可转换为基于预期的最高速度的每秒读数 * Example: * 60 RPM = 60/60 RPS (rounds per seconds) = 1 RPS * 1 round per second -> 8 reading per second -> 1 second/8 readings = 0.125 s = 125 ms is the frequency of readings * * Example 2: * * 100 RPM = 100/60 = 1.667 RPS * 1 round = 0.599 s -> 0.599 s/ 8 readings = 74.98 ~ 75 ms. * Check: 60/100 = 0.6 -> 75/125 = 0.6. */
}
refreshDisplay(); //refresh the display - won't refresh until certain conditions are not fulfilled
// 刷新显示-在某些条件未满足之前不会刷新
while (stepper.distanceToGo() != 0) //This blocks the rest of the code!
{
stepper.runSpeedToPosition(); //Runs to the target position defined by the moveTo() function
//does not use accelerations //运行到moveTo()函数定义的目标位置 ,不使用加速
}
}
void ReadRawAngle()
{
//7:0 - bits
Wire.beginTransmission(0x36); //connect to the sensor
Wire.write(0x0D); //figure 21 - register map: Raw angle (7:0)
Wire.endTransmission(); //end transmission
Wire.requestFrom(0x36, 1); //request from the sensor
while(Wire.available() == 0); //wait until it becomes available
lowbyte = Wire.read(); //Reading the data after the request
//11:8 - 4 bits
Wire.beginTransmission(0x36);
Wire.write(0x0C); //figure 21 - register map: Raw angle (11:8)
Wire.endTransmission();
Wire.requestFrom(0x36, 1);
while(Wire.available() == 0);
highbyte = Wire.read();
//4 bits have to be shifted to its proper place as we want to build a 12-bit number
highbyte = highbyte << 8; //shifting to left
//What is happening here is the following: The variable is being shifted by 8 bits to the left:
//Initial value: 00000000|00001111 (word = 16 bits or 2 bytes)
//Left shifting by eight bits: 00001111|00000000 so, the high byte is filled in
//Finally, we combine (bitwise OR) the two numbers:
//High: 00001111|00000000
//Low: 00000000|00001111
// -----------------
//H|L: 00001111|00001111
rawAngle = highbyte | lowbyte; //int is 16 bits (as well as the word)
//We need to calculate the angle:
//12 bit -> 4096 different levels: 360° is divided into 4096 equal parts:
//360/4096 = 0.087890625
//Multiply the output of the encoder with 0.087890625
degAngle = rawAngle * 0.087890625;
//Serial.print("Deg angle: ");
//Serial.println(degAngle, 2); //absolute position of the encoder within the 0-360 circle
}
void correctAngle()
{
//recalculate angle
correctedAngle = degAngle - startAngle; //this tares the position
if(correctedAngle < 0) //if the calculated angle is negative, we need to "normalize" it
{
correctedAngle = correctedAngle + 360; //correction for negative numbers (i.e. -15 becomes +345)
}
else
{
//do nothing
}
//Serial.print("Corrected angle: ");
//Serial.println(correctedAngle, 2); //print the corrected/tared angle
}
void checkQuadrant()
{
/* //Quadrants: 4 | 1 ---|--- 3 | 2 */
//Quadrant 1
if(correctedAngle >= 0 && correctedAngle <=90)
{
quadrantNumber = 1;
}
//Quadrant 2
if(correctedAngle > 90 && correctedAngle <=180)
{
quadrantNumber = 2;
}
//Quadrant 3
if(correctedAngle > 180 && correctedAngle <=270)
{
quadrantNumber = 3;
}
//Quadrant 4
if(correctedAngle > 270 && correctedAngle <360)
{
quadrantNumber = 4;
}
//Serial.print("Quadrant: ");
//Serial.println(quadrantNumber); //print our position "quadrant-wise"
if(quadrantNumber != previousquadrantNumber) //if we changed quadrant
{
if(quadrantNumber == 1 && previousquadrantNumber == 4)
{
numberofTurns++; // 4 --> 1 transition: CW rotation
}