STC系列8位MCU在Windows下的开发
STC系列 MCS-51 8位MCU
简介
STC的8位MCU有89/90/10/11/12/15/8(A/F/G/H)这几个大系列, 都是8051衍生的8位单片机, 每个系列的特点如下
- STC89系列
- 传统的8051单片机, 功能上和AT89系列兼容, 不同之处在于可以直接用串口烧录
- 属于12T单片机, 即指令需要12个时钟周期完成
- Y1内核
- 带RC/RD+后缀的型号, 有6T模式
- STC89LE52AD、54AD、58AD、516AD这几款带8通道8位ADC, 在P1口上
- STC89C51, STC89C52系列工作频率可以到35MHz
- STC89C51RC/RD+系列工作频率可以到40MHz
- STC90系列
- Y1内核
- STC89系列的改进型
- 12T单片机
- STC90C54AD这些带AD后缀的, 有ADC
- STC10系列
- Y3内核, 同内核汇编指令相同, 寄存器地址通用
- 1T单片机, 1T是指部分指令可以在一个时钟周期完成
- 开始内置R/C时钟
- 4态IO接口, EEPROM等功能
- STC11系列
- 在STC10基础上增加了关机模式下的唤醒时钟
- STC12系列
- Y3内核
- 型号后缀
AD
的带ADC, 后缀S2
的除了ADC还带双串口 - STC12系列是最后一个传统pin脚布局的系列, 之后的型号与AT89不再兼容
- STC12系列增加了硬件SPI, 8位PWM, ADC精度提高到10位
- STC15系列
- Y5内核
- 有部分型号使用Y3内核: STC15F104E, STC5L104E, STC15F204EA, STC15L204EA
- 内部R/C时钟基本可用, 在精度不高的场合可以使用内部时钟驱动, 不需外部晶振
- STC15系列增加了宽电压支持(W型号), 可以在3.3V和5V下工作, 不再区分高低电压版本
- STC15系列封装类型很多, 出现了8pin, 16pin等小封装
- PWM精度提高到15位
- Y5内核
- STC8A/STC8F系列
- Y6内核, 性能提升明显
- 开始出现8K字节的大内存版本
- STC8G系列
- Y6内核
- 内部R/C时钟增加到2频段
- 只有最高2K字节内存
- STC8H系列
- Y6内核
- 当前最高端的版本
- 内置时钟增加到4频段, 去掉了旧的PCA模块, 使用新的8组16位PWM
- 为兼容STC8A8K64S4定制的型号STC8A8K64D4
8位8051 MCU的内存结构
不管哪个系列, 8051系列的内存结构都是一样的. RAM 分为片内 RAM和片外 RAM
- 片内 RAM
标准 51 的片内 RAM 地址从 0x00H~0x7F 共 128 个字节,而现在用的 51 系列都带扩展片内 RAM,即 RAM 从 0x00 - 0xFF 共 256 个字节. - 片外 RAM
最大可以扩展到 0x0000~0xFFFF 共 64K 字节 - 片内 RAM 和片外 RAM 的地址不是连续的,片内从 0x00 开始, 片外也是从 0x0000 开始.
- 片内和片外的区分来自于早期的 51 单片机,分别指在芯片内部和芯片外部,但现在几乎所有的 51 单片机(包括STC89C52)芯片内部都集成了片外 RAM, 很少用到真正的芯片外扩展
Keil C51 和 SDCC 中的关键字, 代表了 RAM 不同区域
data
- Keil C51: data, SDCC: __data
- 片内RAM直接寻址区, 地址范围00 - 7F
一个变量 a, 可以这样声明unsigned char data a=0
, 在 Keil 默认设置下,data
是默认的可以省略. data 区域 RAM 的访问在汇编语言中用的是直接寻址, 执行速度是最快的
bdata
片内RAM位寻址区, 从20 - 2F这块地址的16个字节共128个可寻址位, 位地址从00 - 7F
例如
unsingned char bdata sta; // 8位的数据sta
sbit RX_DR = sta^6; //把 8位中的第6位定义为RX_DR, 下同
sbit TX_DS = sta^5;
sbit MAX_RT = sta^4;
idata
- Keil C51: idata, SDCC: __idata
- 片内RAM间接寻址区, 地址范围 00-FF
data 是 idata 的一部分. 定义成 idata,不仅仅可以访问 data 区域,还可以访问 80-FF 的范围,访问idata的时候用的是通用寄存器间接寻址, 速度较 data会慢一些, 平时大多数情况下不太希望访问 80-FF, 这块通常用于中断与函数调用的堆栈, 所以在绝大多数情况下, 使用内部 RAM 只用 data 就可以了
pdata
- Keil C51: pdata, SDCC: __pdata
- 片外 RAM, 地址范围 00-FF
使用 pdata 定义的变量存到了外部 RAM 的 00-FF 的地址范围, 和 xdata 其实是重叠的, 这块地址的访问和 idata 类似, 都是用通用寄存器间接寻址
xdata
- Keil C51: xdata, SDCC: __xdata
- 片外 RAM, 地址范围 0000 - FFFF
pdata 是 xdata 的一部分. 定义成 xdata, 可以访问更大的地址范围, 从 0 到 64K 的地址都可以访问到, 但是需要使用 2 个字节寄存器DPTRH 和 DPTRL 来进行间接寻址,速度更慢
STC89系列
STC89是最早的一个系列, 同型号的差别在于Flash和RAM的大小, 例如 STC89C51 为4K而 STC89C52 为8K, 常见的封装是DIP40宽体双列
STC89C54RC参数
- Flash: 8K bytes
- RAM: 512 bytes
- 内置4KB EEPROM
- 32-bit I/O
- 看门狗定时器, MAX810复位电路
- 3个16-bit定时器
- 4个外部中断, 一个7向量4级中断结构和全双工串行口
- 最高运作频率35MHz, 6T/12T可选
- VCC 5V
还有规格更高的STC89C516DR+, 这个有62K的flash和1280byte的RAM.
STC89C52的内存寻址
STC89C52共有 512 字节的 RAM, 分为 256 字节的片内 RAM 和 256 字节的片外RAM.
一般情况下使用 data 区域, data 不够用了就用 xdata, 如果希望程序执行效率尽量高一点,就使用 pdata 关键字来定义.
晶振
- STC89系列内部都不带R/C时钟, 必须外接晶振, 常用的有11.0592MHz和12MHz晶振.
- 采用11.0592MHz,或22.1184MHz,可方便得到串口通讯的标准时钟.
硬件准备
- C51最小开发板
- 一个USB2TTL的转接卡
- 用于查看输出的LED+1K限流电阻
Windows下的开发
软件部分
需要准备用于烧录的STC-ISP软件, 以及用于编写编译代码的Keil C51软件
- 从 https://www.stcmcudata.com/ 下载STC-ISP软件的简化版, 当前版本是V6.88F
- STC-ISP的使用说明 http://www.stcmcudata.com/STC8F-datasheet/STC-TOOL-20200821.pdf
- 从 https://www.keil.com/files/uc51/c51v959.EXE 下载Keil C51 V9.59.
STC-ISP安装
用简化版(tiny结尾的那个), 这个版本不会跟你要管理权限. 解压即可不需安装, 界面非常紧凑.
Keil C51安装
安装时, 默认安装到与Keil MDK相同的目录C:\Keil_V5
, 如果之前已经安装了Keil MDK, 可以使用同一目录, 与Keil MDK是可以共存的. 安装完之后在license里可以看到有C51的license记录
安装STC设备库
根据STC-ISP的使用说明, 将STC库文件安装到Keil C51环境中.
- 关闭Keil MDK软件
- 打开STC-ISP
- 点击右侧标签面板中的
Keil ICE Setting
, 点击Add MCU type to Keil, Add STC ICE driver to Keil
这个按钮 - 然后定位到
C:\Keil_v5
目录, 完成安装
检查
- 查看
C:\Keil_v5\UV4\
目录下是否有stc.cdb
文件, 判断是否添加完成 - 查看
C:\Keil_v5\C51\INC\
目录下是否有STC
目录, 这个目录下是STC芯片的头文件 - 重启Keil MDK后, 在
File->Device Database
中选择STC MCU Database可以看到STC下的单片机型号列表
烧录
接线
USB2TTL | STC89C52 |
---|---|
VCC | P40 VCC |
GND | P20 GND |
TX | P10 RxD |
RX | P11 TxD |
烧录
STC单片机使用的是ISP(In System Program)烧录方式, 其原理是在单片机内部固化一段ISP代码, 上电时检测是否有连续的d
字符, 如果检测到则进入ISP准备阶段, 如果超时没有收到则执行用户代码区. 若进入ISP准备阶段, 根据STC定义的协议接收数据帧, 最后完成程序的擦除、写入. 在ISP准备阶段若未收到数据帧, 则超时退出ISP, 执行用户代码区.
STC-ISP的使用说明
- 运行STC-ISP
- 选择烧录芯片对应的型号
- STC89C52RC/LE52RC, 如果不是RC, 则选择 STC89C52
- 如果型号选择不正确, 在Check MCU那一步会一直卡在那里
- 选择USB2TTL对应的COM口
- 点击Open Code File 加载烧录程序
- 设置烧录选项
- 点击Download/Program按钮
如果之前已经连线, 此时STC-ISP的日志窗口会提示Checking target MCU ...
, 按Reset开关不能进入烧录, 需要重新上电才会开始烧录. 显示信息如下
Checking target MCU ...
MCU type: STC89C52RC/LE52RC
F/W version: 3.2C
Current H/W Option:
. Current clock frequency: 11.018MHz
. System use 12T mode
. Oscillator gain is HIGH
. Any reset source can stop WatchDog if WatchDog timer is running
. Internal XRAM is ENABLE . ALE pin behaves as ALE function pin
. Do not detect the level of P1.0 and P1.1 next download
. Do not erase user EEPROM area at next download
MCU type: STC89C52RC/LE52RC
F/W version: 3.2C
Re-handshaking ... Successful [0.625"]
Current Baudrate: 115200
Erasing MCU flash ... OK ! [0.188"]
Programming user code ... OK ! [0.953"]
Programming OPTIONS ... OK ! [0.031"]
H/W Option upgrade to:
. Current clock frequency: 11.018MHz
. System use 12T mode
. Oscillator gain is HIGH
. Any reset source can stop WatchDog if WatchDog timer is running
. Internal XRAM is ENABLE . ALE pin behaves as ALE function pin
. Do not detect the level of P1.0 and P1.1 next download
. Do not erase user EEPROM area at next download
MCU type: STC89C52RC/LE52RC
F/W version: 3.2C
Complete !(2021-06-30 21:54:26)
- 如果F/W version是3.2C, 会弹出对话框, 提示这是一个翻新的芯片, 可以忽略. 4.2C的不会弹出此提示.
- STC12C5A60S2 系列在烧录时会显示MCU ID, STC89C系列不会
Keil C51代码结构
与STM32项目比, C51的项目的结构非常简单. 只需要添加一个reg52.h
的头文件, 剩下的就是自己随意组合. 这个头文件也只是包含寄存器命名.
STC89系列的主流开发方式, 还是对照着手册直接操作寄存器.
在Keil5中创建项目的步骤:
- 运行Keil MDK
- 新建项目, 找一个目录将项目文件保存
- 选择设备
- 对于STC89C52, 定位到STC89C52RC Series
- 对于STC89C516RD+, 定位到STC89C58RD+ Series
- 对于STC12C5A56S2, 定位到STC12C5A60S2 Series
- 这些都可以使用reg52.h作为头文件
- 也可以使用对应系列的头文件, 例如STC12C5A60S2.H
- 中途会提示你是否要保存startup.a51, 选择是
- 新建C文件, 写入代码
- 编译
示例代码
#include <reg52.h>
typedef unsigned int u16;
typedef unsigned char u8;
sbit led0 = P0^0;
sbit led1 = P0^1;
sbit led2 = P0^2;
sbit led3 = P0^3;
void delay(u16 i) {
while(i--);
}
void main() {
while(1) {
led0 = 0;
led1 = 0;
led2 = 0;
led3 = 0;
delay(500000);
led0 = 1;
led1 = 1;
led2 = 1;
led3 = 1;
delay(500000);
}
}
- 对于STC89C52RC, 可以改成
#include <STC89C5xRC.H>
- 对于STC12C5A60S2系列, 可以改成
#include <STC12C5A60S2.H>
常见问题
SBIT 单Bit, Singl Bit
sbit这个类型定义了SFR中的一个bit, 使用操作符^
, 这里是容易引起疑惑的一个地方, 这个符号仅仅在代码变量声明的地方可用, 代表了SFR中这个bit的位置, 在程序当中, 这个符号才是标准的bit异或操作符(XOR Operator). The character ^
is used to denote the bit position in the byte address of the SFR. This syntax is only valid for Declaration code lines. If used inside the program, then the ^
operator is the standard bitwise xor operator from the standard C language (not specific to C51).
//方式1和2: 前面的sfr-name/sfr-address是其基础地址, 一定是要可以被8整除的, 例如0xD0, 0xA8, 后面的bit-position取值可以是0~7
sbit name = sfr-name ^ bit-position;
sbit name = sfr-address ^ bit-position;
//方式3: 必须是0x80-0xFF之间的一个值, 例如0xD2, 0xD7, 0xAF
sbit name = sbit-address;
其中
- name 变量名
- sfr-name 已经定义的SFR变量
- bit-position 在SFR变量中的位置
- sfr-address SFR的地址
- sbit-address 此bit的地址
在8051应用中经常需要直接访问SFR中某一个位的信息, 这种情况就可以使用sbit. 例如:
sbit EA = 0xAF;
上面代码定义了一个sbit类型的变量EA, 其地址在0xAF. 在8051中这是中断开启寄存器中的enable all位的地址.
注意: 使用sbit访问的对象存储通常认为是little endian (LSB first), 如果使用sbit访问标准类型的数据时要留意.
注意:
- 不是所有SFR都可以按位寻址, 只有地址能被8整除的SFR才能, 即低位必须是0或8. 计算SFR的按位地址, 0xC8^6 = 0xC8 + 6 = 0xCE.
- sbit只能在函数外定义.
SFR - 特殊功能寄存器(Special Function Register)
在keil.com上有说明 https://www.keil.com/support/man/docs/c51/c51_le_sfrs.htm
sfr常用于定义寄存器地址, 对应一个8 bit volatile的值. 用法为
sfr name = address;
其中
- name SFR的名称
- address SFR的地址
SFR变量的定义和其他C变量一样, 区别仅在于不使用char或者int, 例如
sfr P0 = 0x80; /* Port-0, address 80h */
sfr P1 = 0x90; /* Port-1, address 90h */
sfr P2 = 0xA0; /* Port-2, address 0A0h */
sfr P3 = 0xB0; /* Port-3, address 0B0h */
P0, P1, P2 和 P3 都是SFR的声明和定义, 等号后面的值必须是8位数值, 8051支持的SFR地址通常为0x80-0xFF. 而NXP 80C51MX提供额外的SFR地址区间0x180-0x1FF. 注意, SFR变量不能定义在函数中, 而必须定义在主代码中, SFR变量永远是volatile的, 编译器不会去优化这种类型的变量的访问.
sfr16 占用两个内存单元, 值域为 0~65535. sfr16 和 sfr 一样用于操作特殊功能寄存器, 不一样的是它用于操作占两个字节的寄存器, 例如定时器 T0 和 T1.
C51寄存器的介绍 http://www.51hei.com/mcuteach/245.html
Flash和EEPROM的区别
EEPROM也叫 E2PROM, 称为电可擦可编程只读存储器, 和EEPROM类似, 写上去的东西也能擦掉重写, 但它要方便一些, 不需要光照, 只要用电就能擦除或者重新改写数据, 方便许多, 而且寿命也很长(几万到几十万次).
FLASH 闪存, 属于EEPROM的改进产品, 最大特点是必须按块(Block)擦除(每个区块的大小不同厂家的产品有不同的规格), 而EEPROM则可以一次只擦除一个字节(Byte). FLASH现在常用于大容量存储, 例如U盘.
C51的变量
c51编译器中int 和 short 相同,float 和 double 相同, 具体定义
类型 | 宽度 | 取值范围 |
---|---|---|
unsigned char | 1字节 | 0~255 |
signed char | 1字节 | -128~+127 |
unsigned int | 2字节 | 0~65535 |
signed int | 2字节 | -32768~+32767 |
unsigned long | 4字节 | 0~4294967295 |
signed long | 4字节 | -2147483648~+2147483647 |
float | 4字节 | -1.175494E-38~+3.402823E+38 |
指针 | 1-3字节 | 对象的地址 |
bit | 位 | 0 或 1 |
sfr | 1字节 | 0~255 |
sfr16 | 2字节 | 0~65535 |
sbit | 位 | 0 或 1 |
C51中定义一个变量的格式如下
[存储种类] 数据类型 [存储器类型] 变量名
- 在定义格式中除了
数据类型
和变量名
是必要的, 其它都是可选项 - 存储种类有四种: 自动(auto), 外部(extern), 静态(static)和寄存器(register). 缺省类型为auto.
- 数据类型即变量类型
- 存储器类型指定该变量在单片机硬件系统中所使用的存储区域, 并在编译时准确的定位. 注意在AT89c51芯片中RAM只有低128位, 80H-FFH的高128位在52芯片中才有用, 并和特殊寄存器地址重叠.
把最常用的命令如循环计数器和队列索引放在内部数据区能显著的提高系统性能, 变量的存储种类与存储器类型是完全无关的.
8051的内存结构
MCS-51内部RAM有128或256个字节的用户数据存储, 用于存放执行的中间结果和过程数据. MCS-51的数据存储器均可读写, 部分单元还可以位寻址:
- 内部RAM共有256个单元, 这256个单元共分为两部分
- 其一是地址从00H—7FH单元(共128个字节)为用户数据RAM
- 从80H—FFH地址单元(也是128个字节)为特殊寄存器(SFR)单元
- 00H—1FH, 共32个单元中被均匀地分为四块, 每块包含8个8位寄存器, 均以R0—R7来命名, 我们常称这些寄存器为通用寄存器. 这四块中的寄存器都称为R0—R7, 那么在程序中怎么区分和使用它们呢? INTEL工程师们安排了一个寄存器 -- 程序状态字寄存器(PSW)来管理它们, CPU只要定义这个寄存的PSW的第3和第4位(RS0和RS1), 即可选中这四组通用寄存器.
- 20H—2FH为位寻址区, 既可用字节寻址, 也可按位寻址. 位寻址区共有16个字节, 128个位, 对应的位地址为00H—7FH. CPU能直接寻址这些位, 按位进行操作. 我们常称MCS-51具有布尔处理功能, 布尔处理的存储空间指的就是这些按位寻址区.
复位后的各寄存器初始
复位后CPU状态
PC: 0000H TMOD: 00H
Acc: 00H TCON: 00H
B: 00H TH0: 00H
PSW: 00H TL0: 00H
SP: 07H TH1: 00H
DPTR:0000H TL1: 00H
P0-P3:FFH SCON: 00H
IP: ×××00000B SBUF: 不定
IE: 0××00000B PCON: 0×××0000B
注意P0 - P3是高位
startup.a51文件的作用
80C51在电源重置(Power On Reset)后执行的第一个程序模块并不是主程序main()
, 而是一个KEIL-C51标准链接库中的startup.a51
程序模块.
startup.a51
的主要工作, 是把包含idata, xdata, pdata在内的内存区块清除为0, 并且初始化递归指针. 在startup.a51
执行完成后, 接着被执行的仍然是一个KEIL-C51标准链接库中的init.a51
程序模块. init.a51
的主要作用, 是初始化具有非零初始值设定的变量.
在完成上述的初始化之后, 80C51才会开始执行main()
程序.
参考
- 不错的教程 http://www.51hei.com/mcuteach/252.html
- C51 汇编写的延时函数说明及时钟频率 http://www.51hei.com/mcuteach/247.html
- Very helpful SDCC C51 code examples https://github.com/hungtcs-lab/8051-examples