个人成就
- 发布了53篇内容
- 获得了1次赞同
- 获得了2次收藏
个人简介
擅长领域
暂时没有设置哦~
-
什么是程序映像
大家好,我是张飞实战电子黄忠老师。
我们通常说的单片机的程序映像一般包含以下几个部分:
向量表;C启动例程;程序代码(应用程序代码和数据);C库代码(C库函数的程序代码,链接时插入)
分别来看下组成部分都是什么,代表什么……
向量表:
向量表可以用C语言或汇编语言实现。由于向量表的入口需要编译器和链接器生成的内容,所以向量表代码的实现细节是同开发工具链接相关的。例如,栈指针的初始值被链接到链接器生成的栈空间地址,而复位向量则指向了C启动代码的地址,这些都是同编译器相关的。有些开发工具,包括Keil MDK,则将向量表作为汇编启动代码的一部分,并且使用定义常量数(DCD)指令创建。
汇编实现的向量表的例子:
这个例子中,向量表被赋予了一个段名(RESET),为了将向量表置于系统存储器映射的开头(地址:0x00000000),链接文件或命令行选项需要知道段的名字,以便链接器能够正确识别向量并将其进行地址映射。复位向量一般指向C启动代码的开头,不过,也可以自己定义复位处理,在跳转到C启动代码前执行附加的初始化操作。
C启动代码
C启动代码用于设置像全局变量之类的数据,也会清零加载时未被初始化的内存区域。对于使用malloc()等C函数的应用程序,C启动代码还需要初始化堆空间的控制变量。初始化完成后,启动代码跳转到main()程序执行。
C启动代码由编译器/链接器自动嵌入到程序中,并且是和开发工具链相关的,而只使用汇编代码编程则可能不存在C启动代码。对于ARM编译器,C启动代码被标识为“_main”,而使用GNU C编译器生成的代码则通常被标记为“_start”。
程序代码
用户指定的任务是由应用程序生成的指令完成的,除了指令以外,还有以下各类数据:
①变量的初始值,函数或子程序中的局部变量需要初始化,这些初始值会在程序执行期间被赋给相应的变量。
②程序代码中的常量。
③有些应用程序可能也会包括其他的常量,比如查找表和图像数据,他们也被合并在程序映像中。
C库代码
当使用特定的C/C++库函数时,它们的库代码就会由链接器嵌入到程序映像中,另外,由于有些数据处理任务需要浮点数或除法运算,在进行这些运算时,C库代码也会被包含进来。具体应用场合不同,内核不同,对C库代码多少以及使用情况也不同。
RAM中的数据
像程序ROM一样,微控制器的RAM也有很多种用法。典型地,RAM的使用一般可以分为数据、栈和堆区域。
对于嵌入式操作系统(如uClinux)或RTOS(如Keil RTX)的微控制器系统,每个任务的栈空间都是独立的。有些操作系统允许用户自定义任务的栈,这样也就需要更大的栈空间。有些操作系统则将内存分为若干个段,每个任务分配一个段,用于各自的数据、栈和堆区域。
那么,这些数据、栈和堆区域都存储了什么内容?
数据,数据存储在内存的底部,包含全局变量和静态变量。
栈,栈空间用于临时数据存储、局部变量的存储空间、函数调用参数传递和异常处理的寄存器备份等。
堆,堆存储用于C函数自动分配存储器区域,例如alloc()和malloc(),以及其他使用这些函数的函数调用,为了确保这些函数能够正确地分配存储器空间,C启动代码需要初始化堆存储及其控制变量。
一般说来,栈位于存储器空间的顶部,而堆区域则位于底部,这样做使得内存使用具有最大的灵活性。在操作系统环境中,可能会有多个内存区域用作数据、栈和堆。
-
异常和中断
异常是能够引起程序流偏离正常流程的事件,当异常发生时,正在执行的程序就会被挂起,处理器转而执行一块与该事件相关的代码(异常处理)。事件可以是外部输入,也可以是内部产生的,外部产生的事件通常被称作中断或中断请求(IRQ)。几乎所有的现代处理器都支持异常和中断,微控制器的中断可以由片上外设或软件产生。由此可见,通常我们处理的中断是异常的一种。
每种异常类型都有对应的优先级,有些异常的优先级是固定的,有些是可编程的。
先说几个概念:
1、不可屏蔽中断(NMI)
NMI同IRQ类似,只是它不能被禁止,并且优先级仅次于复位,它对于工业控制和汽车之类的高可靠性系统非常有用。根据微控制器设计的不同,NMI可以用于掉电处理,也可以连接到看门狗单元,以便在系统停止响应时将系统复位。由于NMI不能被控制寄存器禁止,其响应的及时性就得到了保证。
2、硬件错误
硬件错误异常用于处理程序执行时产生的错误,这些错误可以是试图执行未知的操作码、总线接口或存储器系统的错误,也可以是试图切换至ARM状态之类的非法操作。
3、SVC(请求管理调用)
SVC指令执行时就会产生SVC异常,其通常用在具有操作系统的系统中,为应用程序提供了访问系统服务的入口。
4、PendSV(可挂起的系统调用)
PendSV是用于带OS(操作系统)的应用程序的另外一个异常,SVC异常在SVC指令执行后会马上开始,PendSV在这点上有所不同,它可以延迟执行,在OS上使用PendSV就要确保高优先级任务完成后才执行系统调度。
5、系统节拍
NVIC中的SysTick定时器为OS应用可以利用的另外一个特性。几乎所有操作系统的运行都需要上下文切换,而这一过程通常需要依靠定时器产生定时中断来完成。
6、中断
中断信号可以连接到片上外设,也可以通过IO端口连接到外部中断源上。外部中断只有在使能后才能使用,如果中断被禁止了,或者处理器正在运行另外一个相同或更高优先级的异常处理,则该中断请求会被存储在挂起状态寄存器中。当高优先级的中断处理完成或返回后,挂起的中断请求才可以执行。NVIC能够接受的中断请求信号可以是高逻辑电平,也可以是中断脉冲。应该注意的是,在微控制器的外部接口中,外部中断信号可以是高电平也可以是低电平,或者可以通过编程配置。
异常的处理流程:
1、接受异常请求
处理器要接受一个异常,需要满足的条件:
①对于中断和SysTick中断请求,中断必须使能
②处理器正在执行的异常处理的优先级不能相同或更大
③中断屏蔽寄存器没有屏蔽掉异常
特别注意一点:对于SVC异常,如果用到SVC指令的异常处理的优先级与SVC异常本身相同或更大,这种情况就会引起硬件错误异常处理的执行。
2、压栈和出栈
为了使被中断的程序能正确继续执行,在程序切换至异常处理前,处理器当前状态的一部分应该被保存。不同架构处理器的处理方法不同,有的采用硬件自动处理的方法来备份和恢复处理器状态,看需求,有的是需要程序中增加软件处理过程。
异常处理过程执行到最后时,将会利用执行特殊值来触发异常返回机制。处理器还会查看当前是否还有其他异常需要处理,如果没有,处理器就会恢复之前存储在栈空间的寄存器值,并继续执行中断前的程序。
自动保存和恢复寄存器内容的操作被称为“压栈”和“出栈”,这种机制使得异常处理可以跟普通的C函数一样处理,同时也减小了软件开销以及回路大小,因此也降低了系统的功耗。
3、异常返回指令
根据处理器的不同中断处理返回有的需要特殊指令,一般都是普通的返回指令,加载到PC中的数值则会触发异常返回,这样就使得异常处理可以和普通的C函数一样使用。
两个不同的指令可以用于异常返回:
BX <Reg>q ;将寄存器中的值加载到PC中
或
POP {<Reg1>,<Reg1>,...,PC} ;POP指令,PC也是更新的寄存器之一
当其中一个指令执行,异常返回机制就会启动。
4、末尾连锁
如果当其他的异常处理完成后,还有异常处于挂起状态,这时处理器不会返回到中断前的程序,而是重新进入异常处理流程,这也被称作末尾连锁。当末尾连锁发生时,处理器不必马上恢复栈的值,因为如果这么做的话还得重新压栈。异常的末尾连锁降低了异常处理的开销,因此也提高了能耗效率。
-
USB的四种传输类型之控制传输
在我们前面的文章里面,我们描述了什么是批量传输,中断传输,等时传输,下面的文章我们来介绍一下控制传输。
控制传输与前面三种传输相比,要稍微复杂一些。前面在介绍设备的枚举过程时,就提到过控制传输。控制传输分为三个过程:第一个过程是建立过程;第二个过程是可选的数据过程;第三个过程是状态过程。
建立过程使用一个建立事务。建立事务是一个输出数据的过程,与批量传输的输出事务相比,有几处不一样:首先是令牌包不一样,建立过程使用 SETUP令牌包;其次是数据包类型, SETUP只能使用DATA0包;最后是握手包,设备只能使用ACK来应答(除非出错了,不应答),而不能使用NAK或者 STALL来应答,即设备必须要接收建立事务的数据。图1是建立事务的流程图.
图1
数据过程是可选的,即一个控制传输可能没有数据过程。如果有,一个数据过程可以包含一笔或者多笔数据事务。控制传输所使用的数据事务与批量传输中的批量事务是一样的。要注意的是,在数据过程中,所有的数据事务必须是同一个传输方向的。也就是说,在控制读传输中,数据过程中的所有数据事务都必须是输入的;在控制写传输中,数据过程中的所有数据事务都必须是输出的。一旦数据传输方向发生改变,就会认为进入到了状态过程。数据过程的第一个数据包必须是DATA1包,然后每次正确传输一个数据包后就在DATA0和DATA1之间交替。
状态过程也是一笔批量事务,它的传输方向刚好跟前面的数据阶段相反,即控制写传输在状态过程使用一个批量输入事务;控制读传输在状态过程使用一个批量输出事务。状态过程只使用DATA1包。
控制传输之所以要弄得这么复杂,是因为它要保证数据传输过程的数据完整性。设备枚举过程中各种描述符的获取以及设置地址、设置配置等,都是通过控制传输来实现的。关于USB协议中定义的控制传输所使用的各种标准请求的数据结构和请求命令,将会在后面的实例中具体、详细地分析。
接下来我们来说一下端点类型和传输类型的关系。一个具体的端点,只能工作在一种传输模式下。通常,我们把工作在什么模式下的端点,就叫做什么端点。例如,控制端点、批量端点等。端点0是每个USB设备都必须具备的默认控制端点,它一上电就存在并且可用。设备的各种描述符以及主机发送的一些命令,都是通过端点0传输的。其他端点是可选的,需要根据具体的设备来决定。非0端点只有在 Set Config之后才能使用。
今天就跟大家分享到这里,你学会了吗?
-
51单片机DIY抽奖-技术分享(四)
软件设计
延续前篇,继续分享我的DIY抽奖设计之软件部分,上一篇我介绍了我做这个小产品的PCB设计过程,然后现在就到了实物板调试的阶段了,只有把相应的程序实现了,这个产品才能真正实现自己的价值,实现的过程可能会比较曲折,但没有过程哪来的结果,没有实践哪来的经验,话说真的一点没错吧。
这里我只能描述实现过程和碰到的一些小问题,具体实现过程以及程序源码细节我也会陆续更新出视频,感兴趣的可以关注下~自己实践下更好了。
我们要完成一个程序设计的话需要先设计算法,然后再实现算法,实现就要用到计算机语言来表述了,不然单片机也不认不是。这里我说一个概念就是结构化程序的设计强调程序设计风格和程序结构的规范化,那么就提倡用清晰的结构,我们不管是新手还是老手,规范化还是很有必要的,对自己程序的逻辑关系的考验,或者是对阅读者都是很好的,以至于到后期的调试或者扩展和修改算法都会变得容易了。
我要实现抽奖功能,首先不能只有大的想法就够了,既然要实现就要把每个细节的逻辑关系都要落实才行,否则就不会出现你想要的结果。
那么接下来我从大的方向到细化的过程以及设计要点都分享下哈~
1、程序主要实现的功能:
简单清晰,主要的调试点在内部实现的逻辑关系上。后续精彩着呢,待更完~
2、顺序往下吧,对于单片机设计,首先就是要初始化,对IO口和定时器等进行配置才能使得单片机正常工作,每款单片机对外设的配置都是不一样的,那么就要研读数据手册了,这是避不开的。先来看下我的初始化部分。
既然前面提到了分模块,从初始化到实际处理都要分模块进行,这样思路清晰。比如第一个函数是对单片机的初始化,主要就是I2C通信的IO口部分,蜂鸣器的IO口部分,定时器的部分,那么初始化就是直接对外设寄存器的配置;后面的模块主要就是对当前模块的全局变量的初值配置了。全局变量我一般都是用结构体来实现,每个模块定义一个结构体,名称和用法一目了然,想扩展也方便。
3、初始化了,只能说单片机可以用了,具体还要看需要的应用模块,这样单片机才能顺利完成你需要的使命。继续看下抽奖大的模块,就是显示和按键了,再有一个就是蜂鸣器模块。
有了大的模块就可以细分,把具体功能先确定好,再去实现就方便多了,我觉得做程序一定要把模块做好,然后模块间的全局变量尽量通过结构体来实现,这样结构清晰,逻辑性好。
这是我个人经验,当能把习惯养好的时候,一定不要随意,否则浪费时间不说,程序结果也会不稳定。
4、那这里我就分享下我的小DIY的模块的逻辑实现,先说显示部分吧,首先我做的这个有两个按键,一个开始,一个停止。按键开始后我会把对应显示模式的变量配置好,对应不同的模式再去进行处理。
接下来细化了,就是在不同模式下做些什么,先来看下我的流程图,然后我再针对设计细节把想法说一下。
对于获取当前获奖者,我的处理方法是通过指针获取当前序号对应数组的值,同时把当前值从数组中删除,直到数组中数据全部被抽完为止。
流水灯以及显示数值由慢变快是怎么做的呢,主要就是定义一个全局变量,作为时间参数配置一个大一点的初始值,定时时间到时间参数减值,直到减到一个很小的值,这就是一个渐变的过程。
具体的实现过程可以关注我的视频,所有代码都会分享到。
5、那么按键模块的话就是扫描和处理了,按键部分我当时选取数码管驱动芯片就很明确了,具体可以看下我第一篇给大家推荐的那款芯片,很实用啊。那么对于扫描不需要做过多的处理,只需要直接读取就可以了,这个就没什么好说的了。只是I2C相关的读写函数要仔细研读手册了。
接下来看下我的处理,也是状态机模式,整个网络之间的关系通过结构体全局变量实现。逻辑关系清晰。
框架出来了,就是具体逻辑关系了,有短按和长按,这个长按只针对停止键,短按就是常规抽奖模式的开始和停止了,那么停止键在短按情况下,要判断当前模式是转盘转动模式还是设置模式,如果是转动模式就是进入停止模式(显示当前号码,蜂鸣器唱歌),如果是设置模式(配置抽奖号码总数),号码加1;启动键短按的处理,也是要判断当前模式是停止模式还是设置模式,如果是停止模式则进入转动模式,如果是设置模式,号码减1;还有一个长按停止键了,当前模式是停止模式,长按则进入设置模式,如果当前是设置模式那么就进入停止模式。长按的处理就是进入和退出设置模式。按键处理就这些需要实现的功能。
最后就是一个蜂鸣器的模块处理,由于蜂鸣器我做了唱歌的功能,涉及到频率问题,所以我放在中断中完成(也是经过多次调试)。蜂鸣器唱歌要分析谱子,调调,真的有点音乐人的感觉了,通过计算低音,中音,高音的频率先计算配置好,然后要有曲子的分析,查表配置周期(可以用定时器的时钟分频模式)实现唱歌。这里学问太深,有兴趣的可以深挖一下,还是通过蜂鸣器模式来进入相应的状态,比如一声响,两声响,唱歌。这个都是可以自由分配的。
到这里抽奖程序设计主体思路就这样实现了,整个DIY的产品就完成了。下面就是成品啦。
纵观看下,功能不难,其实实现起来真的没你想的那么简单,逻辑关系要确定好,手册要研读好。有兴趣的话可以自己动手实践下,每个实践过程都会有很大收获的,后续会分享我的程序实现的视频过程,会更细致些。每个项目不论大小都会经历不同的坎坷啊。当你遇到不同的问题的时候慢慢解决了才是经验的积累过程。这个抽奖DIY小产品就完成了,大家可以看下我的实现有没有需要改进的地方呢?随时欢迎交流啊,或者有更好玩的产品一起探讨实现啊,每个都有每个的特点,都能学到不一样的知识点。后面我会陆续做出更多的小产品,每一个都会有侧重点,或者可以给我启发或建议一起完成你的想法啊,感谢大家对我的持续关注,还希望能给出更多的意见和建议。
-
啥?烧录器连不上STM32单片机了,别慌,自举模式来帮忙!
大家好,我是张飞实战黄忠老师,今天我们来分享STM32单片机的自举BOOT模式。
当你拿到项目的线路板,打开电脑,噼里啪啦一阵子,撸了一段代码出来,往单片机里面一下载,纳尼?突然发现烧录器掉线了,怎么整都连接不上了,这个时候整个人心情都不好了,别慌,恢复心情,你需要用到下面这个高端大气上档次的技术,首先说明一下我们本次文章参考芯片以SMT32F103C8T6来做说明,其他芯片同理,话不多说,我们开整。
首先我来简单阐述一下这种方法的原理,这种方法是利用了STM32单片机的“自举BOOT模式”,首先使单片机处于系统BOOT模式,也就是让单片机启动的时候从System memory启动,然后在PC机操作上位机软件通过串口发送控制命令擦除芯片中存储的程序。
1.首先我们要知道第一个信息,有的单片机有很多个串口,那到底是从哪一个串口来发送这个命令呢?我们从数据手册的“存储器和总线架构”章节(2.4启动配置小节)获取如下信息,可以得出可以使用USART1接口启动自举程序。具体要发送什么命令,可以参考AN2606手册,我已经通过这个手册提炼出指令信息,编写成一个上位机小工具。
2. 那么如何进入系统自举模式呢?从下图画红线处可以得出信息,想要进入系统自举模式,需配置BOOT0引脚为1(高电平),BOOT1引脚为0(低电平),然后复位单片机,那么在SYSCLK的第4个时钟上升沿会锁定BOOT引脚状态,并选择启动模式为系统存储器,即系统自举模式。
3. 接下来我们看看接线图,我画出了简单的示意图如下,各位看官,请结合下图看具体操作方法:
图3
1).把USB转换工具按照图示方法连接(注意全过程不需要使用烧录器,且此步操作后目标板已经带电,如果目标板3.3V功耗很大,需要给目标板用外部电源供电)。
2).在设备管理器中查看此转换工具对应的串口号(注意如果没有识别到串口,需要安 装驱动。)识别到的结果如下图4所示:
3).设置BOOT0引脚为高电平,BOOT1引脚为低电平(如果有的MCU没有BOOT1引脚可以忽略),并复位单片机(可以通过单片机复位引脚来复位单片机),使单片机处于自举BOOT模式。
4).在上位机小工具中选择对应的COM串口号,点击打开串口
5).点击擦除按钮
6).在小工具的中间窗口处会显示擦除成功
经过上面的6步擦除成功后,把目标板恢复至初始状态,即可继续正常使用了。(注意在连接不上之前,需先检查烧录器和MCU的接线是否接好)。
如果需要上述小工具的,可以在公众号中添加客服二维码,备注领取串口小工具,来索取。