本帖最后由 子曰好人 于 2019-1-19 12:25 编辑 2 h, A$ t. m: u; f
6 e$ p: l" k* m本来是年末了,应该比较悠闲才对,可是最近却有点忙,芯片的测评报告也没及时的更新,部分原因也是在想怎么写好测评报告。 7 g O& G g1 H2 U
今天就来测评一下做板子模拟功能相关的部分—ADC和DAC。
0 H. U+ A$ Y5 g# W; Q {0 a首先来罗列一下G0芯片ADC的特性: 1. 采样率:2.5MSample/s max (12bitresolution),在12位采样精度的情况下400ns就能完成一次采样,相比于其他M0+内核的芯片算是快的了。 2. 支持硬件过采样功能,过采样的提出是为了提高ADC的采样精度,而损失部分ADC采样速率,在某些需要高精度ADC的场合非常有优势。 3. 在采样率为1Msample/s时,功耗仅为118uA 4. 非常灵活的触发源,软件触发,timer触发以及IO触发 5. 每个ADC均拥有三个模拟看门狗。 6. 自校正功能。
! L" F$ T$ Z5 e# R$ J" S8 `DAC特性: 1. 支持8位和12位模式 2. 一个DAC模块,嵌入了两个DAC转换器 3. 包含低功耗采样保持功能 4. 可控制外部偏移电路,代替电位器。 5. 可作为随机信号发生器。 0 K3 t' p$ v. I0 R9 k- p1 H
本次测评目标是通过DAC生成尽可能高频率的正弦波并由ADC采集出来。
( m( `. Y/ q7 E9 _可以通过cubemx生成默认NUCLEO-G071RB的工程,长这个样子。 默认生成的工程配置了唤醒,低速晶振,低功耗串口以及调试接口,我们可以在这个基础上添加内容。不过串口需要修改一下,我们常用的串口波特率为115200或者9600,工程默认配置是209700,而我们常用的串口工具不支持这个波特率。 ADC配置:
8 z, B9 r* {7 u# I1 N7 X# B5 ^
9 x- d0 r1 Z4 h4 G( H# x
这里选择的是ADC通道0,采样精度为12位,数据右对齐方式,仅开启了DAM连续请求,本次测评准备用DMA触发。设置ADC触发源为timer1,我的打算是后面跑跑电机,所以让adc和高级定时器关联起来。0 Z& Y. p$ v5 m7 U2 F" e
% W& \# O; T" g' j" v1 I& s) x9 CDAC配置
' e; s9 ]7 `3 ^5 `3 F# }0 a
DAC的配置就比较简单了,选择仅连接到外部引脚,输出buffer使能,通过timer6的更新事件触发,其他默认即可。现在有各种各样数据可视化工具出现在工程师的眼前,但是在没有这些工具之前想要看到mcu内部变量的变化趋势可是要费一定精力的。串口虽简单易用,但是占用mcu资源;据我了解的不占mcu资源的JScope(SEGGER家的),简单的应用倒是可以还可以使用,对数据观测量较大或者数据改变速率较快时使用则需要更强大的调试器(Jlink Pro)加持,对于大多数工程师来说购买正版Jlink还是有点贵贵的。这个时候mcu自带DAC就大大帮了忙,通过示波器的电压探头就能以较高帧率去观察mcu内部变量的变化趋势。0 n. z$ \0 o, s' s" t" c. H3 I
Timer6配置
8 X2 T) G3 ]* R7 n3 E9 C& S' P Timer6是普通定时器,只能向上计数,可以产生更新事件,本次测评用timer6触发DAC的DMA转换。
* J: _) }+ P$ z% c* i/ iTimer1配置 在ST的mcu家族中的定时器里,Timer1永远是最高配置,拥有更多强大的功能,特别适用于电机控制,另外就是G0系列的Timer1可以倍频,达到MCU最大主频的2倍,即以128MHz的频率运行。 本次测评暂时只使用定时器1作为ADC的触发源。7 A+ S1 n0 Q$ c, u3 m
代码:
3 A& i% W' ~& {0 U# n- uint32_t adc_value = 0;3 Y/ @0 \' A' \' r
- float ui_f32adc = 0.0f;
# E$ ^! E) f$ h0 } - const uint16_t g_SineWave128[] = {5 D& t* e. x, G1 y& A6 x* C9 |
- 2047, 2147, 2248, 2347, 2446, 2544, 2641, 2737, 2830, 2922, 3012, 3099, 3184, 3266, 3346, 3422,% j. _0 C) @7 R+ F# b2 P& p
- 3494, 3564, 3629, 3691, 3749, 3803, 3852, 3897, 3938, 3974, 4006, 4033, 4055, 4072, 4084, 4092, _8 w2 H, p! L0 q7 J ?& C
- 4094, 4092, 4084, 4072, 4055, 4033, 4006, 3974, 3938, 3897, 3852, 3803, 3749, 3691, 3629, 3564,
4 d, g2 C; _ N( n3 Y - 3494, 3422, 3346, 3266, 3184, 3099, 3012, 2922, 2830, 2737, 2641, 2544, 2446, 2347, 2248, 2147,3 l; T. _ d$ Q2 i( t
- 2047, 1947, 1846, 1747, 1648, 1550, 1453, 1357, 1264, 1172, 1082, 995, 910, 828, 748, 672,
* g" ~+ |) \1 R1 t5 A+ O5 _ - 600, 530, 465, 403, 345, 291, 242, 197, 156, 120, 88, 61, 39, 22, 10, 2,3 l( W. B# F2 x; {& X! ]% U
- 0, 2, 10, 22, 39, 61, 88, 120, 156, 197, 242, 291, 345, 403, 465, 530,1 g: n3 ^: g% m5 j4 c( b% u4 }! D
- 600, 672, 748, 828, 910, 995, 1082, 1172, 1264, 1357, 1453, 1550, 1648, 1747, 1846, 1947,4 y+ J9 t3 s5 r2 W7 Z* v3 }
- };
9 T0 H6 d& T+ o0 k- ?9 R+ \5 c
复制代码主函数代码 - int main(void)
4 R6 L5 C& P3 `" H - {
( c$ `, V+ }2 f/ B3 b' h# q - /* USER CODE BEGIN 1 */
& Z! D/ ^1 h0 _, D. @9 G: Q4 o - 4 ^! b- P5 ?' a& E6 g
- /* USER CODE END 1 */ \4 A& R* B7 L7 I% C1 }
- 3 I2 M' I( s# [& k4 u# Y
- /* MCU Configuration--------------------------------------------------------*/- X. \* ^# U4 d" m
- + K- w: I w$ C$ V. v; h
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
4 ?/ U S! q2 I9 F! ` - HAL_Init();
! _4 }9 C8 J" I. N6 c5 O - . U6 ~* ?6 x o1 g, e c
- /* USER CODE BEGIN Init */
( y4 z& g) S( {# ^9 ^7 {
+ D2 v5 ~! V2 ~2 E9 ^0 M% f! P- /* USER CODE END Init */
. S4 e7 _! }' T
* `) J3 J( M$ J, e- /* Configure the system clock */ s* V2 O1 ~2 G
- SystemClock_Config();7 R" L% f( P5 |# K6 b
' ~% O9 T0 |: E: D. g- /* USER CODE BEGIN SysInit */
: F! F3 Q; Z! ]* ?! ~
4 s8 |9 G; V% V2 t b; u- /* USER CODE END SysInit */- E- S5 o- p( U- R4 h
- ( S! M; X4 |( x1 M
- /* Initialize all configured peripherals */; ?; f7 \# O" q7 t8 E8 c R
- MX_GPIO_Init();9 {! v4 Y2 N; w/ _7 {# M% Q
- MX_DMA_Init();
+ N1 z. m- ~5 l; D - MX_TIM1_Init();! r% z) ^, @6 q) z, J6 ]; |9 X. N
- MX_TIM6_Init();
w% F' p% p& k, v" S - MX_ADC1_Init();
8 d& M- B8 }) l3 J* T - MX_DAC1_Init();
' J% k0 U2 {) |# `5 g4 J - MX_LPUART1_UART_Init();: k% T6 i/ o7 h, `
- /* USER CODE BEGIN 2 */. u- p f! j$ \) v4 c- s- z' O
- HAL_ADCEx_Calibration_Start(&hadc1);
8 T, d% L5 S& J' ?# u6 R - HAL_ADC_Start_DMA(&hadc1,&adc_value,1);
/ g5 K, D/ w4 f/ N - HAL_DAC_Start_DMA(&hdac1,DAC1_CHANNEL_1,(uint32_t *)g_SineWave128,128,DAC_ALIGN_12B_R);1 K; b& p, y1 f1 L3 W
- HAL_TIM_Base_Start(&htim1);% C% W$ n8 S9 a1 B6 h
- HAL_TIM_Base_Start(&htim6);# j0 c6 P" Z( g; d
- /* USER CODE END 2 */
( d# F X7 g; t6 V3 U. H
; o- @0 y/ t; f$ ~- /* Infinite loop */
; b. `* D E/ { Y$ l, L - /* USER CODE BEGIN WHILE */0 [6 ]' y- t3 C: @, N
- while (1)7 T2 K! Y& b( y
- {6 Y! B. i+ t0 h% u
- ui_f32adc = (float)((adc_value&0xfff)*3.3/4096);9 F- P. W0 {! Q( F
- printf("adcvalue: %.2f\r\n",ui_f32adc);2 J4 @+ W, x5 V9 a8 h+ ^
- /* USER CODE END WHILE */
3 T7 U% V7 O* h
* c+ p. w' U' d/ _- /* USER CODE BEGIN 3 */( v! d( o0 D& m% j8 U
- }
$ K, j2 @# L% ~4 P9 a - /* USER CODE END 3 */$ l7 z; c+ s! d. v* N
- }
复制代码串口重定向: - #include <stdio.h>
?5 [8 y( m% W/ k - int fputc(int ch, FILE *f)
$ K% g4 r8 O0 F! `8 n1 e - {
. O: l5 u4 _# i - HAL_UART_Transmit(&hlpuart1, (uint8_t *)&ch, 1, 0xFFFF);/ q4 E1 j& F+ l7 s% _( {
- return ch;# h; l2 x& u v( ]' {8 k& ?: Q* y8 _
- }/ I# F7 I9 L: m/ v5 s% Z
复制代码现在可以通过示波器和Jscope分别看到DAC和ADC的状态了,通过串口也能看到的部分信息。 % y* K \4 R3 e3 O
DAC以较低频率触发(10Hz),timer6配置为不分频,计数值为49999。可以看到在低频时可以看到DAC输出的0V并不是0V,有40mV的偏移,满幅电压是正常的3.3V。
& X0 y- H, C3 H
) U( a; P% G6 q500Hz正弦波,Tiemr6配置为不分频,计数值为999 500Hz的正弦波已经让普通Jlink+JScope无能为力了,再后面的JScope波形也就不贴出来了,其实通过香农定理也能预测到这个结果,普通Jlink+JScope最高采样率才1KHz,而香农定理提出要能还原信号原来的状态,采样频率至少要是信号频率的两倍,这里恰好两倍,不能还原DAC输出的正弦波也算是正常。
6 ~2 |) f0 Y: L) f DAC输出的最大正弦波频率为39.48KHz,这里可以计算的到DAC更新频率为39.48KHz*128=5.053MHz,有DMA的加持就算是很高频的数据也能采集到了。
( w' K; U1 J2 y5 I; r E; e, v5 U; {5 h0 i
) e6 a9 j0 L6 D3 N4 p% X
[color=inherit !important]
6 F. G+ k5 V4 t9 S* G, a+ ?( ]
0 i9 C/ f) y; ^1 f* F# ]( I: D& Y
+ v2 c: w6 t" `% U
0 K, j' o9 s! \% \; [, Q |
限制条件就是dac电压幅值不降低的情况下输出的最大频率,这个地方确实描述有点问题,感谢指正