常见的单片机包括:8051, STM32, STC32G, ATMega328P (Arduino)。
学习单片机汇编语言有多个好处,包括但不限于以下几点:
- 成本较低:单片机通常价格便宜,适合学生和初学者进行实验和学习。
- 风险较小:若发生故障,损失相对较小。单片机的损坏不会像PC系统那样带来重大的经济损失。
- 硬体要求低:单片机的硬体要求相对简单,不需要高性能的计算机,只需基本的开发板即可。
- 简单的软体设置:开发环境设置较易,通常只需安装简单的编辑器和编译器,无需复杂的配置。
- 安全性:失败时只需关掉电源即可重启,避免了系统崩溃的风险。
- 指令集简单:单片机的汇编语言指令相对较少,更容易理解其目的和限制,有助于学习基本的编程概念。
- 程序执行速度更快:单片机的汇编语言指令执行速度相对较快,对一些需要位运算的指令尤为重要,比其他编译程序 (例如C/C++)表现更有效率。
- 实时性:单片机通常用于实时系统,学习汇编语言能帮助理解如何实现实时控制和响应。
- 深入了解电子元件:学习汇编语言可以使学生对底层硬体和电子元件有更深入的理解,提升问题解决能力。
- 实际操作经验:透过实际编写和调试程式,学生能获得宝贵的实践经验,这对未来的工程师职业生涯非常重要。
总体来说,单片机学习汇编语言是一个理想的选择,尤其适合初学者和想要深入了解电子技术的学习者。
以本人之前编写的一文“STC32G12K128计算e常数精确到小数点后19,720位的汇编程序”为例,如果改用C语言会遇到以下两大难题:
所以决定改用传统类似笔算的方法求 e 常数的值,每一个字节代表一个10进制位,故此8K只能够处理 8192个10进制位,比起原先的19,720位少了很多。
先用PC 写一个C 程序测试一下:
/* e8191.c : 计算e常数精确到小数点后8191位 */
#include <stdio.h>
#include <stdlib.h>
#define SIZE 8192 // 阵列大小, 8K xdata
unsigned char e[SIZE]; // 阵列存储常数 e
void clear(unsigned char digits[]) {
for (int i = 0; i < SIZE; i++) {
digits[i] = 0; // 阵列清零
}
}
// digits[] = digits[] / divisor
void divide(unsigned char digits[], int divisor) {
unsigned char *cptr;
int quotient, remainder=0;
for (cptr = digits; cptr < (digits + SIZE); cptr++) {
remainder = remainder * 10 + *cptr; // 右移一位
quotient = remainder / divisor; // 计算商
remainder = remainder % divisor; // 计算余数
*cptr = quotient; // 商存回阵列內
}
}
void print_result() {
unsigned char *cptr;
printf("2."); // 列印 e 常数整数部分
for (cptr = e + 1; cptr < e + SIZE; cptr++) {
printf("%01d", *cptr); // 把阵列内 e 常数小数部分逐一列出
}
printf("\n");
}
int main() {
int divisor;
clear(e); // 阵列清零
e[0] = 1; // 初始化e = 1
for (divisor = 2730; divisor >= 2; divisor--) { // 1/2730! = 6.063x10^-8198
divide(e, divisor); // 不断除 divisor
e[0]++; // 加 1
}
print_result(); // 输出 e 常数
return 0;
}
测试成功!现在改在 STC32G12K128 单片机运行C程序:
// STC32G_e8191.c : STC32G12K128计算e常数精确到小数点后8191位
#include "STC32G.h" // 头文件见下载软件
#include "intrins.h"
#define FOSC 24000000UL // FOSC = 24.000 MHz
#define BRT (256-(FOSC/14400+16)/32) // 加16 操作是为了让Keil 编译器
// 自动实现四舍五入运算
#define SIZE 8192 // 8K xdata
#define DIV_START 2730 // 1/2730! = 6.06x10^-8198
unsigned char xdata e[SIZE]; // 声明关键字为 xdata
bit busy; // 发送忙标志
unsigned int shouldBreak = 0;
char wptr; // 写缓冲区指针
char rptr; // 读缓冲区指针
char buffer[16]; // 缓冲区长度为16个字符
// 串口1中断函数
void UartIsr() interrupt 4
{
if (TI)
{
TI = 0; // 清中断标志
busy = 0; // 清发送忙标志
}
if (RI)
{
RI = 0; // 清中断标志
buffer[wptr++] = SBUF; // 接收串口数据
wptr &= 0x0f; // 环形缓冲
}
}
// 串口1 初始化
void UartInit()
{
SCON = 0x50; // 串口1 mode 1
S1BRT = 0; // 选取Timer1 作为波特率产生器
TMOD = 0x20; // Timer1 mode 2
TL1 = TH1 = BRT; // 设立自动重载值
TR1 = 1; // 启动Timer 1
T1x12 = 1; // Timer1 1T mode (FOSC 不分频)
wptr = 0x00; // 清缓冲区指针
rptr = 0x00;
busy = 0; // 清发送忙标志
}
void UartSend(unsigned char dat)
{
while (busy); // 等待发送完成
busy = 1; // 设置发送忙标志
SBUF = dat; // 数据写入缓冲器
}
void UartSendStr(char *p)
{
while (*p) // 字串未完全送出
{
UartSend(*p++); // 送出字符
}
}
void clear(unsigned char digits[]) {
int i;
for (i = 0; i < SIZE; i++) {
digits[i] = 0; // 清 digits 阵列
}
}
// digits[] = digits[] / divisor
void divide(unsigned char digits[], int divisor) {
unsigned char *cptr;
int quotient, remainder=0;
for (cptr = digits; cptr < (digits + SIZE); cptr++) {
remainder = remainder * 10 + *cptr; // 右移一位即是 x10
quotient = remainder / divisor; // 求商
remainder = remainder % divisor; // 求余数
*cptr = quotient; // 商存回 e 阵列
}
}
// 输出 e
void print_result(unsigned char digits[]) {
unsigned char *cptr;
UartSendStr("\r\n");
UartSendStr("2."); // e 整数部分
for (cptr = digits + 1; cptr < (digits + SIZE); cptr++) {
UartSend((unsigned char)(*cptr+'0')); // e 分数部分
}
UartSendStr("\r\n");
}
// 主程序
int main() {
int divisor;
clear(e); // 清 e 阵列
EAXFR = 1; // 使能访问XFR
CKCON = 0x00; // 设置外部数据总线速度为最快
WTST = 0x00; // 赋值为0 可将CPU 执行程序的速度设置为最快
P3M0 = 0x00; // P3 设定为准双向口
P3M1 = 0x00;
UartInit(); // 串口1 初始化
ES=1; // 使能串口中断
EA=1; // 启动总中断
UartSendStr("\r\nCompute e up to 8191 decimals\r\nWorking, please wait");
e[0] = 1; // 初始化e = 1
for (divisor = DIV_START; divisor >= 2; divisor--) {
UartSend('.'); // 每一个循环输出小圆点, 代表工作中请等候
divide(e, divisor); // 不断除以 divisor
e[0]++; // 加 1
}
print_result(e); // 输出 e
while(1); // 停止
return 0;
}
PC 串口3 接收单片机数据的画面:
总而言之,虽然C 语言比较容易编写和维护,但对单片机来说,C语言在低级操作上可能不如汇编语言灵活,特别是在直接控制硬件时;另外,C语言也较难执行位运算,譬如在任意精度计算程序中,需要移动几千个字节内的某一个位元。
总的来说,选择使用C语言还是汇编语言,通常取决于具体的应用需求和开发环境。对于大多数嵌入式硬件开发,较多人选择C语言,因为它提供了更多的函数库和更好的可读性,但本人却独爱汇编语言。