STMCU小助手
发布时间:2022-9-27 12:21
|
DAC实验# L3 V+ U$ Z5 N3 B2 a4 H' q9 x 前面章节我们学习了ADC实验,本章,我们将介绍STM32MP157的DAC(Digital -to- analog converters,数模转换器)功能。我们将通过三个实验来学习DAC,分别是DAC输出实验、DAC输出三角波实验和DAC输出正弦波实验。 3 d1 P& {* |7 b, a1 N% p6 n 24.1 DAC简介 数字/模拟转换器(Digital-to-Analog Converter),是指将离散的数字信号转换为连续变量的模拟信号的器件,简称DAC。在模拟电路中,电流电压信号的变化是连续的,而数字电路处理的数据都是离散的数据,DAC将表示一定比例电压值的数字信号转换为模拟信号,这使得主控芯片不再只是输出数字0或者1,从而得到更广泛应用。 (1)分辨率 DAC的分辨率定义为输出满刻度电压与2n的比值,其中n为DAC的位数,所以DAC的位数也可以用来表示分辨率,例如某个DAC的分辨率是12bit、10bit。 DAC的分辨率反应了输出模拟电压的最小变化值,以一个12位的DAC为例,其数据变化范围是0~4096,对于3.3V的满量程,采用8位的DAC时,分辨率为3.3V/256=12.89mV,当采用12位的DAC时,分辨率则为3.3V/4096=0.81mV。显然,位数越多,其输出电压的取值个数就越多(2n 个),也就越能反映出输出电压的细微变化,分辨能力就越高。% {: N8 M$ k3 K" d+ D (2)建立时间# W, z8 N/ v% b5 S9 d 建立时间是描述DAC转换速度快慢的参数,定义为从输入数字量变化到输出(DAC_OUT)达到终值误差值±1LSB时所需的时间。从参考手册上可以找到STM32MP157的建立时间tSETTLING的典型值是2us。& [2 w3 T/ z& a' t (3)基准电压- p* l$ ]; q4 J$ X 基准电压也叫参考电压,也就是当做参照作用。我们测试外部电压时会以基准电压作为参考,先把基准电压分成多少分(根据分辨率来分),然后再和被测电压进行比较,然后得到比较的结果,这样就能测试出输入电压是多少了。 24.1.1 DAC特性0 b" l3 h. ?8 n/ S STM32MP157的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,可以在每个DAC输出通道上进行单独的校准,每个通道都有单独的转换器,DAC输出通道支持低功耗模式。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压VREF+(和ADC共用)以获得更精确的转换结果。当DAC输出与焊盘断开并连接至片上外设时,DAC_OUTx引脚可用作通用输入/输出引脚(GPIO)。5 {! T) K0 z$ t9 S STM32MP157的DAC模块主要特点有: ① 1个DAC转换器,每个DAC最多2个输出通道 ;" ~1 e: o. L2 {' u ② 12位模式下数据左对齐或者右对齐 ; ③ 同步更新功能 ;1 n& ]" r- I1 }# j, f ④ 噪声波形生成 ; ⑤ 三角波形生成 ; ⑥ 双DAC通道同时或者分别转换;# v3 Q6 O' y4 B$ P& n ⑦ 每个通道都有DMA功能;+ V" W+ I0 l" B& j* m ⑧ 通过外部触发信号进行转换;! w P, X! W1 a, V% T ⑨ 每路 DAC 输出均可与 DAC_OUTx 输出引脚断开连接;# e: m" h+ i0 z% V; u" J ⑩ DAC 输出可与片上外设连接;% \9 s5 g% {; ~8 t' S ⑪ 可在停止模式下通过采样和保持模式实现低功耗运行; ⑫ 来自VREF+引脚的输入参考电压。6 _4 L. y8 f# E& D( _ DAC通道框图如下图所示: 24.1.2 DAC框图5 b- P! Y6 e" }7 u9 y
图23.1.2. 5 DAC通道框图 ADC框图中的输入/输出引脚信息如下表所示: - k% R+ o4 F: k9 p7 R6 s" `
$ |. ?; ?# z% g+ @. T9 E0 G 表23.1.2. 2 DAC输入/输出引脚$ Y g- [! R6 c+ K3 m: v0 T1 q* P 除了上表列出的输入/输出引脚,DAC通道框图还有一些内部输入/输出信号,具体如下表所示: 8 Z. [9 I: S1 q2 R2 k X6 t
表23.1.2. 3 DAC内部输入/输出信号8 E5 h7 v: e+ M' D z 关于DAC框图的内容,我们做如下讲解:" B9 T4 C5 P5 m* V, G+ }/ L( a0 z 4 M( q/ k4 W( H; t0 B 1.DAC的输入/输出引脚以及内部信号. }! N5 i/ k* d' ]' T" h 图中VDDA和VSSA为DAC模块模拟部分的供电。VREF+ 是正模拟参考电压输入,选择范围是1.62V~3.6V,开发板上我们一般给VREF+接入的电压时3.3V。在ADC实验章节我们说过,STM32MP157有ADC和DAC共用的内部基准电压VREFBUF,可通过VREFBUF_CSR寄存器进行配置,可选1.5 V、1.8 V、2.048 V和2.5 V。9 B2 T0 {# q% _" ^2 `- c 要注意的是,DAC只能在VDDA高于或等于1.8V时使用,使用内部参考电压VREF+时要求VDDA高于或等于VERF+++0.3V。4 ?7 e& f2 i; p DAC_OUT1/2就是DAC的两个输出通道了(对应PA4和PA5引脚):
+ S! b# W5 u8 ^+ O0 O; V 图23.1.2. 6DAC的两个输出通道引脚 注意:表中的dac_pclk即DAC的时钟源,转换和寄存器访问都是靠这个时钟,该时钟来自APB1,最大可以配置为104.5Mhz。9 @% Z! ]. L) U1 U! k6 D2 M3 ~% t 从通道框图可以看出,DAC输出是受DAC_DORx寄存器直接控制的,但是我们不能直接往DAC_DORx寄存器写入数据,而是通过DAC_DHRx间接的传给DAC_DORx寄存器,实现对DAC输出的控制。 2. DAC通道使能/转换速度和输出电压4 t; S4 g4 g1 w# `* G6 C3 B& [ (1)DAC通道使能$ Y1 J! ]* u6 P 将 DAC_CR 寄存器中的相应 ENx位置1,即可使能对应DAC 通道。经过一段启动时间 tWAKEUP后,DAC 通道被真正使能。从数据手册查询,tWAKEUP 的典型值为2us或者5 us:, O2 T# _3 b7 G6 c
图23.1.2. 7 tWAKEUP 时间 一旦使能DAC通道,相应的GPIO引脚(PA4和PA5)就会自动与DAC的模拟输出(DAC_OUTx)相连,为了避免寄生的干扰和额外的功耗,引脚PA4和PA5应当设置成模拟输入。: D* F. ~* K5 |' x% u. F- } (2)DAC的转换速度- |/ q9 S. M+ W DAC的转换速度最快是1MSPS:
图23.1.2. 8 DAC的转换速率 (3)DAC的输出电压 DAC就是数字量转换为模拟量,数字输入经过DAC被线性地转换为模拟电压输出。当DAC的参考电压为VREF+的时候,DAC的输出电压是线性的从0~VREF+,12位模式下DAC输出电压与VREF +以及DORx的计算公式如下: DACx输出电压= VREF *(DORx/4096)) f% t. k5 ?9 _& y 3. DAC的8/12位数据格式 前面我们提到,STM32MP157的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。DAC单通道模式下的数据寄存器对齐方式,总共有3种情况,如下图所示: + J9 g- h" y* J9 F: E6 j
图23.1.2. 9 DAC单通道模式下的数据寄存器对齐方式" e! Q p' l0 z A ①8位数据右对齐:用户将数据写入DAC_DHR8Rx[7:0]位(实际存入DAC_DHRx寄存器的DHRx[11:4]位,后面的描述中我们只写DHRx[11:4]位)。 ②12位数据左对齐:用户将数据写入DAC_DHR12Lx[15:4]位(实际存入DHRx[11:0]位)。/ i q9 F" b. l5 O ③12位数据右对齐:用户将数据写入DAC_DHR12Rx[11:0]位(实际存入DHRx[11:0]位)。/ m4 B# c" w6 A' F/ K/ z 我们本章实验中使用的都是单通道模式下的DAC通道1,采用12位右对齐格式,所以采用第③种情况。另外DAC还具有双通道转换功能。对于 DAC 双通道(可用时),也有三种可能的方式,如下图所示:3 d5 G1 r5 p" ^' ?1 ]
图23.1.2. 10 DAC双通道模式下的数据寄存器对齐方式3 {: D# A3 C0 l7 Z- f) U ①8位数据右对齐:用户将DAC通道1的数据写入DAC_DHR8RD[7:0]位(DHR1 [11:4]位),将DAC通道2的数据写入DAC_DHR8RD[15:8]位(DHR2 [11:4]位)。4 l4 i; ~8 u$ z. y' N/ {6 t ②12位数据左对齐:用户将DAC通道1的数据写入DAC_DHR12LD[15:4]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12LD [31:20]位(实际存入DHR2[11:0]位)。 ③12位数据右对齐:用户将DAC通道1的数据写入DAC_DHR12RD [11:0]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12RD [27:16]位(实际存入DHR2[11:0]位)。0 M0 r* g K% X+ q# t3 F# D3 o 4. DAC的触发选择: S+ T3 l4 g: u, q* S, N( u+ U 写入到DAC_DHRyyy(就是上述提到的DAC_DHR8Rx、DAC_DHR12Lx和DAC_DHR12Rx等)的值,经过相应的移位操作后会被转存到DHRx寄存器中,随后,DHRx寄存器的内容可以通过触发后被自动地传送到DAC_DORx寄存器中,当 DAC_DORx加载了DAC_DHRx内容时,模拟输出电压将在一段时间 tSETTLING 后可用,也就是从DAC_OUTx引脚上输出,输出的值就是我们要获取的转换值。tSETTLING 的具体时间取决于电源电压和模拟输出负载,我们可以从《STM32MP157A&D数据手册》找到此值的典型值是2us。 DAC可以通过软件或者硬件触发转换,通过配置TENx控制位来决定。 (1)不使用触发; i1 ?# C# G# j c TENx为0时不使用触发,DAC通道x的触发被禁用,写入到DAC_DHRx寄存器的数据在一个APB1时钟后自动传至寄存器DAC_DORx中。不使用触发转换的时间框图如下图所示,dac_pclk的时钟其实也就是APB1总线时钟:
图23.1.2. 11 TEN=0时DAC模块转换时间框图- P$ F2 a& X9 Z5 `+ x (2)使用触发3 n* d( o+ v4 f: ^. h" A TENx为1时,表示使能触发,这里分为软件触发和硬件触发。DAC_CR寄存器的TSELx[3:0]为0000时表示选择软件触发,DAC_CR寄存器的TSELx[3:0]不为0000时(具体设置什么可以根据此位的设置来),表示选择硬件触发。; V. V5 e% e, O2 n5 @; } ] 软件触发 如果选择软件触发,一旦DAC_SWTRGR 寄存器的SWTRIGx(x=1,2)位置 1,转换随即开始。DAC_DHRx的内容加载到DAC_DORx寄存器中后,SWTRIG 即由硬件清0。如果选择软件触发,DAC_DHRx寄存器的内容只需一个dac_pclk(也就是一个APB1)时钟周期即可转移到DAC_DORx寄存器中。ENx 位置1(使能DAC通道)时就无法更改TSELx[3:0]位。 硬件触发5 M8 l& s7 o& M3 B 如果选中硬件触发,每次DAC接口检测到触发信号时(定时器TRGO输出,或者外部中断线9的上升沿),最新存放在DAC_DHRx的值会被传入DAC_DORx中,发生触发后再经过3个dac_pclk周期(3个APB1时钟周期)后,DAC_DORx寄存器将会得到更新。一旦DAC_DORx寄存器的值更新后,再经过时间tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。% N( p, H; c- s! \7 ^ 如果使用触发(TEN=1),可通过TSELx[3:0]控制位来决定选择16个触发事件中的某一个来触发DAC转换。这16个触发事件的部分事件如下表所示: 1 R3 [+ B4 L- M$ o' B
- \7 c- r! P% Q! } 其它 保留7 _+ z E) q1 F9 ]) B% m 表23.1.2. 4 DAC触发选择 采样和保持模块(低功耗)及其关联寄存器可在停止模式下使用 LSI时钟源(dac_hold_ck)运行。 a5 U+ ]! a# O# l, B 5. DAC的DMA请求$ L$ J/ R! \$ e g/ A 每个DAC通道都有DMA功能,两个DMA通道分别用于处理两个DAC通道的DMA 请求。如果DMAENx 位置1时,如果发生硬件触发(而不是软件触发),就会产生一个DMA 请求,然后DAC_DHRx的数据被转移到DAC_DORx中。 DAC_DMA请求没有缓冲队列,如果第二个外部触发到达时,尚未收到第一个外部触发的应答,将不会发出新的请求,DMA通道下溢标志 DMAUDRx将置1,产生中断(DMA下溢),DAC 通道仍将继续转换旧有数据。软件通过写1到DMAUDRx中来清除标志位,将所用 DMA 数据流的 DMAEN 位清零,并重新初始化 DMA 和 DAC 通道,以便正确地重新开始 DMA 传输。3 G$ u- z0 m! X2 H# [5 p! X
; g5 f; e/ ^! q2 k, r ~- I6 z+ \ ~ 表23.1.2. 5 DAC中断 24.2 DAC输出实验7 M. C8 C4 N9 h7 B/ r. u$ w7 g 本实验我们来学习DAC输出实验。 本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 17-1 DAC_OUT1。 24.2.1 DAC寄存器. T5 ?; [! i) U- R" I5 K/ D 下面,我们介绍要实现DAC的通道1输出,需要用到的一些DAC寄存器。 & d1 a, F. @# h; m, E0 A3 z 1.DAC控制寄存器(DAC_CR) DAC控制寄存器描述如下图所示:: ?4 r' X6 L, P Z 3 \% Q6 l- A4 |2 u# |( N; Y2 ^
图23.2.1. 1 DAC_CR寄存器 DAC_CR的低16位用于控制通道1,而高16位用于控制通道2,我们这里仅列出本实验需要设置的一些位:0 p& b$ t6 N9 M6 _! i- T9 `& r EN1位:用于DAC通道1的使能,我们需要用到DAC通道1的输出,该位必须设置为1。, N2 U+ X" O x! Z6 I TEN1位:用于DAC通道1的触发使能,我们设置该位为0,不使用硬件触发。写入DAC_DHR1的值会在1个APB1周期后传送到DAC_DOR1,然后输出到PA4口上。 TSEL[3:0]位:用于选择DAC通道1的触发方式,本实验设置为0,使用软件触发。, j8 n; p$ Q$ B3 V* }$ P8 S1 ?+ p WAVE[1:0]位:用于控制DAC通道1的噪声/波形输出功能,默认设置为00,不使能噪声/波形输出。6 e' G' u2 D" q h, A! g, Q DMAEN1位:用于控制DAC通道1的DMA使能,本实验不使用DMA,设置该位为0即可。 CEN1位:用于控制DAC通道1的输出缓冲校准使能,本章不使用校准功能(默认有一个出厂校准值,我们使用默认的校准值即可),设置该位为0即可。5 U1 Y5 c7 z6 m0 E$ I3 S, C 2. DAC模式控制寄存器(DAC_ MCR) DAC模式控制寄存器描述如下图所示:, U- Z6 f$ _1 ~/ b1 M- {/ J 4 K# m: J, {! f/ e. t% m1 S, I
图23.2.1. 2 DAC_ MCR寄存器+ H( C( p% l u% ?: c 该寄存器我们只关心MODE1[2:0],这三个位用于设置DAC通道1的工作模式。本章使用普通模式,且使用输出缓冲,设置MODE1[2:0]=0即可。MODE2[2:0] 设置通道2的工作模式,本实验没用到。' D4 e2 ]2 C* D! m# I& _$ `/ v$ p4 c; ` 3. DAC通道1 12位右对齐数据保持寄存器(DAC_ DHR12R1)# a2 v8 F! m3 I {& z3 w DAC通道1 12位右对齐数据保持寄存器描述如图下图所示: 1 X* N- d& i- Q, O- Y2 N0 D3 V3 J
图23.2.1. 3 DAC_ DHR12R1寄存器, T& V' H# b& H( H 该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果! s) \0 [" B# N 24.2.2 DAC的HAL库驱动 DAC在HAL库中的驱动代码在stm32mp1xx_hal_dac.c和stm32mp1xx_hal_dac_ex.c文件(及其头文件)中,可以翻看这两个文件来了解。+ J- A) b& `; v5 I) `! d$ n 1.HAL_DAC_Init函数 DAC的初始化函数,其声明如下:/ o: X. F+ a9 [, Z HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac);0 J, N4 b! k# W& H# d% z% ?, H 函数描述: 用于初始化DAC。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量,其定义如下:9 T& _. K! R' }9 X& ~' z5 K8 ?
从该结构体看到该函数并没有设置任何DAC相关寄存器,即没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC,为后面HAL库操作DAC做好准备。 函数返回值: HAL_StatusTypeDef枚举类型的值。5 N3 s) i' e G$ x 注意事项: DAC的MSP初始化函数HAL_DAC_MspInit,该函数声明如下: void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac);) L: ]6 q Q. U( m. ^ 2. HAL_DAC_ConfigChannel函数4 P7 X7 n, C' J7 R7 z5 ~ DAC 的通道参数初始化函数,其声明如下: HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel);/ M7 s) M, X% |; p 函数描述: 该函数用来配置DAC通道的触发类型以及输出缓冲。. @9 B7 f5 ^9 F' S: |! ^6 M 函数形参:3 X; _- ~; i( V( ^, ?% l7 P" k; ] 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2是DAC_ChannelConfTypeDef结构体类型指针变量,其定义如下:
形参3用于选择要配置的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。3 X1 a, m6 j0 D 函数返回值: HAL_StatusTypeDef枚举类型的值。 3. HAL_DAC_Start函数 使能启动DAC转换通道函数,其声明如下: HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel); 函数描述: 使能启动DAC转换通道。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。1 o% R* F& j3 n% I9 ~/ B3 N. {- T 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。3 c' m4 K' P: }$ Q% U! Z 函数返回值: HAL_StatusTypeDef枚举类型的值。7 | |- @: k9 f/ Z: M! s 4. HAL_DAC_SetValue函数 DAC的通道输出值函数,其声明如下:/ a* }2 r9 I1 H$ j5 R* w X1 K HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data); 函数描述:! w* q" k; k/ ^. q 配置DAC的通道输出值。 函数形参:, Y& y/ D& d; N: T7 e 形参1是DAC_HandleTypeDef结构体类型指针变量。0 U$ D( @! F1 a/ S/ V! [ 形参2用于选择要输出的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。& |' t7 B; _7 o4 S8 T; D# _% V 形参3用于指定数据对齐方式。 形参4设置要加载到选定数据保存寄存器中的数据。4 ~' M3 S* w2 k! `! X 函数返回值:7 H9 Q2 R% ~2 c# V1 U4 [3 f* d HAL_StatusTypeDef枚举类型的值。% W3 o3 C0 t9 r: R5 w* l 5. HAL_DAC_GetValue函数 j+ q4 o7 `0 s DAC读取通道输出值函数,其声明如下:+ A% ?$ Q7 g B5 Q, e1 |- s5 M d uint32_t HAL_DAC_GetValue(DAC_HandleTypeDef *hdac, uint32_t Channel);8 m* g5 f7 U8 S+ @ 函数描述:6 d1 m; K6 k$ X+ K4 n1 U+ J 获取所选DAC通道的最后一个数据输出值。 函数形参:! c' Z: y7 Y i' ~' e 形参1是DAC_HandleTypeDef结构体类型指针变量。! [1 x* X1 l: ~8 M3 m0 n 形参2用于选择要读取的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。5 [7 y* p+ P2 O9 e. K 函数返回值:" e9 D8 P. l* e# L 获取到的输出值。& c0 q# }3 b7 [/ Z 0 f& q; @+ |" |! @ }( K 24.2.3 硬件设计7 x ^* M' l0 P B* k" @ 1.例程功能9 W* u; `% E1 Q2 u: T 使用KEY0/KEY1两个按键,控制STM32MP157内部DAC的通道1输出电压大小,然后通过ADC1的通道19采集DAC输出的电压,并通过串口将ADC采集到的电压值以及DAC的设定输出电压值等信息打印出来。实验中,LED0闪烁,提示程序在运行。 实验时,请用跳线帽将PA4和PA5接在一起:
& L) U0 B" S" E5 x2 O+ s 图23.2.3. 1 硬件连接 本节实验ADC的配置和相关代码部分和ADC实验章节的 单通道ADC采集实验 部分一样。 2. 硬件资源. r% H4 W8 K; i 1)LED灯:LED0; y+ p2 t) u' M$ [$ L4 ] 2)按键KEY0和KEY1 3)串口4 4)ADC1的通道19引脚(PA5) 5)DAC的通道1 LED0 UART4_TX UART4_RX ADC1_INP19 DAC_CHANNEL_1 KEY0 KEY1' ~! u: F; w- [3 v+ D% K. | PI0 PG11 PB2 PA5 PA4 PG3 PH70 ]; d. I. L1 D) x' g d6 ~+ ` 表23.2.3. 1硬件资源; V1 ^: A( C- n+ x: Z: Q( C 3. 原理图. \2 c4 U2 ?+ @5 Y8 Z& q8 t g' M/ v ADC和DAC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。
8 c S- \+ H. t 图23.2.3. 2 原理图部分" |' N; G* P/ Q1 v' n# ] 24.2.4 软件设计% @# H% ]$ z- x2 X; T 1.新建和配置工程5 u) h% ^9 A5 x (1)配置LED0、KEY0、KEY1和UART4 新建工程DAC_OUT1,本节实验我们会用到UART4发送数据,还会使用到按键KEY1、KEY0以及LED0,所以我们这里按照前面的实验章节配置LED0、KEY0、KEY1。UART4的配置我们这里就不再赘述了,请参考前面串口通信实验章节的来配置。 ! H# x) @7 F3 x; s/ g
图23.2.4. 1 按键和LED的引脚部分配置1 J# [$ _. W. u 9 V4 M- ?. \/ N* B. s9 T
/ O7 h8 O' x, o1 Q7 _! b- f 图23.2.4. 2 UART4引脚部分配置- |$ o# d1 U* I5 [* _1 |$ T LED0和按键以及UART4的代码我们本节实验就不讲解了,大家可以参考本实验工程里的代码,也可以参考前面相关实验章节部分。; a8 L1 G* D T+ k: m# w, N (2)配置ADC ADC1的配置可参考前面 单通道ADC采集实验 部分。选择ADC1的通道19:& M1 ]) w0 h& T$ _6 c# a: J6 H" F 9 ~: R6 x( j+ }" o9 B2 h- c2 q1 i
图23.2.4. 3选择ADC1的通道19' A6 p# A% n! ]# I" U# a% M ADC的配置参数我们前面ADC实验章节已经做了详细讲解,这里就不再说明,配置如下:) _) @' S) o/ r: F9 L/ A0 V5 s
1 G5 C8 u1 D: u3 p# ]# f0 J 图23.2.4. 4配置ADC1的参数 (3)配置DAC. V1 k* W0 X6 F; ?5 \$ a DAC配置如下,DAC的每路输出均可与DAC_OUTx输出引脚断开连接,然后可与片上外设连接。这里我们可以配置DAC输出连接到外部引脚DAC_OUTx。7 p: B3 L7 f9 F/ P DAC参数配置部分: Output Buffer配置使能; Trigger配置触发源,可以配置软件触发和硬件触发以及不触发,我们配置不触发;# Y' p5 i1 |! b( m. x& U* } DAC High Frequency配置DAC是否以指定频率接口模式工作,可以开启或者关闭高频接口模式,或者设置高频接口模式为自动模式。% p% r! ?8 U7 I User Trimming设置DAC的校准方式,采用出厂模式还是用户模式。校准的目的是为了减少误差,这里我们就默认使用出厂模式;9 Z. n! s* L7 h9 @7 W {( g# S Sample And Hold用于设置是否使能低功耗模式,即采样和保持模式,这里我们就不选择了。; C3 s2 N- U( w* ]# O6 p
图23.2.4. 5配置DAC的输出通道1 (4)UART相关的参数配置) G4 l% ]5 V) o! m# X6 n/ |; I+ u UART4的参数配置以及中断配置请参考前面串口通信实验。) P; h+ Z+ l3 d) o9 ^2 g (5)时钟配置 这里配置MCU的时钟为209MHz,使用外部时钟HSE,这部分配置可以参考前面实验章节: ! Z5 L. Z* n$ `6 P. t' Q
图23.2.4. 6时钟树配置 ADC1的时钟使用PER,其中PER时钟源默认使用HSI,即为64MHz。上面ADC参数配置中,我们配置分频系数为2,所以实际ADC的时钟是32MHz。DAC因为接在APB1上,DAC的时钟是104.5MHz。1 y9 z! n6 R$ H (6)配置生成独立的.c和.h文件# i* C5 J4 g! a+ w M2 s
; L& N- }3 _* o/ U' u8 U/ \( e 图23.2.4. 7配置生成独立的文件( a$ t# }. m5 `+ h& e" A 2. 生成工程/ h& I1 h5 l4 |3 H& Y- m2 J 保存配置,生成工程后,将前面按键输入实验章节使用的BSP文件夹拷贝到工程的Src文件夹下,本节我们要使用LED0、KEY0和KEY1的驱动,所以将LED1和WKUP相关的代码注释掉即可。 ( N' V: J! q% a5 M A
图23.2.4. 8生成工程 3. 初始化代码分析3 J4 m8 e7 T0 U* ~7 A/ l$ L ADC的初始化代码我们在前面章节的实验已经介绍过,这里就不再介绍了。DAC的初始化代码在dac.c文件及其头文件中,代码附上了详细的注释,如下:" T( ?. U( J, @: o9 u
4.添加用户代码 (1)修改adc.c文件 按照前面 单通道ADC采集实验 章节,在adc.c中添加如下代码:$ I( X. X! L6 Q+ T* c: Q' S' f; a
adc.h文件添加如下代码:
(2)修改main.c文件% P3 @3 N; e) h! Q$ }! j 在main.c文件添加如下代码,当KEY0按下时,DAC_DHR12R1寄存器的值为2048,然后ADC1的通道19获取DAC1_OUT1的值,根据此值计算出对应的电压值。7 x' T. H8 `; H: \
第37行,先启动DAC,DAC启动后才可以进行后续操作。* R4 m |: f! H8 P% t2 U! V% `7 p 第44~54行,如果是KEY0按下,则设置DAC通道1为 12位数据右对齐模式,DAC_DHR12R1寄存器的值为4095;如果是KEY1按下,设置DAC通道1为 12位数据右对齐模式,DAC_DHR12R1寄存器的值为4000。这里注意的是,ADC是16位的,而DAC是12位的,DAC_DHR12R1寄存器的值最大只能设置为4095。 按照上述参数计算,如果是按键KEY0按下,电压值为3.3V(最大值),如果是按键KEY1按下,电压值应为3.3*4000/4095=3.22V。. X' Z; c( g8 u1 g2 a) ] 第56~71行: 如果是KEY0或者KEY1按下了,或者是10s的时间到了,UART4会打印DAC输出的值和计算出的电压值,以及ADC采集到的转换值以及计算出的电压值。& b, M) J0 c, w* {! |& Y1 V 当没有按键按下时,UART4打印的这些值都应该为0,因为第71行使用HAL_DAC_SetValue函数对DAC_DHR12R1寄存器进行了清零操作。实际的实验中可能会有一些误差,会出现一些非0的情况,不过数值比较小,我们可以认为其值为0。 第72行,LED0闪烁。6 C1 G4 A* M& r" E 第75行,延时1秒,当然也可以将此延迟时间改短一些。 24.2.5 编译和测试 程序中使用浮点运算,所以要设置工程属性,支持浮点打印。! l+ o6 p: J- Z% m
1 w/ u# n+ Z0 H8 `9 l9 ?. M 图23.2.5. 1配置工程支持浮点打印% v" A. w4 w6 F. o- e' H+ B 程序测试结果如下,和上面的计算结果一致:: d6 G6 l3 s8 |6 B! s 4 `2 X. |. {# {& _( t8 E/ a
图23.2.4. 9测试结果 24.3 DAC输出三角波实验 本实验我们来学习使用如何让DAC输出三角波,DAC初始化部分还是使用上一章DAC输出实验 章节的,所以操作本实验的前提是先学习DAC输出实验 章节的内容。 本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 17-2 DAC_Triangle_Wave。 & i+ q: `& W, d% k* W" Z( i 24.3.1 生成三角波原理; e$ l& I" ^ j5 G9 Q' C& h/ w 1.使用外部触发方式生成三角波 要生成三角波,DAC_CR寄存器的TENx位置1来使能DAC触发,WAVEx[1:0]要设置为1:x(x表示0或1)来使能生成三角波。如下图是生成DAC三角波示意图,DAC_DHRx是三角波的基电压值(最低电压值),该值可以调节。MAMPx[3:0]是DAC_CR寄存器的位,通过设置该位可以控制三角波的振幅。DAC_DHRx+MAMPx[3:0]是DAC输出的三角波的最高电压值(电压幅值)。三角波的数据生成是由一个三角波计数器往复加减生成的,每次触发之后该计数器会加1或减1: 每次发生触发事件后,经过三个APB1时钟周期,内部三角波计数器将会递增,在不发生溢出的情况下,该计数器的值将与DAC_DHRx寄存器内容相加,所得总和将传输到DAC_DORx寄存器中,只要该三角波计数器的值小MAMPx[3:0]位定义的最大振幅值,三角波计数器就会一直递增,一旦达到MAMPx[3:0]配置的振幅值,计数器将递减至零,然后再递增,以此类推,这个过程如下图所示:+ k% @. H! t+ O4 e
图24.3.1. 1三角波示意图% z$ O. `% A9 h5 m3 q8 p 使用触发方式,我们可以选择定时器作为触发源,使用定时器来触发。可以设置定时器每周期计时结束时DAC_DHRx寄存器的内容加1,当使用12位模式时,DAC_DHRx寄存器的最高赋值为4095,所以在配置定时器的时候注意此范围。
图24.3.1. 2可选的外部触发源- i6 b4 ]* w" N/ C! j% h 2. 不使用触发方式 当然,我们也可以不使用触发的方式来生成三角波,我们通过每隔一定时间dt往DAC通道12位右对齐数据保持寄存器 (DAC_DHR12R1)中写入一个值,每次写入的值递增incval,总共递增samples/2次,然后再将DAC_DHR12R1的值每隔dt递减incval,总共递减samples/2次,如此反复,DAC_DHR12R1的值传输给DAC_DORx,最后由DAC_OUTx输出转换的电压值,这样就类似一个三角波了。这里,dt我们称为每个采样点的延时时间,samples我们称为采样率,我们用如下一张图来描述:1 }! V: H8 ^# W/ x/ \# O! ?
* u8 Y0 T, W& v8 t$ G7 V 图24.3.1. 3 三角波示意图! Z4 J0 S, \7 E. v3 \8 T 其中波形的振幅对应DAC_DHR12R1的值最大为4095(DAC_DHR12R1是12位的寄存器,最大值为4095),实验中的dt值尽量小,这样得出的波形就越接近三角形,上图中为了讲解,画的dt的间隔比较大。图中显示的是一个三角波的过程,三角的斜边的某个点的值是DAC_OUTx输出的电压值,是由DAC_DHR12R1的值转换得来的,根据勾股定理,三角斜边值大于直角边的值,所以(DAC_DHR12R1+1)>(samples/2),此三角波的周期T=samples*dt,那么此三角波的频率就是1/T。0 j5 ~# v4 _6 W" U( k+ I 本实验我们就采用以上第二种方法,实验中,我们通过修改dt的值和samples的值来实现输出两种波形: 1)设置dt为5us,samples为2000,则输出的三角波频率为100Hz; 2)设置dt为500us,samples为20,则输出的三角波也是100Hz。 24.3.2 DAC寄存器 1.DAC_CR寄存器4 a T6 D8 C: j 本实验用到的DAC_DHRx和DAC_DORx寄存器在上一章节已经介绍了,这里我们介绍DAC_CR寄存器和三角波设置有关的位,如下: 6 `& B7 Q w- y, `8 w' p
M; {3 M' a- [) m 图24.3.2. 1 DAC_CR寄存器0 @& L; k) L) r+ ~ P' c 如果使用触发方式,对应位设置如下: TENx位用于使能DAC的通道,将该位置1则使能对应的通道;6 v, D; n E$ j Y WAVEx[1:0]用于使能或者禁止生成波,如果要生成三角波,此位设置为1x;1 V: D2 A8 ]4 i, i DAC_CR寄存器的MAMP1[3:0]和MAMP2[3:0]分别对应的是的是通道1和通道2的掩码/振幅选择器配置项,这些位由软件写入,用于在生成噪声波模式下选择掩码,或者在生成三角波模式下选择振幅,MAMP1[3:0]配置如下:/ t7 O" t% B) q" c* M 0000:不屏蔽 LFSR 的位 0/三角波振幅等于 18 p) y* U; u3 |+ S; g" t 0001:不屏蔽 LFSR 的位 [1:0]/三角波振幅等于 3 0010:不屏蔽 LFSR 的位 [2:0]/三角波振幅等于 7$ i- A4 a3 n6 }7 m7 j" W* ?/ X 0011:不屏蔽 LFSR 的位 [3:0]/三角波振幅等于 15 0100:不屏蔽 LFSR 的位 [4:0]/三角波振幅等于 31 0101:不屏蔽 LFSR 的位 [5:0]/三角波振幅等于 63 0110:不屏蔽 LFSR 的位 [6:0]/三角波振幅等于 1276 ~1 i: q& S% |0 X- Z 0111:不屏蔽 LFSR 的位 [7:0]/三角波振幅等于 255 1000:不屏蔽 LFSR 的位 [8:0]/三角波振幅等于 511 1001:不屏蔽 LFSR 的位 [9:0]/三角波振幅等于 1023 1010:不屏蔽 LFSR 的位 [10:0]/三角波振幅等于 20471 l6 j+ ~4 i% ?1 t ≥ 1011:不屏蔽 LFSR 的位 [11:0]/三角波振幅等于 4095* p; E, p* ?7 m% v+ O2 D7 A, m+ T WAVE1[1:0]和WAVE2[1:0]用于使能DAC通道1和通道2的噪声/三角波生成,这些位由软件置 1 或清零:4 I& k6 b) D7 E& t! A 00:禁止生成波* h$ V( N0 H; y9 l' Y6 A 01:使能生成噪声波' _. J2 x8 t5 C/ ]9 e 1x:使能生成三角波 2. 其它寄存器$ M& s: X- r* L A 关于本节实验用到的DAC的HAL库驱动,我们前面的实验章节已经介绍过,这里就不再重复介绍了。 24.3.3 DAC的HAL库驱动6 D" n }+ G3 v u 本实验中用到的HAL库驱动前面的实验代码都有介绍到,这里就不再赘述。 24.3.4 硬件设计 1.例程功能# X& q$ s1 G3 z5 n3 S 使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察波形。LED0闪烁,提示程序运行。 我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4的引脚排针已经引出,硬件连接如下图所示:8 g4 X7 ~1 P; L7 j: t/ Z. y
5 x- x& B5 _( m& P: T" v 图24.3.4. 1硬件连接示意图 2. 硬件资源 1)LED灯:LED0" `. Z1 y8 m7 ~& c 2)按键KEY0和KEY1 3)DAC的通道1 LED0 DAC_CHANNEL_1 KEY0 KEY11 g+ H' ^! O' q PI0 PA4 PG3 PH7 表24.3.4. 1硬件资源+ H: a, v0 c' g8 ?9 s 3. 原理图 DAC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。 4 R1 J4 G- b% Y
2 o7 h8 m7 v: h5 C( x+ [$ L9 P5 M 图24.3.4. 2原理图部分 24.3.5 软件设计" G- M5 S0 x* q* D 1.创建和配置工程% ?; _4 `! f+ N( M: _) y 本实验使用到DAC,可以直接在上一章节的实验的基础上操作。为了方便,我们新建了一个工程DAC_Triangle_Wave,DAC工程的配置和上一章节的一样,这里我们没有用到ADC,关于ADC部分我们就不配置了。 图24.3.5. 1DAC配置5 h% H! h% j6 i% f4 c5 p1 \ 关于本节实验用到的LED0、KEY1和KEY0的配置清参考前面相关的实验章节部分。时钟配置使用HSE作为锁相环PLL3的输入时钟源,我们配置APB1为104.5MHz,关于时钟配置也请参考前面实验有关章节部分。 2. 生成工程 生成工程,并将上一章节用到的BSP文件夹拷贝到本工程的Src目录下,因为我们会用到上一章节实验的LED0和KEY0以及KEY1的驱动程序,然后本节实验会用到微秒延时函数(dt采用us来计时),所以将SysTick高精度延时实验的delay.c和delay.h文件拷贝到本工程的BSP文件夹下。 图24.3.5. 2生成工程4 Y# D+ ?1 o; b: z 3. 添加用户代码) A$ T) }/ E& v4 T q (1)添加三角波生成函数; S0 `. L7 n, i; Q; z1 g& D0 P/ M 如下,我们在dac.c文件中添加如下代码:
以上代码中,DAC_DHR12R1的值一开始是0(DAC输出的三角波的基准电压为0),然后每隔一段时间通过给DAC_DHR12R1递增incval,当递减samples/2次时,给DAC_DHR12R1递减incval,当递减samples/2次时,DAC_DHR12R1再做递增运算,如此反复,最后就得到一个三角波。 第14~34行,我们通过直接操作寄存器的方法,将递增的值写入到DHR12R1寄存器中。4 n8 `8 u2 h: F0 Y1 a' m 第37~61行的方法,我们通过调用HAL库的API函数HAL_DAC_SetValue来设置DHR12R1寄存器的值。两种方法等效。4 T& t6 D1 L! I# A (2)修改main.c文件! K/ O) S+ Y9 |( D2 b! |* w: O& f* _ main.c文件部分代码如下,其中红色字体之间的代码是我们手动添加的:% W/ _* V- G/ }( ?3 c 7 n+ [( k# a6 ^0 W, ^
第22行,我们要使用us延时函数,所以要初始化延时函数。9 ~8 [3 x h2 u$ w" I* ] 第24行,自动生成的DAC初始化代码并没有开启DAC,这里我们启动DAC通道,启动后DAC通道才可以工作。+ G: K3 E6 U/ q# q, X 第34~43行,如果是KEY0按下,则DAC以5us的采样时间间隔进行采样,采样点个数为2000个。如果是KEY1按下,则DAC以500us时间的间隔进行采样,采样点的个数是20个。两种采样参数配置,最后得出的波形约为100Hz。 24.3.6 编译和测试 编译后进行测试,实验中我们使用示波器测试PA4引脚输出的波形。如下所示,两种波形的频率接近100Hz。 1)当KEY0按下时:
, W B9 t' m& X 图24.3.6. 1三角波1; |9 i6 ^' ~: h5 u 2)当KEY1按下时: 5 D0 w2 `9 {7 R" I& d8 I9 y q$ c9 d
" }; x) J0 y' g9 g9 Z) M 图24.3.6. 2三角2 8 V3 l- a: @. ], C
- ?" Y; T( }5 e! O0 g 24.4 DAC输出正弦波实验9 I( W( g+ d' H 本实验我们来学习使用如何让DAC的通道输出正弦波,其中DAC使用12位右对齐模式,也就是使用到DAC_DHR12Rx [11:0] 位。实验将用定时器7来触发DAC进行转换输出正弦波,并以DMA传输数据。4 ?$ X6 W0 k5 v- y8 H# R1 \9 R4 U 本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 17-3 DAC_Sine_Wave。 3 N2 W8 M! x3 g) H, m0 H) r 24.4.1 DAC输出正弦波原理 1.y=sinx* y3 h$ W9 O5 ]6 O/ ~ 通过前面输出三角波实验,本节的正弦波实验更加容易理解。正弦函数为y=sinx,一个周期的波形中,0≤x≤2π,-1≤y≤1。如下是一个周期的正弦波形图,实验中可以将2π分为samples等份,每一份的长度是inc,将每一份对应的y值依次写入DAC_DHR12Rx中,DAC_DHR12Rx的值会写入DAC_DORx里,最后由DAC_OUTx输出转换值,输出的波形也类似与一个正弦波。
图24.4.1. 1 y=sinx函数 2. y=1+sinx+ J& W, J. N d- F9 o1 s4 ^ 以上的y值有负数,最小为-1,由于单片机不能直接使用负数来计算,于是我们将上述的公式变换一下:y=1+sinx,如下图所示,这个时候的y值最大为2,最小为0:
图24.4.1. 2 y=1+sinx函数 3. y=maxval * (1 + sinx) 装入DAC_DHR12Rx的值不可能最大只有2,所以可以将此波形放大一定的倍数,这里要注意了,因为12位的DAC_DHR12Rx最大只能是2^12=4096,而4096/2=2048,所以最大只能放大2048倍,于是可以得到公式:y=maxval * (1 + sinx),其中maxval是波形放大的倍数(maxval最大2048),波形如下: 图24.4.1. 3 y=maxval * (1 + sinx)函数" O0 t4 W+ N5 ^$ W' {9 T) }8 h 分析到这里,0≤x≤2π,放大倍数maxval 最大为2048,装入DAC_DHRx的值0≤y<4096,如果想显示正弦波,那么装入DAC_DHR12Rx的值就按照上述的波形图变化。4 n3 H# L7 i$ v1 _2 z4 b 实验中,我们通过将2π分成samples等份,每份为inc=2π/samples,x= inc * i,其中0≤i≤samples,这样y=maxval * (1 + sin(inc * i)),此y值就是以上的波形值,将此值写入DAC_DHR12Rx后,经过DAC_OUTx输出的转换值刚好就形成一个正弦波了。从0~2π之间的点是连续变化的,实验中我们不可能把每个点对应的值都写入DAC_DHR12Rx中,我们选择其中的一些点来实验,当取的点数越多时,输出的波形越接近正弦波。5 x/ Q6 y+ N7 }9 U6 S" _- P$ L $ }0 r P5 H& p8 s1 a; H+ E3 E 24.4.2 DAC寄存器! h6 m7 ]# H* L' k( y+ z, k% W 本实验用到的寄存器在前面的实验都有介绍,这里就不再赘述。6 a& H0 ]. L7 b0 y 7 z* |0 ~! b+ F- M1 d 24.4.3 DAC的HAL库驱动- v2 I- x* ~0 x- D' x 本实验用到的HAL库API函数前面介绍过一部分,下面我们将介绍本实验用到且没有介绍过的几个API函数。) `! b% V: a" w+ i; O0 V2 I' ] 1.HAL_DAC_Start_DMA函数 使能DAC并开始通道转换,并使用DMA方式传输函数,其声明如下: HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel,! o6 l" M5 _, j0 c; Z2 p7 q2 G2 ?+ [ uint32_t *pData, uint32_t Length, uint32_t Alignment);/ X( [0 [8 ?4 h" | L& x0 ~ 函数描述:# A! P( @7 C8 ^5 C0 z 用于启动DAC通道转换,并使用DMA的方式进行传输。 函数形参:9 Q# M3 G, v# U4 a 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。$ F# l# k2 M* E 形参3是使用DAC输出数据缓冲区的指针。 形参4是DAC输出数据的长度。 形参5是指定DAC通道的数据对齐方式,有:DAC_ALIGN_8B_R(8位右对齐)、DAC_ALIGN_12B_L(12位左对齐)和DAC_ALIGN_12B_R(12位右对齐)三种方式。 函数返回值:3 J8 ^+ s* p7 X, G HAL_StatusTypeDef枚举类型的值。, n0 V; A5 s# s- }& a: ? 2.HAL_DAC_Stop_DMA函数1 T0 G8 N- p% n/ v5 q 停止DAC的DMA方式函数,其声明如下:2 f s2 A4 g1 G: ` HAL_StatusTypeDef HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel);' a$ R5 P. t2 Y! T: |5 ?9 t 函数描述:' F! Q4 u& r& W/ P+ k 用于停止DAC通道转换和DMA传输。& W7 e8 @, B) m2 ^0 W5 j 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 函数返回值:9 K7 A4 d) Z0 `8 y HAL_StatusTypeDef枚举类型的值。 3.HAL_TIMEx_MasterConfigSynchronization函数$ \3 Q& @6 q7 ^* R, X1 W 配置主模式下的定时器触发输出选择函数,其声明如下: HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization(- E$ [, M# V6 A) x* R. y TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig); 函数描述: 用于配置主模式下的定时器触发输出选择。6 K& i* `0 H$ x2 f! {. ~ 函数形参: 形参1是TIM_HandleTypeDef结构体类型指针变量。 形参2是TIM_MasterConfigTypeDef结构体类型指针变量,用于配置定时器工作在主/从模式,以及触发输出(TRGO和TRGO2)的选择。 函数返回值:9 i- S3 V6 F* \; h5 |/ a9 o HAL_StatusTypeDef枚举类型的值。 24.4.4 硬件设计 4.例程功能) D7 Z. \ B. o$ q" u; F 使用TIM7触发DAC输出正弦波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种正弦波,实验中需要通过示波器接PA4进行观察,实验中通过LED0闪烁来提示程序在正常运行中。实验中,我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4的引脚排针已经引出,硬件连接如下图所示:% o) w- O4 A1 O( d$ R " k$ p* y6 [, L1 U! Z; {( }
$ m# L3 a* g$ _; w8 E 图24.4.4. 1硬件连接示意图9 h7 |: |" C [4 ], ]! Y 本章节实验我们采用硬件触发(TIM7上溢事件)的方式来触发,每当DAC获取一个触发事件,DAC_DORx就会加一个固定的值outdata,此值通过DMA方式来传输。 2. 硬件资源 1)LED灯:LED0 2)按键KEY0和KEY1 3)DAC的通道1! Q: b/ b; G' Y* d3 l2 s; n0 l 4)DMA2(使用到DMA2数据流6DMA2_Stream6) 5)TIM7& {# t' j$ f- ?6 B3 I6 b LED0 DAC_CHANNEL_1 KEY0 KEY1 PI0 PA4 PG3 PH72 C% r4 m/ }( v1 _$ B5 R 表24.4.4. 1硬件资源* K" ~, H/ e) v8 j- M- J 3. 原理图2 @# S% Q4 J$ g: a TIM7、DMA和DAC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。1 ]: k; {8 E3 ?& c & ^0 K, H' E" F2 K4 l1 ]/ R
: X6 Z, `) L# _- ~" H 图24.4.4. 2原理图部分7 I3 F9 e5 e/ R' T 24.4.5 软件设计* t- \" |! F$ x6 B, p( T+ e 1.新建和配置工程 (1)配置DAC 新建工程DAC_Sine_Wave,本实验用到的LED0、KEY0和KEY1可以按照前面的实验来配置。下面我们先配置DAC:
6 p [$ |8 V; g( ~* L& Y 图24.4.5. 1配置DAC 以上配置中,注意Trigger选项我们配置为使用片上定时器的内部信号来触发DAC通道,这里选择定时器7。注意的是,DAC这里没有DMA的配置选项,前面实验章节会有DMA配置项,而且以前的STM32CubeMX软件中,DAC也是有DMA配置选项的,这里没有,不知是不是软件的bug?本篇教程使用的STM32CubeIDE是V1.4.0版本,更新的高版本笔者还未去验证,既然这里没有DMA选项,那么后面我们就手动添加DMA相关部分的代码吧。7 }! p# C( K1 N( d7 z9 C4 g6 a (2)配置TIM7 图24.4.5. 2配置TIM7 以上配置中,TIM7的分频值为209-1,向上计数模式,计数周期(TIMx_ARR的值)为1,自动重载使能,触发事件为上溢更新事件。也就是计数器每计数一次就触发DAC通道,那么触发信号的频率怎么计算呢?还记得我们前面学习基本定时器时,基本定时器的溢出时间计算方法: ( ^4 Z4 d4 k' A/ u4 R 这里,为209MHz,=1,=209-1,所以:( b( v) }3 N6 I$ J& E =500KHz 也就是说,由定时器触发的触发信号频率为500KHz。- k8 R; C5 f {3 w% @$ Q6 {! P (3)时钟配置% f. k0 @+ G% \* w, h 关于本节实验用到的LED0、KEY1和KEY0的配置清参考前面相关的实验章节部分。本节使用HSE作为锁相环PLL3的输入时钟源,我们配置APB1为104.5MHz,关于时钟配置也请参考前面实验有关章节部分。1 K5 M7 e* ?$ A! O 2. 生成工程! `8 ?+ u/ m; l 配置好工程后,保存修改,生成工程。本实验我们会用到LED0、KEY0和KEY2,所以将上一章节实验的BSP文件夹直接拷贝到本节实验工程的Src目录下,这样我们就可以直接使用里边的驱动程序:
图24.4.5. 3生成的工程 3. 添加用户代码% n: C1 o( V, ` b5 F3 T2 C" n# J% ^ TIM7和DAC的初始化代码我们前面已经分析过了,这里就不再分析了。 (1)添加DMA初始化代码 由于本实验DAC转换需要DMA,而前面的STM32CubeMX配置中没有DMA配置选项,那么我们就手动添加DMA相关的初始化代码,在dac.c文件中直接添加DMA的初始化代码。2 d. F+ C8 l X3 R3 Z6 H DMA_HandleTypeDef g_dma_dac_handle; /* DMA句柄 */! F9 |7 X: ]3 F2 C
(2)添加产生正弦波序列函数 在dac.c文件中添加如下代码,dac_creat_sin_buf函数用于产生正弦波需要的序列,也就是前面我们说的要写入DAC_DHR12R1的值,将这些值存入事先定义好的一个Buffer g_dac_sin_buf[4095]中。前面我们已经分析了产生正弦波的原理,这里的代码也就是按照前面的原理分析来实现的。
在dac.h文件中添加如下代码: 1 /* USER CODE BEGIN Private defines */( ~& ?8 J- b/ z" V9 F void dma_init(void);% o: j9 T! Z6 Q6 L/ t' A ` void dac_creat_sin_buf(uint16_t maxval, uint16_t samples); uint16_t g_dac_sin_buf[4096]; /* 发送数据缓冲区 */ C6 T; V, C, P* Z% m4 z$ S /* USER CODE END Private defines */7 A+ B$ |& N* e& e, Y 1 2 3 4& h, z8 C/ _; f( m1 X( M0 n+ [ 5 (3)main.c文件实现代码5 s% C' f) S4 u/ j5 G) ~8 r main.c文件的代码如下,我们在标红的字体之间手动添加代码 1 #include "main.h"4 x! m$ J; A" E 2 #include "dac.h" 3 #include "tim.h"3 C' e ?( n7 Y1 B$ \$ P2 @" W' f' b 4 #include "gpio.h"7 @6 W; p" j# }$ I# y" ?* j6 y+ J 5 /* USER CODE BEGIN Includes */+ B( m, o7 r8 P* Q8 e 6 #include "./BSP/Include/led.h" u ?7 j% A) Q1 Y# N4 k 7 #include "./BSP/Include/key.h" 8 /* USER CODE END Includes */ 9 void SystemClock_Config(void);, j. j* M8 g' x- ^. U 10 int main(void) 11 {, `" ]; N4 _) n* k9 { 12 HAL_Init(); /* HAL库初始化 */7 {9 R1 M- |7 D4 _) z+ I 13 if(IS_ENGINEERING_BOOT_MODE()) 14 {/ d- f6 J3 p3 ]2 g3 Z 15 SystemClock_Config(); /* 系统时钟配置 */9 |% c" M1 H, a% |5 z7 I w 16 }- D G) D# Y, n1 m3 c+ x- P 17 MX_GPIO_Init(); /* GPIO初始化 */5 B) S0 o7 t4 A% w* ~ 18 MX_DAC1_Init(); /* DAC初始化 */7 C, I/ ~! d" v! c 19 MX_TIM7_Init(); /* 定时器7初始化 *// ^# q7 y- l% `% N+ l& T 20 /* USER CODE BEGIN 2 */& o4 D7 s* h! s$ t4 W1 j1 G 21 uint8_t t = 0;& {1 f" X4 |6 c( ~4 h 22 uint8_t key;5 g+ ?' S6 \" d) P- ~% M' D) Y 23 dma_init(); /* DMA初始化 */4 Y) a' A# i6 M 24 dac_creat_sin_buf(2048, 100); /* 产生正弦波序列 */" `: P7 Y# J( q$ o$ I4 \ 25 /* 启动DAC转换,并使用DMA传输 */9 }2 h/ |. s) d1 N. J 26 HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t *)g_dac_sin_buf,\ 100, DAC_ALIGN_12B_R); 27 HAL_TIM_Base_Start(&htim7); /* 使能定时器7 */ 28 /* USER CODE END 2 */ 29 while (1); t+ E4 d3 M, s# H; q7 n* j2 s 30 { 31 /* USER CODE BEGIN 3 */2 u: g! g' G/ T# w" y 32 t++;' ]! l/ Z' ]. [6 L( H7 q- J 33 key = key_scan(0); /* 按键扫描 */% n# `- u$ D. [: j; H# N: ] 34 if (key == KEY0_PRES) /* 高采样率,约1Khz波形 */7 {/ C7 r5 d) e$ q( Q1 h1 S% e 35 {8 \+ X n/ M9 I* W( P7 G3 B Q) h( U- q 36 dac_creat_sin_buf(2048, 100); /* 产生正弦波序列 */7 L" o4 x0 t% U4 k% d( h- J. w. s 37 HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); /* 先停止之前的传输 */ 38 /* 500Khz触发频率, 100个点, 得到最高5KHz的正弦波. */, P4 N8 L' C, @9 o& C y/ U 39 HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t *)g_dac_sin_buf,\# p. K, I4 b8 v' C% C2 ]/ I2 K; | 100, DAC_ALIGN_12B_R); 40 }4 m$ W7 O/ c# I- i 41 else if (key == KEY1_PRES) /* 低采样率 , 约1Khz波形 */# g# G1 R: e' I% ?" u" T" { G 42 { 43 dac_creat_sin_buf(2048, 10); /* 产生正弦波序列 */ 44 HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); /* 先停止之前的传输 */1 m3 Q5 f4 u: ]& i 45 /* 500Khz触发频率, 10个点, 可以得到最高50KHz的正弦波. */8 m( ]. v' Z, ]& O& ^7 c( P, ~ 46 HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*)g_dac_sin_buf,\6 H9 G) Z* E7 ^" R3 ~4 b& y7 v 10, DAC_ALIGN_12B_R); 47 }! W& G; C. B3 g' T& T+ ^ 48 if (t == 10 ) /* 定时时间到了 */ 49 {) e% M2 M4 m5 b" @ 50 LED0_TOGGLE(); /* LED0闪烁 */ 51 t = 0;& m" ?1 g' k) L0 T: e- ^" L 52 } 53 HAL_Delay(10); /* 延时10毫秒 */ 54 }+ |, `# P0 R1 @ 55 /* USER CODE END 3 *// o, s. I# Z' o1 A; k 56 }0 q6 i2 `/ |) u3 z% i 1 2 32 R# ~( F2 A" O; d. U4 ^! v2 ^ 4) C- ]" w1 ?: F' S 5 6. q; {2 E/ L# K; w I 7! `: @4 R3 W& |% a! W6 d 8 9 10 110 y9 Q; @) m/ R' m( z5 O2 w/ i 12 13( b3 k/ v6 E$ [% K' R4 {- Q 14 154 G8 h- q6 c4 }7 p 16$ |/ s7 R: _( V# [ 17 18 19 20 21. i q1 n1 w/ N2 f- B" ]: X' M 229 E2 Z4 f4 C2 I 23 24 25 26 27 28 29* Y/ z& `- o: v$ L7 T 30! i( I% T5 M4 x0 F" H 31 32) q, ]- I9 ]4 Y" X 33 34 35 36: ` M4 ~: [6 o: ^ 37+ H) h: ?' ~" Z4 ] 384 T% F3 ?8 V# r+ I; H$ p. u' U 39 40% p$ I2 T) G+ [# Z9 f 412 B3 k- P$ H0 E* P$ u0 t. o 42% x; M3 ~1 _1 ~3 c0 z% S 43 @5 u" p6 f9 h 44 45/ x6 T8 y* J6 r% M# n) q( ` 46% h; g n1 @- i' \' [, [4 C 47& z7 J( l& X2 a( c. N; J1 ? 48 X/ S( J k9 |# A# j 49 504 g$ O* W# V1 J9 g( ] 51 52 53, S* B( V/ N, L 54 55/ V1 t9 x5 g* G8 X* C2 q 56 57 58 59 T; _ k3 u1 H/ a T5 n) B! R 前面我们分析过,定时器7产生的触发信号频率为500KHz,如果设置采样点的个数,那么得到不同频率的正弦波。 第23行,DMA初始化,实验中我们需要用DMA将存在发送数据缓冲区g_dac_sin_buf[4096]里的数据发送到DAC_DHR12R1寄存器中,使用DMA传输数据可以大大提高CPU的利用率。 第24行,设置产生正弦波序列,这里取100个采集点,所得波形是500KHz/100=5KHz; 第26行,启动DAC转换和DMA传输。当进入main函数后,会得到一个约5KHz的正弦波;" _0 Q4 b& `, Y$ F 第27行,启动定时器7; 第34~47行,以上代码中,先设置产生正弦波的序列,然后再调用HAL_DAC_Stop_DMA函数将DAC转换以及DMA传输关闭,避免之前的传输干扰,然后再调用HAL_DAC_Start_DMA函数重新开启DAC转换以及DMA传输。 第34~40行,若KEY0按下,则取100个采集点,得到的正弦波频率约5KHz;! G, |5 \% e0 @& }5 \4 ^ 第41~47行,若KEY1按下,则取10个采集点,得到的正弦波频率约为50KHz。+ b( x1 Y# }" A 1 2+ M6 Y) A, }) [) Y1 {5 e) y2 W 3/ V7 n$ y0 U* O) b; J 4& F% C- S3 J4 W' V# S5 [ 5 e7 G; T T$ A( k, B 6 7 8' U: h/ q Z L+ N9 ? 24.4.6 编译和测试 编译无报错后,进入Debug模式,测试PA4引脚输出的波形。/ R8 B( _) \; [9 [! [ 1)进入Debug模式运行程序后的一个波形如下,此波形频率约为5KHz:" M# o' A1 i+ A0 F' B7 o1 p 5 l. i# C9 |8 w& c 图24.4.6. 1进入Debug模式时的第一个波形 2)KEY0按下后的波形得到频率约为5KHz的正弦波形:1 W- {! ~4 f/ j( ?- z / E/ l( l5 f+ ] ) c+ a d- G4 h; K( c8 Q g 图24.4.6. 2 KEY0按下后的波形 3)KEY1按下后得到频率约为50KHz的正弦波形: 图24.4.6. 3 KEY1按下后的波形* k9 A' E( F0 z- A( i1 n0 J/ Y 经过验证可以发现,取的点数越多,波形就越接近正弦波。# c3 X$ t: s& z7 { ———————————————— 版权声明:正点原子; f. s$ o j6 z }8 H1 J w 2 Z5 P$ Y: z0 F2 f 9 F7 ~0 a! D- _5 F5 D0 P) B0 c: e |
扩展STM32微处理器,实现高性价比、低功耗和灵活性能
STM32MP1 自举程序编程:USB DFU/USART 协议全解析
兔哥心中的ST中文论坛
我心中的ST中文论坛
STM32 开发者指南:ST.com 全新 MCU 产品阵容视觉布局深度解析
深度拆解 STM32MP1 Boot 链路
STM32串口自动识别波特率的原理
STM32MP135 MP157 MP257 CoreMark移植&&跑分对比&&多核心负载不均衡问题排查与解决
STM32--STM32 微控制器详解
为什么学了几天STM32一脸茫然?
微信公众号
手机版