一、硬件构成
1、工具材料
废话少说,一般有两种方法实现OLED多级菜单。1、链表。2、树。
本文主要介绍树结构实现的OLED多家菜单。下面来看硬件搭配
- STM32F103C8T6核心板
- 1.3寸SPI OLED屏幕
- EC11旋转编码器
- 串口模块以及Stlink调试工具
2、硬件连接
-
OLED屏幕———————>B12-B15
-
EC11旋转编码器————>A8、A9以及按键引脚A10
二、代码结构
1、前提要求时下面三个模块可以正常工作:
- 编码器驱动
- OLED驱动(密码:w0p8)
- 串口重定向输出
- 如果上面的三个没有做好,可以看我的其他文章
1、main.c
int main(void)
{
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
OLED_Init();//OLED显示屏初始化引脚函数
OLED_ColorTurn(0);//0正常显示,1 反色显示
OLED_DisplayTurn(0);//0正常显示 1 屏幕翻转显示
menu_init();//菜单初始化
while (1)
{
}
}
2、gpio.c
#include "gpio.h"
/* USER CODE BEGIN 0 */
#include "usart.h"
#include "Tree_OLED.h"
/* USER CODE END 0 */
/*----------------------------------------------------------------------------*/
/* Configure GPIO */
/*----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/** Configure pins as
* Analog
* Input
* Output
* EVENT_OUT
* EXTI
*/
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = EC11_SW_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(EC11_SW_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
/* USER CODE BEGIN 2 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
if(GPIO_Pin==EC11_SW_Pin)
{
menu_item_t *item;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
item=get_menu_item_child(current,NUM);//获取到current的子菜单数组的第NUM个菜单项
item->function();//调用菜单项的回调函数
}
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
3、usart.c
void u1_printf(char *fmt,...){//串口重定向输出
uint8_t tempbuff[256];
uint16_t i;
va_list ap;//相当于定义的参数列表
va_start(ap,fmt);
vsprintf((char *)tempbuff,fmt,ap);//把ap以fmt的格式放入到tempbuff中
va_end(ap);
for(i=0;i<strlen((char *)tempbuff);i++){
while(!__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE));
huart1.Instance->DR = tempbuff[i];
}
while(!__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC));
}
4、Tree_OLED.h中菜单的数据结构
extern int NUM;
#define MAX_MENU_ITEMS 4
typedef struct menu_item {
char name[10];//项的姓名
struct menu_item *parent;//父节点
struct menu_item *children[MAX_MENU_ITEMS];//子节点数组
uint8_t child_count;//孩子节点数量
void (*function)(void);//回调函数
} menu_item_t;
typedef struct menu {
menu_item_t *root;
uint8_t item_count;
} menu_t;
5、部分Tree_OLED.c函数
//调用退出菜单回调函数
void EXIT_Function(void){
current=current->parent;
NUM=0;//使其显示子菜单时显示第一个选项
Show_Menu();
}
void show_OLED_Menu(void){//旋转编码器旋转时在中断执行的函数
Show_Menu();
}
void EC11_SW_DOWN(void){//节点的显示回调函数
current=get_menu_item_child(current,NUM);
NUM=0;//使其显示子菜单时显示第一个选项
Show_Menu();
}
void Show_Menu(void){//显示当前菜单的子菜单项
uint8_t i=0;
OLED_Clear();//清屏
for(i=0;i<current->child_count;i++)
{
if(i==NUM)
{
//u1_printf("%s\r\n","MY");
OLED_ShowString(0,i*18,(uint8_t *)get_menu_item_child(current,i),16,0);
}
else
{
//u1_printf("%s\r\n",(char *)get_menu_item_child(current,i));
OLED_ShowString(0,i*18,(uint8_t *)get_menu_item_child(current,i),16,1);
}
}
OLED_Refresh();
}
void menu_init(void)//菜单初始话函数,可以自己添加
{
menu_item_t *root = create_menu_item((char *)"ROOT", NULL, NULL);//创建节点,
menu_item_t *child1 = create_menu_item((char *)"child1", root, EC11_SW_DOWN);
menu_item_t *child2 = create_menu_item((char *)"child2", root, EC11_SW_DOWN);
menu_item_t *child3 = create_menu_item((char *)"child3", root, EC11_SW_DOWN);
menu_item_t *child4 = create_menu_item((char *)"child4", child1, EC11_SW_DOWN);//添加回调函数
menu_item_t *child5 = create_menu_item((char *)"child5", child1, EC11_SW_DOWN);
menu_item_t *child6 = create_menu_item((char *)"EXIT", child1, EXIT_Function);
//
menu_item_t *child7 = create_menu_item((char *)"child7", child2, EC11_SW_DOWN);
menu_item_t *child8 = create_menu_item((char *)"EXIT", child2, EXIT_Function);
// menu_item_t *child9 = create_menu_item((char *)"子菜单项9", child2, NULL);
add_menu_item(root, child1);//添加加节点到树结构中
add_menu_item(root, child2);
add_menu_item(root, child3);
add_menu_item(child1, child4);
add_menu_item(child1, child5);
add_menu_item(child1, child6);
add_menu_item(child2, child7);
add_menu_item(child2, child8);
// add_menu_item(child2, child9);
//创建菜单
menu_t *menu = create_menu(root);//创建菜单(唯一)
//当前节点是根节点
current=root;
}
5、tim.c中的定时器中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM1)//判断是否是定时器1
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)//判断是否是通道1触发
{
if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))//判断DIR,计数方向向下
{
count=((int16_t)HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1)-1)/4;//根据CNT计数器的值少计数1,所以加一
NUM=((abs(count)%get_menu_item_child_count(current)+1)-1);//把NUM的值控制到该子菜单的数量
}
}
else if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
{
if(!__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))
{
count=((int16_t)HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2)+1)/4;
NUM=((abs(count)%get_menu_item_child_count(current)+1)-1);
}
}
show_OLED_Menu();//每次转动旋转编码器都更新屏幕内容
}
}
三、效果