个人成就
- 发布了53篇内容
- 获得了1次赞同
- 获得了2次收藏
个人简介
擅长领域
暂时没有设置哦~
-
STM32学习GPIO之设计篇
大家好,我是张飞实战电子黄忠老师,今日分享GPIO结合寄存器以及硬件电路,再来举例子分析输入输出。
寄存器介绍:
通过寄存器的位标注rw,我们可知这个寄存器的某个位是可读(r)并且可写的(w),我们也可以通过读寄存器里面的值得到引脚的配置信息,如果寄存器的位标注只有r或者w,那么就代表这个寄存器的这个位只能进行读(r)或者写(w)。
复位后IO口寄存器的值每一位可能不都是一样的,也就是说复位后IO口的状态、工作模式等不完全相同,一定要注意。
模式配置寄存器GPIOx_MODER,右上角用蓝色圆圈标识的地方告诉我们IO口的复位值,可以看到复位后对于A组IO口来说初始值0XA8000000,对于B组IO口初始值为0x00000280,其他组IO口都是0。该寄存器共 32 位,每 2个位控制 1个IO。
如图中左下角划蓝线的为第0位和第1位。通过往第0位写0,第1位写0配置成Input Mode(输入模式),通过往第0位写0,第1位写1配置成General purpose output Mode(普通输出模式),通过往第0位写1,第1位写0配置成Alternate function Mode(复用功能模式),通过往第0位写1,第1位写1配置成Analog Mode(模拟模式)。
例如我们要把这个寄存器第0位写1,其他都写0,表示如下:
GPIOA->MODER |= 0x00000001;
这就相当于把A组的PA0口配置成了普通输出模式,其他IO口配置成了输入模式。
GPIOx_OTYPER寄存器用于控制 IO 的输出类型,仅用于输出模式,在输入模式下不起作用。该寄存器低 16 位(第0位-第15位)有效,每一个位控制一个 IO 口,根据往不同的位写1(开漏输出)/写0(推挽输出)来配置成不同的输出模式。复位后,该寄存器值均为 0,即默认为推挽输出。
例如我们往A组IO口的第0位写1那就将PA0成开漏输出模式:
GPIOA->OTYPER |= 0x00000001;
GPIOx_OSPEEDR寄存器用于控制 IO的输出速度,仅用于输出模式,在输入模式下不起作用。该寄存器每 2 个位控制一个 IO 口,根据往不同的位写1/清0不同的位来配置成不同的模式。复位后对于A口来说初始值为0X0C000000,对于B口初始值0x000000C0,其他口都是0。
例如我们往A组IO口的第0位和第1位写1那就将PA0成高速输出模式:
GPIOA->OSPEEDR |= 0x00000003;
GPIOx_PUPDR寄存器用于控制 IO 的上拉/下拉,该寄存器每 2 个位控制一个 IO 口, 用于设置上下拉,根据往不同的位写1/清0不同的位来配置成不同的模式。复位后对于A口来说初始值为0X64000000,对于B口初始值0x00000100,其他口都0x0C000000。
例如我们往A组IO口的第0位写1那就将PA0下拉模式:
GPIOA->PUPDR |= 0x00000001;
GPIOx_ODR寄存器是IO口的输出数据寄存器,寄存器低 16 位(第0位-第15位)有效,寄存器每 1 个位控制一个 IO 口, 也就是这个里面位的状态代表了单片机输出的状态,根据往不同的位写1/清0不同的位来配置成不同的模式。复位后都是0。
例如我们往A组IO口的第0位写1那PA0就输出高:
GPIOA->0DR |= 0x00000001;
对于一些其他的寄存器大家可以去参考数据手册,按照上面讲述的方法去配置。
硬件设计:
图示LED灯部分我们用到了B组的2PIN(脚的简称)和4PIN, C组的15PIN, D组的8PIN, E组的8PIN和9PIN。
LED灯硬件的设计决定了LED灯的驱动方法,LED灯相当于一个发光二极管,如果单片机引脚输出高,则二极管的左边和右边电压相同,不亮,反之单片机输出低,右边电压大于左边电压通过单片机内部电路形成回路点亮二极管,那麽通过控制引脚输出的高低就控制了LED灯的亮灭。
软件设计:
下面就通过实际的寄存器来配置IO口模块,下面为LED寄存器配置代码:
void Init_GPIO(void)
{
//开启IO时钟
/**********************LED*****************************/
RCC->AHBENR |= (1<<18); //I/O port C clock enabled
RCC->AHBENR |= (1<<19); //I/O port D clock enabled
RCC->AHBENR |= (1<<20); //I/O port B clock enabled
RCC->AHBENR |= (1<<17); //I/O port A clock enabled
RCC->AHBENR |= (1<<21); //I/O port E clock enabled
GPIOB->MODER = 0; //Reset register
GPIOB->OSPEEDR = 0;//Reset register
GPIOB->PUPDR = 0;//Reset register
//GPIOB_Pin2 GPIOB_Pin4
GPIOB->MODER |= 0x00000110; //设置GPIOB_Pin2/Pin4输出模式
GPIOB->OSPEEDR |= 0x00000330;//设置GPIOB_Pin2/Pin4输出速度
GPIOB->ODR |= 0x0014; //设置GPIOB_Pin2/Pin4输出高电平
//GPIOE_Pin8 GPIOE_Pin9
GPIOE->MODER |= 0x00050000; //设置GPIOE_Pin8/Pin9输出模式
GPIOE->OSPEEDR |= 0x000F0000; //设置GPIOE_Pin8/Pin9输出速度
GPIOE->ODR |= 0x0300; //设置GPIOE_Pin8/Pin9输出高电平
//GPIOD_Pin8
GPIOD->MODER |= 0x00010000; //设置GPIOD_Pin8输出模式
GPIOD->OSPEEDR |= 0x00030000; //设置GPIOD_Pin8输出速度
GPIOD->ODR |= 0x0100; //设置GPIOD_Pin8输出高电平
//GPIOC_Pin15
GPIOC->MODER |= 0x40000000; //设置GPIOC_Pin15输出模式
GPIOC->OSPEEDR |= 0xC0000000; //设置GPIOC_Pin15输出速度
GPIOC->ODR |= 0x8000; //设置GPIOC_Pin15输出高电平
}
上面没有配置的寄存器我们使用默认值,通过改变输出数据寄存器(ODR)的值,控制某一个引脚输出高和低控制LED灯的状态。
例如:控制IO输出高电平:
GPIOD->ODR |= 0x0100; //设置GPIOD_Pin8输出高电平
控制IO输出低电平:
GPIOD->ODR &= 0xFEFF; //设置GPIOD_Pin8输出低电平
在这里我们仅配置IO口的推挽高速输出,不上拉,不下拉模式作为演示,大家也可以根据上面介绍的方法去配置调试其他模式,GPIO部分设计实现介绍就在此结束,若有不理解的欢迎交流~
-
什么是单片机的闩锁效应?
大家好!我是张飞实战电子黄忠老师!今天给大家分享什么是单片机的闩锁效应?
什么是“闩锁效应”?这个词儿对我们来讲可能有点陌生。从构造上来看,单片机由大量的PN结组成。有一个由四重结构“PNPN”组成的部分,其中连接了两个PN结。PNPN的结构是用作功率开关元件的“晶闸管”的结构,并且单片机中的PNPN部分被称为“寄生晶闸管”。
晶闸管由三个端子组成:阳极(正极),阴极(负极)和栅极(门)。通常,电流不从阳极流向阴极,但是当信号输入到栅极时,电流从阳极流向阴极。一旦电流开始流动,除非电源关闭,否则它将继续流动。由于此时的导通电阻非常小,因此流过大电流。在单片机的寄生晶闸管中发生相同现象的现象称为“闩锁”。
当单片机发生闩锁时,大电流流入内部,不仅导致无法正常工作,而且还可能导致单片机内部的导线熔化并损坏元件。如果使用正确,将不会发生闩锁,但是如果您错误地启动电源或陡峭的高压噪声进入引脚,则会发生闩锁。图1是单片机表面上的金属布线的图片,该金属布线实际上已被闩锁电流熔化。一、晶闸管结构是什么样的?
图2显示了晶闸管(PNPN)的结构。当将正电势施加到阳极而将负电势施加到阴极时,由于J1和J3为正向,而J2为反向,因此没有电流从阳极流向阴极。然而,当将电压施加到栅极并且电流流动时,J2的反向电流被栅极电流加速,并且电流流过J2。由于J1和J3本质上是向前的,因此当发生这种现象时,电流开始从阳极流向阴极。一旦电流开始流动,除非阳极电源关闭,否则它将继续流动。这是晶闸管切换操作。利用这种操作,晶闸管被用作电力设备中的开关元件。PNPN结被认为是PNP晶体管和NPN型的组合,如图2-b所示。电路图显示了如图2-c所示的双晶体管配置。Tr1的发射极(E)成为晶闸管阳极,基极是Tr1的集电极(C),Tr2的基极(B),阴极是Tr2的发射极(E)。
二、闩锁发生的机理?
图3显示了应用于单片机CMOS中的两个晶体管。上图中的示例适用于N型衬底。此外,存在P型衬底的情况,但在两种情况下均会形成寄生PNPN结,因此可以以相同方式考虑闩锁的原理。Tr1由PMOS的源极的P沟道形成,该PMOS的源极连接到N型衬底的电源,然后再连接到P阱。然后,Tr2由从N型衬底连接到P阱和GND的NMOS源极的N够到路径形成。
Tr1和Tr2形成为如图3的CMOS中的黄线所示。电源侧为阳极,GND侧为阴极,而栅极等效于NMOS P阱。CMOS输入线连接到NMOS的栅极。栅极和P-WELL在插入栅极氧化膜的情况下形成与电容器相同的结构。电容器很容易通过高频信号,因此,如果噪声进入输入线,并且噪声的dV/dt大(高频分量大),则它会穿过栅氧化膜并到达P阱。这将触发PNPN结导通,从而导致大电流从电源流向GND。
另外,如果电源线急剧波动,特别是如果它向负侧波动,则栅极电压将高于电源电压,并且状态将与噪声进入栅极时相同。如果在建立单片机的电源之前在端子上施加了电压,则会发生此状态。 -
24C02存储细节规范
大家好,我是张飞实战电子黄忠老师;前面文章分享了I2C的一个标准规范,只是知道这些标准规范,还不能和目标器件进行正常通信,我们以24C02(EEPROM)为例来做一个简单说明,想要和EEPROM通信,就得遵从EEPROM的通信规范,因为EEPROM是一个不可编程器件,它的规范是固定的。
那么它都有哪些规范需要我们去注意呢?比如数据传输的时候拉高拉低要保持多长时间才有效、通信的时候要按照什么格式写,先发高位还是低位等等。在这里一共有4个时间需要注意,分别为数据建立时间,保持时间,时钟高电平时间,时钟低电平时间,手册上对这几个时间都是有要求的。
从图中我们看出,当我们在SCL为低电平期间,把SDA数据改变,这个时候SCL时钟还是要保持一段时间才能拉高发送,这个保持的时间就是数据建立时间。图1 图2
从图1中箭头处可以看出,当我们把SCL拉低之后,SDA数据是不可以立马去改变的,SCL时钟拉低还是要保持一段时间才能去改变SDA数据线电平的,这个时间就是保持时间。从图2中箭头处可以看出,当我们把SCL拉高发送数据的时候,这个SCL时钟保持高电平,是要持续一定时间的,这个时间就是时钟高电平时间,时钟低电平时间也是同理。
这个是24C02器件在400K高速通信4个时间的要求,那我们在进行单片机编程的时候,我们需要根据这几个C间设置相关寄存器,在24C02操作时,不光有这几个时间,它还有自己的通信协议规范。我们以字节写为例,来进行说明。
下图就是24C02通信协议,我们先看对24C02写数据,是1个字节写指令,根据这个写一个字节对应的定义我们来一个一个进行解释。
1表示启动
2表示设备地址也就是我们说的从机地址(设备地址为7位),发地址时,先发地址的高位,再发低位(MSB为高位,MSB在前,表示先发),可以通过24C02器件某几个特定的引脚接不动的电平状态来设置地址。
3表示是对从机如何操作,是读还是写(一个位),这里是写操作。
4表示要读写数据的地址
5表示要读写的数据内容6表示停止
7表示每进行一次传输从机的应答,每次传送1个字节,也就是8个bit,也就是每8个bit一个ACK信号以上就是一个字节的写,当然还有多字节写,单字节读,多字节读等操作,原理都是一样的,知道了这些之后,我们才可以对应的去写程序代码。赶快去试试吧!
-
在嵌入式系统中大小端和对齐问题
大家好,我是张飞实战电子的黄忠老师,今天我们来讲解在嵌入式系统中大小端和对齐端的问题
C语言是一种高级语言,在大多数情况下C语言的代码是和具体的处理器体系结构无关的。然而,在嵌入式系统的编程中,有可能涉及对内存的具体操作。在大小端和内存对齐问题上,C语言就不能屏蔽不同体系结构处理器的差别,也就是说同样的C语言代码在不同的体系结构的处理器上,有可能产生不同的结果。
大小端问题又叫字节序的问题。在各种体系结构的处理器中,对多字节数据的内存操作有着不同的定义。处理器对内存数据的操作有读写两种,这就涉及处理器在读写一个多字节的内存的时候,高字节是在内存的高地址还是低地址。一般在32位或者16位的处理器中,都具有将32位数据和16位数据读写到内存中的指令,这时不同的大小端模式将有不同的结果。
如果读写指令针对的数据长度和类型是一致的,无论数据在内存中存放的形式如何,处理器整体读写都没有问题。这种整内存协调的读写操作问题,一般不会涉及处理器的大小端。
当处理器读写指令针对的数据长度不一致的时候就会涉及大小端的问题,例如:
将0x76543210整体放入内存,然后在内存的首地址用单字节读取的命令读出。
如果不知道大小端模式的情况下,读取的值是多少你能确定吗?
这时就涉及处理器是大端还是小端的问题。
对于小端处理器,写内存的时候会将内存低地址处放入源数据的低字节,在内存的高地址处放入源数据的高字节;读内存的时候,将内存中低地址的数据就视为目标数据的低字节,对应的高地址数据是目标数据的高字节。
对于大端处理器,跟小端就相反的。内存低地址存放数据的高字节,高地址存放数据的低字节。
例如:数据0x76543210在内存中的大端或小端的存放形式如下:
上面的示例只是处理器自身读取和写入内存的情况,在更多的情况下,内存中的数据可能来自外界的输入,例如:来自网络的数据包;处理器在写内存的时候,这块内存也可能是给系统中别的设备使用的,例如:处理器写显示内存的情况。这时,就更需要注意处理器的大小端问题,只有大小端处理协调匹配,才能获得正确的结果。
在C语言中,使用指针就可以操作内存,指针的基本类型long和short分别代表了32位和16位的数据。使用16位或32位指针操作内存的时候,同样涉及内存的大小端问题。
上面我们说了一下内存读写的模式不同,一个地址存的数据不同。
接下来我们说一下内存对齐的问题,有人会说了内存对齐不对齐还需要你来管吗?这个在写程序的时候也是有讲究的,那么到底什么是内存对齐?为什么要有这个概念呢,我们来一起学习一下吧。
内存对齐操作的含义是:对于一个4字节的数据,要求其内存是4字节对齐的(地址为4字节的整数倍)。32位对齐的含义是其内存的地址的最低位是:0x0,0x4,0x8,0xC
16位对齐的含义是其内存的地址的最低位是:0x0,0x2,0x4,0x6,0x8,0xA,0xC,0xE
显然,对于单字节的内存读写操作,没有内存对齐的问题。从处理器硬件的角度,处理器更适合处理对齐的内存操作。对于非对齐的内存操作,不同的处理器则有不同的结果。
局部变量建立在栈空间上的,由编译器分配,一般保证它们都是对齐的。但是在程序中可能出现不对齐的内存操作。对于嵌入式系统中常用的ARM体系结构,并不支持不对齐的地址操作,当进行不对齐的地址访问的时候,处理器将引发异常。
在嵌入式程序的编写过程中,更需要注意内存对齐的问题。对于内存操作,使用字节操作(8bit)不会有内存对齐的问题,但是效率比较低。在32位系统中,应该尽量使用32位的数据操作,但这将带来内存对齐的问题,因此需要根据系统的具体情况选择合适的内存操作。
我们再来说说常纠结或者容易迷惑的结构体成员的对齐问题。
结构体是一个基本的语法单元。在32位系统中,编译器一般会对结构体的成员变量作一定的对齐处理。例如,在程序中定义如下结构体:
typedef struct _S1
{
char m1;
int m2;
char m3;
short m4;
}S1;
在结构体的定义上,结构体的大小应该是各个结构体成员的大小之和。但是,对于上面这个结构体S1,它的大小并不等于4个成员变量之和。在这种定义中,三个成员变量之和是1+4+2+2=8,但是结构体的大小并不是8字节。
编译器在处理结构体的时候,默认将结构体内部各个变量的内存都是对齐的,由此在结构体的内部可能出现一些空的字节。
一般情况下,在结构体含有4字节长整型成员的时候,结构体的大小将是4字节的倍数。为了对齐可能需要在结构体的最后补充1~3个字节。
如果结构体中含有2字节短整型成员的时候,结构体的大小将是2字节的倍数。为了对齐可能需要在结构体的最后补充一个字节。
这个算字节数的一般出现在找工作中的笔试题的概率还是很高的,其实就是考察的对这个内存对齐的掌握。
-
不要再找啦,关于Cortex-Mx芯片的启动没有比这里更清楚啦!
Cortex-Mx启动流程步骤详解
单片机在上电的时候会经历一个启动的流程,不管是你从手册描述上看得见的,还是看不见的,亦或者你不知道还有这种操作的,这个启动都是客观事实存在的,今天我们就用白话文来唠一唠Cortex-Mx系列的启动流程(此文章知识广泛适用于CPU为Cortex-Mx系列的MCU)。
图1
如上图为STM32F0系列单片机系统存储器的映射图(其他系列型号,映射图会有区别,但流程还是一样的),通常,当处理器从复位中启动时,它首先会访问位于0x0000 0000地址处的向量表,这个向量表是什么?从哪里来?跟程序员又有什么关系?明确的讲这几个问题跟我们的启动文件是紧密相关的。启动文件就是对启动流程的“展现”,启动文件中包含了向量表(向量表中包含了堆栈指针地址、复位向量程序地址、以及系统中各类中断函数的入口地址,简单点讲就是单片机启动的时候得经过这个向量表,执行复位程序得经过这个向量表,执行中断还得经过向量表,从向量表中找中断函数的入口地址)。当然启动文件也是由程序员写的(只不过大多数由厂家的Coder代劳了)。下面我们一起来弄清楚吧!
图2(VectorTable部分截图)
上图为厂家参考手册上给出的向量表的部分截图,启动文件中程序员编写的向量表就是根据这个表格来编写的,每一行为向量表的一个组成成员,第一行为表示堆栈指针初始值,第二个字为复位向量地址,后面的行是各种类型的中断向量地址,也就是中断函数的入口地址(在图中第一个字被描述为保留,第二个字描述为复位,这两行内容非常重要!)。
前面我们说了一个关键点,当处理器从复位中启动时,它首先会访问0x0000 0000地址处的向量表,并读取向量表的前2个字,第一个字为堆栈指针MSP初始值(堆栈是一个临时的空间,用来临时存储一些信息,就像电影里面的“龙门客栈”一样,供过往客人临时歇脚。);第二个字为复位向量,它表示程序执行的起始地址。当读取到该地址之后,会自动跳到复位向量处开始执行程序(图2红框处,最右边的一列地址栏,第一行地址空间从0x0000 0000开始 - 0x0000 0003结束,第二行从0x0000 0004开始-0x0000 0007结束,后面以此类推,每行占4个字节,即1个字)。
但是比较晕的是:图1中大家可以看到,地址0x0000 0000处已经存在内容了,是系统BOOT的配置,这段内容是厂家固化的一段代码,我们写的代码是存储在从0x0800 0000开始往后的地方,即Main Flash Memory存储区(见图1)。
那大家想我们写的代码(启动文件也算写的代码的一部分),是放在FLASH存储区的,而单片机上电的时候是从0x0000 0000处开始执行,那么系统是怎么访问到我们自己写的这个向量表呢?
这个设计者考虑了,系统会自动把我们代码启动文件中的向量表映射到0x0000 0000地址处,也就是说在0x0800 0000开始存放代码的地方我们有一张自己写的向量表,系统会找到这张向量表,把这张向量表映射到0x0000 0000处,这样就相当于在0x0000 0000开始的这个地方也有了一张向量表了。
图3
如图3,系统一旦读取到向量表的第二个字复位向量的地址时,那么就跳到复位向量的地址开始执行程序,我们可以在复位向量地址处写上自己的用户代码,执行完这段代码之后,指挥程序跳到主函数main程序运行,那么这样连贯起来,程序正常跑起来了,这就是一个完整的启动。