【GD32F427开发板试用】-油液颗粒检测仪系统(简单版)
本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:时光正好
前言
- 很高兴入选兆易创新GD32F427开发板试用名单,该系列采用Arm® Cortex®-M4内核,处理器主频高达240MHz,可支持算法复杂度更高的嵌入式应用,并具备更快速的实时处理能力。GD32F4新产品配备了512KB到3072KB片上Flash,代码执行零等待区提升至1024KB。还配备了256KB到768KB的SRAM,以业界领先的大容量存储优势支持高级计算、通信网络、人机界面等工业及消费类多元化应用场景。
- 回到正题,本次开发设计的试验为:油液颗粒检测仪系统(简单版),能够实现OLED显示、舵机控制、ADC电压显示、串口等功能。
效果图
系统框架
软件设计
1.OLED显示
使用SPI,1.3寸OLED显示。
extern unsigned char BMP1[]; //OLED字模void OLED_display_info()//显示数据显示界面,电压值,采样值{ OLED_ShowCHinese(10,0,9,16,1); OLED_ShowCHinese(28,0,10,16,1); OLED_ShowCHinese(46,0,58,16,1); OLED_ShowCHinese(64,0,59,16,1); OLED_ShowCHinese(82,0,60,16,1); OLED_ShowCHinese(100,0,61,16,1);delay_1ms(200); OLED_ShowCHinese(0,25,6,16,1); OLED_ShowCHinese(18,25,7,16,1); OLED_ShowCHinese(36,25,8,16,1); OLED_ShowString(54,25,":",16,1); delay_1ms(200); OLED_ShowCHinese(0,48,9,16,1); OLED_ShowCHinese(18,48,10,16,1); OLED_ShowCHinese(36,48,11,16,1); OLED_ShowString(54,48,":",16,1); delay_1ms(200); OLED_Refresh();//更新显存到OLED,重要}void my_testA0(void)//油液颗粒检测仪//首页{ OLED_Clear(0); OLED_ShowCHinese(5,16,49,16,1); OLED_ShowCHinese(23,16,50,16,1); OLED_ShowCHinese(41,16,51,16,1); OLED_ShowCHinese(59,16,52,16,1); OLED_ShowCHinese(77,16,53,16,1); OLED_ShowCHinese(95,16,54,16,1); OLED_ShowCHinese(113,16,55,16,1); OLED_Refresh();}void my_testB1(void)//第一层第一{ OLED_Clear(0); OLED_ShowString(100,0,"<",16,1); OLED_ShowCHinese(0,0,12,16,1); OLED_ShowCHinese(20,0,13,16,1); OLED_ShowCHinese(40,0,14,16,1); OLED_ShowCHinese(60,0,15,16,1); OLED_ShowString(0,16,"1.",16,1); OLED_ShowCHinese(20,16,16,16,1); OLED_ShowCHinese(40,16,17,16,1); OLED_ShowCHinese(60,16,14,16,1); OLED_ShowCHinese(80,16,15,16,1); OLED_ShowString(0,32,"2.",16,1); OLED_ShowCHinese(20,32,18,16,1); OLED_ShowCHinese(40,32,19,16,1); OLED_ShowCHinese(60,32,14,16,1); OLED_ShowCHinese(80,32,15,16,1); OLED_ShowString(0,48,"3.",16,1); OLED_ShowCHinese(20,48,44,16,1); OLED_ShowCHinese(40,48,45,16,1); OLED_ShowCHinese(60,48,14,16,1); OLED_ShowCHinese(80,48,15,16,1); OLED_Refresh(); }void my_testB2(void)//第一层第二{ OLED_Clear(0); OLED_ShowString(100,16,"<",16,1); OLED_ShowCHinese(0,0,12,16,1); OLED_ShowCHinese(20,0,13,16,1); OLED_ShowCHinese(40,0,14,16,1); OLED_ShowCHinese(60,0,15,16,1); OLED_ShowString(0,16,"1.",16,1); OLED_ShowCHinese(20,16,16,16,1); OLED_ShowCHinese(40,16,17,16,1); OLED_ShowCHinese(60,16,14,16,1); OLED_ShowCHinese(80,16,15,16,1); OLED_ShowString(0,32,"2.",16,1); OLED_ShowCHinese(20,32,18,16,1); OLED_ShowCHinese(40,32,19,16,1); OLED_ShowCHinese(60,32,14,16,1); OLED_ShowCHinese(80,32,15,16,1); OLED_ShowString(0,48,"3.",16,1); OLED_ShowCHinese(20,48,44,16,1); OLED_ShowCHinese(40,48,45,16,1); OLED_ShowCHinese(60,48,14,16,1); OLED_ShowCHinese(80,48,15,16,1); OLED_Refresh();}void my_testB3(void)//第一层第三{ OLED_Clear(0); OLED_ShowString(100,32,"<",16,1); OLED_ShowCHinese(0,0,12,16,1); OLED_ShowCHinese(20,0,13,16,1); OLED_ShowCHinese(40,0,14,16,1); OLED_ShowCHinese(60,0,15,16,1); OLED_ShowString(0,16,"1.",16,1); OLED_ShowCHinese(20,16,16,16,1); OLED_ShowCHinese(40,16,17,16,1); OLED_ShowCHinese(60,16,14,16,1); OLED_ShowCHinese(80,16,15,16,1); OLED_ShowString(0,32,"2.",16,1); OLED_ShowCHinese(20,32,18,16,1); OLED_ShowCHinese(40,32,19,16,1); OLED_ShowCHinese(60,32,14,16,1); OLED_ShowCHinese(80,32,15,16,1); OLED_ShowString(0,48,"3.",16,1); OLED_ShowCHinese(20,48,44,16,1); OLED_ShowCHinese(40,48,45,16,1); OLED_ShowCHinese(60,48,14,16,1); OLED_ShowCHinese(80,48,15,16,1); OLED_Refresh();} typedef struct{ unsigned char current;//现在所在页面层数 unsigned char down;//向下翻 unsigned char enter;//进入功能void(*current_operation)();//执行函数。显示图片}key_table;key_table table[30]={//首页 {0,0,1,(*my_testA0)},//第一层 {1,2, 1,(*my_testB1)},//所有模式 {2,3, 7,(*my_testB2)},//冲洗模式 {3,4, 8,(*my_testB3)},//显示模式 {4,5, 9,(*my_testB4)},//串口模式 {5,6, 10,(*my_testB5)},//其他信息 {6,1, 1,(*my_testB6)},//返回//第二层 {7,7,2,(*my_testC5)},//电机转动 {8,8,3,(*my_testC6)},//电压值显示 {9,9,4,(*my_testC7)},//串口开启 {10,11,12,(*my_testC8)},//版本信息 {11,10,5,(*my_testC9)},//返回 }; void(*current_operation_index)(); unsigned char func_index =0; unsigned char last_index =127;
其余显示部分省略,可通过PCtoLCD2002软件,实现图片的代码转换。OLED字库使用SPI通用字库
2.按键
使用板子自带的按键,长按确定,短按为菜单调整。
#define KEY_Press 0 //按键按下#define KEY_Pull 1 //按键放开#define LongPressCount 80 //超过100*5ms=500ms,算做长按//****************************按键返回数值#define KEY1Value 10 //短按#define KEY1LongValue 11 //长按void Scan_Keys_OLED() { uint8_t CountPressTime=0;//长按时间计数 if(gpio_input_bit_get(GPIOA, GPIO_PIN_0)== RESET)//确认 {delay_1ms(10); if(gpio_input_bit_get(GPIOA, GPIO_PIN_0)== RESET) { delay_1ms(10); if(gpio_input_bit_get(GPIOA, GPIO_PIN_0)== RESET) { gpio_bit_set(GPIOC, GPIO_PIN_13); delay_1ms(100); gpio_bit_reset(GPIOC, GPIO_PIN_13); while(gpio_input_bit_get(GPIOA, GPIO_PIN_0)== RESET) { delay_1ms(10);CountPressTime++; } if(CountPressTime>LongPressCount) { CountPressTime=0; func_index = table[func_index].enter; //确认 } else //如果是短按,返回短按的数值 func_index = table[func_index].down; //下 } } } if (func_index != last_index) { current_operation_index = table[func_index].current_operation; (*current_operation_index)();//执行当前操作函数 last_index = func_index; } };
3.PWM
使用PWM控制电机旋转。
#define BSP_PWM_RCU RCU_GPIOA // PWM端口时钟#define BSP_PWM_PORT GPIOA // PWM端口#define BSP_PWM_PIN GPIO_PIN_5// PWM引脚#define BSP_PWM_AF GPIO_AF_1 // PWM引脚复用#define BSP_PWM_TIMER_RCU RCU_TIMER1 // 定时器时钟#define BSP_PWM_TIMER TIMER1 // 定时器#define BSP_PWM_CHANNEL TIMER_CH_0 // 定时器通道static void pwm_gpio_config(void){ /* 使能时钟 */ rcu_periph_clock_enable(BSP_PWM_RCU); /* 配置GPIO的模式 */gpio_mode_set(BSP_PWM_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_PWM_PIN); /* 配置GPIO的输出 */gpio_output_options_set(BSP_PWM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_PWM_PIN); /* 配置GPIO的复用 */ gpio_af_set(BSP_PWM_PORT,BSP_PWM_AF,BSP_PWM_PIN); }void pwm_config(uint16_t pre,uint16_t per){ timer_parameter_struct timere_initpara; // 定义定时器结构体 timer_oc_parameter_struct timer_ocintpara; //比较输出结构体 pwm_gpio_config(); // 使能GPIO rcu_periph_clock_enable(BSP_PWM_TIMER_RCU);// 开启定时器时钟 rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);// 配置 /* 配置定时器参数 */ timer_deinit(BSP_PWM_TIMER); // 复位定时器 timere_initpara.prescaler = pre-1; // 时钟预分频值 PSC_CLK= 200MHZ / 200 = 1MHZ timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 对齐timere_initpara.counterdirection = TIMER_COUNTER_UP; // 计数 timere_initpara.period = per-1; //T = 10000 * 1MHZ = 10ms f = 100HZtimere_initpara.clockdivision = TIMER_CKDIV_DIV1; //分频因子 /* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */ timere_initpara.repetitioncounter = 0; // 重复计数器 0-255 timer_init(BSP_PWM_TIMER,&timere_initpara);// 初始化定时器 /* 配置输出结构体 */ timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 极性 timer_ocintpara.outputstate = TIMER_CCX_ENABLE; /* 配置定时器输出功能 */ timer_channel_output_config(BSP_PWM_TIMER,BSP_PWM_CHANNEL,&timer_ocintpara); /* 配置占空比 */timer_channel_output_pulse_value_config(BSP_PWM_TIMER,BSP_PWM_CHANNEL,0); // 配置定时器通道输出脉冲值timer_channel_output_mode_config(BSP_PWM_TIMER,BSP_PWM_CHANNEL,TIMER_OC_MODE_PWM0); // 配置定时器通道输出比较模式timer_channel_output_shadow_config(BSP_PWM_TIMER,BSP_PWM_CHANNEL,TIMER_OC_SHADOW_DISABLE);// 配置定时器通道输出影子寄存器 /* 只有高级定时器使用 */ timer_auto_reload_shadow_enable(BSP_PWM_TIMER); /* 使能定时器 */ timer_enable(BSP_PWM_TIMER);}void pwm_breathing_lamp(void){ static uint8_t direct = 0; // 方向 static uint16_t value = 0; // 脉冲值 if(direct == 0) // 逐渐变亮 { value += 400; // 值越大 if(value > 10000) direct = 1; // 改变方向 }else // 逐渐变暗 { value -= 400; // 值越小 if(value <= 0) direct = 0; }timer_channel_output_pulse_value_config(BSP_PWM_TIMER,BSP_PWM_CHANNEL,value); // 配置定时器通道输出脉冲值 delay_1ms(50); // 延时50ms}
4.ADC
使用外部光敏电阻模拟电压变化,使用ADC读取数值并计算电压。外部可接传感器电压。
/***********************采样次数 30ADC通道 4***********************/uint16_t gt_adc_val[30][4]; //DMA缓冲区 // ADC Initvoid ADC_DMA_Init(void){ /* DMA初始化功能结构体定义 */ dma_single_data_parameter_struct dma_single_data_parameter; /* 使能GPIOC组时钟 */ rcu_periph_clock_enable(RCU_GPIOC); /* 使能ADC0时钟 */ rcu_periph_clock_enable(RCU_ADC0); /* 使能DMA1时钟 */ rcu_periph_clock_enable(RCU_DMA1); /* 配置ADC时钟 */ adc_clock_config(ADC_ADCCK_PCLK2_DIV4); /* 配置PC1 PC2 PC3 PC4 为浮空模拟输入模式 */ gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1); // PC1 : ADC012_IN11 gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_2); // PC2 : ADC012_IN12 gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_3); // PC3 : ADC012_IN13 gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_4); // PC4 : ADC012_IN14 /* 配置ADC为独立模式 */ adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); /* 使能连续转换模式 */ adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); /* 使能扫描模式 */ adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE); /* 数据右对齐 */ adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); /* ADC0设置为规则组 一共使用2个通道 */ adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, 4); /* ADC规则通道配置:ADC0的通道11,12,13,14的扫描顺序分别为0,1,2,3;采样时间:15个周期 */ /* DMA开启之后 gt_adc_val[x][0] = PC1的数据 gt_adc_val[x][3] = PC4的数据 x的范围0-29 */ adc_routine_channel_config(ADC0, 0, ADC_CHANNEL_11, ADC_SAMPLETIME_15);//PC1 adc_routine_channel_config(ADC0, 1, ADC_CHANNEL_12, ADC_SAMPLETIME_15);//PC2 adc_routine_channel_config(ADC0, 2, ADC_CHANNEL_13, ADC_SAMPLETIME_15);//PC3 adc_routine_channel_config(ADC0, 3, ADC_CHANNEL_14, ADC_SAMPLETIME_15);//PC4 /* ADC0设置为12位分辨率 */ adc_resolution_config(ADC0, ADC_RESOLUTION_12B); /* ADC外部触发禁用, 即只能使用软件触发 */ adc_external_trigger_config(ADC0, ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE); /* 使能规则组通道每转换完成一个就发送一次DMA请求 */ adc_dma_request_after_last_enable(ADC0); /* 使能DMA请求 */ adc_dma_mode_enable(ADC0); /* 使能DMA */ adc_enable(ADC0); /* 等待ADC稳定 */ delay_1ms(1); /* 开启ADC自校准 */ adc_calibration_enable(ADC0); /* 清除 DMA通道0 之前配置 */ dma_deinit(DMA1, DMA_CH0); /* DMA初始化配置 */ dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0)); //设置DMA传输的外设地址为ADC0基地址 dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //关闭外设地址自增 dma_single_data_parameter.memory0_addr = (uint32_t)(gt_adc_val); //设置DMA传输的内存地址为 gt_adc_val数组 dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //开启内存地址自增(因为不止一个通道) dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT; //传输的数据位 为 16位 dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY;//DMA传输方向为 外设往内存 dma_single_data_parameter.number = 4*30; //传输的数据长度为:4个通道 * 每个通道采集30次 dma_single_data_parameter.priority = DMA_PRIORITY_HIGH;//设置高优先级 dma_single_data_mode_init(DMA1, DMA_CH0, &dma_single_data_parameter); //将配置保存至DMA1的通道0 /* DMA通道外设选择 */ /* 数据手册的195页根据PERIEN[2:0]值确定第三个参数,例是100 则为DMA_SUBPERI4 例是010 则为DMA_SUBPERI2 */ /* 我们是ADC0功能,PERIEN[2:0]值为000,故为DMA_SUBPERI0*/ dma_channel_subperipheral_select(DMA1, DMA_CH0, DMA_SUBPERI0); /* 使能DMA1通道0循环模式 */ dma_circulation_enable(DMA1, DMA_CH0); /* 启动DMA1的通道0功能 */ dma_channel_enable(DMA1, DMA_CH0); /* 开启软件触发ADC转换 */ adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL); }//对DMA保存的数据进行平均值计算后输出//传入参数:CHx 第几个扫描的数据 // 根据前面的配置得知:PC1为0 PC2为1 PC3为2 PC4为3//返回数据:对应扫描的ADC值unsigned int Get_Adc_Dma_Value(char CHx){ unsigned char i = 0; unsigned int AdcValue = 0; /* 因为采集30次,故循环30次 */ for(i=0;i<30;i++) { /* 累加 */ AdcValue+=gt_adc_val[i][CHx]; } /* 求平均值 */ AdcValue=AdcValue/30; return AdcValue;}extern uint16_t gt_adc_val[30][4]; //DMA缓冲区/************************void ADC_Init(void);unsigned int Get_ADC_Value(void);**************************/unsigned int Get_Adc_Dma_Value(char CHx);uint8_t str_buff[64]; uint16_t ADC_Value = 0; //ADC临时值uint16_t ADC_Volt = 0; //ADC临时值 unsigned char temp_buff[200]; unsigned int show_buff[4]; void OLED_display_dat()//更新ADC采样数据与换算结果{ sprintf((char *)temp_buff, "%4d",show_buff[0]); OLED_ShowString1(64,3,(uint8_t *)temp_buff); ADC_Value=show_buff[0]; ADC_Volt = ADC_Value * 330 / 4096; //2的8次方256,12次方4096,16次方65536 sprintf((char*)temp_buff, "%d.%d%dV", ADC_Volt/100, (ADC_Volt%100)/10, ADC_Volt%10); OLED_ShowString1(64,6,(uint8_t *)temp_buff);}
5.串口
串口可打印参数
void usart_gpio_config(uint32_t band_rate){ /* 开启时钟 */ rcu_periph_clock_enable(BSP_USART_TX_RCU); rcu_periph_clock_enable(BSP_USART_RX_RCU); rcu_periph_clock_enable(BSP_USART_RCU); /* 配置GPIO复用功能 */ gpio_af_set(BSP_USART_TX_PORT,BSP_USART_AF,BSP_USART_TX_PIN); gpio_af_set(BSP_USART_RX_PORT,BSP_USART_AF,BSP_USART_RX_PIN); /* 配置GPIO的模式 */ /* 配置TX为复用模式 上拉模式 */gpio_mode_set(BSP_USART_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART_TX_PIN); /* 配置RX为复用模式 上拉模式 */ gpio_mode_set(BSP_USART_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART_RX_PIN); /* 配置TX为推挽输出 50MHZ */ gpio_output_options_set(BSP_USART_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART_TX_PIN); /* 配置RX为推挽输出 50MHZ */ gpio_output_options_set(BSP_USART_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_USART_RX_PIN); /* 配置串口的参数 */ usart_deinit(BSP_USART); usart_baudrate_set(BSP_USART,band_rate); usart_parity_config(BSP_USART,USART_PM_NONE); usart_word_length_set(BSP_USART,USART_WL_8BIT); usart_stop_bit_set(BSP_USART,USART_STB_1BIT); /* 使能串口 */ usart_enable(BSP_USART); usart_transmit_config(BSP_USART,USART_TRANSMIT_ENABLE);}/* 发送函数 */void usart_send_data(uint8_t ucch){ usart_data_transmit(BSP_USART,(uint8_t)ucch); while(RESET == usart_flag_get(BSP_USART,USART_FLAG_TBE));}/* 串口发送字符串 */void usart_send_string(uint8_t *ucstr){ while(ucstr && *ucstr) { usart_send_data(*ucstr++); }
主函数
主函数的配置
systick_config(); rcu_periph_clock_enable(RCU_GPIOC); gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_6); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); //KEY_A0初始化 rcu_periph_clock_enable(RCU_GPIOA); gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0); //BEEP_C13初始化 rcu_periph_clock_enable(RCU_GPIOC); gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_13); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13); gpio_bit_set(GPIOC, GPIO_PIN_6); delay_1ms(1000); gpio_bit_reset(GPIOC, GPIO_PIN_6); delay_1ms(1000); OLED_Init(); OLED_ColorTurn(0); //0正常显示 1 黑白相反,反色显示 OLED_Clear(1);//显示全部像素 delay_1ms(500); OLED_Clear(0);//黑屏 OLED_ShowPicture(0,0,128,64,BMP1,1); OLED_Refresh();//更新显存到OLED,重要! delay_1ms(1000); OLED_Clear(0); rcu_periph_clock_enable(RCU_GPIOA); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1); pwm_config(200,10000); // PWM初始化 nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组 systick_config(); ADC_DMA_Init();
While函数
Scan_Keys_OLED(); switch(func_index) //判断系统状态 { case 7: pwm_breathing_lamp();// PWM函数 break; case 8: show_buff[0] = Get_Adc_Dma_Value(0);//根据扫描顺序得知数组[0] = PC1的数据 OLED_display_dat(); OLED_display_info(); break; case 9: delay_1ms(500); break; }