单片机

有小伙伴说:我一个很简单的单片机项目,就点个灯,一个AD采集并通过串口传输一下数据,这还需要分层设计吗?

这个问题,其实没有标准答案,你可以不用分层设计,也可以分层设计,这取决于你,或你们公司对软件的要求。

当然,作为工作十年有余的过来人,我的建议:能采用分层设计,尽量分层设计。

分层设计的好处

这么说吧,小项目不分层设计也没问题,但你想一辈子只做这么简单的“点灯”小项目吗?

当你今后项目做大了,你就知道分层设计会有很多好处了。

1、模块化:分层设计可以让软件被划分为不同的逻辑或功能模块,每个模块都负责一组相对独立的任务,这样做可以提高了代码的模块化和重用性。

2、易于维护:当系统需要修改或扩展时,分层设计使开发者可以专注于修改或添加特定层的功能,而不需要深入了解整个系统的内部细节,这降低了维护的复杂度和成本。

3、提高可读性:清晰的层次结构使得代码更加容易理解和阅读,尤其是对于新加入项目的开发者来说(不要相信网上的段子,代码越乱才不会被人替代)。

4、增强可测试性:分层设计有助于实现单元测试或集成测试,因为你可以独立地测试每一层的功能,而不需要运行整个系统。

所以,为了提升自己,准确的说,为了今后能做大项目,挣大钱,单片机项目很有必要分层设计。


常见的分层结构

我们在很多地方都会看到分层的架构,比如类似下图这样的:


1.jpg

当然,我们在单片机软件设计中,常见的分层结构可能包括:

1、硬件抽象层(HAL):负责与硬件直接交互,提供对硬件设备的抽象访问,如GPIO、ADC、UART等。

2、驱动层:建立在HAL之上,提供更高层次的接口来操作硬件设备,例如具体的传感器或外设的驱动程序。

3、业务逻辑层:处理应用程序的核心业务逻辑,如数据处理、算法实现等。

接口层(或称为通信层):负责与其他设备或系统的通信,如通过串口、I2C、SPI等协议与其他单片机或上位机通信。

4、应用层:面向用户或用户的程序,直接响应用户的操作或请求,如控制LED灯的闪烁、读取传感器的数据等。

最后

虽然单片机软件设计是否需要分层,没有标准的答案。但分层设计通常是一种值得推荐的做法,特别是在处理复杂、大型的项目时,它能够提高代码的可维护性、可读性和可扩展性,同时也便于团队协作和测试。

然而,在实际项目中,开发者应根据具体需求灵活选择是否采用分层设计,并合理设计各层的边界和接口。

来源:嵌入式专栏

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

围观 6

在单片机开发过程中也会遇到各种各样的Flash,比如:SPI Flash、Nor Flash、 Nand Falsh等。

做单片机开发的小伙伴,肯定会好奇,单片机内部的Flash,到底是什么类型的Flash?今天就来讲讲关于Flash的内容。

Flash的发展及NOR 和NAD Flash区别

Flash,又叫Flash Memory,即平时所说的“闪存”。

Flash结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的功能,还可以快速读取数据,具有NVRAM的优势(NVRAM:Non-Volatile Random Access Memory,非易失性随机访问存储器)。

在过去,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,Flash的出现,全面代替了ROM(EPROM)在嵌入式系统中的地位,用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘、固态硬盘)。

Flash通常分为:NOR Flash 和 NAND Flash,它们各自有各自的优缺点。

1.NOR Flash

Intel于1988年首先开发出NOR flash技术,彻底改变了原先由EPROM和EEPROM一统天下的局面。

NOR Flash的读取和我们常见的SDRAM的读取是一样,即可以根据地址随机读写,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本。因为其读取速度快,多用来存储程序、操作系统等重要信息。

2.NAND Flash

1989年,东芝公司发表了NAND flash结构,强调降低每比特的成本,更高的性能,并且象磁盘一样可以通过接口轻松升级。

NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的制作Flash的成本更廉价。

用户不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flash以外,还作上了一块小的NOR Flash来运行启动代码。

3.比较

相比于 NOR Flash,NAND Flash 写入性能好,大容量下成本低。

目前,绝大部分手机、平板、SD卡、固态硬盘等设备中所使用的Flash Memory 都属于 NAND Flash。

单片机是Nor还是Nand Flash?

Flash主要分为Nor Flash 和Nand Flash。

除了我们常见的(并行)Nor Flash 和Nand Flash芯片,其实SPI Flash芯片同样的也分SPI NOR Flash和SPI NAND Flash芯片,你去很多卖SPI Flash的官方都能找到其分类。

1.png

回到本文主题:单片机内部Flash是Nor,还是Nand Flash?

我们使用外部程序存储芯片,主要是NOR Flash,你没见过有谁用Nand Flash跑程序的吧?

Nand Flash不适合跑程序的几点原因:

1、不能随机访问地址;

2.存在坏块,不适合存储程序;

3.在长时间反复读取过程中,Nand中数据相对没有Nor稳定;

所以,单片机内部Flash通常是Nor Flash。

来源:嵌入式专栏

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

围观 16

1、C语言和汇编语言在开发单片机时各有哪些优缺点?

汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言可能有所差异,所以不易移植。

C语言是一种结构化的高级语言。其优点是可读性好,移植容易,是普遍使用的一种计算机语言。缺点是占用资源较多,执行效率没有汇编高。

对于目前普遍使用的RISC架构的8bit MCU来说,其内部ROM、RAM、STACK等资源都有限,如果使用C语言编写,一条C语言指令编译后,会变成很多条机器码,很容易出现ROM空间不够、堆栈溢出等问题。而且一些单片机厂家也不一定能提供C编译器。而汇编语言,一条指令就对应一个机器码,每一步执行什幺动作都很清楚,并且程序大小和堆栈调用情况都容易控制,调试起来也比较方便。所以在资源较少的单片机开发中,还是建议采用汇编语言比较好。

02、C或汇编语言可以用于单片机,C++能吗?

在单片机开发中,主要是汇编和C,没有用C++的。

03、搞单片机开发,一定要会C吗?

汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言可能有所差异,所以不易移植。

而C语言是一种编译型程序设计语言,它兼顾了多种高级语言的特点,并具备汇编语言的功能。C语言有功能丰富的库函数、运算速度快、编译效率高、有良好的可移植性,而且可以直接实现对系统硬件的控制。C语言是一种结构化程序设计语言,它支持当前程序设计中广泛采用的由顶向下结构化程序设计技术。此外,C语言程序具有完善的模块程序结构,从而为软件开发中采用模块化程序设计方法提供了有力的保障。因此,使用C语言进行程序设计已成为软件开发的一个主流。用C语言来编写目标系统软件,会大大缩短开发周期,且明显地增加软件的可读性,便于改进和扩充,从而研制出规模更大、性能更完备的系统。

综上所述,用C语言进行单片机程序设计是单片机开发与应用的必然趋势。所以作为一个技术全面并涉足较大规模的软件系统开发的单片机开发人员最好能够掌握基本的C语言编程。

04、当开发一个较复杂而又开发时间短的项目时,用C还是用汇编开发好?

对于复杂而开发时间紧的项目时,可以采用C语言,但前提是要求对该MCU系统的C语言和C编译器非常熟悉,特别要注意该C编译系统所能支持的数据类型和算法。虽然C语言是最普遍的一种高级语言,但不同的MCU厂家其C语言编译系统是有所差别的,特别是在一些特殊功能模块的操作上。如果对这些特性不了解,那调试起来就有的烦了,到头来可能还不如用汇编来的快。

05、8088和196芯片单片机教材,请问哪里可以找到?

有关这方面的教材,大学里常用的一本是《IBM-PC汇编语言程序设计》清华大学出版社出版的,在网上以及书店都是可以找到的。另外网上还可以搜索到很多其他的教材如:《微机原理及汇编语言教程》(杨延双 张晓冬 等编著 )和《16/32 位微机原理、汇编语言及接口技术》(作者:钟晓捷 陈涛 ,机械工业出版社 出版)等,可以在较大型的科技书店里查找或者直接从网上订购。

06、初学者到底是应该先学C还是汇编?

对于单片机的初学者来说,应该从汇编学起。因为汇编语言是最接近机器码的一种语言,可以加深初学者对单片机各个功能模块的了解,从而打好扎实的基础。

07、大三生,学了电子线路、数字逻辑、汇编和接口、C语言,但总是感觉很迷茫,觉好像什么都不会,怎么办?

大学过程是一个理论过程,实践的机会比较少,往往会造成理论与实践相脱节,这是国内大学教育系统的通病,不过对于学生来说切不可好高骛远。一般从大三会开始接触到一些专业课程,电子相关专业会开设相关的单片机应用课程并且会有简单的实验项目,那么要充分把握实验课的机会,多多地实际上机操作练习。平时可以多看看相关的电子技术杂志网站,看看别人的开发经验,硬件设计方案以及他人的软件设计经验。有可能的话,还可以参加一些电子设计大赛,借此机会2--3个人合作做一个完整系统,会更有帮助。到了大四毕业设计阶段,也可以选择相关的课题作些实际案例增长经验。做什么事情都有个经验的积累过程,循序渐进。

08、作为学生,如何学好单片机?

学习好单片机,最主要的是实践,在实践中增长经验。在校学生的话,实践机会的确会比较少,但是有机会的话,可以毕业实习选择相关的课题,这样就可以接触到实际的项目。而且如果单片机微机原理是一门主课的话,相信学校会安排比较多的实践上机机会。有能力的话,可以找一些相关兼职工作做做,会更有帮助。而且单片机开发应用需要软硬件结合,所以不能只满足于编程技巧如何完美,平时也要注意硬件知识的积累,多上上电子论坛网站,买一些相关杂志。可能的话,可以到电子市场去买一些小零件,自己搭一个小系统让它工作起来。

09、如何才能才为单片机的高手?

要成为单片机高手,应该多实践,时常关注单片机的发展趋势;经常上一些相关网站,从那里可以找到许多有用的资料。

10、8位机还能延续多久?

以现在MCU产品主力还是在8位领域,主要应用于汽车应用、消费性电子、电脑及PC周边、电信与通讯、办公室自动化、工业控制等六大市场,其中车用市场多在欧、美地区,而亚太地区则以消费性电子为主, 并以量大低单价为产品主流,目前16位MCU与8位产品,还有相当幅度的价差,新的应用领域也仍在开发,至少在目前8位的MCU还有一席之地。

来源:单片机与嵌入式学堂

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

围观 12

本文介绍如何使用带FIFO的串口来减少接收中断次数,通过一种自定义通讯协议格式,给出帧打包方法;之后介绍一种特殊的串口数据发送方法,可在避免使用串口发送中断的情况下,提高系统的响应速度。

简介 

串口由于使用简单,价格低廉,配合RS485芯片可以实现长距离、抗干扰能力强的局域网络而被广泛使用。随着产品功能的增多,需要处理的任务也越来越复杂,系统任务也越来越需要及时响应。

绝大多数的现代单片机(ARM7、Cortex-M3)串口都带有一定数量的硬件FIFO,本文将介绍如何使用硬件FIFO来减少接收中断次数,提高发送效率。在此之前,先来列举一下传统串口数据收发的不足之处:

  • 每接收一个字节数据,产生一次接收中断。不能有效的利用串口硬件FIFO,减少中断次数。

  • 应答数据采用等待发送的方法。由于串行数据传输的时间远远跟不上CPU的处理时间,等待串口发送完当前字节再发送下一字节会造成CPU资源浪费,不利于系统整体响应(在1200bps下,发送一字节大约需要10ms,如果一次发送几十个字节数据,CPU会长时间处于等待状态)。

  • 应答数据采用中断发送。增加一个中断源,增加系统的中断次数,这会影响系统整体稳定性(从可靠性角度考虑,中断事件应越少越好)。

  • 针对上述的不足之处,将结合一个常用自定义通讯协议,提供一个完整的解决方案。

串口FIFO

串口FIFO可以理解为串口专用的缓存,该缓存采用先进先出方式。数据接收FIFO和数据发送FIFO通常是独立的两个硬件。

串口接收的数据,先放入接收FIFO中,当FIFO中的数据达到触发值(通常触发值为1、2、4、8、14字节)或者FIFO中的数据虽然没有达到设定值但是一段时间(通常为3.5个字符传输时间)没有再接收到数据,则通知CPU产生接收中断;发送的数据要先写入发送FIFO,只要发送FIFO未空,硬件会自动发送FIFO中的数据。

写入发送FIFO的字节个数受FIFO最大深度影响,通常一次写入最多允许16字节。上述列举的数据跟具体的硬件有关,CPU类型不同,特性也不尽相同,使用前应参考相应的数据手册。

数据接收与打包

FIFO可以缓存串口接收到的数据,因此我们可以利用FIFO来减少中断次数。以NXP的lpc1778芯片为例,接收FIFO的触发级别可以设置为1、2、4、8、14字节,推荐使用8字节或者14字节,这也是PC串口接收FIFO的默认值。

这样,当接收到大量数据时,每8个字节或者14个字节才会产生一次中断(最后一次接收除外),相比接收一个字节即产生一个中断,这种方法串口接收中断次数大大减少。

将接收FIFO设置为8或者14字节也十分简单,还是以lpc1778为例,只需要设置UART FIFO控制寄存器UnFCR即可。

接收的数据要符合通讯协议规定,数据与协议是密不可分的。通常我们需要将接收到的数据根据协议打包成一帧,然后交由上层处理。下面介绍一个自定义的协议帧格式,并给出一个通用打包成帧的方法。

自定义协议格式如下图所示。

1.png

  • 帧首:通常是3~5个0xFF或者0xEE

  • 地址号:要进行通讯的设备的地址编号,1字节

  • 命令号:对应不同的功能,1字节

  • 长度:数据区域的字节个数,1字节

  • 数据:与具体的命令号有关,数据区长度可以为0,整个帧的长度不应超过256字节

  • 校验:异或和校验(1字节)或者CRC16校验(2字节),本例使用CRC16校验

下面介绍如何将接收到的数据按照上图所示的格式打包成一帧。

1 定义数据结构

typedef struct
{
    uint8_t * dst_buf;                  //指向接收缓存  
    uint8_t sfd;                        //帧首标志,为0xFF或者0xEE  
    uint8_t sfd_flag;                   //找到帧首,一般是3~5个FF或EE  
    uint8_t sfd_count;                  //帧首的个数,一般3~5个  
    uint8_t received_len;               //已经接收的字节数  
    uint8_t find_fram_flag;             //找到完整帧后,置1  
    uint8_t frame_len;                  //本帧数据总长度,这个区域是可选的  
}find_frame_struct;

2 初始化数据结构,一般放在串口初始化中

/** 
* @brief    初始化寻找帧的数据结构 
* @param    p_fine_frame:指向打包帧数据结构体变量 
* @param    dst_buf:指向帧缓冲区 
* @param    sfd:帧首标志,一般为0xFF或者0xEE 
*/
void init_find_frame_struct(find_frame_struct * p_find_frame,uint8_t *dst_buf,uint8_t sfd)  
{      
    p_find_frame->dst_buf=dst_buf;      
    p_find_frame->sfd=sfd;      
    p_find_frame->find_fram_flag=0;      
    p_find_frame->frame_len=10;           
    p_find_frame->received_len=0;      
    p_find_frame->sfd_count=0;      
    p_find_frame->sfd_flag=0;  
}

3 数据打包程序

/** 
* @brief    寻找一帧数据  返回处理的数据个数 
* @param    p_find_frame:指向打包帧数据结构体变量 
* @param    src_buf:指向串口接收的原始数据 
* @param    data_len:src_buf本次串口接收到的原始数据个数 
* @param    sum_len:帧缓存的最大长度 
* @return   本次处理的数据个数 
*/

uint32_t find_one_frame(find_frame_struct * p_find_frame,const uint8_t * src_buf,uint32_t data_len,uint32_t sum_len)  
{       
  uint32_t src_len=0;  
  while(data_len--)      
  {  
      if(p_find_frame ->sfd_flag==0)                                
      {   
          //没有找到起始帧首  
          if(src_buf[src_len++]==p_find_frame ->sfd)              
          {                  
              p_find_frame ->dst_buf[p_find_frame ->received_len++]=p_find_frame ->sfd;  
              if(++p_find_frame ->sfd_count==5)                          
              {                      
                  p_find_frame ->sfd_flag=1;                      
                  p_find_frame ->sfd_count=0;                      
                  p_find_frame ->frame_len=10;                  
              }              
          }  
          else            
          {                  
              p_find_frame ->sfd_count=0;                   
              p_find_frame ->received_len=0;               
          }          
      }  
      else        
      {   
          //是否是"长度"字节? Y->获取这帧的数据长度  
          if(7==p_find_frame ->received_len)                            
          {                  
              p_find_frame->frame_len=src_buf[src_len]+5+1+1+1+2; //帧首+地址号+命令号+数据长度+校验       
              if(p_find_frame->frame_len>=sum_len)                  
              {   
                  //这里处理方法根据具体应用不一定相同                      
                  MY_DEBUGF(SLAVE_DEBUG,("数据长度超出缓存!\n"));                      
                  p_find_frame->frame_len= sum_len;                       
              }              
          }             
        p_find_frame ->dst_buf[p_find_frame->received_len++]=src_buf[src_len++];                
        if(p_find_frame ->received_len==p_find_frame ->frame_len)                              
        {                  
            p_find_frame ->received_len=0;              //一帧完成                    
            p_find_frame ->sfd_flag=0;                  
            p_find_frame ->find_fram_flag=1;                   
            return src_len;              
        }          
        }      
    }      
    p_find_frame ->find_fram_flag=0;  return src_len;  
}

使用例子:

定义数据结构体变量:

find_frame_struct slave_find_frame_srt;

定义接收数据缓冲区:

#define SLAVE_REC_DATA_LEN  128
uint8_t slave_rec_buf[SLAVE_REC_DATA_LEN];

在串口初始化中调用结构体变量初始化函数:

init_find_frame_struct(&slave_find_frame_srt,slave_rec_buf,0xEE);

在串口接收中断中调用数据打包函数:

find_one_frame(&slave_find_frame_srt,tmp_rec_buf,data_len,SLAVE_REC_DATA_LEN);

其中,rec_buf是串口接收临时缓冲区,data_len是本次接收的数据长度。
数据发送

前文提到,传统的等待发送方式会浪费CPU资源,而中断发送方式虽然不会造成CPU资源浪费,但又增加了一个中断源。在我们的使用中发现,定时器中断是几乎每个应用都会使用的,我们可以利用定时器中断以及硬件FIFO来进行数据发送,通过合理设计后,这样的发送方法即不会造成CPU资源浪费,也不会多增加中断源和中断事件。

需要提前说明的是,这个方法并不是对所有应用都合适,对于那些没有开定时器中断的应用本方法当然是不支持的,另外如果定时器中断间隔较长而通讯波特率又特别高的话,本方法也不太适用。

公司目前使用的通讯波特率一般比较小(1200bps、2400bps),在这些波特率下,定时器间隔为10ms以下(含10ms)就能满足。如果定时器间隔为1ms以下(含1ms),是可以使用115200bps的。本方法主要思想是:定时器中断触发后,判断是否有数据要发送,如果有数据要发送并且满足发送条件,则将数据放入发送FIFO中,对于lpc1778来说,一次最多可以放16字节数据。之后硬件会自动启动发送,无需CPU参与。

下面介绍如何使用定时器发送数据,硬件载体为RS485。因为发送需要操作串口寄存器以及RS485方向控制引脚,需跟硬件密切相关,以下代码使用的硬件为lpc1778,但思想是通用的。

1 定义数据结构

/*串口帧发送结构体*/
typedef struct
{
    uint16_t send_sum_len;          //要发送的帧数据长度  
    uint8_t  send_cur_len;          //当前已经发送的数据长度  
    uint8_t  send_flag;             //是否发送标志  
    uint8_t * send_data;            //指向要发送的数据缓冲区  
}uart_send_struct;

2 定时处理函数

/** 
* @brief    定时发送函数,在定时器中断中调用,不使用发送中断的情况下减少发送等待 
* @param    UARTx:指向硬件串口寄存器基地址 
* @param    p:指向串口帧发送结构体变量 
*/
#define FARME_SEND_FALG 0x5A          
#define SEND_DATA_NUM   12  
static void uart_send_com(LPC_UART_TypeDef *UARTx,uart_send_struct *p)  
{      
    uint32_t i;      
    uint32_t tmp32;  

    if(UARTx->LSR &(0x01<<6))                      //发送为空      
    {         
        if(p->send_flag==FARME_SEND_FALG)          
        {                                      
            RS485ClrDE;                             // 置485为发送状态  
            tmp32=p->send_sum_len-p->send_cur_len;  
            if(tmp32>SEND_DATA_NUM)                 //向发送FIFO填充字节数据              
            {  
                for(i=0;i<SEND_DATA_NUM;i++)                  
                {                      
                    UARTx->THR=p->send_data[p->send_cur_len++];                  
                }              
            }  
            else            
            {  
                for(i=0;i<tmp32;i++)                  
                {                      
                    UARTx->THR=p->send_data[p->send_cur_len++];                  
                }                  
                p->send_flag=0;                                  
            }          
        }  
        else        
        {              
            RS485SetDE;          
        }      
    }  
}

其中,RS485ClrDE为宏定义,设置RS485为发送模式;

RS485SetDE也为宏定义,设置RS485为接收模式。

使用例子:

定义数据结构体变量:

uart_send_struct uart0_send_str;

定义发送缓冲区:

uint8_t uart0_send_buf[UART0_SEND_LEN];

根据使用的硬件串口,对定时处理函数做二次封装:

2.png

将封装函数uart0_send_data();放入定时器中断处理函数中;

在需要发送数据的地方,设置串口帧发送结构体变量:

uart0_send_str.send_sum_len=data_len;      //data_len为要发送的数据长度
uart0_send_str.send_cur_len=0;             //固定为0
uart0_send_str.send_data=uart0_send_buf;   //绑定发送缓冲区
uart0_send_str.send_flag=FARME_SEND_FALG;  //设置发送标志

总结

本文主要讨论了一种高效的串口数据收发方法,并给出了具体的代码实现。在当前处理器任务不断增加的情况下,提供了一个占用资源少,可提高系统整体性能的新的思路。

来源:STM32嵌入式开发

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

围观 17

为了提供单片机内存数据传输效率,众多单片机都支持 DMA(Direct Memory Access)传输。

但是,还有另外一种可以提高内存传输效率的功能较TCM。今天就结合瑞萨RA8单片机给大家讲讲TCM的优点及应用。

关于TCM

TCM:Tightly-Coupled Memory,紧耦合内存,是通过专用的接口直接连接到处理器的存储器区域,它提供单周期访问,避免其他存储器可能存在的仲裁延时和延迟。
RA8单片机 Cortex-M85内核有2种TCM类型:Instruction TCM (ITCM) 指令TCM和Data TCM (DTCM) 数据TCM。

RA8x1有64KB ITCM和64KB DTCM。

ECC保护(代码生成和纠正逻辑)。

RA8x1支持ITCM和DTCM ECC功能,可以通过OFS2.INITECCEN设置来启用和禁用。

1.png

RA8x1 CPU方框图

TCM具有以下优点:

低延迟访问:TCM与处理器核心之间的直接连接消除了访问延迟,使得数据和指令能够更快速地被处理器访问,从而提高了系统的响应速度。

高带宽:TCM通常具有更高的带宽,可以支持处理器对数据和指令的高速读写,提升了系统的整体性能。

节省功耗:由于TCM与处理器核心直接连接,不需要通过总线进行数据传输,因此可以降低功耗。此外,由于访问延迟较低,处理器可以更快地完成任务并进入休眠模式,进一步降低功耗。

提高实时性能:对于需要实时响应的应用程序,TCM的低延迟和高带宽特性使得处理器能够更快地访问关键数据和指令,从而提高了系统的实时性能。

增强安全性:TCM可以用于存储敏感数据和关键代码,通过与处理器核心的紧密耦合,可以降低数据泄露和恶意攻击的风险,提高系统的安全性。

减少对外部存储器的依赖:TCM可以用于存储频繁访问的数据和指令,减少了对外部存储器的访问次数,降低了总体的访问延迟和功耗。这对于一些资源有限的嵌入式系统尤为重要。

增强可靠性:TCM的直接连接和高速访问特性可以提高系统的可靠性,减少因外部存储器或总线故障而导致的系统性能下降或故障。

因此,TCM适用于许多不同的应用和场景,特别是对于需要高性能、低延迟和实时响应的应用,其优势更加突出。以下是一些适合使用TCM的应用和场景:

实时控制系统:对于需要快速响应的实时控制系统,如工业机器人、航空航天控制系统、医疗设备等,TCM可以提供所需的低延迟和高带宽,确保系统能够及时、准确地响应各种控制指令。

信号处理应用:TCM对于需要大量数据处理和信号处理的应用非常适用,例如无线通信系统、雷达系统、图像处理系统等。通过将频繁访问的数据和指令存储在TCM中,可以提高系统的处理速度和效率。

物联网设备:随着物联网设备的普及,对于需要在资源有限的设备上实现高性能和实时响应的应用,TCM可以帮助提高系统的性能和能效,同时减少对外部存储器和网络带宽的依赖。

高性能计算:在需要进行复杂计算和大规模数据处理的高性能计算应用中,TCM可以提供更快速和可靠的数据访问,从而提高系统的计算性能和吞吐量。

MCU如何将通过FSP将代码/数据放置到TCM中?

瑞萨电子灵活配置软件包(FSP)是用于嵌入式系统设计的高质量增强型软件包,支持瑞萨电子RA产品家族ARM微控制器,提供用户友好的界面且可灵活扩展,确保从入门级到高性能的整个RA微控制器的软件兼容性。FSP包括高性能、低内存占用的业界一流的HAL驱动程序。还包含集成了Azure RTOS和FreeRTOS的中间件协议栈,能够简化通信和安全等复杂模块的实现。e2 studio IDE提供了对图形化配置工具和智能代码生成器的支持,从而使编程和调试变得更加轻松快捷。

  • 瑞萨FSP链接脚本提供TCM内存区域段定义

memory_region.ld中的内存大小定义:

2.png

内存区域定义:

3.png

如下原型定义可用于将用户代码/数据放置到TCM中。在启动过程中,.itcm_data和.dtcm_data区域将通过闪存中存储的初始化代码进行数据初始化。.dtcm_bss区域已初始化为零。

4.png

FSP中的ITCM段定义:

5.png

FSP中的DTCM段定义:

6.png

TCM例子分析

下图是一个RA8x1 MCU实际使用TCM的例子,它使用了ITCM和DTCM。图片中的右边为RA8x1 MCU的系统地址空间。

7.png

具体分析过程为:

1)紫色的代码“uint16_t i;”全局变量,它运行的时候,分配的地址是在从0x2200_0000开始的On-chip SRAM中。

2)红色的代码“BSP_PLACE_IN_SECTION(“.dtcm_data”)uint8x16_t rega_8, regb_8, regc_8, regd_8;”,全局变量,但是由于这些变量前面添加了BSP_PLACE_IN_SECTION(“.dtcm_data”),这表示将这些变量放置到DTCM中,它运行的时候,分配的地址是在从0x2000_0000开始的DTCM中。

3)青色的代码“void hal_entry(void)”,它是函数,运行的时候,分配的地址是在从0x0200_0000开始的On-chip flash中。

4)深绿色的代码“void helium_test(void)”,它是函数,但是由于这些变量前面添加了BSP_PLACE_IN_SECTION(“.itcm_data”),这意味着,该代码运行的时候,分配的地址是从0x0000_0000开始的ITCM中。

下图是上面描述的代码在e2 studio中使用了LLVM工具链编译并仿真运行的截图,可以发现右边表达式窗口中的i,rega_8, regb_8, regc_8, regd_8,helium_test,hal_entry这些代码或者变量的地址和刚刚分析的结果是相符的。

8.png

来源:瑞萨嵌入式小百科

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

围观 16

Holtek持续扩展中高阶市场产品应用,推出全新HT32F49153/HT32F49163系列32-bit单片机,采用高效能Arm® Cortex®-M4核心,提供单精度浮点运算单元(FPU),支持所有Arm®单精度数据处理指令和数据类型。该核心亦具备完整DSP指令与内存保护单元(MPU),增强数值运算效能与应用安全性,并提供多种节能模式,适用于各种应用场景,如嵌入式系统、工业控制、物联网、智慧家庭等。

1.jpg

HT32F49153/HT32F49163系列最高运行速度可达150MHz,工作电压范围为2.4V~3.6V,符合-40℃~105℃工业温度范围。Flash容量为128KB/256KB,并支持外部内存扩展(XMC),兼容8080/6800模式作为TFT-LCD接口。提供耐受5V电压输入的GPIO及丰富的外围资源,如转速高达5.33Msps 12-bit ADC×1,12-bit DAC×2,16-bit通用定时器×8,32-bit通用定时器×1,高级定时器×1,基本定时器×2,I²C×3,SPI/I²S×3,USART/UART×8,IRTMR×1,OTGFS×1 (Device模式下支持无晶振Xtal-less)和CAN×2。

HT32F49153/HT32F49163提供32-pin QFN、48/64/100-pin LQFP封装,依据封装类型GPIO脚位可达87个。全系列支持业界主流Keil MDK及IAR EWARM开发环境,并提供硬件开发工具包、周边驱动函式库(Firmware Library)及应用范例等,提供开发便利性,缩短产品开发周期。

来源:HOLTEK

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

围观 14

单片机可以替代PLC吗?

单片机不能完全替代PLC 。虽然单片机可以通过编程实现类似的功能,但是在可靠性和稳定性方面相对来说稍逊于PLC。PLC具有良好的可编程性、实时性和稳定性等特点,在工业控制和自动化领域受到广泛应用。

PLC有哪些优点是单片机无法替代的?

PLC相对于单片机具有以下一些优点,这些优点使得PLC在某些应用中无法被单片机替代:
  • 可靠性高 :PLC采用工业级元器件,具有可靠、稳定的性能。其内部机制设计复杂、精密,运行稳定,因此很少出现类似死机或蓝屏等情况,使用寿命长。

  • 抗干扰能力强 :PLC通常有更强的抗干扰能力和更高的稳定性,可以工作在更恶劣的环境下,如高温、高湿、强电磁干扰等。

  • 模块化、分布式控制 :PLC可以通过模块化、分布式控制的方式来构建复杂的系统,支持多任务处理和多设备协同控制。

  • 编程简单 :PLC编程相对简单,常常使用图形化编程语言,如梯形图(Ladder Diagram)、顺序功能图(SFC)、结构化文本(ST)等。这使得开发周期缩短,也降低了开发难度。

  • 输入输出接口灵活 :PLC的输入输出接口通常数量较多,可方便扩展使用,另外PLC也具有很好的多通道控制功能,对于输入输出操作可以直接进行操作。

  • 扩展性强 :PLC可以通过模块扩展来增加功能,而不需要对PLC本身进行修改。

  • 安全性高 :PLC通常具有较高的安全性,可以通过安全模块和安全协议来确保系统的安全性。

PLC在可靠性、抗干扰能力、模块化、编程简单、输入输出接口灵活、扩展性和安全性等方面都具有优势,这些优势使得PLC在一些特定应用中无法被单片机替代。

单片机和PLC的区别是什么?

单片机和PLC的区别主要体现在以下几个方面:
  • 应用领域:单片机通常用于小型应用,如小型电器设备中控制电机、传感器等。而PLC则主要用于大型应用,如自动化和机器控制。
  • 功能实现方式:单片机可以通过编程实现其功能,通常用于不需要高级程序和算法的简单应用。而PLC则可以通过编程和配置来实现更为复杂的控制任务。
  • 结构和接口:单片机一般只有少量的输入输出接口,而PLC可以具有多种输入输出接口,以适应不同的应用要求。
  • 可靠性和稳定性:PLC具有良好的可编程性、实时性和稳定性等特点,因此在工业控制和自动化领域受到广泛应用。而单片机的可靠性和稳定性相对来说稍逊于PLC。
  • 成本:对于量大的配套项目,采用单片机系统具有成本低、效益高的优点,但这需要有相当的研发力量和行业经验才能使系统稳定。而PLC的成本相对较高,但在一些需要高可靠性和稳定性的应用中,使用PLC可能更为合适。
  • 编程和调试:PLC广泛使用梯形图代替计算机语言,对编程有一定的优势,使得使用者更加容易使用。而单片机的编程和调试则需要较高的技术水平。

总的来说,单片机和PLC在应用、功能实现方式、结构、接口、可靠性、稳定性、成本和编程调试等方面都有所不同。在实际应用中,需要根据具体的需求和条件来选择合适的控制系统。

来源:电工与电气控制技术知识

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

围观 6

大家好,今天分享一篇关于按键处理的文章,项目地址如下https://gitee.com/wei513723/key_board 

key_board介绍

key_board用于单片机中的小巧多功能按键支持,软件采用了分层的思想,并且做到了与平台无关,用户只需要提供按键的基本信息和读写io电平的函数即可,非常方便移植,同时支持多个矩阵键盘及多个单io控制键盘。

目前已实现按下触发、弹起触发、长按自动触发、长按弹起触发、多击触发、连续触发等功能,并且能够随意组合(支持状态的同一时间轴和非同一时间轴),后续还会添加更多的功能。

使用说明

  1. 初始化相关的硬件资源。

  2. 提供一个1ms的定时器,用于周期性的调用'key_check'函数。

  3. 提供按键的描述及读写io的函数。

  4. 将键盘注册到系统。

  5. 具体的操作参考提供的stm32例程。

  6. 因为程序默认使用了堆内存,当发现程序运行结果不正常时,尝试增大你的程序堆空间,或者注册调试接口查看原因。

  7. 更详细的使用教程见详细使用说明或者提供的stm32例程。

已支持的键盘

1、矩阵键盘

1.jpg

矩阵键盘

2、单io按键

2.jpg

单io按键

详细使用说明

将key_board.c,key_board.h,key_board_config.h放进key_board文件夹中并包含进你的工程,添加头文件路径。

基础功能移植(以stm32矩阵键盘为例)

首先需要一个可使用的定时器(如果不想使用定时器也可直接放到主循环中,但不推荐,会导致时基不准确),固定为1ms触发一次;

准备待检测的按键的基本信息,可参考key_board_sample.c文件中的struct key_pin_t结构体,如:

struct key_pin_t {
    GPIO_TypeDef *port;     //按键端口号
    uint16_t pin;           //按键的引脚号
    GPIO_PinState valid;    //按键的有效电平(即按键按下时的电平)
    GPIO_PinState invalid;  //按键的无效电平(即按键空闲时的电平)
    /*
    可添加你的其它参数
    */
};

定义待检测的按键信息,可参考key_board_sample.c文件中的const struct key_pin_t key_pin_sig[]结构体数组,对应头文件为key_board_sample.h,如:

//全局变量
const struct key_pin_t key_pin_sig[] = {
    {
        .port = KEY_PORT_J12,
        .pin = KEY_PIN_J12,
        .valid = KEY_PRESS_LEVEL_J12,
        .invalid = KEY_RELEASE_LEVEL_J12
    },
    {
        .port = KEY_PORT_J34,
        .pin = KEY_PIN_J34,
        .valid = KEY_PRESS_LEVEL_J34,
        .invalid = KEY_RELEASE_LEVEL_J34
    },
    {
        .port = KEY_PORT_J56,
        .pin = KEY_PIN_J56,
        .valid = KEY_PRESS_LEVEL_J56,
        .invalid = KEY_RELEASE_LEVEL_J56
    },
};

如果为矩阵键盘还需要定义控制io的相关信息,可参考key_board_sample.c文件中的const struct key_pin_t key_pin_ctrl[]结构体数组,对应头文件为key_board_sample.h,如:

const struct key_pin_t key_pin_ctrl[] = {
    {
        .port = KEY_PORT_J135,
        .pin = KEY_PIN_J135,
        .valid = KEY_CTL_LINE_ENABLE,
        .invalid = KEY_CTL_LINE_DISABLE
    },
    {
        .port = KEY_PORT_J246,
        .pin = KEY_PIN_J246,
        .valid = KEY_CTL_LINE_ENABLE,
        .invalid = KEY_CTL_LINE_DISABLE
    },
};

实现按键io的电平读取函数,可参考key_board_sample.c文件中的pin_level_get函数,如:

static inline bool pin_level_get(const void *desc)
{
    struct key_pin_t *pdesc;

    pdesc = (struct key_pin_t *)desc;
    return HAL_GPIO_ReadPin(pdesc->port, pdesc->pin) == pdesc->valid;
}

如果为矩阵键盘还需要实现按键io的电平写入函数,可参考key_board_sample.c文件中的pin_level_set函数,如:

static inline void pin_level_set(const void *desc, bool flag)
{
    struct key_pin_t *pdesc;

    pdesc = (struct key_pin_t *)desc;
    HAL_GPIO_WritePin(pdesc->port, pdesc->pin, flag ? pdesc->valid : pdesc->invalid);
}

定义按键的id及功能结构体struct key_public_sig_t,可参考key_board_sample.c文件中的const struct key_public_sig_t key_public_sig[]结构体数组,对应头文件key_board.h,如:

const struct key_public_sig_t key_public_sig[] = {
    KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_NONE),
    KEY_PUBLIC_SIG_DEF(KEY_LEFT, &key_pin_sig[1], pin_level_get, KEY_FLAG_NONE),
    KEY_PUBLIC_SIG_DEF(KEY_DOWN, &key_pin_sig[2], pin_level_get, KEY_FLAG_NONE),
    //下面的是因为使用的矩阵键盘而扩展出来的三个按键
    KEY_PUBLIC_SIG_DEF(KEY_ENTER, &key_pin_sig[0], pin_level_get, KEY_FLAG_NONE),
    KEY_PUBLIC_SIG_DEF(KEY_RIGHT, &key_pin_sig[1], pin_level_get, KEY_FLAG_NONE),
    KEY_PUBLIC_SIG_DEF(KEY_EXIT, &key_pin_sig[2], pin_level_get, KEY_FLAG_NONE),
};

如果为矩阵键盘还需要定义控制io的id及功能结构体struct key_public_ctrl_t,可参考key_board_sample.c文件中的const struct key_public_ctrl_t key_public_ctrl[]结构体数组,对应头文件key_board.h,如:

const struct key_public_ctrl_t key_public_ctrl[] = {
    KEY_PUBLIC_CTRL_DEF(&key_pin_ctrl[0], pin_level_set),
    KEY_PUBLIC_CTRL_DEF(&key_pin_ctrl[1], pin_level_set),
};

初始化键盘,可参考key_board_sample.c文件中的GPIO_Key_Board_Init函数,如:

void GPIO_Key_Board_Init(void)
{
    //硬件io的初始化
    GPIO_InitTypeDef GPIO_InitStruct;
    unsigned int i;

    RCC_KEY_BOARD_CLK_ENABLE();

    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
    for(i = 0;i < ARRAY_SIZE(key_pin_sig);i++)
    {
        GPIO_InitStruct.Pin   = key_pin_sig[i].pin;
        HAL_GPIO_Init(key_pin_sig[i].port, &GPIO_InitStruct);
    }

    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    for(i = 0;i < ARRAY_SIZE(key_pin_ctrl);i++)
    {
        GPIO_InitStruct.Pin   = key_pin_ctrl[i].pin;
        HAL_GPIO_Init(key_pin_ctrl[i].port, &GPIO_InitStruct);
    }

    //初始化键盘
    key_board_init();
    //注册键盘到系统中(矩阵键盘)
    key_board_register(KEY_BOARD_MATRIX, key_public_sig, ARRAY_SIZE(key_public_sig), key_public_ctrl, ARRAY_SIZE(key_public_ctrl));
}

主流程伪代码框架,更多例子参考main_test.c文件:

int main(void)
{
    //初始化硬件io,并注册键盘
    GPIO_Key_Board_Init();
    //初始化定时器,用于按键扫描(1ms)
    init_tmr();

    for(;;)
    {
        if(key_check_state(KEY_UP, KEY_RELEASE))
        {
            PRINTF("KEY_UP KEY_RELEASE\r\n");
        }
        if(key_check_state(KEY_UP, KEY_PRESS))
        {
            PRINTF("KEY_UP KEY_PRESS\r\n");
        }
    }
}

//定时器到期回调处理函数
void tmr_irq_callback(void)
{
    //调用按键扫描核心函数
    key_check();
}

扩展功能长按的使用

首先确保key_board_config.h文件中宏KEY_LONG_SUPPORT已处于使能状态,并且正确设置了宏KEY_DEFAULT_LONG_TRRIGER_TIME的值;

设置按键功能需要对长按进行检测,如:

KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_PRESS_LONG | KEY_FLAG_RELEASE_LONG)

使用例程:

if(key_check_state(KEY_UP, KEY_PRESS_LONG))
{
    PRINTF("KEY_UP KEY_PRESS_LONG\r\n");
}
if(key_check_state(KEY_UP, KEY_RELEASE_LONG))
{
    PRINTF("KEY_UP KEY_RELEASE_LONG\r\n");
}

扩展功能连按的使用

首先确保key_board_config.h文件中宏KEY_CONTINUOUS_SUPPORT已处于使能状态,并且正确设置了宏KEY_DEFAULT_CONTINUOUS_INIT_TRRIGER_TIME和KEY_DEFAULT_CONTINUOUS_PERIOD_TRRIGER_TIME的值;

设置按键功能需要对连按进行检测,如:

KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_PRESS_CONTINUOUS)

使用例程:

if(key_check_state(KEY_UP, KEY_PRESS_CONTINUOUS))
{
    PRINTF("KEY_UP KEY_PRESS_CONTINUOUS\r\n");
}

扩展功能多击的使用

首先确保key_board_config.h文件中宏KEY_MULTI_SUPPORT已处于使能状态,并且正确设置了宏KEY_DEFAULT_MULTI_INTERVAL_TIME的值;

设置按键功能需要多击进行检测,如:

KEY_PUBLIC_SIG_DEF(KEY_UP, &key_pin_sig[0], pin_level_get, KEY_FLAG_PRESS_MULTI | KEY_FLAG_RELEASE_MULTI)

使用例程:

unsigned int res;
res = key_check_state(KEY_UP, KEY_PRESS_MULTI);
if(res)
{
    PRINTF("KEY_UP KEY_PRESS_MULTI:%d\r\n", res);
}
res = key_check_state(KEY_UP, KEY_RELEASE_MULTI);
if(res)
{
    PRINTF("KEY_UP KEY_RELEASE_MULTI:%d\r\n", res);
}

扩展功能组合状态(同一时间轴)

使用例程:

unsigned int key_down_release_long, key_up_release_long;
key_down_release_long = key_check_state(KEY_DOWN, KEY_RELEASE_LONG);
key_up_release_long = key_check_state(KEY_UP, KEY_RELEASE_LONG);
if(key_down_release_long && key_up_release_long)
{
    PRINTF("KEY_DOWN KEY_RELEASE_LONG && KEY_UP KEY_RELEASE_LONG\n");
}

扩展功能组合状态(非同一时间轴)

首先确保key_board_config.h文件中宏KEY_COMBINE_SUPPORT已处于使能状态,并且正确设置了宏KEY_DEFAULT_COMBINE_INTERVAL_TIME的值;

使用例程:

//用于保存注册后的组合状态id
static unsigned int test_id1, test_id2;

//定义要检测的状态
const struct key_combine_t test_combine1[] = {
    { .id = KEY_UP,     .state = KEY_PRESS },
    { .id = KEY_DOWN,   .state = KEY_PRESS_LONG },
    { .id = KEY_UP,     .state = KEY_PRESS },
};
//注册组合状态
test_id1 = key_combine_register(test_combine1, ARRAY_SIZE(test_combine1));

const struct key_combine_t test_combine2[] = {
    { .id = KEY_UP,     .state = KEY_PRESS },
    { .id = KEY_DOWN,   .state = KEY_PRESS },
    { .id = KEY_UP,     .state = KEY_PRESS },
    { .id = KEY_DOWN,   .state = KEY_PRESS },
};
test_id2 = key_combine_register(test_combine2, ARRAY_SIZE(test_combine2));

if(key_check_combine_state(test_id1))
{
    PRINTF("combine test_id1\r\n");
}

if(key_check_combine_state(test_id2))
{
    PRINTF("combine test_id2\r\n");
}

原文:https://gitee.com/wei513723/key_board

直接转载来源:嵌入式电子

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

围观 18

页面

订阅 RSS - 单片机