liangquan 发表于 2016-11-23 11:19:51

Arduino UNO读绝对值编码器的SSI信号

从淘宝上买了一个绝对值编码器,其链接如下:

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.ErjOfl&id=532121303239&_u=niluasi6bca

经过在论坛上询问,得知应该用SSI信号来读取该编码器的角度。对SSI信号不了解,通过查找资料,才用如下方法进行读取。

1. 电路图:


2. 程序代码
int pinCLK = 11;// OC2A上输出脉冲,对于Arduino UNO来说,OC2A引脚是11号(PB3)
int pinCSn = 10;// 使能
int pinD0 = 9;    // 资料
unsigned int value = 0;
unsigned int valueTmp = 0;
int j = 0;
void setup()
{
cli();// 停止中断

Serial.begin(9600);
pinMode(pinCLK, OUTPUT);
pinMode(pinCSn, OUTPUT);
pinMode(pinD0, INPUT);

// 用定时器2发送脉冲,应该采用比较匹配输出模式,non-PWM 模式,比较匹配时OC2A清零
TCCR2A = 1 << COM2A1 | 1 << WGM21;
TCCR2B = 1 << CS21 | 1 << CS20;   // 内部时钟,32分频(16M/32 = 0.5MHz),CTC模式
TCNT2 = 0x00;   // 初始化计数值
/*
      0.5MHz/100000Hz = 5
*/
OCR2A = 0x4;// OCR2 = 0x4(4) ,(4+1)*(1/0.5MHz) = 10us,也就是发出的脉冲周期是10us

TIMSK2 |= (1 << OCIE2A);// 允许定时器比较匹配中断

sei();// 允许中断
}

void loop()
{
delay(100);
getPosition();// 取得编码器当前角度值
}

// 中断
ISR(TIMER2_COMPA_vect)
{
// 每当比较匹配时,发送一个负缘信号
j++;
value = ((value|digitalRead(pinD0)) << 1);// 当发送一个负缘信号时,读取D0位的值
// 间隔10us送10个正负变化
if (j == 10)
{
    cli();    // 停止中断

    digitalWrite(pinCSn, HIGH);
    digitalWrite(pinCLK, HIGH);
    delayMicroseconds(1); // 延时1us

    digitalWrite(pinCSn, LOW);
    delayMicroseconds(1); // 延时1us
   
    valueTmp = ((value >> 1) & 0x3FF);// 多移动了一位,再移动回来,然后与10为1相与,实际上是取低10位
    value = 0;

    sei();    // 允许中断
}
}
/*
// 格雷码转换成二进制
const unsigned int GraytoDecimal(unsigned int x)
{
int i;
for(i=0;(1<<i)<sizeof(x)*8;i++)
 {
  x ^= x >> (1 << i);
 }
return x;
}
*/

// 将二进制格雷码转换成二进制编码
void getPosition()
{
int i;
float angle = 0.0;
unsigned int x = valueTmp;

for (i=0; (1<<i)<sizeof(x)*8; i++)
{
    x^=x>>(1<<i);
}

//x = GraytoDecimal(x);
angle = 360.0 * x / 1024;
Serial.println(angle);
}


3. 运行结果


我什么地方出了错误,得不到正确结果呢?

croma 发表于 2016-11-24 00:49:18

本帖最后由 croma 于 2016-11-24 00:54 编辑

你 setup 有執行嗎?
中斷的寫法好怪 @@"
你是想 10us 執行一次中斷嗎? 似乎太快了

ISR(TIMER2_COMPA_vect)
{
    cli();    // 停止中斷

    valueTemp = 0;
    digitalWrite(pinCSn, HIGH);
    digitalWrite(pinCLK, HIGH);
    delayMicroseconds(1); // 延時1us

    digitalWrite(pinCSn, LOW);
    delayMicroseconds(1); // 延時1us

    digitalWrite(pinCLK, LOW);
    delayMicroseconds(1); // 延時1us
    digitalWrite(pinCLK, HIGH);

    // 讀取角度資料
    for(int i = 0; i < 10; ++i) {
      delayMicroseconds(1); // 延時1us
      digitalWrite(pinCLK, LOW);
      valueTemp <<= 1;
      valueTemp |= digitalRead(pinD0);
      digitalWrite(pinCLK, HIGH);
    }
    value = valueTemp;
   
    // 接著讀狀態資料
    for(int i = 0; i < 6; ++i) {
      delayMicroseconds(1); // 延時1us
      digitalWrite(pinCLK, LOW);
      valueTemp <<= 1;
      valueTemp |= digitalRead(pinD0);
      digitalWrite(pinCLK, HIGH);
    }
    digitalWrite(pinCSn, HIGH);
   
    sei();    // 允許中斷
}

liangquan 发表于 2016-11-24 12:21:45

croma 发表于 2016-11-24 00:49
你 setup 有執行嗎?
中斷的寫法好怪 @@"
你是想 10us 執行一次中斷嗎? 似乎太快了


谢谢您的指导,真是太感谢了!

我原来的思路是用定时中断产生CLK的周期脉冲,而您的程序是用for循环+delay来产生脉冲,我没有想到。

按您的思路我重新编写了程序:

int pinCLK = 12;// 时钟
int pinCSn = 10;// 使能
int pinD0 = 9;    // 资料
unsigned int valueTmp = 0;
unsigned int value = 0;
void setup()
{
Serial.begin(9600);
pinMode(pinCLK, OUTPUT);
pinMode(pinCSn, OUTPUT);
pinMode(pinD0, INPUT);

// 用定时器2定时中断,采用比较匹配输出模式,CTC模式,NORMAL port operation
TCCR2A = 1 << WGM21;
TCCR2B = 1 << CS22 | 1 << CS21 | 1 << CS20;   // 内部时钟,1024分频(16M/1024 = 15625),CTC模式
TCNT2 = 0x00;   // 初始化计数值
/*
      15625/100Hz(10ms) = 156.25 = 156
*/
OCR2A = 0x9B;// OCR2A = 0x9B(155) ,(155+1)*(1/15625) = 10ms,也就是定时中断的周期是10ms

TIMSK2 |= (1 << OCIE2A);// 允许定时器比较匹配中断
}

void loop()
{
delay(1000);
getPosition();// 取得编码器当前角度值
}

// 中断
ISR(TIMER2_COMPA_vect)
{
cli();    // 停止中断

valueTmp = 0;
digitalWrite(pinCSn, HIGH);
digitalWrite(pinCLK, HIGH);
delayMicroseconds(1);   // 延时1us

digitalWrite(pinCSn, LOW);
delayMicroseconds(1);   // 延时1us

digitalWrite(pinCLK, LOW);
delayMicroseconds(1);   // 延时1us
digitalWrite(pinCLK, HIGH);

// 读取角度资料
for(int i=0; i < 10; i++)
{
    delayMicroseconds(1);
    digitalWrite(pinCLK, LOW);
    valueTmp <<= 1;
    valueTmp |= digitalRead(pinD0);   
    digitalWrite(pinCLK, HIGH);
}

value = valueTmp;

// 接着读状态能资料
for(int i=0; i<6; i++)
{
    delayMicroseconds(1);
    digitalWrite(pinCLK, LOW);
    valueTmp <<= 1;
    valueTmp |= digitalRead(pinD0);
    digitalWrite(pinCLK, HIGH);
}
digitalWrite(pinCLK, HIGH);

sei();    // 允许中断
}

// 获取角度值
void getPosition()
{
int i;
float angle = 0.0;
unsigned int x = value;

// 将二进制格雷码转换成二进制编码
for (i=0; (1<<i)<sizeof(x)*8; i++)
{
    x^=x>>(1<<i);
}

angle = 360.0 * x / 1024;
Serial.println(angle);
}


这回能读到0~360°的角度值了,但是还是有问题!!!

如下:
1. 数据不稳定,如下图,有波动,按说明书上的写,应该很稳定;


2. 我尝试向一个方向转动编码器(从编码器尾部看是逆时针转动编码器),数据不连续,如下图,我只转动很小的角度,却一下从125°跳到349°;


3. 再转动,又从349跳到288


4. 我是向一个方向转动的编码器,可是数据一会减、一会增,如下两图:



---------------------------------------------------------------
我觉得程序还有小问题,但不大,您还能帮帮我么?

croma 发表于 2016-11-25 00:54:11

編碼器只說是 1024 階的絕對值,好像沒提過是格雷碼,直接顯示 value 看看會不會在 0~1024間循環

liangquan 发表于 2016-11-25 08:53:22

本帖最后由 liangquan 于 2016-11-25 08:54 编辑

croma 发表于 2016-11-25 00:54
編碼器只說是 1024 階的絕對值,好像沒提過是格雷碼,直接顯示 value 看看會不會在 0~1024間循環

太感谢您了,问题终于解决了!!!

谢谢您一步一步、不厌其烦的指导,终于实现正确的读数了!!!原来是我画蛇添足,非要进行一下格雷码的转换。

下面是成功的程序:
/*
* 尝试增加脉冲频率的周期,由1us变为2us
* 改周期没能解决问题,尝试取消格雷码转换
*/
int pinCLK = 12;// 时钟
int pinCSn = 10;// 使能
int pinD0 = 9;    // 资料
unsigned int valueTmp = 0;
unsigned int value = 0;
void setup()
{
Serial.begin(9600);
pinMode(pinCLK, OUTPUT);
pinMode(pinCSn, OUTPUT);
pinMode(pinD0, INPUT);

// 用定时器2定时中断,采用比较匹配输出模式,CTC模式,NORMAL port operation
TCCR2A = 1 << WGM21;
TCCR2B = 1 << CS22 | 1 << CS21 | 1 << CS20;   // 内部时钟,1024分频(16M/1024 = 15625),CTC模式
TCNT2 = 0x00;   // 初始化计数值
/*
      15625/100Hz(10ms) = 156.25 = 156
*/
OCR2A = 0x9B;// OCR2A = 0x9B(155) ,(155+1)*(1/15625) = 10ms,也就是定时中断的周期是10ms

TIMSK2 |= (1 << OCIE2A);// 允许定时器比较匹配中断
}

void loop()
{
delay(1000);
getPosition();// 取得编码器当前角度值
}

// 中断
ISR(TIMER2_COMPA_vect)
{
cli();    // 停止中断

valueTmp = 0;
digitalWrite(pinCSn, HIGH);
digitalWrite(pinCLK, HIGH);
delayMicroseconds(1);   // 延时1us

digitalWrite(pinCSn, LOW);
delayMicroseconds(1);

digitalWrite(pinCLK, LOW);
delayMicroseconds(1);
digitalWrite(pinCLK, HIGH);

// 读取角度资料
for(int i=0; i < 10; i++)
{
    delayMicroseconds(1);
    digitalWrite(pinCLK, LOW);
    valueTmp <<= 1;
    valueTmp |= digitalRead(pinD0);   
    digitalWrite(pinCLK, HIGH);
}

value = valueTmp;

// 接着读状态能资料
for(int i=0; i<6; i++)
{
    delayMicroseconds(1);
    digitalWrite(pinCLK, LOW);
    valueTmp <<= 1;
    valueTmp |= digitalRead(pinD0);
    digitalWrite(pinCLK, HIGH);
}
digitalWrite(pinCLK, HIGH);

sei();    // 允许中断
}

// 获取角度值
void getPosition()
{
int i;
float angle = 0.0;
unsigned int x = value;
/*
// 将二进制格雷码转换成二进制编码
for (i=0; (1<<i)<sizeof(x)*8; i++)
{
    x^=x>>(1<<i);
}
*/
angle = 360.0 * value / 1024;
Serial.println(angle);
}


采集界面:


--------------------------------------
对您的帮助再次表示感谢!!!

croma 发表于 2016-11-25 14:18:11

liangquan 发表于 2016-11-25 08:53
太感谢您了,问题终于解决了!!!

谢谢您一步一步、不厌其烦的指导,终于实现正确的读数了!!!原 ...

恭喜你~ 好好玩吧~
页: [1]
查看完整版本: Arduino UNO读绝对值编码器的SSI信号