学习STM32单片机编程入门指南
文章目录
STM32/51单片机编程入门
(一)使用C51实现“HELLO”的数码管
1.安装protues
具体方法:Proteus软件的安装与使用方法(超详细) – 知乎 (zhihu.com)
2.安装Keil
Keil5和Keil MDK的区别:Keil5可以进行C51的编程,MDK可以进行stm32的编程,二者可以融合,具体方法见:使keil5同时支持STM32与C51_keil5可以用于stm32吗_快乐的别走的博客-CSDN博客
3.使用C51程序设计和仿真,实现数码管输出“HELLO”
3.1数码管
数码管分为共阴级和共阳极,其中共阳极给低电平有效,共阴级为高电平有效。
在protues中的区分方式:单独引脚在下边为阴极,单独引脚在上边为阳极。
3.2仿真图:
该数码管为动态显示,由于人体的视觉暂留现象,Delay(10)人体无法察觉图像的变化,所以看上去是静态的,若Delay(500)则可以清楚看到数码管的变化。
3.3代码:
8段位选码:0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
“HELLO”段选码:0x76,0x79,0x38,0x38,0x3F
#include <REGX51.H>
unsigned char str[]={0x76,0x79,0x38,0x38,0x3F};
unsigned char wei[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
void delay(unsigned int n)
{
unsigned int j,i;
for(i=0;i<n;i++)
{
for(j=0;j<120;j++);
}
}
void main()
{
unsigned int i;
while(1){
for(i=0;i<5;i++)
{
P3=~wei[i];
P2=str[i];
delay(10);
}
}
}
3.4仿真改进
引入74LS138译码器可以减少IO口的使用
#include <REGX51.H>
unsigned char str[]={0x76,0x79,0x38,0x38,0x3F};
unsigned char wei[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
void delay(unsigned int n)
{
unsigned int j,i;
for(i=0;i<n;i++)
{
for(j=0;j<120;j++);
}
}
void main()
{
unsigned int i;
while(1){
for(i=0;i<5;i++)
{
P3=wei[i];
P2=str[i];
delay(10);
}
}
}
(二)通过寄存器方式点亮LED灯
1.新建工程
在菜单栏点击Project中的New uVision Project,在想要的存储地址新建文件夹名为“点亮LED”
进入该文件夹将项目命名为project并保存,之后选择我们的单片机–STM32F103RB(在我们的STM32最小板的芯片上写了相应的型号),之后点击OK,
勾选CMSIS下的COFE和Device 下的Startup
在菜单栏点击File–New,将其保存命名为mian.c(注意:这里命名的名字后面一定要加“.c”)
在右边的工具栏中找到Source Goupe 1,右击,点击Add Existing Files to Group ‘Source Goupe 1’
在出现的窗口中选择我们刚刚新建的.c文件,并Add
如下图为添加成功
输入以下代码:
//用来存放STM寄存器映射
#define PERIPH_BASE ((unsigned int)0x40000000)//AHB
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
//GPIOA_BASE=0x40000000+0x10000+0x0800=0x40010800,该地址为GPIOA的基地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
//GPIOB_BASE=0x40000000+0x10000+0x0C00=0x40010C00,该地址为GPIOB的基地址
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
//GPIOC_BASE=0x40000000+0x10000+0x1000=0x40011000,该地址为GPIOC的基地址
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
//GPIOD_BASE=0x40000000+0x10000+0x1400=0x40011400,该地址为GPIOD的基地址
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
//GPIOE_BASE=0x40000000+0x10000+0x0800=0x40011800,该地址为GPIOE的基地址
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
//GPIOF_BASE=0x40000000+0x10000+0x0800=0x40011C00,该地址为GPIOF的基地址
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
//GPIOG_BASE=0x40000000+0x10000+0x0800=0x40012000,该地址为GPIOG的基地址
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define LED0 MEM_ADDR(BITBAND(GPIOA_ODR_Addr,8))
//#define LED0 *((volatile unsigned long *)(0x422101a0)) //PA8
//定义typedef类型别名
typedef struct
{
volatile unsigned int CR;
volatile unsigned int CFGR;
volatile unsigned int CIR;
volatile unsigned int APB2RSTR;
volatile unsigned int APB1RSTR;
volatile unsigned int AHBENR;
volatile unsigned int APB2ENR;
volatile unsigned int APB1ENR;
volatile unsigned int BDCR;
volatile unsigned int CSR;
} RCC_TypeDef;
#define RCC ((RCC_TypeDef *)0x40021000)
//定义typedef类型别名
typedef struct
{
volatile unsigned int CRL;
volatile unsigned int CRH;
volatile unsigned int IDR;
volatile unsigned int ODR;
volatile unsigned int BSRR;
volatile unsigned int BRR;
volatile unsigned int LCKR;
} GPIO_TypeDef;
//GPIOA指向地址GPIOA_BASE,GPIOA_BASE地址存放的数据类型为GPIO_TypeDef
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
void LEDInit( void )
{
RCC->APB2ENR|=1<<2; //GPIOA ????
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;
}
//延时
void Delay_ms( volatile unsigned int t)
{
unsigned int i,n;
for (n=0;n<t;n++)
for (i=0;i<800;i++);
}
int main(void)
{
LEDInit();
while (1)
{
LED0=0;//LED灭
Delay_ms(500);//延时
LED0=1;//LED亮
Delay_ms(500);//延时
}
}
项目没有错误:
2.LED闪烁
2.1下载ST-LINK资料包
http://openedv.com/posts/list/0/62552.htm
在资料包里,点击dpinst_amd64.exe即可下载
2.2硬件连接
点击Keil工具箱的魔术棒,点击Debug里的ST-Link Debuggeer
点击旁边的Settings,勾选ST-LINK/V2和SW
之后点击确定,进行编译,编译成功后点击LOAD即可看到LED闪烁
LED等闪烁:
(三)STM32系列芯片的原理
1.STM32系列芯片的地址映射和寄存器映射原理
地址映射原理:为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这一过程称为地址映射。
存储器映射:下表列出了STM32F10xxx中内置外设的起始地址,如下表:
存储器和总线架构:
Cortex™-M3存储器映像包括两个位段(bit-band)区。这两个位段区将别名存储器区中的每个字
映射到位段存储器区的一个位,在别名存储区写入一个字具有对位段区的目标位执行读-改-写操
作的相同效果。
在STM32F10xxx里,外设寄存器和SRAM都被映射到一个位段区里,这允许执行单一的位段的
写和读操作。
下面的映射公式给出了别名区中的每个字是如何对应位带区的相应位的:
bit_word_addr = bit_band_base + (byte_offset×32) + (bit_number×4)
其中:
bit_word_addr是别名存储器区中字的地址,它映射到某个目标位。
bit_band_base是别名区的起始地址。
byte_offset是包含目标位的字节在位段里的序号
bit_number是目标位所在位置(0-31)
2.嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器—>对应相关管脚)的操作有哪些相同与差别?
相同:
(1)存储位置:内存中的变量和外部设备都需要使用特定的存储位置。变量在内存中有自己的地址,而外部设备通常通过特定的寄存器与处理器或控制器连接,并且每个寄存器都有一个地址。
(2)数据传输:在代码中,变量的修改或外部设备的操作都需要进行数据传输。无论是将值存储到变量中还是将数据发送到外部设备的寄存器中,都需要进行数据传输。
不同:
(1)访问权限:对于内存中的变量,C程序可以直接读取和修改其值,除非变量被声明为常量或指针限定符限制了访问权限。而对于外部设备,其寄存器的访问权限可能受到硬件保护机制的限制,需要通过特定的接口和指令来访问。
(2)时间延迟:对内存中的变量的修改是即时的,读取和写入的延迟很小,因为内存通常是与处理器直接连接的。而对外部设备的操作通常涉及与设备之间的通信,可能需要等待设备响应,因此有时会出现较大的时间延迟。
(3)编程接口:对于内存中的变量,可以通过直接访问变量的地址来读取和修改其值。而对于外部设备,通常需要使用特定的接口和寄存器操作来与设备进行通信和控制。这些操作可能需要使用特定的寄存器配置、位操作或特殊指令来实现正确的设备控制。
总之,尽管在代码层面上,对内存中的变量和外部设备的操作可能存在一些相似之处,但在底层实现和使用接口上存在明显的差异。合理理解和处理这些差异对于编写嵌入式系统中的操作是很重要的。
3.为什么51单片机的LED点灯变成要比STM32的简单?
(1)编译语言:
因为STM32比51的外设、时钟等高出许多,51单片机结构相对简单,所以通常多使用汇编语言和C语言编程,而STM32系列的开发工作,不会采用汇编语言,因为工程量巨大,寄存器太多,位数也多
(2)编程方式:
51单片机只需要配置寄存器打开就可以编程,而STM32系列单片机需要先打开对应的时钟,包括开启后打开外部时钟才开始工作
(3)资源不同:
STM32的内部资源(寄存器和外设功能)较普通的51单片机都要多,所以在编程上有更多的选择
(四)C程序中变量修饰符的作用
1.与PC平台上的一般程序不同,嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用,并用C代码示例进行说明。
在嵌入式C程序中,经常会看到register和volatile这两个变量修饰符,它们用于对变量的行为和存储进行修饰。
(1)register关键字:
下面是一个示例:
#include <stdio.h>;
int main() {
register int count = 0;
while (count < 10) {
printf("Count: %d\n", count);
count++;
}
return 0;
}
上面的示例中,count变量被声明为register类型,这样编译器会将其存储在寄存器中,以提高循环中的访问效率。
(2)volatile关键字:
下面是一个示例:
#include <stdio.h>
int main() {
volatile int sensorValue = 0;
while (1) {
// 从传感器读取新值
sensorValue = readSensor();
// 将传感器值打印到控制台
printf("Sensor Value: %d\n", sensorValue);
}
return 0;
}
上面的示例中,sensorValue变量被声明为volatile类型,这样就确保了每次循环中都会从传感器读取最新的值,并将其打印到控制台。由于传感器值的变化不受程序控制,编译器会避免对该变量进行优化,以确保每次循环都能获取最新的传感器值。
需要注意的是,在使用register和volatile关键字时,应根据具体的嵌入式系统和编译器对其支持和行为进行调查,在适当的地方使用它们,以确保代码的正确性和性能优化。
作者:fw拜拜