主页 > 创业  > 

25/2/17<嵌入式笔记>桌宠代码解析

25/2/17<嵌入式笔记>桌宠代码解析

这个寒假跟着做了一个开源的桌宠,我们来解析下代码,加深理解。

代码中有开源作者的名字。可以去B站搜着跟着做。

首先看下main代码

#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "BlueTooth.h" #include "Servo.h" #include "PetAction.h" #include "Face_Config.h" //作者是Sngels_wyh只在抖音与B站 int main(void) { Servo_Init(); OLED_Init();//OLED初始化 BlueTooth_Init();//蓝牙初始化 OLED_ShowImage(0,0,128,64,Face_sleep); OLED_Update(); while(1) { if(Action_Mode==0){Action_relaxed_getdowm();WServo_Angle(90);}//放松趴下 else if(Action_Mode==1){Action_sit();}//坐下 else if(Action_Mode==2){Action_upright();}//站立 else if(Action_Mode==3){Action_getdowm();}//趴下 else if(Action_Mode==4){Action_advance();}//前进 else if(Action_Mode==5){Action_back();}//后退 else if(Action_Mode==6){Action_Lrotation();}//左转 else if(Action_Mode==7){Action_Rrotation();}//右转 else if(Action_Mode==8){Action_Swing();}//摇摆 else if(Action_Mode==9){Action_SwingTail();}//摇尾巴 else if(Action_Mode==10){Action_JumpU();}//前跳 else if(Action_Mode==11){Action_JumpD();}//后跳 else if(Action_Mode==12){Action_upright2();}//站立方式2 else if(Action_Mode==13){Action_Hello();}//打招呼 } }

这比较好理解,就是导入,初始化,和if语句。

PWM代码

PWM是脉冲宽度调制,是一张通过调节方波脉冲的宽度,即占空比来控制能量传递的一种方式

PWM 的本质是通过**"开"和"关"的快速切换**(即高电平和低电平切换),控制信号输出的平均电压和传递的能量,达到模拟信号控制的效果。

完整代码

#include "stm32f10x.h" // Device header void PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启TIM2时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//开启TIM3时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA时钟 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出模式 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_6;//默认PA0是TIM2通道1的复用,PA1是TIM2通道2的复用所以开启这俩IO口... GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); TIM_InternalClockConfig(TIM2);//TIM2切换为内部定时器 TIM_InternalClockConfig(TIM3);//TIM3切换为内部定时器 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//不分频 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数 TIM_TimeBaseInitStructure.TIM_Period=20000-1; TIM_TimeBaseInitStructure.TIM_Prescaler=72-1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCStructInit(&TIM_OCInitStructure); TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//输出比较模式采用PWM1 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse=0;//初始化CCR的值为0 TIM_OC1Init(TIM2,&TIM_OCInitStructure);//TIM2复用通道1开启 TIM_OC2Init(TIM2,&TIM_OCInitStructure);//TIM2复用通道2开启 TIM_OC3Init(TIM2,&TIM_OCInitStructure);//TIM2复用通道3开启 TIM_OC4Init(TIM2,&TIM_OCInitStructure);//TIM2复用通道4开启 TIM_OC1Init(TIM3,&TIM_OCInitStructure);//TIM2复用通道1开启 TIM_Cmd(TIM2,ENABLE);//使能TIM2 TIM_Cmd(TIM3,ENABLE);//使能TIM3 } //作者是Sngels_wyh只在抖音与B站 void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2, Compare);//设置CCR1的值 } void PWM_SetCompare2(uint16_t Compare) { TIM_SetCompare2(TIM2, Compare);//设置CCR2的值 } void PWM_SetCompare3(uint16_t Compare) { TIM_SetCompare3(TIM2, Compare);//设置CCR3的值 } void PWM_SetCompare4(uint16_t Compare) { TIM_SetCompare4(TIM2, Compare);//设置CCR4的值 } void PWM_WSetCompare(uint16_t Compare) { TIM_SetCompare1(TIM3, Compare);//设置尾巴CCR1的值 }

1. 开启时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 知识点:STM32 的时钟树和时钟管理

STM32 的外设,比如定时器 TIM2 和 GPIO,只能在时钟信号可用时工作。你需要 手动启用相应外设的时钟,否则代码运行时会出错。

每个外设的时钟来源于 RCC 模块内的时钟树: TIM2 和 TIM3 是挂在 APB1(低速外设总线)上的外设。GPIOA 则挂在 APB2(高速外设总线)上。 本质上,调用 RCC_APB1PeriphClockCmd 或 RCC_APB2PeriphClockCmd 函数,就是打开对应模块的开关。 结合到代码:为什么需要时钟? GPIO 时钟用于引脚初始化,配置它们为输入、输出或者复用模式。TIM2 和 TIM3 时钟决定定时器模块的时序计数。如果没有打开时钟,TIM2、TIM3 这些模块将无法产生计数,PWM 也就无法工作。 2. 配置 GPIO 引脚 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); 知识点:GPIO 模式和定时器输出 GPIO 引脚可以有多种模式,比如输入、输出、复用功能等。在这里,GPIO_Mode_AF_PP 表示配置为 复用功能推挽输出: 复用功能(AF):GPIO 不处理普通输入输出,而是作为定时器或其他模块的专用引脚(TIM2 的 PWM 信号通过这些引脚输出)。推挽输出(Push Pull):信号切换速度快,适合 PWM 这样高速信号的需求。 如何结合到代码?

代码中配置了 PA0 ~ PA3 和 PA6 为 TIM2 和 TIM3 的 PWM 信号输出引脚。为什么这么分配?

PA0 用于 TIM2_CH1(定时器 2 的通道 1),PA1、PA2、PA3 类似。你可以查 STM32F103 的管脚功能表,找到 GPIO 引脚和定时器通道之间的复用关系。PA6 是 TIM3 的通道 1,一个单独的尾巴 PWM。

注意:GPIO_Speed 设置为 50MHz 是为了提升引脚响应速度。在 PWM 应用中,推挽输出的响应速度直接会影响高频信号的输出质量。

3. 配置定时器时基单元 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); // 配置 TIM2 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); // 配置 TIM3 知识点:PWM 的频率由 TIM_Period 和 TIM_Prescaler 决定 PWM 的周期(也就是信号从高到低变化的时间)取决于两个参数: TIM_Period(ARR):定时器的自动重装载值。计数器从 0 数到这个值时,完成一个周期。TIM_Prescaler(PSC):用于对时钟进行预分频,减慢计数速度。 定时器工作频率的计算公式:

结合到代码:如何配置 PWM 的周期? 代码中 TIM_Prescaler = 72 - 1,假设系统时钟 f系统=72 MHz,则计数器的工作频率为:

(每秒计数 1,000,000 次)。TIM_Period = 20000 - 1,所以 PWM 信号的频率为

这在舵机和电机控制中是一种标准频率。

4. 配置定时器 PWM 输出通道 TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCStructInit(&TIM_OCInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 设置 PWM 模式 1 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; // 初始 CCR 值设为 0 TIM_OC1Init(TIM2, &TIM_OCInitStructure); // TIM2 通道 1 TIM_OC2Init(TIM2, &TIM_OCInitStructure); // TIM2 通道 2 ... 知识点:PWM 模式和比较寄存器 PWM 模式 1(TIM_OCMode_PWM1): 当计数值 CNT<CCRCNT<CCR 时,输出 High(高电平)。当计数值 CNT≥CCRCNT≥CCR 时,输出 Low(低电平)。CCR 是 比较寄存器 的值,控制占空比。 结合到代码:比较值如何影响占空比? 假设当前 ARR = 20000,并将 CCR1 = 1500: 占空比 = CCR1/ARR=1500/20000=7.5%PWM 信号会保持 7.5% 的时间为高电平,92.5% 的时间为低电平。 TIM_OCPolarity 设置为 TIM_OCPolarity_High,表示高电平为有效信号。 5. 调整占空比 void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2, Compare); } 知识点:实时改变占空比 通过修改 TIM2->CCR1 的值,随时调整 TIM2_CH1 输出的占空比。 总结:STM32 的 PWM 工作原理 TIM2 和 TIM3 定时器负责计时,周期由 ARR 和 PSC 决定。将定时器的输出复用到 GPIO 引脚,实现 PWM 信号输出。通过改变 CCR 值实时调整占空比,从而控制设备的速度、亮度或者角度等。

延时函数 #include "stm32f10x.h" /** * @brief 微秒级延时 * @param xus 延时时长,范围:0~233015 * @retval 无 */ void Delay_us(uint32_t xus) { SysTick->LOAD = 72 * xus; //设置定时器重装值 SysTick->VAL = 0x00; //清空当前计数值 SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器 while(!(SysTick->CTRL & 0x00010000)); //等待计数到0 SysTick->CTRL = 0x00000004; //关闭定时器 } /** * @brief 毫秒级延时 * @param xms 延时时长,范围:0~4294967295 * @retval 无 */ void Delay_ms(uint32_t xms) { while(xms--) { Delay_us(1000); } } /** * @brief 秒级延时 * @param xs 延时时长,范围:0~4294967295 * @retval 无 */ void Delay_s(uint32_t xs) { while(xs--) { Delay_ms(1000); } }

代码中包含了三个函数:Delay_us、Delay_ms和Delay_s,分别用于实现微秒级、毫秒级和秒级的延时。

蓝牙模块

代码通过两个串口(USART1 和 USART3)分别接收语音模块和蓝牙模块发来的指令,并根据接收到的命令,切换 "面部表情" 和执行不同的 "动作模式"。

1. NVIC 中断优先级配置 知识点:NVIC NVIC 全称是 Nested Vectored Interrupt Controller(嵌套向量中断控制器)。它是 ARM Cortex-M 核心中的一个模块,用来管理嵌套中断系统。它决定了不同中断的执行顺序(由优先级决定),并支持多级中断嵌套的实现。 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级 核心概念:

抢占优先级(Preemption Priority):

决定了当两个中断同时发生时,哪个任务先被 CPU 执行。数字越小,优先权越高;抢占优先级越高的中断可以打断优先级低的中断。

响应优先级(SubPriority):

当两个中断抢占优先级相同时,响应优先级决定哪个中断先执行。

USART1 的优先级比 USART3 更高:

在代码中,USART1 的抢占优先级为 1,USART3 的为 2。如果语音模块(USART1)和蓝牙模块(USART3)同时接收到数据,语音指令会被先处理。 2. GPIO 初始化与串口通信 知识点:串口通信(UART/USART) 串口通信是一种常见的数据传输方式,允许两个设备之间传输字节流数据。关键引脚: TX(Transmit):发送数据。RX(Receive):接收数据。 在 MCU 中,串口通常用于与外部传感器、PC 或无线模块(如蓝牙)通信。 GPIO 配置代码: GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX 引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX 引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式 核心概念:

TX(传输引脚)设置成复用推挽输出模式:

意义:用于发送数据,要保证信号强度,驱动能力强。推挽输出模式意味着引脚在高电平和低电平之间切换,这种模式能提供较高的驱动电流,适合通信。

RX(接收引脚)设置成浮空输入模式:

意义:用于接收数据,浮空输入使引脚状态完全由外部设备驱动。

配置 USART 通信的总线、波特率、校验方式等:

串口初始化时需要设置通信参数,如波特率(例如 9600bps)、数据位数(8 位)、校验位(无校验)等。 3. 中断系统 知识点:中断的作用 中断是一种硬件机制,可以暂停 CPU 当前的任务,优先执行紧急任务。优点: 节省 CPU 资源:CPU 不需要一直轮询外设是否有新数据,而是等外设发出中断信号时再处理。提升实时性:高优先级任务不需要等待,能及时响应,例如接收数据 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { Res = USART_ReceiveData(USART1); // 根据接收到的数据调用对应控制逻辑 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } 核心概念:

USART_IT_RXNE:

指的是 USART 数据寄存器非空标志,表示有新数据被接收。中断触发条件:串口硬件检测到有数据到达时,触发中断。

USART_ReceiveData() 读取数据:

从串口数据寄存器中获取发送给 MCU 的数据字节。

清除中断标志 USART_ClearITPendingBit():

每次中断处理后,必须手动清除标志位,否则中断会一直触发,导致程序无法执行其他任务。 4. switch-case 和命令解析 知识点:指令解析 串口接收到的每个数据帧(字节)都是一个指令,通过 switch-case 或其他方式进行解析,执行对应的功能。 switch (Res) { case 0x29: Face_Mode = 0; Action_Mode = 0; break; case 0x38: if (SpeedDelay > 120) Face_Mode = 3; Face_Config(); if (SpeedDelay > 100) SpeedDelay -= 20; else SpeedDelay = 200; break; } 核心概念:

指令分类:

每种指令对应一个功能,例如 0x29 是宠物机器人趴下,0x38 是增加运动速度。更改的两个全局变量: Face_Mode:控制机器人表情(例如通过 LED 显示表情或舵机控制动作)。Action_Mode:控制机器人具体动作(例如前进、转弯、摇摆等)。

状态和速度调整:

SpeedDelay 是控制运动速度的延时时间,延时越短,运动越快。每次接收到对应指令后,减少延时,逐步加速。当速度过快时,逻辑会重置为初始值(如 200),避免速度过快失控。 舵机模块 基于 PWM 驱动,控制多个舵机(左上、右上、左下、右下、尾巴)的转动角度。代码中核心是通过改变 PWM 的占空比来设置舵机的目标角度。 1. 定义基础函数:Servo_Init void Servo_Init() { PWM_Init(); } 这个函数是舵机初始化函数,调用了 PWM_Init() 来初始化 PWM(脉冲宽度调制)模块。PWM 的作用: PWM 模块通过生成周期性信号,将特定占空比的脉冲信号发送给舵机。舵机会根据接收到的脉冲信号调节自己的目标角度。 2. 核心函数:Servo_AngleX void Servo_Angle1(float Angle)//左上 { PWM_SetCompare1(Angle / 180 * 2000 + 500); } (1) 舵机角度驱动的原理 市面上常见的舵机(如 SG90)通常使用 PWM 信号控制角度。舵机角度范围通常是 0°-180°,而它响应的 PWM 脉冲宽度范围是 500μs 到 2500μs。 500μs:舵机移动到最小角度(0°)。2500μs:舵机移动到最大角度(180°)。 (2) 公式解释 Angle / 180 * 2000 + 500

这个公式的作用是将舵机目标角度 Angle 转换为对应的 PWM 脉冲宽度:

Angle / 180:将角度归一化到 0 到 1 的比例值。* 2000:将归一化的比例值映射为脉冲宽度(从 0 到 2000 μs)。+ 500:加上最小的基础脉宽(500 μs),以覆盖舵机的实际工作区间(500-2500 μs)。

也就是说:

当 Angle = 0°:PWM宽度=0/180×2000+500=500 μs当 Angle = 180°:PWM宽度=180/180×2000+500=2500 μs

通过改变输入角度值,就能调整 PWM 输出信号,从而控制舵机的转动。

3. 舵机单个控制的函数: (1) 左上舵机(Servo_Angle1): void Servo_Angle1(float Angle)//左上 { PWM_SetCompare1(Angle / 180 * 2000 + 500); } 作用: 调用 PWM_SetCompare1,将期望角度转换为相应的 PWM 输出,使左上舵机转动到指定角度。 4. 总体代码结构与工作流程 模块划分

这段代码涉及两个主要的模块:

PWM 驱动模块(PWM_Init, PWM_SetCompareX):

负责初始化 PWM 定时器,通过改变占空比输出指定 PWM 信号。不同的舵机占用 MCU 的不同 PWM 通道,例如 Compare1、Compare2、Compare3 对应 MCU 内部的独立 PWM 输出。

舵机控制模块(Servo_Init, Servo_Angle1 等):

调用 PWM 模块的 API,将实际需要的角度转换为 PWM 信号宽度,从而直接控制舵机。 舵机控制运动模块

这段代码定义了一个使用舵机控制的「四足机器人」的各类动作,包括站立、趴下、移动、转向、摇摆、跳跃等。它通过调整舵机角度(调用 Servo_AngleX 函数),实现机器人腿部(舵机1到4)和尾巴(舵机尾巴控制)的复杂运动。

这些函数定义了机器人最基本的姿态,包括站立、趴下、坐下等。它们为更复杂的动作奠定了基础。

举个例子:趴下 - Action_relaxed_getdowm()

void Action_relaxed_getdowm(void) { Servo_Angle1(20); Servo_Angle2(20); Delay_ms(150); Servo_Angle3(160); Servo_Angle4(160); } 前腿(舵机1、舵机2)向前伸到 20°。后腿(舵机3、舵机4)向后扳到 160°,使机器人呈趴下的休息姿势。

前进 - Action_advance()

void Action_advance(void)//前进 { while(Action_Mode==4) { // 前腿右甩+后腿左甩 Servo_Angle2(45); Servo_Angle3(45); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; // 前腿左甩+后腿右甩 Servo_Angle1(135); Servo_Angle4(135); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; // 回归站立位置 Servo_Angle2(90); Servo_Angle3(90); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; Servo_Angle1(90); Servo_Angle4(90); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; // 另一侧腿交替动起来 Servo_Angle1(45); Servo_Angle4(45); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; Servo_Angle2(135); Servo_Angle3(135); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; Servo_Angle1(90); Servo_Angle4(90); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; Servo_Angle2(90); Servo_Angle3(90); Delay_ms(SpeedDelay); if(Action_Mode!=4)break; } } 两对腿交替做前后摆动,用「对角线方式」前进。 右前腿(舵机2)+左后腿(舵机3)先向前摆,左前腿(舵机1)+右后腿(舵机4)后摆。动作完成后回到站立位置。另一对对角线腿重复动作,以继续前进。

OLED 显示屏模块

代码可以分为以下几个模块: 1. 底层通信控制

I²C通信实现: OLED 屏幕通过 I²C 协议进行通信,OLED_GPIO_Init 函数初始化了 I²C 接口引脚 (GPIOB_Pin8 和 GPIOB_Pin9),并通过以下三大函数完成 I²C 信号的发送:

OLED_I2C_Start: 发送起始条件。OLED_I2C_Stop: 发送停止条件。OLED_I2C_SendByte: 按位发送一个字节数据。

命令与数据写入: OLED 表现不同于普通外设,需要区分 命令 和 数据:

OLED_WriteCommand: 向 OLED 发送控制命令,用于初始化或配置显示特性。OLED_WriteData: 向 OLED 写入要显示的内容。

总结: 这一部分定义了OLED硬件级功能抽象,间接操作OLED的显存和功能单元。

2. 内存显存管理

操作 OLED 显示的核心是显存 OLED_DisplayBuf,所有绘制操作仅作用于此虚拟显存,只有调用 OLED_Update 函数时,才会将显存内容同步到 OLED 屏。

显存更新:

全屏更新:OLED_Update 遍历显存中所有数据,并将其发送到 OLED。区域更新:OLED_UpdateArea 对显存的任意矩形区域进行更新,以提高效率。

显存操作:

OLED_Clear:清空整个屏幕。OLED_ClearArea:对指定区域清零。

优势: 在显存中先行处理数据,可以最大限度减少 I²C 的传输负担。

3. 基础绘图函数

所有复杂图形的绘制均基于像素操作,OLED_DrawPoint 是最基本的单位,结合以下功能可满足基本绘图需求:

点操作:

OLED_DrawPoint:在屏幕指定位置点亮一个像素。OLED_GetPoint:读取显存中某个像素是否被点亮。

直线绘制: OLED_DrawLine 使用 Bresenham 算法 绘制高效直线,并支持水平线、垂直线和斜线的生成。

4. 复杂图形绘制

这一部分提供丰富的几何图形绘制方法,适用于各种场景。

矩形绘制: 函数 OLED_DrawRectangle 同时支持填充 (OLED_FILLED) 和空心 (OLED_UNFILLED) 两种样式。

圆形绘制: 使用 Bresenham 圆形算法 实现高效的圆形绘制。OLED_DrawCircle 和 OLED_DrawEllipse 进一步扩展到椭圆绘制,并支持填充模式。

三角形绘制: 可以通过 OLED_DrawTriangle 为三角形指定三个顶点,并支持三角形填充。

角弧绘制: 提供 OLED_DrawArc 绘制扇形或部分环形。通过起始角和终止角参数(-180°到180°),可以实现精确的角度绘制。

5. 字符与图像显示

字符显示: 字符通过字模库 (OLED_F8x16 和 OLED_F6x8) 显示两种不同大小字体。OLED_ShowChar 负责单字符显示,OLED_ShowString 可处理字符串。

数字显示: 提供多种格式的数字显示:

普通整数:OLED_ShowNum有符号整数:OLED_ShowSignedNum浮点数:OLED_ShowFloatNum十六进制:OLED_ShowHexNum二进制:OLED_ShowBinNum

图像显示: 使用 OLED_ShowImage 绘制任意图像,支持不规则形状和任意大小(图像定义通过外部数组传入)。

6. 其他功能

区域取反: OLED_Reverse 和 OLED_ReverseArea 提供显存区域内像素的取反功能,用于特效处理。

多边形内点判断: 函数 OLED_pnpoly 判断某点是否位于多边形内部,常用于复杂形状的填充。

1. 初始化

在调用任何显示功能之前,必须执行 OLED_Init 初始化 OLED 硬件。

OLED_Init(); 2. 基础绘图

以下代码显示了如何在屏幕上画一个点、线和矩形:

OLED_DrawPoint(10, 10); // 点亮(10,10)位置的像素 OLED_DrawLine(0, 0, 127, 63); // 画一条从左上到右下的对角线 OLED_DrawRectangle(20, 20, 40, 30, OLED_FILLED); // 绘制一个填充的矩形 OLED_Update(); // 显示绘图结果 3. 显示文本

以下代码绘制字符串和数字:

OLED_ShowString(0, 0, "Hello, OLED!", OLED_8X16); // 显示字符串 OLED_ShowNum(0, 16, 12345, 5, OLED_8X16); // 显示整数 OLED_Update(); // 显示更新 4. 绘制图形

以下代码显示了如何绘制圆形和椭圆:

OLED_DrawCircle(64, 32, 15, OLED_FILLED); // 绘制一个填充的圆形 OLED_DrawEllipse(64, 32, 20, 10, OLED_UNFILLED); // 绘制一个未填充的椭圆 OLED_Update(); 表情模块

通过 OLED 显示屏实现了表情的变化显示,根据不同的 Face_Mode 值,切换预定义的表情图像(从 Face_sleep 到 Face_hello 等)。

代码逻辑流程

代码采用 if-else 条件分支实现,具体流程如下:

清空 OLED 显示屏:

每次切换表情前,调用 OLED_Clear() 将整个屏幕置空,防止残留的像素影响显示。

     2.根据 Face_Mode 的值选择相应的表情图像:

将选定表情图像显示到屏幕上:

调用 OLED_ShowImage(0, 0, 128, 64, Face_X) 将图像数据加载到显存。使用 OLED_Update() 确保显存中的数据同步到 OLED 屏幕。

这就是所有模块的概述分析。

明天在整体讲解一下我的理解。

标签:

25/2/17<嵌入式笔记>桌宠代码解析由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“25/2/17<嵌入式笔记>桌宠代码解析