前言
- 很高兴入选兆易创新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 = 100HZ
timere_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读取数值并计算电压。外部可接传感器电压。
/***********************
采样次数 30
ADC通道 4
***********************/
uint16_t gt_adc_val[30][4]; //DMA缓冲区
// ADC Init
void 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;
}