一、STM32F103系列芯片的地址映射和寄存器映射
STM32寄存器为32位,恰好又有32根地址总线,可访问2^32(4G)的空间,ARM把存储空间分成了8个512MB大小的块。8个区域中,每一块区域都有它特殊的用途,如block0主要用于设计芯片内的Flash(闪存),Flash掉电不遗失数据,所以经常作为单片机的程序储存器,我们编写的程序就存储在这里面,block1主要用于设计芯片内部的SRAM,block2用于设计片内外设。 地址映射是指可以将寄存器、IO与地址建立一对一关系。 为了操作寄存器就要给存储器分配地址,这就是存储器映射。 开发板的CPU地址引脚并不是所有的都与内存元器件相连的,如果该板上有外设(如一块独立显卡),那么CPU就需要分出一些引脚来与该外设的地址引脚相连,相当于将一部分内存寻址的空间分给了外设。从 AHB 总线延伸出来的两条 APB2 和 APB1 总线,上面挂载着 STM32 各种各样的特色外设。我们经常说的 GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习 STM32的重点,就是要学会编程这些外设去驱动外部的各种设备。在XX外设的地址范围内,分布着的就是该外设的寄存器。GPIO有很多寄存器,每个都有特定的功能。每个寄存器为32位,占4个字节,在该外设的基地址上按照顺序排序,寄存器的位置都以相对该外设基地址的偏移地址来描述。我们以4个bit为一个单元,每个单元都有其功能,当我们控制这些单元的时候也就是在控制这些外设,由于stm32的内部存储空间大,外设繁多,每次操控一个外设时就要写一大串对应的储存单元地址。 存器与地址映射关系定义都统一写在stm32f4xx.h文件正是因为头文件中有了对于各种 寄存器 和 I/O端口 的地址映射,我们才可以在单片机程序中方便地使用P2^0 =0xFF; TMOD =0xFF等赋值句子对寄存器进行配置,从而控制单片机。 更具体地,我们展示下列c程序:
typedef struct
{
IO unit32_t CRL;
IO unit32_t CRH;
IO unit32_t ODR;
IO unit32_t IDR;
IO unit32_t BSRR;
IO unit32_t BRR;
IO unit32_t LCKR;
}GPIO_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRH (*(unsigned int *)0x40010c04)
#define GPIOB_ODR (*(unsigned int *)0x40010c0c)
GPIOA->ODR=0x00000000
GPIO_TypeDef * GPIOx;
GPIOx = GPIOA;
GPIOx->CRL = 0xffffffff;
二、实现流水灯的方法一 目标:用STM32上的GPIOA-5、GPIOB-9、GPIOC-14 这3个引脚上控制LED灯(最高时钟2Mhz),就假设他们分别是R灯、B灯、G灯吧,轮流闪烁,间隔时长1秒。通过配置输出控制引脚高低电平就可以控制灯的亮灭。GPIOx端口的各寄存器地址和详细参数。用C语言 寄存器方式编程实现。 开启时钟,配置GPIO引脚工作模式,控制电平。 时钟控制名字叫做RCC,属于AHB总线。GPIOB属于APB2。由于STM32默认关闭了时钟我们需要使时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。用的端口的复位和时间控制受RCC控制,所以要先配置时钟使能。 GPIO端口A 0x4001 0800 - 0x4001 0BFF GPIO端口B 0X4001 0C00 - 0x4001 0FFF GPIO端口C 0x4001 1000 - 0x4001 13FF 我们先查阅参考手册 手册下载地址 看手册RCC_APB2ENR,位2位3位4是IOPBEN—IO端口A端口B端口C时钟使能,就是我们想要的。把RCC_APB2ENR的位2位3位4赋值为1,就是开启GPIOA、GPIOB、GPIOC时钟。RCC地址0x4002 1000,偏移地址是0x18,所RCC_APB2ENR地址是0x4021018
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
RCC_AP2ENR|=1<<2|1<<3|1<<4;
然后将引脚配置,其实就是为端口配置寄存器。引脚分别为GPIOA-5、GPIOB-9、GPIOC-14(最高时钟2Mhz)
因为STM32中,用端口配置低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用端口配置高寄存器(GPIOx_CRH)来配置引脚Px8-Px15,所以我们用GPIOA_CRL,GPIOB_CRH,GPIOC_CRH。
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
GPIO引脚线路经过两个保护二极管后,向上流向“输入模式”结构,向下流向“输出模式”结构。先看输出模式部分, 线路经过一个由P-MOS和N-MOS管组成的单元电路。这个结构使GPIO具有了“推挽输出”和“开漏输出”两种模式。 复位值是0x4444 4444,并不是0x0000 0000。所谓的复位值,就是指如果没有操作这个寄存器时,寄存器存放的默认值。复位值按位拆分0x4 = 0b0100,0x表示16进制,0b表示二进制,也就是默认CNF 01,MODE 00,是浮空输入。 推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。开漏是需要外接上拉电阻才可以输出高电平的,这里并不适合。所以需要把寄存器设置为推挽输出。最大频率是2Mhz,为了配置MODE,我们需要把寄存器先清零。把推挽输出CNF1、CNF0设置位00,MODE1、MODE0设置为10 在单片机的编程中,要想做某件事,必须寻找相应的寄存器。在手册8.2.4小节,可以找到端口输出数据寄存器(GPIOx_ODR),就是我们需要的。我们需要输出0,地址的偏移是0x0C。 点亮LED需要输出低电平,地址的偏移是0x0C,所以这个数据寄存器的地址就是0x4001 0C0C,把第8位写为0就行。默认就是0,高电压赋值为1
基本配置代码如下:
GPIOA_CRL&=0XFF0FFFFF;
GPIOA_CRL|=0X00200000;
GPIOB_CRH&=0XFFFFFF0F;
GPIOB_CRH|=0X00000020;
GPIOC_CRH&=0XF0FFFFFF;
GPIOC_CRH|=0X02000000;
GPIOA_ODR|=1<<5;
GPIOA_ODR&=~(1<<5);
实验C代码
#include <stm32f10x.h>
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
void Delay_Time( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<400;i++);
}
int main()
{
int t=9000;
RCC_AP2ENR|=1<<2|1<<3|1<<4;
GPIOA_CRL&=0XFF0FFFFF;
GPIOA_CRL|=0X00200000;
GPIOB_CRH&=0XFFFFFF0F;
GPIOB_CRH|=0X00000020;
GPIOC_CRH&=0XF0FFFFFF;
GPIOC_CRH|=0X02000000;
GPIOA_ODR&=~(1<<5);
GPIOB_ODR&=~(1<<9);
GPIOC_ODR&=~(1<<14);
while(t)
{
GPIOA_ODR=1<<5;
Delay_Time(t);
GPIOA_ODR&=~(1<<5);
GPIOB_ODR=1<<9;
Delay_Time(t);
GPIOB_ODR&=~(1<<9);
GPIOC_ODR=1<<14;
Delay_Time(t);
GPIOC_ODR&=~(1<<14);
}
return 0;
}
三、实现流水灯的方法二 用cubemx完成初始化过程,采用HAL库编程实现流水灯 选择芯片 配置引脚: 配置外设: 配置时钟: 配置项目管理: 配置生成的C代码
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_SET);
}
}
在Keil下用软件仿真运行上面代码,并用虚拟逻辑分析仪观察 对应管脚上的输出波形(高低电平转换),看是否是1秒的周期 我们需要配置下debug选项 在逻辑分析仪里面输入相应的引脚,例如GPIOA-5就是PORTA.5 点击运行后效果如下:
可以看到确实发生了周期为1的波形变化 在自己编写并配置环境的时候发现显示unknown signal,这是因为keil对某些芯片没有相应virtual registers,改下芯片就行了 开始配置的是stmf104c4芯片 在添加引脚时发现找不到查看virtual register里面发现根本就没有 把它改为c8芯片就可以了
烧录程序基本连接方式: 3.3v -3.3v GND -GND RXD -A9 TXD - A10 各个输出引脚练到小灯泡同一排 3.3v接小灯泡旁边正极 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e6ef18bb56234830b439ed7602f6e6aa.png#pic_center 最后附上运行效果:
|