单片机学习笔记

整理日期:2026-05-28 作者:个人学习笔记


目录

  1. 单片机概述
  2. 51单片机基础
  3. STM32单片机基础
  4. 外设驱动编程
  5. 通信协议
  6. 实战项目
  7. 开发工具与调试
  8. 常见问题与解决方案

一、单片机概述

1.1 什么是单片机

单片机(Microcontroller Unit,MCU)是一种将中央处理器(CPU)、存储器、输入/输出接口集成在同一芯片上的微型计算机系统。

单片机实物图

主要特点:

  • 体积小、功耗低、成本低
  • 集成度高、可靠性强
  • 适用于嵌入式系统
  • 广泛应用于工业控制、智能家电、汽车电子等领域

1.2 主流单片机系列

系列内核位数特点适用场景
51系列80518位经典入门、结构简单教学、小型控制
STM32ARM Cortex-M32位性能强、资源丰富工业、消费电子
AVRAVR8位高效、RISC架构智能控制、Arduino
PICPIC8/16/32位可靠性高汽车、工业
MSP430MSP43016位超低功耗电池供电设备

1.3 学习路线图

┌─────────────────────────────────────────────────────────┐
│                    单片机学习路线                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   入门阶段          进阶阶段           高级阶段            │
│   ─────────        ─────────          ─────────          │
│   数字电路基础  →  C语言进阶      →  RTOS系统           │
│   51单片机      →  STM32         →  嵌入式Linux         │
│   基础外设      →  通信协议       →  项目实战           │
│   基础实验      →  中断/DMA       →  优化与调试         │
│                                                         │
└─────────────────────────────────────────────────────────┘

二、51单片机基础

2.1 51单片机简介

51单片机是指基于Intel 8051内核的一系列单片机,是最经典的8位单片机之一。

51单片机实物图

主要型号:

  • AT89C51/AT89C52(Atmel)
  • STC89C52RC(宏晶科技)
  • STC15系列(增强型)

2.2 硬件结构

2.2.1 引脚分布

51单片机引脚图

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 数据类型

数据类型位宽取值范围
bit1位0~1
sbit1位0~1(特殊功能寄存器位)
char8位-128~127
unsigned char8位0~255
int16位-32768~32767
unsigned int16位0~65535
long32位-2147483648~2147483647
float32位±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仿真图: LED点亮仿真图

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系列单片机,具有高性能、低功耗、丰富外设等特点。

STM32F103C8T6实物图

产品线分类:

系列内核特点主频
STM32F0Cortex-M0入门级、成本低48MHz
STM32F1Cortex-M3标准型、性价比高72MHz
STM32F4Cortex-M4高性能、DSP168MHz
STM32F7Cortex-M7超高性能216MHz
STM32H7Cortex-M7最高性能480MHz
STM32L0/L4Cortex-M0+/M4超低功耗32/80MHz

3.2 开发板选择

推荐型号:

  • 正点原子mini/stm32f103rct6 - 入门首选
  • 野火指南者/挑战者 - 资料丰富
  • STM32F103C8T6最小系统板 - 性价比高

STM32开发板

3.3 开发环境搭建

3.3.1 Keil MDK安装

  1. 下载Keil MDK:https://www.keil.com/download/
  2. 安装完成后,添加芯片支持包(Pack Installer)
  3. 创建工程,配置Target选项

Keil MDK界面

3.3.2 STM32CubeMX安装

  1. 下载地址:https://www.st.com/en/development-tools/stm32cubemx.html
  2. 安装后,可图形化配置外设和时钟

STM32CubeMX界面

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++);
}

1602 LCD接线图

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 硬件清单

模块型号数量
单片机STC89C52RC1
温度传感器DS18B201
液晶显示1602 LCD1
电机驱动L298N/ULN20031
风扇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等多种单片机仿真。

下载链接:

基本使用:

  1. 新建工程,选择元器件
  2. 绘制原理图
  3. 加载HEX文件
  4. 开始仿真

Proteus仿真界面

7.1.2 Keil MDK/Keil C51

7.2 编程器/调试器

工具用途备注
STC-ISPSTC单片机下载免费软件
ST-LinkSTM32下载调试需安装驱动
J-LinkARM调试收费
USBaspAVR下载开源

7.3 串口调试助手

7.4 逻辑分析仪


八、常见问题与解决方案

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 调试技巧

  1. LED指示法:用LED闪烁表示程序运行状态
  2. 串口打印:输出调试信息
  3. IO口翻转:用示波器观察时序
  4. 仿真调试:设置断点单步执行

参考资源

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的基础知识和进阶内容。由于单片机技术更新较快,建议结合官方数据手册和最新教程学习。

如有错误,欢迎指正!