单片机学习笔记
整理日期:2026-05-28 作者:个人学习笔记
目录
一、单片机概述
1.1 什么是单片机
单片机(Microcontroller Unit,MCU)是一种将中央处理器(CPU)、存储器、输入/输出接口集成在同一芯片上的微型计算机系统。
主要特点:
- 体积小、功耗低、成本低
- 集成度高、可靠性强
- 适用于嵌入式系统
- 广泛应用于工业控制、智能家电、汽车电子等领域
1.2 主流单片机系列
| 系列 | 内核 | 位数 | 特点 | 适用场景 |
|---|---|---|---|---|
| 51系列 | 8051 | 8位 | 经典入门、结构简单 | 教学、小型控制 |
| STM32 | ARM Cortex-M | 32位 | 性能强、资源丰富 | 工业、消费电子 |
| AVR | AVR | 8位 | 高效、RISC架构 | 智能控制、Arduino |
| PIC | PIC | 8/16/32位 | 可靠性高 | 汽车、工业 |
| MSP430 | MSP430 | 16位 | 超低功耗 | 电池供电设备 |
1.3 学习路线图
┌─────────────────────────────────────────────────────────┐
│ 单片机学习路线 │
├─────────────────────────────────────────────────────────┤
│ │
│ 入门阶段 进阶阶段 高级阶段 │
│ ───────── ───────── ───────── │
│ 数字电路基础 → C语言进阶 → RTOS系统 │
│ 51单片机 → STM32 → 嵌入式Linux │
│ 基础外设 → 通信协议 → 项目实战 │
│ 基础实验 → 中断/DMA → 优化与调试 │
│ │
└─────────────────────────────────────────────────────────┘
二、51单片机基础
2.1 51单片机简介
51单片机是指基于Intel 8051内核的一系列单片机,是最经典的8位单片机之一。
主要型号:
- AT89C51/AT89C52(Atmel)
- STC89C52RC(宏晶科技)
- STC15系列(增强型)
2.2 硬件结构
2.2.1 引脚分布
40引脚DIP封装:
- VCC(40脚):电源正极(+5V)
- GND(20脚):电源地
- P0口(32-39脚):8位双向I/O口(需上拉电阻)
- P1口(1-8脚):8位双向I/O口(内置上拉)
- P2口(21-28脚):8位双向I/O口(内置上拉)
- P3口(10-17脚):8位双向I/O口(复用功能)
2.2.2 内部结构
┌─────────────────────────────────────────┐
│ 8051 内部结构 │
├─────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 振荡器 │───→│ 时钟电路 │ │
│ └─────────┘ └─────────┘ │
│ ↓ │
│ ┌─────────────────────────────────┐ │
│ │ CPU核心 │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ ALU│ │寄存器│ │PC │ │ │
│ │ └────┘ └────┘ └────┘ │ │
│ └─────────────────────────────────┘ │
│ ↓ ↓ ↓ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 4KB │ │ 128B │ │ 21个 │ │
│ │ Flash │ │ RAM │ │ 特殊功能寄存器│ │
│ └────────┘ └────────┘ └────────┘ │
│ │
└─────────────────────────────────────────┘
2.2.3 存储器结构
程序存储器(ROM):
- 4KB/8KB 片内Flash
- 地址:0000H~0FFFH/1FFFH
数据存储器(RAM):
- 128B 片内RAM(00H~7FH)
- 128B 特殊功能寄存器(80H~FFH)
2.3 C51编程基础
2.3.1 数据类型
| 数据类型 | 位宽 | 取值范围 |
|---|---|---|
bit | 1位 | 0~1 |
sbit | 1位 | 0~1(特殊功能寄存器位) |
char | 8位 | -128~127 |
unsigned char | 8位 | 0~255 |
int | 16位 | -32768~32767 |
unsigned int | 16位 | 0~65535 |
long | 32位 | -2147483648~2147483647 |
float | 32位 | ±3.4e±38 |
2.3.2 特殊功能寄存器(SFR)
#include <reg52.h>
// 常用SFR定义
sbit P10 = P1^0; // P1.0引脚
sbit LED = P1^0; // LED灯
sbit BUTTON = P3^2; // 按键
void main()
{
P1 = 0xFF; // 初始化P1口
while(1)
{
// 主循环
}
}2.3.3 头文件说明
| 头文件 | 适用型号 |
|---|---|
<reg51.h> | 标准51单片机 |
<reg52.h> | AT89C52/STC89C52 |
<STC89C5xRC.h> | STC89C52RC |
2.4 I/O口操作
2.4.1 点亮第一个LED
硬件连接:
┌─────────────┐ ┌─────────┐
│ P1.0 │────────→│ LED │────━┐
└─────────────┘ └─────────┘ │
│
┌────────┴────────┐
│ VCC (5V) │
└─────────────────┘
注:LED需要串联限流电阻(330Ω)
代码实现:
#include <reg52.h>
#define LED P1^0
void delay(unsigned int ms)
{
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--); // 12MHz晶振,约1ms
}
void main()
{
LED = 0; // 输出低电平,点亮LED
while(1);
}Proteus仿真图:
2.4.2 流水灯效果
#include <reg52.h>
#define LED_PORT P1
void delay(unsigned int ms)
{
unsigned int i, j;
for(i = ms; i > 0; i--)
for(j = 110; j > 0; j--);
}
void main()
{
unsigned char i;
LED_PORT = 0xFE; // 1111 1110
while(1)
{
for(i = 0; i < 8; i++)
{
LED_PORT = ~(0x01 << i); // 循环左移
delay(200);
}
}
}2.5 中断系统
2.5.1 51中断结构
┌─────────────────────────────────────────┐
│ 51单片机中断结构 │
├─────────────────────────────────────────┤
│ │
│ 中断源 中断号 优先级 │
│ ───────────────────────────────── │
│ INT0 (外部中断0) 0 高 │
│ T0 (定时器0) 1 │
│ INT1 (外部中断1) 2 │
│ T1 (定时器1) 3 │
│ UART (串口) 4 低 │
│ │
└─────────────────────────────────────────┘
2.5.2 外部中断示例
#include <reg52.h>
sbit LED = P1^0;
sbit KEY = P3^2; // INT0
void exint0() interrupt 0 // 外部中断0服务函数
{
LED = ~LED; // LED状态翻转
}
void main()
{
IT0 = 1; // 设置下降沿触发
EX0 = 1; // 开启外部中断0
EA = 1; // 开启总中断
while(1);
}2.6 定时器/计数器
2.6.1 定时器配置
#include <reg52.h>
unsigned char count = 0;
void timer0() interrupt 1 // 定时器0中断
{
TH0 = 0x4C; // 重新装载初值(50ms@12MHz)
TL0 = 0x00;
count++;
if(count >= 20) // 1秒
{
count = 0;
P1 = ~P1; // LED闪烁
}
}
void main()
{
TMOD = 0x01; // 定时器0,模式1(16位定时器)
TH0 = 0x4C; // 初值50ms
TL0 = 0x00;
ET0 = 1; // 开启定时器0中断
TR0 = 1; // 启动定时器0
EA = 1;
while(1);
}2.6.2 定时器初值计算
定时器初值计算公式:
THx = (65536 - 定时时长) / 256
TLx = (65536 - 定时时长) % 256
示例:12MHz晶振,定时1ms
65536 - 1000 = 64536 = 0xFC18
TH0 = 0xFC
TL0 = 0x18
2.7 串口通信
2.7.1 串口初始化
#include <reg52.h>
void uart_init()
{
TMOD = 0x20; // 定时器1,模式2(8位自动重装)
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
TR1 = 1; // 启动定时器1
SCON = 0x50; // 串口模式1,8位UART,允许接收
ES = 1; // 开启串口中断
EA = 1; // 开启总中断
}
void uart_send_byte(unsigned char dat)
{
SBUF = dat;
while(!TI); // 等待发送完成
TI = 0;
}
void uart_send_string(unsigned char *s)
{
while(*s)
{
uart_send_byte(*s++);
}
}
void main()
{
uart_init();
uart_send_string("Hello, 51!\r\n");
while(1);
}三、STM32单片机基础
3.1 STM32简介
STM32是ST公司推出的32位ARM Cortex-M系列单片机,具有高性能、低功耗、丰富外设等特点。
产品线分类:
| 系列 | 内核 | 特点 | 主频 |
|---|---|---|---|
| STM32F0 | Cortex-M0 | 入门级、成本低 | 48MHz |
| STM32F1 | Cortex-M3 | 标准型、性价比高 | 72MHz |
| STM32F4 | Cortex-M4 | 高性能、DSP | 168MHz |
| STM32F7 | Cortex-M7 | 超高性能 | 216MHz |
| STM32H7 | Cortex-M7 | 最高性能 | 480MHz |
| STM32L0/L4 | Cortex-M0+/M4 | 超低功耗 | 32/80MHz |
3.2 开发板选择
推荐型号:
- 正点原子mini/stm32f103rct6 - 入门首选
- 野火指南者/挑战者 - 资料丰富
- STM32F103C8T6最小系统板 - 性价比高
3.3 开发环境搭建
3.3.1 Keil MDK安装
- 下载Keil MDK:https://www.keil.com/download/
- 安装完成后,添加芯片支持包(Pack Installer)
- 创建工程,配置Target选项
3.3.2 STM32CubeMX安装
- 下载地址:https://www.st.com/en/development-tools/stm32cubemx.html
- 安装后,可图形化配置外设和时钟
3.4 标准库编程
3.4.1 GPIO操作
LED闪烁实验:
#include "stm32f10x.h"
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// 配置PC13为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void delay_ms(u16 time)
{
u16 i = 0;
while(time--)
{
i = 12000;
while(i--);
}
}
int main(void)
{
GPIO_Configuration();
while(1)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 点亮LED
delay_ms(500);
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄灭LED
delay_ms(500);
}
}按键检测实验:
#include "stm32f10x.h"
void Key_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA和GPIOC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
// PA0配置为上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// PC13配置为推挽输出(LED)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
uint8_t Key_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == Key_DOWN)
{
delay_ms(10); // 消抖
if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == Key_DOWN)
return KEY_DOWN;
}
return KEY_UP;
}
int main(void)
{
Key_GPIO_Config();
while(1)
{
if(Key_Scan(GPIOA, GPIO_Pin_0) == KEY_DOWN)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_13,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
}
}
}3.4.2 NVIC中断配置
#include "stm32f10x.h"
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 外部中断0处理
GPIO_WriteBit(GPIOC, GPIO_Pin_13,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void Exti_Config(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 配置PA0为外部中断输入
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}3.4.3 定时器配置
定时器中断:
#include "stm32f10x.h"
void TIM2_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时1秒:72MHz/7200/10000 = 1Hz
TIM_TimeBaseStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
// 定时1秒处理
GPIO_WriteBit(GPIOC, GPIO_Pin_13,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}3.5 HAL库编程
STM32CubeMX生成的代码使用HAL库,更易上手。
3.5.1 LED闪烁(HAL库)
#include "main.h"
int main(void)
{
HAL_Init(); // 初始化HAL库
SystemClock_Config(); // 配置系统时钟
MX_GPIO_Init(); // 初始化GPIO
while(1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(500); // 延时500ms
}
}3.5.2 按键中断(HAL库)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}四、外设驱动编程
4.1 蜂鸣器驱动
4.1.1 51单片机蜂鸣器
#include <reg52.h>
sbit BEEP = P1^5;
void beep()
{
unsigned char i;
for(i = 0; i < 100; i++)
{
BEEP = ~BEEP;
delay(1);
}
BEEP = 0;
}4.1.2 STM32蜂鸣器
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void BEEP_On(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_8);
}
void BEEP_Off(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
}4.2 数码管显示
4.2.1 51单片机数码管
#include <reg52.h>
// 段选信号(abcdefg + dp)
unsigned char code duan[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};
// 位选信号
unsigned char code wei[] = {0xFE, 0xFD, 0xFB, 0xF7};
void display(unsigned char *buf)
{
unsigned char i;
for(i = 0; i < 4; i++)
{
P2 = wei[i]; // 位选
P0 = duan[buf[i]]; // 段选
delay(2);
P0 = 0x00; // 消隐
}
}4.3 1602液晶显示
4.3.1 1602 LCD引脚
| 引脚 | 功能 |
|---|---|
| VSS | 地 |
| VDD | +5V电源 |
| VO | 对比度调节 |
| RS | 寄存器选择(数据/命令) |
| RW | 读写控制 |
| E | 使能信号 |
| D0-D7 | 数据端口 |
4.3.2 1602驱动代码
#include <reg52.h>
#define LCD_PORT P0
sbit RS = P2^0;
sbit RW = P2^1;
sbit EN = P2^2;
void lcd_delay(unsigned char i)
{
while(i--);
}
void lcd_write_cmd(unsigned char cmd)
{
RS = 0;
RW = 0;
EN = 0;
LCD_PORT = cmd;
EN = 1;
lcd_delay(50);
EN = 0;
}
void lcd_write_data(unsigned char dat)
{
RS = 1;
RW = 0;
EN = 0;
LCD_PORT = dat;
EN = 1;
lcd_delay(50);
EN = 0;
}
void lcd_init()
{
lcd_write_cmd(0x38); // 16x2显示,8位数据
lcd_write_cmd(0x0C); // 开显示,无光标
lcd_write_cmd(0x06); // 光标右移
lcd_write_cmd(0x01); // 清屏
}
void lcd_string(unsigned char x, unsigned char y, unsigned char *s)
{
if(y == 0)
lcd_write_cmd(0x80 + x);
else
lcd_write_cmd(0xC0 + x);
while(*s)
lcd_write_data(*s++);
}4.4 DS18B20温度传感器
4.4.1 特性
- 单总线接口
- 测温范围:-55°C ~ +125°C
- 精度:±0.5°C(-10°C ~ +85°C)
- 分辨率:9~12位可选
4.4.2 驱动代码
#include <reg52.h>
sbit DQ = P1^0;
void ds18b20_init()
{
DQ = 1;
delay_us(1);
DQ = 0;
delay_us(500); // 480us~960us
DQ = 1;
delay_us(50);
}
void write_byte(unsigned char dat)
{
unsigned char i;
for(i = 0; i < 8; i++)
{
DQ = 0;
delay_us(2);
if(dat & 0x01)
DQ = 1;
dat >>= 1;
delay_us(50);
DQ = 1;
}
}
unsigned char read_byte()
{
unsigned char i, dat = 0;
for(i = 0; i < 8; i++)
{
DQ = 0;
delay_us(2);
DQ = 1;
delay_us(5);
if(DQ) dat |= 0x80;
delay_us(50);
}
return dat;
}
float read_temperature()
{
unsigned char temp_low, temp_high;
int temp;
float t;
ds18b20_init();
write_byte(0xCC); // 跳过ROM
write_byte(0x44); // 启动温度转换
ds18b20_init();
write_byte(0xCC);
write_byte(0xBE); // 读暂存器
temp_low = read_byte();
temp_high = read_byte();
temp = (temp_high << 8) | temp_low;
t = temp * 0.0625;
return t;
}五、通信协议
5.1 UART串口通信
5.1.1 基本概念
- 全双工通信
- 起始位:1位低电平
- 数据位:8/9位
- 校验位:可选
- 停止位:1/2位
- 波特率:9600/115200等
5.1.2 51串口通信
#include <reg52.h>
void uart_init(unsigned long baud)
{
TMOD |= 0x20;
TH1 = 256 - (12000000UL / 12 / 32 / baud);
TR1 = 1;
SCON = 0x50;
}
void uart_send(unsigned char c)
{
SBUF = c;
while(!TI);
TI = 0;
}
void uart_print(char *s)
{
while(*s)
uart_send(*s++);
}
void uart_interrupt() interrupt 4
{
if(RI)
{
RI = 0;
// 接收处理
}
}5.1.3 STM32 HAL库串口
// 在main.c中
uint8_t rxBuffer[1];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
// 处理接收数据
HAL_UART_Transmit(huart, rxBuffer, 1, 100);
HAL_UART_Receive_IT(huart, rxBuffer, 1);
}
}
int main(void)
{
HAL_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, rxBuffer, 1);
while(1);
}5.2 I2C通信
5.2.1 I2C时序
┌────────────────────────────────────────────────┐
│ I2C 开始/停止时序 │
├────────────────────────────────────────────────┤
│ │
│ SCL: ___┐___┐___┐___┐___┐___┐___┐___ │
│ │ │ │ │ │ │ │ │ │
│ SDA: ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
│ ──┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─ │
│ ↑ ↑ │
│ 开始 停止 │
│ │
└────────────────────────────────────────────────┘
5.2.2 51模拟I2C(AT24C02)
#include <reg52.h>
sbit SDA = P2^0;
sbit SCL = P2^1;
void i2c_start()
{
SDA = 1;
SCL = 1;
SDA = 0;
SCL = 0;
}
void i2c_stop()
{
SDA = 0;
SCL = 1;
SDA = 1;
}
void i2c_ack()
{
SDA = 0;
SCL = 1;
SCL = 0;
}
unsigned char i2c_wait_ack()
{
SDA = 1;
SCL = 1;
if(SDA)
{
i2c_stop();
return 0;
}
SCL = 0;
return 1;
}
void i2c_write_byte(unsigned char dat)
{
unsigned char i;
for(i = 0; i < 8; i++)
{
SDA = dat & 0x80;
dat <<= 1;
SCL = 1;
SCL = 0;
}
}
unsigned char i2c_read_byte()
{
unsigned char i, dat = 0;
SDA = 1;
for(i = 0; i < 8; i++)
{
SCL = 1;
dat <<= 1;
if(SDA) dat |= 0x01;
SCL = 0;
}
return dat;
}
void at24c02_write(unsigned char addr, unsigned char dat)
{
i2c_start();
i2c_write_byte(0xA0); // 写设备地址
i2c_wait_ack();
i2c_write_byte(addr); // 写存储地址
i2c_wait_ack();
i2c_write_byte(dat); // 写数据
i2c_wait_ack();
i2c_stop();
}
unsigned char at24c02_read(unsigned char addr)
{
unsigned char dat;
i2c_start();
i2c_write_byte(0xA0);
i2c_wait_ack();
i2c_write_byte(addr);
i2c_wait_ack();
i2c_start();
i2c_write_byte(0xA1); // 读设备地址
i2c_wait_ack();
dat = i2c_read_byte();
i2c_stop();
return dat;
}5.3 SPI通信
5.3.1 SPI时序
┌────────────────────────────────────────────────┐
│ SPI 时序图 (模式0) │
├────────────────────────────────────────────────┤
│ │
│ SCLK: ___┐___┐___┐___┐___┐___┐___ │
│ │ │ │ │ │ │ │ │
│ MOSI: ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
│ ──┘ └─┘ └─┘ └─┘ └─┘ └─ │
│ ←─ MSB ──→ │
│ MISO: ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
│ ──┘ └─┘ └─┘ └─┘ └─┘ └─ │
│ │
└────────────────────────────────────────────────┘
5.3.2 STM32 HAL库SPI
void SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
HAL_SPI_Init(&hspi1);
__HAL_SPI_ENABLE(&hspi1);
}
uint8_t SPI1_ReadWriteByte(uint8_t txData)
{
uint8_t rxData;
HAL_SPI_TransmitReceive(&hspi1, &txData, &rxData, 1, 1000);
return rxData;
}5.4 红外遥控(NEC协议)
#include <reg52.h>
sbit IR_IN = P3^2;
unsigned char ir_code;
unsigned char ir_count = 0;
void ir_init()
{
IT0 = 1;
EX0 = 1;
EA = 1;
}
void ir_int() interrupt 0
{
unsigned char i, j;
unsigned int time;
time = 9000; // 引导码9ms低电平
while(!IR_IN);
while(IR_IN)
{
time++;
delay_us(10);
if(time > 2000) return;
}
// 解析32位数据
for(i = 0; i < 4; i++)
{
for(j = 0; j < 8; j++)
{
while(!IR_IN);
time = 0;
while(IR_IN)
{
time++;
delay_us(10);
}
ir_code >>= 1;
if(time > 30) // 1
ir_code |= 0x80;
}
}
}六、实战项目
6.1 项目一:智能温控风扇
6.1.1 功能需求
- DS18B20实时温度采集
- 温度阈值可调
- PWM调速控制风扇
- LCD1602显示温度和档位
6.1.2 硬件清单
| 模块 | 型号 | 数量 |
|---|---|---|
| 单片机 | STC89C52RC | 1 |
| 温度传感器 | DS18B20 | 1 |
| 液晶显示 | 1602 LCD | 1 |
| 电机驱动 | L298N/ULN2003 | 1 |
| 风扇 | 12V直流风扇 | 1 |
| 按键 | 轻触按键 | 3 |
6.1.3 程序框架
#include <reg52.h>
// 定义
bit mode = 0; // 模式:0=手动,1=自动
unsigned char set_temp = 30; // 设置温度
unsigned char current_temp; // 当前温度
// 函数声明
void init_system();
void display_temp();
void key_scan();
void fan_control();
void main()
{
init_system();
while(1)
{
current_temp = read_temperature();
display_temp();
key_scan();
fan_control();
delay_ms(200);
}
}
void fan_control()
{
if(mode == 0) // 手动模式
{
// 根据按键设置PWM
}
else // 自动模式
{
if(current_temp >= set_temp + 5)
fan_speed = 100;
else if(current_temp >= set_temp)
fan_speed = 60;
else if(current_temp >= set_temp - 5)
fan_speed = 30;
else
fan_speed = 0;
}
}6.2 项目二:智能小车
6.2.1 功能需求
- 手机APP蓝牙控制
- 超声波测距避障
- 循迹功能
- 红外遥控
6.2.2 硬件连接
┌─────────────────────────────────────────────────┐
│ 智能小车硬件连接图 │
├─────────────────────────────────────────────────┤
│ │
│ 蓝牙模块 超声波模块 │
│ HC-05 HC-SR04 │
│ ────── ───────── │
│ RXD ──→P3.0 Trig ──→P1.0 │
│ TXD ←─P3.1 Echo ←─P1.1 │
│ │
│ ┌──────────────┐ │
│ │ STM32/51 │ │
│ └──────────────┘ │
│ ↓ ↓ │
│ ┌────┐ ┌────┐ │
│ │左轮│ │右轮│ │
│ └──┬─┘ └──┬─┘ │
│ │ │ │
│ ┌──┴──┐ ┌──┴──┐ │
│ │L298N电机驱动│ │
│ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────────────────┘
6.2.3 蓝牙控制代码
#include "stm32f10x.h"
#include "usart.h"
char command;
void parse_command(char cmd)
{
switch(cmd)
{
case 'F': // 前进
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_3);
GPIO_ResetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_4);
break;
case 'B': // 后退
GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_3);
break;
case 'L': // 左转
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3);
break;
case 'R': // 右转
GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3);
GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_4);
break;
case 'S': // 停止
GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2 |
GPIO_Pin_3 | GPIO_Pin_4);
break;
}
}
int main(void)
{
USART1_Init(9600);
while(1)
{
if(USART_GetRxFlag())
{
command = USART_GetRxData();
parse_command(command);
}
}
}6.3 项目三:智能家居控制系统
6.3.1 系统架构
┌────────────────────────────────────────────────────────┐
│ 智能家居控制系统架构 │
├────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 云平台 │ │
│ │ (MQTT) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ↓ ↓ ↓ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 手机APP │ │ 网页端 │ │ 微信小程序│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ ESP8266 │ │
│ │ WiFi模块 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ STM32 │ │
│ │ 主控板 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────┬───────────┼───────────┬──────────┐ │
│ ↓ ↓ ↓ ↓ ↓ │
│ ┌────┐ ┌────┐ ┌───────┐ ┌──────┐ ┌────┐ │
│ │继电器│ │DHT11│ │人体感应│ │烟雾传感│ │LED │ │
│ │控制│ │温湿度│ │模块 │ │器 │ │ │ │
│ └────┘ └────┘ └───────┘ └──────┘ └────┘ │
│ │
└────────────────────────────────────────────────────────┘
6.3.2 ESP8266 WiFi通信
#include "ESP8266.h"
void ESP8266_Init(void)
{
ESP8266_SendCmd("AT\r\n", "OK", 500);
ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK", 500); // Station模式
ESP8266_SendCmd("AT+CWJAP=\"SSID\",\"PASSWORD\"\r\n", "OK", 5000);
ESP8266_SendCmd("AT+CIPMUX=0\r\n", "OK", 500);
}
void ESP8266_SendData(char *data)
{
char cmd[50];
sprintf(cmd, "AT+CIPSEND=%d\r\n", strlen(data));
ESP8266_SendCmd(cmd, ">", 500);
ESP8266_SendCmd(data, "OK", 1000);
}七、开发工具与调试
7.1 仿真软件
7.1.1 Proteus
Proteus是著名的EDA仿真软件,支持51、AVR、ARM等多种单片机仿真。
下载链接:
- Proteus官网:https://www.labcenter.com/
- 破解版:请自行搜索
基本使用:
- 新建工程,选择元器件
- 绘制原理图
- 加载HEX文件
- 开始仿真
7.1.2 Keil MDK/Keil C51
- Keil C51(51单片机):https://www.keil.com/c51/
- Keil MDK(ARM/STM32):https://www.keil.com/mdk5/
7.2 编程器/调试器
| 工具 | 用途 | 备注 |
|---|---|---|
| STC-ISP | STC单片机下载 | 免费软件 |
| ST-Link | STM32下载调试 | 需安装驱动 |
| J-Link | ARM调试 | 收费 |
| USBasp | AVR下载 | 开源 |
7.3 串口调试助手
- SSCOM:https://github.com/dukelec/sscom32
- SecureCRT:https://www.vandyke.com/products/securecrt/
- Putty:https://www.putty.org/
7.4 逻辑分析仪
- Saleae Logic:https://www.saleae.com/
- DSView:https://www.dreamsourcelab.com/
八、常见问题与解决方案
8.1 51单片机常见问题
Q1: 程序下载不进去?
- 检查串口驱动是否安装
- 确认晶振是否正常
- 检查RST引脚电平
- 尝试冷启动(断电后重新上电)
Q2: LED不亮?
- 检查LED极性是否接反
- 确认限流电阻是否连接
- 测量引脚输出电平
Q3: 串口通信失败?
- 确认波特率设置正确
- 检查晶振频率
- 验证TX/RX接线是否正确
8.2 STM32常见问题
Q1: 程序下载后不运行?
- 检查BOOT引脚设置
- 确认晶振是否起振
- 验证SWD接口连接
Q2: HAL库delay卡死?
- 检查SysTick配置
- 确认系统时钟配置正确
Q3: 外设不工作?
- 检查对应时钟是否使能
- 确认GPIO复用配置
8.3 调试技巧
- LED指示法:用LED闪烁表示程序运行状态
- 串口打印:输出调试信息
- IO口翻转:用示波器观察时序
- 仿真调试:设置断点单步执行
参考资源
51单片机教程
STM32教程
开发工具下载
社区论坛
附录
附录A:51单片机指令表
| 指令 | 功能 |
|---|---|
MOV A, Rn | 寄存器传送到累加器 |
MOV Rn, A | 累加器传送到寄存器 |
MOV A, @Ri | 间接RAM传送到累加器 |
MOVX A, @DPTR | 外部RAM传送到累加器 |
SETB bit | 置位指令 |
CLR bit | 清除指令 |
CPL bit | 取反指令 |
DJNZ Rn, rel | 减1不为0跳转 |
LCALL addr | 长调用子程序 |
RET | 子程序返回 |
附录B:常用C51库函数
// 延时函数
void delay(unsigned int ms);
// 字符串函数
void puts(char *s);
char gets(char *s);
// 数学函数
abs(x); // 绝对值
sin(x); // 正弦
cos(x); // 余弦
sqrt(x); // 平方根附录C:STM32时钟配置
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
// 使能HSE晶振
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz * 9 = 72MHz
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置AHB/APB时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK |
RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 |
RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // 36MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // 72MHz
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}笔记说明 本学习笔记整理于2026年5月,内容涵盖51单片机和STM32的基础知识和进阶内容。由于单片机技术更新较快,建议结合官方数据手册和最新教程学习。
如有错误,欢迎指正!