外观
第5章 STC单片机设计开发🆕
本章旨在全面介绍STC单片机的设计与开发过程,涵盖从硬件到软件的各个方面。通过本章的学习,读者将能够掌握STC单片机的基本开发环境搭建、软件开发入门,以及常见的硬件操作实验。学习要点包括开发板的硬件功能、软件环境的配置、LED输出、按键检测、串口通信、定时器和中断实验等。通过这些实验,读者将能够熟练掌握STC单片机的基本操作和编程技巧,为后续的复杂项目开发打下坚实的基础。
5.1开发环境介绍
5.1.1 硬件介绍
本节介绍所使用的AI8051U试验箱,该试验箱采用STC的AI8051U单片机为核心,具备丰富的外设接口,包括多个GPIO引脚、串口、定时器、中断等。试验箱设计紧凑,适合初学者进行基础实验和项目开发。试验箱上的引脚布局清晰,便于连接外部器件,如LED、按键、传感器等。AI8051U试验箱不仅兼容传统的8051指令集,还支持32位指令集,可以使用Keil C51/IAR/SDCC和Keil C251编译器,实现双核兼容设计。其强大的外设包括TFPU@120MHz,支持微秒级硬件三角函数和浮点运算器,显著提升了计算性能。此外,AI8051U配备了34K SRAM和64K Flash,支持硬件USB直接连接电脑进行仿真和下载,是全球唯一具备此功能的单片机。试验箱还提供了丰富的接口,如QSPI、i8080/M6800-TFT接口、4组串口、12位ADC、轨到轨比较器等,使其成为学习和开发8051单片机的理想选择。
5.1.2 C251软件环境
本节介绍开发环境软件的下载、安装和烧录过程。首先,需要从STC官方网站下载最新的STC-ISP烧录软件和Keil C251开发环境。安装过程简单,按照提示步骤即可完成。安装完成后,通过STC-ISP软件可以对单片机进行程序烧录。烧录时需要注意选择正确的单片机型号和波特率,确保程序能够正确写入单片机。
首先登录 Keil 官网,下载最新版的 C251 安装包,下载链接如下:
信息随便填写,点确定后进入下载页面进行下载。
- 双击下载的安装包开始安装, 点击“Next”,如图5.1所示:
- 勾选“I agree to all the terms of the preceding License Agreement”,然后点击“Next”,如图5.2所示:
3.选择安装目录,然后点击“Next”,如图5.3所示:
4.填写个人信息,然后点击“Next”,如图5.4所示:
5.安装完成,点击“Finish”结束,如图5.5所示。
Ai8051U 型号可支持 32 位模式和 8 位模式。如果用户需要使用 32 位模式,则需要安装 Keil 的 C251
编译器;如果用户需要使用 8 位模式,则需要安装 Keil 的 C51 编译器;如果需要同时使用 32 位模式和
8 位模式,则 C251 和 C51 都需要安装。
“AIapp-ISP”下载软件的如下界面可自动安装 Keil 的仿真驱动程序和所有系列的头文件,如图5.6所示。
对于 Ai8051U 系列:
如果有安装 Keil C51 编译器,则会在 Keil 安装目录中的“C51\INC\STC”目录,安装“Ai8051U.h”头文件,
这个是 8 位 8051 的文件;
如果有安装 Keil C251 编译器,则会在 Keil 安装目录中的“C251\INC\STC”目录,安装“Ai8051U.h”头文件。
这个是 32 位 8051 的同名但放在不同目录的实际不同的 32 位 8051 的头文件。
ISP 下载流程图
5.2 软件开发入门
5.2.1 LED输出实验
本节介绍如何通过IO操作控制LED的亮灭。实验中,将LED连接到单片机P0引脚,通过编程控制该引脚的电平状态来实现LED的开关。实验步骤包括:连接8个LED到P0引脚、编写控制程序、烧录程序到单片机、观察LED的亮灭状态。
/\*---------------------------------------------------------------------\*/
/\* --- Web: www.STCAI.com ---------------------------------------------\*/
/\*---------------------------------------------------------------------\*/
/\* 本例程基于AI8051U为主控芯片的实验箱进行编写测试 \*/
#include "AI8051U.h"
#include "stdio.h"
#include "intrins.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#define MAIN\_Fosc 24000000UL // 主时钟频率
void delay\_ms(u8 ms); // 延时函数声明
void main(void)
{
WTST = 0; // 设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; // 扩展寄存器(XFR)访问使能
CKCON = 0; // 提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0xff; // 设置P0口为推挽输出
P1M1 = 0x00; P1M0 = 0x00; // 设置P1口为准双向口
P2M1 = 0x00; P2M0 = 0x00; // 设置P2口为准双向口
P3M1 = 0x00; P3M0 = 0x00; // 设置P3口为准双向口
P4M1 = 0x00; P4M0 = 0x00; // 设置P4口为准双向口
P5M1 = 0x00; P5M0 = 0x00; // 设置P5口为准双向口
P6M1 = 0x00; P6M0 = 0x00; // 设置P6口为准双向口
P7M1 = 0x00; P7M0 = 0x00; // 设置P7口为准双向口
P40 = 0; // LED Power On
while(1)
{
P00 = 0; // LED On
delay\_ms(250);
P00 = 1; // LED Off
P01 = 0; // LED On
delay\_ms(250);
P01 = 1; // LED Off
P02 = 0; // LED On
delay\_ms(250);
P02 = 1; // LED Off
P03 = 0; // LED On
delay\_ms(250);
P03 = 1; // LED Off
P04 = 0; // LED On
delay\_ms(250);
P04 = 1; // LED Off
P05 = 0; // LED On
delay\_ms(250);
P05 = 1; // LED Off
P06 = 0; // LED On
delay\_ms(250);
P06 = 1; // LED Off
P07 = 0; // LED On
delay\_ms(250);
P07 = 1; // LED Off
}
}
// 延时函数,支持1~255ms的延时
void delay\_ms(u8 ms)
{
u16 i;
do{
i = MAIN\_Fosc / 6000;
while(--i);
}while(--ms);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
🚩练习任务 :
尝试让LED反向流动。
5.2.2 按键检测实验
本节介绍如何通过IO操作检测P32口的按键短按状态,包括按键的消抖处理。实验中,将按键连接到单片机的P32引脚,通过编程读取该引脚的电平状态来判断按键是否按下,并在按键按下时改变P00引脚的电平状态。实验步骤包括:连接按键到P32引脚、编写检测程序、烧录程序到单片机、观察按键状态的变化。
/\*---------------------------------------------------------------------\*/
/\* --- Web: www.STCAI.com ---------------------------------------------\*/
/\*---------------------------------------------------------------------\*/
/\* 本例程基于AI8051U为主控芯片的实验箱进行编写测试 \*/
#include "AI8051U.h"
#include "stdio.h"
#include "intrins.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#define MAIN\_Fosc 24000000UL // 主时钟频率
sbit KEY = P3^2; // 定义按键连接的引脚
sbit LED = P0^0; // 定义LED连接的引脚
void delay\_ms(u8 ms); // 延时函数声明
void main(void)
{
WTST = 0; // 设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; // 扩展寄存器(XFR)访问使能
CKCON = 0; // 提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0xff; // 设置P0口为推挽输出
P1M1 = 0x00; P1M0 = 0x00; // 设置P1口为准双向口
P2M1 = 0x00; P2M0 = 0x00; // 设置P2口为准双向口
P3M1 = 0x00; P3M0 = 0x00; // 设置P3口为准双向口
P4M1 = 0x00; P4M0 = 0x00; // 设置P4口为准双向口
P5M1 = 0x00; P5M0 = 0x00; // 设置P5口为准双向口
P6M1 = 0x00; P6M0 = 0x00; // 设置P6口为准双向口
P7M1 = 0x00; P7M0 = 0x00; // 设置P7口为准双向口
P40 = 0; // LED Power On
while(1)
{
if(KEY == 0) { // 检测按键是否按下
delay\_ms(20); // 消抖延时
if(KEY == 0) { // 再次检测按键是否按下
LED = ~LED; // 改变P00引脚的电平状态
while(KEY == 0); // 等待按键释放
}
}
}
}
// 延时函数,支持1~255ms的延时
void delay\_ms(u8 ms)
{
u16 i;
do{
i = MAIN\_Fosc / 6000;
while(--i);
}while(--ms);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
🚩练习任务 :
实现按键的长按功能。小技巧:通过定时器中断来检测按键按下的时间,从而区分长按和短按。
5.2.3 串口通信实验
本节介绍如何通过串口实现单片机与PC之间的通信。实验中,将单片机的串口引脚连接到PC的串口或USB转串口模块,通过编程实现数据的收发。实验步骤包括:连接串口模块、配置串口参数、编写通信程序、烧录程序到单片机、通过串口调试助手进行通信测试。
这里我们使用STC-ISP的串口生成工具:
将生成的程序直接放入main.c文件中,初始化部分就可以不用担心了,直接调用即可
/\*---------------------------------------------------------------------\*/
/\* --- Web: www.STCAI.com ---------------------------------------------\*/
/\*---------------------------------------------------------------------\*/
/\* 本例程基于AI8051U为主控芯片的实验箱进行编写测试 \*/
#include "AI8051U.h"
#include "stdio.h"
#include "intrins.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#define MAIN\_Fosc 22118400UL // 主时钟频率
sbit KEY = P3^2; // 定义按键连接的引脚
void Uart1\_Init(void); // 串口初始化函数声明
void UART\_Send(unsigned char dat); // 串口发送函数声明
void delay(unsigned int ms); // 延时函数声明
void main(void)
{
WTST = 0; // 设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; // 扩展寄存器(XFR)访问使能
CKCON = 0; // 提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0xff; // 设置P0口为推挽输出
P1M1 = 0x00; P1M0 = 0x00; // 设置P1口为准双向口
P2M1 = 0x00; P2M0 = 0x00; // 设置P2口为准双向口
P3M1 = 0x00; P3M0 = 0x00; // 设置P3口为准双向口
P4M1 = 0x00; P4M0 = 0x00; // 设置P4口为准双向口
P5M1 = 0x00; P5M0 = 0x00; // 设置P5口为准双向口
P6M1 = 0x00; P6M0 = 0x00; // 设置P6口为准双向口
P7M1 = 0x00; P7M0 = 0x00; // 设置P7口为准双向口
P40 = 0; // LED Power On
Uart1\_Init(); // 初始化串口
while(1)
{
if(KEY == 0) { // 检测按键是否按下
delay(20); // 消抖延时
if(KEY == 0) { // 再次检测按键是否按下
UART\_Send('K'); // 发送按键状态
while(KEY == 0); // 等待按键释放
}
}
}
}
// 串口初始化函数,115200bps@22.1184MHz
void Uart1\_Init(void)
{
SCON = 0x50; // 8位数据,可变波特率
AUXR |= 0x40; // 定时器时钟1T模式
AUXR &= 0xFE; // 串口1选择定时器1为波特率发生器
TMOD &= 0x0F; // 设置定时器模式
TL1 = 0xD0; // 设置定时初始值
TH1 = 0xFF; // 设置定时初始值
ET1 = 0; // 禁止定时器中断
TR1 = 1; // 定时器1开始计时
}
// 串口发送函数
void UART\_Send(unsigned char dat)
{
SBUF = dat; // 发送数据
while(!TI); // 等待发送完成
TI = 0; // 清除发送完成标志
}
// 延时函数,支持1~255ms的延时
void delay(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 123; j++);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
🚩练习任务 : 实现通过串口发送按键状态到PC。
5.2.4 定时器实验
本节介绍单片机定时器的原理及应用。实验中,通过定时器实现精确的时间控制,如定时闪烁LED、定时检测按键等。实验步骤包括:配置定时器参数、编写定时器中断服务程序、烧录程序到单片机、观察定时效果。
这里我们使用STC-ISP自带的定时器计算工具,并且在定时器中断里进行LED闪烁状态的切换,定时器模式选用16位自动重载模式,这样中断可以不用重新装载初值,更加方便。
/\*---------------------------------------------------------------------\*/
/\* --- Web: www.STCAI.com ---------------------------------------------\*/
/\*---------------------------------------------------------------------\*/
/\* 本例程基于AI8051U为主控芯片的实验箱进行编写测试 \*/
#include "AI8051U.h"
#include "stdio.h"
#include "intrins.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#define MAIN\_Fosc 24000000UL // 主时钟频率
sbit LED = P1^0; // 定义LED连接的引脚
void Timer0\_Init(void); // 定时器0初始化函数声明
void Timer0\_ISR(void) interrupt 1; // 定时器0中断服务函数声明
void main(void)
{
WTST = 0; // 设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; // 扩展寄存器(XFR)访问使能
CKCON = 0; // 提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0xff; // 设置P0口为推挽输出
P1M1 = 0x00; P1M0 = 0x00; // 设置P1口为准双向口
P2M1 = 0x00; P2M0 = 0x00; // 设置P2口为准双向口
P3M1 = 0x00; P3M0 = 0x00; // 设置P3口为准双向口
P4M1 = 0x00; P4M0 = 0x00; // 设置P4口为准双向口
P5M1 = 0x00; P5M0 = 0x00; // 设置P5口为准双向口
P6M1 = 0x00; P6M0 = 0x00; // 设置P6口为准双向口
P7M1 = 0x00; P7M0 = 0x00; // 设置P7口为准双向口
P40 = 0; // LED Power On
Timer0\_Init(); // 初始化定时器0
while(1); // 主循环
}
// 定时器0初始化函数,1毫秒@24.000MHz
void Timer0\_Init(void)
{
AUXR |= 0x80; // 定时器时钟1T模式
TMOD &= 0xF0; // 设置定时器模式
TL0 = 0x40; // 设置定时初始值
TH0 = 0xA2; // 设置定时初始值
TF0 = 0; // 清除TF0标志
TR0 = 1; // 定时器0开始计时
ET0 = 1; // 使能定时器0中断
}
// 定时器0中断服务函数
void Timer0\_ISR(void) interrupt 1
{
static unsigned int count = 0;
TH0 = 0x40; // 重装定时初值
TL0 = 0xA2;
count++;
if(count >= 500) { // 500ms
count = 0;
LED = ~LED; // 切换LED状态
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
🚩练习任务 : 实现通过改变定时器中断内容控制LED的闪烁频率。
5.2.5中断实验
本节介绍单片机中断的原理及应用。实验中,通过外部中断和定时器中断实现事件的快速响应,通过设定中断优先级,实现定时器中断取反LED,外部中断INT0中断时打断定时器中断,并对当前状态立刻做一次取反,在抬起按键之前始终进行保持。实验步骤包括:配置中断源、编写中断服务程序、烧录程序到单片机、观察中断触发效果。
/\*---------------------------------------------------------------------\*/
/\* --- Web: www.STCAI.com ---------------------------------------------\*/
/\*---------------------------------------------------------------------\*/
/\* 本例程基于AI8051U为主控芯片的实验箱进行编写测试 \*/
#include "AI8051U.h"
#include "stdio.h"
#include "intrins.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#define MAIN\_Fosc 24000000UL // 主时钟频率
sbit LED = P1^0; // 定义LED连接的引脚
sbit KEY = P3^2; // 定义按键连接的引脚
void Timer0\_Init(void); // 定时器0初始化函数声明
void ExtInt0\_Init(void); // 外部中断0初始化函数声明
void Timer0\_ISR(void) interrupt 1; // 定时器0中断服务函数声明
void ExtInt0\_ISR(void) interrupt 0; // 外部中断0中断服务函数声明
void main(void)
{
WTST = 0; // 设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; // 扩展寄存器(XFR)访问使能
CKCON = 0; // 提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0xff; // 设置P0口为推挽输出
P1M1 = 0x00; P1M0 = 0x00; // 设置P1口为准双向口
P2M1 = 0x00; P2M0 = 0x00; // 设置P2口为准双向口
P3M1 = 0x00; P3M0 = 0x00; // 设置P3口为准双向口
P4M1 = 0x00; P4M0 = 0x00; // 设置P4口为准双向口
P5M1 = 0x00; P5M0 = 0x00; // 设置P5口为准双向口
P6M1 = 0x00; P6M0 = 0x00; // 设置P6口为准双向口
P7M1 = 0x00; P7M0 = 0x00; // 设置P7口为准双向口
P40 = 0; // LED Power On
Timer0\_Init(); // 初始化定时器0
ExtInt0\_Init(); // 初始化外部中断0
while(1); // 主循环
}
// 定时器0初始化函数,1毫秒@24.000MHz
void Timer0\_Init(void)
{
AUXR |= 0x80; // 定时器时钟1T模式
TMOD &= 0xF0; // 设置定时器模式
TL0 = 0x40; // 设置定时初始值
TH0 = 0xA2; // 设置定时初始值
TF0 = 0; // 清除TF0标志
TR0 = 1; // 定时器0开始计时
ET0 = 1; // 使能定时器0中断
}
// 外部中断0初始化函数
void ExtInt0\_Init(void)
{
IT0 = 1; // 设置外部中断0为下降沿触发
EX0 = 1; // 使能外部中断0
EA = 1; // 使能总中断
}
// 定时器0中断服务函数
void Timer0\_ISR(void) interrupt 1
{
static unsigned int count = 0;
TH0 = 0x40; // 重装定时初值
TL0 = 0xA2;
count++;
if(count >= 500) { // 500ms
count = 0;
LED = ~LED; // 切换LED状态
}
}
// 外部中断0中断服务函数
void ExtInt0\_ISR(void) interrupt 0
{
LED = ~LED; // 立即取反LED状态
while(KEY == 0); // 等待按键释放
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
🚩练习任务 : 实现通过外部中断检测按键按下并实现LED闪烁频率改变。
5.3综合项目设计
5.3.1 项目介绍
本项目旨在基于STC8051U试验箱设计一个具备实时频谱分析功能的综合项目。项目的主要功能包括从话筒放大电路输入音频信号,通过ADC进行采样,然后进行256点FFT(快速傅里叶变换)分析,最后将频谱结果通过SPI DMA发送到OLED128x64显示屏上显示。项目的主要训练方向包括硬件电路设计、PCB布局注意事项、嵌入式软件编程以及系统调试。
5.3.2 硬件电路设计
5.3.3 软件代码编程
硬件配置与初始化(伪代码):
void PWMA\_config(void) {
// 配置PWM模块,生成触发ADC采样的定时信号
}
void ADC\_config(void) {
// 配置ADC模块,设置采样率和分辨率
}
void OLED\_init(void) {
// 初始化OLED显示屏,配置SPI接口
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
数据采集:
void ADC\_ISR(void) interrupt ADC\_VECTOR {
// 处理ADC中断,读取ADC值并存储到缓冲区
}
1
2
3
4
5
2
3
4
5
FFT计算:
void FFT(int xdata \*sample) {
// 实现256点的FFT算法
}
1
2
3
4
5
2
3
4
5
结果显示:
void Show\_OLED(void) {
// 更新OLED显示,将FFT计算得到的频域数据显示在OLED屏幕上
}
1
2
3
4
5
2
3
4
5
主循环:
void main(void) {
// 初始化系统
PWMA\_config();
ADC\_config();
OLED\_init();
while(1) {
if(B\_ADC\_OK) { // ADC采集完成
FFT(adc\_buf); // 执行FFT计算
Show\_OLED(); // 更新OLED显示
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
函数定义
void FFT(int xdata \*sample)
{
1
2
3
2
3
函数名称: FFT
参数: int xdata *sample - 指向输入样本数据的指针。
初始化变量
u16 i, j;
u16 BlockSize;
int tr, ti;
u8 OffSet1, OffSet2;
long co, si;
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
i, j: 循环索引。
BlockSize: 当前处理的块大小。
tr, ti: 临时存储实部和虚部的中间结果。
OffSet1, OffSet2: 偏移量,用于计算数组索引。
co, si: 余弦和正弦值,用于旋转因子的计算。
第一级蝶形运算
for(j=0; j<LENGTH; j+=2) // 每次处理2个元素
{
tr = sample[j+1];
FFT\_Real[j+1] = (sample[j] - tr);
FFT\_Image[j+1] = 0;
FFT\_Real[j] = (sample[j] + tr);
FFT\_Image[j] = 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
作用: 进行第一级蝶形运算,将输入样本数据分解为实部和虚部。
解释:
tr = sample[j+1]:取出第二个样本值。
FFT_Real[j+1] = (sample[j] - tr):计算第一个样本值与第二个样本值的差,存入实部数组。
FFT_Image[j+1] = 0:初始化虚部为0。
FFT_Real[j] = (sample[j] + tr):计算第一个样本值与第二个样本值的和,存入实部数组。
FFT_Image[j] = 0:初始化虚部为0。
多级蝶形运算
for(BlockSize=4; BlockSize<=LENGTH; BlockSize<<=1) // 逐级处理,每次块大小翻倍
{
for(j=0; j<LENGTH; j+=BlockSize)
{
for(i=0; i<BlockSize/2; i++)
{
OffSet1 = LENGTH/BlockSize \* i;
co = (long)COS\_TABLE[OffSet1];
si = (long)SIN\_TABLE[OffSet1];
OffSet1 = i + j;
OffSet2 = OffSet1 + BlockSize/2;
tr = (co\*FFT\_Real[ OffSet2] + si\*FFT\_Image[OffSet2]) / SCALE;
ti = (co\*FFT\_Image[OffSet2] - si\*FFT\_Real[ OffSet2]) / SCALE;
FFT\_Real[ OffSet2] = (FFT\_Real[ OffSet1] - tr) >> 1;
FFT\_Image[OffSet2] = (FFT\_Image[OffSet1] - ti) >> 1;
FFT\_Real[ OffSet1] = (FFT\_Real[ OffSet1] + tr) >> 1;
FFT\_Image[OffSet1] = (FFT\_Image[OffSet1] + ti) >> 1;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
作用: 进行多级蝶形运算,逐步完成FFT变换。
解释:
for(BlockSize=4; BlockSize<=LENGTH; BlockSize<<=1):从块大小为4开始,每次翻倍,直到块大小等于样本长度。
for(j=0; j<LENGTH; j+=BlockSize):遍历每个块的起始位置。
for(i=0; i<BlockSize/2; i++):遍历每个块内的每一对元素。
OffSet1 = LENGTH/BlockSize * i:计算当前旋转因子的索引。
co = (long)COS_TABLE[OffSet1]:从余弦表中获取旋转因子的余弦值。
si = (long)SIN_TABLE[OffSet1]:从正弦表中获取旋转因子的正弦值。
OffSet1 = i + j:计算当前元素的索引。
OffSet2 = OffSet1 + BlockSize/2:计算下一个元素的索引。
tr = (co*FFT_Real[ OffSet2] + si*FFT_Image[OffSet2]) / SCALE:计算旋转后的实部。
ti = (co*FFT_Image[OffSet2] - si*FFT_Real[ OffSet2]) / SCALE:计算旋转后的虚部。
FFT_Real[ OffSet2] = (FFT_Real[ OffSet1] - tr) >> 1:更新实部。
FFT_Image[OffSet2] = (FFT_Image[OffSet1] - ti) >> 1:更新虚部。
FFT_Real[ OffSet1] = (FFT_Real[ OffSet1] + tr) >> 1:更新实部。
FFT_Image[ OffSet1] = (FFT_Image[OffSet1] + ti) >> 1:更新虚部。
调整结果
FFT_Real[0] = FFT_Real[0] >> 1;
FFT_Image[0] = FFT_Image[0] >> 1;
}
作用: 对第一个元素进行特殊处理,调整其值。
解释:
FFT_Real[0] = FFT_Real[0] >> 1:将第一个实部值右移一位,相当于除以2。
FFT_Image[0] = FFT_Image[0] >> 1:将第一个虚部值右移一位,相当于除以2。
总结
FFT算法:通过多级蝶形运算,将输入信号分解为频域上的复数表示。
蝶形运算:每次处理两个元素,通过旋转因子进行复数乘法和加法操作。
旋转因子:利用预定义的余弦和正弦表,避免实时计算,提高效率。
结果调整:对第一个元素进行特殊处理,确保结果的准确性。
5.3.4 系统调试
说明测试环境与方法步骤
- 测试环境:
使用AI8051U实验箱V1.2进行验证。
连接麦克风放大电路、ADC模块、MCU和OLED显示屏。
- 测试方法步骤:
- 硬件连接检查:确保所有硬件模块正确连接,电源电压稳定。
- 软件烧录:将编写好的程序烧录到STC8051U微控制器中。
- 系统初始化测试:检查系统初始化是否成功,包括PWM、ADC和OLED的初始化。
- ADC采样测试:通过示波器观察ADC采样信号,确保采样率符合25600Hz的要求。
- FFT计算测试:通过示波器或逻辑分析仪观察FFT计算的输出,确保计算结果正确。
- OLED显示测试:观察OLED显示屏上的频谱显示,确保显示结果与预期一致。
5.4学习扩展
- AI8051U实验箱配套资料: 访问STC官网