基于FreeRTOS的按键控制led,舵机,电机加OLED实时显示对应状态项目(hal库)
该项目作为智能家居的项目前提,是一个基于STM32C8T6的综合控制项目,利用了微控制器的定时器、GPIO 等外设,结合 RTOS(实时操作系统)的消息队列机制,实现了对 LED、舵机、电机的控制,同时使用 OLED 显示屏进行状态显示。
基于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.现在开始主要代码模块分析
- 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); // 防止任务跑太快
}
}
- 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;
}
}
}
- 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清屏
}
- 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 的原理和应用
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)