个人成就
- 发布了50篇内容
- 获得了4次赞同
- 获得了3次收藏
个人简介
擅长领域
暂时没有设置哦~
-
STM32中SystTick是个啥?咋用?
Cortex-Mx内核内部包含了一个SysTick定时器, SysTick 是一个24 位的倒计数定时器, 当计到0 时, 将从RELOAD寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。SysTick 在《STM32xx 中文参考手册》里面基本没有介绍,其详细介绍,内核编程手册中。 在工程中我们将以STM32F373为例,为SysTick配置1ms,利用1ms中断处理系统任务。下面我们介绍下寄存器:
下图是SysTick定时器的4个寄存器概括,我们介绍部分使用的寄存器:
SysTick控制和状态寄存器
这个寄存器的EBNALE(0位)为SysTick的使能位,TICKINT(1位)为设置是否产生中断,CLKSOURCE(2位)为时钟选择,当为1 时AHB时钟不分频,为0时AHB时钟8分频。当然我们选择使能定时器,产生中断,并选择AHB 8分频。假如系统时钟为72M,即可获得 72/8 = 9M的SysTick时钟频率。使能SysTick并产生中断,8分频时钟,代码如下:
SysTick->CTRL = (0<<2) | (1<<1) | (1<<0);
SysTick重装载值寄存器
该图来自数据手册中断和事件章节
图5为SysTick重载计数值寄存器RELOAD([23:0]),从该介绍我们可以得出结论,SysTick的计数方式为向下计数,也就是从RELOAD([23:0])值向下递减,当减到0的时候产生标志位,这个时候会重新装载该寄存器值,循环执行上面的步骤。那麽我们可以利用这个功能做一个1ms的定时器,我们已经配置系统时钟为72M,使用系统时钟的8分频(9M)作为SysTick定时器的时钟,也就是说时钟周期T = 1/9M(ns),即SysTick减1需要1/9M(ns),我们定时1ms那麽重载寄存器的值为 1ms/(1/9M(ns))-1 = 8999(注意这里要进行单位换算),这也就是我们的重装载值,下面给出具体代码配置,其中使能等操作包含在SysTick_Config();函数里面。配置代码如下:
SysTick->LOAD = 8999;
单个寄存器讲完了我们总结一下综合起来让SysTick工作起来,我们把SySTick的配置单独做成一个函数如下:
Void SysTick_Init(void)
{
/*
第一步:装载值
第二步:使能SysTick并允许中断,8分频时钟
第三步:设置SysTick优先级
*/
SysTick->LOAD = 8999;
SysTick->CTRL = (0<<2) | (1<<1) | (1<<0);
NVIC_SetPriority (SysTick_IRQn, (1<<4) - 1);
}
我们用库函数表示为:
对于官方库函数的查找我们可以使用《stm32f37x_dsp_stdperiph_lib_um.chm》这个文档,想要找某一方面的函数在里面直接搜索就可以,具体的使用方法我们下面再仔细介绍。
-
别再说你的单片机RAM不够用了,来看看这个吧...
当我们写代码的时候,会用到很多变量,如果随意的定义变量,比如写了N多个“unsigned char/int X;”那么代码可能会显的很乱,自己拐回头看的时候都晕掉了,那么这个时候我们可以构造一个复杂的数据类型-结构体类型,对代码中出现的变量进行类别的划分,用构造的结构体类型定义结构体变量,在写or看代码的时候,只要看到这个结构体,就能大致的知道其实现功能,这样看起来就神清气爽了,可读性大大提高。
我们定义的结构体变量,如果没有特殊规定的话是存储在RAM中的,单片机的RAM资源是有限的,那这个结构体变量在RAM中占的空间大小就是我们需要关注一个问题了,它真的像你想的那么“单纯”吗?接下来我们一起来看看吧!
在看下面的图之前,我们说一个前提,在STM32单片机这个32位系统中,signed/unsigned int 占4个字节,signed/unsigned short int 占2个字节 signed/unsigned char 占1个字节,我们称这些为基本数据类型。Size = Sizeof(Test);这个函数是求取这个结构体变量Test所占内存的大小,并返回给Size。
请看上图,我们使用基本数据类型构造了3个复杂的结构体数据类型,仔细看会发现,这3个数据类型的成员可是不大一样的,我们来看第一个Test,这个数据类型总共占4+4=8个字节,这个很好理解,那第二个Test1,占空间大小按道理来说应该是1+4 = 5个字节,但是为什么还是8呢,第三个Test2,占空间大小应该是1+1+4=8,为什么还是8呢?
这个里面就涉及到了结构体对齐,所有的成员在分配内存时都要与所有成员中占内存最多的基本数据类型所占内存空间的字节数对齐。假如这个字节数为 N,那么对齐的原则是:理论上所有成员在分配内存时都是紧接在前一个变量后面依次填充的,但是如果是“以 N 对齐”为原则,那么,如果一行中剩下的空间不足以填充某成员变量时,即剩下的空间小于某成员变量的数据类型所占的字节数,该成员变量在分配内存时另起一行分配。如图3,4:
通过上面的实际测试,我们得出,在构造结构体复杂数据类型的时候,成员变量的排放一定要注意顺序,遵守排放原则,否则就会白白浪费你的空间,掌握好排放原理,能大大提高你的空间利用率。比如我们构造如图5的结构体类型,它依然还是占8个字节。
文末再给大家出个问题,大家看看下面我们构造的数据类型,它们分别占的空间是多大呢?
-
单片机漫漫学习路
当你在懵懂的年纪的时候,是否也对身边的新鲜事物感兴趣过?也试过很多超越想象的第一次?每个人都是在慢慢成长,有时候会碰到喜欢的事情留恋不已,回想起来是不是觉得当时也是对的?谁还没点回忆呢。谁都有过青春期,谁都有过抗拒学习的心理,那个时候觉得除了学习什么都是美好的。当你真正过了那个阶段,发现学习才能最好的武装自己,有的人会继续前行,把浪费掉的补上;有的人则会真的掉队了,去做了再也不能学习的路。
其实每个人都有自己的特长,当你发现自己的特长并发挥出来都算是成功的。并不是每个人都能考第一,不是每个人都要上清华北大才算是有出息。话说360行,行行出状元。每个人都要找准自己的定位,发挥了自己的优势,生活自然不会亏待你。
不管什么年纪,每个人都在调整自己的心态和眼界,每个人都在时间的长河里醒悟自己,及时调整自己,让自己能有个更好的状态。不管是什么岗位,都要保持积极向上的心态,保持让自己不断进步才是最佳状态。每天都很美好,每天伸手都能触碰到阳光,人生需要奋斗。
除了心态,还要有选择后的坚持,其实近几年智能化产品几乎都占领了大多市场,物联网正是火热,沉静下来,或许你也能找到自己的那一块发光发热的土地。
就拿单片机来说,说难不难,说简单不简单,学精了真的可以有自己的傲娇的小领地可以去施展一下的。
现在太多芯片厂商为了获取客户的依赖,做了很多底层的库函数,所以才会导致太多程序员要依附于他们的工具,依附于他们的库函数做产品,甚至连芯片手册都没碰过,觉得我能实现就好了啊,碰到问题百度好了呀。产品到了交付期,问题还很多,这个时候你焦急了没有?话说真的太多单片机程序员碰到过的。大环境所致,很少有人能静下来心来去研究单片机到底是怎么启动的?到底是怎么工作的?结构是怎么样的?只有碰到问题百感交集时才会觉得其实自己真的不懂单片机,只是会拷贝,会改动。真正从头到尾做过一个产品吗?从底层启动到寄存器配置到上层应用逻辑?话说真的绝大多数程序员没有做过的。
那么到最后是不是发现自己其实什么长进都没有,都在做修修补补的活了。跳槽是不是成了奢望了,想有跳槽的资本就要去做别人不会的,别人没做过的。比如单片机,很多人说我会啊,做项目做产品,可说到一些基本的单片机操作又慌乱了。
如果你尚且有些梦想,如果你是在做单片机软件程序工作,请一定要去把它的原理搞懂,从底层一步一步去实现一下,工程都是从小到大的,当你碰到很多困难还继续前行了,那等待你的肯定是更多的机会。你付出了多少的汗水都会有多少的收获等着你的。只要努力了坚持了,那么看到的风景都是不一样的。加油吧打工人,我们都会更好的~
-
单片机编程关键字之volatile
volatile修饰的变量是说这变量可能会被意想不到地改变。通常对于程序员而言,单片机中用的就算常见了。
volatile 是易变的,不稳定的意思。其实对于很多人来说,根本没见过这个关键字,不知道它的存在。也有很多人知道它的存在,根本没用过,我对它有种“杨家有女初长成,养在深闺无人识”的感觉。
那么volatile关键字到底是什么意思呢,怎么用呢。
1、volatile其实和const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素而改变,比如操作系统、硬件或者其他线程等等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
①、我们举个例子,
int i=10;
int j=i; //①语句
int k=i; //②语句
此时编译器对代码进行优化,这是因为在①、②两条语句中,i没有被用作左值(没有被赋值),这时候编译器认为是i的值没有发生改变,所以在①语句时从内存中取出i的值赋给j之后,这个值并没有被丢掉,而是在②语句时继续用这个值给k赋值。编译器不会生成出汇编代码重新从内存里取i的值(不会编译生成装在内存的汇编指令,比如ARM的LDM指令),这样提高了效率。但要注意①和②语句之间确认i没有被用作左值才行。
②、再看一个例子:
volatile int i=10;
int j=i; //③语句
int k = i; //④语句
volatile关键字告诉编译器,i是随时可能发生改变的。每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i的地址处读取数据放在k中。
这样看来,如果i是一个寄存器变量,表示一个端口数据或者是多个线程的共享数据,那么就容易出错,所以说,volatile可以保证对特殊地址的稳定访问。
2、我们知道做电子方面技术工作的一般面试的时候都有笔试,比如单片机软件方面,考题基本都会有对这个关键字的考察,可想在单片机中这个关键字的重要性,一般题目会有对这个关键字的定义是什么,就是你得知道它的概念,然后就是举例说明这个关键字使用的例子,这个时候就能考察出大家对这个关键字的具体理解了。
一般我们知道的是:
1、 并行设备的硬件寄存器(比如状态寄存器)
2、 一个中断服务子程序中会访问到的非自动变量
3、 多线程应用中被几个任务共享的变量
当我们回答出这些时基本可以知道你对这个关键字是懂的,如果做嵌入式的话,程序员经常和硬件、中断、RTOS等等打交道,所以这个关键字必须要懂。
3、那么我们再延伸一下,一个参数既可以是const也可以是volatile,那么举个例子就是只读的状态寄存器,那么只读就是const,并且要确定程序不能试图去修改它,再有就是volatile代表状态寄存器,可能被意想不到的改变。
4、那么指针是不是可以用到这个volatile关键字吗,是可以的。
这个我们只要了解即可,用的不多。但是得知道。举个例子就是当一个中断服务子程序修改一个指向一个buffer的指针的时候。修饰也和const类似,const有常量指针和指针常量的说法,volatile也可以这样认为,
比如修饰由指针指向的对象或数据是const或volatile的。
例如:
volatile char *p1;
比如指针自身的值---一个代表地址的整数变量是const或volatile的。
例如:
char* volatile p1;
对于这个关键字不仅C语言有,其他语言比如C++,JAVA其实都有。其他语言大部分是因为多线程共享变量的使用不被编译器优化的错误产生,比如优化编译器把一个变量从内存装入CPU寄存器中,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这回造成程序的错误执行。那么用了volatile来修饰变量了呢就是要告诉编译器每次操作这个变量的时候一定要从内存中真正取出,而不是使用已经存在寄存器的值。
对于volatile怎么修饰变量,什么变量需要这个关键字修饰,大家心里有数了吧?
-
STM32的几种开发方式,你都知道吗?
经常有人会问,你们STM32编程是用库函数 还是用寄存器的...会说库函数方便,容易,都用库函数...等等这样的问题,今天我们就来dis一下这几种编程方式,STM32编程目前常见的几种形式如下:
1. 使用标准外设库开发
2.使用寄存器开
3.使用CubeMax生成代码工具开发
下面我们先来说一下目前这几种形式的区别:
1.使用标准外设库开发
Stm32标准外设库是stm32官方提供给用户的全系列芯片的外设驱动,官方把单片机外设的功能进行包装,提供给用户一个现成的接口函数,用户不用去管寄存器到底是如何操作的,直接调用接口函数,即可使用这些外设。在这个基础上你直接开发你的应用层程序即可。
这个驱动包名字一般是STM32Fxxx_StdPeriph_Lib_Vx.x.x有了它可以大大加速我们开发stm32。我们以STM32F10x_StdPeriph_Lib_V3.5.0驱动包为例,解压该zip文件,得到如下文件夹和文件
_htmresc
Libraries
Project
Utilities
Release_Notes.html
stm32f10x_stdperiph_lib_um.chm其中Libraries包含库的源代码,Project包含stm32各个外设的使用范例和一个工程模板,Utilities是使用st公司评估板的例子,stm32f10x_stdperiph_lib_um.chm教我们怎么用标准外设库。
2. 使用寄存器开发
使用寄存器开发,用户需要自己去操作底层外设寄存器,实现想要的外设功能,这个过程是需要自己对照手册,一行一行把代码敲出来的。
3. 使用CubeMax开发
这个是官方提供的图形化的一键生产代码的工具,在这个IDE中我们只要根据你想要的功能,点点鼠标,就可以生成你想要的功能,但是注意这个功能只是你想要的单片机外设配置功能,并不是你的应用程序功能,比如你想要以一个什么样的方式通讯,还是需要你自己去完善的。如下图是图形化开发的界面:
上面我们以简洁明了的描述了三种编程方式的使用方式和编程过程,接下来我们来分析一下这三种方式的优缺点:
第一种方法,官方提供了现成的驱动库,用户可以直接使用,使用起来方便,快捷,开发速度快,相对起来也容易上手一点,但是对底层的寄存器操作原理了解不深,只知其一,不知其二,出了问题,解决起来比较麻烦。且官方的驱动库为了容错性高一些等原因,会引入一些判断机制,相对复杂一些,但是实际上有的东西是我们用不到的,这就会造成代码执行效率会相对低一些。
第二种方法,虽然开发起来相对来说比较慢一些,比较繁琐一些,但是接触的都是真正的底层内容,出了问题,我们也能从源头来快速分析解决问题,而且写的代码中省去了一些不必要的判断过程,执行效率会相对高一些,代码看起来也会清爽一些。
第三种方法,使用IDE开发,这个相比较来讲入门是最快的,不用接触那些库函数接口,也不用去理会那些寄存器操作,只要在图形化的界面上勾选一些选项就可以了,这简直是懒人必备神器啊,但是话说回来,这种开发方式,基本上接触单片机底层内容为0%,只要写应用程序就可以了,出了问题更难解决。如果换了个平台,没有这种方便的IDE,怎么办呢?
上面我们全面分析了STM32的几种编程方式,我认为大家在学习STM32单片机时,应该先从寄存器入手,知其所以然,理解了原理,底层知识之后,可以再拐回头使用标准库,或者IDE,这样效果会更好,你认为呢?