stm32g4_cordic与定点带符号整数数据格式2019年st推出的g4系列芯片是stm32系列第一款带有cordic协同处理器的芯片。cordic协同处理器提供某些数学函数的硬件加速,尤其是三角函数。它能加快这些函数的运算,释放处理器以执行其他任务。通常用于电机控制、测量、信号处理和许多其他应用。
# d: [# b& q0 o0 v# t0 w
1. 关于cordiccordic(coordinate rotation digital computer坐标旋转数字计算机)是一种用于计算三角函数和双曲线函数的低成本逐次逼近算法。最初由jack volder在1959年提出,它被广泛用于早期计算器当中。cordic算法通过基本的加和移位运算代替乘法运算,具体原理不在此赘述。
: f6 m( @7 a3 }* z: t" {# y
坐标旋转算法示意图6 w/ u q4 l) e
) y1 l6 a& _8 ?, z6 l
2. stm32g4中使用cordic2.1 初始配置使用stm32cubemx激活cordic,再按需选择配置nvic或者dma。生成代码支持hal库和ll库。此时代码包含了cordic的初始化(cordic_initialize),不包括cordic的配置(cordic_configure)。需要用户自行实例化结构体cordic_configtypedef: 9 q1 k- y. _2 e( c# u/ y
0 m0 d# \4 p/ o1 g. m r结构体成员变量) q9 x$ n: k5 i& {
4 n2 i0 m) ~7 `. j z1 h! x/ l/ ?function包含共10种函数:余弦、正弦、方位角、取模、反正切、双曲余弦、双曲正弦、双曲反正切、自然对数、开平方。 scale指缩放因子;cordic要求输入数值在[-1,1]区间内;设输入值为x,缩放因子为n,则会先对输入数值做运算x=x·2^-n,cordic计算完成后输出结果y再做运算y=y·2^n;缩放因子取值范围视所选函数规定,在[0,7]区间内;缩放因子可能会导致运算精度的丢失。(可查阅st官方文档:rm0440) insize与outsize:提供两种输入输出数据格式q1.31和q1.15;每次向cordic输入数据时,会发送一个32位的数据,当选择q1.31数据格式时,cordic一次运算一个数据;当选择q1.15数据格式时,将两个数据封装在一个32位数据中,cordic一次运算两个数据;选择q1.15数据格式能提高运算速度,但牺牲了运算精度;有关q1.31和q1.15数据格式的内容下文会讲到。 nbwrite与nbread:写入写出的参数数量;有些函数,比如求方位角、取模,需要输入x,y两个参数才能进行运算;有些函数,比如余弦、正弦,cordic运算后可以输出两个结果,例如进行余弦函数运算时,可以输出一个余弦结果和一个正弦结果;两个数据以交替的形式进行输入输出;输入时若次要参数一直保持不变,可在第一次计算开始后将nbwrite设置为1个参数输入模式。(可查阅st官方文档:rm0440) precision指迭代周期;取值范围为1到13;迭代周期越多,运算精度越高,运算速度越低;当运算精度到达数据格式所能表达的精度极限时,继续增加迭代周期毫无意义,例如迭代6周期运算已经达到q1.31数据格式正弦函数运算所能表达的精度极限,继续增加迭代周期的运算结果与迭代6周期的运算结果无异;对于大多数函数,迭代周期推荐3到6周期。 配置完结构体变量,后使用cordic_configure函数将数据写入cordic_csr寄存器,cordic的初始化和配置完成。
函数输入与输出
?: b b6 x- t2 [/ e% g * i* ~$ |$ c/ h
4 p. s0 g3 c l& ^cordic_csr寄存器 p# \) `* o1 j! ?
1 q3 ^, z* v) q; c: e1 d r6 r6 ]9 e
$ k% _* a' s8 g# u* v& m& k2.2 cordic的读写操作步骤- 零开销模式(zero-overhead mode)
该模式下cordic运算效率最高,上一个结果被读取后即刻开始下一次运算,期间没有空闲时间;该模式中cpu的占用率为100%。
, p/ p9 i, r9 v" k0 b6 o
% c6 q) a8 e9 b w! f g2 h
- 配置cordic_csr寄存器,也就是初始化和配置cordic;
- 向cordic_wdata寄存器写入参数,写入完成后cordic将会开始第一次计算;
- 如果需要,为下一次计算重新配置cordic_csr寄存器,此时不论上一个计算是否完成,重新配置csr寄存器不会对上一次计算结果产生影响;
- 向cordic_wdata寄存器写入下一次计算所需参数;
- 从cordic_rdata寄存器读取上一次计算的结果,读取结果的操作完成后会触发下一次计算的开始;一旦开始计算,读取cordic_rdata寄存器的操作都会插入ahb总线等待状态,直到计算结束才返回结果;因此,即使cordic未运算出结果,也可以进行读操作,当计算结果返回时读操作完成;
- 重复第3至第6步骤;
- 从cordic_rdata寄存器读取最后一个结果,完成计算。
9 y7 d& o2 p1 x
# d/ h% \* x% ^
' s9 r/ p3 h3 ?零开销模式示意图
. ?# z8 a4 g$ n9 \6 y/ j y! v' ?. b/ n( q$ k/ f) g
- 轮询模式(polling mode)
该模式会轮询cordic_csr寄存器的rrdy标志位以判断运算完成;该模式不会使cpu处于100%的占用率,使cpu可以处理其他任务;轮询模式会比零开销模式消耗稍长时间,因为计算完成后结果不会立即被读取,需要等待下一个轮询周期到来,且读取rrdy标志位后再读取结果会产生延迟。 f* j; l6 u q7 r
, g8 t) p0 _) c2 m3 s. c
- 配置cordic_csr寄存器,也就是初始化和配置cordic;
- 向cordic_wdata寄存器写入参数,写入完成后cordic将会开始第一次计算;
- 如果需要,为下一次计算重新配置cordic_csr寄存器,此时不论上一个计算是否完成,重新配置csr寄存器不会对上一次计算结果产生影响;
- 向cordic_wdata寄存器写入下一次计算所需参数;
- 轮询cordic_csr寄存器的rrdy标志位,直到该位被置1;
- rrdy标志位置1时,从cordic_rdata寄存器读取上一次计算的结果,读取结果的操作完成后会触发下一次计算的开始;
- 重复第3至第7步骤;
- 从cordic_rdata寄存器读取最后一个结果,完成计算。$ ? {" x, {$ u3 o, b) \ i
; b8 w7 b5 o e% s' _ d" k
- 8 ?4 |: p; t6 u4 `' x
- 中断模式(interrupt mode)
当rrdy标志位被置1时产生中断信号,该位被置0时会清除中断标志位;该模式下使得结果的读取具有优先级;因此会比零开销模式和轮询模式消耗更长时间。 ; z5 w d8 e# b, {! d, o" d z8 ]
' d! n h% q# y: b# x' j, r
- 配置cordic_csr寄存器,也就是初始化和配置cordic,并且设置ien位为1;可以直接在stm32cubemx中设置为中断模式;
- 向cordic_wdata寄存器写入参数,写入完成后cordic将会开始第一次计算;
- 如果需要,为下一次计算重新配置cordic_csr寄存器,此时不论上一个计算是否完成,重新配置csr寄存器不会对上一次计算结果产生影响;
- 向cordic_wdata寄存器写入下一次计算所需参数;
- 当计算完成,rrdy位被置1,产生中断信号;在中断服务函数中读取cordic_rdata寄存器上一次计算的结果,读取结果的操作完成后会触发下一次计算的开始;读取cordic_rdata的操作会清除rrdy位和中断信号;;
- 从cordic_rdata寄存器读取最后一个结果,完成计算。
p7 p8 u0 ? v) `' d# o) e, u
$ a! g- y; ~% x4 f6 p
- $ o& h) o [; q- x! k6 e) a5 |; e
- 直接存储器访问模式(dma mode)
该模式下cpu占用率为0%;dma请求不能对cordic_csr寄存器进行读写操作,因此dma模式只适用于相同模式下的运算,比如使用相同的函数、相同的缩放因子、相同的迭代周期等;dma模式下,数据的来源与输出目的地不一定是片上内存,可以是其他外设,比如dac和adc;
: n$ g' k9 s h }2 i
0 m3 p* z. b' a3 c# z5 m
配置cordic_csr寄存器,也就是初始化和配置cordic;在stm32cubemx中设置dma模式; 使用cordic_calculate_dma函数启动dma运算,入口参数中配置数据源的地址以及输出地址,并且设置dmawen位和dmaren位; 注意事项:当dmawen置1时会产生dma写请求,当dmaren置1且rrdy置1时会产生dma读请求,因此要暂停dma读写只需将dmawen和dmaren置0即可;在dma模式运行中应避免对cordic_wdata寄存器做写操作和cordic_rdata寄存器做读操作,否则可能会产生dma阻塞。 ; k1 p4 c; q2 x
0 ?" e% f- k& k! s! e! c8 x u5 `3. 定点带符号整数数据格式(q1.31,q1.15)3.1 定义在q1.31格式中,数字由一个符号位和31个小数位(二进制位)表示;数值范围是-1(0x80000000)到1 - 2^-31(0x7fffffff);精度是2^-31(大约5 x 10^-10)。
4 m6 d' v6 n) u% ?
) p l5 ^- t' n. m
* k, p9 y8 p8 j5 j' o% x) a9 |在q1.15格式中,数字由一个符号位和15个小数位(二进制位)表示;数值范围是-1(0x8000)到1 - 2^-15(0x7fff);
4 a# k3 j9 [2 z9 d
: [! n; x. x9 o% p8 c4 b' }4 [
7 \! c0 ~% e l" i6 }7 @" z
8 @, o/ m0 y$ ?# p这种格式的优点是可以将两个输入参数打包到一个32位的数据中,并且可以在一个32位的读操作中获取两个结果;但精度降低到2^-15(大约3 × 10^-5)。
9 x* m v- s/ k4 y0 p. ~2 c# u3 g9 @& e3 {6 k' h
3.2 带符号浮点格式的转换q1.31或q1.15格式对开发者而言不够直观,以下提供四条函数,可将q1.31和q1.15格式转换成带符号浮点数。
! m" `) e( b- ?, m
将带符号浮点数转换成q1.31格式: - /**% c. s4 g" ~; }9 p9 ~4 [& z
- * @brief 将数据转换成cordic要求的q1.31整型数据格式。
* y, w2 @ w$ g5 j% ~% |8 s - * @param 需要转换的数据,取值可以为负数。
! l/ z; l, r b% y6 }4 z - * @param 比例系数;数据除以比例系数之后再转换格式;9 q2 f- y6 m1 f$ h8 d' v0 ~
- * cordic的输入参数数值在[-1,1]之间,故需要先除以一个比例系数。
1 ?9 \; n$ c4 }$ l8 m2 k! g - * @ref stm32g4 series advanced arm-based 32-bit mcus - reference manual" }1 v8 q" g6 }2 k3 r$ a
- * @retval q1.31整型数据' y8 ~& x4 g% n. }6 ] f' a
- */
. o" f; k a2 ~6 l" a - int value_to_cordic31(float value, float coefficient)
. a) m3 r" a6 u$ b - {
* t* x$ ^; m- w6 }8 \1 _1 t - int cordic31;) ?* j0 i" j1 h% r
- cordic31 = (int)((value/coefficient)*0x80000000);3 y; b- ^, o$ g( m) \
- return cordic31;4 w q) i3 s3 o
- }
复制代码将带符号浮点数转换成q1.15格式: - /**# t* f4 p3 t* t. s
- * @brief 将数据转换成cordic要求的q1.15整型数据格式。
" r3 u/ z. k8 m, ^* p2 t - * @param 需要转换的数据,取值可以为负数;该数据存放在高16位。5 t5 u( [* b2 n5 k- i
- * @param 需要转换的数据,取值可以为负数;该数据存放在低16位。
4 c9 n2 _" l) [7 c1 ^ - * @param 比例系数;数据除以比例系数之后再转换格式;
0 w# \- w) m1 ~ ?# t3 o - * cordic的输入参数数值在[-1,1]之间,故需要先除以一个比例系数。
a$ a) n. }/ x, l: z8 q - * @ref stm32g4 series advanced arm-based 32-bit mcus - reference manual8 q# f' ~# d6 b9 d, o
- * @retval q1.15整型数据6 u/ a& j8 ^6 f
- */
0 q& j \" g8 r0 l, `5 f - int value_to_cordic15(float valuea, float valueb, float coefficient)
$ e' v5 a' a" c2 x3 z( r& z - {9 a* o$ g2 o6 k$ ~' a) r5 b" i, i
- int cordic15;, @9 o6 p5 ~8 e/ u; g {7 h: h
- cordic15 = (int)((valuea/coefficient)*0x8000) << 16;
- m: @* {" d' y - cordic15 = cordic15|(int)((valueb/coefficient)*0x8000);
$ j b: y$ @" t1 j0 m8 n - return cordic15;
m- c0 ]/ `5 _% y' [9 u5 \" s - }
复制代码
' |, ~! e; z5 i j& z" r
" t) |. l/ g* j& ` ~# k9 _将q1.31格式转换成带符号浮点数: - /**; {7 p; o) r) i6 n3 ?" n% ?; t
- * @brief 将cordic输出的q1.31整型数据转换成带符号的浮点数值格式。
2 n6 f8 m0 h5 y - * @param 需要转换的数据。) m, q; w q" [' f- g! }
- * @param 存放输出数据的地址。/ `3 i" b, o: f( q3 h9 [, e' r
- * @note 转换无精度损失;- b# l0 m0 j5 z7 i# y a
- * 精度要求不高时,可以将double类型换成float类型以节约ram空间;此时精度将下降至1/10000000。
l% x4 |2 v2 l- w& ? - */
' `4 p* h& a j, k9 ?4 c o/ v e/ e - void cordic31_to_value(int cordic31, double *res)) e$ q w: v) f) k& e; a' m7 }
- {
5 p; x4 y$ h0 \! l& r6 d! w - if (cordic31&0x80000000)
8 l) b5 l) }" u& a! f! \ - { /*为负数*/
& y* k* k; v: l - cordic31 = cordic31&0x7fffffff;
# d* u, y9 y1 [7 v - *res = (((double)(cordic31)-0x80000000)/0x80000000);2 _6 m1 y5 n) y4 h* q# v6 z% d4 m
- }
/ b' g% n3 b5 d - else
$ q7 e/ c s9 v - {/*为正数*/# p7 w s2 h$ r- g' b
- *res = (double)(cordic31)/0x80000000;' j: w- f0 l: f& o3 y6 c
- }0 q( t, r; r' p& d0 t5 k
- }
复制代码 ; t c6 {3 h0 b4 e9 u# u
t% h4 ?1 q# b6 k g0 o' ?
9 y! t4 `! w" z0 i* p将q1.15格式转换成带符号浮点数: - /**
/ a6 g: r2 m3 g4 q - * @brief 将cordic输出的q1.15整型数据转换成两个带符号的浮点数值格式。$ a' `& k' ? q! x( `: y
- * @param 需要转换的数据。! w( m* m( z( @9 i
- * @param 存放输出数据的地址;输出高位数据。/ h v e z3 o1 p i) k @2 p
- * @param 存放输出数据的地址;输出低位数据。
1 s& u( z: h# a9 @1 v5 z& g - */ n) n2 [/ b. o b4 e9 w& b
- void cordic15_to_value(int cordic15, float *rea, float *reb)! l, q' k3 m8 t; r, r- y
- {
3 b o' r% l9 n) a4 i/ d - if (cordic15&0x80000000)//处理高16位7 q' _; [ f: f, ^
- {/*为负数*// k% v( u# m2 i, j. o1 s
- *rea = ((float)((cordic15>>16)&0x7fff)-0x8000)/0x8000;
4 ^# z. w1 w8 {3 u- h - }
$ u* u v' s. f6 p/ e3 x - else0 y$ w: ~( k, p! t/ i* y: p# t6 ]; p
- {/*为正数*/
" j g% u# a7 j# b) z4 }, ]$ v - *rea = (float)((cordic15>>16)&0xffff)/0x8000;2 r; d& t% b7 z
- }
s) c. e0 q- d4 a" h( h. x8 j7 u - if (cordic15&0x8000)//处理低16位6 o9 k- j( x. i3 n3 o, n
- {/*为负数*/8 g; m1 y7 \, o& f4 s3 p& g2 x
- *reb = ((float)(cordic15&0x7fff)-0x8000)/0x8000;
5 o! @5 n$ t) v1 o# w1 k) c - }
9 l; e% ?# n) ~& x& @1 u ~* o - else
1 \) s$ g' b) ] s - {/*为正数*/
# h# @0 l/ r, _- a' f& m - *reb = (float)(cordic15&0xffff)/0x8000;
i- w: ]. m1 c5 h - }
1 e2 x1 d: ]" p! t5 h% m - }
复制代码
% \3 t8 m4 |# w# a p i4 v# s* n5 p
|
关于迭代次数有些问题,原文中“对于大多数函数,迭代周期推荐3到6周期。”,实际cordic精度在迭代20次左右时q1.15的误差才趋于稳定(来源rm0440 16.3.5图)。
[md]补充一下,csr寄存器的precision位表示的是“迭代次数/4”,因此原文中应该是配置precision为3-6,对应迭代次数为12-24次