
DAC实验 前面章节我们学习了ADC实验,本章,我们将介绍STM32MP157的DAC(Digital -to- analog converters,数模转换器)功能。我们将通过三个实验来学习DAC,分别是DAC输出实验、DAC输出三角波实验和DAC输出正弦波实验。 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 个),也就越能反映出输出电压的细微变化,分辨能力就越高。 (2)建立时间 建立时间是描述DAC转换速度快慢的参数,定义为从输入数字量变化到输出(DAC_OUT)达到终值误差值±1LSB时所需的时间。从参考手册上可以找到STM32MP157的建立时间tSETTLING的典型值是2us。 (3)基准电压 基准电压也叫参考电压,也就是当做参照作用。我们测试外部电压时会以基准电压作为参考,先把基准电压分成多少分(根据分辨率来分),然后再和被测电压进行比较,然后得到比较的结果,这样就能测试出输入电压是多少了。 24.1.1 DAC特性 STM32MP157的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,可以在每个DAC输出通道上进行单独的校准,每个通道都有单独的转换器,DAC输出通道支持低功耗模式。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压VREF+(和ADC共用)以获得更精确的转换结果。当DAC输出与焊盘断开并连接至片上外设时,DAC_OUTx引脚可用作通用输入/输出引脚(GPIO)。 STM32MP157的DAC模块主要特点有: ① 1个DAC转换器,每个DAC最多2个输出通道 ; ② 12位模式下数据左对齐或者右对齐 ; ③ 同步更新功能 ; ④ 噪声波形生成 ; ⑤ 三角波形生成 ; ⑥ 双DAC通道同时或者分别转换; ⑦ 每个通道都有DMA功能; ⑧ 通过外部触发信号进行转换; ⑨ 每路 DAC 输出均可与 DAC_OUTx 输出引脚断开连接; ⑩ DAC 输出可与片上外设连接; ⑪ 可在停止模式下通过采样和保持模式实现低功耗运行; ⑫ 来自VREF+引脚的输入参考电压。 DAC通道框图如下图所示: 24.1.2 DAC框图 ![]() 图23.1.2. 5 DAC通道框图 ADC框图中的输入/输出引脚信息如下表所示: ![]() 表23.1.2. 2 DAC输入/输出引脚 除了上表列出的输入/输出引脚,DAC通道框图还有一些内部输入/输出信号,具体如下表所示: ![]() 表23.1.2. 3 DAC内部输入/输出信号 关于DAC框图的内容,我们做如下讲解: 1.DAC的输入/输出引脚以及内部信号 图中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。 要注意的是,DAC只能在VDDA高于或等于1.8V时使用,使用内部参考电压VREF+时要求VDDA高于或等于VERF+++0.3V。 DAC_OUT1/2就是DAC的两个输出通道了(对应PA4和PA5引脚): ![]() 图23.1.2. 6DAC的两个输出通道引脚 注意:表中的dac_pclk即DAC的时钟源,转换和寄存器访问都是靠这个时钟,该时钟来自APB1,最大可以配置为104.5Mhz。 从通道框图可以看出,DAC输出是受DAC_DORx寄存器直接控制的,但是我们不能直接往DAC_DORx寄存器写入数据,而是通过DAC_DHRx间接的传给DAC_DORx寄存器,实现对DAC输出的控制。 2. DAC通道使能/转换速度和输出电压 (1)DAC通道使能 将 DAC_CR 寄存器中的相应 ENx位置1,即可使能对应DAC 通道。经过一段启动时间 tWAKEUP后,DAC 通道被真正使能。从数据手册查询,tWAKEUP 的典型值为2us或者5 us: ![]() 图23.1.2. 7 tWAKEUP 时间 一旦使能DAC通道,相应的GPIO引脚(PA4和PA5)就会自动与DAC的模拟输出(DAC_OUTx)相连,为了避免寄生的干扰和额外的功耗,引脚PA4和PA5应当设置成模拟输入。 (2)DAC的转换速度 DAC的转换速度最快是1MSPS: ![]() 图23.1.2. 8 DAC的转换速率 (3)DAC的输出电压 DAC就是数字量转换为模拟量,数字输入经过DAC被线性地转换为模拟电压输出。当DAC的参考电压为VREF+的时候,DAC的输出电压是线性的从0~VREF+,12位模式下DAC输出电压与VREF +以及DORx的计算公式如下: DACx输出电压= VREF *(DORx/4096) 3. DAC的8/12位数据格式 前面我们提到,STM32MP157的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。DAC单通道模式下的数据寄存器对齐方式,总共有3种情况,如下图所示: ![]() 图23.1.2. 9 DAC单通道模式下的数据寄存器对齐方式 ①8位数据右对齐:用户将数据写入DAC_DHR8Rx[7:0]位(实际存入DAC_DHRx寄存器的DHRx[11:4]位,后面的描述中我们只写DHRx[11:4]位)。 ②12位数据左对齐:用户将数据写入DAC_DHR12Lx[15:4]位(实际存入DHRx[11:0]位)。 ③12位数据右对齐:用户将数据写入DAC_DHR12Rx[11:0]位(实际存入DHRx[11:0]位)。 我们本章实验中使用的都是单通道模式下的DAC通道1,采用12位右对齐格式,所以采用第③种情况。另外DAC还具有双通道转换功能。对于 DAC 双通道(可用时),也有三种可能的方式,如下图所示: ![]() 图23.1.2. 10 DAC双通道模式下的数据寄存器对齐方式 ①8位数据右对齐:用户将DAC通道1的数据写入DAC_DHR8RD[7:0]位(DHR1 [11:4]位),将DAC通道2的数据写入DAC_DHR8RD[15:8]位(DHR2 [11:4]位)。 ②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]位)。 4. DAC的触发选择 写入到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)不使用触发 TENx为0时不使用触发,DAC通道x的触发被禁用,写入到DAC_DHRx寄存器的数据在一个APB1时钟后自动传至寄存器DAC_DORx中。不使用触发转换的时间框图如下图所示,dac_pclk的时钟其实也就是APB1总线时钟: ![]() 图23.1.2. 11 TEN=0时DAC模块转换时间框图 (2)使用触发 TENx为1时,表示使能触发,这里分为软件触发和硬件触发。DAC_CR寄存器的TSELx[3:0]为0000时表示选择软件触发,DAC_CR寄存器的TSELx[3:0]不为0000时(具体设置什么可以根据此位的设置来),表示选择硬件触发。 软件触发 如果选择软件触发,一旦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]位。 硬件触发 如果选中硬件触发,每次DAC接口检测到触发信号时(定时器TRGO输出,或者外部中断线9的上升沿),最新存放在DAC_DHRx的值会被传入DAC_DORx中,发生触发后再经过3个dac_pclk周期(3个APB1时钟周期)后,DAC_DORx寄存器将会得到更新。一旦DAC_DORx寄存器的值更新后,再经过时间tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。 如果使用触发(TEN=1),可通过TSELx[3:0]控制位来决定选择16个触发事件中的某一个来触发DAC转换。这16个触发事件的部分事件如下表所示: ![]() 其它 保留 表23.1.2. 4 DAC触发选择 采样和保持模块(低功耗)及其关联寄存器可在停止模式下使用 LSI时钟源(dac_hold_ck)运行。 5. DAC的DMA请求 每个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 传输。 ![]() 表23.1.2. 5 DAC中断 24.2 DAC输出实验 本实验我们来学习DAC输出实验。 本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 17-1 DAC_OUT1。 24.2.1 DAC寄存器 下面,我们介绍要实现DAC的通道1输出,需要用到的一些DAC寄存器。 1.DAC控制寄存器(DAC_CR) DAC控制寄存器描述如下图所示: ![]() 图23.2.1. 1 DAC_CR寄存器 DAC_CR的低16位用于控制通道1,而高16位用于控制通道2,我们这里仅列出本实验需要设置的一些位: EN1位:用于DAC通道1的使能,我们需要用到DAC通道1的输出,该位必须设置为1。 TEN1位:用于DAC通道1的触发使能,我们设置该位为0,不使用硬件触发。写入DAC_DHR1的值会在1个APB1周期后传送到DAC_DOR1,然后输出到PA4口上。 TSEL[3:0]位:用于选择DAC通道1的触发方式,本实验设置为0,使用软件触发。 WAVE[1:0]位:用于控制DAC通道1的噪声/波形输出功能,默认设置为00,不使能噪声/波形输出。 DMAEN1位:用于控制DAC通道1的DMA使能,本实验不使用DMA,设置该位为0即可。 CEN1位:用于控制DAC通道1的输出缓冲校准使能,本章不使用校准功能(默认有一个出厂校准值,我们使用默认的校准值即可),设置该位为0即可。 2. DAC模式控制寄存器(DAC_ MCR) DAC模式控制寄存器描述如下图所示: ![]() 图23.2.1. 2 DAC_ MCR寄存器 该寄存器我们只关心MODE1[2:0],这三个位用于设置DAC通道1的工作模式。本章使用普通模式,且使用输出缓冲,设置MODE1[2:0]=0即可。MODE2[2:0] 设置通道2的工作模式,本实验没用到。 3. DAC通道1 12位右对齐数据保持寄存器(DAC_ DHR12R1) DAC通道1 12位右对齐数据保持寄存器描述如图下图所示: ![]() 图23.2.1. 3 DAC_ DHR12R1寄存器 该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果 24.2.2 DAC的HAL库驱动 DAC在HAL库中的驱动代码在stm32mp1xx_hal_dac.c和stm32mp1xx_hal_dac_ex.c文件(及其头文件)中,可以翻看这两个文件来了解。 1.HAL_DAC_Init函数 DAC的初始化函数,其声明如下: HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac); 函数描述: 用于初始化DAC。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量,其定义如下:
从该结构体看到该函数并没有设置任何DAC相关寄存器,即没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC,为后面HAL库操作DAC做好准备。 函数返回值: HAL_StatusTypeDef枚举类型的值。 注意事项: DAC的MSP初始化函数HAL_DAC_MspInit,该函数声明如下: void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac); 2. HAL_DAC_ConfigChannel函数 DAC 的通道参数初始化函数,其声明如下: HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel); 函数描述: 该函数用来配置DAC通道的触发类型以及输出缓冲。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2是DAC_ChannelConfTypeDef结构体类型指针变量,其定义如下:
形参3用于选择要配置的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 函数返回值: HAL_StatusTypeDef枚举类型的值。 3. HAL_DAC_Start函数 使能启动DAC转换通道函数,其声明如下: HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel); 函数描述: 使能启动DAC转换通道。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 函数返回值: HAL_StatusTypeDef枚举类型的值。 4. HAL_DAC_SetValue函数 DAC的通道输出值函数,其声明如下: HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data); 函数描述: 配置DAC的通道输出值。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要输出的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 形参3用于指定数据对齐方式。 形参4设置要加载到选定数据保存寄存器中的数据。 函数返回值: HAL_StatusTypeDef枚举类型的值。 5. HAL_DAC_GetValue函数 DAC读取通道输出值函数,其声明如下: uint32_t HAL_DAC_GetValue(DAC_HandleTypeDef *hdac, uint32_t Channel); 函数描述: 获取所选DAC通道的最后一个数据输出值。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要读取的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 函数返回值: 获取到的输出值。 24.2.3 硬件设计 1.例程功能 使用KEY0/KEY1两个按键,控制STM32MP157内部DAC的通道1输出电压大小,然后通过ADC1的通道19采集DAC输出的电压,并通过串口将ADC采集到的电压值以及DAC的设定输出电压值等信息打印出来。实验中,LED0闪烁,提示程序在运行。 实验时,请用跳线帽将PA4和PA5接在一起: ![]() 图23.2.3. 1 硬件连接 本节实验ADC的配置和相关代码部分和ADC实验章节的 单通道ADC采集实验 部分一样。 2. 硬件资源 1)LED灯:LED0 2)按键KEY0和KEY1 3)串口4 4)ADC1的通道19引脚(PA5) 5)DAC的通道1 LED0 UART4_TX UART4_RX ADC1_INP19 DAC_CHANNEL_1 KEY0 KEY1 PI0 PG11 PB2 PA5 PA4 PG3 PH7 表23.2.3. 1硬件资源 3. 原理图 ADC和DAC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。 ![]() 图23.2.3. 2 原理图部分 24.2.4 软件设计 1.新建和配置工程 (1)配置LED0、KEY0、KEY1和UART4 新建工程DAC_OUT1,本节实验我们会用到UART4发送数据,还会使用到按键KEY1、KEY0以及LED0,所以我们这里按照前面的实验章节配置LED0、KEY0、KEY1。UART4的配置我们这里就不再赘述了,请参考前面串口通信实验章节的来配置。 ![]() 图23.2.4. 1 按键和LED的引脚部分配置 ![]() 图23.2.4. 2 UART4引脚部分配置 LED0和按键以及UART4的代码我们本节实验就不讲解了,大家可以参考本实验工程里的代码,也可以参考前面相关实验章节部分。 (2)配置ADC ADC1的配置可参考前面 单通道ADC采集实验 部分。选择ADC1的通道19: ![]() 图23.2.4. 3选择ADC1的通道19 ADC的配置参数我们前面ADC实验章节已经做了详细讲解,这里就不再说明,配置如下: ![]() 图23.2.4. 4配置ADC1的参数 (3)配置DAC DAC配置如下,DAC的每路输出均可与DAC_OUTx输出引脚断开连接,然后可与片上外设连接。这里我们可以配置DAC输出连接到外部引脚DAC_OUTx。 DAC参数配置部分: Output Buffer配置使能; Trigger配置触发源,可以配置软件触发和硬件触发以及不触发,我们配置不触发; DAC High Frequency配置DAC是否以指定频率接口模式工作,可以开启或者关闭高频接口模式,或者设置高频接口模式为自动模式。 User Trimming设置DAC的校准方式,采用出厂模式还是用户模式。校准的目的是为了减少误差,这里我们就默认使用出厂模式; Sample And Hold用于设置是否使能低功耗模式,即采样和保持模式,这里我们就不选择了。 ![]() 图23.2.4. 5配置DAC的输出通道1 (4)UART相关的参数配置 UART4的参数配置以及中断配置请参考前面串口通信实验。 (5)时钟配置 这里配置MCU的时钟为209MHz,使用外部时钟HSE,这部分配置可以参考前面实验章节: ![]() 图23.2.4. 6时钟树配置 ADC1的时钟使用PER,其中PER时钟源默认使用HSI,即为64MHz。上面ADC参数配置中,我们配置分频系数为2,所以实际ADC的时钟是32MHz。DAC因为接在APB1上,DAC的时钟是104.5MHz。 (6)配置生成独立的.c和.h文件 ![]() 图23.2.4. 7配置生成独立的文件 2. 生成工程 保存配置,生成工程后,将前面按键输入实验章节使用的BSP文件夹拷贝到工程的Src文件夹下,本节我们要使用LED0、KEY0和KEY1的驱动,所以将LED1和WKUP相关的代码注释掉即可。 ![]() 图23.2.4. 8生成工程 3. 初始化代码分析 ADC的初始化代码我们在前面章节的实验已经介绍过,这里就不再介绍了。DAC的初始化代码在dac.c文件及其头文件中,代码附上了详细的注释,如下:
4.添加用户代码 (1)修改adc.c文件 按照前面 单通道ADC采集实验 章节,在adc.c中添加如下代码:
adc.h文件添加如下代码:
(2)修改main.c文件 在main.c文件添加如下代码,当KEY0按下时,DAC_DHR12R1寄存器的值为2048,然后ADC1的通道19获取DAC1_OUT1的值,根据此值计算出对应的电压值。
第37行,先启动DAC,DAC启动后才可以进行后续操作。 第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。 第56~71行: 如果是KEY0或者KEY1按下了,或者是10s的时间到了,UART4会打印DAC输出的值和计算出的电压值,以及ADC采集到的转换值以及计算出的电压值。 当没有按键按下时,UART4打印的这些值都应该为0,因为第71行使用HAL_DAC_SetValue函数对DAC_DHR12R1寄存器进行了清零操作。实际的实验中可能会有一些误差,会出现一些非0的情况,不过数值比较小,我们可以认为其值为0。 第72行,LED0闪烁。 第75行,延时1秒,当然也可以将此延迟时间改短一些。 24.2.5 编译和测试 程序中使用浮点运算,所以要设置工程属性,支持浮点打印。 ![]() 图23.2.5. 1配置工程支持浮点打印 程序测试结果如下,和上面的计算结果一致: ![]() 图23.2.4. 9测试结果 24.3 DAC输出三角波实验 本实验我们来学习使用如何让DAC输出三角波,DAC初始化部分还是使用上一章DAC输出实验 章节的,所以操作本实验的前提是先学习DAC输出实验 章节的内容。 本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 17-2 DAC_Triangle_Wave。 24.3.1 生成三角波原理 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]配置的振幅值,计数器将递减至零,然后再递增,以此类推,这个过程如下图所示: ![]() 图24.3.1. 1三角波示意图 使用触发方式,我们可以选择定时器作为触发源,使用定时器来触发。可以设置定时器每周期计时结束时DAC_DHRx寄存器的内容加1,当使用12位模式时,DAC_DHRx寄存器的最高赋值为4095,所以在配置定时器的时候注意此范围。 ![]() 图24.3.1. 2可选的外部触发源 2. 不使用触发方式 当然,我们也可以不使用触发的方式来生成三角波,我们通过每隔一定时间dt往DAC通道12位右对齐数据保持寄存器 (DAC_DHR12R1)中写入一个值,每次写入的值递增incval,总共递增samples/2次,然后再将DAC_DHR12R1的值每隔dt递减incval,总共递减samples/2次,如此反复,DAC_DHR12R1的值传输给DAC_DORx,最后由DAC_OUTx输出转换的电压值,这样就类似一个三角波了。这里,dt我们称为每个采样点的延时时间,samples我们称为采样率,我们用如下一张图来描述: ![]() 图24.3.1. 3 三角波示意图 其中波形的振幅对应DAC_DHR12R1的值最大为4095(DAC_DHR12R1是12位的寄存器,最大值为4095),实验中的dt值尽量小,这样得出的波形就越接近三角形,上图中为了讲解,画的dt的间隔比较大。图中显示的是一个三角波的过程,三角的斜边的某个点的值是DAC_OUTx输出的电压值,是由DAC_DHR12R1的值转换得来的,根据勾股定理,三角斜边值大于直角边的值,所以(DAC_DHR12R1+1)>(samples/2),此三角波的周期T=samples*dt,那么此三角波的频率就是1/T。 本实验我们就采用以上第二种方法,实验中,我们通过修改dt的值和samples的值来实现输出两种波形: 1)设置dt为5us,samples为2000,则输出的三角波频率为100Hz; 2)设置dt为500us,samples为20,则输出的三角波也是100Hz。 24.3.2 DAC寄存器 1.DAC_CR寄存器 本实验用到的DAC_DHRx和DAC_DORx寄存器在上一章节已经介绍了,这里我们介绍DAC_CR寄存器和三角波设置有关的位,如下: ![]() 图24.3.2. 1 DAC_CR寄存器 如果使用触发方式,对应位设置如下: TENx位用于使能DAC的通道,将该位置1则使能对应的通道; WAVEx[1:0]用于使能或者禁止生成波,如果要生成三角波,此位设置为1x; DAC_CR寄存器的MAMP1[3:0]和MAMP2[3:0]分别对应的是的是通道1和通道2的掩码/振幅选择器配置项,这些位由软件写入,用于在生成噪声波模式下选择掩码,或者在生成三角波模式下选择振幅,MAMP1[3:0]配置如下: 0000:不屏蔽 LFSR 的位 0/三角波振幅等于 1 0001:不屏蔽 LFSR 的位 [1:0]/三角波振幅等于 3 0010:不屏蔽 LFSR 的位 [2:0]/三角波振幅等于 7 0011:不屏蔽 LFSR 的位 [3:0]/三角波振幅等于 15 0100:不屏蔽 LFSR 的位 [4:0]/三角波振幅等于 31 0101:不屏蔽 LFSR 的位 [5:0]/三角波振幅等于 63 0110:不屏蔽 LFSR 的位 [6:0]/三角波振幅等于 127 0111:不屏蔽 LFSR 的位 [7:0]/三角波振幅等于 255 1000:不屏蔽 LFSR 的位 [8:0]/三角波振幅等于 511 1001:不屏蔽 LFSR 的位 [9:0]/三角波振幅等于 1023 1010:不屏蔽 LFSR 的位 [10:0]/三角波振幅等于 2047 ≥ 1011:不屏蔽 LFSR 的位 [11:0]/三角波振幅等于 4095 WAVE1[1:0]和WAVE2[1:0]用于使能DAC通道1和通道2的噪声/三角波生成,这些位由软件置 1 或清零: 00:禁止生成波 01:使能生成噪声波 1x:使能生成三角波 2. 其它寄存器 关于本节实验用到的DAC的HAL库驱动,我们前面的实验章节已经介绍过,这里就不再重复介绍了。 24.3.3 DAC的HAL库驱动 本实验中用到的HAL库驱动前面的实验代码都有介绍到,这里就不再赘述。 24.3.4 硬件设计 1.例程功能 使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察波形。LED0闪烁,提示程序运行。 我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4的引脚排针已经引出,硬件连接如下图所示: ![]() 图24.3.4. 1硬件连接示意图 2. 硬件资源 1)LED灯:LED0 2)按键KEY0和KEY1 3)DAC的通道1 LED0 DAC_CHANNEL_1 KEY0 KEY1 PI0 PA4 PG3 PH7 表24.3.4. 1硬件资源 3. 原理图 DAC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。 ![]() 图24.3.4. 2原理图部分 24.3.5 软件设计 1.创建和配置工程 本实验使用到DAC,可以直接在上一章节的实验的基础上操作。为了方便,我们新建了一个工程DAC_Triangle_Wave,DAC工程的配置和上一章节的一样,这里我们没有用到ADC,关于ADC部分我们就不配置了。 图24.3.5. 1DAC配置 关于本节实验用到的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生成工程 3. 添加用户代码 (1)添加三角波生成函数 如下,我们在dac.c文件中添加如下代码:
以上代码中,DAC_DHR12R1的值一开始是0(DAC输出的三角波的基准电压为0),然后每隔一段时间通过给DAC_DHR12R1递增incval,当递减samples/2次时,给DAC_DHR12R1递减incval,当递减samples/2次时,DAC_DHR12R1再做递增运算,如此反复,最后就得到一个三角波。 第14~34行,我们通过直接操作寄存器的方法,将递增的值写入到DHR12R1寄存器中。 第37~61行的方法,我们通过调用HAL库的API函数HAL_DAC_SetValue来设置DHR12R1寄存器的值。两种方法等效。 (2)修改main.c文件 main.c文件部分代码如下,其中红色字体之间的代码是我们手动添加的:
第22行,我们要使用us延时函数,所以要初始化延时函数。 第24行,自动生成的DAC初始化代码并没有开启DAC,这里我们启动DAC通道,启动后DAC通道才可以工作。 第34~43行,如果是KEY0按下,则DAC以5us的采样时间间隔进行采样,采样点个数为2000个。如果是KEY1按下,则DAC以500us时间的间隔进行采样,采样点的个数是20个。两种采样参数配置,最后得出的波形约为100Hz。 24.3.6 编译和测试 编译后进行测试,实验中我们使用示波器测试PA4引脚输出的波形。如下所示,两种波形的频率接近100Hz。 1)当KEY0按下时: ![]() 图24.3.6. 1三角波1 2)当KEY1按下时: ![]() 图24.3.6. 2三角2 ![]() 24.4 DAC输出正弦波实验 本实验我们来学习使用如何让DAC的通道输出正弦波,其中DAC使用12位右对齐模式,也就是使用到DAC_DHR12Rx [11:0] 位。实验将用定时器7来触发DAC进行转换输出正弦波,并以DMA传输数据。 本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 17-3 DAC_Sine_Wave。 24.4.1 DAC输出正弦波原理 1.y=sinx 通过前面输出三角波实验,本节的正弦波实验更加容易理解。正弦函数为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 以上的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)函数 分析到这里,0≤x≤2π,放大倍数maxval 最大为2048,装入DAC_DHRx的值0≤y<4096,如果想显示正弦波,那么装入DAC_DHR12Rx的值就按照上述的波形图变化。 实验中,我们通过将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中,我们选择其中的一些点来实验,当取的点数越多时,输出的波形越接近正弦波。 24.4.2 DAC寄存器 本实验用到的寄存器在前面的实验都有介绍,这里就不再赘述。 24.4.3 DAC的HAL库驱动 本实验用到的HAL库API函数前面介绍过一部分,下面我们将介绍本实验用到且没有介绍过的几个API函数。 1.HAL_DAC_Start_DMA函数 使能DAC并开始通道转换,并使用DMA方式传输函数,其声明如下: HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, uint32_t Alignment); 函数描述: 用于启动DAC通道转换,并使用DMA的方式进行传输。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 形参3是使用DAC输出数据缓冲区的指针。 形参4是DAC输出数据的长度。 形参5是指定DAC通道的数据对齐方式,有:DAC_ALIGN_8B_R(8位右对齐)、DAC_ALIGN_12B_L(12位左对齐)和DAC_ALIGN_12B_R(12位右对齐)三种方式。 函数返回值: HAL_StatusTypeDef枚举类型的值。 2.HAL_DAC_Stop_DMA函数 停止DAC的DMA方式函数,其声明如下: HAL_StatusTypeDef HAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel); 函数描述: 用于停止DAC通道转换和DMA传输。 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 函数返回值: HAL_StatusTypeDef枚举类型的值。 3.HAL_TIMEx_MasterConfigSynchronization函数 配置主模式下的定时器触发输出选择函数,其声明如下: HAL_StatusTypeDef HAL_TIMEx_MasterConfigSynchronization( TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef *sMasterConfig); 函数描述: 用于配置主模式下的定时器触发输出选择。 函数形参: 形参1是TIM_HandleTypeDef结构体类型指针变量。 形参2是TIM_MasterConfigTypeDef结构体类型指针变量,用于配置定时器工作在主/从模式,以及触发输出(TRGO和TRGO2)的选择。 函数返回值: HAL_StatusTypeDef枚举类型的值。 24.4.4 硬件设计 4.例程功能 使用TIM7触发DAC输出正弦波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种正弦波,实验中需要通过示波器接PA4进行观察,实验中通过LED0闪烁来提示程序在正常运行中。实验中,我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4的引脚排针已经引出,硬件连接如下图所示: ![]() 图24.4.4. 1硬件连接示意图 本章节实验我们采用硬件触发(TIM7上溢事件)的方式来触发,每当DAC获取一个触发事件,DAC_DORx就会加一个固定的值outdata,此值通过DMA方式来传输。 2. 硬件资源 1)LED灯:LED0 2)按键KEY0和KEY1 3)DAC的通道1 4)DMA2(使用到DMA2数据流6DMA2_Stream6) 5)TIM7 LED0 DAC_CHANNEL_1 KEY0 KEY1 PI0 PA4 PG3 PH7 表24.4.4. 1硬件资源 3. 原理图 TIM7、DMA和DAC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。 ![]() 图24.4.4. 2原理图部分 24.4.5 软件设计 1.新建和配置工程 (1)配置DAC 新建工程DAC_Sine_Wave,本实验用到的LED0、KEY0和KEY1可以按照前面的实验来配置。下面我们先配置DAC: ![]() 图24.4.5. 1配置DAC 以上配置中,注意Trigger选项我们配置为使用片上定时器的内部信号来触发DAC通道,这里选择定时器7。注意的是,DAC这里没有DMA的配置选项,前面实验章节会有DMA配置项,而且以前的STM32CubeMX软件中,DAC也是有DMA配置选项的,这里没有,不知是不是软件的bug?本篇教程使用的STM32CubeIDE是V1.4.0版本,更新的高版本笔者还未去验证,既然这里没有DMA选项,那么后面我们就手动添加DMA相关部分的代码吧。 (2)配置TIM7 图24.4.5. 2配置TIM7 以上配置中,TIM7的分频值为209-1,向上计数模式,计数周期(TIMx_ARR的值)为1,自动重载使能,触发事件为上溢更新事件。也就是计数器每计数一次就触发DAC通道,那么触发信号的频率怎么计算呢?还记得我们前面学习基本定时器时,基本定时器的溢出时间计算方法: 这里,为209MHz,=1,=209-1,所以: =500KHz 也就是说,由定时器触发的触发信号频率为500KHz。 (3)时钟配置 关于本节实验用到的LED0、KEY1和KEY0的配置清参考前面相关的实验章节部分。本节使用HSE作为锁相环PLL3的输入时钟源,我们配置APB1为104.5MHz,关于时钟配置也请参考前面实验有关章节部分。 2. 生成工程 配置好工程后,保存修改,生成工程。本实验我们会用到LED0、KEY0和KEY2,所以将上一章节实验的BSP文件夹直接拷贝到本节实验工程的Src目录下,这样我们就可以直接使用里边的驱动程序: ![]() 图24.4.5. 3生成的工程 3. 添加用户代码 TIM7和DAC的初始化代码我们前面已经分析过了,这里就不再分析了。 (1)添加DMA初始化代码 由于本实验DAC转换需要DMA,而前面的STM32CubeMX配置中没有DMA配置选项,那么我们就手动添加DMA相关的初始化代码,在dac.c文件中直接添加DMA的初始化代码。 DMA_HandleTypeDef g_dma_dac_handle; /* DMA句柄 */
(2)添加产生正弦波序列函数 在dac.c文件中添加如下代码,dac_creat_sin_buf函数用于产生正弦波需要的序列,也就是前面我们说的要写入DAC_DHR12R1的值,将这些值存入事先定义好的一个Buffer g_dac_sin_buf[4095]中。前面我们已经分析了产生正弦波的原理,这里的代码也就是按照前面的原理分析来实现的。
在dac.h文件中添加如下代码: 1 /* USER CODE BEGIN Private defines */ void dma_init(void); void dac_creat_sin_buf(uint16_t maxval, uint16_t samples); uint16_t g_dac_sin_buf[4096]; /* 发送数据缓冲区 */ /* USER CODE END Private defines */ 1 2 3 4 5 (3)main.c文件实现代码 main.c文件的代码如下,我们在标红的字体之间手动添加代码 1 #include "main.h" 2 #include "dac.h" 3 #include "tim.h" 4 #include "gpio.h" 5 /* USER CODE BEGIN Includes */ 6 #include "./BSP/Include/led.h" 7 #include "./BSP/Include/key.h" 8 /* USER CODE END Includes */ 9 void SystemClock_Config(void); 10 int main(void) 11 { 12 HAL_Init(); /* HAL库初始化 */ 13 if(IS_ENGINEERING_BOOT_MODE()) 14 { 15 SystemClock_Config(); /* 系统时钟配置 */ 16 } 17 MX_GPIO_Init(); /* GPIO初始化 */ 18 MX_DAC1_Init(); /* DAC初始化 */ 19 MX_TIM7_Init(); /* 定时器7初始化 */ 20 /* USER CODE BEGIN 2 */ 21 uint8_t t = 0; 22 uint8_t key; 23 dma_init(); /* DMA初始化 */ 24 dac_creat_sin_buf(2048, 100); /* 产生正弦波序列 */ 25 /* 启动DAC转换,并使用DMA传输 */ 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) 30 { 31 /* USER CODE BEGIN 3 */ 32 t++; 33 key = key_scan(0); /* 按键扫描 */ 34 if (key == KEY0_PRES) /* 高采样率,约1Khz波形 */ 35 { 36 dac_creat_sin_buf(2048, 100); /* 产生正弦波序列 */ 37 HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); /* 先停止之前的传输 */ 38 /* 500Khz触发频率, 100个点, 得到最高5KHz的正弦波. */ 39 HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t *)g_dac_sin_buf,\ 100, DAC_ALIGN_12B_R); 40 } 41 else if (key == KEY1_PRES) /* 低采样率 , 约1Khz波形 */ 42 { 43 dac_creat_sin_buf(2048, 10); /* 产生正弦波序列 */ 44 HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); /* 先停止之前的传输 */ 45 /* 500Khz触发频率, 10个点, 可以得到最高50KHz的正弦波. */ 46 HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*)g_dac_sin_buf,\ 10, DAC_ALIGN_12B_R); 47 } 48 if (t == 10 ) /* 定时时间到了 */ 49 { 50 LED0_TOGGLE(); /* LED0闪烁 */ 51 t = 0; 52 } 53 HAL_Delay(10); /* 延时10毫秒 */ 54 } 55 /* USER CODE END 3 */ 56 } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 前面我们分析过,定时器7产生的触发信号频率为500KHz,如果设置采样点的个数,那么得到不同频率的正弦波。 第23行,DMA初始化,实验中我们需要用DMA将存在发送数据缓冲区g_dac_sin_buf[4096]里的数据发送到DAC_DHR12R1寄存器中,使用DMA传输数据可以大大提高CPU的利用率。 第24行,设置产生正弦波序列,这里取100个采集点,所得波形是500KHz/100=5KHz; 第26行,启动DAC转换和DMA传输。当进入main函数后,会得到一个约5KHz的正弦波; 第27行,启动定时器7; 第34~47行,以上代码中,先设置产生正弦波的序列,然后再调用HAL_DAC_Stop_DMA函数将DAC转换以及DMA传输关闭,避免之前的传输干扰,然后再调用HAL_DAC_Start_DMA函数重新开启DAC转换以及DMA传输。 第34~40行,若KEY0按下,则取100个采集点,得到的正弦波频率约5KHz; 第41~47行,若KEY1按下,则取10个采集点,得到的正弦波频率约为50KHz。 1 2 3 4 5 6 7 8 24.4.6 编译和测试 编译无报错后,进入Debug模式,测试PA4引脚输出的波形。 1)进入Debug模式运行程序后的一个波形如下,此波形频率约为5KHz: 图24.4.6. 1进入Debug模式时的第一个波形 2)KEY0按下后的波形得到频率约为5KHz的正弦波形: 图24.4.6. 2 KEY0按下后的波形 3)KEY1按下后得到频率约为50KHz的正弦波形: 图24.4.6. 3 KEY1按下后的波形 经过验证可以发现,取的点数越多,波形就越接近正弦波。 ———————————————— 版权声明:正点原子 |
更新STM32MP135-Openwrt镜像
基于STM32MP1和STM32MP2在嵌入式Linux平台上部署有效的安全保护机制
利用STM32MP1和STM32MP2为嵌入式Linux提供有效的安全措施:供当今决策者参考的3条宝贵经验
STM32MP1 WiFi连接
【STM32MP157】从ST官方例程中分析RPMsg-TTY/SDB核间通信的使用方法
【STM32MPU 安全启动】 TF-A BL2 TrustedBoot原理学习
《STM32MPU安全启动》学**结
《STM32MPU安全启动》学习笔记之optee 如何加载CORTEX-M核和使能校验
《STM32MPU安全启动》学习笔记之TF-A BL2校验optee和uboot的流程以及如何使能
《STM32MPU 安全启动》课程学习心得+开启一扇通往嵌入式系统安全领域深处的大门。