嵌入式智能门锁项目学习笔记(二):舵机与屏幕显示
嵌入式智能门锁项目学习笔记(二):舵机与屏幕显示
温馨提示:内容源自米醋电子工作室培训课,由本人总结
PS:在本次课程中,我们用舵机的运动来模拟门锁的开关。
本节课用到的物品:
- CH341A串口转通信模块:充当单片机和电脑沟通的“翻译官”。
- SG90舵机:门锁的“机械臂”。
- DC-DC降压电源模块:负责把高电压降到适合元器件的安全电压。
- 12V2A电源适配器:提供充足的动力。
- 1.44英寸 SPI串口屏幕(ST7735S):门锁交互的“显示器”。
一、串口移植
上一讲,我们讲了定时器的移植。那么照猫画虎,这里尝试一下串口的移植:找到串口代码。把他改成串口3,移植到我们的工程,让他能够接收到数据,并+1发送出去。
硬件原理
易错点:在我们的板子上,串口1的引脚是PA9和PA10,他们在板子上还有连着个USB转TTL的芯片,这个芯片还连着TypeC接口,让我们可以下载芯片,收发数据。这里会有问题:串口3是没有板载这样的芯片的,串口3是TTL信号,电脑是USB信号,他们是不能直接通信的(就像两个人语言不通)。我们要用串口3的话,需要单独找一个USB转TTL的模块,和电脑进行连接。
我们拿出CH341A串口转通信模块,跳线帽接到3.3V,蓝色跳线帽跳到TTL哪里,模块的VCC接到单片机的3V3,GND接到单片机的GND(必须共地才能通信),模块的RX连接单片机的TX,TX连单片机的RX(交叉接线:接收连接发送,就像耳朵对准嘴巴)。
我们打开手册系统框图,可以看到串口3挂在APB1总线上,再查询串口3的引脚,可知串口3的Rx是PB11,串口3的TX是PB10。
软件构建
我们打开乡村智居系统的智能中控,打开Uart.c,把串口2的代码复制到我们的工程里:
//串口初始化函数
void Usart2_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART2, ENABLE);
}
//串口快速中断
void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));//串口2快速中断
//串口中断函数
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
temp=USART_ReceiveData(USART2);
}
USART_ClearITPendingBit(USART2, USART_IT_RXNE);
}
然后我们把他改成串口3(所有的2替换为3,GPIOA替换为GPIOB,引脚替换为10和11):
//串口初始化函数
void Usart3_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3 , ENABLE); // 开启串口3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); // 开启GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11; // 配置PB10和PB11
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // 设置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;
USART_Init(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // 开启接收中断
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART3, ENABLE); // 使能串口3
}
//串口快速中断
void USART3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));//串口2快速中断
//串口中断函数
void USART3_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) // 注意:这里原代码有个USART2未完全改掉的遗留
{
temp=USART_ReceiveData(USART3);
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
}
然后在main函数里,我们调用一下串口初始化函数,再在while里写:
while(1)
{
USART_SendData(USART3,'a'); // 不断向电脑发送字符'a'
Delay_Ms(1000); // 每隔1秒发一次
}
然后我们烧录程序,打开stc-isp,在串口助手就可以接收到数据了。那么怎么+1发送出去呢?我们在中断函数里写:
void USART3_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) // 修正为USART3,判断是否收到数据
{
temp=USART_ReceiveData(USART3); // 将接收到的数据存入temp
USART_SendData(USART3,temp+1); // 将temp+1后再发回去
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE); // 清除中断标志位
}
这样就实现我们的目的了。
二、舵机原理介绍
原理讲解
舵机是一个闭环控制的电机,用PWM信号来给他发送数据,控制他转到相应的角度(就像是用特定长度的脉冲作为“咒语”指挥它的机械臂)。
我们要学会找到资料。比方说这里我们用的是SG90舵机,可以去淘宝卖家那里查询资料。由此可知,棕线接GND,红线接+5V,黄线接PWM。舵机这种原件电流是比较大的,不能直接接在开发板上(会拉低单片机电压),我们需要单独的供电。
我们拿出12V2A电源适配器和DC-DC降压电源模块相连,把舵机的GND接到降压模块的GND,把舵机的5V接到降压模块的5V,舵机的pwm接到单片机的pc2(为了方便移植),然后把降压模块的GND接到单片机的GND,实现共地。
我们找到上一讲的有关PC2的代码,将其复制到这一讲里:
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
GPIO_InitTypeDef GPIO_InitStructure = {0};//定义结构体,结构体用来传参
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//打开APP2总线上GPIOC的时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//配置引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//配置模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//配置速度
GPIO_Init(GPIOC, &GPIO_InitStructure);
while(1)
{
}
}
测试
舵机的控制一般需要一个20ms及以上的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度为例,那么对应的控制关系是这样的:
- 0.5ms-0度
- 1.0ms-45度
- 1.5ms-90度
- 2.0ms-135度
- 2.5ms-180度
这里的意思是,我们有一个信号,一直处于低电平,高电平的时间长短(上述时间)决定了舵机转过的角度。而整个高电平低电平加起来的时间要大于20ms。至于没有提到的角度,之间是线性递增的关系。下面,我们要用Delay手动输出这样的波形:
while(1)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_2);
Delay_Ms(20);//低电平维持20ms
GPIO_SetBits(GPIOC, GPIO_Pin_2);
Delay_Us(2500);//高电平维持2500us,也就是2.5ms (理论上会让舵机转到180度)
}
如图所示,我们先手动生成这样的一个波形,来验证一下接线和舵机有没有问题。 上电验证后,我们可以看到,舵机转过了一个大角度,没有问题。舵机成功转动后,我们就可以把刚刚写的“验证代码”删掉了。
pwm移植
然后,我们找到工作室的pwm例程:
#include "debug.h"
// 定义舵机的PWM周期和占空比
#define PWM_PERIOD 20000 // PWM周期为20ms (20000us)
#define PWM_MIN 500 // 最小脉宽为0.5ms (500us)
#define PWM_MAX 2500 // 最大脉宽为2.5ms (2500us)
// 初始化TIM2以生成PWM信号
void TIM2_PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置TIM2的基本参数
TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD - 1; // 设置自动重装值:计满20000次产生一个周期(20ms)
TIM_TimeBaseStructure.TIM_Prescaler = 96 - 1; // 设置预分频器:将96MHz分频,得到1MHz频率,即计数1次为1us
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 配置TIM2的PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = PWM_MIN; // 初始占空比为最小值 (对应0.5ms,0度)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 配置TIM2通道1
TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 使用TIM2的通道1
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // 使能TIM2通道1的预装载寄存器
TIM_ARRPreloadConfig(TIM2, ENABLE); // 使能TIM2的自动重装寄存器预装载
TIM_Cmd(TIM2, ENABLE); // 使能TIM2
}
// 初始化GPIO以输出PWM信号
void GPIO_Init_PWM(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure;
// 配置PA0为复用推挽输出(TIM2通道1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
TIM2_PWM_Init();
GPIO_Init_PWM();
TIM_SetCompare1(TIM2,1500); // 设置脉宽为1.5ms,让舵机转到90度
while(1)
{
}
}
这里的核心逻辑是通过硬件定时器(TIM2)来自动输出PWM波。Prescaler = 96 - 1 将单片机的 96MHz 频率放慢了 96 倍,变成 1MHz(即 1us 计一次数)。Period = 20000 - 1 意味着计满 20000 次(也就是 20ms)作为一个周期。这完美符合了舵机 20ms 周期的要求。TIM_SetCompare1 函数则用来设定高电平的时间。
简单了解这些代码后,把除了main的代码复制到我们的工程里。 注意到,GPIO初始化的是PA0,所以我们把PC2那根线转接到PA0。对应的初始化函数编写完后,我们把占空比调整为2000:
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
TIM2_PWM_Init();
GPIO_Init_PWM();
TIM_SetCompare1(TIM2,2000); // 将占空比改为2000us (2.0ms),对应135度
while(1)
{
}
}
上电烧录程序后,可以发现,舵机正常转动。
三、屏幕
屏幕的作用相当于给门锁加上可视化的“脸”,可以显示系统的各种状态。
例程的移植
我们首先来研究一下屏幕的接线。找到乡村智居系统,沁恒资料,例程,雪龙开发板例程,雪龙资料,例程,spi-lcd,这里就有这块屏幕的使用教程。这里直接说结论,我们把屏幕的GND接到降压模块的GND,屏幕的VCC接到降压模块的3.3V,LED接降压模块的GND,CLK接单片机的PB3,SDI接单片机的PB5,RS接单片机的PD3,RST接降压模块的3.3V,CS接PD4。这样连接好后,上电,屏幕会亮白光。
线连好后,我们就可以开始移植了。这次,我们打开spi-lcd的MounRiver工程。 注意,这个例程是有一个语法错误的
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET); // 结尾多了一个分号,会导致死循环
我们找到这一行,把他注释掉就可以了。此时我们上电,把例程烧录进程序,屏幕会刷新红,绿,蓝,白光,然后显示我 爱你中国的字样,计时器,还有一只企鹅。
下面,我们就要开始进行代码的移植。我们打开第二讲的文件夹,创建一个Driver的文件夹,把他添加到我们的工程里。我们打开例程,找到LCD_Init();的声明,可以看到这个函数在Led.c文件里。所以我们在我们的Driver里添加Led.c和Led.h文件。我们把例程中,所有Led.c的函数,复制到我们的Led.c里,led.h也是同理。完成后,我们把LCD_Init()放在main里,编译,会报很多错。
首先是fatal error: lcd.h: No such file or directory这个问题,说明我们没有添加路径。怎么做呢?右键“User”文件夹,属性,C/C++General,这里就可以添加了。然后我们点击添加,勾选“是一个工作空间的路径”,点击工作空间,找到我们的Driver文件夹,然后确定。再次编译,解决下一个错误:fatal error: font_ascii_16x8.h: No such file or directory,说找不到这个头文件,那我们就需要创建一个,还是在Driver里,新建一个font_ascii_16x8.h的头文件。然后我们去例程里,把相同的文件移植过来,这个问题就解决了。再次编译,就没有任何报错了。
屏幕的使用
屏幕实际上就是128*128个像素点,能够控制每一行每一列的点来显示颜色。我们打开Lcd.h,找到
void LCD_DrawPoint(u16 x,u16 y,u16 color);//在指定位置画一个点
这个函数,就是用来画一个点的。其他函数也类似,我们在Led.h里看注释,然后进行使用即可。比如说,我们不知道用什么颜色,那我们可以跳转到color的宏定义,就可以看到各种颜色。还有需要注意的是,坐标是0-127,不是1-128。
void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color);//指定区域填充颜色
比如这个函数,在指定区域填充颜色,xsta和ysta是横坐标和纵坐标的起始位置,xend和yend是横坐标和纵坐标的终止位置,这样四个点能确定一个矩形。我们可以尝试一下:
LCD_Fill(0,0,127,127,WHITE); // 用白色填满全屏,充当清屏刷底色
LCD_Fill(10,10,20,20,RED); // 画一个红色小方块
由于我们前边下载过例程,这里我们先把整个屏幕填充成白色,再进行操作。这个时候,屏幕就会很清晰的显示白底的左上角有一个小红色正方形。
void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color);//在指定位置画一条线
比如这个函数就是用来划线的(x1,y1)是起始点,(x2,y2)是结束点。我们再试试:
LCD_DrawLine(1,10,1,30,RED); // 从(1,10)画一条垂直的红线到(1,30)
添加这行语句后,小正方形的左边就会出现一条竖着的红线。
利用画线设置动态进度条
unsigned char i = 0;
while(i < 128)
{
LCD_DrawLine(i,10,i,30,RED); // 在当前列画一条竖线
Delay_Ms(100); // 停顿100ms
i++; // 列数+1
}
这里的逻辑是利用 while 循环,从屏幕的第 0 列一直遍历到第 127 列。每次循环时,在对应的横向位置 i 画一条垂直的红线。因为每次画线后都有一个 100ms 的延时,所以肉眼能够看到红色区域一点点向右“生长”,这就是经典加载进度条的实现原理。
烧录程序后,我们可以看到,屏幕上有一个增长的红条。
显示汉字
void LCD_ShowChinese(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示汉字串
void LCD_ShowChinese16x16(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个16x16汉字
void LCD_ShowChinese24x24(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个24x24汉字
void LCD_ShowChinese32x32(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个32x32汉字
这些函数就是用来显示汉字的。而第一个函数,是通过入口参数sizey,来选择下边的函数。你的sizey是16x16,就调用16x16的函数。汉字的显示也是通过像素点一个一个打出来的,但我们不可能真的去设计汉字,我们可以找到字库:
const typFNT_GB16 tfont16[]={
"我",0x00,0x00,0x20,0x02,0x70,0x0A,0x1E,0x12,0x10,0x12,0x10,0x02,0xFF,0x7F,0x10,0x02,
0x10,0x22,0x50,0x22,0x30,0x12,0x18,0x0C,0x16,0x44,0x10,0x4A,0x10,0x51,0xD4,0x60,/*0*/
"爱",0x00,0x00,0x00,0x10,0x80,0x3F,0x7E,0x08,0x44,0x08,0x88,0x04,0xFE,0x7F,0x42,0x40,
0x41,0x20,0xFE,0x1F,0x20,0x00,0xE0,0x0F,0x50,0x08,0x88,0x04,0x04,0x03,0xC2,0x0C,/*1*/
"你",0x00,0x00,0x10,0x01,0x10,0x01,0x10,0x01,0x88,0x7F,0x88,0x40,0x4C,0x20,0x2C,0x04,
0x0A,0x04,0x89,0x14,0x88,0x24,0x48,0x24,0x48,0x44,0x28,0x44,0x08,0x04,0x08,0x05,/*2*/
"中",0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,
0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x10,0x80,0x00,0x80,0x00,0x80,0x00,/*3*/
"国",0x00,0x00,0xFE,0x3F,0x02,0x20,0x02,0x20,0xFA,0x2F,0x82,0x20,0x82,0x20,0xF2,0x27,
0x82,0x20,0x82,0x22,0x82,0x24,0xFA,0x2F,0x02,0x20,0x02,0x20,0xFE,0x3F,0x02,0x20,/*4*/
};
这个字库就有“我””爱”“你”“中””国”这五个字,我们可以试着调用一下。
LCD_ShowChinese(1,1,"我",RED,WHITE,16,0);
我们在第一行,第一列,显示一个汉字“我”,红色字体,白底,大小是16x16,显示模式0(这个暂时不用管)。编译烧录后,由于我们把这句话写在进度条程序的后边,所以这个我字会在进度条结束之后显示。
如果我们想显示别的东西,就需要改字库了。字库怎么添加呢?我们找到乡村智居系统,模块资料,取模软件,打开文字取模软件(PCtoLCD2002),在这个软件里,我们点设置,自定义格式为C51格式,然后点击确定,格式就设置完了。
举个例子,我们生成一个“正在启动”的字库。我们在输入行里输入正在启动,点击生成字模,然后复制字模到我们的字库里:
const typFNT_GB16 tfont16[]={
"我",0x00,0x00,0x20,0x02,0x70,0x0A,0x1E,0x12,0x10,0x12,0x10,0x02,0xFF,0x7F,0x10,0x02,
0x10,0x22,0x50,0x22,0x30,0x12,0x18,0x0C,0x16,0x44,0x10,0x4A,0x10,0x51,0xD4,0x60,/*0*/
"爱",0x00,0x00,0x00,0x10,0x80,0x3F,0x7E,0x08,0x44,0x08,0x88,0x04,0xFE,0x7F,0x42,0x40,
0x41,0x20,0xFE,0x1F,0x20,0x00,0xE0,0x0F,0x50,0x08,0x88,0x04,0x04,0x03,0xC2,0x0C,/*1*/
"你",0x00,0x00,0x10,0x01,0x10,0x01,0x10,0x01,0x88,0x7F,0x88,0x40,0x4C,0x20,0x2C,0x04,
0x0A,0x04,0x89,0x14,0x88,0x24,0x48,0x24,0x48,0x44,0x28,0x44,0x08,0x04,0x08,0x05,/*2*/
"中",0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,
0x84,0x10,0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x10,0x80,0x00,0x80,0x00,0x80,0x00,/*3*/
"国",0x00,0x00,0xFE,0x3F,0x02,0x20,0x02,0x20,0xFA,0x2F,0x82,0x20,0x82,0x20,0xF2,0x27,
0x82,0x20,0x82,0x22,0x82,0x24,0xFA,0x2F,0x02,0x20,0x02,0x20,0xFE,0x3F,0x02,0x20,/*4*/
{"正",0x00,0x00,0xFE,0x3F,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x88,0x00,0x88,0x1F,0x88,0x00,0x88,0x00,0x88,0x00,0x88,0x00,0x88,0x00,0x88,0x00,0xFF,0x7F,0x00,0x00},/*"正",0*/
{"在",0x40,0x00,0x40,0x00,0x20,0x00,0xFF,0x7F,0x10,0x00,0x10,0x02,0x08,0x02,0x0C,0x02,0xEA,0x3F,0x09,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF8,0x7F,0x08,0x00},/*"在",1*/
{"启",0x80,0x00,0x00,0x01,0xF8,0x3F,0x08,0x20,0x08,0x20,0x08,0x20,0xF8,0x3F,0x08,0x00,0x08,0x00,0x08,0x00,0xE8,0x3F,0x24,0x20,0x24,0x20,0x22,0x20,0xE1,0x3F,0x20,0x20},/*"启",2*/
{"动",0x00,0x02,0x00,0x02,0x3E,0x02,0x00,0x02,0x80,0x3F,0x00,0x22,0x7F,0x22,0x04,0x22,0x04,0x22,0x04,0x21,0x12,0x21,0x22,0x21,0xBF,0x20,0xA2,0x20,0x40,0x14,0x20,0x08},/*"动",3*/
};
现在,我们通过刚才讲解的函数,来试一下新生成的字能不能使用:
LCD_ShowChinese(1,1,"正",RED,WHITE,16,0);
LCD_ShowChinese(1+16,1,"在",RED,WHITE,16,0);
LCD_ShowChinese(1+32,1,"启",RED,WHITE,16,0);
LCD_ShowChinese(1+48,1,"动",RED,WHITE,16,0);
烧录程序后,可以看到,这四个字正常显示在屏幕上。
现在,我们就可以做一些有趣的事了。比如,我们想实现:进度条未加载完,显示正在启动,进度条加载完,显示启动成功。 怎么做呢?先在字库里加载“成功”两个字(字库的字不能重复添加),然后再进度条前,编写正在启动四个字,进度条完成后,同样的位置加载启动成功四个字。 代码如下:
LCD_ShowChinese(32,50,"正",RED,WHITE,16,0);
LCD_ShowChinese(32+16,50,"在",RED,WHITE,16,0);
LCD_ShowChinese(32+32,50,"启",RED,WHITE,16,0);
LCD_ShowChinese(32+48,50,"动",RED,WHITE,16,0);
unsigned char i = 0;
while(i < 128)
{
LCD_DrawLine(i,100,i,127,RED);
Delay_Ms(50);
i++;
}
LCD_ShowChinese(32,50,"启",RED,WHITE,16,0);
LCD_ShowChinese(32+16,50,"动",RED,WHITE,16,0);
LCD_ShowChinese(32+32,50,"成",RED,WHITE,16,0);
LCD_ShowChinese(32+48,50,"功",RED,WHITE,16,0);
烧录程序后,可以在自己的板子看到现象。
显示图片
void LCD_ShowPicture(u16 x,u16 y,u16 length,u16 width,const u8 pic[]);//显示图片
我们找到这个函数,就是专门用来显示图片的,前两个入口参数是起始的横纵坐标,第三四个参数是结束的横纵坐标,最后一个参数是图片的数组。所以,为了图片数组,我们需要生成一个pic.h(图片的头文件)。图片取模,我们还是用上次取模软件的路径,只不过这回我们打开图片取模软件。
打开软件,我们选16位真彩色,最大宽度和高度调整为128和128,勾选“包含图像头数据”和”高位在前”,这个软件就设置完成了。请注意:这个软件智能识别gif格式的图片。用这个软件打开图片后,点击保存,会给我们一个大数组。我们把大数组复制到pic.h里,再把数组名扔到刚才的函数里:
LCD_ShowPicture(1,1,128,128,gImage_1);
现在我们编译下载,就能看到我们想要的图片了。
附录:main.c文件
#include "debug.h"
#include "lcd.h"
#include "pic.h"
// 定义舵机的PWM周期和占空比
#define PWM_PERIOD 20000 // PWM周期为20ms (20000us)
#define PWM_MIN 500 // 最小脉宽为0.5ms (500us)
#define PWM_MAX 2500 // 最大脉宽为2.5ms (2500us)
// 初始化TIM2以生成PWM信号
void TIM2_PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置TIM2的基本参数
TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD - 1; // 设置自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = 96 - 1; // 设置预分频器,96MHz/96 = 1MHz (1us)
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 配置TIM2的PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = PWM_MIN; // 初始占空比为最小值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 配置TIM2通道1
TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 使用TIM2的通道1
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // 使能TIM2通道1的预装载寄存器
TIM_ARRPreloadConfig(TIM2, ENABLE); // 使能TIM2的自动重装寄存器预装载
TIM_Cmd(TIM2, ENABLE); // 使能TIM2
}
// 初始化GPIO以输出PWM信号
void GPIO_Init_PWM(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure;
// 配置PA0为复用推挽输出(TIM2通道1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
TIM2_PWM_Init();
GPIO_Init_PWM();
//TIM_SetCompare1(TIM2,2000); // 之前用于测试舵机的语句,被注释保留
LCD_Init();
LCD_Fill(0,0,128,128,WHITE); // 初始化后用白色填满屏幕做底色
//LCD_Fill(10,10,20,20,RED); // 之前用于测试画方块的语句,被注释保留
LCD_ShowChinese(32,50,"正",RED,WHITE,16,0); // 显示启动前的文字
LCD_ShowChinese(32+16,50,"在",RED,WHITE,16,0);
LCD_ShowChinese(32+32,50,"启",RED,WHITE,16,0);
LCD_ShowChinese(32+48,50,"动",RED,WHITE,16,0);
unsigned char i = 0;
while(i < 128) // 循环画线,实现动态进度条效果
{
LCD_DrawLine(i,100,i,127,RED);
Delay_Ms(50);
i++;
}
LCD_ShowChinese(32,50,"启",RED,WHITE,16,0); // 进度条结束后,覆盖显示成功文字
LCD_ShowChinese(32+16,50,"动",RED,WHITE,16,0);
LCD_ShowChinese(32+32,50,"成",RED,WHITE,16,0);
LCD_ShowChinese(32+48,50,"功",RED,WHITE,16,0);
LCD_ShowPicture(1,1,128,128,gImage_1); // 显示准备好的图片
while(1)
{
TIM_SetCompare1(TIM2,2500); // 主循环中让舵机保持在最大脉宽位置
}
}
更多推荐



所有评论(0)