STM32流水灯程序---2种方式

一、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;//结构体的成员的地址分配(RAM中)是连续而 STM32 的一个
 //模块的功能寄存器都是连续的,每个寄存器都是相当于 基地址加偏移
#define PERIPH_BASE     ((uint32_t)0x40000000)    
//外设基地址,因为stm32是32位的,宏展开为0x40000000并转化为 uint32_t    
//APB2基地址=外部总线基地址+偏移量;APB2总线对应外设区。
#define APB2PERIPH_BASE     (PERIPH_BASE + 0x10000)     
//GPIOA基地址=APB2基地址+偏移量
#define GPIOA_BASE     (APB2PERIPH_BASE + 0x0800)          
//GPIOA将地址顺序分配给7个32位寄存器(结构体分配)
#define GPIOA     ((GPIO_TypeDef*)GPIOA_BASE)  
//将寄存器地址映射到7个32位寄存器,分别控制
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRH (*(unsigned int *)0x40010c04)
#define GPIOB_ODR (*(unsigned int *)0x40010c0c)
//GPIOB寄存器地址

GPIOA->ODR=0x00000000     //为GPIOA的ODR寄存器地址赋值0x00000000
GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOA; //把指针地址设置为宏 GPIOA 地址
GPIOx->CRL = 0xffffffff; //通过指针访问并修改 GPIOA_CRL 寄存器
//控制PB3引脚电平是通过GPIOB基地址+端口输入寄存器的地址偏移



二、实现流水灯的方法一
目标:用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;

STM32流水灯程序---2种方式_第1张图片
然后将引脚配置,其实就是为端口配置寄存器。引脚分别为GPIOA-5、GPIOB-9、GPIOC-14(最高时钟2Mhz)
STM32流水灯程序---2种方式_第2张图片
STM32流水灯程序---2种方式_第3张图片
STM32流水灯程序---2种方式_第4张图片

因为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;//设置CNF和MODE
			GPIOB_CRH&=0XFFFFFF0F;
			GPIOB_CRH|=0X00000020;
        GPIOC_CRH&=0XF0FFFFFF;
			GPIOC_CRH|=0X02000000;  
			GPIOA_ODR|=1<<5;//设置高电平,亮灯
			GPIOA_ODR&=~(1<<5);//设置低电平,灭灯

实验C代码

#include  
#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库编程实现流水灯
选择芯片
STM32流水灯程序---2种方式_第5张图片
配置引脚:
STM32流水灯程序---2种方式_第6张图片
配置外设:
STM32流水灯程序---2种方式_第7张图片
配置时钟:
STM32流水灯程序---2种方式_第8张图片
配置项目管理:
STM32流水灯程序---2种方式_第9张图片
STM32流水灯程序---2种方式_第10张图片
配置生成的C代码

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
	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);
		
	

  }
  /* USER CODE END 3 */
}

在Keil下用软件仿真运行上面代码,并用虚拟逻辑分析仪观察 对应管脚上的输出波形(高低电平转换),看是否是1秒的周期
我们需要配置下debug选项
STM32流水灯程序---2种方式_第11张图片
在逻辑分析仪里面输入相应的引脚,例如GPIOA-5就是PORTA.5
STM32流水灯程序---2种方式_第12张图片
点击运行后效果如下:
STM32流水灯程序---2种方式_第13张图片

可以看到确实发生了周期为1的波形变化
在自己编写并配置环境的时候发现显示unknown signal,这是因为keil对某些芯片没有相应virtual registers,改下芯片就行了
开始配置的是stmf104c4芯片
STM32流水灯程序---2种方式_第14张图片
STM32流水灯程序---2种方式_第15张图片
在添加引脚时发现找不到查看virtual register里面发现根本就没有
STM32流水灯程序---2种方式_第16张图片
STM32流水灯程序---2种方式_第17张图片
把它改为c8芯片就可以了
STM32流水灯程序---2种方式_第18张图片
STM32流水灯程序---2种方式_第19张图片
STM32流水灯程序---2种方式_第20张图片
STM32流水灯程序---2种方式_第21张图片

烧录程序基本连接方式:
3.3v -3.3v
GND -GND
RXD -A9
TXD - A10
各个输出引脚练到小灯泡同一排
3.3v接小灯泡旁边正极
![在这里插入图片描述](https://img-blog.csdnimg.cn/e6ef18bb56234830b439ed7602f6e6aa.png#pic_center
最后附上运行效果:
STM32流水灯程序---2种方式_第22张图片

VID_20221010_211108

你可能感兴趣的:(stm32,单片机,arm)