# 第一个程序
与 hello world 类似,嵌入式第一个程序为点亮 LED, 在 STM 32MP157 上点亮 LED.
# 如何点亮一个 LED
- 看原理图,找到相关引脚
- 看主芯片的数据手册,如何配置控制引脚
- 写程序
# 看原理图
对于 百问网 的这块板子来说,LED 部分的原理图如下
以 LED2 为例子,从图中可以看出 这个 LED 正极接入了 3.3V, 负极接到了 主控的 PA10 上.
由初中物理可知,电路的导通是因为电子的定向移动,电子的定向移动是因为存在电势差
所以要想点亮 LED, 就要形成电势差,而 LED 的正极接入的 3.3v 的电源,所以只能通过 改变 PA10 输出的电压来形成电势差
显然,PA10 输出高电平时 电势差为 0, LED 熄灭;PA10 输出低电平时 电势差为 3.3v, LED 点亮
# 驱动能力不足的情况
对于 MP1 来说,IO 能输出 3.3v 的电压所以采用当前的电路
有有些 驱动能力较弱的 主控 可能只能输出 1.2v 那么他们的原理图电路可能如下
当 电路设计如上时,
GPIO 输出 1.2v 时,三极管导通,3.3v 和 GND 形成通路 LED 被点亮
GPIO 输出 0v 时,三极管截至,无法形成通路,LED 熄灭
也有可能如下设计
当 电路设计如上时,
GPIO 输出 1.2v 时,第一个三极管导通,三极管集电极处电压几乎为 0, 第二个三极管不导通,LED 熄灭
GPIO 输出 0 v 时,第一个三极管截至,集电极处存在电压,第二个三极管导通,LED 点亮
PS: 其实无论,输出的电压是多少,如果外围电路没有错误,对我们编程来说,只有 0 或者 1, 输出的只有高低电平
# 操作相关引脚 (逻辑性推导)
GPIO: General-purpose input/output
回顾上面,需要 GPIO 输出电平,我们就需要操作 GPIO, 可是 GPIO 输出高电平有很多可能
例如 通用输入输出,ADC, DAC, UART .... 这就涉及到了 GPIO 的多路复用,GPIO_MUX 多路选择器部分,通过这个选择器来选择 GPIO 的工作模式
这一部分比 STM32 上 体现为 GPIO 的模式选择,NXP 的 RT 系列的单片机就有一个专门的用于控制 IOMUXC 寄存器,用于选择 复用
所有 ARM 架构的芯片为了省电 默认所有的 模块都是 被关闭 的需要手动打开,GPIO 也是如此,在使用之前需要 ENABLE 相应的时钟
咱们 控制 LED 亮灭就是简单的 GPIO 基本输入输出,不用复用成其他模块
GPIO 有 I 也有 O, 就会存在一个方向控制器
上面分析的就是 LED 操作就是 GPIO 电平输出,所以 咱需要 把 GPIO 的方向配置成输出
刚刚分析 MP1 的 LED 电路 为低电平点亮,所以将 输出寄存器 配置为高电平
PS: 操作寄存器时,不要影响其他位,先读出寄存器原本的值,再写入寄存器中.(大部分芯片都采用这种协议 (set and clear protocol))
小结一下:
- 有多组 GPIO, 每组有多个 GPIO
- 使能:电源 / 时钟
- 模式 (Mode): 引脚可用于 GPIO 或其他功能
- 方向:引脚 Mode 设置为 GPIO 时,可以继续设置它是输出引脚,还是输入引脚
- 数值:对于输出引脚,可以设置寄存器让它输出高、低电平
对于输入引脚,可以读取寄存器得到引脚的当前电平
# STM32MP157 操作 点亮 LED 的寄存器
# 时钟配置
上面咱们说过 点亮 LED 需要,先使能 对应的 电源总线进行使能,
这一部分就需要查看数据手册对应的寄存器操作,这里涉及到 RCC 模块,RCC 是管理复位 和 时钟控制的模块
在手册的 10.2 章节中找到看见一张 RCC 模块的功能框图,这里直观的表示出了 RCC 个中功能
在图的下面,可以看见 PLLx 的功能,咱们的要使用的 PLL4 右侧很明显有 per_clk
, GPIO 是一种最基本的外设,所以咱们找对 PLL 了
让咱们继续向下找一找,在数据手册的 522 页,RCC 的 10.4 章节,功能描述部分,有着这么一段话
The RCC provides up to 4 PLLs, each of them can be configured with integer or fractional
ratios.
• The PLL1 is dedicated to the MPU clocking
• The PLL2 provides:
– The clocks for the AXI-SS (including APB4, APB5, AHB5 and AHB6 bridges)
– The clocks for the DDR interface
– The clocks for the GPU
• The PLL3 provides:
– The clocks for the MCU, and its bus matrix (including the APB1, APB2, APB3,
AHB1, AHB2, AHB3 and AHB4)
– The kernel clocks for peripherals
• The PLL4 is dedicated to the generation of the kernel clocks for various peripherals
可以看到,原文中表示 PLL4 是专门用于 生成各种外设时钟的内核时钟,还用了 dedicated to
来表示专门,说明肯定没找错 PLL
再往下也能看到更详细的关于 RCC 模块的框图
这时候就该去找 RCC 的寄存器啦,当然和 PLL4 相关的手册肯定会有很多,这时候当然是找 类似于 CR 寄存器,而且大概率是 RCC_CR
至于为什么,CR 是 Control Register
的缩写,而 PLL4 归属于 RCC 那么一定要先在 RCC 中使能,使能是一种中控制操作,那必然就是 CR 寄存器啦
很显然,猜错名字了,差了个 PLL4, 不过大差不差.
从下面的文字中可以看出 0 bit 位是使能位,只要把这一位置 1 即可使能 PLL4
由寄存器表可知 RCC_PLL4CR, 为控制 外设的时钟线,使能只需要拉高第一位,在不影响其他位的情况下
咱们可以这么写 RCC_PLL4CR |= (1 << 0)
PLL4 的使能并不是瞬间的,所以在操作之前需要等待,PLL4 准备好,即等待 RCC_PLL4CR 的 PLL4REDY
位复位,才算启动成功
这里咱可以这样写 RCC_PLL4CR & (1 << 1)
来检测 PLL4 是否使能
对于 ARM 架构的芯片来说,大部分外设都是关闭的,所以要配置一个外设之前必须要做的事情是,使能对应的外设时钟
一般来说,外设时钟的使能都是 AxBx_ENSETR
, 但是这里有一个很大的问题,如何确定这些外设到底归属于哪些总线管理?
其实在文档中有一章节叫做 Memory and bus architecture
这里介绍了内存分布和总线架构
在 2.1.2 Memory Map Organization 中有一节 Peripherals clusters 的内容,第一段就涉及到了 GPIO 外设
AHB4/APB3
This cluster groups important system functions as clock and power control, system
configuration, most GPIOs control, plus some additional timers and low-speed
communication interfaces
这里大致意思就是 AHB3 和 AHB4, 管理系统配置、电源、时钟和 GPIO 以及一部分定时器和低速通信接口
那这就很明确了,直接去寻找 AHB3 和 AHB4 相关的寄存器,直接查找会好一些
这里可以需要注意一下因为用的 A7 内核,所以要找 Enable for MPU 的
# gpio 配置
和一般的 A 系列 或 M 系列 的 ARM 不同,157 的引脚被 和功能被不同的模块复用,有些 M4 专用,有些 A7 专用,还有些二者复用
但是不管是 A 系列还是 M 系列,引脚配置的思路应该大差不差,具体的配置方式参考 文档的第 13 章的 Table 81, 以及 13.3.10 小结
这部分对 GPIO 的配置有专门的讲解
这里我就直接通过寄存器表来查看相关配置啦
首先配置 GPIO 的工作模式,文章开始说过,现在的 GPIO 都是身兼数职,所以需要先指定它工作在哪个职责上
这个就涉及 GPIO_MODER 寄存器
从图中可以看到 GPIO 输出模式需要将对应的位配置为 01
伪代码可以这样实现 GPIOA_MODER &= ~(3 << 20); GPIOA_MODER |= (1 << 20)
;
接下去就先不配置上下拉,输出速度之类的,只先看一下输出类型默认方式,是推挽 还是开漏的模式了 (因为默认推挽)
直接快进到 输出
从图中可知,要想输出对应电平只需要,将电平写入对应位即可,即 GPIOA_ODR |= (1 << 10); GPIOA_ODR &= (1 << 10);
到此,整个逻辑层就捋出来了
至于代码实现就就按着这个逻辑即可,这里贴一份我的代码
# 代码
#define USE_A7 | |
static void delay(volatile long ntime) | |
{ | |
while (ntime--) | |
{ | |
; | |
} | |
} | |
int main(void) | |
{ | |
volatile unsigned int *p_reg; | |
/* 使能 PLL4 */ | |
p_reg = (volatile unsigned int *)(0x50000000 + 0x894); | |
*p_reg |= (1 << 0); | |
// 等待时钟稳定 | |
while (!(*p_reg & (1 << 1))) | |
{ | |
; | |
} | |
/* 使能 GPIOA */ | |
#ifdef USE_A7 | |
p_reg = (volatile unsigned int *)(0x50000000 + 0xA28); | |
*p_reg |= (1 << 0); | |
#endif | |
#ifdef USE_M4 | |
p_reg = (volatile unsigned int *)(0x50000000 + 0xAA8); | |
*p_reg |= (1 << 0); | |
#endif | |
/* 设置 GPIO10 为输出模式 */ | |
p_reg = (volatile unsigned int *)(0x50002000 + 0x00); | |
*p_reg &= ~(3 << 20); | |
*p_reg |= (1 << 20); | |
// 指向 (GPIOA_ODR) | |
p_reg = (volatile unsigned int *)(0x50002000 + 0x14); | |
/* 执行任务 */ | |
while (1) | |
{ | |
/* 设置 LED 电平变化 */ | |
*p_reg |= (1 << 10); | |
delay(1000000); | |
*p_reg &= ~(1 << 10); | |
delay(1000000); | |
} | |
return 0; | |
} |
启动文件
.text
.global _start
_start:
/* 设置sp */
ldr sp, =0xc0000000 + 0x100000
/* 调用main函数 */
bl main
makefile
PREFIX=arm-linux-gnueabihf- | |
CC=$(PREFIX)gcc | |
LD=$(PREFIX)ld | |
AR=$(PREFIX)ar | |
OBJCOPY=$(PREFIX)objcopy | |
OBJDUMP=$(PREFIX)objdump | |
led.img : start.S main.c | |
$(CC) -nostdlib -g -c -o start.o start.S | |
$(CC) -nostdlib -g -c -o main.o main.c | |
$(LD) -T stm32mp157.lds -g start.o main.o -o led.elf | |
$(OBJCOPY) -O binary -S led.elf led.bin | |
$(OBJDUMP) -D -m arm led.elf > led.dis | |
mkimage -A arm -T firmware -C none -O u-boot -a 0xC0100000 -e 0 -d led.bin led.img | |
mkimage -T stm32image -a 0xC0100000 -e 0xC0100000 -d led.bin led.stm32 | |
clean: | |
rm -f led.dis led.bin led.elf led.img *.o *.log |
# 其它注意
烧录的话目前我没有研究明白,暂时跟着韦东山老师的课程来,走 USB 下载,烧入到 RAM 中
要注意的是,需要修改一下 led.stm32
的地址
# 参考文献
[1] Linux 完全开发手册。韦东山
[2] STM32MP157 数据手册
[3] https://www.100ask.net/p/t_pc/course_pc_detail/column/p_5f85791ee4b06aff1a03d614?content_app_id=
大道五十,天衍四十九,人遁其一!