在嵌入式系统开发中,三角函数计算一直是资源消耗的大户。传统基于泰勒展开的算法需要大量乘法和存储空间,这对于资源有限的PIC16F系列微控制器来说是个巨大挑战。2007年Microchip发布的技术文档AN1061展示了一种革命性的解决方案——通过CORDIC算法在PIC16F877A上实现高效三角函数计算。
CORDIC(Coordinate Rotation Digital Computer)算法由Jack Volder于1959年发明,其核心思想是通过迭代的位移和加法操作来逼近三角函数值。这种算法特别适合硬件实现,因为它避免了复杂的乘法运算。在20MHz主频的PIC16F877A上,同时计算sin和cos仅需370μs,比标准math.h库函数快5倍,且仅占用190字节程序存储和11字节数据存储。
关键优势:CORDIC算法将复杂的三角函数计算转化为一系列简单的位移和加法操作,这种计算范式与PIC16F的RISC架构完美匹配。实测显示其效率是传统方法的4倍以上,特别适合实时控制系统。
CORDIC算法的核心是向量旋转公式。给定初始向量(x,y)和旋转角度φ,旋转后的坐标(x',y')可表示为:
code复制x' = x*cosφ - y*sinφ
y' = y*cosφ + x*sinφ
这个公式看似需要四次乘法和两次加法,但CORDIC的巧妙之处在于将旋转角度分解为一系列已知角度(如45°、26.56°、14.04°等)的组合,这些角度的正切值恰好是2的负幂次(2⁰、2⁻¹、2⁻²...)。
通过数学变换,旋转公式可改写为:
code复制x(i+1) = x(i) - y(i)*d(i)*2^(-i)
y(i+1) = y(i) + x(i)*d(i)*2^(-i)
z(i+1) = z(i) - d(i)*atan(2^(-i))
其中d(i)表示旋转方向(±1),z是剩余角度。这种形式仅需位移(2^(-i))和加法,完美适配嵌入式处理器。
每次迭代中,2^(-i)的乘法可通过右移i位实现。PIC16F的汇编指令直接支持这种操作:
assembly复制RRF REG, F ; 循环右移实现除法
角度累加器z的更新使用预计算的atan(2^(-i))值,这些值可存储在查找表中。经过约15次迭代后,结果精度可达16位定点数的极限。
注意:由于省略了cos(atan(2^(-i)))缩放因子(约0.607253),最终结果需要乘以1.64676(1/0.607253)补偿。但在sin/cos计算中,通过初始化x0=1/An可自动消除这个系数。
在PIC16F877A的368字节RAM中,我们需精心分配:
程序存储器中存储:
旋转模式核心代码示例:
assembly复制CORDIC_ROTATE:
MOVLW 15 ; 15次迭代
MOVWF ITER_COUNT
BCF STATUS,C ; 清除进位标志
ROT_LOOP:
MOVF Y_LOW,W ; 检查z符号位
BTFSS Y_HIGH,7 ; 测试最高位(符号位)
GOTO POSITIVE
NEGATIVE: ; z为负,顺时针旋转
MOVLW 1
MOVWF DIR_FLAG
CALL ROTATE_CW
GOTO NEXT_ITER
POSITIVE: ; z为正,逆时针旋转
MOVLW 0
MOVWF DIR_FLAG
CALL ROTATE_CCW
NEXT_ITER:
DECFSZ ITER_COUNT,F
GOTO ROT_LOOP
RETURN
ROTATE_CW: ; 顺时针旋转子程序
; x_new = x + (y >> i)
; y_new = y - (x >> i)
; z_new = z + atan_table[i]
; 具体实现省略...
ROTATE_CCW: ; 逆时针旋转子程序
; x_new = x - (y >> i)
; y_new = y + (x >> i)
; z_new = z - atan_table[i]
; 具体实现省略...
assembly复制ATAN_TABLE:
ADDWF PCL,F
RETLW 0x20 ; atan(2^0)*256/360 = 45°
RETLW 0x12 ; atan(2^-1)*256/360 ≈26.56°
; 其余角度值...
早期终止:当z的绝对值小于最小角度(0.1°)时提前退出循环
移位加速:使用循环移位配合进位标志实现算术右移
双缓冲存储:在迭代中交替使用两组寄存器避免数据拷贝
初始化设置:
经过15次迭代后:
实测性能(20MHz时钟):
| 方法 | 执行时间 | 程序空间 | 数据RAM |
|---|---|---|---|
| CORDIC(汇编) | 370μs | 190字 | 11字节 |
| math.h(sin) | 1.9ms | 1117字 | 40字节 |
初始化设置:
迭代结束后:
典型应用场景:
c复制// 计算电机控制中的Park逆变换角度
int16_t calc_angle(int16_t alpha, int16_t beta) {
cordic_vectoring(alpha, beta);
return angle_accumulator; // 返回zn
}
通过初始化x0=幅度/An,可一次性完成幅度调制:
assembly复制; 输入:角度在Z0,幅度在AMP_H:AMP_L
MOVF AMP_L,W ; 加载幅度低字节
MOVWF X_LOW ; x0 = 幅度/An
MOVF AMP_H,W
MOVWF X_HIGH
CALL CORDIC_ROTATE ; 执行旋转
; 此时xn = 幅度*cos(z0), yn = 幅度*sin(z0)
现象:小角度计算结果偏差较大
CORDIC标准算法仅适用于±90°范围,扩展方案:
c复制// 角度范围扩展伪代码
if(angle > 90°){
angle -= 180°;
result_sign = -1;
}
cordic_calculate(angle);
result *= result_sign;
在模拟器中遇到的典型问题:
振荡问题:在z接近0时方向频繁变化
溢出问题:大角度计算时结果异常
时序验证:
assembly复制BSF PORTB,0 ; 测试点1
CALL CORDIC_ROTATE
BCF PORTB,0 ; 用示波器测量脉冲宽度
结合旋转和向量模式,实现直角坐标←→极坐标转换:
assembly复制; 直角坐标转极坐标
MOVF X_IN,W
MOVWF X_CORDIC
MOVF Y_IN,W
MOVWF Y_CORDIC
CALL CORDIC_VECTORING
; 此时zn=角度,xn=幅度/An
; 极坐标转直角坐标
MOVF AMPLITUDE,W
MOVWF X_CORDIC
MOVLW 0
MOVWF Y_CORDIC
MOVF ANGLE_H,W
MOVWF Z_HIGH
MOVF ANGLE_L,W
MOVWF Z_LOW
CALL CORDIC_ROTATE
对于PIC16F系列无硬件乘法器的局限,可考虑:
基于相同核心算法可实现:
我在多个电机控制项目中实践发现,将CORDIC算法与PWM中断服务程序结合时,需要注意保存上下文的速度。一个实用技巧是在进入CORDIC计算前:
assembly复制MOVWF W_TEMP ; 保存W寄存器
SWAPF STATUS,W ; 直接操作STATUS会影响标志位
MOVWF STATUS_TEMP
这比完整的PUSH/POP操作节省至少10个周期。