极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 28300|回复: 6

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

[复制链接]
发表于 2016-11-23 11:19:51 | 显示全部楼层 |阅读模式
从淘宝上买了一个绝对值编码器,其链接如下:

https://item.taobao.com/item.htm ... &_u=niluasi6bca

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

1. 电路图:


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

  10.   Serial.begin(9600);
  11.   pinMode(pinCLK, OUTPUT);
  12.   pinMode(pinCSn, OUTPUT);
  13.   pinMode(pinD0, INPUT);
  14.   
  15.   // 用定时器2发送脉冲,应该采用比较匹配输出模式,non-PWM 模式,比较匹配时OC2A清零
  16.   TCCR2A = 1 << COM2A1 | 1 << WGM21;
  17.   TCCR2B = 1 << CS21 | 1 << CS20;   // 内部时钟,32分频(16M/32 = 0.5MHz),CTC模式
  18.   TCNT2 = 0x00;   // 初始化计数值
  19.   /*
  20.         0.5MHz/100000Hz = 5
  21.   */
  22.   OCR2A = 0x4;  // OCR2 = 0x4(4) ,(4+1)*(1/0.5MHz) = 10us,也就是发出的脉冲周期是10us
  23.   
  24.   TIMSK2 |= (1 << OCIE2A);  // 允许定时器比较匹配中断

  25.   sei();  // 允许中断
  26. }

  27. void loop()
  28. {
  29.   delay(100);
  30.   getPosition();  // 取得编码器当前角度值
  31. }

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

  42.     digitalWrite(pinCSn, HIGH);
  43.     digitalWrite(pinCLK, HIGH);
  44.     delayMicroseconds(1); // 延时1us

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

  50.     sei();    // 允许中断
  51.   }
  52. }
  53. /*
  54. // 格雷码转换成二进制
  55. const unsigned int GraytoDecimal(unsigned int x)
  56. {
  57.   int i;
  58.   for(i=0;(1<<i)<sizeof(x)*8;i++)
  59.  {
  60.   x ^= x >> (1 << i);
  61.  }
  62.   return x;
  63. }
  64. */

  65. // 将二进制格雷码转换成二进制编码
  66. void getPosition()
  67. {
  68.   int i;
  69.   float angle = 0.0;
  70.   unsigned int x = valueTmp;

  71.   for (i=0; (1<<i)<sizeof(x)*8; i++)
  72.   {
  73.     x^=x>>(1<<i);
  74.   }

  75.   //x = GraytoDecimal(x);
  76.   angle = 360.0 * x / 1024;
  77.   Serial.println(angle);
  78. }
复制代码


3. 运行结果


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 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();    // 允許中斷
}
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-11-24 12:21:45 | 显示全部楼层
croma 发表于 2016-11-24 00:49
你 setup 有執行嗎?
中斷的寫法好怪 @@"
你是想 10us 執行一次中斷嗎? 似乎太快了

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

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

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

  1. int pinCLK = 12;  // 时钟
  2. int pinCSn = 10;  // 使能
  3. int pinD0 = 9;    // 资料
  4. unsigned int valueTmp = 0;
  5. unsigned int value = 0;
  6. void setup()
  7. {
  8.   Serial.begin(9600);
  9.   pinMode(pinCLK, OUTPUT);
  10.   pinMode(pinCSn, OUTPUT);
  11.   pinMode(pinD0, INPUT);
  12.   
  13.   // 用定时器2定时中断,采用比较匹配输出模式,CTC模式,NORMAL port operation
  14.   TCCR2A = 1 << WGM21;
  15.   TCCR2B = 1 << CS22 | 1 << CS21 | 1 << CS20;   // 内部时钟,1024分频(16M/1024 = 15625),CTC模式
  16.   TCNT2 = 0x00;   // 初始化计数值
  17.   /*
  18.         15625/100Hz(10ms) = 156.25 = 156
  19.   */
  20.   OCR2A = 0x9B;  // OCR2A = 0x9B(155) ,(155+1)*(1/15625) = 10ms,也就是定时中断的周期是10ms
  21.   
  22.   TIMSK2 |= (1 << OCIE2A);  // 允许定时器比较匹配中断
  23. }

  24. void loop()
  25. {
  26.   delay(1000);
  27.   getPosition();  // 取得编码器当前角度值
  28. }

  29. // 中断
  30. ISR(TIMER2_COMPA_vect)
  31. {
  32.   cli();    // 停止中断

  33.   valueTmp = 0;
  34.   digitalWrite(pinCSn, HIGH);
  35.   digitalWrite(pinCLK, HIGH);
  36.   delayMicroseconds(1);   // 延时1us

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

  39.   digitalWrite(pinCLK, LOW);
  40.   delayMicroseconds(1);   // 延时1us
  41.   digitalWrite(pinCLK, HIGH);

  42.   // 读取角度资料
  43.   for(int i=0; i < 10; i++)
  44.   {
  45.     delayMicroseconds(1);
  46.     digitalWrite(pinCLK, LOW);
  47.     valueTmp <<= 1;
  48.     valueTmp |= digitalRead(pinD0);   
  49.     digitalWrite(pinCLK, HIGH);
  50.   }

  51.   value = valueTmp;

  52.   // 接着读状态能资料
  53.   for(int i=0; i<6; i++)
  54.   {
  55.     delayMicroseconds(1);
  56.     digitalWrite(pinCLK, LOW);
  57.     valueTmp <<= 1;
  58.     valueTmp |= digitalRead(pinD0);
  59.     digitalWrite(pinCLK, HIGH);
  60.   }
  61.   digitalWrite(pinCLK, HIGH);
  62.   
  63.   sei();    // 允许中断
  64. }

  65. // 获取角度值
  66. void getPosition()
  67. {
  68.   int i;
  69.   float angle = 0.0;
  70.   unsigned int x = value;
  71.   
  72.   // 将二进制格雷码转换成二进制编码
  73.   for (i=0; (1<<i)<sizeof(x)*8; i++)
  74.   {
  75.     x^=x>>(1<<i);
  76.   }

  77.   angle = 360.0 * x / 1024;
  78.   Serial.println(angle);
  79. }
复制代码


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

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


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


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


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



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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2016-11-25 00:54:11 | 显示全部楼层
編碼器只說是 1024 階的絕對值,好像沒提過是格雷碼,直接顯示 value 看看會不會在 0~1024間循環
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-11-25 08:53:22 | 显示全部楼层
本帖最后由 liangquan 于 2016-11-25 08:54 编辑
croma 发表于 2016-11-25 00:54
編碼器只說是 1024 階的絕對值,好像沒提過是格雷碼,直接顯示 value 看看會不會在 0~1024間循環


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

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

下面是成功的程序:
  1. /*
  2. * 尝试增加脉冲频率的周期,由1us变为2us
  3. * 改周期没能解决问题,尝试取消格雷码转换
  4. */
  5. int pinCLK = 12;  // 时钟
  6. int pinCSn = 10;  // 使能
  7. int pinD0 = 9;    // 资料
  8. unsigned int valueTmp = 0;
  9. unsigned int value = 0;
  10. void setup()
  11. {
  12.   Serial.begin(9600);
  13.   pinMode(pinCLK, OUTPUT);
  14.   pinMode(pinCSn, OUTPUT);
  15.   pinMode(pinD0, INPUT);
  16.   
  17.   // 用定时器2定时中断,采用比较匹配输出模式,CTC模式,NORMAL port operation
  18.   TCCR2A = 1 << WGM21;
  19.   TCCR2B = 1 << CS22 | 1 << CS21 | 1 << CS20;   // 内部时钟,1024分频(16M/1024 = 15625),CTC模式
  20.   TCNT2 = 0x00;   // 初始化计数值
  21.   /*
  22.         15625/100Hz(10ms) = 156.25 = 156
  23.   */
  24.   OCR2A = 0x9B;  // OCR2A = 0x9B(155) ,(155+1)*(1/15625) = 10ms,也就是定时中断的周期是10ms
  25.   
  26.   TIMSK2 |= (1 << OCIE2A);  // 允许定时器比较匹配中断
  27. }

  28. void loop()
  29. {
  30.   delay(1000);
  31.   getPosition();  // 取得编码器当前角度值
  32. }

  33. // 中断
  34. ISR(TIMER2_COMPA_vect)
  35. {
  36.   cli();    // 停止中断

  37.   valueTmp = 0;
  38.   digitalWrite(pinCSn, HIGH);
  39.   digitalWrite(pinCLK, HIGH);
  40.   delayMicroseconds(1);   // 延时1us

  41.   digitalWrite(pinCSn, LOW);
  42.   delayMicroseconds(1);

  43.   digitalWrite(pinCLK, LOW);
  44.   delayMicroseconds(1);
  45.   digitalWrite(pinCLK, HIGH);

  46.   // 读取角度资料
  47.   for(int i=0; i < 10; i++)
  48.   {
  49.     delayMicroseconds(1);
  50.     digitalWrite(pinCLK, LOW);
  51.     valueTmp <<= 1;
  52.     valueTmp |= digitalRead(pinD0);   
  53.     digitalWrite(pinCLK, HIGH);
  54.   }

  55.   value = valueTmp;

  56.   // 接着读状态能资料
  57.   for(int i=0; i<6; i++)
  58.   {
  59.     delayMicroseconds(1);
  60.     digitalWrite(pinCLK, LOW);
  61.     valueTmp <<= 1;
  62.     valueTmp |= digitalRead(pinD0);
  63.     digitalWrite(pinCLK, HIGH);
  64.   }
  65.   digitalWrite(pinCLK, HIGH);
  66.   
  67.   sei();    // 允许中断
  68. }

  69. // 获取角度值
  70. void getPosition()
  71. {
  72.   int i;
  73.   float angle = 0.0;
  74.   unsigned int x = value;
  75. /*  
  76.   // 将二进制格雷码转换成二进制编码
  77.   for (i=0; (1<<i)<sizeof(x)*8; i++)
  78.   {
  79.     x^=x>>(1<<i);
  80.   }
  81. */
  82.   angle = 360.0 * value / 1024;
  83.   Serial.println(angle);
  84. }
复制代码


采集界面:


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2016-11-25 14:18:11 | 显示全部楼层
liangquan 发表于 2016-11-25 08:53
太感谢您了,问题终于解决了!!!

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

恭喜你~ 好好玩吧~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|联系我们|极客工坊

GMT+8, 2026-6-9 21:47 , Processed in 0.040873 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表