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: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(); // 允許中斷
} 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. 我是向一个方向转动的编码器,可是数据一会减、一会增,如下两图:
---------------------------------------------------------------
我觉得程序还有小问题,但不大,您还能帮帮我么? 編碼器只說是 1024 階的絕對值,好像沒提過是格雷碼,直接顯示 value 看看會不會在 0~1024間循環 本帖最后由 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);
}
采集界面:
--------------------------------------
对您的帮助再次表示感谢!!! liangquan 发表于 2016-11-25 08:53
太感谢您了,问题终于解决了!!!
谢谢您一步一步、不厌其烦的指导,终于实现正确的读数了!!!原 ...
恭喜你~ 好好玩吧~
页:
[1]