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); // 先输出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); } }
- 外层循环
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的通道3 g_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); //初始化PWM timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; //模式选择PWM1 timx_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通道2 HAL_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