coderz开发札记 - 51单片机
type: channel
link: https://www.136366514.xyz/category/xiu_33/
items: DS1302时钟
items: OLED（二）
items: OLED模块（一）
items: I2C总线
items: UART（四）发送多字节命令
items: UART（三）发送单字节命令
items: UART（二）使用说明
items: UART（一）概述
items: 74HC595芯片
items: 51单片机概述

DS1302时钟
type: item
date: 2025-08-08T09:27:31+08:00
description: DS1302时钟1.1 简介DS1302是一个低功耗、实时时钟（RTC）芯片，如下图所示：1.2 引脚功能32.768kHz分频15次即频率为1hz，一秒一次引脚号名称方向功能说明1VCC2电源输入主电源（+5V 或 +3.3V），通常是系统电源供给2X1输入32.768kHz 晶振的第一端（接晶振）3X2输出32.768kHz 晶振的第二端4GND电源地线（0V）5CE输入芯片使能信号（Chip Enable，高电平有效），通信时需先拉高6I/O双向数据线（串行输入/输出），命令、数据都通过它传输7SCLK输入串行时钟信号（上升沿或下降沿采样，DS1302在上升沿接收数据）8VCC1电源输入备用电源（接纽扣电池，掉电时维持时钟运行）1.3 基本工作原理基本组成结构工作流程1.4 通信协议​ 根据上述的工作流程可知，使用DS1302时，核心操作就是读写寄存器。1.4.1 写操作​ 以下是向寄存器写入单个字节的时序图。发送数据前，需要将CE拉高，使能通信。需要注意的是，拉高CE时，需确保SCLK处于低电平需要先发送命令字节，再发送数据字节，发送时都是低位优先命令字节用于指定寄存器地址以及声明读/写操作，各位的含义如下数据字节为发送的具体数据，每位数据都需要在一个时钟周期上升沿发送，意味着在上升沿之前要准备数据1.4.2 读操作​ 以下是从寄存器读取单个字节的时序图：接收数据前，需要将CE拉高，使能通信。需要注意的是，拉高CE时，需确保SCLK处于低电平需要先发送命令字节，再接受数据字节，发送和接收时都是低位优先发送每位数据要在时钟信号的上升沿，读取每位数据要在时钟信号的下降沿​ 注意:第8个时钟周期，需要完成最后1位数据的发送和第1位数据的接收1.4.3 常用寄存器​ 以下是各时间寄存器的地址和每位的定义：1）中文版2）英文版#寄存器名称读地址写地址位分布说明范围或含义1秒寄存器81h80hBIT7: CH（1=停止，0=运行）BIT6~4: 十秒位（0–5）BIT3~0: 个位秒（0–9）00–59 秒2分钟寄存器83h82hBIT6~4: 十分钟位（0–5）BIT3~0: 个位分钟（0–9）00–59 分钟3小时寄存器85h84hBIT7: 12/24 小时制（1=12hr，0=24hr）；BIT6: 保留，固定0； BIT5: AM/PM（12hr制下，1=PM，0=AM）；BIT4~0: 小时数值1–12 / 0–23 小时4日期（日）寄存器87h86hBIT5~0: 日期1–315月份寄存器89h88hBIT4~0: 月份1–126星期寄存器8Bh8AhBIT2~0: 星期1–77年寄存器8Dh8ChBIT7~0: 年份（低两位）00–998写保护寄存器（WP）8Fh8EhBIT7: WP（1=禁止写操作，0=允许写）—注意：在向DS1302写入数据前需要先关闭写保护，写完再开启写保护。1.5 相关资料（1）购买链接：https://item.szlcsc.com/15455.html（2）实物图、原理图、封装如何设置和显示时间信息？1.6 硬件原理图1.7 软件设计1.7.1 初始化初始化 DS1302 RTC 芯片 ，上电时先给它一个已知的稳定状态void Int_DS1302_Init(void) { SCLK = 0; // 时钟信号 IO = 0; // 数据 CE = 0; // 使能 } 1.7.2 写数据向 DS1302 写 1 个字节数据static void Int_DS1302_WriteByte(u8 byte) { u8 i; for (i = 0; i < 8; i++) { // 执行 8 次，依次把这个字节的 最低位（LSB）到最高位（MSB） 依序送给 DS1302 IO = byte & 0x01; // 准备数据，取当前字节的最低位 SCLK = 1; SCLK = 0; // 先将 SCLK 时钟线拉高再拉低，产生一个上升沿/下降沿组合 byte >>= 1; // 把 byte 右移一位，这样下一次循环时最低位就是原来的第二位 } }1.7.3 读数据从 DS1302 读取一个字节数据static u8 Int_DS1302_ReadByte() { u8 i, byte = 0; for (i = 0; i < 8; i++) { // 循环 8 次，每次从 DS1302 读 1 位, 读到的第一个位应该放在最低位 byte >>= 1; // 右移一位，为即将读入的新位腾出最高位,第一次无所谓，反正都是0 if (IO == 1) { // 如果是高电平（1），就把当前字节的 最高位 置 1 byte = byte | 0x80; } SCLK = 1; // 产生一个时钟…

OLED（二）
type: item
date: 2025-08-07T09:23:27+08:00
description: OLED（二）（1）购买链接：https://item.szlcsc.com/5960631.html（2）实物图​1.1 硬件设计硬件引脚和之前EEPROM接的一样，形成主从架构，单片机主，从设备为这两个；如何在OLED屏指定位置显示ASCII表中的指定字符？1.2 点亮屏幕1.2.1 定义写控制命令的函数I2C介绍和相关函数#define OLED_ADDR 0x78 #define CMD_MODE 0x00 // 发送多个命令 void Int_Oled_WriteCmd(u8 bytes[], u8 length) { u8 i; // 发送起始信号 Dri_IIC_Start(); // 发送设备地址 Dri_IIC_SendByte(OLED_ADDR); Dri_IIC_ReceiveAck(); // 发送控制字节 Dri_IIC_SendByte(CMD_MODE); Dri_IIC_ReceiveAck(); // 发送数据 for (i = 0; i < length; i++) { Dri_IIC_SendByte(bytes[i]); Dri_IIC_ReceiveAck(); } Dri_IIC_Stop(); }1.2.2 点亮关于点亮屏幕的初始化命令操作见屏幕手册，开启屏幕之前做 Segment重映射和Common重映射：void Int_Oled_Init() { u8 bytes[6]; // 关闭屏幕 bytes[0] = 0xAE; // Segment重映射 bytes[1] = 0xA1; // Common重映射 bytes[2] = 0xC8; // 启动内置电荷泵（用于升压） bytes[3] = 0x8D; bytes[4] = 0x14; // 开启屏幕 bytes[5] = 0xAF; Int_Oled_WriteCmd(bytes, 6); }执行函数Int_Oled_Init()即可点亮屏幕，此时屏幕呈雪花状，需要下一步清屏。1.3 清除屏幕1.3.1 定义写显示数据的函数#define DATA_MODE 0x40 // 01 000000连续发送多个显示数据 void Int_Oled_WriteData(u8 bytes[], u8 length) { u8 i; // 发送起始信号 Dri_IIC_Start(); // 发送设备地址 Dri_IIC_SendByte(OLED_ADDR); Dri_IIC_ReceiveAck(); // 发送控制字节 Dri_IIC_SendByte(DATA_MODE); Dri_IIC_ReceiveAck(); // 发送数据 for (i = 0; i < length; i++) { Dri_IIC_SendByte(bytes[i]); Dri_IIC_ReceiveAck(); } Dri_IIC_Stop(); }1.3.2 清屏发送显示数据0，让所有二极管都熄灭即可完成清屏发送控制命令选择内存寻址模式为水平寻址：左->右，满一行后换到下一行；连续发送多个显示数据0，熄灭二极管；void Int_OLED_Clear(){ u8 cmd_bytes[2]; u8 data_bytes[8] = {0}; u8 i; cmd_bytes[0]= 0x20; // 0x20: 0b00100000 （内存寻址模式key） cmd_bytes[1]= 0x00; // 0x00: 0b00000000 （内存寻址模式value） 水平寻址：左->右，满一行后换到下一行 Int_Oled_WriteCmd(cmd_bytes,2); for(i = 0; i < 128; i++){ // 每次写8位，写128次，为什么不一次写128位，ram不足 Int_Oled_WriteData(data_bytes, 8); } }1.4 左上角显示一个8*8的正方形I²C 协议中数据是按字节传输的，一个字节点亮竖直八个二极管，需要点亮8列，清屏后写入的指向地址刚好是第一行第一个：u8 data_bytes[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; Int_Oled_Init(); Int_OLED_Clear(); Int_Oled_WriteData(data_bytes, 16);1.5 在指定位置显示ASCII表中的指定字符​ 想要在指定位置显示ASCII表中的指定字符，首先需要为每个字符制作字模。至于显示位置的控制，只需通过命令指定自模数据写入GDDRAM的位置即可实现。1.5.1 制作字模​ 现规定，每个字符占用屏幕上的8*16个像素：…

OLED模块（一）
type: item
date: 2025-08-06T10:58:30+08:00
description: OLED模块-I2C协议OLED模块概述1.1 OLED简介​ OLED（Organic Light-Emitting Diode，有机发光二极管）显示屏由许多微小的发光二极管构成，每个发光二极管就对应着屏幕上的一个像素，通过控制二极管的亮灭就能在屏幕上显示图像或文字。​ 按照驱动方式划分，OLED显示屏分可分为两类，分别是PMOLED（Passive Matrix，被动矩阵）和AMOLED（Active Matrix，主动矩阵）。PMOLED的驱动方式和LED点阵类似，每次只能点亮一行或者一列，需要快速的逐行或者逐列扫描来显示内容。AMOLED屏幕上的每个像素点都有一个单独的控制电路，可以独立进行控制。​ 本文使用的是一块PMOLED（Passive Matrix，被动矩阵）屏，其分辨率为128x64。1.2 驱动芯片​ 市面上的多数OLED显示屏都会带有驱动芯片，驱动芯片的核心作用是控制每个像素点的亮灭以显示图像或文字，此外它还会提供数据接口（例如I2C），用于接收屏幕上要显示的数据。驱动芯片的存在使得我们可以通过简单的接口控制OLED显示屏，大大简化了开发和控制流程。​ 本文所使用的这块OLED屏的驱动芯片为SSD1315，由于我们需要通过它来控制OLED显示的内容，所以需要重点学习它的用法。1.2.1 基本工作原理工作流程1.2.2 IIC接口​ 虽然SSD1315的MCU Interface支持多种通信协议，但我们所选用的OLED显示模块只暴露出了IIC接口，如下图所示。​ 由于OLED显示屏的作用是显示数据，它对于MCU来讲更多的是一个输出设备，因此我们只需关注如何向SSD1315写入数据即可。下面是写数据的时序图：​ 说明：SSD1315的地址，高6位是固定的011110，最后1位SA0可由用户自定义。参考OLED模块手册，可得最后1位的默认值为0R/W#(读写标识)应为0。（增加或移出相应电阻可以更改高低电平实现修改地址）​ 结论：地址 + 读写标识应为01111000(0x78)。说明：MCU向SSD1315发送的数据可分为两类 -- Controlbyte和Data byte。Data byte: 数据字节，其内容可能是Data(显示数据)，也可能是Command(控制命令)。Control byte: 控制字节，在Data byte之前发送，用于声明后续Data byte内容的类型和格式，该字节只有高2位有效，其余6位均为0;D/C#(Data/Command): 用于声明后续的Data byte是Data还是Command，1表示Data，0表示Command。Co(Contination Bit): 用于声明后内容是否连续。0表示连续。此时后边可以连续发送多个Data bvte；1表示不连续，此时后边只能发送一个Data byte。需要注意的是，当Co设置0时，本次通信中后续所有的字节都会被视作Databyte，co位的具体用法如下:1.2.3 GDDRAM​ GDDRAM的作用是存储显示数据，其容量是128×64 bit，每个bit对应着屏幕上的一个像素，该bit的值就能决定该像素的亮灭。1.2.3.1 内存组织结构​ 分为8页，每一页128x8个字节：每一页的结构写一个Data byte：从上往下，而不是从左往右1.2.3.2 内存寻址​ 在了解了内存组织结构之后，我们还需掌握内存的寻址方式，这样才能在我们指定的位置写入指定的内容。​ 向GDDRAM写入数据时是以byte为单位的，按照上述的内存组织结构， 定位一个byte，至少需要明确两点，分别是第几页（Page Address，页地址）和第几列（Column Address，列地址）。因此GDDRAM内部设有两个指针， Page Address Pointer和Column Address Pointer，分别指向当前要写入的页和列。为方便连续写入多个字节，每当成功写入一个字节后，Page Address Pointer或者Column Address Pointer会自动递增，指向下一个要写入的位置。​ 根据指针递增逻辑的不同，GDFRAM提供了三种不同的寻址方式，分别是页寻址，水平寻址和垂直寻址，具体使用哪种模式，可以通过向SSD1315发送控制命令进行选择，具体命令如下:​ 下面逐一介绍三种寻址方式的工作原理。页寻址这种寻址方式下，用户需要先向SSD1315发送控制命令设置页地址（例如Page1）和列地址（例如COL3），此时Page Address Pointer和Column Address Pointer就会指向对应的位置。之后便可开始连续发送数据，每成功写入一个字节，Column Address Pointer都会自动+1，而Pa…

I2C总线
type: item
date: 2025-08-04T15:38:00+08:00
description: I2C总线1.1 概述​ I2C（Inter-Integrated Circuit），通常简称为IIC，是一种用在集成电路（IC）之间的串行通信总线。它是由Philips（现在的NXP半导体）在上世纪80年代开发的，并在之后广泛应用于各种电子设备和嵌入式系统中。1.2 信号线​ I2C为同步串行通信，使用两根线路进行通信，分别是数据线（SDA）和时钟线（SCL），SDA线用于数据传输，SCL线用于数据传输的同步。SCL的每个时钟周期，SDA传输一位数据。​ I2C规定，数据接收方会在每个时钟周期的高电平期间读取数据，具体来讲就是在SCL处于高电平时，读取SDA上的数据，如下图所示:​ 因此，SDA必须在SCL低电平期准备好要发送的下一位数据，然后在SCL高电平期间保持稳定。1.3 主从架构​ I2C采用主从架构，一个主设备可连接多个从设备。主设备负责发起通信和控制总线，而从设备负责响应主设备的请求。如下图所示:​ I2C总线中的每个设备都有一个唯一的地址（用7位二进制数字表示），用于在总线上标识自己。主设备可以根据地址选择性的与特定的从设备进行通信。​ 需要注意的是，SCL信号线上的时钟信号始终由主设备产生，而SDA信号线上的数据信号既可由主设备产生，也可由从设备产生。当主设备向从设备发送数据时，SDA信号由主设备产生，从设备接收信号；当主设备从从设备读取数据时，SDA信号由从设备产生，主设备接收信号。1.4 通信协议1.4.1 空闲状态​ I2C协议规定，当SDA和SCL均为高电平时，总线为空闲状态。1.4.2 起始和结束信号​ 主设备和从设备间的每次通信，都需要以一个起始信号开始，以一个结束信号终止。起始信号和结束信号的定义如下：​ 起始信号：当SCL处于高电平时，SDA由高变低。​ 停止信号：当SCL处于高电平时，SDA由低变高。 ​ 如下图：​ 起始信号和结束信号，都只能由主设备产生。1.4.3 确认信号​ I2C协议规定，发送方每发送一个字节（8位）的数据，接收方都要向发送方回复一个1位的确认信号，如下图所示。​ ​ 如果该确认信号为0表示接收方已成功接收到该字节，发送方可继续发送下一字节，这个信号在I2C协议中称为ACK（Acknowledge）；如果该信号为1，则表示接收方未能成功接收到该字节，或者不希望接收更多数据，该信号在I2C协议中称为NACK（Not Acknowledge）。1.4.4 从机地址和读写标识​ 由于一个I2C总线上可能有多个从设备，所以开始通信前，主设备需要先与目标设备取得联系，然后再进行数据传输，除此之外，主机还需要向目标设备明确本次通信的操作是写数据还是读数据。​ 上述操作的实现思路如下：当主设备发送起始信号之后，会向所有设备发送一个字节的数据，这一个字节中，前7位为目标设备地址，第8位为读/写标识（1表示读，0表示写），如下图所示。当各从设备收到这个字节的数据后，会将7位地址与自身进行对比，相同则会向主设备回复确认信号，不相同则不做任何回应。当主设备收到目标设备的确认信号后，便会开始与该设备进行通信。1.4.5 完整通信流程​ 综上所述，当主设备想要与某个从设备进行通信时，需要经历如下流程。发送起始信号;发送目标从设备地址+读写标识位;接收从设备回复的确认信号;与从设备进行数据传输（发送/接收）;发送终止信号;1.5 基础驱动编写1.5.1功能分析​ 为方便后序使用I2C协议通信，此处先编写一个通用的基础的驱动，该驱动包含的具体函数如下图所示:1.5.2 发送起始信号确保在空闲状态：当SDA和SCL均为高电平时，总线为空闲状态SCL = 1; SDA = 1;拉低SDA，发送起始信号SDA = 0;拉低SCL，准备后面的数据位:SCL = 0;代码：void Dri_IIC_Start(){ SCL = 1; SDA = 1; SDA = 0; SCL = 0; }1.5.3 发送一个字节SCL低电平期间准备数据位，例如:SDA = 1拉高SCL，发送数据:SCL = 1拉低SCL，准备下一位：SCL = 0准备下一位数据：SDA = 0拉高SCL，发送数据:SCL = 1SCL = 0至此，发送了两个数据位，不难发现，发送每个数据位的操作是相同的，因此，想要发送一个字节，只需将发送一个数据位的操作循环执行8次即可：需要注意的是I2C协议规定，发送一个字节的数据时，要从高位开始，具体代码如下：void Dri_IIC_SendByte(u8 byte) { u8 i; for (i = 0; i < 8; i++) { // SDA = (byte & (0x80 >> i)) == 0 ? 0 : 1; // 0x80: 1000 0000 if((byte & 0x80) == …

UART（四）发送多字节命令
type: item
date: 2025-08-04T10:50:00+08:00
description: UART（四）发送多字节命令1.1 具体要求当PC向单片机发送字符串turn on时，单片机需要令LED亮起，并向PC回复：OK: LED turned on\r\n当PC向单片机发送字符串off时，单片机需要令LED熄灭，并向PC回复：OK: LED turned off\r\n当PC向单片机发送其他字符串时，单片机不做任何操作，只需向PC回复：Error:Unknown command1.2 实现方式1特定结束符判断是否接收完毕数据，如回车符：定义接收缓冲区和接收到的数据索引以及是否接收完毕的变量：u8 buffer_str[32]; // 接收缓冲区 u8 buffer_str_len = 0; bit recv_line_ready;中断函数， 接收到\r时将是否接收完毕的变量置1void Dri_UART_Func() interrupt 4 { // 收到命令,将数据放入缓冲区 if (RI == 1) { unsigned char ch = SBUF; // 避免数组越界 if (buffer_str_len < sizeof(buffer_str) - 1) { if (ch == '\r') { recv_line_ready = 1; // 不处理'\n' } else if (ch != '\n') { buffer_str[buffer_str_len++] = ch; } } RI = 0; } ... }外界循环调用一个函数判断是否接收完毕的变量置1情况，外界提供数组，函数内部将接收到的数组内容循环赋值给外部数组：bit Dri_UART_RecvStr(char *str) { if(recv_line_ready == 1){ u8 i = 0; while (i < buffer_str_len) { str[i] = buffer_str[i]; i++; } str[i] = '\0'; buffer_str_len = 0; // 清空缓冲区 recv_line_ready = 0; // 重置接收状态 return 1; }else { return 0; // 没有接收到完整的字符串 } }外界循环调用，收到完整字符串后判断是否为需要的内容，并做相应的处理逻辑：while (1) { if(Dri_UART_RecvStr(buffer_strs) == 1){ if (strcmp(buffer_strs, "turn on") == 0) { Dri_UART_SendStr("OK: LED turned on\r\n"); } else if (strcmp(buffer_strs, "off") == 0) { Dri_UART_SendStr("OK: LED turned off\r\n"); } else { Dri_UART_SendStr("ERROR: Unknown command\r\n"); } } }完整代码Dri_UART.h#ifndef __DRI_UART_H__ #define __DRI_UART_H__ #include <STC89C5xRC.H> #include "Util.h" void Dri_UART_Init(); void Dri_UART_Func(); void Dri_UART_SendChar(char str); void Dri_UART_SendStr(char *str); bit Dri_UART_RecvStr(char *str); #endifDri_UART.c#include "Dri_UART.h" u8 buffer_str[32]; // 接收缓冲区 u8 buffer_str_len = 0; bit recv_line_ready; // 发送状态,1:正在发送,0:未在发送 static bit is_sending; void Dri_UART_Init() { // 1.设置串口工作模式 SM0 = 0; SM1 = 1; // 2.设置波特率 // 2.1 设置SMOD控制位 PCON &= ~0x80; // 2.2设置定时器1工作模式 TMOD &= 0x0F; TMOD |= 0x20; // 2.3 设置定时器1初值 TH1 = 0xFD; TL1 = 0xFD; // 2.4 启动定时器1 TR1 = 1; // 3.串口接收相关配置 REN = 1; SM2 = 0; // 打开中断总开关和串口中断开关 EA = 1; ES = 1; TI = 0; RI = 0; is_sending = 0; } void Dri_UART_SendChar(char ch) …

UART（三）发送单字节命令
type: item
date: 2025-08-02T17:54:11+08:00
description: UART（三）发送单字节命令​ 使用UART与PC进行通信，通过PC向单片机发送命令，控制LED的亮灭​ 当前需求是实现PC单片机的串口通讯，但是现在的PC基本都不再提供串口，因此需要使用一个USB转串口的芯片来实现PC与单片机的通讯，如下图所示。1.1 具体要求当PC向单片机发送字符A时，单片机需要令LED亮起，并向PC回复：Ok: LED is on。当PC向单片机发送字符B时，单片机需要令LED熄灭，并向PC回复：Ok: LED is off。当PC向单片机发送其他字符时，单片机不做任何操作，只需向PC回复：Error:Unknown command。1.2 实现思路​ 实现当前需求，需要先对串口进行初始化，具体操作如下。1.2.1 选择串口工作模式​ 本案例选择模式1，因此需要将SM0和SM1做出如下配置。SM0 = 0; SM1 = 1;1.2.2 设置波特率​ 本案例波特率选用9600，需要做如下配置。（1）SMOD控制位​ 按照前文的计算，将SMOD设置为0即可，由于SMOD不可进行位寻址，因此我们需要对其所在的寄存器PCON进行整体赋值，如下。PCON &= 0x7F;（2）定时器1​ 按照前文的计算，定时器1应工作在模式2（8位自动重装载），每次重装载的初始值应为253。具体设置如下。// 定时器1工作模式 TMOD &= 0x0F; TMOD |= 0x20; // 定时器1的初值 TH1 = 0xFD; TL1 = 0xFD; // 启动定时器1 TR1 = 1;（3）串口接收相关配置​ 串口默认不接收数据，因此需要先使能接受，另外还需将SCON寄存器中的SM2控制位设置为0，表示接受数据时不校验数据帧的停止位。REN = 1; SM2 = 0;（4）启动串口中断// 开启中断 EA = 1; // 开启串口中断 ES = 1; // 复位中断标志位 RI = 0; TI = 0;​ 完成串口的初始化之后，根据需求编写响应的业务逻辑即可。1.3 代码1.3.1 初始化void Dri_UART_Init() { // 1.设置串口工作模式 SM0 = 0; SM1 = 1; // 2.设置波特率 // 2.1 设置SMOD控制位 PCON &= ~0x80; // 2.2设置定时器1工作模式 TMOD &= 0x0F; TMOD |= 0x20; // 2.3 设置定时器1初值 TH1 = 0xFD; TL1 = 0xFD; // 2.4 启动定时器1 TR1 = 1; // 3.串口接收相关配置 REN = 1; SM2 = 0; // 打开中断总开关和串口中断开关 EA = 1; ES = 1; TI = 0; RI = 0; }1.3.2 串口中断函数当收到上位机发送的'A'命令后，打开led，收到'B'命令后，关闭led：void Dri_UART_Func() interrupt 4 { // 收到命令,将数据放入缓冲区 if (RI == 1) { if(SBUF == 'A'){ P0 = 0x00; }else if(SBUF == 'B'){ P0 = 0xFF; } RI = 0; // 清除接收中断标志 } if (TI == 1) { TI = 0; // 清除发送中断标志 } } 1.3.3 发送一个字符数据void Dri_UART_SendChar(char ch) { SBUF = ch; }1.3.4 发送一个字符串void Dri_UART_SendStr(char *str) { while (*str) { Dri_UART_SendChar(*str); str++; } }循环调用发送一个字符的函数，直接写 SBUF，但没有检查上一个字符是否已经发完，这样可能会丢字符！修改发送单个字符函数，添加全局变量is_sending避免了数据在未发送完成时被覆盖；// 发送状态,1:正在发送,0:未在发送 static bit is_sending; void Dri_UART_SendChar(char ch) { // 如果有数据在发送，等待发送完成 while (is_sending); is_sending = 1; SBUF = ch; } // 初始化函数 void Dri_UART_Init() { ... // 初始化函数添加 is_sending = 0; } // 中断函数 void Dri_UART_Func() interrupt 4 { ... // 发送完成,将发送状态置为0 if (TI == 1) { is_sending = 0; ... } }1.3.5 收到指令后回复字符串中断函数添加逻辑：void Dri_UART_Func() i…

UART（二）使用说明
type: item
date: 2025-08-02T14:45:00+08:00
description: UART（二）使用说明STC89C52系列单片机内部集成了一个功能强大的全双工串行通信口，以下是相关引脚。该通信口具有四种工作模式，如下。其中模式0为同步通讯，该模式下，TxD引脚会作为时钟信号线，RxD作为数据信号线，该模式下只能实现半双工通讯。模式1、2、3为经典的UART通讯，三者的区别在与是否有校验位，以及波特率是否可变。模式1的一个数据帧包含1个起始位，8个数据位和1个停止位。模式2和模式3的一个数据帧包含1个起始位，8个数据位，1个校验位和1个停止位。另外模式1和模式3的波特率可由定时器1进行配置，因而可以自由设置，而方式2的波特率直接由系统时钟决定，因而不可自由配置。1.1 设置工作模式​ 工作模式需要通过SCON（Serial Control，串行口控制）寄存器中的M0和SM1两个控制位进行设置，如下图所示。​ 如果使用方式1，则应将SM0置为0，SM1置为1。1.2 设置波特率1.2.1 设置原理以方式1为例:方式1的波特率会受到两个因素的影响，分别是SMOD控制位和定时器1的溢出频率，具体作用如下图所示。以中间水平线大致分割，上面为发送模块，下面为接收模块：时钟脉冲决定了发送一帧数据的频率：最初时钟来源为定时器1的溢出频率，每溢出一次向外输出一次时钟信号，经过SMOD根据配置是否分频，再往后分频16次即为波特率：溢出频率由定时器初始值决定，SMOD由配置决定​ 根据结构图可以得出公式，模式一的波特率为：1.2.2 设置步骤​ UART常用的波特率有4800、9600、19200、38400、57600、115200等，此处以9600为例。1.2.2.1 设置SMOD控制位SMOD控制位位于PCON（Power Control，电源控制）寄存器，如下图所示。此处选择0，所以此时的波特率应为（定时器1的溢出率）/ 32。1.2.2.2 设置定时器1​ 由于定时器1当前的作用仅仅是为串口提供时钟信号，因此可不开启定时器中断，只需完成以下配置即可。（1）选择定时器工作模式​ 定时器1的工作模式需要通过TMOD寄存器中的C/T控制位，以及M1和M0两个控制位进行设置，如下图所示。C/T用于设置计数/定时的工作方式，此处应选择定时模式，因此需将C/T设置为0。M1和M0，用于配置具体的工作模式，相关配置如下。由于没有启用定时器中断，因此无法在中断中重新设置定时器脉冲计数器的初始值，所以此处选择模式2（8位自动重装载）最为合理。（2）设置脉冲计数器初始值模式2的最大计数为2⁸=256，因此定时器1的溢出频率等于SYSclk / 12 /（256 - TH1）或者是SYSclk / 6 /（256 - TH1），假如当前MCU工作在12T模式，所以溢出频率就等于SYSclk / 12 /（256 - TH1）。SYSclk为系统时钟频率，工作模式为12t，则时钟频率分频12，即系统时钟频率除以12定时器计数+1；寄存器为8位，可以存256个数，当TH1为255时，此时寄存器再来一次脉冲就溢出了；每秒定时器溢出几次 = 定时器每秒加了多少次（SYSclk / 12），再除以它每次溢出需要多少次（256 - TH1）​ 因此最终的波特率就应该等于SYSclk / 12 /（256-TH1）/32，假如当前的系统时钟频率为11059200Hz，目标的波特率为9600，那么就能得到以下方程​ 所以可计算出TH1的值应为253。（3）启动定时器​ 定时器1的启动也无需外部引脚控制，因此应将GATE控制位设置为0，并将TR1控制位设置为1。1.3 发送数据​ STC89C52的串口的发送模块示意图如下。​ 数据发送的大致流程如下：​ 开发者将待发送的8位数据写入发送缓冲器（SBUF），此时发送控制器（Tx Control）就会开始工作，它会自动为数据添加起始位和结束位，从而构成一个完整的UART数据帧，然后逐位通过TxD引脚输出出去。当完成一个数据帧的输出之后，发送控制器会将发送中断控制位TI置1，向CPU请求中断，CPU检测到中断请求后就执行相应的中断服务程序。​ 总结：发送数据只需要将待发送的数据写入SBUF即可。1.4 接收数据​ STC89C52的串口的接收模块示意图如下。​ 默认情况下，串口并不会接收数据。如需接收数据，需要先将REN（Receive Enable）控制位置为1，REN控制位位于SCON寄存器，如下图所示。​ 当REN置为1后，上图中的1到0跳变检测器（1-To-0 Transition Detector）就会开始工作，具体来讲就是不断检测RxD引脚的起始位。当检测到1到0的跳变后，就会启动接收控制器（Rx Control），接收控制器会将接收到数据逐位移入到输入移位寄存器（Input Shift REG），…

UART（一）概述
type: item
date: 2025-08-01T16:38:00+08:00
description: UART（一）概述1.1 串行通讯和并行通讯串行通讯和并行通讯是数据传输的两种主要方式，两者的区别如下：串行通讯是指数据通过一条数据线逐位传输并行通讯是指数据通过多条数据线同时传输 串行并行传输速率低高硬件成本低高抗干扰能力强弱通讯距离远近1.2 单工通讯和双工通讯​ 单工和双工是通讯领域中用于描述数据传输方向的术语，它们定义了两个设备之间的数据通信方式。​ 单工通信，只允许数据在一个方向上传输，即数据只能从发送端传输到接收端，接收端无法向发送端传输数据。简单来说，就是一种“单向”通信模式，可以类比电视广播。​ 双工通信，允许数据在两个方向上传输，其下又分为两种类型：半双工和全双工 。​ 半双工通信允许数据在两个方向上传输，但不能同时进行。在任何时刻，数据只能在一个方向上传输。这意味着通信的两端可以轮流发送和接收数据，但不能同时进行。可以类比对讲机。​ 全双工通信允许数据同时在两个方向上传输。这种通信方式最为高效，因为它允许通信双方同时发送和接收数据，可以类比电话。1.3 同步通讯和异步通讯​ 同步通讯和异步通讯的区别在于发送方和接收方如何对数据进行协调统一，详细内容如下图所示：​ 假如发送方和接收方正在以串行的方式进行通信，发送方发出了这样一段信号，请思考，接收方应该如何解析这段信号？这样？还是这样？应该如何协调发送方和接收方？​ 方案一: 发送方发送数据的同时，发送一个时钟信号，并规定，接收方在每个时钟周期的下降沿对数据进行采样，这种方案叫做同步通讯(发送方和接收方使用相同的时钟信号，因此两者的时钟的是同步的)​ 方案二: 统一发送方发送数据的频率和接收方的接收频率，这样就能保证数据的正确解析，同时还不需要额外的时钟信号，这种方案叫做异步通讯(发送方和接收方的时钟不一定同步)同步通信（Synchronous Communication）： 发送方和接收方共享同一个时钟信号，数据的发送和接收都依赖这个共同的时钟来协调。常见于 I²C、SPI 等通信方式。异步通信（Asynchronous Communication）： 发送方和接收方不共享时钟，而是通过事先约定的波特率（每秒传输的比特数）和起始位、停止位等机制来实现数据同步。常见于 UART 通信。1.4 UART定义​ UART（Universal Asynchronous Receiver/Transmitter）是一种异步、全双工的串行通信接口，常用于微控制器与计算机、其他微控制器或外部设备之间的数据交换，下图是UART通信所需的信号线，其中Tx用于发送数据，Rx用于接受数据。1.5 UART通讯协议1.5.1 数据格式​ 在UART通信中，数据是逐帧（Frame）发送的，每个数据帧通常包括起始位、数据位、校验位（可选）和停止位，具体结构如下图所示。​ 下面对每个部分进行详细介绍。1）空闲状态​ 协议规定，在空闲状态下，也就是没有数据传输时，应为高电平。2）起始位​ 起始位表示一个数据帧的开始，起始位为低电平（区别于空闲状态）。3）数据位​ 传输的主体内容，位于起始位之后，长度可以是5到9位，一般都是8位。低电平表示0，高电平表示1。低位在前。4）校验位（可选）​ 用于校验当前帧的正确性，校验算法可以是奇校验或偶校验，该算法的思想如下。​ 奇校验（odd parity）：如果数据位中1的数目是偶数，则校验位为1，如果1的数目是奇数，校验位为0，目的是保证数据位+校验位中的1的总个数是奇数。​ 偶校验（even parity）：如果数据为中1的数目是偶数，则校验位为0，如果1的数目为奇数，校验位为1，目的是保证数据位+校验位中的1的个数是偶数。5）停止位​ 停止位表示数据帧的结束，通常为1位或2位，停止位为高电平。1.5.2 发送方和接收方的约定​ 为保证UART通信能够正常工作，发送方和接收方必须提前做好如下约定。1）波特率​ 波特率（Baud Rate）用于表示数据的传输速率，发送方和接收方必须约定好传输速率，才能保证数据被正确的发送和接收。​ 需要注意波特率（Baud Rate）和比特率（Bit Rate）的区别，比特率表示每秒传输的位（bit）数，而波特率表示每秒传输的符号（symbol）数。但是串口通信中，只有0和1这两个符号，因此1个符号用1位就能表示，所以此处的波特率和比特率是等价的。​ 波特率和比特率的关系取决于每个符号（symbol）携带的比特数。如果每个符号只携带1个比特，那么波特率和比特率是相等的。但如果每个符号携带多个比特（比如通过调制技术），比特率就会高于波特率。​ 公式：比特率 = 波特率 × 每个符号的比特数​ 例如：波特率是9600，每个符号携带2个比特，那么比特率就是9600 × 2 = 19200 bps。2）数据位​ 发送方和接收方…

74HC595芯片
type: item
date: 2025-07-31T10:42:00+08:00
description: 74HC595​ 74HC595芯片的相关资料如下。（1）购买链接：https://item.szlcsc.com/520614.html （2）实物图、原理图、封装工作原理​ 74HC595是一种常用的8位串行输入并行输出芯片，简单来说就是一位输入，多位输出​ 74HC595内部有两个寄存器，分别是移位寄存器(Shfit Register)和存储寄存器(storage Register)​ 移位寄存器用于接受串行数据输入，芯片会根据SCK时钟信号(上升沿)将串行输入逐位移入移位寄存器。存储寄存器与输出引脚相连，因此存储寄存器中的数据，就是芯片输出的数据，芯片会根据RCK时钟信号(上升沿)，将移位寄存器接受的串行数据一次性加载到存储寄存器进行并行输出。使用演示将SER、SCK、RCK接到单片机引脚，QA-QH接到输出引脚（如led引脚），如何输出01000000？其他引脚G#输出使能，低电平有效；SCLR#给低电平清除移位寄存器；QH‘为输出移位寄存器移出的位；

51单片机概述
type: item
date: 2025-07-16T15:48:00+08:00
description: 相关资料（1）STC单片机官网：https://www.stcaimcu.com/（2）STC89C52RC-40I-LOFP44购买链接：https://item.szlcsc.com/9213.html （3）STC89C52RC系列单片机手册 https://pan.baidu.com/s/1jrlVVyeKlaDejVkKXsJqwQ?pwd=syfc51单片机概述单片机是一种将中央处理器(CPU)、存储、输入/输出接口等集成在一个芯片上的微型计算机。它具有体积小、价格便宜、易于编程等特点，广泛应用于家用电器的智能控制模块，无人售卖系统、医疗设备中的监测系统等。1. 单片机概述51单片机（或称8051单片机）原是指1980年由Intel公司推出的一种8位微控制器系列，现在更多的指实现了8051指令集（单片机的“语言”）的一系列单片机。2. STC89C52RC概述型号STC89C52RC-40I-LOFP44，其内部同样采用了8051指令集。3. 命名规范4. 封装类型单片机的封装是指单片机的外形尺寸及材质，目前STC89C52RC有两种常见封装，如下图所示：两种封装的芯片并无性能差异，只是外观不同，具体选择哪一种需要看PCB板的设计要求5. 引脚功能概述STC89C52RC-40I-LOFP44共有44个引脚，各引脚的功能如下图所示。6. 最小系统概述电源: 和电脑电源一样，起到供电作用。电容104即10×10^4pF（0.1μF），还有一个稍大的电容10μF，电源接一大一小电容用来过滤不同频率的干扰。复位电路: 相当于电脑的重新启动功能。10μF电容串联一个10k电阻，一般称为rc复位。给单片机通电，电容开始充电，没电相当于导通，充满相当于断路，可以将这里的电容等效看做可变电阻，充的电越来越多，阻值越大。电压变化5 - 0，相当于持续一段时间是高电平，实现上电复位；在电容两侧并联一个瞬时开关，开关按下，电容短路放电，此时类似于单片机上电状态实现复位；晶振电路: 是电脑处理器的主频，2.3GHz，主频越快，处理速度越快。。不同最小系统看对应芯片手册。