开源Atmega8+74hc595方案的32路舵机控制板(慎入:软件误差太...
本帖最后由 superplaim 于 2014-3-26 17:51 编辑Atmega8+74hc595方案的32路舵机控制板(慎入:软件误差太大)
想做个机器人,自己设计一款舵机控制器,也插了很多资料(大多是本论坛的实例,有硬件也有软件)
最后确定了舵机的方案利用成熟的atmega8+4片74HC595利用spi总线的方式传输数据来控制32路舵机
废话不说先上个原理图。
本帖最后由 superplaim 于 2014-3-26 17:09 编辑
原理图和PCB都看不太清楚我把文件传进来 这个系统用来控制8路舵机也就是只用一个74HC595的精度还是可以的,pwm也比较稳定
但是如果扩展的32路基本可以输出Pwm但是精度极差,这肯定是由于软件的原因引起的
但是研究了好久也不知道怎么去优化,两种编程方案都不是太理想,现在都想放弃整个方案重新用STM32做一个引脚直接输出的,估计精度会提高很多
现在把代码贴出来,希望给能对别人有些参考,也希望高手做过类似方案的能给指导一下看能不能重新优化一下,毕竟PCB已经打样放弃也怪浪费的
编译用的是ICC8.0,等下把软件也贴出来,这个软件的8.05版当时还很不好找,需要的人可以下载 本帖最后由 superplaim 于 2014-3-27 10:20 编辑
另外先前还用labview写了串口上位机控制软件,如果有人需要请留言我可以开源,下面是Proteus的仿真图仿真的效果跟实测的效果基本一致 控制板的硬件部分与SSC-32开源控制板(说明书上写的精度能达到1us,我认为如果达到的话,程序员一定逆天了)基本一致只不过没有加入动作存储和波特率选择的部分,如果谁有需要我可以把资料都贴出来,希望哪位高手能给指导一下,论坛中的实例大部分都是来控制8路的 楼主厉害,给顶上去,让论坛的高手出来给大家出出福利{:lol:} 我用PIC做过,用的是CCP的Compare模式,把舵机脉冲时间排序后,启动定时器,定时器与比较寄存器一致时,触发中断,然后更新比较寄存器.实现依次更新相应舵机通道为低电平.
跟楼主的模式应该是一样的.追求精度可以尝试用汇编写排序完成后的代码. 之前下的资料,有程序和原理图,感谢作者!搬运过来
32V103 windingway 发表于 2014-3-26 18:06
我用PIC做过,用的是CCP的Compare模式,把舵机脉冲时间排序后,启动定时器,定时器与比较寄存器一致时,触发中断 ...
就是这个原理,这是借鉴了论坛某位兄弟的程序,汇编语言好难上手,C还是比较习惯 donglaile 发表于 2014-3-27 00:58
之前下的资料,有程序和原理图,感谢作者!搬运过来
哥们 这个附件不能下载了啦
为什么我上传的附件全部挂掉了 superplaim 发表于 2014-3-26 17:23
这个系统用来控制8路舵机也就是只用一个74HC595的精度还是可以的,pwm也比较稳定
但是如果扩展的32路基本可 ...
沒看LZ的思路,能否用文字描述一下思路。PWM周期和位数是多少?
建议搜索"595 灰度"
一个位权控制LED灰度的方法可以降低对硬件的速度要求。 http://www.amobbs.com/forum.php?mod=viewthread&tid=985613&highlight=595%2B灰度
27楼
理解可以看我在那里的回复234楼 zyw19987 发表于 2014-3-27 06:34
http://www.amobbs.com/forum.php?mod=viewthread&tid=985613&highlight=595%2B灰度
27楼
理解可以看我在那 ...
首先将各路的pwm的高电平时间按照从小到大的时间排序,然后计算这32个时间之间的差值生成一个延时差值的32数组,先让各路输出先为高电平 然后将时间差值数组送给定时器T1,依次产生延时,每20ms进入外部中断INT0产生周期
这个灰度的例子我还没有看到,我现在研究一下 donglaile 发表于 2014-3-27 00:58
之前下的资料,有程序和原理图,感谢作者!搬运过来
上传的附件代码应该是SSC-32的原版程序 是用codevision编译的 我用这个软件总是编译通不过,能不能麻烦你给编译一下,最好能生成hex文件 superplaim 发表于 2014-3-26 17:33
控制板的硬件部分与SSC-32开源控制板(说明书上写的精度能达到1us,我认为如果达到的话,程序员一定逆天了 ...
其實未必不可能的,但必須要用匯編,用 c 只能到 5us 級別。 irobotto 发表于 2014-3-27 13:27
其實未必不可能的,但必須要用匯編,用 c 只能到 5us 級別。
原版代码使用gcc和codeversion编译好像嵌入了部分汇编语句 研究舵機是很久的事了,匯編也只懂皮毛,看不明白的。但這方向是對的。當時還在用89c2051, 11.0592 MHz 晶振,跑一個指令要12個時鐘周期,很慢的。但用得好最多可以控制13個舵機(其中2支要上拉電阻),訊號之間相隔最少是10us. superplaim 发表于 2014-3-27 14:15
原版代码使用gcc和codeversion编译好像嵌入了部分汇编语句
剛抽空看了一下樓主的程序,寫得很工整。第一步要想是中斷內要處理的事情是否太多?必須要在中斷內做的?因為這個直接影響程序的效能。例如 計算 Serial_temp , TCNT1等都花了太多時間了。 留意 16 MHz 代表每做 16個單周期指令,便花了 1us 時間啦!先試改一下吧 {:smile:} irobotto 发表于 2014-3-28 13:37
剛抽空看了一下樓主的程序,寫得很工整。第一步要想是中斷內要處理的事情是否太多?必須要在中斷內做的? ...
以前也考虑过这个问题,也不知道怎么解决,请教一下有没有好的建议 superplaim 发表于 2014-3-28 17:20
以前也考虑过这个问题,也不知道怎么解决,请教一下有没有好的建议
在做好 sorting 後就可以預先計算好 serial_temp 和 tcnt1 的數值了,用陣列貯存好就可以了。在中斷內只調用,不計算。 labview写了串口上位机控制软件,给参考一下 邮箱:cdl35@sina.com irobotto 发表于 2014-3-28 17:47
在做好 sorting 後就可以預先計算好 serial_temp 和 tcnt1 的數值了,用陣列貯存好就可以了。在中斷內只 ...
有道理啊 我怎么没有想到 现在去改改 irobotto 发表于 2014-3-28 17:47
在做好 sorting 後就可以預先計算好 serial_temp 和 tcnt1 的數值了,用陣列貯存好就可以了。在中斷內只 ...
#include<iccioavr.h>
#include<AVRdef.h>
#include"AVR_SPI.h"
unsigned char m=0,a=0;
unsigned int Dispersion;//存放各个延时值之间的时间差
//第1组舵机数据
unsigned char Length_Temp;//从小到大存放各路高电平延时值
unsigned char Length={50,60,60,80,90,100,130,130,
50,60,60,80,90,100,130,30,
50,60,60,80,90,100,130,130,
50,60,60,80,90,100,130,130};//高电平时间 单位10us
unsigned long Serial_Num={0xfffffffe,0xfffffffd,0xfffffffb,0xfffffff7,0xffffffef,0xffffffdf,0xffffffbf,0xffffff7f,
0xfffffeff,0xfffffdff,0xfffffbff,0xfffff7ff,0xffffefff,0xffffdfff,0xffffbfff,0xffff7fff,
0xfffeffff,0xfffdffff,0xfffbffff,0xfff7ffff,0xffefffff,0xffdfffff,0xffbfffff,0xff7fffff,
0xfeffffff,0xfdffffff,0xfbffffff,0xf7ffffff,0xefffffff,0xdfffffff,0xbfffffff,0x7fffffff};
unsigned long Serial_Temp;//临时存放Serial_Num[
void Timer1_Init(void);//定时器3初始化
void Int0_Init(void);//外部中断0初始化,下降沿触发
void Sorting(void);//冒泡法排序函数 主要对Length_Temp0排序
void main(void)
{
SPI_MasterInit();//SPI总线初始化
DDRD|=0x08;//设置PD0为输出
PORTD|=0x08;//置PD0为高电平
Timer1_Init();//初始化定时器3 8分频
Int0_Init();//初始化外部中断0 1分频
Sorting();//对初始值进行排序,确定dispersion数组的值
//因为vlaue的值是0—255,外部晶振为16M,定时器的计数频率设为8分频。这样每计一个值
//耗费的时间是0.5us.舵机要求的高电平时间是500us到2500us之间,对定时器赋初值
//就以应该是65536-500*2=64536到65536-2500*2=60536之间。对应的value的值就是50-250
//所以,给定时器赋初值就是vlaue*20value的单位是10us
TCNT1H=(0xffff-Length_Temp*20)/256;//经过排序后,vlaue是最小的数
TCNT1L=(0xffff-Length_Temp*20)%256;//给定时器3赋初值
HC595_OUT_BANK0(0xff);
HC595_OUT_BANK1(0xff);
HC595_OUT_BANK2(0xff);
HC595_OUT_BANK3(0xff);
//定时器开始计时,对外部16M晶振进行8分频,定时器计数为0.5us一个增量,
//单片机的指令执行速度仍然是1/16m=62.5ns一个指令周期
TCCR1B=0x02; //T1定时器8分频
SREG|=BIT(7);//总中断使能
while(1)
;
}
#pragma interrupt_handler Int0:2//中断0处理程序
void Int0(void)
{
TCCR1B=0x00;
PORTD|=0x08;//恢复PD3口为高电平,开始响应中断0
Sorting();
m=0;
TCNT1H=(0xffff-Length_Temp*20)/256; //给定时器3赋初值
TCNT1L=(0xffff-Length_Temp*20)%256;
HC595_OUT_BANK0(0xff);//PORTB=0xff;//置所有位为高
HC595_OUT_BANK1(0xff);
HC595_OUT_BANK2(0xff);
HC595_OUT_BANK3(0xff);
TCCR1B=0x02;//打开定时器1,8分频,每计一位为0.5us
}
#pragma interrupt_handler Timer1_Time:9//定时器1的溢出中断处理程序
void Timer1_Time(void)
{
TCCR1B=0x00;
if(m==32)
{
TCCR1B=0x00;//一个周期结束,关闭定时器1
PORTD&=0xf7; //PD0置低位,引发外部中断0
}
else
{
spot0:HC595Output(Serial_Temp);
//第一次中断溢出时,m=0,Dispersion为value-value
if(Dispersion==0)
{
m=m+1;
goto spot0;
}
else
{
TCNT1H=(0xffff-Dispersion+40)/256;//+40为补偿值
TCNT1L=(0xffff-Dispersion+40)%256;
}
//当m=7的时候,dispersion为所有管脚置低后低电平维持的时间
m=m+1;
}
TCCR1B=0x02;
}
void Timer1_Init(void)
{
TCCR1A=0x00;//
TCCR1B=0x02;//8 frequency division
TIMSK|=(1<<TOIE1);//TOIE1=1
}
void Int0_Init(void)
{
GICR|=0X40;//ENABLE INT0
MCUCR|=0X02;//drop edge trigger
}
//冒泡法将Length_Temp和Serial_Num从小到大排序
void Sorting(void)
{
unsigned char i,j,k;
unsigned long t;
for(i=0;i<32;i++)
Length_Temp=Length;//将时常数据传送给临时数组Length_Temp
for(i=0;i<32;i++)
Serial_Temp=Serial_Num;//将时常数据传送给临时数组Serial_Temp
for(j=0;j<31;j++)//冒泡算法,Length_Temp被从小到大排序
{
for(i=0;i<31-j;i++)
{
if(Length_Temp>Length_Temp)
{
k=Length_Temp;
Length_Temp=Length_Temp;
Length_Temp=k;
t=Serial_Temp;
Serial_Temp=Serial_Temp;
Serial_Temp=t;//相应要置低的管脚也排序
}
}
}
for(j=0;j<31;j++)//相邻的数之间做差值,总共得到差值7个
{
//if(Length_Temp==Length_Temp)
//Dispersion=6;
//else
Dispersion=(Length_Temp-Length_Temp)*20;
}
//最后一个值是低电平延时,65536-40000是20ms的定时初值
Dispersion=40000-Length_Temp*20;
for(i=0;i<32;i++)
{
if(i>0)
Serial_Temp=Serial_Temp&Serial_Temp;
}
}
将函数改了一下在 Sorting中将Serial_Temp和TCNT1计算 在T1中断中用:HC595Output(Serial_Temp);调用发现误差依然很大 误差依然很大,是不是说明HC595Output()会的调用会占用很多时间?另外周期也不准确了,周期倒是比较修正 superplaim 发表于 2014-3-30 13:51
#include
#include
#include"AVR_SPI.h"
有輕微改善, 但思路是要盡量縮短處理 "把訊號線拉低" 時用的 時鐘周期。
仍看到這計算在 中斷內, 是不是可以預先計算好在陣列內? 應該可以減省少許 時鐘周期。
else
{
TCNT1H=(0xffff-Dispersion+40)/256;//+40为补偿值
TCNT1L=(0xffff-Dispersion+40)%256;
}
之後仍要 縮短時鐘周期的。
另外你是對的, 用 595 肯定是會慢一點的, 因為是 串行的, 但不知會慢多少, 這裡用滙編會高效一點罷。 忘了你提到 "另外周期也不准确了,周期倒是比较修正" 的問題, 在程序裡計時是用
TCNT1H=(0xffff-Dispersion+40)/256;//+40为补偿值
TCNT1L=(0xffff-Dispersion+40)%256;
因為在中斷內已花了很多時間, 所以計時要加數值 40 作補償, ( 40/2 = 20 us)
亦即實際計時減 20 us, (20us *16 = 320 個時鐘周期, 亦即原先的中斷時間太長了)
而且 20us 是很不準確的, 運算的耗時跟要計的數字有關。
當你按建議縮短了中斷程序所用的時間後, 40 這數值應要減少一點的, 可試著用 38, 35 , 30, ... irobotto 发表于 2014-3-30 17:53
忘了你提到 "另外周期也不准确了,周期倒是比较修正" 的問題, 在程序裡計時是用
TCNT1H=(0xffff-Disp ...
这个方案暂时放弃了,现在用stm32的4个定时器利用定时器分时法可以产生精度很高的pwm波形
labview上位机也完成的才差不多了但是初学labview程序框图画的乱七八糟的,有兴趣可以讨论一下 superplaim 发表于 2014-4-28 08:42
这个方案暂时放弃了,现在用stm32的4个定时器利用定时器分时法可以产生精度很高的pwm波形
labview上位机 ...
这个就这么放弃了,先别,这个先看看,或许能解决一些问你,程序可能有些简单,主要通过595分别更新,32路分4组,每组8路,4组同时给个00000001,然后看高电平那路高电平时间到了没,到了就输出00000000,然后4组同时给00000010。。。如此循环,因为周期20ms,所以8路每路就是2.5ms,现在没有解决的问题是判断高电平时间函数放在主函数,有干扰就舵机跳动的厉害,请指点下,能不能再完美了,如果写的真的很完美了,请发我一份源程序,先谢谢! 这个代码错了,大家帮忙修改! 没有人关注这个问题啊~?{:sweat:} superplaim 发表于 2014-3-27 10:22
上传的附件代码应该是SSC-32的原版程序 是用codevision编译的 我用这个软件总是编译通不过,能不能麻烦你 ...
原版是使用CodeVisionAVR C Compiler V1.24.6 Standard的工程,找了好久没有找到这个版本的cvavr,前段时间找到了一种方法可以编译该工程,见附件。
先按要求安装cvavr,再打开源文件,修改两个地方就可以了,修改的地方有截图。
donglaile 发表于 2014-6-28 14:54
原版是使用CodeVisionAVR C Compiler V1.24.6 Standard的工程,找了好久没有找到这个版本的cvavr,前段时 ...
楼上很牛掰啊 佩服佩服 哪里挖的啊 楼主,32路控制系统是不是也可以用于控制继电器哈 先看了,MARK 好东西 不错支持个 好东西 不错支持 牛啊 群主群主 路数有点多啊 一个个好厉害的样子,我过来学习。 学习大虾,积极向上
superplaim 发表于 2014-3-26 17:23
这个系统用来控制8路舵机也就是只用一个74HC595的精度还是可以的,pwm也比较稳定
但是如果扩展的32路基本可 ...
我看了下 LZ 的代码,最大的问题在于:没用硬件 SPI 加载 595.
如果用上的话可以极大提升加载速度(fosc/2),并且可以实现加载同步运算
页:
[1]