一、实验目的
通过实践环节锻炼学生的工程能力,巩固课堂的知识,为毕业后走向工作岗位奠定基础。
了解STM32的通用外设:TIM\DMA\DAC等等。
学会使用示波器。
设计要求:
- 以STM32单片机开发板为核心设计数字信号发生器程序。
2. 要求产生正弦,方波,三角波三种波形。波形的幅值,相位,周期均可调。
幅值设定:1V、2V、3V;相位设定:0、90、180;周期:500ms、1s、2s。
3. 波形通过上位机软件或DA输出展示。二、实验设备
NUCLEO-64 F334、正点原子手持示波器三、设计思路
1、最终方案
首先生成三种波形(正弦、三角波、方波)各自的波形数组。接着将已经三个波形数组按照课程设计要求,分别生成对应的三个幅值(1v、2v、3v),共九个数组。定义一个结构体存放波形的类型、幅值、相位、频率、周期、指针等等数据。
接收到上位机的数据后进入UART接收中断,处理上位机信号,将解码后的数据存入结构体。在定时器溢出中断中根据结构体中的波形类型和幅值数据修改指针指向的数组,并重新开启DMA传输。产生波形则使用的是TIM+DAC+DMA的方案。DAC的触发源采用定时器的事件更新,使用DMA自动搬运数据到 DAC_DHR12R1寄存器。//为了排版简洁仅摘录了部分代码
typedef enum
{
none = 0x00,
sine = 0x01,
triangle = 0x02,
square = 0x03,
}wave_type_t;
typedef struct
{
wave_type_t waveType; //波形类型 sine triangle square
uint16_t amplitude; //幅值1v 2v 3v
uint16_t phase; //相位 0,90,180
float frequence; //频率1~64k
uint16_t ARR; //auto-reload-value/counter period 定时器自动重装载值也称计数周期
uint16_t* pWaveForm; //波形数组指针
}DSG_data_t;//digital signal generator data
在主循环中修改定时器的ARR寄存器,以定时器触发更新事件的频率来修改生成波形的频率。HAL_DAC_Start_DMA(&hdac1,DAC_CHANNEL_1,(uint32_t*)(DSG.pWaveForm),64,DAC_ALIGN_12B_R);
例如,设置一个周期的数组长度为64,预分频系数为15,计数周期为65535,那么
得到的波形频率为
(这里我错误地将64MHz当作6410241024,故得出结果为1,设定值与实际值始终有5%的误差)2、误差分析
在后来的实际测试中发现,输出的频率始终与设定值有5%的误差,我原先不以为然。在验收时老师指出5%的频率误差太大。虽然我当场快速地修正了误差,但是直到现在编写设计报告时,才发现这误差来源。即晶振的主频为8MHz,通过锁相环倍频后得到64Mhz。
此时的64Mhz应该为64000000,而我错误地将64Mhz当作6410241024
由可知在我原先的计算基础上乘以0.9536即可修正偏差//频率处理,在主循环中,不能放在中断里
void frequenceResolve()
{
__HAL_TIM_DISABLE_IT (&htim15, TIM_IT_UPDATE);//首先关闭定时器中断,否则影响修改ARR的值
DSG.ARR = (uint16_t)((65536/DSG.frequence - 1)*0.9536F);
__HAL_TIM_SET_AUTORELOAD(&htim15, DSG.ARR); //设置自动重装载值
__HAL_TIM_ENABLE_IT (&htim15, TIM_IT_UPDATE);
}
/**
* @brief Set the TIM Autoreload Register value on runtime
without calling another time any Init function.
*/
#define __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) \
do{ \
(__HANDLE__)->Instance->ARR = (__AUTORELOAD__); \
(__HANDLE__)->Init.Period = (__AUTORELOAD__); \
} while(0)
上位机则使用的是VOFA+,这是一款开源的高度可自定义的上位机软件。
我们可以通过设置VOFA+的命令组来快速地和下位机通信/*
以下是VOFA+解析串口数据并且绘制波形时需要使用的格式
*/
uint8_t tail[4] = {0x00,0x00,0x80,0x7f};
float VOFA_Data[4] = {0};
void VOFA_PackageANDSend()
{
VOFA_Data[0] = DSG.realTimAmp;
HAL_UART_Transmit (&huart2 ,(uint8_t*)VOFA_Data ,sizeof(VOFA_Data),0x02);
HAL_UART_Transmit (&huart2 ,(uint8_t*)tail ,sizeof(tail),0x02);
}
3、排除的方案
原定一个周期数组长度为1024,修改周期通过采样这1024个长度的数据。但是这样做就无法使用DMA,因为DMA的步进量是固定的三个值(字,半字或字节)。而且若初始化9个1024个长度的uint16_t类型数组,将占用18k的RAM,但是F334只有16k的RAM。还有一个问题就是假设要输出100Hz的正弦波,那么1s内就要执行中断服务函数102400次,过于占用CPU资源,会导致CPU没有时间运行主程序。查阅资料发现一个周期由32位长的数组组成就能较好地还原正弦波形。故我采用了64位长度的数组。然后通过修改ARR的值来修改频率,就能利用DMA来节省CPU资源。四、心得体会
虽然我接触单片机有一年半之久,由于没有需求,对单片机的DMA和DAC等外设没有使用经验。非常感谢这次课程设计,让我深入研究了如何使用DMA和DAC。并且对定时器的使用有了更深刻的理解。如我原先不知道PSC和ARR寄存器的值可以实施修改,以为只能在初始化时赋值。知道了影子寄存器的存在,PSC和ARR写入值时不会立刻生效,而是等到定时器溢出时,再装入影子寄存器。若使能auto-reload preload 那么要在下一次定时器溢出时,才将我们写入的值装入实际的寄存器。若失能auto-reload preload,则在本次定时器溢出时立即装入实际寄存器。对以__HAL_TIM_X_X()类型宏定义有更深刻的理解,这是HAL库封装好的供用户直接操作寄存器的集合。