簡介
都是8051衍生的8位單片機, STC單片機有89/90/10/11/12/15這幾個大系列, 每個系列的特點如下
- 89系列是傳統的8051單片機, 燒錄方法有區別, 但是功能上可以和AT89系列兼容, 屬於12T單片機
- 90系列是89系列的改進型, 12T單片機
- 10和11系列是1T單片機, 有PWM, 4態IO接口, EEPROM等功能, 但都沒有ADC
- 12系列是增強型功能的1T單片機, 型號后綴
AD
的帶ADC, 后綴S2
的除了ADC還帶雙串口 - 15系列是最新的產品, 內部集成了高精度R/C時鍾, 不需要外部晶振
STC89C52參數
STC89C52和STC89C51的區別僅在於Flash大小, C51為4K而C52為8K. 都有多種封裝, 常見的是體積較大的DIP40寬體雙列
- Flash: 8K bytes
- RAM: 512 bytes
- 內置4KB EEPROM
- 32-bit I/O
- 看門狗定時器, MAX810復位電路
- 3個16-bit定時器
- 4個外部中斷, 一個7向量4級中斷結構和全雙工串行口
- 最高運作頻率35MHz, 6T/12T可選
- VCC是5V, 不要高於這個電壓.
如果需要ADC, 可以選 STC12C5A60S2 系列, 1T型的指令時鍾速度比普通STC89系列(12T)快, 做定時時要注意.
硬件准備
- 使用常見的C51最小開發板
- 一個USB2TTL的轉接卡
- 用於查看輸出的LED+1K限流電阻
在Ubuntu20.04下的開發
軟件部分
- 編譯工具 SDCC http://sdcc.sourceforge.net/
- 燒錄工具 stcgal https://github.com/grigorig/stcgal
- (可選)封裝庫 HML_FwLib_STC89 https://github.com/MCU-ZHISHAN-IoT/HML_FwLib_STC89
安裝sdcc
Ubuntu20.04自帶的sdcc版本為3.8.0, 所以打算自行編譯安裝. 先閱讀sdcc的安裝說明, 在 http://sdcc.sourceforge.net/doc/sdccman.pdf
- 前往 https://sourceforge.net/projects/sdcc/files/sdcc/4.1.0/ 下載最新的4.1.0源碼.
- 解壓
tar -xvjf sdcc-src-4.1.0.tar.bz2
- 配置
./configure
, 過程中提示gputils需要安裝, 通過apt install gputils
安裝, 再次./configure
未再報錯, 但是提示這個gputils比較舊, 有些設備不支持, 先忽略 - 編譯
make
, 過程中提示缺texinfo, 通過apt install texinfo
安裝, 然后再make. 需要說一下的是編譯時間很長, 如果僅僅需要編譯C51, 可以在配置時禁掉其他一些不需要的芯片. - 安裝
sudo make install
檢查和測試安裝好的sdcc
- 執行
sdcc --version
看看輸出是否正確, 執行sdcc --print-search-dirs
查看庫文件的位置. 默認的8051的庫文件位置為/usr/local/share/sdcc/include/mcs51/
- 寫一段代碼test001.c,
char test;
void main(void) {
test = 0;
}
用sdcc編譯, 如果生成結果文件正常沒有報錯, 就說明安裝沒問題
# 這一步會產生test001.asm等文件
sdcc -c test001.c
# 這一步會產生test001.ihx等文件
sdcc test001.c
安裝stcgal
根據Github上項目首頁的說明, 直接用pip3 install stcgal
安裝, 然后執行stcgal -h
查看輸出
編寫代碼並用sdcc編譯
編寫一個test002.c, 通過控制占空比讓P0.7上的LED產生呼吸燈的效果.
- 8052.h是sdcc自帶的通用的C52頭文件, 不是STC89Cxx的定制頭文件
- 需要STC的頭文件, 對應STC89C52, 可以用stc89.h; 對應STC12C5A60S2, 可以用stc12.h
#include <8052.h>
#define Led10 P0_7
typedef unsigned int u16;
int atime = 64;
void delay(u16 pms) {
u16 x, y;
for (x=pms; x>0; x--) {
for (y=11; y>0; y--);
}
}
void ledfade(u16 i) {
Led10 = 0;
delay(i);
Led10 = 1;
delay(atime-i);
}
int main(void) {
u16 a, b;
while(1) {
for (a=0; a<atime; a++) {
for (b=0; b < (atime - a)/4; b++) {
ledfade(a);
}
}
for (a=atime; a>0; a--) {
for (b=0; b < (atime - a)/4; b++) {
ledfade(a);
}
}
}
}
編譯有兩種方式
- 加上
-m<port>
參數, 對應89C52/12C5A60S2, 用-mmcs51
, 如果是Makefile, 加到CCFLAG里面 - 不加參數, 在頭文件中指定路徑, 例如
#include <mcs51/8052.h>
這是使用參數的方式
sdcc -mmcs51 test002.c
燒錄
燒錄使用stcgal, 這個頁面下有詳細的使用方法
對於STC89C52使用-P stc89
, 對於STC12C5A60S2系列, 使用-P stc12
stcgal -P stc89 test002.ihx
Waiting for MCU, please cycle power: done # 這里要關閉再打開電源
Target model:
Name: STC89C52RC/LE52R
Magic: F002
Code flash: 8.0 KB
EEPROM flash: 6.0 KB
Target frequency: 11.010 MHz
Target BSL version: 3.2C
Target options:
cpu_6t_enabled=False
bsl_pindetect_enabled=False
eeprom_erase_enabled=False
clock_gain=high
ale_enabled=True
xram_enabled=True
watchdog_por_enabled=False
Loading flash: 243 bytes (Intel HEX)
Switching to 19200 baud: checking setting testing done
Erasing 2 blocks: done
Writing flash: 640 Bytes [00:00, 1844.10 Bytes/s]
Setting options: done
Disconnected!
燒錄完就會自動運行. 默認的波特率為19200, 寫入較慢, 對於STC89C52RC可以直接使用115200的波特率, 用-b
參數指定
stcgal -P stc89 -b 115200 test002.ihx
如果同時有多個com口, 用-p
指定端口
stcgal -P stc89 -b 115200 dist/89/89.hex -p /dev/ttyUSB1
使用-D
參數能顯示出串口的交互信息
stcgal -D -P stc89 -b 115200 test002.ihx
Waiting for MCU, please cycle power: <- Packet data: 46 B9 68 00 1C 00 0A 76 0A 72 0A 76 0A 72 0A 76 0A 72 0A 76 0A 72 32 43 FD F0 02 82 5A 16
done
Target model:
Name: STC89C52RC/LE52R
Magic: F002
Code flash: 8.0 KB
EEPROM flash: 6.0 KB
Target frequency: 11.010 MHz
Target BSL version: 3.2C
Target options:
cpu_6t_enabled=False
bsl_pindetect_enabled=False
eeprom_erase_enabled=False
clock_gain=high
ale_enabled=True
xram_enabled=True
watchdog_por_enabled=False
Loading flash: 337 bytes (Intel HEX)
Switching to 115200 baud: checking -> Packet data: 46 B9 6A 00 0C 8F FF FD 00 06 A0 81 28 16
<- Packet data: 46 B9 68 00 0C 8F FF FD 00 06 A0 81 26 16
setting -> Packet data: 46 B9 6A 00 0B 8E FF FD 00 06 A0 A5 16
<- Packet data: 46 B9 68 00 0B 8E FF FD 00 06 A0 A3 16
testing -> Packet data: 46 B9 6A 00 0C 80 00 00 36 01 F0 02 1F 16
<- Packet data: 46 B9 68 00 06 80 EE 16
-> Packet data: 46 B9 6A 00 0C 80 00 00 36 01 F0 02 1F 16
<- Packet data: 46 B9 68 00 06 80 EE 16
-> Packet data: 46 B9 6A 00 0C 80 00 00 36 01 F0 02 1F 16
<- Packet data: 46 B9 68 00 06 80 EE 16
-> Packet data: 46 B9 6A 00 0C 80 00 00 36 01 F0 02 1F 16
<- Packet data: 46 B9 68 00 06 80 EE 16
done
Erasing 2 blocks: -> Packet data: 46 B9 6A 00 0D 84 02 33 33 33 33 33 33 2F 16
<- Packet data: 46 B9 68 00 06 80 EE 16
done
-> Packet data: 46 B9 6A 00 8C 00 00 00 00 00 00 80 02 00 06 02 00 AC 75 81 09 12 01 4D E5 82 60 03 02 00 03 79 00 E9 44 00 60 1B 7A 00 90 01 51 78 01 75 A0 00 E4 93 F2 A3 08 B8 00 02 05 A0 D9 F4 DA F2 75 A0 FF E4 78 FF F6 D8 FD 78 00 E8 44 00 60 0A 79 01 75 A0 00 E4 F3 09 D8 FC 78 00 E8 44 00 60 0C 79 00 90 00 01 E4 F0 A3 D8 FC D9 FA 75 08 40 75 09 00 02 00 03 AE 82 AF 83 EE 4F 60 14 7C 0B 7D 00 1C BC FF 01 1D EC 4D 70 F7 1E BE FF FB 16
<- Packet data: 46 B9 68 00 07 80 85 74 16
Writing flash: 0%| | 0/512 [00:00<?, ? Bytes/s]-> Packet data: 46 B9 6A 00 8C 00 00 00 00 80 00 80 01 1F 80 E8 22 AE 82 AF 83 C2 87 8E 82 8F 83 C0 07 C0 06 12 00 68 D0 06 D0 07 D2 87 AC 08 AD 09 EC C3 9E F5 82 ED 9F F5 83 02 00 68 7E 00 7F 00 AC 08 AD 09 C3 EE 9C EF 9D 50 45 7C 00 7D 00 AA 08 AB 09 EA C3 9E FA EB 9F C3 13 CA 13 CA C3 13 CA 13 CA FB C3 EC 9A ED 9B 50 1E 8E 82 8F 83 C0 07 C0 06 C0 05 C0 04 12 00 85 D0 04 D0 05 D0 06 D0 07 0C BC 00 C9 0D 80 C6 0E BE 00 B3 0F 80 B0 14 16
<- Packet data: 46 B9 68 00 07 80 1E 0D 16
-> Packet data: 46 B9 6A 00 8C 00 00 00 01 00 00 80 AE 08 AF 09 EE 4F 60 A4 7C 00 7D 00 AA 08 AB 09 EA C3 9E FA EB 9F C3 13 CA 13 CA C3 13 CA 13 CA FB C3 EC 9A ED 9B 50 1E 8E 82 8F 83 C0 07 C0 06 C0 05 C0 04 12 00 85 D0 04 D0 05 D0 06 D0 07 0C BC 00 C9 0D 80 C6 1E BE FF 01 1F 80 B7 75 82 00 22 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF AD 16
<- Packet data: 46 B9 68 00 07 80 36 25 16
Writing flash: 75%|█████████████████████████▌ | 384/512 [00:00<00:00, 3814.67 Bytes/s]-> Packet data: 46 B9 6A 00 8C 00 00 00 01 80 00 80 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 77 16
<- Packet data: 46 B9 68 00 07 80 80 6F 16
Writing flash: 640 Bytes [00:00, 4207.06 Bytes/s]
Setting options: -> Packet data: 46 B9 6A 00 0A 8D FD FF FF FF FB 16
<- Packet data: 46 B9 68 00 0A 8D FD FF FF FF F9 16
done
-> Packet data: 46 B9 6A 00 06 82 F2 16
Disconnected!
SDCC 下的串口
開啟串口, 並將putchar()重定向到串口
- 對於使用11.0592晶振的STC89C52
- 使用T1,不加倍時最高波特率只能達到28800, 加倍后是57600
- 使用T2, T2作為波特率發生器時, 遞增頻率為晶振頻率的2分頻, 可以達到115200
- 對於使用11.0592晶振的STC12C5A60S2, 1T模式波特率達到115200沒問題, 如果開了12T模式, 和89C52是一樣的
頭文件serial.h
#ifndef __SERIAL_H__
#define __SERIAL_H__
#include <mcs51/8051.h>
void serial_init(void);
#endif // __SERIAL_H__
C文件serial.c
#include "serial.h"
// configure serial for 9600 baud, 8 data bits, 1 stop bit.
void serial_init(void) {
TMOD = 0x21;
SCON = 0x40;
TH1 = 0xFD; // TH1 = 256 - 11.0592 * 1000 * 1000 / 12 / 32 / 9600;
TCON |= 0x40; // start the timer1
SCON |= 0x02; // tell putchar() the serial is ready to send
}
如果使用T2, 波特率公式如下:
波特率 = 11059200 / { 32×[65536-(RCAP2H,RCAP2L)] }
其中的RCAP2H,RCAP2L為自動重裝值,由上式得
RCAP2H, RCAP2L = 65536 - 11059200 / (32×波特率)
這樣得波特率為115200時,RCAP2H, RCAP2L = 0xff, 0xfd
void init_com( void ) {
SCON=0x50; //串口工作方式1,8位UART,波特率可變
TH2=0xFF;
TL2=0xFD; //波特率:115200 晶振=11.0592MHz
RCAP2H=0xFF;
RCAP2L=0xFD; //16位自動再裝入值
TCLK=1; //波特率發生器工作方式
RCLK=1;
C_T2=0;
EXEN2=0;
TR2=1 ; //定時器2開始
}
SDCC下的時鍾中斷
用11.0592MHz晶振的C52產生較精確的1秒定時中斷, 定時器初始值由如下計算得到
- 由晶振11.0592 MHz, 得到定時器時鍾為 11.0592 / 12 = 0.9216 MHz,
- 因此1ms對應 921.6 個時鍾周期,
- 因此50ms對應 46080 個時鍾周期,
- 將其設為一次中斷后, 20次中斷就對應1s
代碼
#include <8052.h>
const unsigned char th = (65536 - 46080) / 256;
const unsigned char tl = (65536 - 46080) % 256;
volatile unsigned char i = 0;
void main() {
TMOD= 0x01; //工作方式為16位定時器
TH0 = th; //計數寄存器高8位
TL0 = tl; //計數寄存器低8位
EA = 1; //允許中斷
ET0 = 0x01; //允許T0中斷
TR0 = 1; //啟動T0
while(1);
}
void Timer0IRQ(void) __interrupt (1) // 中斷處理函數 T0 -> 中斷1
{
i++;
if(i > 20) {
P0_7 = (P0_7 == 1)? 0 : 1; //觸發P0.7 LED閃爍
i = 1; // 注意這邊不能初始化為0, 否則每次會多跑一個中斷
}
TH0 = th; //計數寄存器高8 位重新載入
TL0 = tl; //計數寄存器低8 位重新載入
}
參考
- Very helpful SDCC C51 code examples https://github.com/hungtcs-lab/8051-examples