STM32-17-DAC
STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
STM32-13-MPU
STM32-14-FSMC_LCD
STM32-15-DMA
STM32-16-ADC
文章目录
- STM32-17-DAC
- 1. DAC简介
- 2. DAC工作原理
- 3. DAC输出实验
- 4. DAC输出三角波实验
- 5. DAC输出正弦波实验
- 6. PWM DAC实验
STM32-17-DAC
1. DAC简介
-
什么是DAC?
DAC,全称:Digital-to-Analog Converter,指数字/模拟转换器。STM32F103的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压Vref+以获得更精确的转换结果。 -
主要特点:
- 2个DAC转换器,每个转换器对应1个输出通道
- 8位或12位单调输出
- 12位模式下数据左对齐或右对齐
- 同步更新功能
- 噪声/三角波形生成
- 双DAC双通道同时或分别转换
- 每个通道都有DMA功能
-
ADC与DAC的关系

-
DAC的特性参数
-
分辨率:表示模拟电压的最小增量,常用二进制位数表示,比如8/12位等
-
建立时间:表示一个数字量转换为稳定模拟信号所需的时间
-
精度:转换器实际特性曲线与理想特性曲线之间的最大偏差
误差源:比例系统误差、失调误差、非线性误差
原因:元件参数误差、基准电压不稳定、运算放大器零漂等
-
2. DAC工作原理
- DAC框图

引脚信息:

参考电压:

DAC数据格式:

触发源:

关闭触发时(TEN=0)的转换时序图:

DMA请求:

DAC输出电压:

3. DAC输出实验
-
功能:
通过DAC1通道1(PA4)输出预设电压,然后由ADC1通道1 (PA1) 采集,最后显示ADC转换的数字量及换算后的电压值。
-
相关寄存器
DAC控制寄存器(DAC_CR)

DAC_CR寄存器的低16位用于控制通道1,高16位用于控制通道2。
DAC通道1 12位右对齐数据保持寄存器(DAC_DHR12R1)

-
DAC初始化函数
void dac_init(void) {DAC_ChannelConfTypeDef dac_ch_conf;g_dac_handle.Instance = DAC;HAL_DAC_Init(&g_dac_handle);dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1); }初始化DAC句柄
g_dac_handle.Instance = DAC; HAL_DAC_Init(&g_dac_handle);g_dac_handle.Instance = DAC;:将DAC外设的基地址赋给全局DAC句柄的实例成员g_dac_handle.Instance。HAL_DAC_Init(&g_dac_handle);:调用HAL库的初始化函数HAL_DAC_Init,使用DAC句柄g_dac_handle进行初始化。这一步设置了DAC的时钟和一些基本的硬件配置。
配置DAC通道
dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE; dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE; HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;:设置DAC的触发方式为无触发,即DAC输出不依赖于外部事件或定时器。dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;:禁用DAC输出缓冲,这通常用于降低功耗或满足特定应用要求。HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);:调用HAL_DAC_ConfigChannel函数,将配置应用到DAC通道1。这个函数根据提供的配置结构体dac_ch_conf和DAC句柄g_dac_handle,对指定通道(这里是通道1)进行配置。
启动DAC通道
HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1);最后,调用
HAL_DAC_Start函数,启动DAC通道1,使其开始输出模拟信号。 -
DAC MSP初始化函数
void HAL_DAC_MspInit(DAC_HandleTypeDef *hadc) {if(hadc->Instance == DAC){GPIO_InitTypeDef gpio_init_struct;//使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_DAC_CLK_ENABLE();//配置工作模式gpio_init_struct.Pin = GPIO_PIN_4;gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟输入HAL_GPIO_Init(GPIOA, &gpio_init_struct);} } -
设置通道输出电压
void dac_set_voltage(uint16_t vol) {double temp = vol;temp /= 1000; temp = temp * 4096 / 3.3;if(temp >= 4096) temp = 4095;HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp); }电压转换为DAC值
double temp = vol; temp /= 1000; temp = temp * 4096 / 3.3;double temp = vol;:将输入参数vol转换为double类型并赋值给临时变量temp。这样做是为了后续的浮点运算。temp /= 1000;:将电压值从毫伏转换为伏特。temp = temp * 4096 / 3.3;:将电压值转换为DAC寄存器值。这里的4096是12位DAC的最大值(2^12),3.3是参考电压。公式计算方法如下:- 先将电压值转换为0到1之间的比值:
temp / 3.3 - 然后乘以DAC的最大值4096,得到对应的DAC寄存器值。
- 先将电压值转换为0到1之间的比值:
限制最大值
if(temp >= 4096) temp = 4095;因为12位DAC的最大值是4095,所以需要确保计算出的DAC寄存器值不超过4095。如果
temp大于等于4096,则将其限制为4095,以防止超出DAC的范围。设置DAC值
HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);调用
HAL_DAC_SetValue函数,设置DAC通道1的输出值。参数说明如下:&g_dac_handle:DAC句柄,指向全局的DAC配置结构体。DAC_CHANNEL_1:指定DAC的通道1。DAC_ALIGN_12B_R:使用右对齐方式设置12位的DAC值。temp:计算出的DAC寄存器值。
-
主函数
/*确保按键按下*/ void key_led(void) {LED1(0);delay_ms(20);LED1(1); } /*LCD显示函数*/ void lcd_display(void) {lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "ADC TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */ }/*循环内部显示函数*/ void lcd_display_value(void) {static uint16_t adcx;static float temp;adcx = adc_get_result();lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADCC采样后的原始值 */temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */ }/*按键值确认函数*/ void key_value(void) {static int swap = 0;switch(key_scan(0)){case 2: swap += 100;if(swap >= 3300)swap = 0;key_led();break;case 4: swap -= 100;if(swap < 0)swap = 3300;key_led();break;default: break;}dac_set_voltage(swap); }/*主函数*/ int main(void) {HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */adc_init(); /* 初始化ADC3 */dac_init();key_init();lcd_display();while (1){key_value();lcd_display_value();LED0_TOGGLE();delay_ms(250);} }执行逻辑:
- 初始化硬件和外设。
- 显示初始LCD内容。
- 主循环中,持续检测按键输入,更新DAC电压,并刷新LCD显示值。
- 定时切换LED状态。
总结
实现一个简单的ADC采集和DAC输出的系统,通过按键控制DAC输出电压,并在LCD上实时显示ADC采样值和对应的电压值。按键按下时,通过视觉反馈(LED闪烁)确认按键操作,并根据按键输入调整DAC输出。
4. DAC输出三角波实验
-
功能:
使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察。也可以通过usmart调用
dac_triangular_wave函数,来控制输出哪种三角波。LED0闪烁,提示程序运行。 -
输出三角波:

-
输出三角波函数:
void dac_triangular_wave(uint16_t maxval, uint16_t dt, uint16_t samples, uint16_t n) {uint16_t i, j;float incval; // 递增量 float Curval; // 当前值 if(samples > ((maxval + 1) * 2))return ; // 数据不合法 incval = (maxval + 1) / (samples / 2); // 计算递增量 for(j = 0; j < n; j++){ Curval = 0;HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval); // 先输出0 for(i = 0; i < (samples / 2); i++) // 输出上升沿 {Curval += incval; // 新的输出值 HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);delay_us(dt);}for(i = 0; i < (samples / 2); i++) // 输出下降沿 {Curval -= incval; // 新的输出值 HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);delay_us(dt);}} }参数说明:
maxval: 三角波的最大值(峰值)。dt: 每次输出之间的延时,以微秒为单位。samples: 完整波形的样本数。n: 波形重复的次数。
代码分析:
if(samples > ((maxval + 1) * 2)) return; // 数据不合法- 检查
samples是否超过合法范围,如果是,直接返回。
incval = (maxval + 1) / (samples / 2); // 计算递增量- 计算每次递增或递减的量
incval。
for(j = 0; j < n; j++) {Curval = 0;HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval); // 先输出0for(i = 0; i < (samples / 2); i++) // 输出上升沿{Curval += incval; // 新的输出值HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);delay_us(dt);}for(i = 0; i < (samples / 2); i++) // 输出下降沿{Curval -= incval; // 新的输出值HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);delay_us(dt);} }- 外层循环
for(j = 0; j < n; j++)用于生成n次三角波。 - 每次循环开始时,将
Curval设置为0,并通过HAL_DAC_SetValue输出0电压。 - 内层两个
for循环分别生成上升沿和下降沿:- 第一个
for循环从0递增到最大值maxval,每次增加incval,通过HAL_DAC_SetValue更新DAC输出,并延时dt微秒。 - 第二个
for循环从最大值maxval递减到0,每次减少incval,通过HAL_DAC_SetValue更新DAC输出,并延时dt微秒。
- 第一个
功能总结:
该函数生成并输出一个三角波形。通过调整
maxval控制波形的峰值,调整dt控制波形的频率,调整samples控制波形的采样点数,调整n控制波形的重复次数。每个周期的波形由samples/2个上升点和samples/2个下降点构成,每个点之间延时dt微秒。 -
主函数:
while (1){t++;key = key_scan(0); /* 按键扫描 */if (key == 4) /* 高采样率 , 100hz波形 , 实际只有65.5hz */{lcd_show_string(30, 130, 200, 16, 16, "DAC Wave1 ", BLUE);dac_triangular_wave(4095, 5, 2000, 100); /* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */lcd_show_string(30, 130, 200, 16, 16, "DAC None ", BLUE);}else if (key == 2) /* 低采样率 , 100hz波形 , 实际99.5hz */{lcd_show_string(30, 130, 200, 16, 16, "DAC Wave2 ", BLUE);dac_triangular_wave(4095, 500, 20, 100); /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */lcd_show_string(30, 130, 200, 16, 16, "DAC None ", BLUE);}if (t == 10) /* 定时时间到了 */{LED0_TOGGLE(); /* LED0闪烁 */t = 0;}/* 高采样率, 100hz波形, 实际只有65.5hz */ /* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */ dac_triangular_wave(4095, 5, 2000, 100); /* 低采样率, 100hz波形, 实际99.5hz */ /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */ dac_triangular_wave(4095, 500, 20, 100);采样频率的计算和波形质量之间的关系
采样频率计算
采样频率是指在一秒钟内采集的采样点数,单位为赫兹(Hz)。采样频率的计算公式是:
[ 采样频率 = 1 采样间隔时间 ] [ \text{采样频率} = \frac{1}{\text{采样间隔时间}} ] [采样频率=采样间隔时间1]
其中,采样间隔时间是每个采样点之间的时间间隔。代码中的采样频率计算
高采样率:
dac_triangular_wave(4095, 5, 2000, 100); /* 幅值4095, 采样点间隔5us, 2000个采样点, 100个波形 */- 采样间隔时间:5微秒(us)
- 每秒采样次数 = 1秒 / 5微秒 = 1,000,000微秒 / 5微秒 = 200,000次/秒
所以,采样频率为200 kHz(千赫兹)。
低采样率:
dac_triangular_wave(4095, 500, 20, 100); /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */- 采样间隔时间:500微秒(us)
- 每秒采样次数 = 1秒 / 500微秒 = 1,000,000微秒 / 500微秒 = 2000次/秒
所以,采样频率为2 kHz(千赫兹)。
波形频率计算
高采样率波形频率:
-
一个完整的波形周期有2000个采样点。
-
200 kHz的采样频率 = 200,000个采样点/秒
-
波形周期时间 = 2000采样点 / 200,000采样点/秒 = 0.01秒
代码里面使用的是采样间隔时间*采样点
- 波形周期时间 = 采样点数 采样频率 = 2000 采样点 200 , 000 采样点/秒 = 0.01 秒 = 10 毫秒 \text{波形周期时间} = \frac{\text{采样点数}}{\text{采样频率}} = \frac{2000 \text{采样点}}{200,000 \text{采样点/秒}} = 0.01 \text{秒}= 10 \text{毫秒}\\ 波形周期时间=采样频率采样点数=200,000采样点/秒2000采样点=0.01秒=10毫秒
-
波形频率 = 1 / 波形周期时间 = 1 / 0.01秒 = 100Hz
低采样率波形频率:
- 一个完整的波形周期有20个采样点。
- 2 kHz的采样频率 = 2000个采样点/秒
- 波形周期时间 = 20采样点 / 2000采样点/秒 = 0.01秒
- 波形频率 = 1 / 波形周期时间 = 1 / 0.01秒 = 100 Hz
由于采样频率较低,波形频率接近100 Hz,但更高的非理想因素影响使其实际波形频率为99.5 Hz。
高采样率与低采样率的区别
- 高采样率:更高的采样率使得DAC输出能够更加精确地重现波形的细节,减少失真,波形看起来更平滑和接近于理想形态。
- 低采样率:较低的采样率会导致波形重现不够精确,采样点之间的间隔大,波形失真较大,看起来会比较粗糙。
-
实验结果:
高采样率的波形图:

低采样率的波形图:

5. DAC输出正弦波实验
-
配置步骤:
-
初始化DMA
HAL_DMA_Init() -
将DMA和ADC句柄联系起来
__HAL_LINKDMA() -
初始化DAC
HAL_DAC_Init() -
DAC MSP初始化
HAL_DAC_MspInit() -
配置DAC相应通道相关参数
HAL_DAC_ConfigChannel() -
启动DAM传输
HAL_DMA_Start() -
配置定时器溢出频率并启动
HAL_TIM_Base_Init() HAL_TIM_Base_Start() -
配置定时器触发DAC转换
HAL_TIMEx_MasterConfigSynchronization() -
停止/启动DAC转换、DMA传输
HAL_DAC_Stop_DMA() HAL_DAC_Start_DMA()
-
-
产生正弦波的函数

-
DAC初始化函数
void dac_dma_wave_init(void) {DAC_ChannelConfTypeDef dac_ch_conf;__HAL_RCC_DMA1_CLK_ENABLE(); //使能DMA1时钟g_dma_dac_handle.Instance = DMA2_Channel3; //初始化DMA2的通道3g_dma_dac_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; //数据传输方向设置为内存到外设g_dma_dac_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址不递增g_dma_dac_handle.Init.MemInc = DMA_MINC_ENABLE; //内存地址递增g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//数据对齐使用半字g_dma_dac_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //数据对齐使用半字g_dma_dac_handle.Init.Mode = DMA_CIRCULAR; //传输模式配置为正常模式g_dma_dac_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //优先级设置为中等HAL_DMA_Init(&g_dma_dac_handle);__HAL_LINKDMA(&g_dac_dma_handle, DMA_Handle1, g_dma_dac_handle);g_dac_dma_handle.Instance = DAC;HAL_DAC_Init(&g_dac_dma_handle);dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T7_TRGO; //触发源设置为T7触发输出(TRGO)dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE; //关闭输出缓冲HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, DAC_CHANNEL_1);//启动DMA传输,将数据从内存(g_dac_sin_buf)传输到DAC的通道1数据寄存器(DAC1->DHR12R1)HAL_DMA_Start(&g_dma_dac_handle, (uint32_t)g_dac_sin_buf, (uint32_t)&DAC1->DHR12R1, 0); } -
DAC MSP初始化函数
void HAL_DAC_MsoInit(DAC_HandleTypeDef *hdac) {if(hdac->Instance == DAC){GPIO_InitTypeDef gpio_init_struct;//使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_DAC_CLK_ENABLE();//配置工作模式gpio_init_struct.Pin = GPIO_PIN_4;gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟输入HAL_GPIO_Init(GPIOA, &gpio_init_struct);} } -
DAC DMA使能波形输出
void dac_dma_wave_enable(uint16_t cndtr, uint16_t arr, uint16_t psc) {TIM_HandleTypeDef tim7_handle = {0};TIM_MasterConfigTypeDef tim_mater_config;__HAL_RCC_TIM7_CLK_ENABLE();tim7_handle.Instance = TIM7;tim7_handle.Init.Prescaler = psc;tim7_handle.Init.Period = arr; HAL_TIM_Base_Init(&tim7_handle);//设置为更新事件触发(TIM_TRGO_UPDATE)tim_mater_config.MasterOutputTrigger = TIM_TRGO_UPDATE;//设置为禁用主从模式(TIM_MASTERSLAVEMODE_DISABLE)tim_mater_config.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&tim7_handle ,&tim_mater_config);//启动定时器, 开始计时HAL_TIM_Base_Start(&tim7_handle);//HAL_DAC_Stop(&g_dac_dma_handle, DAC_CHANNEL_1);HAL_DAC_Start_DMA(&g_dac_dma_handle, DAC_CHANNEL_1, (uint32_t *)g_dac_sin_buf, cndtr, DAC_ALIGN_12B_R); }DAC通过DMA从内存缓冲区中读取波形数据并输出模拟信号。定时器7的预分频器和周期决定了DAC的更新频率,从而控制输出波形的频率和形状。
-
DAC正弦波输出函数
void dac_create_sin_buf(uint16_t maxval, uint16_t samples) {uint8_t i;float outdata = 0;float inc = (2 * 3.1415962) / samples;if(maxval <= (samples / 2)) return;for(i = 0;i < samples; i++){outdata = maxval * sin(inc * i) + maxval;if(outdata > 4095)outdata = 4095;g_dac_sin_buf[i] = outdata;} }-
变量初始化:
i:循环计数器。outdata:临时变量,用于存储当前计算的输出数据。inc:每次递增的角度值,计算方式为 ( \frac{2\pi}{\text{samples}} ),用于生成正弦波。
-
合法性检查:
- 判断
maxval是否小于等于samples / 2,如果是则直接返回,不进行后续操作。这是为了确保生成的波形幅值合理。
- 判断
-
生成正弦波缓冲区:
- 使用
for循环遍历每个采样点,计算对应的正弦波值。 - 计算公式为
outdata = maxval * sin(inc * i) + maxval,即将生成的正弦波调整为非负值,并确保最大幅值为maxval。 - 如果计算出的
outdata超过了4095(DAC的最大值),则将其限制为4095。 - 将计算出的
outdata存入全局缓冲区g_dac_sin_buf对应的位置。
- 使用
该函数通过计算并填充正弦波数据的方式,生成了一组用于DAC输出的波形数据。生成的数据缓冲区可以在后续通过DMA传输到DAC,从而生成平滑的正弦波输出。
-
-
主函数
while (1){t++;key = key_scan(0); // 按键扫描 if (key == 4) // 高采样率 {dac_create_sin_buf(2048, 100);dac_dma_wave_enable(100, 10 - 1, 24 - 1); // 300Khz触发频率, 100个点, 得到最高3KHz的正弦波. }else if (key == 2) // 低采样率 {dac_create_sin_buf(2048, 10);dac_dma_wave_enable(10, 10 - 1, 24 - 1); // 300Khz触发频率, 10个点, 可以得到最高30KHz的正弦波. }if (t == 40) {LED0_TOGGLE(); t = 0;}delay_ms(5);}触发频率计算
定时器的触发频率由预分频器和自动重装载寄存器决定。假设系统时钟频率为72MHz:
触发频率:
-
触发频率 = 系统时钟频率 ( 预分频器 + 1 ) × ( 自动重装载寄存器值 + 1 ) 触发频率 = 72 MHz 24 × 10 = 72 MHz 240 = 300 kHz \text{触发频率} = \frac{\text{系统时钟频率}}{(\text{预分频器} + 1) \times (\text{自动重装载寄存器值} + 1)}\\ \text{触发频率} = \frac{72\text{MHz}}{24 \times 10} = \frac{72\text{MHz}}{240} = 300\text{kHz} 触发频率=(预分频器+1)×(自动重装载寄存器值+1)系统时钟频率触发频率=24×1072MHz=24072MHz=300kHz
波形频率: -
波形频率 = 触发频率 每周期采样点数 \text{波形频率} = \frac{\text{触发频率}}{\text{每周期采样点数}} 波形频率=每周期采样点数触发频率
高采样率:
波形频率 = 300 kHz 100 = 3 kHz \text{波形频率} = \frac{300\text{kHz}}{100} = 3\text{kHz} 波形频率=100300kHz=3kHz
低采样率:
波形频率 = 300 kHz 10 = 30 kHz \text{波形频率} = \frac{300\text{kHz}}{10} = 30\text{kHz} 波形频率=10300kHz=30kHz
通过调节采样点数和触发频率,可以生成不同频率和精度的波形。较高的采样点数会使波形更平滑,但波形频率较低;较低的采样点数则会使波形频率较高,但波形较为粗糙。 -
-
实验结果
高采样率波形图:

低采样率波形图:

6. PWM DAC实验
-
PWM DAC初始化函数
void pwmdac_init(uint16_t arr, uint16_t psc) {TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};g_timx_pwm_chy_handle1.Instance = TIM1; //定时器选择g_timx_pwm_chy_handle1.Init.Prescaler = psc; //定时器分频g_timx_pwm_chy_handle1.Init.Period = arr;g_timx_pwm_chy_handle1.Init.CounterMode = TIM_COUNTERMODE_UP; //定时器计数模式g_timx_pwm_chy_handle1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle1); //初始化PWMtimx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; //模式选择PWM1timx_oc_pwm_chy.Pulse = 0; //占空比为50%timx_oc_pwm_chy.OCNPolarity = TIM_OCNPOLARITY_LOW; //输出比较极性为低HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle1, &timx_oc_pwm_chy, TIM_CHANNEL_1); //配置定时器3通道2HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle1, TIM_CHANNEL_1); //开启PWM通道 }定时器配置:
g_timx_pwm_chy_handle1.Instance = TIM1;:选择定时器1。g_timx_pwm_chy_handle1.Init.Prescaler = psc;:设置预分频器值。g_timx_pwm_chy_handle1.Init.Period = arr;:设置自动重装载寄存器值。g_timx_pwm_chy_handle1.Init.CounterMode = TIM_COUNTERMODE_UP;:设置计数模式为向上计数。g_timx_pwm_chy_handle1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;:使能自动重装载预装载。HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle1);:初始化定时器以生成PWM信号。
PWM通道配置:
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;:设置输出比较模式为PWM模式1。timx_oc_pwm_chy.Pulse = 0;:设置初始脉冲宽度(占空比)。timx_oc_pwm_chy.OCNPolarity = TIM_OCNPOLARITY_LOW;:设置输出极性为低。HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle1, &timx_oc_pwm_chy, TIM_CHANNEL_1);:配置定时器的第1通道。
启动PWM输出:
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle1, TIM_CHANNEL_1);:启动PWM输出。
-
定时器输出PWM MSP初始化函数
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) {if(htim->Instance == TIM1){GPIO_InitTypeDef gpio_init_struct;__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_TIM1_CLK_ENABLE();gpio_init_struct.Pin = GPIO_PIN_8;gpio_init_struct.Mode = GPIO_MODE_AF_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &gpio_init_struct);} } -
设置PWM DAC输出电压
void pwmdac_set_voltage(uint16_t vol) {float temp = vol;temp /= 1000;//将电压值转换为相应的PWM占空比值temp = temp * 256 / 3.3;//设置PWM占空比__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle1, TIM_CHANNEL_1, temp); }通过计算PWM占空比来模拟设定的输出电压,适用于需要将数字信号转换为模拟信号的场合。这个过程的关键是将输入电压值转换为适合定时器使用的PWM占空比值,并将其应用于定时器通道,以生成所需的模拟电压。
-
实验结果

声明:资料来源(战舰STM32F103ZET6开发板资源包)
- Cortex-M3权威指南(中文).pdf
- STM32F10xxx参考手册_V10(中文版).pdf
- STM32F103 战舰开发指南V1.3.pdf
- STM32F103ZET6(中文版).pdf
- 战舰V4 硬件参考手册_V1.0.pdf
相关文章:
STM32-17-DAC
STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU STM32-14-FSMC_LCD STM32-15-DMA…...
一杯咖啡的艺术 | 如何利用数字孪生技术做出完美的意式浓缩咖啡?
若您对数据分析以及人工智能感兴趣,欢迎与我们一起站在全球视野关注人工智能的发展,与Forrester 、德勤、麦肯锡等全球知名企业共探AI如何加速制造进程, 共同参与6月20日由Altair主办的面向工程师的全球线上人工智能会议“AI for Engineers”…...
使用QT制作QQ登录界面
mywidget.cpp #include "mywidget.h"Mywidget::Mywidget(QWidget *parent): QWidget(parent) {/********制作一个QQ登录界面*********************/this->resize(535,415);//设置登录窗口大小this->setFixedSize(535,415);//固定窗口大小this->setWindowTi…...
代码随想录训练营第七天 344反转字符串 541反转字符串II 替换数字
第一题: 原题链接:344. 反转字符串 - 力扣(LeetCode) 思路: 双指针,一根指向字符串的头部,一根指向字符串的尾部。两个指针向中间移动,交换两根指针指向的值。 代码如下…...
【Python】数据处理:SQLite操作
使用 Python 与 SQLite 进行交互非常方便。SQLite 是一个轻量级的关系数据库,Python 标准库中包含一个名为 sqlite3 的模块,可以直接使用。 import sqlite3数据库连接和管理 连接到 SQLite 数据库。如果数据库文件不存在,则创建一个新数据库…...
NXP RT1060学习总结 - fsl_flexcan 基础CAN函数说明 -3
概要 CAN测试源码: https://download.csdn.net/download/qq_35671135/89425377 根据fsl_flexcan.h文件从文件末尾往前面梳理,总共30个基础CAN函数; 该文章只梳理常规CAN,增强型CAN后面再单独梳理。 使用的是RT1064开发板进行测试…...
2024年第三届数据统计与分析竞赛(B题)数学建模完整思路+完整代码全解全析
你是否在寻找数学建模比赛的突破点?数学建模进阶思路! 详细请查 作为经验丰富的数学建模团队,我们将为你带来2024年第三届数据统计与分析竞赛(B题)的全面解析。这个解决方案包不仅包括完整的代码实现,还有…...
高通Android 12 右边导航栏改成底部显示
最近同事说需要修改右边导航栏到底部,问怎么搞?然后看下源码尝试下。 1、Android 12修改代码路径 frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java a/frameworks/base/services/core/java/com/android/server/wm/Display…...
2.6数据报与虚电路
数据报 当作为通信子网用户的端系统要发送一个报文时,在端系统中实现的高层协议先把报文拆成若干个带有序号的数据单元,并在网络层加上地址等控制信息后形成数据报分组(即网络层PDU)中间结点存储分组一段很短的时间,找到最佳的路由后&#x…...
小主机折腾记26
双独立显卡调用问题 前两天将tesla p4从x99大板上拆了下来,将880G5twr上的rx480 4g安装到了x99大板上,预计是dg1输出,rx480做3d运算。安装完驱动后,还想着按照之前tesla p4的设置方法去设置rx480,结果果然,…...
ArrayList浅析
目录 一、ArrayList源码1.1 迭代器1.1.1 Itr源码浅析1.1.2 ListItr源码浅析 1.2 常用方法1.3 System.arraycopy1.4 ArrayList 的创建方式 二、引申问题2.1 ArrayList的大小是如何增加的?2.2 什么情况下你会使用ArrayList2.3 在索引中ArrayList的增加或者删除某个对象…...
Spring Boot整合hibernate-validator实现数据校验
文章目录 概念基本概念常用校验注解 前置内容整合Hibernate Validator快速入门优雅处理参数校验异常其余注解校验自定义校验注解 参考来源 概念 基本概念 Hibernate Validator 是一个参数校验框架,可以非常方便地帮助我们校验应用程序的入参,实现了参数…...
Ubuntu系统中网易云音乐编译安装
项目地址: netease-cloud-music-gtk: Linux 平台下基于 Rust GTK 开发的网易云音乐播放器 目录 1.README.md中按照步骤来 2.安装git 3.报错 sudo apt install cmake sudo apt-get install libdbus-1-dev sudo apt install dnf sudo dnf install gettext 继…...
MPLS标签号
标签被压入在2层与3层之间 称为 2.5层 标签的格式----32 位4 个字节 前 20 位为标签号,2~20 个标签号;其中1-15号保留,作为特殊编号; 第 21-23位 exp,3位8个数,为优先级,用于Q0S 策略使用&a…...
OpenHarmony napi 编译 .so 并打包成 .har
一、前言 最近在搞公司标准产品适配OpenHarmony 平台, 按照行业上的常用方法,在Android 是将底层代码用c 封装成 xxx.so ,然后将其他一部分打包成 xxx.jar。 因此,在OpenHarmony 平台也是打算按照这个模式。正所谓,好…...
python 循环导入(circular imports)解决方法
在 Python 中,大部分人都应该都遇到过循环导入的问题。 循环导入是指两个文件各自尝试导入另一个文件(模块),当一个模块没有完全初始化时会导致失败。解决这种情况的最好方法是将代码分层组织,这样导入的关系就会自然…...
01、Linux网络设置
目录 1.1 查看及测试网络 1.1.1 查看网络配置 1、查看网络接口地址 2、查看主机状态 3、查看路由表条目 4、查看网络连接qing 1.1.2 测试网络连接 1.测试网络连接 2.跟踪数据包的路由路径 3.测试DNS域名解析 1.2 设置网络地址参数 1.2.1 使用网络配置命令 1.修改网卡…...
ssm160基于Java技术的会员制度管理的商品营销系统的设计与实现+vue
商品营销系统计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本商品营销系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理…...
边缘计算网关在智慧厕所远程监测与管理的应用
随着智慧城市建设的不断深入,城市公共设施的智慧化管理成为了提升城市品质和居民生活质量的关键建设。公厕作为城市基础设施的重要组成部分,其管理效率和卫生状况直接影响着市民的日常生活体验。在公厕设施建设背景下,边缘计算网关技术的应用…...
嵌入式linux中设备树使用of函数操作基本方法
各位开发者大家好,今天主要给大家分享一下,如何使用of操作函数,获取对应设备树节点先关的属性信息。 第一:of_find_property函数 of_find_property 函数用于在设备树中查找节点下具有指定名称的属性。如果找到了该属性,可以通过返回的属性结构体指针进行进一步的操作,比…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
