近期由于公司要求更改使用GD32做项目,究其原因也就不多说了。自己之前一直使用KEIL+cubeMX做STM32做开发,STM32的标准库基本都没有怎么用过,突然要改到使用GD的标准库来开发,所以在开发过程中遇到了不少的问题。作为记录供参考吧。
首先为自己做个辩解:项目进展是其一,再加上自己也想快速 的完成项目,所以都是看网上的例程或者他的经验来写自己的驱动程序部分。但是有些弯路并不是所有的人都走过......
使用GD的标准库版本:GD32F4xx_Firmware_Library_V1.4
标准库说明文档:GigaDevice Semiconductor Inc.GD32F4xxARM® Cortex™-M4 32-bit MCU 《固件库使用指南1.0 版本(2018年 12月 )》
芯片使用说明文档:《GigaDevice Semiconductor Inc.GD32F4xxARM® Cortex ™ M4 32 bit MCU适用GD32F405xx 、 GD32F407 xx 、 GD32 F450xx 系列用户手册2.2 版 本(2020 年 3 月)》
弯路1: 不知道标准库的中断入口函数是怎么写的?(例程中也没有明显的标识); 根据自己的经验,一般都是在《gd32f4xx_it.c》这个代码中有写道,但是没有! 答案: 找到GD32对应芯片的启动文件 在 ASM代码中写明了各个中断入口函数的名称 xxx_ccc_IRQHandler 结尾的,xxx标识外设名称,ccc表示通道名称;例如DMA1_Channel4_IRQHandler 表示DMA1 通道4的中断入口函数名称。拷贝到 _it.c文件中即可使用。
弯路2: 针对DMA+ADC+中断+多通道采集 不知道如何配置DMA和ADC? 答案:网上有说到ADC+DMA配置的程序但是有些版本的函数和我现在使用的函数不一样,有些一样,但是也不一定能用,所以一定要仔细区分自己的库函数版本。
话不多说贴代码:验证可以使用的。功能:
- tim4 CH0触发启动DMA,当DMA采集完成所设定的通道数量后,产生中断,设置标志位,关闭TIM4,由外部程序重新开启TIM4后才可以再次开始采样,这样做只是为了调试使用,正常使用过程中,TIM4不需要关闭;
- TIM4对应的A0后为PWM 输出口,在开启定时器后有PWM输出,可以使用示波器查看波形输出,确定定时器是否在触发ADC工作。后期程序中只需要关闭PWM端口输出即可正常使用。
- 通过标志位读取ADC的值。ADC设定为7个通道,规则采样,一次DMA采集一个规则。
//---------------------------------------------------------
void adc_config(void)
{
rcu_periph_clock_enable(RCU_ADC0);
/* config ADC clock */
adc_clock_config(ADC_ADCCK_PCLK2_DIV4);
adc_deinit();
/* ADC channel length config */
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, ADC_CHS);
/* ADC discontinuous mode */
//adc_discontinuous_mode_config(ADC0,ADC_REGULAR_CHANNEL, 4); //使能断续模式时无法使用DMA连续采样
/* ADC regular channel config */
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_11, ADC_SAMPLETIME_144);
adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_8, ADC_SAMPLETIME_144);
adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_3, ADC_SAMPLETIME_144);
adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_12, ADC_SAMPLETIME_144);
adc_regular_channel_config(ADC0, 4, ADC_CHANNEL_14, ADC_SAMPLETIME_144);
adc_regular_channel_config(ADC0, 5, ADC_CHANNEL_15, ADC_SAMPLETIME_144);
adc_regular_channel_config(ADC0, 6, ADC_CHANNEL_2, ADC_SAMPLETIME_144);
/* ADC external trigger source config */
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_FALLING); // EXTERNAL_TRIGGER_FALLING
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_T4_CH0); //TIM4 CH0
/* ADC data alignment config */
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
adc_resolution_config(ADC0, ADC_RESOLUTION_12B);
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE); //ENABLE 将ADC将自动重复启动测试
/* ADC contineous function enable */
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE); // ADC SCAN MODE ENABLE ADC将自动完成一个规则的所有通道数据的转化,并参数一个中断
/* ADC DMA function enable */
adc_dma_mode_enable(ADC0);
adc_dma_request_after_last_enable(ADC0);//
/* enable ADC interface */
adc_enable(ADC0);
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0);
/* ADC software trigger enable */
//adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
//adc_end_of_conversion_config
}
void dma_config(void)
{
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA1);
/* ADC_DMA_channel configuration */
dma_deinit(DMA1,DMA_CH0);
dma_periph_address_config(DMA1,DMA_CH0,(uint32_t )(&ADC_RDATA(ADC0)));
dma_peripheral_address_generation_config(DMA1, DMA_CH0, DMA_PERIPH_INCREASE_DISABLE);
dma_periph_width_config(DMA1, DMA_CH0, DMA_PERIPH_WIDTH_16BIT); //很重要的设置
dma_memory_address_config(DMA1,DMA_CH0, DMA_MEMORY_0, (uint32_t)&adcData); //
dma_memory_address_generation_config(DMA1, DMA_CH0, DMA_MEMORY_INCREASE_ENABLE);
dma_memory_width_config(DMA1,DMA_CH0, DMA_MEMORY_WIDTH_16BIT); //很重要的设置,注意观察区别
dma_priority_config(DMA1,DMA_CH0, DMA_PRIORITY_HIGH);
dma_transfer_direction_config(DMA1, DMA_CH0, DMA_PERIPH_TO_MEMORY);
dma_transfer_number_config(DMA1,DMA_CH0, 7); //和设定的ADC输入采样个数有关系
dma_circulation_enable(DMA1,DMA_CH0); //没有去测试具体功能
dma_channel_subperipheral_select(DMA1, DMA_CH0, DMA_SUBPERI0); //没有去测试具体功能
dma_channel_enable(DMA1,DMA_CH0);
//----------------------------------------------------
//设置dma中断优先级
nvic_irq_enable(DMA1_Channel0_IRQn, 0, 0);
/* enable DMA transfer complete interrupt */
dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INTC_FTFIFC);
dma_interrupt_enable(DMA1,DMA_CH0, DMA_CHXCTL_FTFIE);
}
//---------------------------- ADC 端口配置---------------------------
void init_adcgpio(void)
{
/* enable GPIO clock */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOC);
/* enable ADC clock */
//rcu_periph_clock_enable(RCU_ADC0);
/* enable DMA clock */
//rcu_periph_clock_enable(RCU_DMA1);
/* config ADC clock */
//adc_clock_config(ADC_ADCCK_PCLK2_DIV4);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_3 | GPIO_PIN_2);
gpio_mode_set(GPIOB, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5);
}
//-------------------初始化入口函数----------------------
void init_adc_dam(void)
{
init_adcgpio();
adc_config();
dma_config();
}
//当传输完成足够的数据后,就关闭定时器,设置标志位,进行处理
void DMA1_Channel0_IRQHandler(void)
{
if (dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INTF_FTFIF))
{
//设置标志位 DMA完成了
//清除全部标志位
timer_disable(TIMER4);
test_adc_v();
dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INTC_FTFIFC);
}
}
库函数中有几个坑要注意:
坑1: DMA 配置中配置外设宽度函数, 只能使用DMA_PERIPH_WIDTH_XXX的声明,千万不能使用DMA_MEMORY_WIDTH_xxx MEMORY的声明。同理,在配置MEMORY地址宽度时也不能选错。切记,写错后编译器是不会报错的。但是采集的数据会让.......
dma_periph_width_config(DMA1, DMA_CH0, DMA_PERIPH_WIDTH_16BIT);
dma_memory_width_config(DMA1,DMA_CH0, DMA_MEMORY_WIDTH_16BIT);
坑2:标准库说明文档中,提到关于DMA中断标志清零API函数的标志清零选项中写的不对;下图红色标记是正确的。
坑3:先引用一篇文章,链接:GD32F330 | ADC实例 基于DMA方式 - Tuple - 博客园 (cnblogs.com) 一定要看准库版本信息,文章中的函数和我现在是使用的库不同,我的库,都需要增加一个设备的入口。
坑4: 在坑3提到的文章中,最后博主给出了很好的总结,但是我要再加一条: 仔细看自己的使用的单片机是否有AVCC或者VREF引脚,看你的开发板上是否将这些引脚准确连接? 硬件是软件的基础!!!
弯路3: 切记要多看文档!!! 我之前按照网上的一些例程,直接拿来用,并不能工作,也无法进入DMA中断,除了有入口函数的问题,还有一个重要问题就是DMA是否支持你所使用的设备;例如上面的博主中使用的是DMA0,但是CPU是F3,而我使用的是F4,DMA0是无效的,通过看文档,才发现只有DMA1才支持ADC0和ADC1功能。如下图
而DMA0 支持的如下图:
先记下这些内容,以备今后参考。