STM32实战八 DAC/ADC

这一章编写DAC和ADC程序,即数模/模数转换。程序中封装了两个DAC,各1个独立通道,对应输出脚为PA4和PA5,提供两个方法,ADDA::daDMA(Timer & tim)成员方法以DMA方式按预定数据生成两个正弦波,通道1(PA4)是半幅波形,通道2(PA5)是全幅波形。 ADDA::da()成员方法把指定内存的数据转换成模拟信号,未使用DMA,因为已经是一一对应。

模数转换使用ADC1转换器,共有10个通道,采用硬件存储DMA,不占用CPU时间,包括8个端口通道,对应输入为PA0-7,其中PA4和PA5与DAC共用,可以从内部检测DA/AD的正确性。还有两个内部通道,CPU温度和基准电压。

值得一提的是,参考代码中没有 “DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;    // 不限幅值” 这一行,造成daDMA程序有时能用,有时不能用,费了好多时间才找出问题。

ADDA.h

#ifndef __ADDA__
#define __ADDA__

extern "C" {				// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368		//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x.h"
#include "stm32f10x_dma.h"

#pragma diag_default 368	// 恢复368号警告
}

#include "IO.h"
#include "Timer.h"

#define DAC_DHR12RD_Address      0x40007420	// DAC地址
#define ADC1_DR_Address    ((uint32_t)0x4001244C)	// ADC地址
//ADC_DR(ADC规则数据寄存器),偏移量=0x4c  ADC1(0x40012400-0x400127ff)

// 设置通道1数据
#define SETDAC1( v ) *((__IO uint32_t *)(DAC_BASE + (u32)0x00000008 + DAC_Align_12b_R)) = (uint32_t)v
// 设置通道2数据
#define SETDAC2( v ) *((__IO uint32_t *)(DAC_BASE + (u32)0x00000014 + DAC_Align_12b_R)) = (uint32_t)v

class ADDA : public IO
{
// Construction
public:
	ADDA();

// Properties
public:
u16 m_adData[10];	// 10个ADC数据	PA0-7,CPU内部温度,内部基准电压

private:

// Methods
public:
	void daDMA(Timer & tim);	// 用DMA生成正弦波
	inline void ad(void);		// ADc
	inline void da(void);		// DAC

// Overwrite
public:
};

#endif

ADDA.cpp

/**
  ******************************************************************************
  * @file		ADDA.cpp
  * @author		Mr. Hu
  * @version	V1.0.0 STM32F103VET6
  * @date		06/07/2019
  * @brief		DMA,AD,DA
  *	@IO
  *		DMA2_Channel4	传送ADC1转换数据
  *		ADC1		数模转换器1
  *		ADC通道0-7	外部模拟量输入,对应PA0-5,其中PA4-5与DAC共用
  *		ADC通道16	CPU内部温度
  *		ADC通道17	内部基准电压值
  *		DAC通道1-2	模数转换器1-2,对应PA4-5与ADC共用
  *		PA0-7		ADC1的0-7通道
  *		PA4-5		ADC1的4-5通道,同时也是DAC的1-2通道
  ******************************************************************************
  * @remarks
  *	基于DMA的ADC,硬件转换和存储,不占CPU时间
  * DAC有两个方法,一个是单一数据转换,未用DMA,因为本来就是直接写内存。另一个方法是按
  * 指定数据生成正弦波,使用DMA。
  *	实现8个模拟量输入,2个模拟量输出,其中PA4-5两个IO口既是输入,又是输出,AD/DA共用,
  *	可以从软件中监视AD/DA的数据,实际应用用时只能用于DA输出
  *
  * 参考资料
  * https://blog.csdn.net/qq_38410730/article/details/80071349 ADC有端口分配图
  * https://blog.csdn.net/weixin_42653531/article/details/81123770
  * https://blog.csdn.net/iteye_3759/article/details/82547927
  * https://www.cnblogs.com/zhoubatuo/p/6118897.html
  */ 

/* Includes ------------------------------------------------------------------*/
extern "C" {				// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368		//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x_adc.h"
#include "stm32f10x_dac.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_tim.h"
#include <stm32f10x.h>

#pragma diag_default 368	// 恢复368号警告
}

#include "IO.h"
#include "ADDA.h"

/**
  * @date	06/07/2019
  * @brief  数模/模数转换
  *		IO(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7, GPIO_Mode_AIN, 2)
  *		占用PA0-PA7共8个ADC端口,其中PA4-5与DAC共用。
  * @param	None
  * @retval None
  */
ADDA::ADDA()
: IO(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7, GPIO_Mode_AIN, 2)	// GPIOx, nPin, GPIO_Mode_IPU 上拉, 2 输入时无效
{
	for(u16 i = 0; i < sizeof(m_adData)/sizeof(m_adData[0]); i++)
		m_adData[i] = 0;
	
	/* Enable peripheral clocks ------------------------------------------------*/
	/* DMA2 clock enable */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

	/* DAC Periph clock enable */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
	
	da();	// 启动DAC
	ad();	// 启动ADC
}

/**
  * @date	06/07/2019
  * @brief  数模转换,DMA方式按预定数据生成两个正弦波,通道1(PA4)是半幅波形,通道2(PA5)是全幅波形。
  * @param	tim 触发源
  * @retval None
  */
void ADDA::daDMA(Timer & tim)
{
	TIM_TypeDef * ti = tim.m_pTIMx;
	assert_param( ti == TIM2
		|| ti == TIM4
		|| ti == TIM5
		|| ti == TIM6
		|| ti == TIM7
		|| ti == TIM8
	);
 
	/* TIMx TRGO selection */
	TIM_SelectOutputTrigger(ti, TIM_TRGOSource_Update);
	
	u32 trigger = // 触发源
		ti != TIM2 ? ti != TIM4 ? ti != TIM5 ? ti != TIM6 ? ti != TIM7 ? ti != TIM8
		? 0		// 异常
		: DAC_Trigger_T8_TRGO
		: DAC_Trigger_T7_TRGO
		: DAC_Trigger_T6_TRGO
		: DAC_Trigger_T5_TRGO
		: DAC_Trigger_T4_TRGO
		: DAC_Trigger_T2_TRGO
		;

	/* DAC channel1 Configuration */
	// 特别提示,参考代码中没有给DAC_LFSRUnmask_TriangleAmplitude赋值,
	// 造成有时能用,有时不能用,赋值以后正常
	DAC_InitTypeDef DAC_InitStructure;
	DAC_InitStructure.DAC_Trigger = trigger;	// 设置触发源
	DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;	// 不产生波形
	DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;	// 关闭输出缓存
	DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;	// 不限幅值
	DAC_Init(DAC_Channel_1, &DAC_InitStructure);	// 初始化通道1 PA4
	DAC_Init(DAC_Channel_2, &DAC_InitStructure);	// 初始化通道2 PA5
 
	// 正弦波数据
	const uint16_t Sine12bit[32] = {		// 全幅
		2047, 2447, 2831, 3185, 3498, 3750, 3939, 4056, 4095, 4056,
		3939, 3750, 3495, 3185, 2831, 2447, 2047, 1647, 1263, 909, 
		599, 344, 155, 38, 0, 38, 155, 344, 599, 909, 1263, 1647};
	const uint16_t Sine12bitlow[32] = {		// 半幅
		1024, 1224, 1415,1592,1749,1875,1970,2028,2048,2028,
		1969, 1875, 1748,1592,1415,1224,1024,824,632,454,
		300,172,78,19,0,19,78,172,300,454,632,824};
	uint32_t DualSine12bit[32];    

	//* Fill Sine32bit table *
	for (u8 Idx = 0; Idx < 32; Idx++)
	{
		DualSine12bit[Idx] = (Sine12bit[Idx] << 16) + (Sine12bitlow[Idx]);
	}
  
	//* DMA2 channel4 configuration *
	// DMA通道与DAC对应,不能换
	DMA_DeInit(DMA2_Channel4);
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_Address;		// 外设地址,双通道模式,其他模式请看手册
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit;	// 原始数据指针,正弦数据
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;					// 传输方向外设到内存
	DMA_InitStructure.DMA_BufferSize = 32;								// 缓存大小32个数据,每个数据对应两个通道
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;	// 外设地址固定
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;				// 内存地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;	// 外设传输双字
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;		// 内存传输双字
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;						// 循环输出
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;					// 优先级为高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;						// 禁止内存间传输
	DMA_Init(DMA2_Channel4, &DMA_InitStructure);						// 初始化
	/* Enable DMA2 Channel4 */
	DMA_Cmd(DMA2_Channel4, ENABLE);
 
	/* Enable DAC Channel1: Once the DAC channel1 is enabled, PA.04 is 
	   automatically connected to the DAC converter. */
	DAC_Cmd(DAC_Channel_1, ENABLE);
	/* Enable DAC Channel2: Once the DAC channel2 is enabled, PA.05 is 
	   automatically connected to the DAC converter. */
	DAC_Cmd(DAC_Channel_2, ENABLE);
 
	/* Enable DMA for DAC Channel2 */
	DAC_DMACmd(DAC_Channel_2, ENABLE);
}

/**
  * @date	06/07/2019
  * @brief  数模转换,工作方式是循环转换指定的数据
  *		SETDAC1和SETDAC2 分别设置两个通道数据,模拟量输出到PA4,PA5
  * @param	None
  * @retval None
  */
void ADDA::da()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE );	  //使能DAC通道时钟 

	DAC_InitTypeDef DAC_InitType;
	DAC_InitType.DAC_Trigger=DAC_Trigger_None;	//不使用触发功能 TEN1=0
	DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
	DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;//屏蔽、幅值设置
	DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ;	//DAC1输出缓存关闭 BOFF1=1
	DAC_Init(DAC_Channel_1,&DAC_InitType);	 //初始化DAC通道1
	DAC_Init(DAC_Channel_2,&DAC_InitType);	 //初始化DAC通道1

	DAC_Cmd(DAC_Channel_1, ENABLE);  //使能DAC1
	DAC_Cmd(DAC_Channel_2, ENABLE);  //使能DAC1

	DAC_SetChannel1Data(DAC_Align_12b_R, 0);  //12位右对齐数据格式设置DAC值
	DAC_SetChannel2Data(DAC_Align_12b_R, 0);  //12位右对齐数据格式设置DAC值
}

/**
  * @date	06/07/2019
  * @brief  数模转换,用DMA方式,把结果存到m_adData[10]中
  *		m_adData[0]-[7]对应PA0-7的输入电压转换结果
  *		PA4,PA5与DAC共用
  *		m_adData[8]-[9]分别的CPU温度和内部基准电压
  * @param	None
  * @retval None
  */
// https://blog.csdn.net/nicholas_dlut/article/details/80937036
void ADDA::ad(void)
{
	ADC_TempSensorVrefintCmd(ENABLE); //开启内部温度传感器

	/* Enable peripheral clocks ------------------------------------------------*/
	/* Enable DMA1 clock */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	/* Enable ADC1 and GPIOC clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	// DMA通道与ADC对应,不能换
	/* DMA1 channel1 configuration ----------------------------------------------*/
	DMA_InitTypeDef DMA_InitStructure;
	DMA_DeInit(DMA1_Channel1); //选择DMA的通道1
	//设定从ADC外设的数据寄存器(ADC1_DR_Address)转移到内存(ADCConcertedValue)
	//每次传输大小16位,使用DMA循环传输模式
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;	// 外设地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)m_adData;//数据缓冲区的地址
	//外设为数据源
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	//数据缓冲区,大小2半字
	DMA_InitStructure.DMA_BufferSize = 10;
	// 外设地址固定
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	//内存地址增加,多组adc时,使能,数据传输时,内存增加
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//半字
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	//DMA循环传输
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	//优先级高
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	//??
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	//执行
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	/* Enable DMA1 channel1 */
    DMA_Cmd(DMA1_Channel1, ENABLE);
	  
	/* ADC1 configuration ------------------------------------------------------*/
	ADC_InitTypeDef ADC_InitStructure;
	//ADC独立模式 相对于双重模式
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	//扫描模式用于多通道采集
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	//开启连续转换模式   当转换完本组(可能是一个)继续重新开始执行
	//相对於单次模式:转换一次后就结束
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	//不使用外部触发转换
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	//采集数据右对齐
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	//转换组的通道数目
	ADC_InitStructure.ADC_NbrOfChannel = 10;
	//执行
	ADC_Init(ADC1, &ADC_InitStructure);
   
	//配置ADC时钟,为PCLK2的8分频,即9Hz
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	/* ADC1 regular channel11 configuration */ 
	//配置ADC1的通道11为55.5个采样周期
	//默认组,adc1 ,通道11,排序为1,55.5周期
	//ADC1,ch17,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0,1, ADC_SampleTime_239Cycles5);
	//ADC1,ch16,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1,2, ADC_SampleTime_239Cycles5);
	//ADC1,ch4,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2,3, ADC_SampleTime_239Cycles5);
	//ADC1,ch5,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3,4, ADC_SampleTime_239Cycles5);
	//ADC1,ch6,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_4,5, ADC_SampleTime_239Cycles5);
	//ADC1,ch7,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5,6, ADC_SampleTime_239Cycles5);
	//ADC1,ch6,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_6,7, ADC_SampleTime_239Cycles5);
	//ADC1,ch7,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_7,8, ADC_SampleTime_239Cycles5);
	//ADC1,ch10,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_16,9, ADC_SampleTime_239Cycles5);
	//ADC1,ch11,序号1,55.5.。。
	ADC_RegularChannelConfig(ADC1, ADC_Channel_17,10, ADC_SampleTime_239Cycles5);
	//----------------------使能温度传感器----------------------------
	ADC_TempSensorVrefintCmd(ENABLE);
	/* Enable ADC1 DMA */
	//使能ADC_DMA
	ADC_DMACmd(ADC1, ENABLE);
	
	/* Enable ADC1 */
	//使能ADC
	ADC_Cmd(ADC1, ENABLE);
	/* Enable ADC1 reset calibration register */ 
	//使能ADC1的复位校准寄存器  
	ADC_ResetCalibration(ADC1);
	/* Check the end of ADC1 reset calibration register */
	//等待校准完成
	while(ADC_GetResetCalibrationStatus(ADC1));
	/* Start ADC1 calibration */
	//使能ADC1的开始校准寄存器
	ADC_StartCalibration(ADC1);
	/* Check the end of ADC1 calibration */
	//等待完成
	while(ADC_GetCalibrationStatus(ADC1));
	   
	/* Start ADC1 Software Conversion */ 
	//使用软件触发,由于没有采用外部触发
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

Main.h


#ifndef __MAIN__
#define __MAIN__

extern "C" {				// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368		// 消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x.h"

#pragma diag_default 368	// 恢复368号警告
}

s32 m_nCPUTemperate;		// CPU温度 x 100

#endif

Main.cpp

/**
  ******************************************************************************
  * @file		Main.cpp
  * @author		Mr. Hu
  * @version	V1.0.0 STM32F103VET6
  * @date		05/18/2019
  * @brief		程序入口
  *	@io
  *		TIM3	PWM
  *		TIM4	Encode
  *		TIM7	通用定时器
  *		ADC1	ADC
  *		DAC1
  *		DAC2
  *
  *		PA0-PA7 ADC
  *		PA4		DAC1输出
  *		PA5		DAC2输出
  *		PA6 	ADC
  *		PA7 	ADC
  *		PA9 	板载串口
  *		PA10 	板载串口
  *		PA13	板载JLINK占用
  *		PA14	板载JLINK占用
  *		PA15	板载JLINK占用
  *
  *		PB1		板载SW2
  *		PB3		板载JLINK占用
  *		PB4		板载JLINK占用,部分映像的通道1不能用,所以用了没有得映像
  *		PB6		编码器 A
  *		PB7		编码器 B
  *		PB8		板载CAN
  *		PB9		板载CAN
  *		PB10	板载RS485
  *		PB11	板载RS485
  *		PB13	板载LED2
  *		PB14	板载LED3
  *		PB15	板载SW3
  *
  *		PC4		板载RS485
  *		PC5		板载RS485
  *		PC6		PWM1
  *		PC7		PWM2
  *		PC8		PWM预留
  *		PC9		PWM预留
  ******************************************************************************
  * @remarks
  *
  */ 

extern "C" {	// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368			//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x_tim.h"
#include "stm32f10x_dac.h"

#pragma diag_default 368	// 恢复368号警告
}

#include "stm32f10x_adc.h"
#include "IO.h"
#include "Timer.h"
#include "GeneralTimer.h"
#include "BoardLED.h"
#include "PWM.h"
#include "MedianFilter.h"
#include "AverageFilter.h"
#include "ADDA.h"
#include "Main.h" 

/**
  * @date	05/18/2019
  * @brief  主入口,主循环
  *			如果不正常运行,可能是栈设置不够 startup_stm32f10x_hd.s Stack_Size EQU 0x600
  * @param	None
  * @retval None
  */
int main(void)
{
	m_nCPUTemperate = 0;

	SystemInit();	// 配置系统时钟为72M

	GeneralTimer tim(TIM2);		// 通用定时器,实际用TIM7,不占用IO,但软件仿真只有1-4,所以选2

	ADDA adda;	// 定时器下紧跟启动ADDA,因为转换需要时间
	//adda.daDMA(tim);	// DMA方式,按数据生成正弦波,使用这个功能时,注释下面的三角波代码
	s16 dainc = 1;
	u16 daval = 0;

	BoardLED boardLED( &tim );	// 板载LED
	
	// 板载按键,PB1 SW2, PB2 SW3,不同的板子不一样。
	IO key(GPIOC, GPIO_Pin_1 | GPIO_Pin_15, GPIO_Mode_IPU, 2);	// GPIOx, nPin, GPIO_Mode_IPU 上拉, 2 输入时无效
	
	// 使能按键滤波
	//tim.inb[1].level = 1;		// SW2 PB1 上拉
	tim.inb[1].enable = 1;		// SW2 PB1 使能
	//tim.inb[15].level = 1;	// SW3 PB15 上拉
	tim.inb[15].enable = 1;		// SW3 PB15
	
	u32 loopCount = 0;	// 主循环计数
	
	PWM pwm;
	//pwm.orthogonal( 2 - 1, 1000 - 1 );	// 18kHz 移相正交波形
	
	for(int i = 0; i < 3600; i++)	// 延时大约1ms,等待AD转换后再往下接行,求平均时要以获得比较准确的初值
	{
		i++;	// 加一句,不然优化编译时会被删掉
	}

	// 计算方法
	// float v2 = d * 5.f / 0xfff;	// 把测量数d(0-ffff)转换成电压,单片机用了5V电源,所以用5.f,否则改用3.3f
	// (1.43f - v2) / 0.0043 + 25;	// 1.43f 25度时的电压值,v2 测量值,0.0043 每度电压变化
	// 下面是简化后的公式,因为没有FPU,不能用浮点计算,结果单位为1/100度
	#define CPUT ((s32)35756 - 1221 * adda.m_adData[8] / 43) /* adda.m_adData[8]是内部CPU温度 */
	MedianFilter mfTemperate( CPUT, 2 );
	AverageFilter afTemperate( CPUT, 3 );
	
	while(1)
	{
		tim.loop();	// 必须放在主循环的第一行,按键滤波和上下沿微分。
		
		// PWM
		pwm.setData(0, 300);	// PWM1 PC6 30%的占空比
		pwm.setData(1, 700);	// PWM2 PC7 70%的占空比
		
		// LED
		// 测试时间
		loopCount++;
		if( !tim.m_t[2] )	// 定时器2
		{
			tim.m_t[2] = 1000;		// 延时1000ms
			boardLED.m_nNum = 100 * 1000 / loopCount;	// 计算循环周期,1000*1000对应周期单位是1us,100*1000是10us,以此类推。
			if( boardLED.m_nNum > 0xf )
				boardLED.m_nNum = 0xf;		// 大于15时,显示15
			loopCount = 0;
		}
		boardLED.showNumber();	// 显示四位二进制boardLED.m_nNum,用了m_t[0]
		
		// CPU温度 https://blog.csdn.net/qq_27970103/article/details/81325418
		if(!tim.m_t[3])
		{
			s32 mf = mfTemperate.filter( CPUT );	// 中值滤波
			m_nCPUTemperate = afTemperate.filter( mf );	// 平均滤波,效果最好
			tim.m_t[3] = 100;	// 100ms 计算一次
		}
		
		// 开关LED
		if( tim.inb[1].down | tim.inb[15].down ) 	// 两个板载开关的下降沿
		{
			boardLED.showLED(GPIO_Pin_14, 1);		// 点亮LED3
		}
		else if( tim.inb[1].up | tim.inb[15].up )	// 两个板载开关的上升沿
		{
			boardLED.showLED(GPIO_Pin_14, 0);		// 熄灭LED3
		}
				
		// DA-AD 测试,先设置数据,用DA转换成电压,再用AD转换成数字,用示波器观察,延后1ms
		// 产生三角波
		SETDAC2( daval );
		daval += dainc;
		if(daval > 4095)	// daval是无符号数,减过0以后是很大的数,所以只用一个判断
		{
			dainc = -dainc;		// 改变方向
			daval += dainc;		// 调到范围内
		}
		u16 test1 = adda.m_adData[5];		// adda.m_adData[5]是PA5电压的转换结果,而PA5的电压是数字adda.m_daData.da2的转换结果,用了同一个IO脚,不用接线测试
		SETDAC1(test1);	// 再把结果送到DAC通道1(adda.m_daData.da1 = test1)PA4,再用示波器观查,延后1ms,DA触发是1ms
	}
}

Main.cpp中下面的代码是获取CPU温度,每100ms采样一次,先用中值滤波,去掉高幅值偶发性干扰,再用均值滤波减弱随机噪声,得到比较稳定的温度值,实测±0.6度内跳动。

// CPU温度 https://blog.csdn.net/qq_27970103/article/details/81325418
if(!tim.m_t[3])
{
	s32 mf = mfTemperate.filter( CPUT );		// 中值滤波
	m_nCPUTemperate = afTemperate.filter( mf );	// 平均滤波
	tim.m_t[3] = 100;	// 100ms 计算一次
}

下面的代码是ADC/DAC应用,daval是0-4095递加和递减的数据,通过SETDAC2(daval)变成0-5V的模拟信号,输出到PA5脚,adda.m_adData[5]把PA5的电压转换成数字,SETDAC1(test1)把这个数字转换电压,输出到PA4。下图是PA4和PA5的示波器测量波形。两个脚的波形差1ms。

// DA-AD 测试,先设置数据,用DA转换成电压,再用AD转换成数字,用示波器观察,延后1ms
// 产生三角波
SETDAC2( daval );
daval += dainc;
if(daval > 4095)	// daval是无符号数,减过0以后是很大的数,所以只用一个判断
{
	dainc = -dainc;		// 改变方向
	daval += dainc;		// 调到范围内
}
u16 test1 = adda.m_adData[5];		// adda.m_adData[5]是PA5电压的转换结果,而PA5的电压是数字adda.m_daData.da2的转换结果,用了同一个IO脚,不用接线测试
SETDAC1(test1);	// 再把结果送到DAC通道1(adda.m_daData.da1 = test1)PA4,再用示波器观查,延后1ms,DA触发是1ms

注释上面的三角波发生器,启用 adda.daDMA(tim); 以DMA方式生成正弦波,如下图,因为数据较小,锯齿较大。

 

STM32实战系列源码,按键/定时器/PWM/ADC/DAC/DMA/滤波
STM32实战一 初识单片机
STM32实战二 新建工程
STM32实战三 C++ IO.cpp
STM32实战四 定时器和按键
STM32实战五 板载LED显示数据
STM32实战六 PWM加移相正交
STM32实战七 数字滤波
STM32实战八 DAC/ADC
STM32实战九 编码器
STM32开发过程的常见问题

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章