一、STM32F103系列芯片的存储器映射和寄存器映射原理
1.存储器映射 存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址 的过程就称为存储器映射,具体见下图。如果给存储器再分配一个地址就叫存储器重映射。
存储器区域功能划分
在这 4GB 的地址空间中,ARM 已经粗线条的平均分成了 8 个块,每块 512MB,每个 块也都规定了用途,具体分类见表格 6-1。每个块的大小都有 512MB,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的 一部分而已。
存储器功能分类 2.寄存器映射 我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫 寄存器映射?寄存器到底是什么?
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit, 每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到 每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通 过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的 不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个 给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
比如,我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C(至于这 个地址如何找到可以先跳过,后面我们会有详细的讲解),ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指 针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平。
二、 用C语言寄存器实现流水灯
接线
main.c
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
#define GPIOA_CRH *((unsigned volatile int*)0x40010804)
#define GPIOA_ORD *((unsigned volatile int*)0x4001080C)
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ORD *((unsigned volatile int*)0x40010C0C)
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ORD *((unsigned volatile int*)0x4001100C)
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
int main()
{
int j=100;
RCC_AP2ENR|=1<<2;
RCC_AP2ENR|=1<<3;
RCC_AP2ENR|=1<<4;
GPIOA_CRH&=0xFFF0FFFF;
GPIOA_CRH|=0x00020000;
GPIOA_ORD|=1<<12;
GPIOB_CRL&=0xFFFFFF0F;
GPIOB_CRL|=0x00000020;
GPIOB_ORD|=1<<1;
GPIOC_CRH&=0xF0FFFFFF;
GPIOC_CRH|=0x02000000;
GPIOC_ORD|=1<<14;
while(j)
{
GPIOA_ORD=0x1<<12;
Delay_ms(3000000);
GPIOA_ORD=0x0<<12;
Delay_ms(3000000);
GPIOB_ORD=0x1<<1;
Delay_ms(3000000);
GPIOB_ORD=0x0<<1;
Delay_ms(3000000);
GPIOC_ORD=0x1<<14;
Delay_ms(3000000);
GPIOC_ORD=0x0<<14;
Delay_ms(3000000);
}
}
汇编代码
RCC_APB2ENR EQU 0x40021018
GPIOA_CRH EQU 0x40010804
GPIOA_ODR EQU 0x4001080C
GPIOB_CRL EQU 0x40010C00 ;寄存器映射
GPIOB_ODR EQU 0x40010C0C
GPIOC_CRH EQU 0x40011004
GPIOC_ODR EQU 0x4001100C
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
;NOINIT: = NO Init,不初始化。READWRITE : 可读,可写。ALIGN =3 : 2^3 对齐,即8字节对齐。
Stack_Mem SPACE Stack_Size
__initial_sp
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp
DCD Reset_Handler
AREA |.text|, CODE, READONLY
THUMB
REQUIRE8
PRESERVE8
ENTRY
Reset_Handler
MainLoop BL LED2_Init
BL LED2_ON
BL Delay ;LED2灯闪烁
BL LED2_OFF
BL Delay
BL LED1_Init
BL LED1_ON
BL Delay ;LED1灯闪烁
BL LED1_OFF
BL Delay
BL LED3_Init
BL LED3_ON
BL Delay ;LED3灯闪烁
BL LED3_OFF
BL Delay
B MainLoop
LED1_Init
PUSH {R0,R1, LR} ;R0,R1,LR中的值放入堆栈
LDR R0,=RCC_APB2ENR ;LDR是把地址装载到寄存器中(比如R0)。
ORR R0,R0,#0x08 ;开启端口GPIOB的时钟,ORR 按位或操作,01000将R0的第二位置1,其他位不变
LDR R1,=RCC_APB2ENR
STR R0,[R1] ;STR是把值存储到寄存器所指的地址中,将r0里存储的值给rcc寄存器
;上面一部分汇编代码是控制时钟的
LDR R0,=GPIOB_CRL
ORR R0,R0,#0X00000020 ;GPIOB_Pin_1配置为通用推挽输出;开启的是pb1,所以是2,为0010,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOB_CRL
STR R0,[R1]
LDR R0,=GPIOB_ODR
BIC R0,R0,#0X00000002 ;BIC 先把立即数取反,再按位与
LDR R1,=GPIOB_ODR ;GPIO_Pin_1输出为0;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
POP {R0,R1,PC} ;将栈中之前存的R0,R1,LR的值返还给R0,R1,PC
LED1_OFF
PUSH {R0,R1, LR}
LDR R0,=GPIOB_ODR
BIC R0,R0,#0X00000002 ;因为是pb1所以对应二进制0010;GPIO_Pin_1输出为0,LED1熄灭
LDR R1,=GPIOB_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED1_ON
PUSH {R0,R1, LR}
LDR R0,=GPIOB_ODR
ORR R0,R0,#0X00000002 ;GPIO_Pin_1输出为1,LED1亮
LDR R1,=GPIOB_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED2_Init
PUSH {R0,R1, LR};R0,R1,LR中的值放入堆栈
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x04 ;打开GPIOA的时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOA_CRH
ORR R0,R0,#0X00020000 ;GPIOA_Pin_12配置为通用推挽输出
LDR R1,=GPIOA_CRH
STR R0,[R1]
LDR R0,=GPIOA_ODR
BIC R0,R0,#0X00001000
LDR R1,=GPIOA_ODR ;GPIOA_Pin_12输出为0
STR R0,[R1]
POP {R0,R1,PC}
LED2_OFF
PUSH {R0,R1, LR}
LDR R0,=GPIOA_ODR
BIC R0,R0,#0X00001000 ;GPIOA_Pin_12输出为0,LED2熄灭
LDR R1,=GPIOA_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED2_ON
PUSH {R0,R1, LR}
LDR R0,=GPIOA_ODR
ORR R0,R0,#0X00001000 ;GPIOA_Pin_12输出为1,LED2亮
LDR R1,=GPIOA_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED3_Init
PUSH {R0,R1, LR}
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x10 ;打开GPIOC的时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOC_CRH
ORR R0,R0,#0X02000000 ;GPIOC_Pin_14配置为通用推挽输出
LDR R1,=GPIOC_CRH
STR R0,[R1]
LDR R0,=GPIOC_ODR
BIC R0,R0,#0X00004000 ;GPIOC_Pin_14输出为0
LDR R1,=GPIOC_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED3_OFF
PUSH {R0,R1, LR}
LDR R0,=GPIOC_ODR
BIC R0,R0,#0X00004000 ;GPIOC_Pin_14输出为0,LED3熄灭
LDR R1,=GPIOC_ODR
STR R0,[R1]
POP {R0,R1,PC}
LED3_ON
PUSH {R0,R1, LR}
LDR R0,=GPIOC_ODR
ORR R0,R0,#0X00004000 ;GPIOC_Pin_14输出为1,LED3亮
LDR R1,=GPIOC_ODR
STR R0,[R1]
POP {R0,R1,PC}
Delay
PUSH {R0,R1, LR}
MOVS R0,#0
MOVS R1,#0
MOVS R2,#0
DelayLoop0
ADDS R0,R0,#1
CMP R0,#300
BCC DelayLoop0
MOVS R0,#0
ADDS R1,R1,#1
CMP R1,#300
BCC DelayLoop0
MOVS R0,#0
MOVS R1,#0
ADDS R2,R2,#1
CMP R2,#15
BCC DelayLoop0
POP {R0,R1,PC}
END
编译生成hex文件 结果
三、总结
通过查阅资料与请教,慢慢的理解了stm32的工作原理,以及串行口的转换关系,基本掌握了软硬件结合的实验。
四、参考文献
https://blog.csdn.net/geek_monkey/article/details/86291377 https://blog.csdn.net/geek_monkey/article/details/86293880 https://blog.csdn.net/m0_51120713/article/details/120832645?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163488412516780357238656%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=163488412516780357238656&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-120832645.pc_search_ecpm_flag&utm_term=STM32F103%E5%AF%84%E5%AD%98%E5%99%A8%E6%96%B9%E5%BC%8F%E7%82%B9%E4%BA%AELED%E6%B5%81%E6%B0%B4%E7%81%AF&spm=1018.2226.3001.4187
|