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開發過程的常見問題

 

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