基于CH32V307VCT6的基本定时器功能实现——定时中断、DAC+TIM触发+DMA搬运
基于CH32V307VCT6的基本定时器功能实现——定时中断、DAC+TIM触发+DMA搬运
定时器的基础功能说明
基本定时功能

对于基本定时器,通常拥有定时中断、主模式触发DAC的功能,下面主包将对上述两种模式做出一些简单的说明以及如何配置。
第一需要提到的概念是定时器所用时钟,定时器通常具体内部时钟与外部时钟,而外部时钟又有两种模式:外部时钟模式1与外部时钟模式2。对于基本定时器,它只有可以使用内部时钟。
定时器的时钟来源来则于TIMxCLK,也就是时钟树,一般为系统主频96MHZ,因此我们在配置定时器时(以及所有单片机所涉及的外设)需要打开对应的时钟。而预分频器、自动重装载寄存器、计数器构成的时基单元,在定时器配置时,也是需要配置的必要环节。
预分频器可以将输入的基准频率提前进行一个分频的操作(可以把输入变慢),从而可以获取更长的计时时间,但计时的分辨率也会随之减小。而计数器的作用就是对预分频后输入的时钟脉冲进行计数。最后自动重载寄存器的作用就是,(如果为向上计数)当计数器的计数值自动达到自动重装载值时,计数器会自动置0,如果配置好对应的NVIC定时器通道,同时也会产生对应的更新中断与更新事件,更新中断则会得到CPU的响应,执行我们想要的操作。下面是时序图:
计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器溢出频率:CK_CNT_OV
= CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
主模式触发DAC功能
STM的一大特色就是定时器的主从触发模式,它可以让定时器的内部硬件实现自动运行,从而减轻CPU的负担。比如在DAC输出时,需要输出一段波形,需要频繁触发更新DAC,如果采用定时器中断的方式,则会频繁的进入中断。因此采用定时器的主模式便会解决这个问题。主模式可以将上述提到的更新事件映射到TRGO,而TRGO直接连接到DAC触发转换引脚上,从而DAC的触发就不需要定时器中断来触发了,而是通过更新事件。
定时中断的基本结构

这里运行控制寄存器可以控制定时的计数模式(向上计数、向下计数)等等。
如果需要使用中断,则需要在中断输出控制内允许中断。此时中断会通过NVIC通道得到CPU响应。
定时的代码配置
基本定时功能
通过查阅手册可知,CH32V307VCT6的定时器分类为高级定时器 1、8、9、10,通用计数器 2、3、4、5,基本定时器6、7。下面主包以定时器6为例进行配置,时基与中断的配置如下:
/*
对TIM_TimeBaseInitStruct.TIM_ClockDivision的说明
打开函数说明可以看到:
TIM_Clock_Division_CKD
#define TIM_CKD_DIV1 ((uint16_t)0x0000)
#define TIM_CKD_DIV2 ((uint16_t)0x0100)
#define TIM_CKD_DIV4 ((uint16_t)0x0200)
该参数的作用是,当定时器对外部引脚ETR或者是四个定时器输入通道TIMx的输入信号进行采样以及滤波时,其采样频率是基于内部时钟的,而该参数的作用是可以对采样频率进行分频,与时基单元无关。
*/
void Tim6_Init(u16 arr,u16 psc)
{
//定义配置结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStructure;
//使能定时器的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
//选择定时器6的时钟为内部时钟(默认也是内部时钟)
TIM_InternalClockConfig(TIM6);//使用内部时钟(默认)
//与时基单元无关
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;//不分频(高级定时器才会用到)
//设置为向上计数
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
//设置预分频值
TIM_TimeBaseInitStruct.TIM_Prescaler=psc-1;
//自动重装载值
TIM_TimeBaseInitStruct.TIM_Period=arr-1;
//初始化时基单元配置
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStruct);
//使能中断
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
//选择中断通道
NVIC_InitStructure.NVIC_IRQChannel=TIM6_IRQn;
//使能中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
//设置中断抢占优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
//设置中断响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
//初始化中断配置
NVIC_Init(&NVIC_InitStructure);
//开启定时器
TIM_Cmd(TIM6, ENABLE);
}
//在主函数里初始化定时器配置(同时需要开启快速中断,引用下面这句话)
void TIM6_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));//定时器6快速中断
u_int32_t uwTick;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
SystemInit();
Tim6_Init(100,960);
while (1)
{
}
}
void TIM6_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6, TIM_IT_Update)!=RESET)
{
uwTick++;
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}
}
现在定时器的初始化配置遍完成啦!
定时器主模式触发DAC
DAC的触发模式为:定时器触发+DMA
总配置如下:
1.打开相关时钟
2.配置GPIO用于DAC的输出通道
3.配置DAC
4.配置定时器
5.配置DMA
void DAC_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
DAC_InitTypeDef DAC_InitType={0};
DMA_InitTypeDef DMA_InitStructure={0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure={0};
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE );
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
/*GPIO配置*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*DAC1配置*/
DAC_InitType.DAC_Trigger = DAC_Trigger_T7_TRGO;//定时器主触发模式
DAC_InitType.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_1,&DAC_InitType);
DAC_Cmd(DAC_Channel_1, ENABLE);
DAC_DMACmd(DAC_Channel_1,ENABLE);
DAC_SetChannel1Data(DAC_Align_12b_R,0);
/*DAC2配置*/
DAC_InitType.DAC_Trigger = DAC_Trigger_T7_TRGO;//定时器主触发模式
DAC_InitType.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_2,&DAC_InitType);
DAC_Cmd(DAC_Channel_2, ENABLE);
DAC_DMACmd(DAC_Channel_2,ENABLE);
DAC_SetChannel1Data(DAC_Align_12b_R,0);
/*定时器配置*/
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 3600-1;
TIM_TimeBaseStructure.TIM_Prescaler =0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM7, TIM_TRGOSource_Update);
TIM_Cmd(TIM7, ENABLE);
/*DMA配置*/
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DAC->RD12BDHR);// 使用CH32V307特定的寄存器名称
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DacWaveData;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 内存到外设
DMA_InitStructure.DMA_BufferSize = WAVE_POINTS;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;// 32位传输
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_Channel3, &DMA_InitStructure);
DMA_Cmd(DMA2_Channel3, ENABLE);
}
这里主包分段作出解释
GPIO的配置
查看手册引脚复用表可知需要配置的引脚为PA4与PA5
//定义GPIO配置结构体
GPIO_InitTypeDef GPIO_InitStructure={0};
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
//这里配置DAC的两个输出通道
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
//注意模式需要配置模拟输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
//速度选50MHZ即可
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//初始化配置
GPIO_Init(GPIOA, &GPIO_InitStructure);
DAC的配置
为了让DAC两通道独立输出不同波形,所以配置如下
/*DAC1配置*/
//初始化DAC配置结构体
DAC_InitTypeDef DAC_InitType={0};
//打开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE );
//DAC的触发方式为定时器主模式触发
DAC_InitType.DAC_Trigger = DAC_Trigger_T7_TRGO;
//DAC的固定波形不设置,我们通道数组来控制DAC的输出值
DAC_InitType.DAC_WaveGeneration = DAC_WaveGeneration_None;
//波形的幅值自然也不用配置
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
//输出缓存寄存器会减慢波形输出的速度,这里关闭
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
//初始化DAC配置
DAC_Init(DAC_Channel_1,&DAC_InitType);
//使能DAC
DAC_Cmd(DAC_Channel_1, ENABLE);
//使能DAC的DMA输出
DAC_DMACmd(DAC_Channel_1,ENABLE);
//设置通道输出的初始值,以及DAC的位数(12为对用0-4095)
DAC_SetChannel1Data(DAC_Align_12b_R,0);
/*DAC2配置同理,不做详细介绍*/
DAC_InitType.DAC_Trigger = DAC_Trigger_T7_TRGO;//定时器主触发模式
DAC_InitType.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_2,&DAC_InitType);
DAC_Cmd(DAC_Channel_2, ENABLE);
DAC_DMACmd(DAC_Channel_2,ENABLE);
DAC_SetChannel2Data(DAC_Align_12b_R,0);
定时器的配置
/*定时器配置*/
//前面的内容正常配置即可
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure={0};
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 3600-1;
TIM_TimeBaseStructure.TIM_Prescaler =0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);
//这里出现一个新的函数用于触发DAC,选择触发源为定时器更新事件
TIM_SelectOutputTrigger(TIM7, TIM_TRGOSource_Update);
//使能定时器
TIM_Cmd(TIM7, ENABLE);
/*********************************************************************
* @fn TIM_SelectOutputTrigger
*
* @brief Selects the TIMx Trigger Output Mode.
*
* @param TIMx - where x can be 1 to 10 select the TIM peripheral.
* TIM_TRGOSource - specifies the Trigger Output source.
* TIM_TRGOSource_Reset - The UG bit in the TIM_EGR register is
* used as the trigger output (TRGO).
* TIM_TRGOSource_Enable - The Counter Enable CEN is used as the
* trigger output (TRGO).
* TIM_TRGOSource_Update - The update event is selected as the
* trigger output (TRGO).
* TIM_TRGOSource_OC1 - The trigger output sends a positive pulse
* when the CC1IF flag is to be set, as soon as a capture or compare match occurs (TRGO).
* TIM_TRGOSource_OC1Ref - OC1REF signal is used as the trigger output (TRGO).
* TIM_TRGOSource_OC2Ref - OC2REF signal is used as the trigger output (TRGO).
* TIM_TRGOSource_OC3Ref - OC3REF signal is used as the trigger output (TRGO).
* TIM_TRGOSource_OC4Ref - OC4REF signal is used as the trigger output (TRGO).
* TIM6/TIM7 only have TIM_TRGOSource_Reset/TIM_TRGOSource_Enable/TIM_TRGOSource_Update
* @return none
*/
void TIM_SelectOutputTrigger(TIM_TypeDef *TIMx, uint16_t TIM_TRGOSource)
{
TIMx->CTLR2 &= (uint16_t) ~((uint16_t)TIM_MMS);
TIMx->CTLR2 |= TIM_TRGOSource;
}
DMA的配置
为了减小CPU的负担,这里寄存器的值由DMA来搬运,下面先简单回顾一下DMA的内容。

DMA的传输方向可以是外设到存储器,也可以是存储器到外设,并且外设也可以是存储器。在本例很明显DMA的传输方向为存储器到外设。
传输计数器的作用是,(如果自动重装器没有打开)在DMA数据传输的过程中,当传输计数器置0时,DMA数据传输停止。(如果自动重装器打开)当传输计数器置0时,自动重装器自动将归位,类似于定时器的AAR。
以ADC的多通道扫描为例,简单讲述一下DMA的重要性:
如果ADC开启连续扫描模式的话,则DMA可以实现与ADC同步将序列中不同通道的数据按照顺序来转运,其每个循环里转运数据数目与传输计数器有关,以上图为例,当传输计数器的初始值为7(DMA与ADC扫描同步),那DMA最后转运的数据为序列7通道6的数据,此时如果开启了自动重装功能,那么DMA也会同步转运序列1的数据。由此可见,DMA可以解决ADC外设寄存器只有一个,并且不同通道转换出来的数据会相互覆盖的弊端。即每当ADC转运一通道的数据完成时,DMA收到硬件触发信息,及时将数据搬运出来,防止被覆盖。
回到本例的DAC触发的例子,由于DAC外设的数据寄存器地址是固定的32位数据,其中低16位为DAC1的数据,高16位为DAC2的数据,所以我们的源存储器地址是需要自增的,从而改变DAC输出的值,但外设寄存器的地址只有一个,故不需要自增。话不多说,下面开始讲代码。
//初始化DMA2结构体
DMA_InitTypeDef DMA_InitStructure={0};
//使能时钟(DMA的硬件触发是有对应通道的,这里DAC的触发请求对应的是DMA2的通道3)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
//初始化结构体
DMA_StructInit(&DMA_InitStructure);
//确定外设地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DAC->RD12BDHR);// 使用CH32V307特定的寄存器名称
//确认内部存储器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DacWaveData;
//配置传输方向
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 内存到外设
//传输计数寄存器数
DMA_InitStructure.DMA_BufferSize = WAVE_POINTS;
//外设地址不自增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//内存地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//32位数据传输
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
//循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//设置DMA请求优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//非内存到内存的软件触发(这里软件触发并不类似于ADC的软件触发,而是对于内存到内存的搬运方式,是不需要硬件触发时机的,目的是为了尽可能快的搬运所有数据,即让传输计数器快速置0。)
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//初始化DMA配置(DAC的触发请求对应的是DMA2的通道3)
DMA_Init(DMA2_Channel3, &DMA_InitStructure);
DMA_Cmd(DMA2_Channel3, ENABLE);
主函数
#include "debug.h"
#include "math.h"
#define WAVE_POINTS 100 // 波形点数
#define DAC_VREF 3.3f // DAC参考电压(V)
#define VOLTAGE_3V (uint16_t)(3.0f / DAC_VREF * 4095) // 3.0V对应的DAC值
#define VOLTAGE_2V (uint16_t)(2.0f / DAC_VREF * 4095) // 2.0V对应的DAC值
uint32_t DacWaveData[WAVE_POINTS];
/*生成正弦波和固定2.0V数据*/
void Generate_WaveData(void)
{
for (int i = 0; i < WAVE_POINTS; i++)
{
// 计算通道1的正弦波数据 (0-3V范围)
float sin_value = sin(2.0f * PI * i / WAVE_POINTS);
uint16_t ch1_data = (uint16_t)((sin_value + 1.0f) / 2.0f * VOLTAGE_3V);
// 通道2保持2V直流
uint16_t ch2_data = VOLTAGE_2V;
// 组合双通道数据 (高16位为通道2,低16位为通道1)
DacWaveData[i] = ((uint32_t)ch2_data << 16) | ch1_data;
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
SystemInit();
Generate_WaveData();
DAC_Configuration();
while (1)
{
}
}
至此功能即可实现,最后本文章插图来源于江协科技,尊重原创!
更多推荐

所有评论(0)