基于FreeRTOS的按键控制led,舵机,电机加OLED实时显示对应状态项目(hal库)

欢迎观看我的文章

大家好!今天我要分享一个stm32c8t6基于RTOS的一个小项目,基于FreeRTOS的按键控制led,舵机,电机项目,是通过HAL实现的,这个项目作为RTOS的基础项目。大家观看完后可以依据这个项目去实现其他项目,最典型的就是智能家居,在本项目基础上加入温度传感器和WiFi模块就能实现硬件端到手机端的智能家居了。

一、项目概述

基于FreeRTOS的按键控制led,舵机,电机加OLED实时显示对应状态项目(hal库)

第一章: 项目介绍
第二章: HAL库配置设置
第三章: 代码实现详解
第四章: 总结与未来功能

第一章: 项目介绍

本项目基于 FreeRTOS 操作系统,旨在实现运用多任务处理机制。通过按键控制模块的状态来实现不同pwm下 LED 灯、电机、舵机的不同状态,同时通过 OLED 显示屏实时展示设备状态的功能,通过HAL库简化开发。共涉及 5 个任务和 3 个队列,以下是详细总结:

1.项目整体功能
按键控制:通过三个按键(KEY1、KEY2、KEY3)分别控制 LED 灯、舵机和电机的状态。
设备状态切换:每个设备都有多种状态可供切换,如 LED 灯有关闭、50% 亮度、100% 亮度和呼吸灯模式;舵机有关闭、30 度、90 度、120 度和 180 度角度;电机有关闭、50% 速度和 100% 速度。
状态显示:使用 OLED 显示屏实时显示 LED 灯、电机和舵机的当前状态。

2.项目硬件选择
使用的芯片为stm32f103c8t6,舵机为sg90,电机为TB6612 电机驱动,OLED为SSD1306 驱动的 4角 OLED,外加一个led灯和USB串口模块,这些硬件都是网上买的套件里面自带的,我这里就直接用江科大的设备了。

第二章: HAL库配置设置

1.配置对应的芯片
首先你得电脑里面要有STM32CubeMX这个软件,没有的话可以去 ST官方网站 这个网站里面下载,下载和注册什么的可以去搜教程,我这里就不说了,因为看视频下载更快点。
有这个软件后就可以去里面配置了,主页创建一个工程,在这里选择芯片。在这里插入图片描述
2.配置时钟树和时基源
在RCC里要将其配置为这个高速时钟,否则不能配置时钟树。(时钟树按照我下面的图片配置即可)
另外,将SYS里的Debug改为Serial Wire不然不能使用st-link下载程序,还要将时基源改为TIM4(这样就可以避免 RTOS 和 HAL 库抢 SysTick)。
这是因为RTOS 本身需要 SysTick 作为任务调度时基:
比如 FreeRTOS 用 SysTick 产生 1ms 中断,实现任务切换同时,STM32 的 HAL 库也依赖 SysTick 作为延迟函数 HAL_Delay() 的时基。两者同时用 SysTick,会导致中断冲突或延时不准,若是RTOS 调度改了 SysTick 周期,HAL 延时就乱了。
在这里插入图片描述
高速时钟的配置

在这里插入图片描述
时钟树的配置

在这里插入图片描述时基源的配置

3.配置定时器
由于这三个模块都是有pwm控制的,所以要配置对应的定时器来产生pwm,这里我们分别用TIM1的通道1来控制电机,TIM2的通道2来控制LED,TIM3的通道1来控制舵机,还要为其配置对应的分频器和自动重装寄存器:

定时器 分频器(PC) 自动重装寄存器(ARR)
TIM1 36-1 100-1
TIM2 72-1 20000-1
TIM3 720-1 100-1

注意,这里的数值舵机修改要谨慎,因为舵机通常需要 50Hz 的 PWM 信号来控制,即周期为 20ms。
在这里插入图片描述
TIM1的配置

在这里插入图片描述
TIM2的配置

在这里插入图片描述
TIM3的配置

4.配置按键、OLED、串口、电机驱动模块的引脚
按键的引脚:
按键1: B0 (控制LED)
按键2: B10(控制舵机)
按键3: B1 (控制电机)
按键配置为输入模式,加上拉电阻,三个按键都是这样配置。
在这里插入图片描述
按键配置

OLED的引脚:
SDL:B8(时钟线)
SDA:B9(数据线)
OLED配置为推挽输出,既不上拉,也不下拉。
在这里插入图片描述

OLED配置

串口的引脚:
USART1_TX:PA9 (接串口RXD)
USART1_RX:PA10 (接串口TXD)
串口选择异步模式,引脚默认配置即可
在这里插入图片描述
串口配置

电机驱动模块引脚:
IN1:A11
IN2:A12
电机驱动模块配置为推挽输出,不用上拉,也不下拉在这里插入图片描述
电机驱动模块

5.配置freertos,创建任务和队列
根据我们的功能,我们创建5个任务:Key_TASK,Motor_Task,LED_Task,Servo_Task,OLED_Task。
优先级可以用默认的,也可以自己设置。
入口函数为:StartKeyTask,StartMotor,StartLEDTask,StartServoTask,TaskStartOLEDTask。
三个队列:motorQueue,servoQueue,ledQueue,直接改个名字,配置默认即可。
下面是我配置好的,如果还要加任务或者队列的话,直接Add就行

freertos配置

6.开始生成代码文件
配置好工程名字,和IDE就可以生成代码了,IDE我选的MDK-ARM,因为我用的是keil写代码,用其他IDE也可以。最后也可以配置好代码文件的格式,推荐这样配置就可以
在这里插入图片描述
在这里插入图片描述
7.生成代码后先打开生成的文件夹,我们要增加一些文件
在生成的文件夹里面打开Drivers文件夹,进入后在打开inc文件夹,在里面增加这些任务的头文件,因为我并没有用到freertos创建的入口任务,而是自己加了对应的任务文件,这样模块化更好调试和编写。

在这里插入图片描述
在这里插入图片描述
还要打开MDK-ARM文件夹,增加一个Hardware文件夹,在里面创建对应的任务的.c文件(usart.c文件可以不用自己创建,cube会自己生成,这里是我用来测试的文件)。在这里插入图片描述

第三章: 代码实现详解

重点内容: 本项目的代码包含以下几个核心文件:
key_task.c:负责按键检测和处理,根据按键输入向对应的消息队列发送设备状态切换请求。
OLED_task.c:初始化 OLED 显示屏,并循环显示 LED 灯、电机和舵机的状态。
servo_task.c:处理舵机状态队列,根据接收到的状态设置舵机的角度。
led_task.c:处理 LED 状态队列,根据接收到的状态设置 LED 灯的亮度。
motor_task.c:处理电机状态队列,根据接收到的状态设置电机的速度。
OLED.c:提供 OLED 显示屏的底层驱动函数,包括初始化、写命令、写数据、显示字符和数字等功能。
usart.c:配置 USART 串口通信,实现 printf 函数重定向,方便调试信息输出(本代码可以省略,直接在cube自己生成的ussrt.c加上 printf 函数重定向即可)。

1.要在这些自己创建的任务文件里面写代码,首先要把freertos.c文件在cube里生成的任务加上注释,或者删除也可以。

//void StartMotorTask(void *argument)
//{
//  /* USER CODE BEGIN StartMotorTask */
//  /* Infinite loop */
//  for(;;)
//  {
//    osDelay(1);
//  }
//  /* USER CODE END StartMotorTask */
//}

///* USER CODE BEGIN Header_StartServoTask */
///**
//* @brief Function implementing the ServoTask thread.
//* @param argument: Not used
//* @retval None
//*/
///* USER CODE END Header_StartServoTask */
//void StartServoTask(void *argument)
//{
//  /* USER CODE BEGIN StartServoTask */
//  /* Infinite loop */
//  for(;;)
//  {
//    osDelay(1);
//  }
//  /* USER CODE END StartServoTask */
//}

///* USER CODE BEGIN Header_StartLEDTask */
///**
//* @brief Function implementing the BreathingTask thread.
//* @param argument: Not used
//* @retval None
//*/
///* USER CODE END Header_StartLEDTask */
//void StartLEDTask(void *argument)
//{
//  /* USER CODE BEGIN StartLEDTask */
//  /* Infinite loop */
//  for(;;)
//  {
//    osDelay(1);
//  }
//  /* USER CODE END StartLEDTask */
//}

///* USER CODE BEGIN Header_StartKeyTask */
///**
//* @brief Function implementing the Key_TASK thread.
//* @param argument: Not used
//* @retval None
//*/
///* USER CODE END Header_StartKeyTask */
//void StartKeyTask(void *argument)
//{
//  /* USER CODE BEGIN StartKeyTask */
//   /* Infinite loop */
//   for(;;)
//   {
// 		printf("key_task1\r\n");
//     osDelay(1);
//   }
//  /* USER CODE END StartKeyTask */
//}

///* USER CODE BEGIN Header_StartOLEDTask */
///**
//* @brief Function implementing the OLEDtask thread.
//* @param argument: Not used
//* @retval None
//*/
///* USER CODE END Header_StartOLEDTask */
//void StartOLEDTask(void *argument)
//{
//  /* USER CODE BEGIN StartOLEDTask */
//  /* Infinite loop */
//  for(;;)
//  {
//    osDelay(1);
//  }
//  /* USER CODE END StartOLEDTask */
//}

2.现在开始主要代码模块分析

  1. key_task.c
    按键检测:Key_Detect函数通过轮询的方式检测按键状态,采用软件消抖处理,确保按键检测的准确性。
    状态切换:在StartKeyTask函数中,根据按键输入切换 LED 灯、舵机和电机的状态,并将新状态发送到对应的消息队列中。

#include "key_task.h"

//读取按键状态
uint8_t Key_Detect(void)
{
    // 按键优先级:KEY3 > KEY2 > KEY1
    if (KEY3 == GPIO_PIN_RESET) { // 按下为低电平
        HAL_Delay(10); // 去抖动
        if (KEY3 == GPIO_PIN_RESET) return KEY3_PRESS;
    }
    if (KEY2 == GPIO_PIN_RESET) {
        HAL_Delay(10);
        if (KEY2 == GPIO_PIN_RESET) return KEY2_PRESS;
    }
    if (KEY1 == GPIO_PIN_RESET) {
        HAL_Delay(10);
        if (KEY1 == GPIO_PIN_RESET) return KEY1_PRESS;
    }
    return 0;
}
// 按键任务
void StartKeyTask(void *argument)
{
    uint8_t key = 0;
    uint8_t last_key = 0;
    // 初始化LED、马达和舵机状态
    LedState_t ledState = LED_STATE_OFF;
    MotorState_t motorState = Motor_STATE_OFF;
    ServoState_t servoState = Servo_STATE_OFF;
    // 定义osStatus_t变量用于存储消息队列操作的返回值
    osStatus_t res;	
    // 无限循环,持续检测按键状态
    for( ; ; )
    {
        // 检测按键状态
        // 注意:Key_Detect函数返回值为0表示没有按键按下
        // 如果有按键按下,返回对应的按键值
        // KEY1_PRESS = 1, KEY2_PRESS = 2, KEY3_PRESS = 3
        // 这里假设KEY1、KEY2、KEY3是GPIO引脚的宏定义
        // 如果没有按键按下,key将为0
        (void)last_key; // 避免未使用变量警告
        if (key != 0 && last_key == 0) { // 只在按下瞬间响应一次
            if (key == KEY1_PRESS) {
                switch (ledState) {
                    case LED_STATE_OFF:
                        ledState = LED_STATE_50_BRIGHT;
                        break;
                    case LED_STATE_50_BRIGHT:
                        ledState = LED_STATE_100_BRIGHT;
                        break;
                    case LED_STATE_100_BRIGHT:
                        ledState = LED_STATE_BREATH;
                        break;
                    case LED_STATE_BREATH:
                        ledState = LED_STATE_OFF;
                        break;
                    default:
                        ledState = LED_STATE_OFF;
                        break; 
                }
                res = osMessageQueuePut(
                    ledQueueHandle,
                    &ledState,
                    0U,
                    portMAX_DELAY
                );
            }
        }
    if (key != 0 && last_key == 0) {    
       if  (key==KEY2_PRESS){
           switch (servoState) {
               case Servo_STATE_OFF:
                   servoState = Servo_STATE_30_ANGLE;
                   break;
               case Servo_STATE_30_ANGLE:
                   servoState = Servo_STATE_90_ANGLE;
                   break;
               case Servo_STATE_90_ANGLE:
                   servoState = Servo_STATE_120_ANGLE;
                   break;
               case Servo_STATE_120_ANGLE:
                   servoState = Servo_STATE_180_ANGLE;
                   break;
                case Servo_STATE_180_ANGLE:
                   servoState = Servo_STATE_OFF;
                   break;
               default:
                   servoState = Servo_STATE_OFF;
                   break;
           }
              res = osMessageQueuePut(  
                servoQueueHandle,  // CubeMX生成的句柄
                &servoState,         // 要发送的数据
                0U,                // 消息优先级
                portMAX_DELAY      // 超时时间(一直等待)
         );
        }
        }
    if (key != 0 && last_key == 0) {
       if (key == KEY3_PRESS) {
           switch (motorState) {
               case Motor_STATE_OFF:
                   motorState = Motor_STATE_50_SPEED;
                   break;
               case Motor_STATE_50_SPEED:
                   motorState = Motor_STATE_100_SPEED;
                   break;
               case Motor_STATE_100_SPEED:
                   motorState = Motor_STATE_OFF;
                   break;
               default:
                   motorState = Motor_STATE_OFF;
                   break;
           }
              res = osMessageQueuePut(  
                 motorQueueHandle,  // CubeMX生成的句柄
                 &motorState,         // 要发送的数据
                 0U,                // 消息优先级
                 portMAX_DELAY      // 超时时间(一直等待)
              );
       }
    }
       last_key = key; // 记录上一次的按键状态
        osDelay(10);    // 防止任务跑太快
	}
}

  1. led_task.c
    状态处理:StartLEDTask函数中,通过osMessageQueueGet函数从消息队列中获取 LED 灯状态,并根据状态设置 LED 灯的亮度。
#include "led_task.h"

// 定义LED状态变量
//将其定义成全局变量,以便在其他任务中访问
LedState_t ledState = LED_STATE_OFF;
extern TIM_HandleTypeDef htim3;
extern osMessageQueueId_t ledQueueHandle; 
// LED任务
void StartLEDTask(void *argument) {
    LedState_t receivedState;
    osStatus_t res;
    // LED亮度范围 (0-99)
    const uint16_t maxBrightness = 99;
    uint16_t duty = 0;
    int8_t direction = 1;
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0);

    for (;;) {
		// 每次循环检查LED状态队列
        // 检查队列是否有新消息(不阻塞)
        res = osMessageQueueGet(ledQueueHandle, &receivedState, NULL, 0);
        if (res == osOK) {
            ledState = receivedState;
        }
		if(res != osOK && res != osErrorResource) {
			printf("error: %d\r\n", res);
		}
		
		// 根据当前状态设置LED亮度
		// printf("receivedState: %d\r\n", ledState);
        switch (ledState) {
            case LED_STATE_OFF:
                __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0);
                osDelay(10);
                break;
            case LED_STATE_50_BRIGHT:
                __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 50);
                osDelay(10);
                break;
            case LED_STATE_100_BRIGHT:
                __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 100);
                osDelay(10);
                break;
            case LED_STATE_BREATH:
                __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
                duty += direction;
                if (duty >= maxBrightness) { duty = maxBrightness; direction = -1; }
                else if (duty <= 0) { duty = 0; direction = 1; }
                osDelay(10);
                break;
			default:
				__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0);
				osDelay(10);
				break;
        }
    }
}

3.servo_task.c
状态处理:StartServoTask函数中,通过osMessageQueueGet函数从消息队列中获取舵机状态,并根据状态设置舵机的角度。

#include "servo_task.h"

// 定义舵机状态变量
//将其定义成全局变量,以便在其他任务中访问
ServoState_t servoState = Servo_STATE_OFF;
extern TIM_HandleTypeDef htim2;
extern osMessageQueueId_t servoQueueHandle;

// 舵机任务
void StartServoTask(void *argument) {
  ServoState_t receivedState;
  osStatus_t res;
  // 舵机角度范围 (0-180度)
  const uint16_t minAngle = 500;  // 0.5ms (0)
  const uint16_t maxAngle = 2500; // 2.5ms (180)
  const uint16_t midAngle = (minAngle + maxAngle) / 2; // 90度
  
  for(;;) {
		printf("servo_task2\r\n");
    // 每次循环检查舵机状态队列
    // 检查队列是否有新消息(不阻塞)
    res = osMessageQueueGet(servoQueueHandle, &receivedState, NULL, 0);
    if (res == osOK) {
      servoState = receivedState;
    }
    if(res != osOK && res != osErrorResource) {
      printf("error: %d\r\n", res);
    }
    // 根据当前状态设置舵机角度
    // printf("receivedState: %d\r\n", servoState);
    switch (servoState) {
      case Servo_STATE_OFF:
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, minAngle);
        osDelay(10);
        break;
      case Servo_STATE_30_ANGLE:
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, minAngle + (maxAngle - minAngle) * 30 / 180);
        osDelay(10);
        break;
      case Servo_STATE_90_ANGLE:
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, midAngle);
        osDelay(10);
        break;
      case Servo_STATE_120_ANGLE:
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, minAngle + (maxAngle - minAngle) * 120 / 180);
        osDelay(10);
        break;
      case Servo_STATE_180_ANGLE:
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, maxAngle);
        osDelay(10);
        break;
      default:
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, minAngle);
        osDelay(10);
        break;
    }
  }
}

4.motor_task.c
状态处理:StartMotorTask函数中,通过osMessageQueueGet函数从消息队列中获取电机状态,并根据状态设置电机的速度。

#include "motor_task.h"

// 定义电机状态变量
//将其定义成全局变量,以便在其他任务中访问
MotorState_t motorState = Motor_STATE_OFF;
extern TIM_HandleTypeDef htim1;
extern osMessageQueueId_t motorQueueHandle;
// 电机任务
void StartMotorTask(void *argument)
{
  MotorState_t receivedState;
  osStatus_t res;
  // 速度范围 (0-90% 安全范围)
  const uint16_t min_speed = 0;     // 0% 占空比
  const uint16_t max_speed = 900;   // 90% 占空比
  
  for(;;)
  {
		printf("motor_task3\r\n");
    // 正转加速
    MOTOR_FORWARD();
    res = osMessageQueueGet(motorQueueHandle, &receivedState, NULL, 0);
    if (res == osOK) {  
      motorState = receivedState;
    }
    if(res != osOK && res != osErrorResource) {
      printf("error: %d\r\n", res);
    }
    // 根据当前状态设置电机速度
    printf("receivedState: %d\r\n", motorState);
    switch (motorState) {
      case Motor_STATE_OFF:
        MOTOR_STOP();
        osDelay(10);
        break;
      case Motor_STATE_50_SPEED:
        __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, min_speed + (max_speed - min_speed) * 50 / 100);
        osDelay(10);
        break;
      case Motor_STATE_100_SPEED:
        __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, max_speed);
        osDelay(10);
        break;
      default:
        MOTOR_STOP();
        osDelay(10);
        break;
    }
    
  }
}

  1. OLED.c
    底层驱动:提供了 OLED 显示屏的底层驱动函数,包括 I2C 通信、初始化、清屏、显示字符和数字还有汉字等功能。
#include "gpio.h"
#include "OLED_Font.h"

// OLED I2C引脚定义
void OLED_W_SCL(GPIO_PinState x){
	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,x);
}
void OLED_W_SDA(GPIO_PinState x){
	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,x);
}

/*初始化*/
void OLED_I2C_Init(void)
{
	MX_GPIO_Init();
	
    OLED_W_SCL(GPIO_PIN_SET);
    OLED_W_SDA(GPIO_PIN_SET);
}

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
    OLED_W_SDA(GPIO_PIN_SET);
    OLED_W_SCL(GPIO_PIN_SET);
    OLED_W_SDA(GPIO_PIN_RESET);
    OLED_W_SCL(GPIO_PIN_RESET);
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
    OLED_W_SDA(GPIO_PIN_RESET);
    OLED_W_SCL(GPIO_PIN_SET);
    OLED_W_SDA(GPIO_PIN_SET);
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        OLED_W_SDA((Byte & (0x80 >> i)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
        OLED_W_SCL(GPIO_PIN_SET);
        OLED_W_SCL(GPIO_PIN_RESET);
    }
    OLED_W_SCL(GPIO_PIN_SET);	//额外的一个时钟,不处理应答信号
    OLED_W_SCL(GPIO_PIN_RESET);
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x00);		//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);		//从机地址
	OLED_I2C_SendByte(0x40);		//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  OLED显示一个汉字
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~8
  * @param  num 汉字对应的序号
  * @retval 无
  */
void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t num)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 16);		//设置光标位置在上半部分
	for (i = 0; i < 16; i++)
	{
		OLED_WriteData(Hzk1[num][i]);			//显示上半部分内容
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 16);	//设置光标位置在下半部分
	for (i = 0; i < 16; i++)
	{
		OLED_WriteData(Hzk1[num][i + 16]);		//显示下半部分内容
	}
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//关闭显示
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//设置显示开始行
	
	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//设置对比度控制
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/倒转显示

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//开启显示
		
	OLED_Clear();				//OLED清屏
}

  1. OLED_task.c
    初始化:StartOLEDTask函数中调用OLED_Init函数初始化 OLED 显示屏。
    状态显示:循环显示 LED 灯、电机和舵机的状态信息。
#include "OLED_task.h"

#include "OLED.h"
void StartOLEDTask(void *argument)
{
    // 初始化OLED显示
    OLED_Init();
    
    // 无限循环,持续更新OLED显示内容
    // 这里假设OLED_ShowString和OLED_ShowNum是用于显示字符串和数字的函数
    // OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
    // OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
	OLED_Init();
    for(;;)
    {
        OLED_ShowString(1, 1, "LED:");
        OLED_ShowNum(1, 6, ledState, 1);

        OLED_ShowString(2, 1, "Motor:");
        OLED_ShowNum(2, 7, motorState, 1);

        OLED_ShowString(3, 1, "Servo:");
        OLED_ShowNum(3, 8, servoState, 1);

        osDelay(200);
    }
}

7.usart.c
串口配置:配置 USART1 串口通信,实现 printf 函数重定向,方便调试信息输出。
因为在cube生成代码之前我们设置了为每个外设配置.c/.h文件,所以只要在cube自己生成的usart.c里面加入以下代码即可,这些自己添加的代码一定要写在 /* USER CODE BEGIN 0 / 和 / USER CODE END 0 */ 之间,不然cube重新生成代码会把它们清除掉。
另外还要打开编译器里的这个设置,这样才能成功使用printf函数
在这里插入图片描述

/* USER CODE BEGIN 0 */
#include <stdio.h>
//重定向printf函数到USART1
int fputc(int ch, FILE *f)
{
    // Send a single character via USART1
    // This function is used by printf to output characters
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);    
    
    return (ch);
}
/* USER CODE END 0 */

到这里,我们就基本完成了这个项目,由于篇幅过长,大家还需要完整源码的可以直接去我的git仓库
里面下载,里面有完整源码和演示视频之类的文件

第四章: 总结与未来功能

本项目作为一个基础RTOS项目,可以在在本项目的基础上面添加各种功能,也可以按照项目里面的代码编写逻辑写出自己的小项目,以下是我对该项目的总结。

功能拓展
增加传感器支持:添加温度、湿度、光照等传感器,实现环境数据的采集和显示。
增加通信功能:支持蓝牙、Wi-Fi 等无线通信协议,实现远程控制和数据传输。
优化用户交互:添加更多的按键功能,或者使用触摸屏、遥控器等设备,提升用户体验。
项目应用场景
智能家居:可以作为智能家居控制系统的一部分,实现对灯光、窗帘、家电等设备的控制。
机器人控制:用于机器人的舵机控制、电机驱动和状态显示,实现机器人的运动控制和人机交互。
工业自动化:在工业自动化场景中,用于设备的状态监测和控制,提高生产效率和安全性。
教育教学:作为嵌入式系统开发的教学案例,帮助学生学习微控制器的使用、RTOS 的原理和应用

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐