在刚开始接触STM32的时候,使用的keil作为IDE,由于在这之前,使用过VS, 使用过eclipse,因而在使用keil之后,实在难以忍受keil编辑器简陋的功能,可以说是极其糟糕的写代码体验。 之后,尝试过各种IDE,使用eclipse+keil,结果发现eclipse对C语言的支持也是鸡肋,使用emBits+gcc,需要和其他人协同的话就比较麻烦,之后发现了platformIO,也是使用gcc作为编译器,不过只支持HAL库,而且还有一个重要的原因,同事都是用的keil,如果我使用gcc,就不能协同工作了。 最后,通过使用VS Code + keil的方式,完美解决了写代码的体验问题,以及工程协作问题,其实网上使用VS Code作为编辑器,keil作为编译器的教程很多,不过基本都是需要在VS Code中编辑,然后在keil中编译,下载,调试,本文就要实现编辑,编译,下载,调试,全部使用VS Code。 Part1环境 (1)VS Code; (2)keil;python; (3)GNU Arm Embedded Toolchain(arm gcc工具链); (4)C/C++(VS Code 插件); (5)Cortex-Debug(VS Code 插件); (6)其他VS Code插件(提升体验)。 Part2前提 正式写代码之前,首先需要建立好一个工程,这个需要使用keil完成,包括工程配置,文件添加… Part3编辑 在安装好VS Code插件之后,VS Code编写C代码本身体验就已经很好了, 但是,因为我们使用的是keil环境,所以需要配置头文件包含,宏定义等,在工程路径的.vscode文件夹下打开c_cpp_properties.json文件,没有自己新建一个,内容配置如下: { "configurations": [ { "name": "STM32", "includePath": [ "D:/Program Files/MDK5/ARM/ARMCC/**", "${workspaceFolder}/**", "" ], "browse": { "limitSymbolsToIncludedHeaders": true, "databaseFilename": "${workspaceRoot}/.vscode/.browse.c_cpp.db", "path": [ "D:/Program Files/MDK5/ARM/ARMCC/**", "${workspaceFolder}/**", "" ] }, "defines": [ "_DEBUG", "UNICODE", "_UNICODE", "__CC_ARM", "USE_STDPERIPH_DRIVER", "STM32F10X_MD" ], "intelliSenseMode": "msvc-x64" } ], "version": 4 } 其中,需要在includePath和path中添加头文件路径,${workspaceFolder}/**是工程路径,不用改动,额外需要添加的是keil的头文件路径, 然后在defines中添加宏,也就是在keil的Options for Target的C++选项卡中配置的宏,然后就可以体验VS Code强大的代码提示,函数跳转等功能了(甩keil的编辑器一整个时代)。 Part4编译、烧录 编译和烧录通过VS Code的Task功能实现,通过Task,使用命令行的方式调用keil进行编译和烧录。 keil本身就支持命令行调用,具体可以参考keil的手册,这里就不多说了,但是问题在于,使用命令行调用keil,不管是什么操作,他的输出都不会输出到控制台上!!!(要你这命令行支持有何用) 不过好在,keil支持输出到文件中,那我们就只能利用这个做点骚操作了。一边执行命令,一边读取文件内容并打印到控制台,从而就实现了输出在控制台上,我们就能直接在VS Code中看到编译过程了 为此,我编写了一个Python脚本,实现keil的命令行调用并同时读取文件输出到控制台。 #!/usr/bin/python # -*- coding:UTF-8 -*- import os import threading import sys runing = True def readfile(logfile): with open(logfile, 'w') as f: pass with open(logfile, 'r') as f: while runing: line = f.readline(1000) if line != '': line = line.replace('\\', '/') print(line, end = '') if __name__ == '__main__': modulePath = os.path.abspath(os.curdir) logfile = modulePath + '/build.log' cmd = '\"D:/Program Files/MDK5/UV4/UV4.exe\" ' for i in range(1, len(sys.argv)): cmd += sys.argv[i] + ' ' cmd += '-j0 -o ' + logfile thread = threading.Thread(target=readfile, args=(logfile,)) thread.start() code = os.system(cmd) runing = False thread.join() sys.exit(code) 此脚本需要结合VS Code的Task运行,通过配置Task,我们还需要匹配输出中的错误信息(编译错误),实现在keil中,点击错误直接跳转到错误代码处,具体如何配置请参考VS Code的文档,这里给出我的Task。 { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "py", "args": [ "-3", "${workspaceFolder}/scripts/build.py", "-b", "${config:uvprojxPath}" ], "group": { "kind": "build", "isDefault": true }, "problemMatcher": [ { "owner": "c", "fileLocation": [ "relative", "${workspaceFolder}/Project" ], "pattern": { "regexp": "^(.*)\\((\\d+)\\):\\s+(warning|error):\\s+(.*):\\s+(.*)$", "file": 1, "line": 2, "severity": 3, "code": 4, "message": 5 } } ] }, { "label": "rebuild", "type": "shell", "command": "py", "args": [ "-3", "${workspaceFolder}/scripts/build.py", "-r", "${config:uvprojxPath}" ], "group": "build", "problemMatcher": [ { "owner": "c", "fileLocation": [ "relative", "${workspaceFolder}/Project" ], "pattern": { "regexp": "^(.*)\\((\\d+)\\):\\s+(warning|error):\\s+(.*):\\s+(.*)$", "file": 1, "line": 2, "severity": 3, "code": 4, "message": 5 } } ] }, { "label": "download", "type": "shell", "command": "py", "args": [ "-3", "E:\\Work\\Store\\MyWork\\STM32F1\\FreeModbus_M3\\scripts\\build.py", "-f", "${config:uvprojxPath}" ], "group": "test" }, { "label": "open in keil", "type": "process", "command": "${config:uvPath}", "args": [ "${config:uvprojxPath}" ], "group": "test" } ] } 对于使用ARM Compiler 6编译的工程,build和rebuild中的problemMatcher应该配置为: "problemMatcher": [ { "owner": "c", "fileLocation": ["relative", "${workspaceFolder}/MDK-ARM"], "pattern": { "regexp": "^(.*)\\((\\d+)\\):\\s+(warning|error):\\s+(.*)$", "file": 1, "line": 2, "severity": 3, "message": 4, } } ] 文件中的config:uvPath和config:uvprojxPath分别为keil的UV4.exe文件路径和工程路径(.uvprojx),可以直接修改为具体路径,或者在VS Code的setting.json中增加对应的项,至此,我们已经完美实现了在VS Code中编辑,编译,下载了。 编译输出: 有错误时输出: 错误匹配: Part5调试 调试需要使用到Cortex-Debug插件,以及arm gcc工具链,这部分可以参考Cortex-Debug的文档,说的比较详细; 首先安装Cortex-Debug插件和arm gcc工具链,然后配置好环境路径,如果使用Jlink调试,需要下载Jlink套件,安转好之后,找到JLinkGDBServerCL.exe这个程序,在VS Code的设置中添加"cortex-debug.JLinkGDBServerPath": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe",后面的路径是你自己的路径。 这里补充一下arm gcc工具链的配置:"cortex-debug.armToolchainPath": "D:\\Program Files (x86)\\GNU Arm Embedded Toolchain\\9 2020-q2-update\\bin",后面的路径是你自己的路径。如果使用STLink调试,需要下载stutil工具,在GitHub上搜索即可找到,同样配置好路径即可。 以上步骤弄好之后,可以直接点击VS Code的调试按钮,此时会新建luanch.json文件,这个文件就是VS Code的调试配置文件,可参考我的文件进行配置。 { // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Cortex Debug(JLINK)", "cwd": "${workspaceRoot}", "executable": "${workspaceRoot}/Project/Objects/Demo.axf", "request": "attach", "type": "cortex-debug", "servertype": "jlink", "device": "STM32F103C8", "svdFile": "D:\\Program Files\\ARM\\Packs\\Keil\\STM32F1xx_DFP\\2.3.0\\SVD\\STM32F103xx.svd", "interface": "swd", "ipAddress": null, "serialNumber": null }, { "name": "Cortex Debug(ST-LINK)", "cwd": "${workspaceRoot}", "executable": "${workspaceRoot}/Project/Objects/Demo.axf", "request": "attach", "type": "cortex-debug", "servertype": "stutil", "svdFile": "D:\\Program Files\\ARM\\Packs\\Keil\\STM32F1xx_DFP\\2.3.0\\SVD\\STM32F103xx.svd", "device": "STM32F103C8", "v1": false } ] } 注意其中几个需要修改的地方,executable修改为你的工程生成的目标文件,也就是工程的.axf文件,svdFile用于对MCU外设的监控,该文件可以在keil的安装路径中找到,可以参考我的路径去找,配置完成后,再次点击调试按钮即可进行调试。 相比keil自己的调试功能,VS Code还支持条件断点,可以设置命中条件,次数等,可以极大的方便调试。 总结 通过以上的配置,我们基本上,除了建立工程和往工程中添加文件,其他完全不需要打开keil,所以也无妨说一句,再见,智障keil!
1.Keil编程开发环境(必备) 这个是最核心的工具了,用来编写和编译程序,还有一个最重要的功能就是仿真,快速地帮你定位程序BUG,不过要配合ST-Link或者其他仿真器用。 一般51我是用C51V9.0的,STM32我是用Keil4.72...
嵌入式开发,特别是单片机os-less的程序,最易范的错误是全局变量满天飞。 这个现象在早期汇编转型过来的程序员以及初学者中常见,这帮家伙几乎把全局变量当作函数形参来用。 在.h文档里面定义许多杂乱的结构体,extern一堆令人头皮发麻的全局变量,然后再这个模块里边赋值123,那个模块里边判断123分支决定做什么。 每当看到这种程序,我总要戚眉变脸而后拍桌怒喝。没错,就是怒喝。 不否认全局变量的重要性,但我认为要十分谨慎地使用它,滥用全局变量会带来其它更为严重的结构性系统问题。 为什么全局变量要越少越好? 它会造成不必要的常量频繁使用,特别当这个常量没有用宏定义“正名”时,代码阅读起来将万分吃力。 它会导致软件分层的不合理,全局变量相当于一条快捷通道,它容易使程序员模糊了“设备层”和“应用层”之间的边界。写出来的底层程序容易自作多情地关注起上层的应用。 这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往bug一堆,处处“补丁”,雷区遍布。说是度日如年举步维艰也不为过。 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块, 而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个“泥潭”,注释的唯一作用只是使泥潭上方再加一些迷烟瘴气。 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。 这个时候如果处理不当,系统的bug就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。 无需多言,您已经成功得到一个畸形的系统,它处于一个神秘的稳定状态! 你看着这台机器,机器也看着你,相对无言,心中发毛。你不确定它什么时候会崩溃,也不晓得下一次投诉什么时候道理。 全局变量大量使用有什么后果? “老人”气昂昂,因为系统离不开他,所有“雷区”只有他了然于心。当出现紧急的bug时,只有他能够搞定。你不但不能辞退他,还要给他加薪。 新人见光死,但凡招聘来维护这个系统的,除了改出更多的bug外,基本上一个月内就走人,到了外面还宣扬这个公司的软件质量有够差够烂。 随着产品的后续升级,几个月没有接触这个系统的原创者会发现,很多雷区他本人也忘记了,于是每次的产品升级维护周期越来越长, 因为修改一个功能会冒出很多bug,而按下一个bug,会弹出其他更多的bug。在这期间,又会产生更多的全局变量。 终于有一天他告诉老板,不行啦不行啦,资源不够了,ram或者flash空间太小了,升级升级。 客户投诉不断,售后也快崩溃了,业务员也不敢推荐此产品了,市场份额越来越小,公司形象越来越糟糕。 要问对策,只有两个原则 能不用全局变量尽量不用,我想除了系统状态和控制参数、通信处理和一些需要效率的模块,其他的基本可以靠合理的软件分层和编程技巧来解决。 如果不可避免需要用到,那能藏多深就藏多深。 如果只有某.c文件用,就static到该文件中,顺便把结构体定义也收进来; 如果只有一个函数用,那就static到函数里面去; 如果非要开放出去让人读取,那就用函数return出去,这样就是只读属性了; 如果非要遭人蹂躏赋值,好吧,我开放函数接口让你传参赋值; 实在非要extern侵犯我,我还可以严格控制包含我.h档的对象,而不是放到公共的includes.h中被人围观,丢人现眼。 如此,你可明白我对全局变量的感悟有多深刻,悲催的我,已经把当年那些“老人”交给我维护的那些案子加班全部重新翻写了。 最后补充 全局变量是不可避免要用到的,每一个设备底层几乎都需要它来记录当前状态,控制时序,起承转合。但是尽量不要用来传递参数,这个很忌讳的。 尽量把变量的作用范围控制在使用它的模块里面,如果其他模块要访问,就开个读或写函数接口出来,严格控制访问范围。 这一点,C++的private属性就是这么干的,这对将来程序的调试也很有好处。 C语言之所以有++版本,很大原因就是为了控制它的灵活性,要说面向对象的思想,C语言早已有之,亦可实现。 当一个模块里面的全局变量超过3个(含)时,就用结构体包起来吧,要归0便一起归0,省得丢三落四的。 在函数里面开个静态的全局变量,全局数组,是不占用栈空间的,只是有些编译器对于大块的全局数组,会放到和一般变量不同的地址区。 若是在keil C51,因为是静态编译,栈爆掉了会报警,所以大可以尽情驰骋,注意交通规则就是了。 单片机的os-less系统中,只有栈没有堆的用法,那些默认对堆分配空间的“startup.s”,可以大胆的把堆空间干掉。 程序模型?如何分析抽象出来呢,从哪个角度进行模型构建呢?很愿意聆听网友的意见。 本人一直以来都是从两个角度分析系统,事件--状态机迁移图 和 数据流图,前者分析控制流向,完善UI,后者可知晓系统数据的缘起缘灭。 这些理论,院校的《软件工程》教材都有,大家不妨借鉴下。只不过那些理论,终究是起源于大型系统软件管理的,牛刀杀鸡,还是要裁剪一下的。
AT89C51是40针微控制器,属于8051系列微控制器。它有四个端口,每个端口有8位P0,P1,P2和P3。AT89C51具有4K字节的可编程闪存。端口P0覆盖引脚32至引脚39,端口P1覆盖引脚1至引脚8,端口P2覆盖引脚21至引脚28,端口P...
对于pic单片机的学习,很多朋友总是能充满激情,不断利用闲余时间研究pic单片机的各类技术。而谈及pic单片机,必须牵扯至51、AVR单片机。因此本文中,将探讨pic单片机以及51、AVR单片机对于IO口的操作。对于本文,希...
单片机编程软件是单片机编程不可或缺的利器,一款好的单片机编程软件更能极大程度提高开发效率。在本文中,主要为大家介绍IAR单片机编程软件的菜单栏,以帮助大家更好了解这款单片机编程软件。 Ⅰ、写在前面 IAR软件...
单片机编程软件数量不多,Keil和IAR为当前主流的单片机编程软件。对于每门单片机编程软件的学习,总需耗费一定必要的时间。为最大化减少大家对单片机编程软件学习时间的投入,本文特地带来IAR单片机编程软件相关教程...
好的单片机编程软件受到众多开发人员青睐,而对单片机编程软件了解较多的朋友都知道,目前市场上主要流通的单片机编程软件为Keil和IAR。本文中,主要为大家讲解IAR单片机编程软件的基础教程。如果你对IAR存在一定兴...
方法一: 直接使用Keil自带的fromelf 工具 比如用命令行根据axf,生成bin: "D:\Program Files\Keil\ARM\ARMCC\bin\fromelf" --bin --output ./Objects/Led_Reg.bin ./Objects/Led_Reg.axf 或者在编译器配置中添加: fromelf --bin --output ./Objects/Led_Reg.bin ./Objects/Led_Reg.axf 如果需要srce文件,方法也是类似: "D:\Program Files\Keil\ARM\ARMCC\bin\fromelf" --bin --output ./Objects/Led_Reg.srec ./Objects/Led_Reg.axf 方法二: 使用专用的工具,很多工具都支持不同格式的转换。 比如Hex2bin,可以将hex转换为bin 使用Hex2bin-2.5软件,只需将需要转换的hex文件,拖动到这个小软件上面就会生产所需的bin文件。 生产的bin文件与hex文件在同一个路径下,注意路径不要有中文。 https://sourceforge.net/projects/hex2bin