单片机

↓↓ 抽奖方式详见文末 ↓↓

与郭天祥老师一起学习GD32F303系列主流型MCU开发实践

15年前,《十天学会单片机》系列视频课程如同一把火炬,点燃了整个单片机教育行业。它颠覆了传统的单片机教学风格,并配套开发板,至今累计下载量数千万次,已成为当今单片机入门普及不可或缺的一套优秀的视频教程。作为视频制作者的他,也成为了无数单片机爱好者的启蒙老师。

2009年,他又编著了《新概念51单片机C语言教程》,书籍上市后很快成为业内知名的教材,至今累计印刷30余次,销量达50多万册,连续多年销量排行同类书籍榜首,目前已经成为诸多高校的学生教材。

他就是大名鼎鼎的郭天祥,被电子爱好者们亲切地称为“郭老师”。无论是自学成才的单片机爱好者,或是在校学习电子专业的大学生,都通过这套视频和教材深受裨益。

“单片机大神郭天祥老师"
单片机大神郭天祥老师

❖ 2007年出版《十天学会单片机》系列视频教程

❖ 2009年编著《新概念51单片机C语言教程》

❖ 2012年创办北京海克智动科技开发有限公司

❖ 2020年开通"郭天祥老师"视频号讲解电子知识

❖ 2021年携手兆易创新GD32开展Arm MCU讲座

行业深耕廿余载

郭天祥老师深刻体会到,创作和分享优质知识内容是情怀更创造价值。知识口耳相授、长久流传,既可以帮助他人,也能够提升自我。怀抱着为电子行业持续培养人才的信念,以”强我强国,科技兴国“为己任,郭天祥老师自2020年12月在各视频平台开通"郭天祥老师"账号,深入浅出地讲解电子知识点,短短1年多时间,已汇聚了100多万名电子工程师和学生订阅收藏。

“”

MCU风潮来袭

近年来受到疫情和国际贸易争端的影响,电子元器件缺货、涨价的新闻层出不穷。国产化替代和供应保障已成为大势所趋,眼下正是产品开发导入的黄金窗口期。郭天祥老师在视频中谈到的联合中国领先的MCU厂商,重点推广中国自主的MCU嵌入式技术和应用。现已正式揭开谜底!

郭天祥老师与中国本土MCU“头把交椅”兆易创新GigaDevice合作,重磅推出《GD32F303系列MCU培训课程》,基于GD32F303系列讲授Arm MCU的开发设计和应用实践。由郭天祥老师和《十天学会单片机》系列视频时期的创业伙伴叶大鹏老师共同授课。为广大行业用户、嵌入式工程师、电子发烧友打造时下最受欢迎的优质内容,分享给大家在课本上学不到,但恰恰又是工作中经常用到的“葵花宝典”。

此番与兆易创新的强强联合,郭天祥老师正是看中了GD32 MCU庞大的用户群体和杰出的市场口碑。无论是丰富完善的MCU产品家族,还是已经得到多年验证的行业认可,都能为郭老师的课程提供施展和发挥的平台。兆易创新也相信通过郭老师标志性接地气的讲课风格,能让更多的行业从业者,真正实现“踏实学芯片、上手做项目”,培养出更多满足当下社会和企业的刚需人才。

“单片机大神郭天祥老师携手兆易创新GD32重出江湖,掀起MCU学习风暴!"

北京兆易创新科技股份有限公司(GigaDevice)成立于2005年,总部设于中国北京,并于2016年8月在上海证券交易所主板上市,目前拥有超过1300名员工,是一家致力于开发先进的存储器技术、MCU和传感器解决方案的领先半导体公司。

作为兆易创新的核心产品线之一,GD32 MCU是中国高性能通用微控制器领域的领跑者,中国最大的Arm MCU家族,也是中国首个Arm Cortex-M3/M4/M23/M33 MCU产品系列,并且在全球范围内首个推出RISC-V内核通用32位MCU产品系列。产品通过长期市场检验,连续六年在中国32位MCU市场位列本土厂商第一。以累计超过10亿颗的出货数量,超过2万家客户数量,35个系列近400余款型号选择所提供的广阔应用覆盖率稳居中国本土首位。现已成为系统设计与项目开发的创新首选。

GD32F303系列主流型MCU

F3系列是兆易创新于2017年推出的基于Arm Cortex-M4内核的经典主流型MCU产品,以优异的市场表现屡获殊荣。今年更推出多个全新型号,覆盖更全更广,更添供货保障!

“单片机大神郭天祥老师携手兆易创新GD32重出江湖,掀起MCU学习风暴!"

当下郭老师以GD32F303系列MCU为主题的Arm培训课程为系统开发工程师,电子爱好者奉上一场深入浅出的技术盛宴,助力单片机行业蓬勃发展和国产化替代的殷切需求。

GD32F303系列MCU视频课程内容

∎ 兆易创新公司及产品线介绍

∎ GD32F303芯片整体介绍及开发环境讲解

∎ 时钟、GPIO、中断子系统解读与实验

∎ 串口、ADC子系统解读与实验

∎ 定时器子系统解读与实验

∎ I2C、SPI串行总线解读与实验

∎ SDIO子系统解读与实验

∎ CAN子系统解读与实验

∎ USBD子系统解读与实验

∎ 行业应用案例介绍

3/10(周四)至4/28(周四)晚上8点起

每周两期(周二、周四)

“单片机大神郭天祥老师携手兆易创新GD32重出江湖,掀起MCU学习风暴!"

在西瓜、抖音、头条、知乎、B站、好看、视频号、YouTube、PN学堂自有平台等主流视频平台同时播出,在各平台搜索“郭天祥老师”关注即可查阅学习。

此外,GD32MCU微信视频号和技术网站也将同步收录系列培训课程,共同为广大用户提供优质的MCU原创视频知识库。

“单片机大神郭天祥老师携手兆易创新GD32重出江湖,掀起MCU学习风暴!"

收看GD32F303系列MCU培训视频并积极参与互动的小伙伴,还有机会获赠与本次课程配套的GD32F303E-EVAL全功能评估板以及其它精美礼品,参见上图,即刻添加微信“pnxuetang”加入群组参与幸运抽奖!

来源:电子创新网张国斌
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 227

公共场所的喷泉一般只是将音乐和喷泉高低简单配合, 或者是采用了高成本复杂的控制系统,搭建复杂的外围电路实现功能;并且多数只能在现场观赏,不能进入家庭。

本文主要介绍基于海速芯TM52F1376单片机控制的小型室内移动式音乐喷泉。它使用了较少的外围器件和较为简单的电路设计,成本低、体积小、水型变换多样,实用性强,适合室内观赏。

系统设计

该系统采集音乐信号,根据音乐信号的强弱来控制水泵电机的转速以及LED彩灯的亮灭。系统的总体结构由音乐输入部分、音响放大部分、单片机控制部分和输出控制部分组成。TM52F1376单片机作为系统的主芯片,一方面采集音乐信号,另一方面依据采集到的音乐信号的强弱输出延时不等的矩形波来控制可控硅的导通时间,进而控制水泵电机的转速,从而达到控制喷水高度的目的。彩灯的亮灭也由单片机依据音乐采样值的大小来控制。

系统硬件设计

硬件系统由单片机电路、音频电路、水泵控制电路、彩灯控制电路、电源电路等组成。

单片机电路

单片机要采集音乐信号,并据此调节I/O口的输出来控制水泵和彩灯。主芯片TM52F1376是8051架构的通用微控制器,片内集成多种功能,包括8K/16K字节的Flash程序存储器,128字节的EEPROM数据存储器,512字节SRAM,低电压复位(LVR),低电压检测(LVD),双时钟省电工作模式,8051标准UART和定时器Timer0/Timer1/Timer2,实时计时器Timer3,LCD/LED驱动器,3组16位脉冲宽度调制器,24通道的12位模数转换器(ADC),I2C串口和看门狗定时器(WDT)等等。

这样不仅可以简化单片机的外围电路,而且处理速度和灵活度都大大增强,并且具有片内调试电路,设计调试周期短。

“封装类型:LQFP32/SOP28/SSOP28/QFN28/SSOP24/SOP20/TSSOP20/QFN20/SOP16"
封装类型:LQFP32/SOP28/SSOP28/QFN28/SSOP24/SOP20/TSSOP20/QFN20/SOP16

总体来讲,设计的喷泉控制系统可基于TM52F1376单片机,采用音频放大、可控硅控制等简洁的外围电路,经过焊接、组装、调试后,很好地实现控制功能,具有很强的实用性,尤其是具有体积小、易移动、适合家庭和室内使用的特点。本方案也可以在功能上加以扩展,如加上对乐曲的频域分析,结合频域特点控制水泵;还可以制作雾化器来渲染效果等。

来源:国芯思辰
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 44

在平常的项目开发过程中,会遇到各种各样的问题,下面分享一篇文章,是常见问题的解决思路和解决办法。

1、问题复现

稳定复现问题才能正确的对问题进行定位、解决以及验证。一般来说,越容易复现的问题越容易解决。

1.1 模拟复现条件

有的问题存在于特定的条件下,只需要模拟出现问题的条件即可复现。对于依赖外部输入的条件,如果条件比较复杂难以模拟可以考虑程序里预设直接进入对应状态。

1.2 提高相关任务执行频率

例如某个任务长时间运行才出现异常则可以提高该任务的执行频率。

1.3 增大测试样本量

程序长时间运行后出现异常,问题难以复现,可以搭建测试环境多套设备同时进行测试。

2、问题定位

缩小排查范围,确认引入问题的任务、函数、语句。

2.1 打印LOG

根据问题的现象,在抱有疑问的代码处增加LOG输出,以此来追踪程序执行流程以及关键变量的值,观察是否与预期相符。

2.2 在线调试

在线调试可以起到和打印LOG类似的作用,另外此方法特别适合排查程序崩溃类的BUG,当程序陷入异常中断(HardFault,看门狗中断等)的时候可以直接STOP查看call stack以及内核寄存器的值,快速定位问题点。

2.3 版本回退

使用版本管理工具时可以通过不断回退版本并测试验证来定位首次引入该问题的版本,之后可以围绕该版本增改的代码进行排查。

2.4 二分注释

二分注释即以类似二分查找法的方式注释掉部分代码,以此判断问题是否由注释掉的这部分代码引起。
具体方法为将与问题不相干的部分代码注释掉一半,看问题是否解决,未解决则注释另一半,如果解决则继续将注释范围缩小一半,以此类推逐渐缩小问题的范围。

2.5 保存内核寄存器快照

Cortex M内核陷入异常中断时会将几个内核寄存器的值压入栈中,如下图:

“30个单片机常见问题解决办法!"

我们可以在陷入异常中断时将栈上的内核寄存器值写入RAM的一段复位后保留默认值的区域内,执行复位操作后再从RAM将该信息读出并分析,通过PC、LR确认当时执行的函数,通过R0-R3分析当时处理的变量是否异常,通过SP分析是否可能出现栈溢出等。

3、问题分析处理

结合问题现象以及定位的问题代码位置分析造成问题的原因。

3.1 程序继续运行

3.1.1 数值异常

3.1.1.1 软件问题

1、数组越界

写数组时下标超出数组长度,导致对应地址内容被修改。

如下:

“30个单片机常见问题解决办法!"

此类问题通常需要结合map文件进行分析,通过map文件观察被篡改变量地址附近的数组,查看对该数组的写入操作是否存在如上图所示不安全的代码,将其修改为安全的代码。

2、栈溢出

0x20001ff8 g_val
0x20002000 栈底
………… 栈空间
0x20002200 栈顶

如上图,此类问题也需要结合map文件进行分析。假设栈从高地址往低地址增长,如果发生栈溢出,则g_val的值会被栈上的值覆盖。

出现栈溢出时要分析栈的最大使用情况,函数调用层数过多,中断服务函数内进行函数调用,函数内部申明了较大的临时变量等都有可能导致栈溢出。

解决此类问题有以下方法:

  • 在设计阶段应该合理分配内存资源,为栈设置合适的大小;
  • 将函数内较大的临时变量加”static”关键字转化为静态变量,或者使用malloc()动态分配,将其放到堆上;
  • 改变函数调用方式,降低调用层数。

3、判断语句条件写错

“30个单片机常见问题解决办法!"

判断语句的条件容易把相等运算符“==”写成赋值运算符“=”导致被判断的变量值被更改,该类错误编译期不会报错且总是返回真。

建议将要判断的变量写到运算符的右边,这样错写为赋值运算符时会在编译期报错。还可以使用一些静态代码检查工具来发现此类问题。

4、同步问题

例如操作队列时,出队操作执行的过程中发生中断(任务切换),并且在中断(切换后的任务)中执行入队操作则可能破坏队列结构,对于这类情况应该操作时关中断(使用互斥锁同步)。

5、优化问题

“30个单片机常见问题解决办法!"

如上图程序,本意是等待irq中断之后不再执行foo()函数,但被编译器优化之后,实际运行过程中flg可能被装入寄存器并且每次都判断寄存器内的值而不重新从ram里读取flg的值,导致即使irq中断发生foo()也一直运行,此处需要在flg的申明前加“volatile”关键字,强制每次都从ram里获取flg的值。

3.1.1.2 硬件问题

1、芯片BUG

芯片本身存在BUG,在某些特定情况下给单片机返回一个错误的值,需要程序对读回的值进行判断,过滤异常值。

2、通信时序错误

“30个单片机常见问题解决办法!"

例如电源管理芯片Isl78600,假设现在两片级联,当同时读取两片的电压采样数据时,高端芯片会以固定周期通过菊花链将数据传送到低端芯片,而低端芯片上只有一个缓存区.

如果单片机不在规定时间内将低端芯片上的数据读走那么新的数据到来时将会覆盖当前数据,导致数据丢失。此类问题需要仔细分析芯片的数据手册,严格满足芯片通信的时序要求。

3.1.2 动作异常

3.1.2.1 软件问题

1、设计问题

设计中存在错误或者疏漏,需要重新评审设计文档。

2、实现与设计不符

代码的实现与设计文档不相符需要增加单元测试覆盖所有条件分支,进行代码交叉review。

3、状态变量异常

例如记录状态机当前状态的变量被篡改,分析该类问题的方法同前文数值异常部分。

3.1.2.2 硬件问题

1、硬件失效

目标IC失效,接收控制指令后不动作,需要排查硬件。

2、通信异常

与目标IC通信错误,无法正确执行控制命令,需要使用示波器或逻辑分析仪去观察通信时序,分析是否发出的信号不对或者受到外部干扰。

3.2 程序崩溃

3.2.1 停止运行

3.2.1.1 软件问题

1、HardFault

以下情况会造成HardFault:

  • 在外设时钟门未使能的情况下操作该外设的寄存器;
  • 跳转函数地址越界,通常发生在函数指针被篡改,排查方法同数值异常;
  • 解引用指针时出现对齐问题:

以小端序为例,如果我们声明了一个强制对齐的结构体如下:

“30个单片机常见问题解决办法!"

“30个单片机常见问题解决办法!"

此时a.val1的地址为0x00000001,如果以uint16_t类型去解引用此地址则会因为对齐问题进入HardFault,如果一定要用指针方式操作该变量则应当使用memcpy()。

2、中断服务函数中未清除中断标志

中断服务函数退出前不正确清除中断标志,当程序执行从中断服务函数内退出后又会立刻进入中断服务函数,表现出程序的“假死”现象。

3、NMI中断

调试时曾遇到SPI的MISO引脚复用NMI功能,当通过SPI连接的外设损坏时MISO被拉高,导致单片机复位后在把NMI引脚配置成SPI功能之前就直接进入NMI中断,程序挂死在NMI中断中。这种情况可以在NMI的中断服务函数内禁用NMI功能来使其退出NMI中断。

3.2.1.2 硬件问题

① 晶振未起振
② 供电电压不足
③ 复位引脚拉低

3.2.2 复位

3.2.2.1 软件问题

看门狗复位

除了喂狗超时导致的复位以外,还要注意看门狗配置的特殊要求,以Freescale KEA单片机为例,该单片机看门狗在配置时需要执行解锁序列(向其寄存器连续写入两个不同的值),该解锁序列必须在16个总线时钟内完成,超时则会引起看门狗复位。此类问题只能熟读单片机数据手册,注意类似的细节问题。

3.2.2.2 硬件问题

① 供电电压不稳
② 电源带载能力不足

4、回归测试

问题解决后需要进行回归测试,一方面确认问题是否不再复现,另一方面要确认修改不会引入其他问题。

5、经验总结

总结本次问题产生的原因及解决问题的方法,思考类似问题今后如何防范,对相同平台产品是否值得借鉴,做到举一反三,从失败中吸取经验。

原文:https://www.cnblogs.com/jozochen/p/8541714.html

来源:小麦大叔
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 36

智能电表和HVAC控制器等连接到网络的设备不仅需要具备实时性能,以同时实现复杂的系统执行和网络通信。还需要定期更新固件(FW),以确保持续稳定的应用。

也就是说,负责设备主控的单片机也需要上述性能和功能。那么您是否希望拥有一款在单个芯片中实现这些特性的单片机,以及一款能够对其进行轻松评测的套件呢?

今天,我为大家介绍的就是能够满足上述要求的32位单片机RX66N,以及能够轻松快速地进行评测和PoC试制的评测套件Target board for RX66N。

RX66N卓越的实时性能完全得益于它搭载的CPU和闪存。由RX系列独有RXv3内核组成的CPU在120MHz的主频下可提供707CoreMark的卓越性能,而4MB闪存能够确保CPU以零等待周期运行,因此即使运行的是闪存中存储的代码,也能够保持较高的处理速度。

“32位单片机RX66N帮您解决联网设备面临的难题"

另外,4MB闪存可以实现方便固件更新的双存储区配置,再配合后台操作功能,在系统运行过程中也可以将更新固件下载到闪存中。

“32位单片机RX66N帮您解决联网设备面临的难题"

此外,RX66N还配备有Target board for RX66N套件,可对上述特性进行轻松评测。该套件搭载有仿真电路,只需使用USB线与PC连接即可轻松评测RX66N卓越的实时性能。并且该套件通过将Wi-Fi模块连接到板载Pmod连接器,可以实现基于实际场景中RTOS操作的云连接,以及构建基于网络的固件更新系统。

“32位单片机RX66N帮您解决联网设备面临的难题"

利用针对RX系列的云解决方案以及固件更新用中间件*和样例程序,还可简单快捷地进行评测和PoC试制。

*点击链接查看:RX产品家族系列技术干货分享(八)-介绍可以轻松实现RX系列FOTA(Firmware Update Over-The-Air)固件更新的中间件

如果您对RX66N Target Board感兴趣,并希望体验卓越的实时性能以及可轻松实现固件更新的闪存,那么RX66N绝对是您的不二选择。在下次的相关文章中,我将实际使用RX66N Target Board来为大家展示它的固件更新功能。敬请期待!

来源:瑞萨电子
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 24

嵌入式编程的需求千变万化,要做到系统稳定,又要代码可复用,就要做到高内聚低耦合。

前言

我们通常认为,在中断中,不能执行耗时的操作,否则会影响系统的稳定性,尤其对于嵌入式编程。对于带操作系统的程序而言,可以通过操作系统的调度,将中断处理分成两个部分,耗时的操作可以放到线程中去执行,但是对于没有操作系统的情况,又应该如何处理呢

比较常见的,我们可能会定义一些全局变量,作为flag,然后在mainloop中不停的判断这些flag,再在中断中修改这些flag,最后在mainloop中执行具体的逻辑,但是这样,无疑会增加耦合,增加程序维护成本。

cpost

cpost正是应用在这种情况下的一个简单但又十分方便的工具,它可以特别方便的进行上下文的切换,减少模块耦合。

cpost链接:

https://github.com/NevermindZZT/cpost

cpost借鉴的Android的handler机制,通过在mainloop中跑一个任务,然后在其他地方,可以是中断,也可以是模块逻辑中,直接抛出需要执行的函数,使其脱离调用处的上下文,运行在mainloop中。cpost还支持延迟处理,可以指定函数在抛出后多久执行

使用

cpost的使用十分简单,这里以使用在嵌入式无操作系统中为例,主要用作中断延迟处理的情况

1、配置系统tick

配置cpost.h中的宏CPOST_GET_TICK(),配置成获取系统tick,以stm32 hal为例

#define     CPOST_GET_TICK()            HAL_GetTick()

2、配置处理进程

在mainloop调用cpostProcess函数

int main(void)
{
    ...
    while (1)
    {
        cpostProcess();
    }
    return 0;
}

3、抛出任务

在中断等需要进行上下文切换的地方调用cpsot接口,使其在mainloop中运行

cpost(intHandler);

原理解析

cpost的原理其实很简单,其代码量也十分少,总共加起来就只有几十行代码,cpost维护了一个而全局的数组

CpostHandler cposhHandlers[CPOST_MAX_HANDLER_SIZE] = {0};

其中,数组的每一个元素表示包含了需要执行的函数和参数,当调用cpost接口时,被post的函数和参数会被保存在这个数组中,然后mainloop中运行的cpostProcess函数会遍历这个数组,当满足条件时,执行对应的函数,从而达到上下文切换的目的

void cpostProcess(void)
{
    for (size_t i = 0; i < CPOST_MAX_HANDLER_SIZE; i++)
    {
        if (cposhHandlers[i].handler)
        {
            if (cposhHandlers[i].time == 0 || CPOST_GET_TICK() >= cposhHandlers[i].time)
            {
                cposhHandlers[i].handler(cposhHandlers[i].param);
                cposhHandlers[i].handler = NULL;
            }
        }
    }
}

其实,cpost的方式,和一开始提到的使用全局的flag进行上下文切换的方法很像,只不过,cpost通过一个数组的维护和直接post函数的方式,省去了维护flag的成本,也不需要将需要执行的函数耦合到mianloop中,从而变得简单易用。

完美解耦 - cevent应用

对于模块化编程来说,如何实现各模块间的解耦一直是一个比较令人头疼的问题,特别是对于嵌入式编程,由于控制逻辑复杂,并且对程序体积有控制,经常容易写出各独立模块之间相互调用的问题。由此,cpost中的cevent组件,通过模仿Android系统中的广播机制,提供了一种非常简单的模块间解耦实现。

原理

cevent借鉴的是Android系统的广播机制,一方面,各模块在工作的时候,都会有多个具体的事件点,在高耦合的编程中,可能会在这些地方调用其他模块的功能,比如说,在通信模块接收到指令的时候,需要闪烁一下指示灯。

使用cevent,我们可以在这些地方抛出一个事件,当前模块不需要关心在这各地方需要执行哪些其他模块的逻辑,由其他模块,或者用户定义一个事件监听,当具体的事件发生时,执行相应的动作。

使用

cevent使用注册的方式监听事件,会依赖于编译环境,目前支持keil,iar,和gcc,对于gcc,需要修改链接文件(.ld),在只读数据区添加:

_cevent_start = .;
KEEP (*(cEvent))
_cevent_end = .;

1、初始化cevent

系统初始化时,调用ceventInit

ceventInit();

2、注册cevent事件监听

在c文件中,调用CEVENT_EXPORT导出事件监听

CEVENT_EXPORT(0, handler, (void *)param);

3、发送cevent事件

在事件发生的地方,调用ceventPost抛出事件

ceventPost(0);

使用cevent解耦模块初始化

嵌入式编程中,我们习惯会在程序启动的时候,调用各个模块的初始化函数,其实这也是一种耦合,会造成main函数中出现很长的初始化代码,借助cevent,我们可以对初始化进行优化解耦。

1、定义初始化事件

定义初始化事件的值,对于初始化,有些模块可能会依赖于其他模块的初始化,会有一个先后顺序要求,所以这里我们可以把初始化分成两个阶段,定义两个事件,当然,如果有更复杂的要求,可以再多分几个阶段,只需要多定义几个事件就行

#define     EVENT_INIT_STAGE1       0
#define     EVENT_INIT_STAGE2       1

2、初始化cevent,抛出事件

在main函数中初始化cevent,并抛出初始化事件

int main(void)
{
    ...
    ceventInit();

    ceventPost(EVENT_INIT_STAGE1);
    ceventPost(EVENT_INIT_STAGE2);
    ...
    return 0;
}

3、注册事件监听

对所有需要初始化的函数注册事件监听,这里我以对letter-shell注册事件监听为例,分为两个部分,初始化串口和初始化shell。

在serial模块中,将串口初始化注册到初始化第一阶段,cevent支持将不大于7个的参数直接传递到注册的监听函数中,下面的注册方式,相当于在EVENT_INIT_STAGE1事件发生的地方,也就是main函数中对应的位置,调用serialInit(&debugSerial)

CEVENT_EXPORT(EVENT_INIT_STAGE1, serialInit, (void *)(&debugSerial));

然后再shell模块中,将shell初始化函数注册到初始化第二阶段。

CEVENT_EXPORT(EVENT_INIT_STAGE1, shellInit);

使用cevent解耦mainloop

再无操作系统的嵌入式编程中,我们如果同时希望运行多个模块的逻辑,通常是在mainloop中循环调用,这种将函数写入mainloop的做法,也会增加耦合

int main(void)
{
    ...

    while (1)
    {
        // 写在mainloop中的模块逻辑
        shellTask(&shell);
        LedProcess();
        ...
    }
    return 0;
}

通过使用cevent,也可以很方便的消除这种耦合

1、定义mainloop事件

定义mainloop事件的值

#define     EVENT_MAIN_LOOP         3

2、在mainloop中抛出事件

去掉mainloop中对其他模块的调用,改为排除mainloop事件

int main(void)
{
    ...

    while (1)
    {
        ceventPost(EVENT_MAIN_LOOP);
    }
    return 0;
}

3、在各模块中注册事件监听

分别在各个模块中,注册对mainloop事件的监听

CEVENT_EXPORT(EVENT_MAIN_LOOP, shellTask, (void *)(&shell));
CEVENT_EXPORT(EVENT_MAIN_LOOP, LedProcess);

结语

cevent是一个非常小的模块,本身代码及其简单,但是,通过模仿广播机制,让cevent可以发挥很强大的功能,通过,还可以结合cpost,实现延迟事件等功能。

来源:
https://blog.csdn.net/qq_34245464/article/details/111804661

围观 49

瑞萨电子微控制器产品重点系列

“瑞萨电子单片机一览"
  • 为满足客户不断增长的需求,瑞萨电子推出的微控制器产品,可让客户充分利用现有资源,同时提供出色的可扩展性。

  • 瑞萨微控制器提供多种存储器和封装选项,具有运行快速、可靠性高、低成本和环保的特性。

  • 融合最新工艺技术、集成大容量闪存、应用范围广泛,包括需要高质量和高可靠性的要求严苛应用领域。

  • 配备强大的支持系统,以帮助减少研发成本,缩短研发时间。包含各种开发工具,包括来自其他公司的产品,并受到广泛的技术文档、软件库和活跃用户社区的支持。作为全球位列前茅的MCU供应商,瑞萨在各种微控制器 (MCU) 的基础上,带来最出色、最强大的解决方案。

RL78系列-真正的低功耗微控制器

“RL78:为下一个十年提供动力"
RL78:为下一个十年提供动力

RL78 8/16位微控制器(MCU)具有业界领先的低功耗特点,正常工作期间功耗可低至41μA/MHz(RL78/G23),时钟工作期间为0.355μA,从而大大提高了电源效率。高精度(±1%)高速片上振荡器、可重写 100 万次的后台操作数据闪存、温度传感器和用于多个电源的接口端口等内置功能有助于降低系统成本和尺寸。

RX系列:性能市场领导者

“瑞萨电子单片机一览"

RX系列由四个产品子系列组成, 可提供从小规模到大规模应用的无缝可扩展性。

  • 旗舰RX700系列,具有最快的性能和最先进的功能。

  • 标准RX600系列和RX200系列,在功率效率和高性能之间实现了最佳平衡。

  • 入门级RX100系列,具有极低的功耗。

RA系列:引领物联网革命

“瑞萨电子单片机一览"
  • 瑞萨电子(RA)32位微控制器(MCU)是业界领先的32位MCU,具有Arm® Cortex®-M33、-M23、-M4处理器内核和PSA认证。

  • 与竞争对手Arm Cortex-M MCU相比,RA通过提供更强的嵌入式安全性、卓越的CoreMark® 性能和超低功耗操作,进而使得自身具有关键优势。PSA认证为客户提供了快速部署安全物联网端点和边缘设备以及面向工业4.0智能工厂设备的信心和保证。

  • RA系列MCU的独特之处在于极低功耗、一流的安全选项,包括Arm TrustZone®技术以及支持所有RA系列产品的瑞萨电子灵活软件程序(FSP)。

  • FSP包括高效的驱动程序和中间件,以简化通信和安全性的实施。FSP的GUI简化并加速了开发过程。它支持灵活使用旧代码,并可与其他RA系列设备轻松兼容和扩展。使用FSP的设计人员还可以访问广泛的Arm生态系统、提供广泛的工具、帮助加快上市时间以及瑞萨电子广泛的合作伙伴网络。

RA:灵活的软件包 (FSP),支持安全设备和物联网连接

生产就绪型外设驱动程序

- 用于访问MCU外设和所需功能的HAL API,ARM 信任区已启用

- 直观的配置器和代码生成器

- 经过测试的单元和系统

- 使用行业标准工具进行静态和动态分析

使用实时操作系统

- 最新的Azure RTOS、FreeRTOS和Flexible软件包集成

- 工具可配置的RTOS资源(线程、互斥锁等)

- 包括裸机支持

连接

- 包括Azure RTOS NetX Duo&add-ons、FreeRTOS TCP/IP stack、Secure Sockets、Wi-Fi&BLE

- 包括MQTT和TLS

- 支持与所有主要云平台的连接

- 用于CDC、MSC、HID(主机和外设)的USB中间件

安全性

- 基于Arm Mbed PSA、NetX Crypto和FSP Crypto API的加密API

- 支持加密硬件加速

- PSA 2级认证

- 安全调试

Renesas MCU生态系统:以技术为导向的广泛应用

“瑞萨电子单片机一览"

瑞萨电子正在实现一个全面的合作伙伴生态系统,以提供一系列软件和硬件构建模块,这些构建模块将与瑞萨电子MCU一起开箱即用。瑞萨电子MCU生态系统将有助于加速物联网应用的开发,包括安全性、连接性和HMI等核心技术。

来源:瑞萨MCU小百科
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 142

QueueForMcu

基于单片机实现的队列功能模块,主要用于8位、16位、32位非运行RTOS的单片机应用,兼容大多数单片机平台。

开源代码:https://github.com/xiaoxinpro/QueueForMcu

一、特性

  • 动态创建队列对象
  • 动态设置队列数据缓冲区
  • 静态指定队列元素数据长度
  • 采用值传递的方式保存队列数据

二、快速使用

#include "queue.h"

#define Q_UART_BUFFER_SIZE  1024

QUEUE_HandleTypeDef qUartTx;
QUEUE_DATA_T BufferUartTx[Q_UART_BUFFER_SIZE];

int main(void)
{
  QUEUE_DATA_T temp;
  
  //初始化队列
  Queue_Init(&qUartTx, BufferUartTx, Q_UART_BUFFER_SIZE);
  
  while(1)
  {
    //入队
    Queue_Push(&qUartTx, 'Q');
    Queue_Push(&qUartTx, 'u');
    Queue_Push(&qUartTx, 'e');
    Queue_Push(&qUartTx, 'u');
    Queue_Push(&qUartTx, 'e');
    
    //出队
    Queue_Pop(&qUartTx, &temp);
    Queue_Pop(&qUartTx, &temp);
    Queue_Pop(&qUartTx, &temp);
    Queue_Pop(&qUartTx, &temp);
    Queue_Pop(&qUartTx, &temp);
  }
}

三、配置说明

目前QueueForMcu只有一个静态配置项,具体如下:

在文件 queue.h 中有一个宏定义 QUEUE_DATA_T 用于指定队列元素的数据长度,默认是 unsigned char ,可以根据需要更改为其他数据类型。

四、数据结构

队列的数据结构为 QUEUE_HandleTypeDef 用于保存队列的状态,源码如下:

typedef struct QUEUE_HandleTypeDef{
    unsigned int head;                      //队列头指针
    unsigned int tail;                      //队列尾指针
    unsigned int buffer_length;             //队列缓存长度(初始化时赋值)
    QUEUE_DATA_T * buffer;                  //队列缓存数组(初始化时赋值)
}QUEUE_HandleTypeDef;

其中 QUEUE_DATA_T 为配置项中自定义的数据类型。

五、创建队列

1、创建队列缓存

由于我们采用值传递的方式保存队列数据,因此我们在创建队列前要手动创建一个队列缓存区,用于存放队列数据。

QUEUE_DATA_T BufferUartTx[1024];

以上代码即创建一个大小为 1024 的队列缓存区。

2、创建队列结构

接下来使用 QUEUE_HandleTypeDef 创建队列结构,用于保存队列的状态:

QUEUE_HandleTypeDef qUartTx;

3、初始化队列

准备好队列缓存和队列结构后调用 Queue_Init 函数来创建队列,该函数原型如下:

void Queue_Init(QUEUE_HandleTypeDef * hqueue, QUEUE_DATA_T * buffer, unsigned int len)

参数说明:

参数名 描述
hqueue 需要初始化的队列结构,如果二次初始化将清空原队列的内容。
buffer 队列缓存的首地址指针
len 队列长度,不能比队列缓存长度还要大。

参考代码:

Queue_Init(&qUartTx, BufferUartTx, Q_UART_BUFFER_SIZE);

六、压入队列

1、单数据压入

将数据压入队列尾部使用 Queue_Push 函数,该函数原型如下:

QUEUE_StatusTypeDef Queue_Push(QUEUE_HandleTypeDef * hqueue, QUEUE_DATA_T data)

参数说明:

参数名 描述
hqueue 需要压入数据的队列结构。
data 待压入队列的数据。

返回值说明:

该函数会返回一个 QUEUE_StatusTypeDef 枚举数据类型,返回值会根据队列状态返回以下几个值:

返回值 描述
QUEUE_OK 数据压入队列成功。
QUEUE_OVERLOAD 未压入数据到队列中,原因队列已满。

参考代码:

Queue_Push(&qUartTx, 'Q');
Queue_Push(&qUartTx, 0x51);
Queue_Push(&qUartTx, 81);

2、多数据压入

若需要将多个数据(数组)压入队列可以使用 Queue_Push_Array 函数,原理上循环调用 Queue_Push 函数来实现的,函数原型如下:

unsigned int Queue_Push_Array(QUEUE_HandleTypeDef * hqueue, QUEUE_DATA_T * pdatas, unsigned int len)

参数说明:

参数名 描述
hqueue 需要压入数据的队列结构。
pdatas 待压入队列的数组首地址。
len 待压入队列的数组长度。

当数组长度大于队列剩余长度时,数组多余的数据将被忽略。

返回值说明:

  • 该函数将返回实际被压入到队列中的数据长度。

  • 当队列中的剩余长度富余时,返回值将等于参数 len 的值。

  • 当队列中的剩余长度不足时,返回值为实际被压入到队列的数据长度。

七、弹出队列

1、单数据弹出

将队列头部数据弹出队列使用 Queue_Pop 函数,需要注意的是,弹出的数据将从队列中删除,该函数原型如下:

QUEUE_StatusTypeDef Queue_Pop(QUEUE_HandleTypeDef * hqueue, QUEUE_DATA_T * pdata)

参数说明:

参数名 描述
hqueue 需要弹出数据的队列结构。
pdata 用于保存弹出数据变量的指针。

返回值说明:

该函数会返回一个 QUEUE_StatusTypeDef 枚举数据类型,返回值会根据队列状态返回以下几个值:

返回值 描述
QUEUE_OK 数据弹出队列成功。
QUEUE_VOID 未弹出数据到队列中,原因队列为空。

参考代码:

QUEUE_DATA_T temp;
if(QUEUE_OK = Queue_Pop(&qUartTx, &temp))
{
    // temp 为队列弹出的数据
}
else
{
    // 弹出数据失败
}

2、多数据弹出

若需要将多个数据弹出队列可以使用 Queue_Pop_Array 函数,原理上循环调用 Queue_Pop 函数来实现的,需要注意的是,成功弹出的数据将从队列中删除,函数原型如下:

unsigned int Queue_Pop_Array(QUEUE_HandleTypeDef * hqueue, QUEUE_DATA_T * pdatas, unsigned int len)

参数说明:

参数名 描述
hqueue 需要弹出数据的队列结构。
pdatas 用于保存弹出数据数组的首地址。
len 需要弹出数据数组的长度。

当需要弹出数据的长度大于队列中的数据长度时,弹出数组多余的空间将不会被赋值。

返回值说明:

  • 该函数将返回实际从队列中弹出的数据长度。

  • 当队列中的数据长度足够时,返回值将等于参数 len 的值。

  • 当队列中的数据长度不足时,返回值为实际从队列中弹出的数据长度。

3、单数据复制

当需要从队列头部获取数据,但又不希望数据从队列中删除时,可以使用 Queue_Peek 函数来实现,该函数的参数与返回值与 Queue_Pop 完全相同。

使用 Queue_Peek 和 Queue_Pop 函数的区别在于:

  • Queue_Pop 得到队列中的数据后会删除队列中的数据。
  • Queue_Peek 得到队列中的数据后会保留队列中的数据。

4、多数据复制

当需要从队列头部获取多个数据,但又不希望数据从队列中删除时,可以使用 Queue_Peek_Array 函数来实现,该函数的参数与返回值与 Queue_Pop_Array 完全相同。

使用 Queue_Peek_Array 和 Queue_Pop_Array 函数的区别在于:

  • Queue_Pop_Array 得到队列中的数据后会删除队列中的数据。
  • Queue_Peek_Array 得到队列中的数据后会保留队列中的数据。

八、其他功能

1、清空队列

当需要清空队列数据时,无需弹出所有数据,只需要调用 Queue_Clear 即可快速清空指定队列,在创建队列时会调用此函数来初始化队列,因此对于刚创建完成的队列无需调用清空队列函数。

函数原型:

void Queue_Clear(QUEUE_HandleTypeDef * hqueue)

参数说明:

参数名 描述
hqueue 需要清空的队列结构。

2、获取队列数据数量

当需要获取队列中的数据长度时,调用 Queue_Count 函数,函数原型如下:

unsigned int Queue_Count(QUEUE_HandleTypeDef * hqueue)

参数说明:

参数名 描述
hqueue 需要获取数据长度的队列结构。

返回值说明:

  • 该函数将返回队列中的数据长度。
  • 返回值范围在0到创建队列时的长度之间。

License

Copyright © 2020 QueueForMcu Released under the GPL-3.0 License.

来源:嵌入式大杂烩
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 45

在单片机系统里对模拟量的处理要比数字量稍显复杂,但是只要掌握了使用技巧,使用起来也很简单,很多朋友一开始比较纠结于单片机的底层语言,非要先弄个明白才罢休,其实大可不必,重要的是我们要先学会怎么应用。

现以铅酸电池电压检测及充电电流检测为例讲解模拟量的硬件和程序的设计。

如图1为28节铅酸电池的电压检测电路,1--14节组成电池组1,15--28节组成电池组2;第1节正极为BAT+,14与15节之间为BATM,第28节负极为BAT-。输入端的8个二极管的作用是钳位作用;电路计算如图所示。

“图1:电池组电压检测电路"
图1:电池组电压检测电路

如图2为铅酸电池的充电电流检测电路,TA1为工频电流互感器,输入的4个二极管为整流二极管,电流流过R37(510Ω)形成压差△V。电路计算如图所示。

“图2:电池组充电电流检测电路"
图2:电池组充电电流检测电路

如图3为单片机STM32F103CBT6,图1和图2的模拟信号输入至单片机的PA5、PA6、PA7。

“图3:STM32F103CBT6单片机"
图3:STM32F103CBT6单片机

由于代码较多,为便于浏览,我就把其中一部分以截图的形式展示

如图4为单片机adc.c文件的底层配置,把PA5、PA6、PA7端口配置成模拟输入模式。

“图4:配置端口模式"
图4:配置端口模式

如图5对以上三个模拟量进行模数转换并缓存入数组ADC_ConvertedValue[3],得到的AD值的范围是0~4096。

“图5:模数转换并缓存"
图5:模数转换并缓存

如图6把以上两个配置函数整合在一起,定义成模拟量的初始化函数void ADC1_Init(void)。

“图6:初始化"
图6:初始化

如图7在adc.h文件里声明函数void ADC1_Init(void),另外几个函数也在adc的c文件里定义的,后面附上源程序(非截图)。

“图7:声明函数"
图7:声明函数

如图8在main()主函数里调用ADC1_Init()初始化函数(要去掉void),初始化函数一定要放在while(1)的前面,表示在进入while(1)无限循环前只执行一次。Analog_Processing()为模拟量处理函数,要放在while(1)无限循环里面(该函数在下面讲)。

“图8:函数调用"
图8:函数调用

以下为模拟量在main.c文件里的定义。

s16 Charging_Current;		 //充电电流实际值
s16 Battery1_Voltage;		 //电池组1电压实际值
s16 Battery2_Voltage;		 //电池组2电压实际值
s16 Battery_Voltage; 		 //电池组总电压值

下面三个函数的定义都在adc.c文件里面定义的。

以下代码为模拟量处理函数:①对数组ADC_ConvertedValue[3]缓存值进行滤波处理;②对滤波后的AD值转换为实际值。

/******************************
模拟量处理函数
******************************/
void Analog_Processing(void)
{
//对AD值进行滤波
ADC_Charging_Current=Filter(ADC_ConvertedValue[0],ADC_Charging_Current,1,10);
ADC_Battery1_Voltage=Filter(ADC_ConvertedValue[1],ADC_Battery1_Voltage,1,10);
ADC_Battery2_Voltage=Filter(ADC_ConvertedValue[2],ADC_Battery2_Voltage,1,10);
//AD值转换为实际值
Charging_Current = Adc_To_Act(ADC_Charging_Current, 10, 4096, 0, 220);//22.0A
Battery1_Voltage = Adc_To_Act(ADC_Battery1_Voltage, 10, 4096, 0, 267);//267V
Battery2_Voltage = Adc_To_Act(ADC_Battery2_Voltage, 10, 4096, 0, 267);//267V
//两组电压相加得到总电压
Battery_Voltage = Battery1_Voltage + Battery2_Voltage;
}

以下代码为滤波函数,滤波函数有很多,采用合适的才是最实用的(该函数滤波后的值是连续变化的,有些滤波函数滤波后的值是跳变的)。

/******************************
滤波函数(base/k越大,容性越大)
该函数相当于是一个电容,通常取值k=1,base=10
******************************/
u16 Filter(u16 NewData, u16 OldData, u8 k, u8 base)
{
	u16 uiResult;
	if (NewData > OldData)
	{
		uiResult = NewData - OldData;
		uiResult *= k;
		uiResult += base >> 2;
		uiResult /= base;
		uiResult = OldData + uiResult; 
	}
	else if (OldData > NewData)
	{
		uiResult = OldData - NewData;
		uiResult *= k;
		uiResult += base >> 2;
		uiResult /= base;
		uiResult = OldData - uiResult; 
	}
	else
	{
		uiResult = NewData;
	}
	
	return(uiResult);
}

使用方法如下:NewData表示最新采用的模拟量;OldData表示滤波后的模拟量。

ADC_Battery1_Voltage=Filter(ADC_ConvertedValue[1],ADC_Battery1_Voltage,1,10);

为便于逻辑计算、控制及显示,以下代码是把AD值转换为实际值,

/******************************
AD值转换实际值函数
******************************/
s16 Adc_To_Act(s16 Adc_Value, s16 Pre_Adc_Min, s16 Pre_Adc_Max, s16 Pre_Act_Min, s16 Pre_Act_Max)
{
s32 _temp;
s32 _range;
_temp = (s32)((Adc_Value - Pre_Adc_Min) * (Pre_Act_Max - Pre_Act_Min) / (Pre_Adc_Max-Pre_Adc_Min)) + Pre_Act_Min;
_temp = Adc_Value - Pre_Adc_Min;
_range = Pre_Act_Max - Pre_Act_Min;
_temp = _temp * _range;
_range = Pre_Adc_Max - Pre_Adc_Min;
_temp = _temp + _range / 2;
_temp = _temp / _range;
_temp = _temp + Pre_Act_Min;
return(_temp);
}

使用方法如下:Adc_Value表示要转换的模拟量;Pre_Adc_Min表示模拟量AD值的最小值;Pre_Adc_Max表示模拟量AD值的最大值;Pre_Act_Min表示转换后实际值的最小值;Pre_Act_Max表示转换后实际值的最大值;(以下最大实际值220表示22.0A,是因为数码管显示需要小数表示)。

Charging_Current = Adc_To_Act(ADC_Charging_Current, 10, 4096, 0, 220);//22.0A

要点:

① 模拟量的采样电路,我多采用运放的差分放大电路,原因是被测电压可以和运放不用共地,且可有效抑制共模噪声,可达到较高的精确线性测量,比如以上电池组的被测电压的误差与实际相差在0.3V左右;

② 电池组输入至运放的8个1M的电阻是两个为一组的,且功率至少1/4W以上,因为在高压下的电阻容易老化,为保险起见,通常一个电阻的最大压差在100V以下为宜;

③ 电池组分为两组检测,一是为了降低元件所承受的电压,二是为了监视两组电池电压之间是否平衡,达到保护电池目的。

④ 函数应功能模块化,且具备通用性质,便于移植和调用,对于很多朋友应先学会如何使用,底层代码只要会配置就完全足够了。

当然,以上提供的设计是我通常的做法,能满足大多数的常规应用。

来源:今日头条(版主:电卤药丸)
链接:
https://www.toutiao.com/a6635851850929144323/
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 58

续接上期说的华大单片机设计特别注意事项

6. 使用内部RC主时钟RCH切换频率

方案一:切换RCH时必须遵循逐级调高4M->8M->16M->22.12M/24M,或者逐渐降低22.12M/24M->16M->8M->4M的过程,不能一步到位。

方案二:先切到RCL做系统时钟再改RCH。否则会影响产品可靠性,造成大批量时出现不良。

注意:进行时钟切换的时候要把全部中断都关掉。

7. I/O口电压不能大于VCC

华大L110/L13X/L17X/L19X/L07X;F003/F005/F030/F17X/F19X/F07X系列I/O没有5V tolerant功能,所以I/O电压不能大于VCC。

当I/O电压大于VCC时,最先出现的特征是ADC不准。工程师调试时喜欢用PC+USB-UART+串口调试助手来查看UART送出的数据。

这时如果UART电平大于MCU电源电压VCC,ADC数据会不准。

8. I2C模块使用特别注意

I2C模块在初始化的时候,在使能I2C时钟后,对模块进行一次复位,再对I2C其它的寄存器进行初始化。

9. HC32L110/HC32F003/HC32F005复位管脚

HC32L110/HC32F003/HC32F005芯片的RESET管脚可以复用为带上拉的GPIO数字输入端口,外围的复位电路需要保留。

L13X/L17X/L19X/L07X/F030/F17X/F19X/F07X系列芯片RESET管脚不可以利用为GPIO。

10. 低功耗模式程序调试

在应用程序中,如果需要进入低功耗模式,程序将无法进行调试。

如果程序中需要使用该功能,建议在调试开发阶段,在程序一开始添加几秒钟的延时程序,或者添加外部 IO 控制程序等方法来决定是否执行该段程序,或者增加外部唤醒机制,以便在二次调试开发时 SWD 功能能够正常使用。

来源:芯虎论坛-龙飞虎
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 302

页面

订阅 RSS - 单片机