IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> GD32F103调试小记(一)之ADC+DMA -> 正文阅读

[嵌入式]GD32F103调试小记(一)之ADC+DMA

前言

由于芯片外部大坏境,结合自身内部原因,这次开始改用国产32位单片机。近些年来,国产32位单片机确实做的还可以(靠谱的多了起来),且有着如ST这种在MCU32位领域里的行业标杆,上手一个新的单片机也变得容易的多。废话不多说,进入今天的正题。

ADC

ADC,模数转换器,会将模拟信号(连续变化的电压值)转换为数字值,以便在处理和控制系统中使用。特定的外围元器件,可以将温度、湿度、光线亮暗、气压大小等等转化成一个会随之变化的电压信号。再通过它,我们的处理器可以得到一个会随之变化的数值。利用其特性,我们可以间接的得到各种信息并加以处理。ADC的位数是其精度的描述,又或者说是最小分辨率,即数字值变化1对应的模拟信号变化是多大。如一个ADC的位数是12位,参考电压为3.3V,那么其数字值变化1对应的模拟信号电压变化为3.3V/(2^12)≈0.8mV。想要获得更高的精度,可以选择位数跟高的ADC或者降低参考电压。ADC又有许多种类,这里不多做介绍,我们使用的是逐次比较型ADC。

DMA

直接访问存储器,即从一个地方把东西送到另一个地方。这个地方可以是外设,也可以是内存的一个地址。这样在最初配置好后,完全不需要CPU介入,只需要在需要的时候读取对应的内容就行,大大减轻了CPU的负荷。在我看来这是32位单片机真正比8位机强大的最为关键所在。所以在用32位做开发时,我都尽可能的使用DMA去减轻CPU的负荷。

各模块程序编写

罗嗦完使用的外设后,开始进入我们的正题,代码的编写。我用的是GD32F103RCT6(注:请确保自己有一个可以编译的GD32F103工程且里面已经包含了标准库)。

  • 做任何事之前,先配置以下我们的时钟。
  • 注意手册中提醒的ADC模块的最大时钟频率为14MHz
void SystemClock_Reconfig(void)
{
		/* Initializes the CPU, AHB and APB busses clocks */
		rcu_pll_config(RCU_PLLSRC_HXTAL,RCU_PLL_MUL9);				//set PLL's Source and multiple number				
		rcu_system_clock_source_config(RCU_CKSYSSRC_PLL);			//select the PLL to the system clock source	
		
		/* Due to AHB,APB2,APB1 has been initialized in SystemInit();(AHB = SYSCLK,APB2 = AHB/1,APB1 = AHB/2)
		 * We don't need to configure it again if unnecessary.
		 *
		 * CK_APB1 Max is 54MHz and CK_APB2 Max is 108MHz in GD32F10X,
		 * Please ensure your prescaler is not above Max Frequency.
	   */
//		rcu_ahb_clock_config(RCU_AHB_CKSYS_DIV1);					//set AHB Clock is equal to system clock		
//		rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2);					//set CK_APB1 is equal to half AHB clock			Fck_apb1 = 72M / 2 = 36MHz
//		rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1);					//set CK_APB1 is equal to AHB Clock		
		rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);				//set CK_ADCx is equal to CK_APB2/6					Fck_adcx = 72M / 6 = 12MHz
		
		/* Enable all peripherals clocks you need*/
		rcu_periph_clock_enable(RCU_GPIOA);
		rcu_periph_clock_enable(RCU_GPIOB);
		rcu_periph_clock_enable(RCU_GPIOC);
		rcu_periph_clock_enable(RCU_GPIOD);
		rcu_periph_clock_enable(RCU_GPIOE);
		rcu_periph_clock_enable(RCU_GPIOF);
		rcu_periph_clock_enable(RCU_GPIOG);
		
		rcu_periph_clock_enable(RCU_AF);
		rcu_periph_clock_enable(RCU_DMA0);
		rcu_periph_clock_enable(RCU_DMA1);
		rcu_periph_clock_enable(RCU_I2C1);
		rcu_periph_clock_enable(RCU_ADC0);
		rcu_periph_clock_enable(RCU_ADC2);

		/* Timer1,2,3,4,5,6,11,12,13 are hanged on APB1,
		 * Timer0,7,8,9,10 			 are hanged on APB2
		 */
		rcu_periph_clock_enable(RCU_TIMER1);		
}
  • 接着,配置我们的GPIO。
  • 把对应的脚配置成模拟输入
/* the ADC port and pins definition */
#define ADC0_PORT						GPIOA
#define ADC2_PORT						GPIOC
#define ADC0_CH4						GPIO_PIN_4			
#define ADC0_CH5						GPIO_PIN_5
#define ADC0_CH6						GPIO_PIN_6
#define ADC2_CH12						GPIO_PIN_2
#define ADC2_CH13						GPIO_PIN_3

void GPIO_Init(void)
{
	/* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);

	/* demo board LED I/O*/
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
	gpio_bit_reset(GPIOB,GPIO_PIN_4);
	
	/* demo board OLED I/O*/
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
	gpio_bit_write(GPIOB, GPIO_PIN_12, 0);
	gpio_bit_write(GPIOB, GPIO_PIN_13, 0);	
	
	/* demo board IIC I/O*/
#if USED_IIC_SOFT
	gpio_init(IIC_AT24C08_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, IIC_AT24C08_SCL);
	gpio_init(IIC_AT24C08_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, IIC_AT24C08_SDA);
	gpio_bit_write(IIC_AT24C08_PORT, IIC_AT24C08_SCL, 1);
	gpio_bit_write(IIC_AT24C08_PORT, IIC_AT24C08_SDA, 1);	
#else 
	gpio_init(IIC_AT24C08_PORT,GPIO_MODE_AF_OD,GPIO_OSPEED_50MHZ,IIC_AT24C08_SCL | IIC_AT24C08_SDA);
#endif

	/* demo board ADC I/O */
	gpio_init(ADC0_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC0_CH4 | ADC0_CH5 | ADC0_CH6);
	gpio_init(ADC2_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC2_CH12 | ADC2_CH13);  

}
  • 接着配置DMA。
  • ADC0对应的DMA通道为DMA0_CH0。
  • ADC2对应的DMA通道为DMA1_CH4。
  • 方向都是从具体外设的数据寄存器到我们自己定义的变量里面。
  • 次数=总的通道数x单个通道采集多少次。
  • 最后记得打开DMA循环模式。这样在开启转换后,我们ADCn_Buffer[ ][ ]里面的值一直会是最新的。
/* ADC0 and ADC2 ADrawbuffer definition */
#define ADC0_CHANNELS	3 
#define ADC0_NUMBER		16
#define ADC2_CHANNELS	2
#define ADC2_NUMBER		16

uint32_t ADC0_Buffer[ADC0_CHANNELS][ADC0_NUMBER] = {0};
uint32_t ADC2_Buffer[ADC2_CHANNELS][ADC2_NUMBER] = {0};

void DMA_Init(void)
{
	dma_parameter_struct dma_init_ADC0;
	dma_parameter_struct dma_init_ADC2;

	/* deinitialize DMA channel */
	dma_deinit(DMA0, DMA_CH0);
	dma_deinit(DMA1, DMA_CH4);

	/* initialize DMA0 channeln(ADC0) */							
	dma_init_ADC0.direction = DMA_PERIPHERAL_TO_MEMORY;
	dma_init_ADC0.memory_addr = (uint32_t)(&ADC0_Buffer);
	dma_init_ADC0.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_ADC0.memory_width = DMA_MEMORY_WIDTH_32BIT;
	dma_init_ADC0.number = (uint32_t)ADC0_CHANNELS*ADC0_NUMBER;
	dma_init_ADC0.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
	dma_init_ADC0.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_ADC0.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
	dma_init_ADC0.priority = DMA_PRIORITY_MEDIUM;
	dma_init(DMA0, DMA_CH0, &dma_init_ADC0);	
	dma_circulation_enable(DMA0, DMA_CH0);	
	
	/* initialize DMA1 channeln(ADC2) */						
	dma_init_ADC2.direction = DMA_PERIPHERAL_TO_MEMORY;
	dma_init_ADC2.memory_addr = (uint32_t)(&ADC2_Buffer);
	dma_init_ADC2.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_ADC2.memory_width = DMA_MEMORY_WIDTH_32BIT;
	dma_init_ADC2.number = (uint32_t)ADC2_CHANNELS*ADC2_NUMBER;
	dma_init_ADC2.periph_addr = (uint32_t)(&ADC_RDATA(ADC2));
	dma_init_ADC2.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_ADC2.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
	dma_init_ADC2.priority = DMA_PRIORITY_LOW;
	dma_init(DMA1, DMA_CH4, &dma_init_ADC2);	
	dma_circulation_enable(DMA1, DMA_CH4);						
	
	/* enable all DMA channels you need */
	dma_channel_enable(DMA0,DMA_CH0);
	dma_channel_enable(DMA1,DMA_CH4);		
}
  • 最后配置我们的ADC。
  • 这里因为用到了DMA所以得把ADC的转换模式配置在连续扫描模式下。
  • 触发源配置成软件触发。
  • 同步模式配置成独立模式。(当然也可以设置成规则并行模式)。
  • 配置通道长度及规则通道转换顺序。
  • 使能ADCn后记得延时一小段时间后再进行自校准。
  • 最后记得把DMA功能开启。
void ADCx_Init(void)
{
	/* reset ADC */
	adc_deinit(ADC0);
	adc_deinit(ADC2);
	/* ADC scan mode function enable */
	adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
	adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
	adc_special_function_config(ADC2, ADC_SCAN_MODE, ENABLE);
	adc_special_function_config(ADC2, ADC_CONTINUOUS_MODE, ENABLE);
	/* ADC trigger config */
	adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
	adc_external_trigger_source_config(ADC2, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE); 
	/* ADC data alignment config */
	adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
	adc_data_alignment_config(ADC2, ADC_DATAALIGN_RIGHT);
	/* configure the ADC sync mode */
	adc_mode_config(ADC_MODE_FREE);  	//   ADC_DAUL_REGULAL_PARALLEL  ADC_MODE_FREE
	
  /* ADC channel length config */
	adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 3);
	adc_channel_length_config(ADC2, ADC_REGULAR_CHANNEL, 2);
  
	/* ADC regular channel config */
	adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_4, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_5, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_6, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC2, 0, ADC_CHANNEL_12, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC2, 1, ADC_CHANNEL_13, ADC_SAMPLETIME_71POINT5);
  
	/* ADC external trigger enable */
	adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
	adc_external_trigger_config(ADC2, ADC_REGULAR_CHANNEL, ENABLE);
	
//	/* ADC internal tempsensor and vrefint enable */
//	adc_tempsensor_vrefint_enable();
   
	/* enable ADC interface */
	adc_enable(ADC0);
	delay_ms(10);    
	/* ADC calibration and reset calibration */
	adc_calibration_enable(ADC0);
	/* enable ADC interface */
	adc_enable(ADC2);    
	delay_ms(10);
	/* ADC calibration and reset calibration */
	adc_calibration_enable(ADC2);
    
	/* ADC DMA function enable */
	adc_dma_mode_enable(ADC0);
	/* ADC DMA function enable */
	adc_dma_mode_enable(ADC2);
}

主函数程序

  • 在系统时钟配置完后,先配置我们的GPIO和DMA,再配置具体用到DMA的外设,不然有可能有问题(GD32对配置顺序要求比较高)。
  • 在进入while(1)循环之前,使用adc_software_trigger_enable();开始触发我们的AD转换。
  • 进入while(1)之后,ADC会不停的转换,而DMA也会不停的把ADC转换完成后的数据放到我们指定的变量里。
  • 这里我用OLED做的显示,每秒更新下显示的内容。
  • 另外说下,由于我是用的网上买的开发板做的测试,配置好的AD口都是浮空的,导致给其中一路(如ADC0_CH3)加电压时,另外两路(ADC0_CH4、CH5)的变量BUFF里也会有差不多的AD值。当给每路施加不同的电压时,读出来的数据就都是对应正确AD值的了。这是由于AD口没有外接实际电路导致的,不必意外。
int main(void)
{
		/* Define and initialize all Variables */
		uint8_t memory_s[16]={1,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30};
		uint8_t memory_r[16]={0};

		/* Reset of all peripherals, Initializes the Systick. */
		SystemTick_Init();
		/* Initializes all peripherals clock you need */
		SystemClock_Reconfig();		
		/* Initialize all configured peripherals */
		GPIO_Init();
		DMA_Init();
		Timer1_Init();
	  	OLED_Init();			       
		ADCx_Init();
		
		
#if USED_IIC_SOFT		
//		I2C_WritePage(SLAVE_ADDRESS0,5,0,memory_s);
//		delay_ms(100);
		I2C_ReadPageBytes(SLAVE_ADDRESS0,5,0,memory_r);
#else
		I2C1_Init();
		delay_ms(10);
		I2C_WriteOneByte(SLAVE_ADDRESS0,5,5,138);
#endif
		/* trigger start ADCx_channels conversion */
		adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
		adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL);
		while(1)
		{			
				
				if(Module.LED_REFRESH)
				{
						gpio_bit_set(GPIOB,GPIO_PIN_4);					
				}
				else
				{
						gpio_bit_reset(GPIOB,GPIO_PIN_4);
				}	
				
				if(Module.OLED_REFRESH)
				{
						Module.OLED_REFRESH = 0;
//						memory_r[3] = I2C_ReadOneByte(SLAVE_ADDRESS0,5,5);
						I2C_ReadPageBytes(SLAVE_ADDRESS0,5,0,memory_r);
					
						OLED_ShowNum(24,0,ADC0_Buffer[0][0],5,16);
						OLED_ShowNum(24,2,ADC0_Buffer[2][0],5,16);
						OLED_ShowNum(24,4,ADC2_Buffer[1][0],5,16);
						OLED_ShowNum(24,6,memory_r[3],5,16);
					
						OLED_ShowNum(96,0,ADC0_Buffer[1][0],4,16);
						OLED_ShowNum(96,2,ADC2_Buffer[0][0],4,16);
						OLED_ShowNum(96,4,memory_r[2],4,16);
						OLED_ShowNum(96,6,memory_r[4],4,16);					
				}
		}
    return 0;
}

总结

总的来说,毕竟是STM32的孪生兄弟(异父异母?),只要熟悉STM32,再做GD32其实也没多大区别。
最后,今天可是七夕,我居然在码这个文章。兄弟们,看完不给我个赞,对得起我的女朋友吗?

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-15 15:45:24  更:2021-08-15 15:46:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 23:42:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码