50.1 初学者重要提示
0 s- \" Y; b/ e) CDSP库支持了样条插补,双线性插补和线性插补,我们这里主要介绍样条插补的实现。
6 x$ ^/ z7 E' o7 H8 s7 h2 `! j: b% Q2 o* C% J) W2 D8 G. h
50.2 样条插补介绍
4 k* E( P+ H, X$ r& l8 g* q在数学学科数值分析中,样条是一种特殊的函数,由多项式分段定义。样条的英语单词spline来源于可变形的样条工具,那是一种在造船和工程制图时用来画出光滑形状的工具。在中国大陆,早期曾经被称做“齿函数”。后来因为工程学术语中“放样”一词而得名。在插值问题中,样条插值通常比多项式插值好用。用低阶的样条插值能产生和高阶的多项式插值类似的效果,并且可以避免被称为龙格现象的数值不稳定的出现。并且低阶的样条插值还具有“保凸”的重要性质。在计算机科学的计算机辅助设计和计算机图形学中,样条通常是指分段定义的多项式参数曲线。由于样条构造简单,使用方便,拟合准确,并能近似曲线拟合和交互式曲线设计中复杂的形状,样条是这些领域中曲线的常用表示方法
( j8 ]' k K4 m, Z6 h+ u* S5 |% U" g9 a+ O% Y! }' u2 `
50.3 样条插补实现" m% `" B% B9 I W' }
样条插补主要通过下面两个函数实现。: q% T, a( q A/ F
* t$ `$ y/ k3 Z5 l$ A2 Y4 o7 m50.3.1 函数arm_spline_init_f32
' @0 z$ b7 g2 F3 d% s, h3 T函数原型:+ {; L2 e: w( `/ p' S* E! p
/ }3 c- o5 i$ `4 W
- void arm_spline_init_f32(
( H& k! R" G- [3 r3 J5 G# B9 B - arm_spline_instance_f32 * S,
5 Z; S1 I$ I3 U- D1 n9 K - arm_spline_type type,& g" Q) Y7 }, d$ @* D
- const float32_t * x,
" E3 m6 I, v) i% v* ] - const float32_t * y,9 V- z. V# x' d5 k4 n. A& {' J
- uint32_t n, 0 B/ @/ p- Y8 r f5 V
- float32_t * coeffs,0 l; y0 P3 [( A1 N1 k2 g
- float32_t * tempBuffer)
复制代码 ( ?0 q% f& E' `5 Z
函数描述:, ?& Q+ r' i% P$ d* u- S2 b
* F1 E& q5 S# ~; C# T3 Q
此函数用于样条函数初始化。
( ]) I' R* Y* R2 g* J& p, i; @) u; W2 m! z; e9 Y
函数参数:+ w0 Y7 Z* z8 K9 r+ D
2 ~2 K9 @" k+ ` 第1个参数是arm_spline_instance_f32类型结构体变量。 e! a. } j% N+ e9 h4 {
第2个参数是样条类型选择:
: m4 u% Q: B' ?$ I ARM_SPLINE_NATURAL 表自然样条。
$ @( Y+ X7 K5 ^) z ARM_SPLINE_PARABOLIC_RUNOUT 表示抛物线样条。% u$ l. W% N6 Z+ P( W
第3个参数是原始数据x轴坐标值。
) S; c5 v& Y5 l 第4个参数是原始数据y轴坐标值。
# z/ h4 |# t5 ~6 G- R0 w 第5个参数是原始数据个数。
( F) m# P2 o; r 第6个参数是插补因数缓存。2 ^2 ~7 P3 D5 {% \' k {1 G
第7个参数是临时缓冲。
" H* E6 s* O8 {( M" N0 s) [注意事项:
* e7 `% [9 a4 a" P" n! I% y% L) z W3 ^% z9 m% M) W+ p$ t
x轴坐标数据必须是递增方式。
- m, m/ ~( P. W% r. j m! S 第6个参数插补因数缓存大小问题,如果原始数据个数是n,那么插补因数个数必须要大于等于3*(n-1)。1 B$ y, N: M# q& D- ^# s
第7个参数临时缓冲大小问题,如果原始数据个数是n,那么临时缓冲大小必须大于等于2*n - 1
( I# W+ w: v' i G$ E7 I
/ R8 `# h" s t, \& U50.3.2 函数arm_spline_f32
* }, F O8 c) }& N函数原型:
: A+ H3 }% Q1 g a. V' |3 s" m
: b4 g) Y& e7 L( J. I- void arm_spline_f32(
0 d) r( N8 a2 C$ O - arm_spline_instance_f32 * S, : o: z4 M: N1 B5 e% C/ p. X
- const float32_t * xq,
8 v4 p& m9 b% v( Y - float32_t * pDst,
' c+ n) [9 w7 ~$ \ - uint32_t blockSize)
复制代码 / D+ l8 B7 E; n" J! |
函数描述:
( F7 T& i9 n" I8 o+ i
6 Q( i4 E- E' p4 M" Z此函数用于样条插补实现。( N" X& I7 q7 F8 w% r
6 O$ H0 F. A% q( p' n- I. n' x
函数参数:) t/ {7 |' e# u& R
2 ^- t1 r, I. L9 a; m 第1个参数是arm_spline_instance_f32类型结构体变量。
3 ]/ F- C+ C3 a n0 B 第2个参数是插补后的x轴坐标值,需要用户指定,注意坐标值一定是递增的。) j1 E q$ x' ~; e' @
第3个参数是经过插补计算后输出的y轴数值
: q: W& ^+ T1 T 第4个参数是数据输出个数. A& C0 A! c( R3 ~2 h' x7 G
50.3.3 使用样条插补函数的关键点6 r/ {7 s' ^4 A1 ^1 C* I
样条插补的主要作用是使得波形更加平滑。比如一帧是128点,步大小是8个像素,我们可以通过插补实现步长为1, 1024点的波形,本质是你的总步长大小不能变,我们这里都是1024,这个不能变,在这个基础上做插补,效果就出来了。
( C0 F5 S2 f* h' i! I4 x( e' n" O) M( M) f$ S
这个认识非常重要,否则无法正常使用插补算法。
: Z: `# G% r# E5 |- v l# A* P
# L# b w7 {- q: }* n50.3.4 自然样条插补测试
- N- ]0 }+ l) v2 |) l# T; F# ?样条测试代码的实现如下:
1 H% c; n' I, \% X" Q1 {! L
6 n3 H0 P6 l4 v. I- #define INPUT_TEST_LENGTH_SAMPLES 128 /* 输入数据个数 */" b' S( B4 M n% w* @' t6 G l' d
- #define OUT_TEST_LENGTH_SAMPLES 1024 /* 输出数据个数 */5 C4 T5 T3 u0 o5 g
- ) X2 x" D7 A5 V5 g& H4 F
- #define SpineTab OUT_TEST_LENGTH_SAMPLES/INPUT_TEST_LENGTH_SAMPLES /* 插补末尾的8个坐标值不使用 */
9 C1 [4 f9 g3 e+ m3 c6 r
+ R& G& @- ^1 O9 Z6 ^$ j H5 D
$ U, J$ E# {: |/ G1 D9 K- float32_t xn[INPUT_TEST_LENGTH_SAMPLES]; /* 输入数据x轴坐标 */$ }% g) \7 Q6 }
- float32_t yn[INPUT_TEST_LENGTH_SAMPLES]; /* 输入数据y轴坐标 */+ b% ?2 f3 i% h/ K: B" @. y
- $ _ L, L m/ e' `! v7 t! Z; T# S
- float32_t coeffs[3*(INPUT_TEST_LENGTH_SAMPLES - 1)]; /* 插补系数缓冲 */
?% f* {5 c* X7 q3 m - float32_t tempBuffer[2 * INPUT_TEST_LENGTH_SAMPLES - 1]; /* 插补临时缓冲 */ ) d. h- w% c" T2 Y
5 E. W+ c; Q8 x3 O, v u% @- float32_t xnpos[OUT_TEST_LENGTH_SAMPLES]; /* 插补计算后X轴坐标值 */
! H; F! Z, W2 d+ K6 t - float32_t ynpos[OUT_TEST_LENGTH_SAMPLES]; /* 插补计算后Y轴数值 */
9 }, o( n- K* A0 d: Q - - r# l# b) E: }/ p& c: s5 n
- /*, R8 I# @ ?$ a- L5 P1 L6 \+ X
- *********************************************************************************************************) E* _9 X( m/ |5 [1 ~) d. I2 h
- * 函 数 名: main
2 j% P! A K' c/ c8 b, G. S2 s - * 功能说明: c程序入口- z( \6 s4 b: D; g5 a* K& g, A! E
- * 形 参: 无( l& d4 @+ J3 N/ t" |0 p$ x3 A) P
- * 返 回 值: 错误代码(无需处理)
9 L! Y1 _$ j M- @2 R3 A# | - *********************************************************************************************************
& M B9 z: j1 R7 l" Y* X1 p; V - */
4 t* c( @' ]# x. X - int main(void)
4 z- W) b9 I0 ~* ?( ~" b - {
2 r3 p* |5 V) A+ r! O( P - uint32_t i;
& Z: `7 _+ p _/ v2 \/ ^7 H - uint32_t idx2;2 G ^' c2 t4 L/ u' j: H3 y$ S
- uint8_t ucKeyCode;
( R+ L( w( Q w3 ]! H+ T8 j - arm_spline_instance_f32 S;
5 m0 E. k, X: f! O5 g J2 | -
% c$ c7 h( X) }3 {( a& F, e - 6 ?+ v* }& ~9 z; f
- bsp_Init(); /* 硬件初始化 */
0 u" J o5 U. K# X - PrintfLogo(); /* 打印例程名称和版本等信息 */
9 p) h0 J* j; N5 t$ t2 q2 n; i - PrintfHelp(); /* 打印操作提示 */
+ ~( G3 t; E% \( T -
8 a: o2 {9 n- n6 Y5 Q' a9 H - bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */. O" g/ k! p! [% G: P( Y
-
* @# O8 a; }- v; C- e$ t -
% n# |4 O f5 m, K" C) H - /* 原始x轴数值和y轴数值 */
! h3 e/ v4 M! t. |/ G - for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++)
/ B4 [6 U2 z" N3 A; A0 J$ Y - {
$ Y" S& ~! j6 g* G4 g6 ~/ h - xn<i> = i*SpineTab;# q3 K* W4 p. Z! h* E
- </i> yn = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3);
) i( g# S1 d/ n6 Z' z - }
2 {0 {5 T/ @; b1 {. H -
2 h6 v: Q$ J$ g$ Q+ Y" {# Z - /* 插补后X轴坐标值,这个是需要用户设置的 */
* t" ?- F+ ]# P - for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++)- L6 R0 Z1 Z( r) A
- {3 X9 ^) N, X5 G& [
- xnpos = i;; o2 r. G7 |3 \8 E: ^
- }$ Q0 r" N3 S$ P' J
- # Y3 h7 t% ?! q1 X' s5 j; \3 s
- while (1)
4 z' s& J2 d5 Q - {4 S5 j' ]2 V4 F4 @' Y' I6 L; _% A$ M
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */: J" | N0 m0 n8 v j) c
- , c. B. m& L/ Z& n3 T! t) \ \
- /* 判断定时器超时时间 */
* ^/ g6 `( h, v: y6 x2 F, f - if (bsp_CheckTimer(0))
0 V4 q; { w% \+ Q - {, g" k0 b4 q6 P8 E0 a/ x* t
- /* 每隔100ms 进来一次 */ 1 x, p6 z4 }, C
- bsp_LedToggle(2);
$ M6 q& M T; G5 c* a - }
3 V1 y" O% q* O4 v }' ^3 ?
. d7 z, S+ G% w, z) T/ L- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
, P9 W% Q6 d4 v - if (ucKeyCode != KEY_NONE), b4 x+ D# v; K& O& x p8 W
- {4 @" u6 f& H+ w& i9 W1 u$ P
- switch (ucKeyCode), Q5 Z2 Y, M d: K+ f
- {2 N1 [6 N ?; h; A% f# p
- case KEY_DOWN_K1: /* K1键按下,自然样条插补 */
" ~1 A7 o& s2 b7 l( O9 R; G - /* 样条初始化 */% {! u( r4 L. }8 g( }$ D' p; |
- arm_spline_init_f32(&S,2 T+ X: E8 [6 _- h8 ], f# l
- ARM_SPLINE_NATURAL ,
2 j9 m2 g. Q$ l* |5 a4 e; K - xn,7 `6 B% Y8 ^' P8 X1 k q! L K
- yn,; a" Q0 V$ V! x! K$ u0 p \
- INPUT_TEST_LENGTH_SAMPLES,
9 P3 }0 C3 ~# [8 B. G- F' U; s - coeffs,
. a8 ^2 S- p F4 D - tempBuffer);" B' M2 Y* \# H" T
- /* 样条计算 */4 [' k6 n0 B. B
- arm_spline_f32 (&S,$ y4 m2 y5 D2 n: Y, L# W1 I6 [
- xnpos,
6 `7 _& L9 t& N - ynpos,
- {0 x, A5 K% A* {# E/ r- M - OUT_TEST_LENGTH_SAMPLES);
- T! [" q0 M5 b$ U3 F - " a" u- r' \3 x. f0 h
-
% o, J+ k7 D1 f - /* 打印输出输出 */" r( V$ J& P; _; N# N, x$ K' g( z
- idx2 = 0;) c. u' ?5 L1 W' e/ ? z
- for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++)/ K* w8 Y. D6 A
- {
: g* v L$ B5 u. S - if ((i % SpineTab) == 0)
& R3 [( N1 s" N2 |. T' z( K& E, h) \* N - {
3 Y; V- @/ f; [: S - printf("%f,%f\r\n", ynpos, yn[idx2++]);! {# s. Y& `0 w# Q: I
- }
& @0 ~+ t0 Y0 A p - else
& D: [5 q4 t5 }7 x$ ~ - {
# n% g* g+ Y$ ` - printf("%f,\r\n", ynpos);
9 ^( _1 k3 c: h9 [ - }4 }, f' S" T/ J2 u$ ]' R( {4 _
- }! } x d5 b6 K, V2 O5 U: K& ^
- break;
- `+ W3 e, R* W, e% S# x1 d6 p - / r3 h9 q3 t1 D4 Z+ E6 v
- default:
2 u, g) Q9 ]4 U0 l - /* 其它的键值不处理 */
1 A- [. l* c2 |* o1 w; ^ - break;
' y- y: l, K$ w, t9 z T8 C - }) ]- r1 J' E [* Q! _
- }
! K6 B5 l1 z& w2 ]0 q- |: w - }$ s; `+ s2 v+ z6 N# Q) D
- }
复制代码
& K2 e7 M3 [0 X代码里面的几个关键地方:( _9 P9 ?5 o6 _4 Z; P6 }
?# p1 v2 a3 j
原始坐标数组xn和yn是128组,而我们通过插补生成的是1024组xnpos和ynpos,其中1024组的xnpos需要用户设置初值,这点不能忽略。% A/ i7 m8 g' r& j% b
函数arm_spline_init_f32用于样条函数初始化,这里特别注意,此函数主要是对原始数据的操作。自然样条插补用的ARM_SPLINE_NATURAL。
! U# L2 y2 H0 S# D* d& v 函数arm_spline_f32用于样条函数计算。" n1 c x; G! K3 ^; `
实际输出效果如下:! S. e* T# S4 \$ t% Q, n
& U) ^: f3 E7 ?1 B% R6 `# c
! }; Q5 C: ~/ g5 k! q, X& @; J$ O& a0 [6 Q$ l0 ]' A
6 S8 z$ m- `% d6 h7 X
3 b: P1 w) k9 g0 V: `5 ^% i
50.3.5 抛物线样条插补测试
}9 F* [- H! c: C! p1 }8 D N1 w# L样条测试代码的实现如下:
9 l' V2 n) [3 w& X1 @2 @; u. \+ D4 k# L
- #define INPUT_TEST_LENGTH_SAMPLES 128 /* 输入数据个数 */! V$ n! s! l( W( u" \. W7 Y$ j
- #define OUT_TEST_LENGTH_SAMPLES 1024 /* 输出数据个数 */
2 s( C4 t4 Y9 k* a1 |& [
5 d& v I* Y* v- z' C9 A K. P- #define SpineTab OUT_TEST_LENGTH_SAMPLES/INPUT_TEST_LENGTH_SAMPLES /* 插补末尾的8个坐标值不使用 */
, S# P8 f2 H" r3 V# g5 i$ k - . b- i/ ~7 ^7 a1 I) P! O
! g5 P$ O1 p" L3 b% Z6 V8 @- float32_t xn[INPUT_TEST_LENGTH_SAMPLES]; /* 输入数据x轴坐标 */
2 m5 O, c1 R8 ~9 }9 }9 H. X - float32_t yn[INPUT_TEST_LENGTH_SAMPLES]; /* 输入数据y轴坐标 */" e/ \2 m0 V( M* ^: t
3 q) y5 B: x8 Q" K0 u( Q- float32_t coeffs[3*(INPUT_TEST_LENGTH_SAMPLES - 1)]; /* 插补系数缓冲 */ 0 B1 X) [; O% H' M. q
- float32_t tempBuffer[2 * INPUT_TEST_LENGTH_SAMPLES - 1]; /* 插补临时缓冲 */
% \6 s* V: y- }3 {
, w6 F: i( y* _6 x- float32_t xnpos[OUT_TEST_LENGTH_SAMPLES]; /* 插补计算后X轴坐标值 */
6 c C6 O& w" L6 t6 h - float32_t ynpos[OUT_TEST_LENGTH_SAMPLES]; /* 插补计算后Y轴数值 */7 G5 ?. j! W+ Z) C7 \6 B
* O2 k! W0 z5 o5 ?! k# ^9 i+ T8 _- /*2 A5 N% V- U- t7 A$ p0 `
- *********************************************************************************************************
6 H* g' o% T/ w" } - * 函 数 名: main% x0 _/ M( N p5 p8 h$ D1 [: z
- * 功能说明: c程序入口2 [* X3 j: Z, Q
- * 形 参: 无
4 g ^' l; j0 S ]; Y0 {% q0 I j - * 返 回 值: 错误代码(无需处理)
* M+ a1 M( ]- z; \+ O) D* s2 Q5 Z. U - *********************************************************************************************************) F1 _3 U3 G9 n" L5 P2 `
- */7 x D7 t3 O Z5 D5 [
- int main(void)9 m. x6 h7 D& n4 y& o. v
- {! P, T4 T* h6 c q
- uint32_t i;) P, m# N, O* c; w; g- C) Q
- uint32_t idx2;# d; |% ~1 H6 q& ~1 n) Z9 j
- uint8_t ucKeyCode;
8 ~, }2 ^& _1 U! m - arm_spline_instance_f32 S;/ Z1 C! l7 C6 {: j
- / _1 O4 p3 C" o9 [( b+ p0 M+ `8 f, j# W
- 0 l( {5 ^5 H& o+ V
- bsp_Init(); /* 硬件初始化 */: M( ?9 Y/ f. r8 }" N, s
- PrintfLogo(); /* 打印例程名称和版本等信息 */+ @1 Z4 N) W* E$ Z7 Y" e
- PrintfHelp(); /* 打印操作提示 */
6 `2 @3 W( j' J8 b7 C' }: _$ v - 7 v& f& I) Z+ O8 b
- bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
* l' g2 [+ ~4 @5 v. t; m -
# }5 M, \- O6 R4 e# Z. [ - : `1 K- n2 T( x+ d
- /* 原始x轴数值和y轴数值 */
' c" S, n/ u! T* u1 u - for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++)
5 O+ H; ^- [3 A# x0 O6 F) [ - {
3 Q/ L: |9 I+ o% M* K - xn<span style="font-style: italic;"><span style="font-style: normal;"> = i*SpineTab;
7 v; u* z; K1 { ^ - yn</span><span style="font-style: normal;"> = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3);
& `& @% S5 Z. `1 b& C* K - }1 e+ [1 `9 z; E7 h7 m: h3 C9 C
- " c# f' q) d) N! c: i3 {$ v- s+ S+ f
- /* 插补后X轴坐标值,这个是需要用户设置的 */1 o* e. x- _! C1 e/ V+ ~; a
- for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++). q& N9 a1 `( t; h1 H6 n
- {
. |. Z/ Z6 r+ W" T: t - xnpos</span><span style="font-style: normal;"> = i;3 h1 R& s" }, Q4 R! |
- }
% N# W9 [3 v5 @. U, H+ W+ t% N4 W -
: F7 {+ u) W$ Z0 k( r' S" Y. f - while (1)
( \. q( \ ?. W& P# s9 n2 X' U* ]4 P7 h - {; E( m# w# v! D
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
* L l) V$ P, k2 J% Q
8 T" X5 T. y7 P+ x/ f- /* 判断定时器超时时间 */
9 A& s% D) \* N& z6 S - if (bsp_CheckTimer(0))
5 ~( m/ y2 u' F1 t$ Q' W7 H6 F - {
0 H2 y, P" }) X1 p - /* 每隔100ms 进来一次 */
* r6 _. v1 N4 _5 i' A5 m8 A - bsp_LedToggle(2);
& \6 Y' f5 u3 D2 s, E. W' k* k - }
; u+ a9 d+ b& o/ |7 b' N5 j3 Q2 w - : P2 V' b0 m( u
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
$ ^ c4 E9 [0 E' n5 f! X- O* D! o - if (ucKeyCode != KEY_NONE)
: F, a5 {" K+ o2 l0 G4 C" U - {
- C5 e' ]9 |' r - switch (ucKeyCode)1 G4 P' a$ F" j# u/ {
- {
. w# M* Q- l: b! a - case KEY_DOWN_K2: /* K2键按下,抛物线样条插补 */
1 g1 [' O7 v2 W2 c; B. x/ A/ ` - /* 样条初始化 */
0 U" [& z- {* @5 l5 [ b - arm_spline_init_f32(&S,% r8 O4 @; t7 h
- ARM_SPLINE_PARABOLIC_RUNOUT , $ S8 ?' j9 a. r. D" D5 |
- xn,' F2 T$ h( g) Z# w+ L
- yn,- H+ k+ i8 c/ Q% e+ T0 K
- INPUT_TEST_LENGTH_SAMPLES,
( _* u/ K# P0 Q - coeffs,
) u4 `* F' z* ?7 M% U; i) P) f* c; P4 g - tempBuffer);4 x( N3 R; Y. J6 z$ T- A, a
- /* 样条计算 */% H9 h6 s. t3 h% z) |
- arm_spline_f32 (&S,+ `+ d& J! A8 A; q
- xnpos,
8 t8 n7 e0 k: H q& o9 ? - ynpos,
. X9 {5 E* W/ i" p$ F9 u2 M ?+ t - OUT_TEST_LENGTH_SAMPLES);; T/ j2 C% M ?/ ~+ t$ R
- h1 H7 Y/ O# D# P
- . e8 x, g/ y" t" R) L( j- M! N5 g! ]
- /* 打印输出输出 */5 s. m7 ~+ ~" [1 |
- idx2 = 0;
" ?1 T) r$ z m, k% H0 k0 n - for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++); ~/ L5 {5 [7 C8 G$ Z f
- { 4 l: E9 }; `" }
- if ((i % SpineTab) == 0)+ y9 A0 k+ g" R/ v
- {
* V& \+ c7 O, c - printf("%f,%f\r\n", ynpos</span><span style="font-style: normal;">, yn[idx2++]);. v: m3 P8 H* l2 n0 i6 d! M6 @- r6 P
- }
( U% z3 F% s2 `) E/ B - else# h* F* g, P& S6 r& J1 \
- {4 u9 j; b! n5 ]' l$ X) n N5 d
- printf("%f,\r\n", ynpos</span><span style="font-style: normal;">);
( i- }1 ?% H2 M) s3 } - }: r6 y$ w# x" u, V6 l
- }
) y7 b! E F0 Y5 ^; X) T) @2 ]8 U - break;8 W; X, m4 X) |3 N. e q
- 1 D- w( t H* t# k' X
- default:4 \! M6 m& U1 u
- /* 其它的键值不处理 */9 F3 v2 d. V' ^- l( T/ {
- break;3 l- l2 l0 y: U5 t0 a( O
- }
' W% S. `' y( m3 h - }8 b6 P2 K4 Y7 `7 Y6 d9 _
- }
5 I, N$ z+ O1 Z% U* ]) Y - }</span></span>
复制代码
) `' C1 y V: |5 c7 s代码里面的几个关键地方:! N. ]* [" f3 l' j" Z
" ]. k0 S) k' H& L/ Y 原始坐标数组xn和yn是128组,而我们通过插补生成的是1024组xnpos和ynpos,其中1024组的xnpos需要用户设置初值,这点不能忽略。
6 K* h8 Q% X* `# x" n 函数arm_spline_init_f32用于样条函数初始化,这里特别注意,此函数主要是对原始数据的操作。抛物线样条插补用的ARM_SPLINE_PARABOLIC_RUNOUT。
; Z* L6 h/ {- E: Q/ S, b( U# N7 | 函数arm_spline_f32用于样条函数计算。
7 n1 z8 Z( P) }' p4 l5 C
4 F, i! _2 Y6 {4 U+ Y( s实际输出效果如下: }& _: ]" n5 H8 h
) Q6 D) Y; L3 \! S! k" W: ~$ k3 M2 x
1 h, w9 v/ ~2 V3 T" c( B6 ~& v: Z; V
$ ?2 i2 K8 N) |
50.4 实验例程说明(MDK)* ~" R4 k$ c) t) x. e
配套例子:/ w1 @9 y2 u0 E3 r
: y- s9 |' H% X6 p% l# n
V7-235_样条插补,波形拟合丝滑顺畅* d$ O- D0 O2 L# L, {2 Y
' ]# B: k) \) k8 G) k实验目的:9 o5 W1 `+ ~. ?8 K% H. t: f
* `9 ~. [# K- s p8 a$ l9 ^1 s& X
学习样条插补的实现。$ a$ n, J7 j: _1 j6 \
7 I. z; [6 E% V1 D" U: f4 V实验内容:! o, M. x' ?& `6 [
6 H& a/ X$ e p" s Q( v
启动一个自动重装软件定时器,每100ms翻转一次LED2。; }- _4 \2 ^* T$ u/ j0 {
K1键按下,自然样条插补测试。
6 k6 D8 G: E! c' _, m! YK2键按下,抛物线样插补测试。3 G& B' n4 p+ g4 M7 V4 [4 j
8 X* J# `5 V. y9 n/ Y5 N y0 k/ c: q# p2 x
使用AC6注意事项
1 S" ^- J# J, k6 R3 A
6 d% o( K& H% G4 f# P特别注意附件章节C的问题! C+ x$ b, p: s6 N D- e
3 c/ N& a1 y- p) F上电后串口打印的信息:: K' w! F/ `& U
; k5 C, u& d9 H2 w波特率 115200,数据位 8,奇偶校验位无,停止位 1。 r& ] I( x( M
, V* D6 I: y7 O+ e& o* l: \
/ H2 L* f6 s8 @" o J( G- a1 a Z
" s* p6 ^* h; h4 N* hRTT方式打印信息:' p6 o6 Q8 _* o! m: u" I
) L6 e( r6 j0 g
8 ^9 N2 g9 ~% r" m( U" m* p& v" p2 c9 d. D3 w; K
3 A$ u( L2 i% R! n3 a% r C( g
程序设计:
3 N2 D- q5 _& ?3 a- k3 _9 @; y# U" l0 |- X& {+ l4 j
系统栈大小分配:( z# K2 `& ~; k" W
/ l9 I1 y* {# E$ W6 w8 Y ~7 \3 z" V0 {/ [% L6 c
3 Z( a; |4 F6 n9 P' d5 [$ Q$ g- o8 z' s! v
RAM空间用的DTCM:) }+ D5 f# A! w- V5 d8 V
$ z# e' T' e% Q- \& P
* j+ u. F0 Y+ T* t4 h# y/ s* z, V! w1 I, x+ u& }$ @2 }* e) u; C& v8 p! v
. r0 ?5 @; o: _& f" n2 I. K- i9 X( g 硬件外设初始化$ _3 R o( ^$ {5 H; `
_" N8 K, F8 Y( J1 j
硬件外设的初始化是在 bsp.c 文件实现:
7 ?4 j# _! s. \" T# K4 B+ r" j! j+ j( Q6 e$ F/ t! X+ `
- /*
( o3 o# c5 x n1 K! Y4 l' H6 S - *********************************************************************************************************
. E2 J3 q. S' W9 R - * 函 数 名: bsp_Init9 U6 B9 ]# Y/ g) T' F8 B' C' @& y
- * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
- i* M* p+ K7 ^' @ - * 形 参:无7 D1 j8 R. d2 O3 K
- * 返 回 值: 无
6 e1 `7 F7 x) S - *********************************************************************************************************3 ^0 p0 l' T! W4 V
- */
& {& P( G' n# k$ q" ? - void bsp_Init(void)
- Z& T# k! n$ y/ ?5 Z& _8 O. c - {% L5 f0 c+ V# I& P" T
- /* 配置MPU */
6 K9 x* K* @3 x: R - MPU_Config();
. ]+ G$ v- Q& P/ c- g - 9 p# B" R- U" n1 v
- /* 使能L1 Cache */: k7 y9 R5 o6 t
- CPU_CACHE_Enable();- X9 `# l1 _0 v- Y: j
- 9 C3 A9 _; B6 X; V& ?( i
- /* ; i& ?: j6 H6 P5 ?) a) b, W
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
2 o* J) Y. Z6 @2 H9 d$ E @ - - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。8 F8 s, s& Y3 _
- - 设置NVIC优先级分组为4。
# E( |! X/ O2 d' H2 @1 A5 J - */9 A9 {" D- l% y! H! X2 F' E, L2 R
- HAL_Init();
1 I+ L" u4 U; R) B' T; ]! ]0 C' ]
% j8 R& H$ e5 [- /* " l& a# p9 O9 F- W; I
- 配置系统时钟到400MHz+ F; @7 C2 n0 V+ j3 n/ Z& l: u
- - 切换使用HSE。
* j1 k. y3 F! S4 x, ?/ G - - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
6 U P6 g; b9 y+ Z# | - */& X- }5 `, Q% {4 h- ]
- SystemClock_Config();0 S" A7 k' B0 f
; @" T! |0 y& ~% P1 }5 q- }- /*
+ X4 Q; Q0 w6 t; k: |1 @8 v - Event Recorder:8 l# e J' V9 T ~' E0 R
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
8 ~2 g0 q7 G0 ~) b6 }4 b - - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
8 w. C1 S3 }& {4 Y$ v - */ ' {8 b* u8 q- M4 V
- #if Enable_EventRecorder == 1 # c2 ?1 D) O& J/ r0 }
- /* 初始化EventRecorder并开启 */
9 @7 B0 N8 y) }5 n+ q - EventRecorderInitialize(EventRecordAll, 1U);* |! ~% q/ T& d( B: g
- EventRecorderStart();
- C" v9 c) D' t' r - #endif* |8 [3 H3 V; i6 u! m% y
-
9 O. @& s9 k- N+ ]5 t - bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */5 f# y. u3 X- o0 ^% W
- bsp_InitTimer(); /* 初始化滴答定时器 */
; P, y/ f1 A* d& f - bsp_InitUart(); /* 初始化串口 */
. y! [9 m0 r7 ?+ q9 A8 F - bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
3 e6 j5 u* T3 t - bsp_InitLed(); /* 初始化LED */ & x- \1 n K- `) ~
- }
复制代码 ! P! G' o; c, |4 G$ X
MPU配置和Cache配置:
! W3 d- e- U& S- {" _/ Z8 B8 u5 O: |! ], w8 z" }( n0 Y. s% _
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区。
( W, [% C# [% [) x4 U, A3 h" Z% g: w) b5 _# t' ?
- /*+ n1 i8 i0 O' S# ^5 I# r
- *********************************************************************************************************
7 l) _1 u, D/ C - * 函 数 名: MPU_Config0 l5 O% @3 m$ Y! T. v5 S- _! e
- * 功能说明: 配置MPU, I0 j( O+ \9 ?: N6 ^+ R+ l
- * 形 参: 无! e7 J; o/ _4 x$ U
- * 返 回 值: 无* Z9 Z" R6 M$ K. i, u/ A
- *********************************************************************************************************
z q1 ^$ ~& z9 ?/ i# O# Z) S% M6 {; D - */* f/ x) d3 Z/ y p9 W% |
- static void MPU_Config( void )/ R" `& p# O$ G C! C5 [1 L
- {
+ V0 R* B+ s6 @, X - MPU_Region_InitTypeDef MPU_InitStruct;
q& V# f: h* a8 H1 j) M
) x1 _) r! r! o- K4 Q- /* 禁止 MPU */
$ R- ~( @: d+ ^' r - HAL_MPU_Disable();/ j0 C& k P* ]4 d& l
- 9 m; C5 V( y, w& m; s) O7 x
- /* 配置AXI SRAM的MPU属性为关闭读Cache和写Cache *// {. T& i1 r2 l1 p- T
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;, g' Y, \& r$ K& s5 z
- MPU_InitStruct.BaseAddress = 0x24000000;( ~ @$ L3 C0 t% o4 e: s9 m
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; F- @( {6 t8 j6 y+ f: k7 D+ |
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
! p" ?4 k* e1 C - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT _BUFFERABLE;
/ f) F8 ]1 G. D; }9 ^ - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT _CACHEABLE;
0 \9 ^9 _4 i t8 I+ X - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
0 N6 [; v: N1 U1 q8 ` - MPU_InitStruct.Number = MPU_REGION_NUMBER0;2 f- V X1 ^4 m6 d+ A
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
! ]( l; Y% C9 e9 F: D# y - MPU_InitStruct.SubRegionDisable = 0x00;2 j& H. g+ e0 S3 R$ d' e
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;. v; y2 G6 _ E2 c: o' t
- 9 J5 |) `6 B. L# X1 {0 \4 @2 o5 g
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
4 t2 n" {7 E2 l- a( G - N6 Q( i( }7 f7 R/ @) d$ B, I% a
- / X+ q! |7 \ P
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
. T# m' B) d0 g5 o d - MPU_InitStruct.Enable = MPU_REGION_ENABLE;1 u- I7 y1 Z$ \1 ?" E: N X
- MPU_InitStruct.BaseAddress = 0x60000000;
8 f# n. `0 c9 [- `( s: M - MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
& u4 }! m1 m+ {: ^: [ - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
3 n& N/ O& m5 y& F, M - MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;& U' o" p! L, N
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
4 P" t$ B; P* h) ]% r - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;8 K8 J" N ^0 E
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
! [% @! B8 G5 X k: R) s+ N0 N, o - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;7 K6 J3 j* m! m" u5 t, n
- MPU_InitStruct.SubRegionDisable = 0x00;. f3 \3 n7 K7 A0 W' k4 v; F
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;* g2 k1 n) i6 F
-
6 O c" G1 O' ~3 r7 B# H - HAL_MPU_ConfigRegion(&MPU_InitStruct);1 _# b+ d' P0 [
+ ]3 A3 `1 N9 L& f% j T) `- /*使能 MPU */
2 L5 w# F% ?8 _( w/ m! Y - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
0 k& T9 Q) X- R! P - }
& G7 C+ |; O: d: T I U3 i - 0 x1 U! c- J- s$ [
- /*
! N* d% O( e: g - *********************************************************************************************************
' u9 e* V; s/ r: g0 M - * 函 数 名: CPU_CACHE_Enable
' l9 x- f$ A; @2 K6 a1 h# B - * 功能说明: 使能L1 Cache$ m# ~1 |, r* C2 Q* x2 t! c
- * 形 参: 无' G# v6 n, D% j3 j4 }4 t
- * 返 回 值: 无& v4 e9 a6 s/ L
- ********************************************************************************************************** C) W) x; a: T9 O6 z
- */
; S/ Z, ?( k6 L/ z; `7 i! J5 Z - static void CPU_CACHE_Enable(void)8 b& B. V6 r R" g* F) r' |
- {
. E/ T& H4 J, }: t z - /* 使能 I-Cache */* f# }+ E( J5 b" s% k
- SCB_EnableICache();
( q: Z- l! J# @/ g7 | - 0 U) M" ]+ e: o, {( @' H
- /* 使能 D-Cache */
+ z) a+ m% }8 Q- t8 ]' `9 ? - SCB_EnableDCache();
) X: G. Q& \! T" d. Z. s6 h - }
复制代码 2 i5 F: q4 A8 H- o9 s% w( q' A
主功能: }; }2 R5 Y/ n2 l4 i
6 [% B7 t! O5 y7 V( `$ B8 z C
主程序实现如下操作:
) Z; {6 n" g' F( R& C+ j( a
7 E/ s) T7 K$ o9 x3 \" x0 D1 I( d6 z/ F 启动一个自动重装软件定时器,每100ms翻转一次LED2。6 _9 w" ]. c* W) R* T! s2 N( a
K1键按下,自然样条插补测试。# u: W( ]7 e* c
K2键按下,抛物线样插补测试。
) k0 u- k4 N6 w# s g) P% I- /*7 @" D) `. q v u' `9 c; L
- *********************************************************************************************************8 A/ m4 l1 V3 h" `* l7 G
- * 函 数 名: main) x$ _+ M4 U) i
- * 功能说明: c程序入口
! N. Y* P+ g" L - * 形 参: 无0 ~9 Q: w9 m% F9 ~% \- d
- * 返 回 值: 错误代码(无需处理)) {% V0 T ~3 V
- *********************************************************************************************************
% p, {* ?/ h1 j1 ?6 @ - */" z! W5 M! v2 h! D/ U! j+ i# _
- int main(void)
2 t ^+ |' W, w5 Y; W: E. I3 N - {' b/ ~0 M- |9 ]8 c$ J1 V
- uint32_t i;/ l; z2 Z# e/ z. R
- uint32_t idx2;2 G: ], c# D& Q3 g6 y2 T" b7 b
- uint8_t ucKeyCode;
8 r$ W- ?; R y {, b3 O+ O8 v - arm_spline_instance_f32 S;
9 H6 B& b' b1 ^: E; d. m* A -
: l$ a( C( Z: z" g: ^. b- Q# S - % G' ~& Q& V4 l5 [
- bsp_Init(); /* 硬件初始化 */+ O3 C3 S9 T- L i: n7 ~* T
- PrintfLogo(); /* 打印例程名称和版本等信息 */
3 G) W# q/ P& v9 Y. d - PrintfHelp(); /* 打印操作提示 */( l. L* _7 T7 c6 A
-
1 Z% W, Z, r% ?8 Q1 [3 x- ?( A - bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
9 t9 `' `. E3 c: E( i -
4 q$ Z2 K; E( X5 _* L1 u, O - & C- Z9 @ R0 a7 x! T `
- /* 原始x轴数值和y轴数值 */1 I5 D. h" @2 {: O
- for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++)
0 X, I) L0 E l8 }/ q0 E - {
* F& z7 c# U, q; X - xn<span style="font-style: italic;"><span style="font-style: normal;"> = i*SpineTab;( V0 k; M7 C1 C. c5 a0 K7 x
- yn</span><span style="font-style: normal;"> = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3);
' [9 x% k7 ?8 s: o% D - }5 }: X6 U" _, ]
- & m" J. l: D/ F( X+ Z
- /* 插补后X轴坐标值,这个是需要用户设置的 */0 }/ A# @( g* y$ W5 M
- for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++)
8 f* h: [& w$ G - {( u( `1 {$ L* S0 z
- xnpos</span><span style="font-style: normal;"> = i;
$ y! {1 I& j& Z5 B M0 m, J - }
" j5 R4 I9 B n/ c! m - 8 a$ p2 L1 T; b% U7 [+ J
- while (1)' u1 a7 y8 [1 n4 k( w) f
- {" G9 h' y; a6 s7 F. n+ g2 b
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */9 }4 p$ x% s" }! I& l- x- p
- 9 P4 h) ?! s( P" V; B& x
- /* 判断定时器超时时间 */
/ [' @: N* S0 Y - if (bsp_CheckTimer(0)) * R5 R: _6 w1 ]6 x- ] g# X
- {
7 Z/ z8 _ n) S& e+ M8 L - /* 每隔100ms 进来一次 */
9 W, d' {1 b: x3 Q) i: R2 ~3 L* E - bsp_LedToggle(2);7 |! n& I4 E" B' r4 ^5 \1 |) y
- }# O0 `. ?2 D: z; \, x7 H" j- f% c
- + S p0 b: U0 S, C$ |4 T. H/ _( C% H
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */: i, K* p- A( o& i
- if (ucKeyCode != KEY_NONE)7 J* l( H) _, H/ x- `
- {
/ O" y% _6 H" _; T/ b J+ V9 T - switch (ucKeyCode)
0 c/ h) z; i& G7 ?1 U& j7 [ - {
! k' ~5 R+ r6 z5 W4 _ - case KEY_DOWN_K1: /* K1键按下,自然样条插补 */7 b3 C% J: J& z( Z c F, r+ b
- /* 样条初始化 */8 x5 O8 c+ m; ^. D$ W
- arm_spline_init_f32(&S,
* L5 n6 U1 B+ I/ V( A/ I7 u - ARM_SPLINE_NATURAL ,, u8 O% h; b6 k3 y
- xn,
. R4 W* s* ]) N' K+ x8 ~7 \ - yn,
~1 O4 S9 d, v - INPUT_TEST_LENGTH_SAMPLES,
/ P( I* F! x V - coeffs,/ M) r! M3 W8 L. r3 W1 Q
- tempBuffer);/ V/ m! S' [$ c) D2 J1 ~
- /* 样条计算 */
. E/ g) _" R3 {) d. J9 B' l - arm_spline_f32 (&S,# z; X. y' ?0 V/ z: F3 M8 m; @* M
- xnpos,
, C( V$ C2 P) u( t$ E - ynpos,# q$ p3 X" ~ Z& h
- OUT_TEST_LENGTH_SAMPLES);; m; L, ? K! x* w4 @- ?0 k- I* U4 X
( z- Z: n2 v8 o. J7 H0 M1 G-
, }9 m, k8 n3 s) ` - /* 打印输出输出 */( E8 V" [) { G+ [
- idx2 = 0;
! u* f' e2 d7 Z2 A3 f) V* [2 f9 l - for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++): I/ j. f, Z* z: ^- ~2 d
- {
7 \0 a9 N8 ~8 n* L+ _ R: W/ j - if ((i % SpineTab) == 0)
0 f" G4 I# x. u) q) t4 Z - {* y8 c7 x8 [! i( a1 l$ P6 H
- printf("%f,%f\r\n", ynpos</span><span style="font-style: normal;">, yn[idx2++]);8 s8 A3 Y2 A( E0 l) f: i
- }
# U9 `0 x: @" O6 f% [' G - else
$ n' n2 d0 U8 S% u/ P& M) |: d - {" c' F1 M; |. M5 B: y
- printf("%f,\r\n", ynpos</span><span style="font-style: normal;">);
% J }, @3 U+ w; \ - }
- n7 ^- I3 I) A( E/ I - }
/ p6 S/ s* r; v+ s" p5 R% w) I0 z - break;
" F! l4 I# y( t. j, v2 a* e, L9 x$ {
$ K% Q7 L8 J, U7 z o; U- case KEY_DOWN_K2: /* K2键按下,抛物线样条插补 */
" V6 N: b7 ]2 Q+ ~ - /* 样条初始化 */
8 R; Z4 g, d5 h4 p8 g& t - arm_spline_init_f32(&S,: ~0 C- i6 B1 E5 z# \. z
- ARM_SPLINE_PARABOLIC_RUNOUT , 4 e( r/ A' r1 [+ A- G
- xn,
! x/ b' ~ l1 [1 c- }7 y! v - yn, {* `. d8 T7 @. |5 P% @
- INPUT_TEST_LENGTH_SAMPLES,6 M' ?7 y" [. Q; g2 r1 P
- coeffs,
. Q3 g# N; V3 q8 H) _- \4 @ - tempBuffer);
0 f) C! b' O3 B; G - /* 样条计算 */
3 ]4 {1 _4 L: f9 l. k3 u+ ` - arm_spline_f32 (&S,$ X& j/ m) G" @% ^
- xnpos,
% M, G3 }) `$ [* I- a$ ^ - ynpos,
9 Q L+ F2 B/ [. a; b' }+ M$ c& P - OUT_TEST_LENGTH_SAMPLES);4 Q7 X/ l' E( b: E% g# u# y
) t: W5 X; \. O# ?4 V$ f4 R" r z- & l, g2 {; j* j* H; Z( H" c' C8 K
- /* 打印输出输出 */2 Y9 O( p7 S* m* W q
- idx2 = 0;
% R% |) G$ q* B2 Y; v& J - for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++)
9 N* e9 K$ R% E" m* { - {
! A; j% D7 J4 p0 `" o0 N - if ((i % SpineTab) == 0)
o3 N: n$ _/ m9 d4 c0 q - {
) }5 Q/ o" H7 S/ U. o Y7 ^* \* H - printf("%f,%f\r\n", ynpos</span><span style="font-style: normal;">, yn[idx2++]);
8 }; o: O' {( L& I - } }6 g) `$ G1 X k/ }
- else
# L4 s9 V' Z- }( G/ E; r0 l# B - {
8 ^7 Q$ k& B' f# @" T z - printf("%f,\r\n", ynpos</span><span style="font-style: normal;">);
) ?8 Q+ X/ D* x5 g - }
1 v! d% o1 K" e: O# Q4 `0 ` - }2 l/ a: _4 F, z; G4 E+ o
- break;6 g; \0 L5 F* z' \6 W) e
- : Y) o: P; ~* Z2 @' ?; Q
- default:+ F0 f: z9 J5 J7 ^2 } J
- /* 其它的键值不处理 */5 x& r6 t1 ]1 H7 g9 o1 b/ o3 I- s
- break; G$ f0 r- v) X# ?" T# m
- }
, }6 K3 P1 u% F - }. ?$ V$ N3 W( y9 i
- }, e1 H$ t( v3 N9 w! d
- }</span></span>
复制代码
/ x& V9 [: R$ W- d- q: b50.5 实验例程说明(IAR)
2 w+ Y7 Y, B: w* y, o" w6 V配套例子:
* U. o; M! F8 Q& k1 f' K: Z3 Q
# b: S$ h( W4 _& H4 O( P) wV7-235_样条插补,波形拟合丝滑顺畅
( P2 W9 }4 A0 ?2 b% }0 D% q! k& X( d z& E* q9 F- U6 y! `
实验目的:
* x8 l3 q! H, C) G# b" l# N: X: B, f( H* V+ L
学习样条插补的实现。% q( Y. M+ y A$ A* A4 u) m
+ |" y* p& z: h& B2 C: u实验内容:" _. J) X: T1 N- C) P9 _" K
9 o" |. z& F! @9 @7 Z- v" Q
启动一个自动重装软件定时器,每100ms翻转一次LED2。3 i) i; P, w5 ?4 N( d; Z5 i1 @0 z, x
K1键按下,自然样条插补测试。, p$ K: V- h. `5 R+ O- j
K2键按下,抛物线样插补测试。* S0 p0 n9 P" z [0 W# [
* y2 _9 n% d+ N6 q
上电后串口打印的信息:
9 ^% e8 z, }1 L) T- g$ P3 ?; B9 X) e; o4 y) N
波特率 115200,数据位 8,奇偶校验位无,停止位 1。" d" S, y% e* F" }
- p9 ?! Y* z" H, K# `; d$ K
9 k& p' V5 i# V' d" C
$ o; `' `0 o: D5 @3 _3 e& jRTT方式打印信息:
/ N6 c# a/ P. G+ d
1 u7 t- z/ n# D" i/ p) ~) m3 F, ~4 D2 G6 i7 W$ @6 e6 v& e9 \
! i3 f, t+ i3 j8 Y4 P; }/ G7 c7 l/ D* P" }9 e& E( W
) _& E7 `$ S; \5 U6 T& V4 S程序设计:
5 ^9 t( e1 h9 p1 x% S8 B7 Q
; Y' L5 g5 w! d' C+ s' D 系统栈大小分配:
/ e" X- @8 `( l) q, K0 P
) {& E$ h- q4 `* a0 y; B+ g3 y N) p: u2 z# C
- \$ t0 u$ u9 R" ~
* f9 V$ O" v3 N
5 c% _; R0 @6 T; `9 F" b+ ^) e2 E
RAM空间用的DTCM:
6 h# b' W+ m. Z4 N1 S. h6 q' L7 B" d( j3 F# W
7 U' b/ [9 h: r5 c
: H, {; C2 \0 @, K6 L7 ` Z
U {5 M$ T8 j5 l
0 U7 M5 H7 i6 P3 T6 R 硬件外设初始化* F/ C: S7 c" q: a5 A m, q! P7 ^- X
; |& D: @' n9 T$ z8 Q/ O0 F; s9 Z硬件外设的初始化是在 bsp.c 文件实现:
, N; N; W: d, m- I8 S& w( }+ e
- /*! Q# O- k, {0 j
- *********************************************************************************************************
$ B: v( Y- d' s" R. i, D - * 函 数 名: bsp_Init! D& C8 a2 z& Y- U
- * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
. [# p- ~2 S1 |0 C; p: ]; K - * 形 参:无4 e8 J$ G+ N1 X; W1 W' l, F
- * 返 回 值: 无3 j$ P* R4 ]1 b- R% L- ?' t
- *********************************************************************************************************
" R2 Y8 ^( N# n, U) f$ g/ A+ ` - */3 L( `" @: j& G0 v: n% l$ h; V& `
- void bsp_Init(void)0 s4 l5 [ H* q/ f. n
- {
0 R5 T/ i- e' ]( s7 g# R - /* 配置MPU */& t3 h |$ b7 Q( v) q
- MPU_Config();6 ~3 _# k5 {+ }9 j5 X* N
- ! M* a4 ^' j) I2 l' j
- /* 使能L1 Cache */
1 Y! Z4 G% k' h$ t/ x( E - CPU_CACHE_Enable();
: P8 P# q# b0 _/ c" x" G - 9 y W" G) f# v! P s- J% _' ^
- /* + u; ^3 Y I! N
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:* i$ `4 p! @- B4 }' \
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。) q& @$ z/ B! N7 q. }. \
- - 设置NVIC优先级分组为4。2 a! x* @6 v$ Y8 C2 S9 A
- */! v8 P' n4 ~7 _5 I y( X6 b# u* D
- HAL_Init();& G! H# ^& t" Z8 k1 \
4 }' ^3 M+ S6 n$ T- /* 7 K- ]! \# A6 {7 g) j
- 配置系统时钟到400MHz5 d( C- i3 p/ M. @ n9 ?/ y$ z9 s
- - 切换使用HSE。
" c' @9 q; W; P9 i9 X - - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
3 \9 t% l( @7 b" `! f; { - */
; b5 T E. z2 e7 D) P - SystemClock_Config();
* u& W$ r0 p1 g% E
3 ]7 ~9 f, u% Y% @" z- /* 3 Z" V7 o( q5 W4 E6 n; M7 Y
- Event Recorder:5 b. Q. z3 G* t( g, h1 r% w
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
& m1 l. i( Q, w7 r( i. W8 | - - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
, r' O$ X- X5 S- {9 _1 m5 Q - */
6 T8 k" F) G9 M/ I - #if Enable_EventRecorder == 1 r1 b. p4 {$ h# }8 `; {1 m* s
- /* 初始化EventRecorder并开启 */- X9 |* y3 I) `3 m! D& V+ j
- EventRecorderInitialize(EventRecordAll, 1U);
4 d! o G9 g" G% m9 e1 e, L0 B+ T - EventRecorderStart();
. Q7 |, h. U2 e: t- y - #endif) p! g! ?) b0 |: s2 l
- # b* R2 o7 W9 ?: W: g4 ?( u
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
A0 L3 c9 T, v% ~ - bsp_InitTimer(); /* 初始化滴答定时器 */% \$ ?) p$ O2 T! S' b
- bsp_InitUart(); /* 初始化串口 */' z" _8 j0 n4 A$ r8 N7 D
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
$ L; y1 y# w; Y- t7 p% Z; r - bsp_InitLed(); /* 初始化LED */ ! g4 h& ?' [% v6 `
- }
复制代码 ' M$ f5 z! h; ?7 |$ ]7 B
MPU配置和Cache配置:
7 b0 e8 j/ S O! w
) [" h$ h/ ]; l数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区。
- k! B+ j* w; p& G! z1 p1 I. k$ B |; a* U2 c4 ?
- /*, |: Y+ D( r6 K( s
- *********************************************************************************************************
: E- O! `+ Q* W( J* \# R - * 函 数 名: MPU_Config
$ A5 }4 Y) I8 ^% F, H/ D/ q4 p4 S - * 功能说明: 配置MPU ?) W- O" g8 p" A
- * 形 参: 无" a( c) V- ?/ L+ c: u# [0 @+ K( D
- * 返 回 值: 无
, T( e$ l" k7 N - *********************************************************************************************************
+ ?/ `) I+ w1 E. B0 {9 o - */) k$ ^% v9 i n: c" V$ r
- static void MPU_Config( void )
3 A. i2 p+ o1 C; w M - {4 k2 [2 k) t- n" ~
- MPU_Region_InitTypeDef MPU_InitStruct;
- L0 E1 I) g; K7 T/ G$ q
( ?. Y5 A4 ^8 T8 D8 _+ o5 i- @. G- /* 禁止 MPU */
$ Z4 B" @# N9 k3 l$ r! E+ F* H' x - HAL_MPU_Disable();
- L2 a) p, [' r- K" ~" w- V! C. `
. Q+ T: l( [' [, n- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
5 j3 z( k H5 f( [! l' s1 `! e - MPU_InitStruct.Enable = MPU_REGION_ENABLE;
3 t' O2 r# l& P - MPU_InitStruct.BaseAddress = 0x24000000;
- }/ c, a6 i& y8 x - MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;, h3 r2 z3 L) [2 l& E, F
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;- r1 L& Q! h4 M* ?
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;7 I/ o5 u! B- S6 X+ [% ^8 \
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
4 _- |* z: ]% A6 d( g8 a [: ~2 s - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
y& @5 i8 ?& E - MPU_InitStruct.Number = MPU_REGION_NUMBER0;
2 s7 u+ {# }6 U6 p - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
# o& V' m9 S: q- \& ]& U" G# ^ - MPU_InitStruct.SubRegionDisable = 0x00;
' H4 ^; e; Q" N& w* X - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
( {' ~' q* k5 b5 c
0 }# d4 s7 ]+ g `- HAL_MPU_ConfigRegion(&MPU_InitStruct);4 D# e( m$ B5 L6 v9 ]
- 4 u6 |( ?" v6 k7 c# [' G
- ! E1 i- \ M- Z6 d- b' \
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */ }! u( ]% r2 z. y8 K* \' |
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
" }0 D) S! K2 D5 z) ~& z - MPU_InitStruct.BaseAddress = 0x60000000;
$ ^7 t, \' R* P" z3 e5 l6 \ - MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
" d3 k* ~% B P - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;/ r, f. p I+ P {7 W
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;8 c$ z. K a/ b+ d; e9 q4 @" B
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
$ l' ]4 g6 [3 e2 T) `( U+ L - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;3 M2 g. d' I2 R/ d; b3 o( j- {+ I
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
" e: v8 ]& g/ i+ Y/ D+ \ - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
/ ]+ m. B# m% o+ t0 I - MPU_InitStruct.SubRegionDisable = 0x00;7 K' d9 j* a9 T0 V
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
) a. j; @" U% R/ @9 ?: w( T: h8 q -
8 ~$ _+ e( ^3 I* ^; q - HAL_MPU_ConfigRegion(&MPU_InitStruct);5 x. C4 E3 L# C7 E1 Y& \& d
- . y: H4 K/ J$ t: n
- /*使能 MPU */
1 P' k+ U0 i' Y c: e - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);5 \% T, w0 g# d% b3 u4 v
- }% u- W: W$ H8 S* ^
- 6 x+ e! H8 }' [* s8 l
- /*
; @, s1 `: {; A. A/ ^& n - *********************************************************************************************************- Q3 s$ \4 y" ~6 I2 K
- * 函 数 名: CPU_CACHE_Enable# j7 `( l' Z, r, u
- * 功能说明: 使能L1 Cache
4 V, v5 e5 C4 U4 A8 l, i - * 形 参: 无
2 y* ]$ `2 ]8 a/ k% W - * 返 回 值: 无
& F' ~6 I3 Y3 f) F; w& @# c$ }! H - *********************************************************************************************************, a( P# J- B8 J. Z7 Q0 c
- */4 p- `3 ]- P2 I% I
- static void CPU_CACHE_Enable(void)& `7 ?1 G3 U3 U- u R' Q7 w
- {6 _) Q2 l( j) y
- /* 使能 I-Cache */
# m. c5 e$ P) g- T3 q! g1 H7 h - SCB_EnableICache();. v5 S: W0 r- E
& b) z( U( U' V! ^0 P- /* 使能 D-Cache */! N3 e. F! b+ V4 R
- SCB_EnableDCache();+ G3 g1 \9 `& Y# d
- }
复制代码
3 t- Y( t4 P) ~/ f% d9 ^" R 主功能:' Q4 H C1 K0 G9 C2 m
2 a0 Q: `- F5 w: D主程序实现如下操作:2 {4 x4 e/ M% H F+ F5 _
7 H, w7 y& L7 I$ C8 B3 p- L 启动一个自动重装软件定时器,每100ms翻转一次LED2。% m* q! V; I" f; }
K1键按下,自然样条插补测试。
( I5 W: v: Y0 F1 J; `3 X8 d5 G K2键按下,抛物线样插补测试。' l. Y( }4 a5 O/ f3 ?% e7 E5 {
- /*
+ b4 S8 X/ n4 D9 O - *********************************************************************************************************! i4 @, H, e3 r5 p( ^3 p0 R- u
- * 函 数 名: main
0 ]: W# S6 ^5 O& p' i J: {; ? - * 功能说明: c程序入口# E2 i' H2 p3 k* ^5 H* ~# G
- * 形 参: 无
. ~* y" [( ^# M8 C* C - * 返 回 值: 错误代码(无需处理)0 o8 u# m7 O" ^' j
- ********************************************************************************************************** X& w: H) n) x' s
- */5 S/ p' e. e2 [ |. |! P
- int main(void)
2 u" I! k2 b B+ |1 l - {6 X) C, p: m2 e& `% g |' q9 |
- uint32_t i;
6 C' j3 d9 `/ U/ q - uint32_t idx2;
# H+ }5 W! z1 o6 \: R - uint8_t ucKeyCode;
0 \3 c: k1 y. G5 L, K4 \. D - arm_spline_instance_f32 S;% N1 Y" ~" s. g, u i: w
-
' M8 y$ \- J! l* `- G
6 Y% ^$ i5 Y# b* z; w& ~( |: g+ v, ]6 T- bsp_Init(); /* 硬件初始化 */
, ^2 o/ o& N, x - PrintfLogo(); /* 打印例程名称和版本等信息 */
" d* l( p6 ]- _( p* m& t - PrintfHelp(); /* 打印操作提示 */
9 G& m1 b' v: b5 A -
/ d+ |( r& M, v1 r7 H - bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 *// D/ H: V3 t% h
- * }4 h/ K1 ^% w- Y: m" v) O
-
: y# d" W7 d+ b' u0 V$ I - /* 原始x轴数值和y轴数值 */
% ], z8 G% b6 n5 S2 q$ K' P - for(i=0; i<INPUT_TEST_LENGTH_SAMPLES; i++)4 ]$ M9 K* I6 }6 c: b( _6 s
- {
0 z& Z5 Z' S( J6 Y; } - xn<span style="font-style: italic;"><span style="font-style: normal;"> = i*SpineTab;
! l+ c3 @4 {" t m7 o/ Z - yn</span><span style="font-style: normal;"> = 1 + cos(2*3.1415926*50*i/256 + 3.1415926/3);
. q. S" n& n$ H6 ^6 e - }
8 i% \9 h) l5 `8 t' J - ! _$ @2 k+ t1 W
- /* 插补后X轴坐标值,这个是需要用户设置的 */6 }$ _+ r9 D. v& i. N# X
- for(i=0; i<OUT_TEST_LENGTH_SAMPLES; i++)
' K( O, g; T2 f5 d( f6 W8 Z - {
- Z& O7 x B% ^$ C# L4 M3 I - xnpos</span><span style="font-style: normal;"> = i;+ n' ]1 X# f% p- _
- }8 ~% Z! i& ~8 Y+ X% M4 Y% v9 ?
- ; k6 }2 r9 ~0 h1 S
- while (1)
) D, h% I8 j$ c2 c0 x, {; ?, w - {. v$ ]3 T+ E8 S$ w
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
1 X [" c5 U. n& E- B) M% J0 E - ! }) E& F8 C% R5 i& W
- /* 判断定时器超时时间 */1 H$ R* c% Z: h+ r7 u
- if (bsp_CheckTimer(0)) b. M( `& J* H; v% V5 R
- {* D' Y5 ~! D- w, O3 `/ e3 `
- /* 每隔100ms 进来一次 */
1 C. g. G* m5 W+ z4 m7 z* @ - bsp_LedToggle(2);
/ `/ h) Q8 M. ]. w) @( n - }
. S( ~9 d7 P+ B+ r - / P5 s6 ]$ F/ h z8 B
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */0 v: ?" Z% ]+ M$ p6 D/ [
- if (ucKeyCode != KEY_NONE)
9 F3 E( Z _( ~: C - {0 L( C. b( G! I6 H1 C, ]
- switch (ucKeyCode)
7 A x& u) `( T - {
( c) s) P% w9 c) f1 Q - case KEY_DOWN_K1: /* K1键按下,自然样条插补 */: L: C2 J6 I, N$ K, v
- /* 样条初始化 */0 X$ Q# i3 a, Y8 f( Y
- arm_spline_init_f32(&S,4 i8 z9 ^- u: Q
- ARM_SPLINE_NATURAL ,
: `" j2 [( W- Z# V" L, h - xn," {, B6 C$ J! i1 R' v; r* O) V
- yn,
- T3 _1 F& t2 a+ C2 v. { - INPUT_TEST_LENGTH_SAMPLES,- r3 X- M# F d" D/ ~. _
- coeffs,% ?; c: i' C1 [2 x
- tempBuffer);/ V4 S6 ]' ~7 p/ d
- /* 样条计算 */, \+ x( I! K0 R/ e! m0 O
- arm_spline_f32 (&S,8 c8 l5 _5 \$ z, S
- xnpos,
1 p: y# v' h) h/ H0 u0 _1 N - ynpos,
" k4 c3 X( y2 R" U* ?) F. F) i - OUT_TEST_LENGTH_SAMPLES);9 L9 D, g. ]: ]. M5 V. w$ L
4 x/ p' q! Z4 a4 D: W1 f- $ y) A. Z1 C3 F$ E2 H
- /* 打印输出输出 */$ D( b* g* L. ~: E3 w; h
- idx2 = 0;
( v! ~1 q1 F9 Y - for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++)
% S" C7 L8 ^. x" g" H - { 0 \8 K7 F: T, b/ [9 y" ^ ^
- if ((i % SpineTab) == 0)% @+ L! I$ m% s& b5 m" A- v
- {/ c) n2 R/ Q+ u; f1 G: s" Q2 w
- printf("%f,%f\r\n", ynpos</span><span style="font-style: normal;">, yn[idx2++]);5 k y7 T, D8 w& E! W* J/ r
- }
/ O+ @, c0 {! V5 j& c; I - else
. N; S, p$ f- p |* B - {
# C0 A( U1 l% C0 E - printf("%f,\r\n", ynpos</span><span style="font-style: normal;">);
4 h9 H; r/ R8 [2 X, V - }
3 f9 N3 F/ |( L' S! D/ \2 q/ r/ z) y4 h - }0 q- p, b& g0 o% x% R
- break;& W, f7 j0 U$ B" _: K# W' ]
1 ^) r( U2 a9 z+ |# s: ~- case KEY_DOWN_K2: /* K2键按下,抛物线样条插补 */* p9 C2 w k k9 x7 [2 t7 F1 U8 A
- /* 样条初始化 */
# X% f- {8 g5 X1 w) L7 y- \ - arm_spline_init_f32(&S,
5 K1 q2 X% L9 u* N - ARM_SPLINE_PARABOLIC_RUNOUT , , C) [5 }; v/ B( w4 Z. X. t
- xn,) b5 G! K$ M, D/ k1 q
- yn,. U6 x6 `& D) L
- INPUT_TEST_LENGTH_SAMPLES,
0 n, I$ n9 R7 x& S" S% } - coeffs,
- I! _; k7 J. w. E( K) Y& ^ - tempBuffer);8 q, H9 v+ A H( v
- /* 样条计算 */) y! J* |7 O" d: [0 z
- arm_spline_f32 (&S,
% `7 ~6 U: l0 z# ? - xnpos,7 F( A, c5 j* g* a& P7 D! f+ p
- ynpos,
9 v& k$ w2 o1 z+ M: m# y+ e - OUT_TEST_LENGTH_SAMPLES);6 r/ v- j% P+ L9 ?/ r% }
- * W6 N7 E: R6 E( ^7 `) k7 L
- ) k8 A/ a9 x: Q! z0 L! G! X7 n$ s
- /* 打印输出输出 */
- J$ S6 L+ G' b1 {3 { - idx2 = 0;; A5 ?6 `4 }, s' c' u' w
- for (i = 0; i < OUT_TEST_LENGTH_SAMPLES-SpineTab; i++)
0 Q* S% _& i' i& w: { - {
! u. Y) E5 c) R" I - if ((i % SpineTab) == 0)7 I& X) j* H) D% y Z9 F! U0 s0 w
- {
, i* v9 ^, j& t- _ - printf("%f,%f\r\n", ynpos</span><span style="font-style: normal;">, yn[idx2++]);" S' J& M. ?' u, Y- t9 L' J- C3 ?1 Z6 S$ G
- }3 Q: r2 ]# `% H) G. B/ W$ T
- else% J" n: y; w& [7 S1 r; O& [! a
- {
/ f( ? }7 z$ @2 }; u4 H$ {$ g - printf("%f,\r\n", ynpos</span><span style="font-style: normal;">);
4 m* N; g `4 F8 u3 L" D - }
( Q" q2 K/ `4 G* |$ y/ W$ Y% T+ A - }7 W r6 T! o6 ^& q1 S# _' @
- break;; W# L, H- @( t' r6 S) Z
- ( ~: w. k2 e/ }4 |0 ~5 c6 k! N
- default:
) O* N0 B0 N6 t; o - /* 其它的键值不处理 */
: H3 r, j2 _- O+ }! | c - break;- a6 }5 x: }- W! l9 n6 V
- }
9 u# |1 H! j) N8 H2 Z* k' e: V - }
# P5 D% H+ }& ~* ~- H - }" U9 S5 K! h6 Z8 q
- }</span></span>
复制代码 50.6 总结
, \2 g3 ?& r* J! F6 O( Z本章节主要讲解了样条插补的实现,实际项目比较实用,有兴趣可以深入源码了解。
+ k! p$ r, V: C' P( o. p3 W6 N3 G1 V' O* A
: r$ r% t% i$ R7 L: m# x9 ]
2 g+ Y; }' h( |: k2 O- [
|