# C51 **Repository Path**: zhaoyisha/C51 ## Basic Information - **Project Name**: C51 - **Description**: 江协科技 C51 学习记录,结合普中A2 C51开发板,开发平台keil5,内容包括项目代码、电路原理图、部分开发手册和各章节个人学习心得笔记 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-22 - **Last Updated**: 2026-04-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 江协科技 C51 学习记录 ## 普中A2 C51开发板介绍 1. CPU/MCU STM89C52 最小芯片 单片机 (40引脚) USB直接下载程序,flash rom 2. CPU/MCU晶振 11.0592MHz 3. D1~D8 LED灯 4. K1~K4 独立按键 5. S1~S16 矩阵按键 6. IR1 红外传感器 遥控器 7. AT 多根线下载程序 8. 无线模块 2.4G 无遥控器 9. USB口 下载程序 10. DS1302 实时时钟芯片 可定时控制或者闹钟 11. 时钟晶振(金属柱状晶振) 12. 复位按钮 13. AD/DA 模数转换器 14. GR1 光敏电阻 15. NTC1 热敏电阻 16. 电位器--滑动变阻器 17. 74HC595 芯片 扩展IO口 18. 步进电机/直流电机 工业控制领域,精确控制角度 19. 蜂鸣器 20. 超声波接口 外接测距 21. 74HC138 芯片 译码器,扩展IO口,驱动数码管 22. 24C02 芯片 EEPROM 掉电不丢失 23. 18B20 芯片 I2C 模块,温度传感器 24. 74HC245 芯片 驱动数码管 25. 排座 插液晶屏LCD1602/LCD12864 可用电位器调对比度 26. 点阵屏 16*16 LED矩阵 27. 电源开关 28. 五线四相步进电机 使用ULN2003D驱动 ## 元件数据标注规定 102 = 10 00 1K 473 = 47*10^3 47K **数字过大情况** 1002 = 100*10^2 10K ## 十六进制转换表 | 十进制 | 二进制 | 十六进制 | 十进制 | 二进制 | 十六进制 | |--------|--------|----------|--------|--------|----------| | 0 | 0000 | 0 | 8 | 1000 | 8 | | 1 | 0001 | 1 | 9 | 1001 | 9 | | 2 | 0010 | 2 | 10 | 1010 | A | | 3 | 0011 | 3 | 11 | 1011 | B | | 4 | 0100 | 4 | 12 | 1100 | C | | 5 | 0101 | 5 | 13 | 1101 | D | | 6 | 0110 | 6 | 14 | 1110 | E | | 7 | 0111 | 7 | 15 | 1111 | F | ## 编写程序 ### 创建工程 1. 打开 Keil uVision 软件 2. 点击 `Project` -> `New Project`,自行保存项目位置和名称 3. 在弹出的`select device for target`对话框中,选择芯片厂家`Atmel`->`AT89C52` 4. 自行选择是否需要启动文件`START` ### 添加源文件 1. 在生成的项目目录结构里,在`Source Group 1`下添加源文件`Add New Item to Group` 2. 在弹出的对话框中,自行选择编辑语言,建议选择 `C Source File` ### 编译 1. 编译常用按钮在左上角,依次为`translate`、`build`、`rebuild`,一般使用 `Build`按钮编译软件 2. 编译前根据需要点击 `options for target` -> `output` -> `create hex file` 3. 编写代码时可`configuration` -> `font`更改显示字体 4. 添加头文件可以右键`insert #include<···>`,想查看此文件内容就右键`open document <···>` ### 烧写 1. USB连接开发板,打开Ai-Cube-ISP软件 2. 选择芯片型号和扫描串口 3. 点击 `打开程序文件` -> 选择之前生成的 `hex` 文件 4. 点击 `下载` 按钮,打开开发板开关上电 5. 观察日志输出,检查LED灯的亮灭 **注意** - `Atmel` 2016年就被 Microchip 收购了,现在新的 Keil 里 AT89C51 系列都在 `Microchip`目录下。 - 可能存在开发板引脚复用导致的引脚冲突,具体表现为其他模块可能也会亮起 - 若烧录软件日志一直显示`正在监测目标单片机`,请检查开发板型号是否选择正确 ## 串口调试工具 1. 打开 `Ai-Cube-ISP` 软件 2. 选择 `Tools` -> `软件延时计算器` 3. 根据开发板晶振频率选择 `11.0592MHz` 的系统频率 4. 选择`定时时间`为`500ms`,`8051指令集`选择对应开发板系列硬件的指令集,这里选择`STC89` 5. 复制生成的delay延时代码 ## C51数据类型 ![alt text](source/dataType.png) ## C51数据运算 ![alt text](source/dataComputation.png) ### 常用位运算符 1. 按位左移运算:`<<` - 运算规则:将二进制数向左移动一位,最高位丢弃,最低位补0 - 例如:1100 0001 << 1 = 1000 0010 - 2. 按位右移运算:`>>` - 运算规则:将二进制数向右移动一位,最低位丢弃,最高位补0 - 例如:0010 0011 >> 1 = 0001 0001 3. **按位与**运算:`&` - 运算规则:将两个二进制数按位与,**相同为1,否则为0** - 例如:0010 0011 & 1100 0001 = 0000 0001 4. **按位或**运算:`|` - 运算规则:将两个二进制数按位或,有一个为1,就为1 - 例如:0010 0011 | 1100 0001 = 1110 0011 5. **按位异或**运算:`^` - 运算规则:将两个二进制数按位异或,**相同为0,不同为1** - 例如:0010 0011 ^ 1100 0001 = 1110 0010 6. **按位非**运算:`~` - 运算规则:将二进制数按位取反,0变1,1变0 - 例如:~0010 0011 = 1101 1100 ## 单片机电平有效状态 本实验采用开发板为低电平有效,低电平点亮LED灯。 单片机“弱上拉,强下拉”,P1,P2,P3都是弱上拉工作模式,即低电平对电路影响更大 ### 单片机引脚并不是“默认高电平有效”。 **“有效”这个概念完全取决于外部电路的连接方式,而不是单片机本身**。单片机引脚本身只是一个可以输出高电平(通常接近VCC,如5V或3.3V)或低电平(接近0V)的端口,或者可以读取外部输入电平状态的接口。 #### 引脚默认状态(上电状态) 首先,要区分“默认电平”和“有效电平”。 1. 默认电平(上电状态):指单片机刚通电、尚未被程序初始化时,引脚的 electrical state(电气状态)。 >对于大多数51系列单片机(如STC89C52),P0口是高阻态(既不是高电平也不是低电平,状态不确定),而 P1, P2, P3口 通常是弱上拉(Weak Pull-up)状态,可以理解为默认被内部电阻拉成了一个不稳定的高电平。所以严格来说,51单片机的多数IO口上电后有一个趋向于高电平的默认状态,但这并不代表“高电平有效”。 2. “有效电平”由电路设计决定 “有效”指的是“让某个外部设备(如LED、继电器、蜂鸣器)开始工作”的电平。 **核心原则**:**电流从单片机流向地(GND)最容易驱动设备**。 #### 我们来看两个最经典的例子: ##### **案例一:控制LED(低电平有效更常见)** 1. **电路A(低电平有效 `Low-Active`):** **VCC -> 电阻 -> LED -> 单片机引脚** - 程序输出 0(低电平):LED两端形成电压差(VCC到GND),电流通过,LED点亮。此时低电平是“有效”电平 - 程序输出 1(高电平):LED两端电位几乎相等(都是VCC),没有电流,LED熄灭。此时高电平是“无效”电平。 结论:在这个电路中,是低电平有效。 2. **电路B(高电平有效 `High-Active`):** **单片机引脚 -> 电阻 -> LED -> GND** - 程序输出 1(高电平):单片机引脚提供VCC,电流从引脚流出经过LED到地,LED点亮。此时高电平是“有效”电平。 - 程序输出 0(低电平):无法提供电流,LED熄灭。此时低电平是“无效”电平。 结论:在这个电路中,是高电平有效。 3. 为什么电路A(低电平有效)更常用? 因为51系列单片机的**拉电流**(输出高电平时提供电流)能力很弱(约几十uA),而**灌电流**(输出低电平时吸收电流)能力较强(约几个mA)。用电路A(**低电平有效)可以更明亮地驱动LED**。现代单片机的驱动能力已大大增强,但这种设计习惯保留了下来。 ##### **案例二:读取按键(低电平有效更常见)** 1. **电路A(低电平有效):** **单片机引脚 -> 上拉电阻 -> VCC** 按键 -> 连接在引脚和GND之间 - 按键未按下:引脚被上拉电阻拉到VCC,程序读到 1。 - 按键按下:引脚直接连接到GND,程序读到 0。 结论:在这个标准电路中,按键按下时产生低电平,所以是低电平有效。 2. **电路B(高电平有效):** **单片机引脚 -> 下拉电阻 -> GND** 按键 -> 连接在引脚和VCC之间 这种接法较少见,因为不符合常规思维。 #### **总结** 1. 没有默认有效电平:“有效”电平是电路工程师根据硬件特性(如驱动能力、功耗、安全)设计电路时决定的,不是单片机固有的。 2. 低电平有效更常见:由于历史原因(驱动能力)和电路设计习惯,在51单片机项目中,LED、按键、蜂鸣器等外设常用低电平作为有效控制电平。 3. 编程中,是让引脚输出高电平还是低电平来控制设备,完全取决于你的硬件电路是怎么连接的。 永远以原理图为准。 >**如果LED的一端接在VCC,那么控制脚输出低电平(0)时灯才亮,这就是低电平有效。** >**如果LED的一端接在GND,那么控制脚输出高电平(1)时灯才亮,这就是高电平有效。** ## 定时器和中断(单片机内部中断) [定时器和中断原理]() **本质是对各个相关的寄存器进行操作,设置相关工作模式** 定时器寄存器初值的设置: **初值 = 65536 - ( 定时时间 * 晶振频率 / 12 )** >例如 寄存器为8位寄存器,共TH和TL两个8位寄存器,则有0~65535 每隔1us计数加一,总定时为65535us **65545**离计数溢出差值为1000,则计时时间为1ms ## 串口通信 [51单片机串口通信介绍]() 1. 串口是一种应用十分广泛的通讯接口,成本低、易使用、线路简单,可实现两个设备的互相通信。 2. 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,扩展单片机的应用范围,增强单片机系统的硬件实力。 3. 51单片机**内部自带UART**(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。 ### 常见通信接口 | 名称 | 引脚定义 | 通信方式 | 特点 | | :--------: | :---------------------------: | :--------: | :------------------------------------------- | | **UART** | TX(发送)、RX(接收)、GND | 全双工 / 异步串行 | 点对点通信、常用于调试与模块通信,传输距离有限,需波特率一致 | | **I2C** | SDA(数据)、SCL(时钟)、GND | 半双工 / 同步串行 | 双线总线,多主多从,支持挂载多个设备,速率中等(100kHz\~3.4MHz) | | **SPI** | SCLK(时钟)、MOSI、MISO、CS(片选)、GND | 全双工 / 同步串行 | 速率高(MHz级\~几十MHz),主从结构,支持挂载多个设备常用于高速外设通信,线数较多 | | **1-Wire** | DQ(数据+时钟)、GND | 半双工 / 单总线 | 只需一根数据线+地线,支持挂载多个设备,速率低,常用于温度传感器(DS18B20)等 | | **CAN** | CANH、CANL、GND | 多主 / 差分串行 | 抗干扰、实时、总线式结构,常用于汽车、工业控制等场景 | | **USB** | D+、D-、VCC、GND(另有ID用于OTG) | 主从 / 差分串行 | 即插即用、速率高,支持供电 | ### 术语 **全双工**:通信双方可以在同一时刻互相传输数据 **半双工**:通信双方可以互相传输数据,但必须分时复用一根数据线 **单工**:通信只能有一方发送到另一方,不能反向传输 **异步**:通信双方各自约定通信速率 **同步**:通信双方靠一根时钟线来约定通信速率 **总线**:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流) ### 串口常用电平标准 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种: - **TTL电平**:+5V表示1,0V表示0 - **RS232电平**:-3 ~ -15V表示1,+3 ~ +15V表示0 - **RS485电平**:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号) **两设备之间电平不同,通信时加上电平转换电路即可** ## C51特殊功能寄存器声明 1. **sfr(special function register)**:特殊功能寄存器声明 例:sfr P0 = 0x80; 声明P0口寄存器,物理地址为0x80 2. **sbit(special bit)**:特殊位声明 例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1; 声明P0寄存器的第1位 3. **可位寻址/不可位寻址**:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以**位的数量是寄存器数量的8倍**,单片机无法对所有位进行编码,故**每8个寄存器中,只有一个是可以位寻址的**。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作 ## BCD 码 BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数 >例:0001 0011表示13,1000 0101表示85,0001 1010不合法 在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法 **BCD码转十进制**:DEC=BCD/16*10+BCD%16; (2位BCD) **十进制转BCD码**:BCD=DEC/10*16+DEC%10; (2位BCD) ## 学会如何看数据手册进行学习和开发 1. 引脚定义和应用电路 - 引脚分为直插封装和贴片封装 - 电路方面需要查看硬件连接,通过引脚和其对应作用确定对后续寄存器的设置 2. 查看芯片数据手册,明确具体需要配置的模式和寄存器位赋值,确认自己的操作任务是什么 - 在哪(寄存器地址) 写入(操作) 什么(数据) - 在哪(寄存器地址) 读出(操作) 什么(数据) 3. 开发时注意模块化编程,并且在编写代码前,重新命名需要的各个端口名称,单片机存在引脚复用,不这样容易搞混端口 4. 记得查看各个操作支持的最小速率,确保没有时序错误 ## 反码补码 ### 为什么需要反码和补码? 1. 背景:计算机用二进制表示数,最高位一般是符号位: 0 → 正数 1 → 负数 2. 问题:直接用原码(最高位是符号,剩下是绝对值)进行加减,会很复杂。 3. 解决办法:引入 反码 和 补码,使得加法运算统一。 ### 原码(Sign-Magnitude) 1. 定义:最高位是符号位,其余表示数值的绝对值。 2. 特点: +x 的原码 = 数值二进制。 -x 的原码 = 符号位为 1,数值部分不变。 ```scss (+5) 原码:0000 0101 (-5) 原码:1000 0101 ``` ### 反码(One's Complement) 正数:反码 = 原码。 负数:反码 = 符号位不变,其余位按位取反(0→1,1→0)。 ```scss (+5) 反码:0000 0101 (-5) 反码:1111 1010 (原码 1000 0101 → 取反) ``` ### 补码(Two's Complement) 正数:补码 = 原码。 负数:补码 = 反码 + 1。 ```scss (+5) 补码:0000 0101 (-5) 反码:1111 1010 → +1 = 1111 1011 ``` ### 特殊点 **0 的表示**: 原码:+0 = 0000 0000,-0 = 1000 0000 反码:有两个 0(全 0 和全 1) 补码:只有一个 0(全 0) ? **负数范围**:补码可以多表示一个最小负数。 8 位:-128 ~ +127 ### 运算规律 统一加法:补码加法 = 真正的加减法。 丢弃溢出位:超过指定位数的高位直接丢弃。 例:5 + (-5)(8 位补码) ```scss 0000 0101 + 1111 1011 ------------ 0000 0000 (正确结果 0) ``` ## 单片机外部中断 具体可见红外遥控模块的[外部中断]() STC89C52单片机有4个外部中断源: **INT0 (P3.2)** **INT1 (P3.3)** **INT2 (P4.3)** (注意:标准51内核只有2个,STC89C52是增强型51,有4个) **INT3 (P4.2)** 最常用、最基础的是 INT0 和 INT1。 # 从0开始开发单片机的工作流 ## 第一阶段:认知与准备 (看视频+看手册) ### 认识新器件 (看视频入门) 拿到一个新模块(如LCD1602、DS18B20),先看数据手册相关章节,建立一个宏观概念:它是做什么的?大致如何工作?需要连接哪些线? **目的**: 避免一开始就陷入复杂的信息而失去方向。 ### **阅读数据手册** (Datasheet) - 核心技能 **抓大放小**: 不要试图一次性读懂手册的所有内容,带着问题去读 **电气特性**: 找到电压(是3.3V还是5V?)和引脚定义(每个脚是干嘛的?)。这是硬件连接的基础,接错会烧芯片! **通信接口**: 是GPIO、I2C、SPI还是单总线?这决定了你的编程模式。 **指令集/控制字**: 如果是屏幕类器件,找到初始化指令、清屏指令、写入数据的格式。 **时序图 (Timing Diagram)**: 这是驱动编写的唯一圣经! 找到最关键的操作时序图,比如“写数据时序”、“读数据时序”。 ## 第二阶段:硬件设计 & 搭建 根据手册了解硬件电路: **电源**: VCC接5V,GND接地。这是最重要的第一步。 **信号线**: 将模块的控制引脚(如RS, RW, E, SCL, SDA, DQ)连接到单片机的任意I/O口(除非有特殊要求,如外部中断)。 **上拉电阻**: 如果是开漏输出的总线(如I2C、单总线),必须在信号线上加上拉电阻(通常4.7K-10K),否则无法输出高电平! **硬件调试**: 连接好后,最好用万用表测量一下电压,确保电源和地没有接反或短路。 ## 第三阶段:软件驱动开发 (最核心部分) ### **底层驱动**函数编写 (对照时序图) **核心方法**: 用基本的“**拉高**”“**拉低**”“**延时**”函数,去**模拟时序图**上的每一个**波形**。 **步骤**: 1. **“肢解”时序图**: 将一张复杂的时序图分解成几个简单的步骤(如:拉低->延时->拉高->延时)。 2. **编写底层函数**: 针对时序图中的每个操作,编写一个最基本的函数。例如: * void I2C_Start(): // SCL高电平时,SDA产生一个下降沿 * void DS18B20_WriteBit(char bit): // 向单总线写一位 3. **微调延时**: 时序图里通常有t_SU, t_HD等时间要求。使用定时器或软件延时_nop_()来满足这些时间要求。“延时是模拟时序的重要的步骤”。 4. **由底向上搭建**: 比如用写好的WriteBit函数去实现WriteByte函数,再用WriteByte函数去实现更上层的SendCommand函数。 ### 封装功能函数 在**底层驱动**的基础上,封装易于使用的功能函数。 例如: LCD1602_Init():初始化屏幕。 LCD1602_ShowChar(unsigned char line, unsigned char col, char chr):在指定位置显示一个字符。 DS18B20_ReadTemp():读取温度值并返回。 ## 第四阶段:功能设计与调试 ### 主程序逻辑设计 **初始化**: 在主函数main()中,首先初始化所有用到的模块(串口、定时器、LCD、传感器等)。 **循环体**: 在while(1)循环中,设计主要逻辑。推崇“状态机”思想,尽量避免使用delay进行延时等待,容易造成时序错乱。 >示例: 用定时器中断做一个1ms的时基,在主循环里判断“是否到了1000ms?”,到了就执行一次“读取温度并显示”的任务,没到就继续执行其他任务或空循环。这样CPU效率最高。 ### 调试 (Debug) - 必备技能 **LCD显示大法**: 最常用、最有效的调试方法。在程序关键点通过LCD显示数据,查看变量值、程序执行到哪一步了。 **LED指示**: 用LED灯的亮灭来简单指示程序状态,0或1(如开机亮、进入中断闪一下)。 **逻辑分析仪/示波器**: 高级调试工具。当程序怎么调都没现象时,就用逻辑分析仪抓取引脚的实际波形,和程序中的时序图对比,“波形不会说谎”,能立刻发现问题(如延时不对、电平没变化)。