> 文档中心 > 【GD32F427开发板试用】-油液颗粒检测仪系统(简单版)

【GD32F427开发板试用】-油液颗粒检测仪系统(简单版)


本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:时光正好

前言

  • 很高兴入选兆易创新GD32F427开发板试用名单,该系列采用Arm® Cortex®-M4内核,处理器主频高达240MHz,可支持算法复杂度更高的嵌入式应用,并具备更快速的实时处理能力。GD32F4新产品配备了512KB到3072KB片上Flash,代码执行零等待区提升至1024KB。还配备了256KB到768KB的SRAM,以业界领先的大容量存储优势支持高级计算、通信网络、人机界面等工业及消费类多元化应用场景。
  • 回到正题,本次开发设计的试验为:油液颗粒检测仪系统(简单版),能够实现OLED显示、舵机控制、ADC电压显示、串口等功能。

效果图

系统框架

【GD32F427开发板试用】-油液颗粒检测仪系统(简单版)

软件设计

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;    }