Files
fzu-product/2023旧版内容/5.富有生命的嵌入式/5.2New meaning of C.md

158 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# New meaning of C
> Author肖扬
在上一篇文章中,我们介绍了嵌入式相关的产品,其中芯片充当着及其重要的作用,本篇我们将以孵化器实验室的部分考核流程与教程作为参考,并且加以一定的概念解析,逐步地为大家解开单片机裸机开发的面纱。
PS在学习此模块之前我希望你们能熟练掌握 C 语言的基础知识,特别是指针方面的内容。
如果你还没有完成 C 语言的学习,可以参考此文章:[3.4C 语言](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.4C%E8%AF%AD%E8%A8%80.md)
## STC89C52--一款入门级的芯片
### 相关介绍
Intel 的 STC89C51\C52 系列早在上世纪 80 年代就已经广泛运用,考虑到其精简但较为充足的硬件资源以及其低廉的价格,笔者十分建议可以从 51 单片机系列开始去接触整个嵌入式行业(因为你需要知道自己适不适合进入到嵌入式行业,大学本就是一个试错的过程,而低廉的价格使得其试错成本变得很低)
以下是比较推荐的学习路线:
1、购买 51 单片机:[CZ0001「普中 51 单片机学习板开发板 stc89c52 单片机实验板 C51 单片机 diy 套件」](https://m.tb.cn/h.UuGJR5G?tk=kN94dm040JX)
2、推荐学习视频[【51 单片机入门教程 -2020 版 程序全程纯手打 从零开始入门】](https://b23.tv/KmaWgUK)
3、相关学习资料
软件安装包、开发板资料、课件及程序源码百度网盘链接:
[https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng](https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng) 提取码gdzf链接里压缩包的解压密码51如果打不开请复制链接到浏览器再打开
## 寄存器VERRY--IMPORTANT!!!
相信学完 C51 的内容,你已经对寄存器有了一定的了解。
但是由于其重要性,以及为了巩固各位的基础知识,防止出现有些人学了 A 只会 A学了 B 只会 B而不会 CDEF...的现象,所以这里我必须要着重重新讲解一下寄存器的概念。
在 C 语言中我们有 int、short、long 等变量,实际上 C 语言中还可以定义一个寄存器变量register
那么为什么需要寄存器呢,或者说我们为什么需要一个寄存器变量呢。
这里我不从计算机组成原理或者微机接口与技术的概念入手,而是举一个简单的例子:
如果我们在图书馆上准备看书,去获取知识,此时我们是 CPU、书则是数据。
如果我们去图书馆里的书架上拿书并观看,则需要:走到对应书架 - 拿书(获取数据)- 回到书桌,这需要花费相当一部分的时间,此时硬盘相当于书架;如果我们直接拿书桌上的书,则相对速度会快很多,此时书桌相当于主存;如果我们手上就有一本书,那么我们低头就可以看到,手就相当于寄存器。所以,寄存器是 CPU 内部用来存放数据的一些小型的存储区域,用来暂时存放参与运算的数据和运算结果以及一些 CPU 运行所需要的信息。
以我举例而言,**可见寄存器获得数据的速度会快于主存与硬盘,而存储数据的大小将会小于主存与硬盘**,如果这块不清楚的话也可以去看 也许你会用上的基础知识 中的存储器知识部分。
而从汇编语言的角度来讲(此为 Intel 的汇编语法):
```asm
WAIT_FOR_TIMER:
MOV A, LED_COUNT ;读取当前亮灯的编号
CJNE A, #00H, NOT_FIRST_LED ;如果不是第一个灯则跳转到NOT_FIRST_LED标签
MOV A, #03H ;如果是第一个灯则将延时设为3秒
SJMP DELAY ;跳转到延时
NOT_FIRST_LED:
.......
DELAY:
.......
;此汇编代码块中累加器A作为一个常用的特殊寄存器充当着暂时存储数据信息的作用
;存储LED_COUNT的编号并用于比较大小存储所需延时时间并用于DELAY块
```
```asm
ORG 0BH
PUSH ACC ; 保存寄存器状态
PUSH PSW
......
POP PSW ; 恢复寄存器状态
POP ACC
RETI ; 返回中断
;此汇编代码块用于基本的中断处理我们需要保存ACC、PSW的状态来维持中断以及中断后程序的正常进行
```
以上简单例举了寄存器的一般作用,以汇编语言出发去讲的原因是:它能有效地展现底层代码的工作原理,既不会像机器语言那样只用 0、1 表示的晦涩难懂,又不会像高级语言那般难以直观地看到底层的工作方式。但是,我们做嵌入式入门开发的过程中并不会让你直接去写汇编语言,而是以最基础的 C 语言相比汇编而言C 在功能上、结构性、可读性、可维护性上有明显的优势),通过 Keil、IAR 等拥有交叉编译器的 C 语言软件开发系统来完成高级语言、汇编语言、机器语言的转码工作,从而通过 C 语言的编写来控制单片机等嵌入式系统的开发。
而抽象层面的 C 代码需要通过访问寄存器来直接控制硬件。所以在嵌入式开发的过程中C 有了特殊的含义:**C 里的数字代表的可能只是一个地址或者一个数据,但是在嵌入式开发里,这样一个数字也可以代表着一种寄存器状态。**
下面引入一个 STM32F1 系列的 GPIO 部分寄存器图(来源正点原子提供的 F1 参考手册):
![](https://cdn.xyxsw.site/MyDMbeCKLowC1Mx7Q6Ec9BLPn4g.png)
![](https://cdn.xyxsw.site/LJ1SbFfv6oUIgtx8CstcbWTNnRg.png)
如果我们想做一个简单的实验 - 驱动一个 LED 灯(假设此 LED 灯以 PB5 为输出驱动口),在对相应的 RCC 时钟等配置之外,最重要的是对相应的 GPIO 口的配置,首先我们查阅其寄存器的物理起始地址:
![](https://cdn.xyxsw.site/CZ3cbiEhsoWDgJxhwXIcpUkAnMg.png)
![](https://cdn.xyxsw.site/HTFUbsQCNouQVzx0QYiciQWOnZf.png)
可见 GPIO 外设通过 APB2 总线进行地址定位与传输数据的,所以我们要控制 PB5 的话首先需要定位到对应的地址:
```c
#define PERIPH_BASE ((uint32_t)0x40000000) //外设基址
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2 基址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define AFIO_BASE (APB2PERIPH_BASE + 0x0000)
#define EXTI_BASE (APB2PERIPH_BASE + 0x0400)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)//GPIOB 基址,计算可得 0x40010C00
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
//APB2 还有相关定时器的基址,这里就不再展示
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
```
根据如上提供的 CRL、ODR 寄存器的功能映射,要使得 PB5 推挽输出且初始电平输出为高,则需要:
```c
void LED_Init(void)
{
RCC->APB2ENR|=1<<3; //GPIOB 的时钟使能,只有使能对应的时钟后 GPIO 才能正常工作
GPIOB->CRL&=0XFF0FFFFF; //由图可知CRL 的第 20-23 位控制 5 口,此举是对第 20-23 位清零
GPIOB->CRL|=0X00300000; //此举是对第 20-23 位赋值 0011根据寄存器功能可知此代表 50Mhz 推挽输出
GPIOB->ODR|=1<<5; //设置 ODR 第 5 位为 1输出高电平
}
```
其中 GPIOB 的结构体如下所示:
```c
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
```
所以由以上提到的例子而言C 语言可以从如下三方面进行与寄存器之间的控制:
1. 寄存器的地址可以使用**指针变量**来访问。
2. C 语言中的**结构体可以用于表示寄存器映射**。
3. C 语言中的**位域可以用于表示寄存器中的位**。
而且 C 语言中的内联汇编可以用于直接访问寄存器。在某些情况下,如果我们需要直接访问寄存器来完成复杂的硬件控制操作,则可以使用汇编语言指令来直接访问寄存器,从而实现复杂的硬件控制操作。常见的如,堆栈大小的设置等。
```asm
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
```