주의할 것은 기본적으로 Blackfin은 fixed point CPU이고, 16-bit fractional number에서
는 1.15 format을 사용하여 표현한다. 이와 같은 data type이 fract16이다.
예를 들면, 0.25를 표현하고 싶을때는 0x2000이 된다
음수는 2의 보수로 표시되며 아래의 그림은 하나의 예이다.
그림 1
0b1011_0001_1011_0001인데, 최상위 bit는 부호를 의미하고, 나머지로 값을 표현하는
1.15 format에서 [그림 3-8]은 2의 보수 값을 계산하기 위해서 0b1100_1110_0100_1111
이 되며 이것을 십진수로 표현하면,
>> x='0.100111001001111'
x =
0.100111001001111
>> y=LimBin2Dec(x)
y =
0.6118
결국, -0.6118의 값을 가지게 된다. 이와 같은 표현을 사용하는 경우에 주의사항들은 다음
과 같다.
1.15 format에서 [그림 3-8]은 2의 보수 값을 계산하기 위해서 0b1100_1110_0100_1111
이 되며 이것을 십진수로 표현하면,
>> x='0.100111001001111'
x =
0.100111001001111
>> y=LimBin2Dec(x)
y =
0.6118
결국, -0.6118의 값을 가지게 된다. 이와 같은 표현을 사용하는 경우에 주의사항들은 다음
과 같다.
1) 최대값 : 1- = 0.999969482421875 = 0x7FFF.
2) 최소값 : -1 = 0x8000, 예를 들면, 0xc000=-1/2.
3) “0” : 0xFFFF
4) resolution(또는 precision) :
5) Dynamic range : [0.999969482421875 -1]
2) 최소값 : -1 = 0x8000, 예를 들면, 0xc000=-1/2.
3) “0” : 0xFFFF
4) resolution(또는 precision) :
5) Dynamic range : [0.999969482421875 -1]
이 된다. 단, 1.0은 1.15 format에서 표현될 수 없다는 데 주의하자.
어쨌든, VisualDSP math library는 다음과 같은 함수들을 제공하여 sin 값을 쉽게 계산할
수 있도록 하는데, 여기서 설명한 방식과 유사한 방법으로 cos,tan등등의 삼각함수
에 대한 값들을 구할 수 있다.
수 있도록 하는데, 여기서 설명한 방식과 유사한 방법으로 cos,tan등등의 삼각함수
에 대한 값들을 구할 수 있다.
#include <math.h>
double sin (double x);
float sinf (float x);
long double sind (long double x);
fract16 sin_fr16 (fract16 x);
fract32 sin_fr32 (fract32 x);
_Fract sin_fx16 (_Fract x);
long _Fract sin_fx32 (long _Fract x);
- 기능 설명 :
sin 함수는 입력 매개변수와 출력을 모두 radians로 return해 준다.
sin_fr16, sin_fr32, sin_fx16 그리고, sin_fx32 함수들은 [-π/2, π/2]에 대응하는 [-1 1] 범
위 안에 있는 임의의 소수를 입력으로 받아들인다. 만일, 입력 매개변수 x가 범위를 벗어
난 것이 들어오면 “0”을 return하며, 어떠한 error condition을 return하지 않는데 주의하
자. 그러므로, 한주기를 유도하기 위해서는 다음과 같이 나머지 반주기를 계산해 주어야 한
다.
sine [0, π/2] = - sine [π, 3/2 π]
sine [-π/2, 0] = - sine [π/2, π]
다음의 예제는 [0, 0x7fff]의 input domain을 이용하여 sine function의 한주기를 계산하는
예제 code이다.
//예제 1
fract16 sin2pi_fr16 (fract16 x)
{
if (x < 0x2000) { /* <0.25 */
/* first quadrant [0..π/2): */
/* sin_fr16([0x0..0x7fff]) = [0..0x7fff) */
return sin_fr16(x * 4);
} else if (x == 0x2000) { /* = 0.25 */
return 0x7fff;
} else if (x < 0x6000) { /* < 0.75 */
/* if (x < 0x4000) */
/* second quadrant [π/2..π): */
/* -sin_fr16([0x8000..0x0)) = [0x7fff..0) */
/* */
/* if (x < 0x6000) */
/* third quadrant [π..3/2π): */
/* -sin_fr16([0x0..0x7fff]) = [0..0x8000) */
return -sin_fr16((0xc000 + x) * 4);
} else {
/* fourth quadrant [3/2π..π): */
/* sin_fr16([0x8000..0x0)) = [0x8000..0) */
return sin_fr16((0x8000 + x) * 4);
}
}
{
if (x < 0x2000) { /* <0.25 */
/* first quadrant [0..π/2): */
/* sin_fr16([0x0..0x7fff]) = [0..0x7fff) */
return sin_fr16(x * 4);
} else if (x == 0x2000) { /* = 0.25 */
return 0x7fff;
} else if (x < 0x6000) { /* < 0.75 */
/* if (x < 0x4000) */
/* second quadrant [π/2..π): */
/* -sin_fr16([0x8000..0x0)) = [0x7fff..0) */
/* */
/* if (x < 0x6000) */
/* third quadrant [π..3/2π): */
/* -sin_fr16([0x0..0x7fff]) = [0..0x8000) */
return -sin_fr16((0xc000 + x) * 4);
} else {
/* fourth quadrant [3/2π..π): */
/* sin_fr16([0x8000..0x0)) = [0x8000..0) */
return sin_fr16((0x8000 + x) * 4);
}
}
//예제2
fract16 TempOut[8192]={0}, TempIn=0x0000; // 0d8192=0x2000
// First Quadrant
for(TempIn=0;TempIn<8192;TempIn++) {
TempOut[TempIn]=sin_fr16(TempIn*4);
}
// Second Quadrant
for(TempIn=8192;TempIn<(8192*2);TempIn++) {
TempOut[TempIn-8192]=-sin_fr16((49152+TempIn)*4); // 0d49152=0xC000
}
// Third Quadrant
for(TempIn=(8192*2);TempIn<(8192*3);TempIn++) {
TempOut[TempIn-(8192*2)]=-sin_fr16((49152+TempIn)*4);// 0d49152=0xC000
}
// Fourth Quadrant
for(TempIn=(8192*3);TempIn<(8192*4);TempIn++) {
TempOut[TempIn-(8192*3)]=sin_fr16((32768+TempIn)*4);
}
// First Quadrant
for(TempIn=0;TempIn<8192;TempIn++) {
TempOut[TempIn]=sin_fr16(TempIn*4);
}
// Second Quadrant
for(TempIn=8192;TempIn<(8192*2);TempIn++) {
TempOut[TempIn-8192]=-sin_fr16((49152+TempIn)*4); // 0d49152=0xC000
}
// Third Quadrant
for(TempIn=(8192*2);TempIn<(8192*3);TempIn++) {
TempOut[TempIn-(8192*2)]=-sin_fr16((49152+TempIn)*4);// 0d49152=0xC000
}
// Fourth Quadrant
for(TempIn=(8192*3);TempIn<(8192*4);TempIn++) {
TempOut[TempIn-(8192*3)]=sin_fr16((32768+TempIn)*4);
}
여기서 주의할 것은 input domain의 끝이 0x7FFF, 즉, 1.15 format의 fract16이 가질 수
있는 최대 값이라는 것이다.
그런데, 여기서 주의할 것은 배열의 index를 fract16 data type으로 선언하면 “음수”를 만
나는 경우에 VisualDSP가 hang 걸리게 된다. 그러므로, 위에서 마지막 Fourth Quadrant의
경우, 즉, 8192*4의 경우, 0x8000 즉, 음수가 되어서 문제가 되므로 다음과 같이 cast 연
산자를 사용해 주어야 한다.
// Fourth Quadrant
unsigned short TempIn=0x0000; // 0d8192=0x2000
for(TempIn=(8192*3);TempIn<(8192*4);TempIn++) {
TempOut[TempIn-(8192*3)]=sin_fr16(fract16((32768+TempIn)*4));
}
있는 최대 값이라는 것이다.
그런데, 여기서 주의할 것은 배열의 index를 fract16 data type으로 선언하면 “음수”를 만
나는 경우에 VisualDSP가 hang 걸리게 된다. 그러므로, 위에서 마지막 Fourth Quadrant의
경우, 즉, 8192*4의 경우, 0x8000 즉, 음수가 되어서 문제가 되므로 다음과 같이 cast 연
산자를 사용해 주어야 한다.
// Fourth Quadrant
unsigned short TempIn=0x0000; // 0d8192=0x2000
for(TempIn=(8192*3);TempIn<(8192*4);TempIn++) {
TempOut[TempIn-(8192*3)]=sin_fr16(fract16((32768+TempIn)*4));
}
이제, 예제 2로 각각의 quadrants에 대해서 simulation을 하면, 다음과 같은 그림을 얻을 수 있다.
그림 2
위에 보여준 것과 같이 4개의 사분면이 각각 8192=0x2000개의 points로 구성되고 있다.
그러므로, (π/2)/8192 = 1.9175e-004의 각도 reoslution을 가지게 되며, 이것을 degree로 환산하면, 0.0110 degree의 resolution을 가지는 것이다.
그런데, 위에 표현해 준sin(theta)는 theta의 값이 0부터 최대 8192*4-1=32767=0x7FFF 즉, 양의
각도에 대한sin(theta)의 값들만 돌려주는 단점이 있다.
실질적으로는 theta가 음의 값을 가지는 경우도 고려해 주어야 한다.
이를 위해서 sin2pi_fr16이라는 함수를 제공해 주는 데, 이 함수는 [그림 3]에서 보는
것과 같이 0도부터 pi까지는 0x7FFF=32767=8192*4-1의 points로 구성되고, pi부터 2*pi
까지도 32768개의 points로 구성되어 총 0~0xFFFF까지, 즉, 양수 32767개의 points에 따
른 각도와 음수 32768개의 points에 따른 각도를 얻을 수 있게 지원해 준다
그림 3
그러므로, 총 65536개로 360도를 나누어서 그에 따른 각도를 얻을 수 있도록 한다.
예를 들면, sin(45)를 얻고 싶은 경우,
65536/360× 45 = 8192 이므로, sin2pi_fr16(8192)를 입력해 주면, sin(pi/4)=0.7071을 얻을 수 있게 된다. 정리하면, sin2pi_fr16(65536/360* 45*theta) = sin(theta)가 된다.
예를 들면, sin(45)를 얻고 싶은 경우,
65536/360× 45 = 8192 이므로, sin2pi_fr16(8192)를 입력해 주면, sin(pi/4)=0.7071을 얻을 수 있게 된다. 정리하면, sin2pi_fr16(65536/360* 45*theta) = sin(theta)가 된다.
예를 들어보자. 사용하는 모터가 8극이고, 2pi를 65536 points로 표현한다.
그림 4 전기각이 360도가 4번 나오는 구조
여기서, 앞서 설명한 것과 같이 0도부터 180도를 32640 points로 표현하였는데, 각각의
step을 128등분하였다. 즉, 32640/128=255 이므로, 결국, 0도부터 180도를 255등분한 것
이며, 0도를 포함하면, 256등분하였다고 볼 수 있고, 32640+128=32768이 되어서, 앞서
배운 내용과 동일한 것을 알 수 있다. 256은 2의 8승임으로 8bits로 0도부터 180도를 표현하게 만든 것이다. 그리고, 180도에서 360도의 경우도 동일하게 32640 points로 표현하였
으며, 이때에는 180도보다는 크고, 360와 같은 것을 역시 256등분하여 8bits로 표현하였
다. 결국, 16bits로 0도부터 360도를 표현하게 된다.또한, [그림 4]로부터 이 encoder는 한바퀴 돌면, 2048개의 pulses를 내보내는
2048ppr 증가형 encoder이고, 앞에서 설명하였듯이 4번의 360도가 나타나는 8-poles
motor이므로 2048/4=512, 즉, 512 pulses와 2*π=65536을 matching하도록 하며, 동시에
4개의 360도가 나오도록 하여야 한다.
여기서 주의할 것은 4개의 512 pulses 각각은 65536 y축과 matching되어야 한다는 것이
다
step을 128등분하였다. 즉, 32640/128=255 이므로, 결국, 0도부터 180도를 255등분한 것
이며, 0도를 포함하면, 256등분하였다고 볼 수 있고, 32640+128=32768이 되어서, 앞서
배운 내용과 동일한 것을 알 수 있다. 256은 2의 8승임으로 8bits로 0도부터 180도를 표현하게 만든 것이다. 그리고, 180도에서 360도의 경우도 동일하게 32640 points로 표현하였
으며, 이때에는 180도보다는 크고, 360와 같은 것을 역시 256등분하여 8bits로 표현하였
다. 결국, 16bits로 0도부터 360도를 표현하게 된다.또한, [그림 4]로부터 이 encoder는 한바퀴 돌면, 2048개의 pulses를 내보내는
2048ppr 증가형 encoder이고, 앞에서 설명하였듯이 4번의 360도가 나타나는 8-poles
motor이므로 2048/4=512, 즉, 512 pulses와 2*π=65536을 matching하도록 하며, 동시에
4개의 360도가 나오도록 하여야 한다.
여기서 주의할 것은 4개의 512 pulses 각각은 65536 y축과 matching되어야 한다는 것이
다
이에 대한 code는 다음과 같다.
s16 thetaCalc(s16 RotaryCout)
{
s16 pollnum,PiCouter;
s16 i;
pollnum = 4;//SetNum[5]; //Harry: polar number??
PiCouter = 512 * 4 / 2 / pollnum;//256
if(RotaryCout < 0) RotaryCout = - RotaryCout;
for(i = 0;i < (pollnum*2);i=i+2)
{
if(RotaryCout >= PiCouter*i && RotaryCout <= PiCouter*(i+1)) //theta range from 0 to pi
{
thetaOut = (RotaryCout -i * PiCouter) << 7;
}
if(RotaryCout > PiCouter*(i+1) && RotaryCout < PiCouter*(i+2))//range from pi to 2pi
{
thetaOut = ((RotaryCout - (i+2) * PiCouter) << 7);
}
}
return thetaOut;
}
또한, theta각은 실질적으로 encoder에서 돌려주는 것으로 PWM switching 주파수와 동기
되어 encoder에서 읽어들인다. 그러므로, PWM sync와 관련된 ISR에서 theta를 계산하는
데 주의하자.
void PreLockMag(void)
{
s16 pwmtep;
pwmtep = Max_Duty_Value/13;// 1/12 2/25 1/13
// 0x00ab
*pPWM0_CHA = (u16)pwmtep;
*pPWM0_CHB = (u16)(-pwmtep/2);
*pPWM0_CHC = (u16)(-pwmtep/2);
}
이 code는 open loop 제어가 아닌 feedback 제어를 할 때, 증가형 encoder를 사용하는
경우에 rotor가 처음 정지되어 있을 때, 강제로 PWM을 발생하여 rotor가 고정자와 정렬되
도록 rotor를 처음에 구동시키는 code로서 이와 같은 code를 사용하게 되면, 일반 상용의
경우 rotor가 처음에 어떻게 움직일 지 예측할 수 없으므로 사용하지 않는다. 이것보다는
처음에 V/F제어를 하여 rotor가 회전하게 하고, 정지시킨 뒤에 회전에 따른 각도의 변화량
을 encoder를 통하여 history를 기억한 뒤에 feedback 제어를 할 때, 자동으로 구동되도록
하는 compensation 알고리즘을 사용하는 것이 보다 합리적이다.
결국, V/F open loop또는 절대형 encoder또는 resolver를 사용하는 경우에는 필요하지 않
은 것은 확실하다.
s16 thetaCalc(s16 RotaryCout)
{
s16 pollnum,PiCouter;
s16 i;
pollnum = 4;//SetNum[5]; //Harry: polar number??
PiCouter = 512 * 4 / 2 / pollnum;//256
if(RotaryCout < 0) RotaryCout = - RotaryCout;
for(i = 0;i < (pollnum*2);i=i+2)
{
if(RotaryCout >= PiCouter*i && RotaryCout <= PiCouter*(i+1)) //theta range from 0 to pi
{
thetaOut = (RotaryCout -i * PiCouter) << 7;
}
if(RotaryCout > PiCouter*(i+1) && RotaryCout < PiCouter*(i+2))//range from pi to 2pi
{
thetaOut = ((RotaryCout - (i+2) * PiCouter) << 7);
}
}
return thetaOut;
}
또한, theta각은 실질적으로 encoder에서 돌려주는 것으로 PWM switching 주파수와 동기
되어 encoder에서 읽어들인다. 그러므로, PWM sync와 관련된 ISR에서 theta를 계산하는
데 주의하자.
void PreLockMag(void)
{
s16 pwmtep;
pwmtep = Max_Duty_Value/13;// 1/12 2/25 1/13
// 0x00ab
*pPWM0_CHA = (u16)pwmtep;
*pPWM0_CHB = (u16)(-pwmtep/2);
*pPWM0_CHC = (u16)(-pwmtep/2);
}
이 code는 open loop 제어가 아닌 feedback 제어를 할 때, 증가형 encoder를 사용하는
경우에 rotor가 처음 정지되어 있을 때, 강제로 PWM을 발생하여 rotor가 고정자와 정렬되
도록 rotor를 처음에 구동시키는 code로서 이와 같은 code를 사용하게 되면, 일반 상용의
경우 rotor가 처음에 어떻게 움직일 지 예측할 수 없으므로 사용하지 않는다. 이것보다는
처음에 V/F제어를 하여 rotor가 회전하게 하고, 정지시킨 뒤에 회전에 따른 각도의 변화량
을 encoder를 통하여 history를 기억한 뒤에 feedback 제어를 할 때, 자동으로 구동되도록
하는 compensation 알고리즘을 사용하는 것이 보다 합리적이다.
결국, V/F open loop또는 절대형 encoder또는 resolver를 사용하는 경우에는 필요하지 않
은 것은 확실하다.
댓글 없음:
댓글 쓰기