1. 嵌入式智能门锁开发实战:舵机控制与屏幕显示
作为一名嵌入式开发者,我最近在做一个智能门锁的实战项目。这个项目涉及到多个关键模块的协同工作,其中舵机和显示屏是两个非常重要的组成部分。舵机负责模拟门锁的开关动作,而显示屏则提供人机交互界面。下面我将详细分享这两个模块的实现过程,包括硬件连接、软件编程以及实际调试中的经验教训。
1.1 硬件准备与系统架构
在开始编码之前,我们需要准备好所有必要的硬件组件:
-
CH341A串口转通信模块:这个模块相当于单片机和电脑之间的"翻译官",负责将USB信号转换为TTL电平信号。在实际项目中,我发现这个模块的稳定性直接影响调试效率,建议选择质量可靠的版本。
-
SG90舵机:作为门锁的"机械臂",SG90是一款经济实惠的微型舵机。它的工作电压为4.8-6V,扭矩约为1.6kg/cm,足够模拟门锁开关动作。需要注意的是,舵机工作时电流较大,不能直接连接到开发板供电。
-
电源系统:
- DC-DC降压电源模块:将12V降压到5V和3.3V
- 12V/2A电源适配器:为整个系统提供充足电力
-
显示模块:1.44英寸SPI串口屏幕(ST7735S驱动),分辨率128x128,采用SPI接口通信,占用IO资源少,非常适合嵌入式应用。
重要提示:在连接舵机时,一定要确保电源能够提供足够的电流。我曾经尝试用开发板的5V引脚直接供电,结果导致单片机频繁复位,这就是因为舵机工作时瞬间电流过大造成的。
2. 串口通信模块实现
2.1 串口硬件连接与原理
在我们的开发板上,串口1(PA9/PA10)已经连接了USB转TTL芯片,可以直接通过Type-C接口与电脑通信。但我们需要使用串口3,它没有板载转换芯片,必须外接CH341A模块。
正确的连接方式如下:
| CH341A模块引脚 | 单片机引脚 | 说明 |
|---|---|---|
| VCC(3.3V) | 3V3 | 电源正极 |
| GND | GND | 必须共地 |
| RX | PB11(TX) | 交叉连接 |
| TX | PB10(RX) | 交叉连接 |
这里有个常见错误:初学者经常把RX-RX、TX-TX直连,导致无法通信。记住串口通信的原则是"交叉连接"——发送端接接收端,就像打电话时你的话筒要对准对方的听筒。
2.2 串口软件实现
我们从现有工程中移植串口2的代码,修改为串口3。关键修改点包括:
- 时钟配置:串口3挂在APB1总线上
- 引脚配置:PB10(TX)、PB11(RX)
- 中断向量:USART3_IRQn
以下是完整的串口初始化代码:
c复制void Usart3_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置PB10和PB11为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
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);
}
中断处理函数实现了一个简单的回显+1功能:
c复制void USART3_IRQHandler(void)
{
uint8_t temp;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
{
temp = USART_ReceiveData(USART3);
USART_SendData(USART3, temp + 1); // 接收到的数据+1后发回
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
}
调试技巧:在初次测试串口时,可以先让单片机定时发送固定字符(如'a'),用串口助手查看是否接收正常。确认硬件连接无误后,再测试中断接收功能。
3. 舵机控制模块实现
3.1 舵机工作原理
SG90舵机采用PWM控制,关键参数如下:
- 控制周期:20ms(50Hz)
- 脉冲宽度与角度关系:
- 0.5ms → 0度
- 1.0ms → 45度
- 1.5ms → 90度
- 2.0ms → 135度
- 2.5ms → 180度
实际测试发现,不同舵机可能存在微小差异,建议在实际使用时进行校准。
3.2 硬件连接
正确的连接方式:
- 舵机棕色线 → 降压模块GND
- 舵机红色线 → 降压模块5V输出
- 舵机黄色线(PWM) → 单片机PC2(或其他PWM输出引脚)
- 降压模块GND → 单片机GND(必须共地)
常见问题:舵机不工作或抖动严重。首先检查电源是否足够(建议单独供电),其次检查PWM信号是否符合标准。我曾遇到因电源功率不足导致舵机只能轻微抖动的情况。
3.3 软件实现
我们使用TIM2的PWM功能来控制舵机。关键配置如下:
c复制#define PWM_PERIOD 20000 // 20ms周期
#define PWM_MIN 500 // 0.5ms(0度)
#define PWM_MAX 2500 // 2.5ms(180度)
void TIM2_PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 时基配置:1MHz计数频率(1us/计数)
TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD - 1;
TIM_TimeBaseStructure.TIM_Prescaler = 96 - 1; // 96MHz/96=1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = PWM_MIN; // 初始位置0度
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
void GPIO_Init_PWM(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // TIM2_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
使用时,通过TIM_SetCompare1()函数设置脉冲宽度:
c复制TIM_SetCompare1(TIM2, 1500); // 1.5ms脉冲,舵机转到90度
4. SPI显示屏实现
4.1 硬件连接
1.44英寸SPI屏幕的连接方式:
| 屏幕引脚 | 单片机引脚 | 说明 |
|---|---|---|
| GND | GND | 地线 |
| VCC | 3.3V | 电源 |
| LED | GND | 背光控制 |
| CLK | PB3 | SPI时钟 |
| SDI | PB5 | SPI数据输入 |
| RS | PD3 | 数据/命令选择 |
| RST | 3.3V | 复位(常高) |
| CS | PD4 | 片选 |
4.2 软件驱动移植
从现有例程移植LCD驱动时,需要注意以下几点:
-
文件结构:
- 添加lcd.c和lcd.h到Driver目录
- 添加字库文件font_ascii_16x8.h
- 在工程设置中添加头文件搜索路径
-
常见编译错误解决:
- 缺少头文件:检查路径设置
- 语法错误:如例程中USART中断函数后的多余分号
-
初始化流程:
c复制LCD_Init(); // 初始化屏幕
LCD_Fill(0,0,127,127,WHITE); // 清屏为白色
4.3 显示功能实现
基本绘图函数:
c复制// 画点
LCD_DrawPoint(x, y, color);
// 画线
LCD_DrawLine(x1, y1, x2, y2, color);
// 填充矩形
LCD_Fill(x_start, y_start, x_end, y_end, color);
// 显示字符
LCD_ShowChar(x, y, chr, font_color, back_color, size);
// 显示字符串
LCD_ShowString(x, y, width, height, size, *p, font_color, back_color);
动态进度条实现:
c复制uint8_t i = 0;
while(i < 128) {
LCD_DrawLine(i, 100, i, 127, RED); // 在底部画进度条
Delay_Ms(50);
i++;
}
汉字显示需要先取模。使用PCtoLCD2002软件,设置C51格式,生成字模数据:
c复制const typFNT_GB16 tfont16[] = {
{"正",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},
// 其他汉字...
};
显示汉字:
c复制LCD_ShowChinese(32, 50, "正", RED, WHITE, 16, 0);
图片显示也需要先取模。使用Img2Lcd工具生成图片数组,然后调用:
c复制LCD_ShowPicture(0, 0, 128, 128, gImage_1);
5. 系统集成与优化
将各模块整合后,main函数的主要逻辑:
c复制int main(void)
{
// 初始化系统
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
// 初始化各模块
TIM2_PWM_Init();
GPIO_Init_PWM();
LCD_Init();
LCD_Fill(0,0,127,127,WHITE);
// 显示启动界面
LCD_ShowChinese(32,50,"正",RED,WHITE,16,0);
LCD_ShowChinese(48,50,"在",RED,WHITE,16,0);
LCD_ShowChinese(64,50,"启",RED,WHITE,16,0);
LCD_ShowChinese(80,50,"动",RED,WHITE,16,0);
// 进度条动画
for(uint8_t i=0; i<128; i++) {
LCD_DrawLine(i,100,i,127,RED);
Delay_Ms(50);
}
// 显示启动完成
LCD_ShowChinese(32,50,"启",RED,WHITE,16,0);
LCD_ShowChinese(48,50,"动",RED,WHITE,16,0);
LCD_ShowChinese(64,50,"成",RED,WHITE,16,0);
LCD_ShowChinese(80,50,"功",RED,WHITE,16,0);
// 显示图片
LCD_ShowPicture(0, 0, 128, 128, gImage_logo);
// 主循环
while(1) {
// 门锁开关控制
TIM_SetCompare1(TIM2, 500); // 开门
Delay_Ms(2000);
TIM_SetCompare1(TIM2, 2500); // 关门
Delay_Ms(2000);
}
}
6. 常见问题与解决方案
-
舵机不工作或抖动
- 检查电源是否足够(建议单独5V/2A以上电源)
- 确认PWM信号参数正确(周期20ms,脉宽0.5-2.5ms)
- 检查接线是否正确,特别是GND必须共地
-
屏幕无显示或花屏
- 检查SPI引脚连接是否正确
- 确认初始化序列正确
- 检查背光控制引脚电平
-
串口通信失败
- 确认TX/RX交叉连接
- 检查波特率设置是否一致
- 确保共地
-
程序卡死
- 检查中断优先级配置
- 确认堆栈空间足够
- 排查是否有硬件冲突
在实际项目中,我还遇到了一个有趣的问题:当同时使用SPI屏幕和PWM时,偶尔会出现屏幕闪烁。最终发现是因为SPI和PWM共用同一个定时器资源。解决方案是为每个功能分配独立的定时器资源。
