找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 6|回复: 0
打印 上一主题 下一主题
收起左侧

从STC轻松转向STM32

[复制链接]
跳转到指定楼层
楼主
ID:266429 发表于 2026-8-6 21:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
对于单片机来说,不管你用的是什么语言什么方法来编程,它本质上其实就是操作单片机的寄存器,区别无非在于是直接操作还是间接操作。
用过STC的都知道,对于它的编程,不管是用汇编还是C,我们在编制时可以说基本上都是直接操作寄存器而极少用到库函数的,比如我们要置P2端口组的8个引脚全部为高电平以便点亮以共阴极法连接在其上的8LED灯,我们就会直接置P2端口寄存器的值为11111111B(即十六进制的FF)。
具体来说,如果我们用的是STC89系列的单片机,有下面三句就完成了:
#include "reg52.h"                //这个是头文件
void main()  
{
        P2=0xFF;  //直接操作P2端口寄存器,其值置为11111111B
                while(1) ;
}
如果我们用的是其它型号的STC芯片,这三句可能就不够用了,因为其每一个引脚在芯片内部的电路设计上为适应于不同的应用目 的而设计了几种不同功能和硬件模式的电路如推挽输出、准双向等等,我们在应用时,就要根据自己的需要选择其中某一种电路,这个选择的实现,是通过编程来操作的,具体如何选择如何实现,查手册啊。
所以,我们在使用端口之前,首先要查芯片手册中有关端口的内容,它会一条一条地告诉你怎么使用端口。同样的,如果我们要使用单片机的某个功能如果定时器,那就要查与定时器有关的所有寄存器的相关内容,弄明白它并按手册所述来配置这些寄存器。
对于熟练掌握了STC的人来说,以上内容有点啰嗦了,所以,下面,进入正题。
本人在学了一段时间的STC之后,就想用用STM32,于是看了很多资料,也买了几本书,然后,就感觉自己只能照抄别人的程序,而且还抄得不明所以,完全违背了自己只依靠芯片手册就能编程的目标,这个不能忍,于是,就想着单片机不都是一个原理么,直接操作寄存器不就得了,于是就试着按之使用STC的方法直接操作寄存进行编程。
还是从点亮LED灯入手,比如说,我们来点亮STM32GPIOB端口的所有LED灯。
抛开其它所有资料,直接查手册,《STM32中文参考手册》,既然是使用端口,那理所当然地先找到第8 部分通用和复用功能I/O(GPIO和AFIO) 8.1认真看,于是我们看到了STM32STC的一个很大的区别,端口硬件电路对于端口是用于输出高低电位还是读入端口的高低电位在功能上是作了两套电路的,你要使用哪种功能是必须先使用相应的配置寄存器进行选择的;再就是它与STC一样也对端口做了多种电路模式如上拉下拉之类的,只是它比STC的多一些。我们这里既然是点亮LED,那我们用到的就是输出功能中的推挽电路模式,编程时找到相关寄存器内容,据此进行编程就是了。这里说一句,8.1很重要,应该仔细看,当然,对于熟练掌握了STC的人来说,这部分内容很好懂。也当然,如果你不耐烦看的话,那这里只看有关输出部分的内容也行,至于输入部分的内容,以后需要用到时再看就是了。
再往下看就是8.2 GPIO寄存器描述,这部分就是描述如何操作使用端口的相关寄存器了,这部分就是我们编程的直接依据:
1. 端口配置低寄存器(GPIOx_CRL) (x=A..E)
内容写得很清楚,没什么写得含糊的地方,它是用于配置一组端口的低八位引脚的硬件电路功能及模式
的,每个寄存器有32位,每连续4位控制一个端口引脚,这4位的前两位确定引脚的电路模式,后两位用于确定引脚功能是用于输入还是输出。前面我们已经确定是推挽输出模式,那前两位就可以确定为00,后两位我们可选择11让引脚工作在50MHz频率下(当然你也可以选择01或10)。
2. 端口配置高寄存器(GPIOx_CRH) (x=A..E)
这个是配置一组端口的高八位引脚的,功能与用法同上。
3. 端口输入数据寄存器(GPIOx_IDR) (x=A..E)
这个是端口用作输入用途时才用到的,看一下就行了。
4. 端口输出数据寄存器(GPIOx_ODR) (x=A..E)
这个就是操作引脚电位时用到的寄存器了,它的值会直接传到相应的引脚,也就是说,我们只要直接改变这个寄存器的值,则与其相关的引脚的电位就会随之改变。
看到这里,按照我们使用STC的经验,似乎我们已经找到编程的方法了,不就是操作这三个寄存器么,那好,我们就去试试。
在着手编写程序之前,按照之前我们使用STC的经验,我们还要找到这三个寄存器的地址值,怎么找?还是查手册,第2部分存储器和总线构架:
2.1 系统构架,这部分应该能看得明白吧?如果你看不明白也没关系,溜一遍。
2.2 存储器组织,“程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内”,这句话很重要,它说明程序存储器、数据存储器、寄存器和输入输出端口的地址值的安排不同于STC,它们不存在地址值的重合,而是统一编排在同一个4GB的线性地址空间内。根据这个规则,我们在2.3 存储器映像的表1寄存器组起始地址表中就可以找到GPIO的B组(GPIOB)相关寄存器的地址值的范围值为0X40010C00 - 0x4001 0FFF,注意这张表的表头的一个写法是有点问题的,0X40010C00 - 0x4001 0FFF表述的是GPIOB端口所有寄存器的地址值的范围,其中的0X40010C00才可以称之为起始地址,GPIOB的CRL、CRH、ODR这三个寄存器的地址值就在这个0X40010C00 - 0x4001 0FFF范围之内,至于其具体是什么值,我们在之前看过的8.2 GPIO寄存器描述看到过一个说法“偏移地址”,这个偏移地址值就是相对于起始地址的偏移值,用这个值加上起始地址值就是这个寄存器的绝对地址值。
于是,我们得到这三个寄存器的绝对地址值如下:
GPIOB_CRL:0X40010C00
GPIOB_CRH:0X40010C04
GPIOB_ODR:0X40010C0C
好了,现在可以编程了,用的是keil,在Run-Time配置里面只勾选两项:CMSIS下的CORE、Device下的Startup。程序如下:
int  main(void)
{
        (*(volatile unsigned int *)0x40010C00)=0x33333333;
        (*(volatile unsigned int *)0x40010C04)=0x33333333;
        (*(volatile unsigned int *)0x40010C0C)=0xFFFF;
        while(1);
}       
顺便说一句,如果你想简单点,可以只操作低八位,于是(*(volatile unsigned int *)0x40010C04)=0x33333333这一句可以不要。
编译、下载,嗯?不亮!
好吧,将ODR的值改为0x0000,再编译下载,还是不亮。
好吧,LED电路改共阳极,再将0xFFFF和0x0000试一遍,怎么还是不亮?怎么跟STC不一样啊?STC怎么着都会有一种情况是可以点亮LED的不是?这种情况,明显引脚电路根本就没有供电嘛,难道板子是坏的?
不慌,再看看手册,我们前面看过2.1 系统构架这一部分,里面关于AHB/APB桥(APB)有一句话在每一次复位以后,所有除SRAM和 FLITF以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR来打开该外设的时钟”,我们知道所有端口都属于外设,于是我们知道了要想应用GPIOB,我们就得用寄存器RCC_AHBENR来“打开该外设的时钟”。
接下来我们就来在手册中找寄存器RCC_AHBENR的相关内容,既然说到是RCC,那么相关内容应该在第6 部分小容量、中容量和大容量产品的复位和时钟控制(RCC),这一部分里我们来个笨办法,在6.3 RCC寄存器描述这部分章节中一个寄存器一个寄存器地找与打开GPIOB相关的内容,在看到6.3.7 APB2 外设时钟使能寄存器(RCC_APB2ENR)这部分时,我们终于在表中看到了IO端口B时钟使能 (I/O port B clock enable) ”这句话,好了,找到了,将外设时钟使能寄存器(RCC_APB2ENR)的位3置为1就行了。
与前相同的方法,我们找到它的偏移地址为0x18,RCC的起始地址值为0x40021000,那么RCC_APB2ENR的绝对地址值就是0x40021018。
前面的程序中加上这个语句后程序如下:
int  main(void)
{
(*(volatile unsigned int *)0x40021018)|=(1<<3);
        (*(volatile unsigned int *)0x40010C00)=0x33333333;
        (*(volatile unsigned int *)0x40010C0C)=0xFFFF;
        while(1);
}       
编译下载,亮了亮了!
验证一下,将0xFFFF改成0x0000,不亮了。
再改回去,又亮了!
成就感满满不是?
说一个很重要的问题,对于“在每一次复位以后,所有除SRAM和 FLITF以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR来打开该外设的时钟”这句话,我一直没能理解打开该外设的时钟是个什么设计,我个人的理解是:该芯片电路在设计上,它是有节电、功耗、温度、移动等设计要求的,而外设在不使用时如果也通着电,那它必然是白白消耗电能,所以,在电路设计上,我们应该如同电灯不用时随手关灯一样,让所有未投入使用的外设全部断开电源,所以,芯片复位后,所有除SRAM和 FLITF以外的外设都被关闭电源,而前面我们分析端口电路根本没有通电的情况,正好与此相符。再有,芯片上的这种设计在理论上是一定应该有的,所以,所谓的打开该外设的时钟,其实质就是打开该外设的电源开关。据此,如果我们在应用时有节能设计要求,那么在编程时,我们可以做随手关灯式设计。表明一下,以上只是个人分析,但可能性极大。
再说另一个事,我们前面找到RCC_APB2ENR寄存器的方法,是一个一个RCC寄存器地硬找出来的,其实,我们在看到2.1 系统构架里的1 系统结构时,就应该看到GPIOB是挂在APB2下面的,由此我们理所当然地去找RCC_APB2类寄存器,也就是只看RCC里面的名字里有APB2的寄存器就找到了。这个方法,同样适用于其它功能的外设的相关寄存器的查找。所以,这张图,很重要,当然,你并不需要记住它,只是在要用到时看一下就行了。
以上是纯用地址值来编程的方法,这个都不习惯,所以,我们还是给各个寄存器起个好记的名字,然后程序就这样了:
#define  RCC_APB2ENR   (*(volatile unsigned int *)0x40021018)
#define  GPIOB_CRL   (*(volatile unsigned int *)0x40010C00)
#define  GPIOB_CDR   (*(volatile unsigned int *)0x40010C0C)
                               
int  main(void)
{
RCC_APB2ENR|=(1<<3);
GPIOB_CRL=0x33333333;
        GPIOB_CDR=0xFFFF;
        while(1);
}       
其实吧,如同STC一样,keil软件也有个现成的头文件,它已经把各个寄存器的名字给起好了,文件名stm32f10x.h,拷进文件夹,在编程时把它包含进去就是了,于是程序就这样了:
#include <stm32f10x.h>
int main(void)
{
  RCC->APB2ENR|=1<<3;           
        GPIOB->CRL=0X33333333;
        GPIOB->ODR=0x0000;
        while(1);
}
如果你只想点亮位5即GPIOB.5引脚上的LED,程序是这样的:
#include <stm32f10x.h>
int main(void)
{
  RCC->APB2ENR|=1<<3;           
        GPIOB->CRL=0X00300000;
        GPIOB->ODR|=1<<5;
        while(1);
}
说明一下,STM32的端口寄存器是不能位寻址的,也就是说它并没有给端口寄存器的每一位分配地址,这点与STC不一样。
整个文件夹就987KB大小,其中stm32f10x.h就占了620KB。你们可以看看自己做的有多大,一些资料中的程序有多大,网上的一些帖子给的程序有多大。
STM32的编程就是如此简单。这个方法的成功验证,非常重要的一点,说明了只要你熟练掌握了STC,那你再使用其它任何单片机都将变得非常简单。
补充一点有关芯片硬件电路知识,端口输入输出寄存器电路与端口电路是两回事,它们两者的值并不是任何时候都一致,中央控制器会每隔一定时间将输出寄存器的值传送到端口,或每隔一定时间将端口的值送入端口输入寄存器,这个时间间隔就是频率。

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表