本例记录使用GD32307C开发板,实现以内部Timer1 CH1为触发源,触发ADC0的两个通道,进行并行非连续采样,病通过DMA传输采样结果。同时输出PWM,用来检测Timer1 CH1的触发周期。
下面介绍具体实现过程:
本例需要用到以下三根IO:
PA1 -- TIMER1_1,PWM
PC3 -- ADC0_CH13
PC5 -- ADC0_CH15
查询芯片手册需要将PC3/PC5设定为模拟输入AIN(ADC功能),将PA1设定为备份功能输出脚AF_PP(PWM输出)。
以PA1为例,设定为Alternate功能后,如果有启动Timer1,则该脚位的功能为TIMER1_CH1。
代码如下:
/*!
\brief configure the GPIO peripheral
\param[in] none
\param[out] none
\retval none
*/
//PA1 -- TIMER1_1,PWM
//PC3 -- ADC0_CH13
//PC5 -- ADC0_CH15
static void gpio_config(void)
{
/* enable GPIOC clock */
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOA);
/* 开启复用功能时钟 */
rcu_periph_clock_enable(RCU_AF);
/* config the GPIO as analog mode */
gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_MAX, GPIO_PIN_3|GPIO_PIN_5);
/*configure PB3(TIMER1 CH1) as alternate function*/
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
}
这里配置Timer1的定时周期为1ms,附上Timer时间公式:
定时时间:Time= (1+prescaler)/ systemcoreclock *(1+period)
我这边systemcoreclock为120MHZ。
设定CH1位PWM0模式输出,用来验证ADC采样间隔(ADC采样以内部Timer1_CH1为触发源)。
在下面代码里面,我们设定为边沿对齐方式(EAPWM),向上计数:
EAPWM的周期由TIMERx_CAR寄存器值决定,占空比由TIMERx_CHxCV寄存器值决定。
这里介绍一下PWM的工作模式(PWM0/PWM1)
PWM的工作模式:
- PWM 模式0。在向上计数时,一旦计数器值小于TIMERx_CH0CV时,
O0CPRE为有效电平,否则为无效电平。在向下计数时,一旦计数器的值大
于TIMERx_CH0CV时,O0CPRE 为无效电平,否则为有效电平。- PWM 模式1。在向上计数时,一旦计数器值小于TIMERx_CH0CV时,
O0CPRE为无效电平,否则为有效电平。在向下计数时,一旦计数器的值大
于TIMERx_CH0CV时,O0CPRE为有效电平,否则为无效电平。
占空比计算公式:
Duty = A/(N+1)
N = Timer装载值
A = TIMER_CHxCV,即timer_channel_output_pulse_value_config的最后一个参数
EAPWM的时序示意图如下:
/*!
\brief configure the timer peripheral
\param[in] none
\param[out] none
\retval none
*/
static void timer1_config(void) // 1 ms
{
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
//使能定时器时钟
rcu_periph_clock_enable(RCU_TIMER1);
timer_deinit(TIMER1);
/* TIMER1 基本配置 */
timer_initpara.prescaler = 119;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 999;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER1,&timer_initpara);
/* CH1 configuration in PWM mode1 */
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; /* 通道使能 */
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; /* 通道互补输出使能(定时器2无效) */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; /* 通道极性 */
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;/* 互补通道极性(定时器2无效)*/
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;/* 通道空闲状态输出(定时器2无效)*/
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;/*互补通道空闲状态输出(定时器2无效) */
timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocintpara);
/* duty 占空比设定*/
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 500);
timer_channel_output_mode_config(TIMER1, TIMER_CH_1, TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER1, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);
timer_auto_reload_shadow_enable(TIMER1);
}
使用示波器量测PA1脚位,输出为1KHZ的方波。
这里我们选择DMA0_CH0,这里我们设定为16bit宽度数据源,与16bit外设数据宽度。DMA传输数量设定为100.
所以我们定义一个100 uint16_t的数组作为DMA存放数据的内存。
开启半满与全满中断。
代码如下:
/*!
\brief configure the DMA peripheral
\param[in] none
\param[out] none
\retval none
*/
uint16_t adc_value[100];
uint32_t dmaHalfIntCnt;
static void dma_config(void)
{
/* ADC_DMA_channel configuration */
dma_parameter_struct dma_data_parameter;
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA0);
/* 复位dma 通道0 */
dma_deinit(DMA0, DMA_CH0);
/* initialize DMA data mode */
dma_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));//配置外设寄存器为adc的规则数据寄存器
dma_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;//外设寄存器地址不增加
dma_data_parameter.memory_addr = (uint32_t)(&adc_value);//存放数据的内存地址
dma_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;//存放数据的内存地址自增
dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;//外设数据宽度16位,每个16bit存储一个通道的值
dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT; //内存数据宽度16位
dma_data_parameter.direction = DMA_PERIPHERAL_TO_MEMORY;//数据传输方式是外设到内存
dma_data_parameter.number = 100;//dma传输数量
dma_data_parameter.priority = DMA_PRIORITY_HIGH;//dma优先级高
dma_init(DMA0, DMA_CH0, &dma_data_parameter);
//循环模式开启
dma_circulation_enable(DMA0, DMA_CH0);
/* dam 转换结束/半满产生中断*/
dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF);
dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_HTF);
/* 使能dma通道0*/
dma_channel_enable(DMA0, DMA_CH0);
//中断管理器开启通道中断
nvic_irq_enable(DMA0_Channel0_IRQn, 0, 0);
}
DMA中断程序如下:(函数名与startup中的中断向量表函数名一致)
void DMA1_Channel1_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF)){
//清除dma中断标记
dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF);
gd_eval_led_toggle(LED2);
//dma_finish = SET;
} else if(dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_HTF)){
//清除dma中断标记
dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_HTF);
dmaHalfIntCnt ++;
}
}
截取部分Startup_xx.s向量表
g_pfnVectors:
.
.word EXTI4_IRQHandler
.word DMA1_Channel1_IRQHandler
.
我们使用ADC0的CH13/CH15两个通道,这里设置ADC为规则并行模式。
开启扫描模式,ADC会自动采样转换开启的通道。
不要开启连续扫描,采用间断扫描方式。
设定TIM1_CH1为外部触发源,病使能ADC的DMA mode,具体代码如下:
/*!
\brief configure the ADC peripheral
\param[in] none
\param[out] none
\retval none
*/
static void adc_config(void)
{
/* enable ADC0 clock */
rcu_periph_clock_enable(RCU_ADC0);
/* reset ADC */
adc_deinit(ADC0);
/* ADC mode config 工作在规则并行模式 */
//adc_mode_config(ADC_MODE_FREE);
adc_mode_config(ADC_DAUL_REGULAL_PARALLEL);
// 连续转换模式使能
//adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
/* ADC continous function enable */
//adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE);
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
/* 右对齐*/
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
/* 配置规则通道数量 */
//adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 2);
/* ADC规则模式配置;2个adc并行模式下,不可以2个adc同时采集一个通道,adc1数据放在高16位,adc0数据放在低16位*/
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_13, ADC_SAMPLETIME_55POINT5);
adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_15, ADC_SAMPLETIME_55POINT5);
/* adc 使用TIM1 的CH1 的上升沿触发*/
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_EXTTRIG_REGULAR_T1_CH1);
/* adc触发使能*/
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
/* 使能ADC*/
adc_enable(ADC0);
delay_1ms(1U);
/* 标定复位ADC */
adc_calibration_enable(ADC0);
/* ADC DMA 使能*/
adc_dma_mode_enable(ADC0);
}
这里创建一个thread来测试,测试代码如下:
void LED1_Task(void* parameter)
{
gpio_config();
timer1_config();
dma_config();
adc_config();
timer_enable(TIMER1);
while(1){
//gd_eval_led_toggle(LED3);
printf("%s tick:%d \n",__FUNCTION__,xTaskGetTickCount()); //Ben 221024#1
printf("dmaHalfIntCnt:%d \n",dmaHalfIntCnt);
printf("%d %d %d %d\n",adc_value[0],adc_value[1],adc_value[2],adc_value[3]);
vTaskDelay(500);
}
}
在500ms间隔下的测试结果为:
dmaHalfIntCnt:2410
1663 0 1663 1
LED1_Task tick:120965
dmaHalfIntCnt:2420
1663 1 1663 0
LED1_Task tick:121469
dmaHalfIntCnt:2430
1663 0 1661 1
LED1_Task tick:121973
dmaHalfIntCnt:2440
1663 0 1663 0
LED1_Task tick:122477
dmaHalfIntCnt:2450