tag 标签: 嵌入式

相关帖子
相关博文
  • 2021-4-27 09:15
    13 次阅读|
    0 个评论
    2021/04/23 “创龙首款NXP工业评估板,全新i.MX 8惊艳登场” 重磅!创龙科技(Tronlong)今日首次推出NXP工业评估板及核心板—— i.MX 8M Mini ,让大家久等了!! 图1| TLIMX8-EVM评估板正面 图2 | TLIMX8-EVM评估板侧面 (带壳) 图3 | SOM-TLIMX8核心板正面 图4 | SOM-TLIMX8核心板背面 1、 高性能 创龙科技i.MX 8系列产品基于 NXP i.MX 8M Mini 的 4核ARM Cortex-A53 + 单核ARM Cortex-M4多核 处理器设计,主频高达 1.6GHz 。 图5 |i.MX 8M Mini处理器功能框图 2、 工 业级核心板 SOM-TLIMX8是一款高端工业级核心板,通过邮票孔连接方式引出千兆网口、PCIe、USB 2.0、MIPI CSI、MIPI DSI等接口。支持1080P60 H.264视频硬件编解码、1080P60 H.265视频硬件解码。核心板经过专业的PCB Layout和高低温测试验证,稳定可靠,可满足各种工业应用环境。 用户使用核心板进行二次开发时,仅需专注上层运用,降低了开发难度和时间成本,可快速进行产品方案评估与技术预研。 图6 |核心板硬件框图 3、 接口资源丰富 TLIMX8-EVM评估板接口资源丰富,引出 MIPI CAMERA、MIPI/LVDS LCD、HDMI OUT、LINE IN/OUT、PCIe、FlexSPI、USB、RS485、RS232、千兆网口、百兆网口 等接口,板载WIFI模块,支持Mini-PCIe 4G模块,选配外壳直接应用于工业现场,方便用户快速进行产品方案评估与技术预研。 图7 |评估板硬件框图 图8 |评估板硬件资源图解1 图9 |评估板硬件资源图解2 4、 多媒体应用领域 i .MX 8系列以其出色的 视频编解码能力 ,广泛应用于 医疗设备、机器视觉、工业HMI 等领域。 图10 5、 开发资料齐全 创龙科技秉承“因我们的存在,让嵌入式应用更简单”的愿景,i.MX 8系列产品为用户提供丰富的软、硬件开发资料,包括: ( 1 )提供核心板引脚定义、可编辑底板原理图、可编辑底板 PCB 、芯片 Datasheet ,缩短硬件设计周期; ( 2 )提供系统固化镜像、内核驱动源码、文件系统源码,以及丰富的 Demo 程序; ( 3 )提供完整的平台开发包、入门教程,节省软件整理时间,上手容易; (4)提供详细的 ARM + FPGA 架构通信教程,完美解决异构多核开发瓶颈。 开发案例主要包括: •基于Linux 的应用开发案例 •基于ARM Cortex-M4 的裸机/FreeRTOS 开发案例 •基于ARM Cortex-A53 与 Cortex-M4 的核间 OpenAMP 通信开发案例 •基于FlexSPI 的 ARM 与 FPGA 通信开发案例 •基于PCIe 的 ARM 与 FPGA 通信开发案例 •基于H.264 的视频硬件编解码开发案例 •基于H.265 的视频硬件解码开发案例 •基于OpenCV 的图像处理开发案例 •Qt 开发案例
  • 热度 4
    2021-3-21 21:57
    479 次阅读|
    0 个评论
    FreeModbus 从站设计( 8 ) - 用 HAL 库函数理清 Modbus 的数据收发流程 关键词: FreeModbus STM32F103 CubeMX HAL 库 1. 基本框图 如图 1 所示, HAL 库的函数中,与 Freemodbus 协议栈相关的,主要是定时器和串口的操作部分。孔丙火(微信公众号:孔丙火)认为,可以这样简单描述:在协议栈完成初始化后,就将串口( RS485 )设置为接收状态,等待主站的数据,当接收到主站的一个字节的数据后,开启定时器,在 3.5 个字符周期内如果接收到了第二个字节的数据,则将定时器清零重新开始计时,若果 3.5 个字符周期内没有接收到新的字节,则认为一帧数据接收完毕,开始处理数据,并相应地发送回复数据,回复数据也是逐个字节进行发送的。 图 1 2. 接口函数 2.1 vMBPortSerialEnable() 这个函数定义在 portserial.c 中,所有的调用均在 mbrtu.c 和 mbascii.c 中,由于这里我们只实现 RTU ,因此,只关注 mbrtu.c 中调用。在 mbrtu.c 中,共有 5 处调用此函数,分别在 eMBRTUStop() 、 eMBRTUStop() 、 eMBRTUSend() 、 xMBRTUTransmitFSM() 中,这个函数的作用,就是使能或禁止串口的接收或发送中断,状态的转换是根据协议栈的状态机来进行的,孔丙火(微信公众号:孔丙火)认为,看一看 Modbus 协议文本中的状态机图,有助于深入理解协议的运行。基于以上理解, vMBPortSerialEnable() 函数的代码如下: 图2 HAL_UART_Receive_IT(&huart2,&ucUsrUart2Rxbuf,1) ,这个函数是 HAL 库函数,孔丙火(微信公众号:孔丙火)认为,可以这样理解,以中断方式通过串口 2 接收一个字节的数据,存在变量 ucUsrUart2Rxbuf 中,这里取得是 ucUsrUart2Rxbuf 的地址指针。 __HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE) 这个函数是禁止接收中断。在这个函数下面,写了这样一个语句: huart2.RxState = HAL_UART_STATE_READY; 同样的,在发送部分有: huart2.gState = HAL_UART_STATE_READY; 下面简单阐述一下作用。 最初开始弄 FreeModbus 移植的时候,在网上查阅了一些资料,有的是基于 HAL 库的,有的不是,有的是交叉使用,由于学习了一段时间的 HAL 库,感觉其还是比较易用的,孔丙火(微信公众号:孔丙火)就想着能不能完全用 HAL 库的函数来实现的 FreeModbus 移植,于是就有后来的实践。 加这样一个语句,是为了解决在实际调试过程中碰到的问题。刚开始调试的时候发现,单片机作为从站,只能接收一帧数据,第 2 帧以后的数据,不再有反应。由于是调用 HAL_UART_Receive_IT(&huart2,&ucUsrUart2Rxbuf,1); 来使能接收非空中断,调用 __HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE); 来禁用接收非空中断,那就要从 HAL_UART_Receive_IT() 这个函数开始说起,在以前的文章中,孔丙火(微信公众号:孔丙火)说过,这个函数本质上是一个配置函数,配置好接收缓冲区的指针和一次接收的字节数,把这个函数贴出来看一下: 图 3 可以看出,这个函数的主要功能就是设置接收缓存的指针和字节数,然后使能接收非空中断,但在一开始有一个条件判断, RxState ,这其实是对串口接收状态的一个管理,当串口中断方式接收已经被配置过了、接收过程还没有完成的时候,是不能再次配置的,防止接收数据出错。 在这个函数中,配置完了以后,就将 RxState 的状态置为了 HAL_UART_STATE_BUSY_RX ,这个状态是在串口中断处理函数中完成数据接收后,重新设为 HAL_UART_STATE_READY 。在本文的程序中,当从站接收数据完成后(定时器动作,表示一个完整的帧接收完成),协议栈需要将接收非空中断禁掉,但这之前串口仍处于接收状态(调用过 HAL_UART_Receive_IT(&huart2,&ucUsrUart2Rxbuf,1) ),因此 RxState 的状态是 HAL_UART_STATE_BUSY_RX ,这样在下次转为接收时,无法 HAL_UART_Receive_IT ()来进行配置,就是由于上面提到的图 3 红框中的条件判断。因此,在发现此问题后,加了这个语句,确保后续能正常接收,并且这样可以不用修改 HAL 库本身的函数。发送部分是同样的道理。 下一篇精彩继续。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、 PLC 、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 4
    2021-3-18 21:34
    589 次阅读|
    1 个评论
    STM32F103、FreeModbus从站设计(6)-让串口和Modbus初始化的参数同步起来
    FreeModbus从站设计(6)-让串口和Modbus初始化的参数同步起来 关键词:Modbus FreeModbus STM32F103C8T6 CubeMX 移植 1.基本原理 在这一篇文章中,孔丙火(微信公众号:孔丙火)主要介绍协议栈初始化,以及与串口相关的代码修改。串口的初始化函数是CubeMx自动生成的,其波特率等参数最好与freemodbus协议栈初始化参数保持一致,这样方便程序的维护和功能扩展,例如,当需要修改通信的波特率的时候,只需要修改一处就可以了,不用先修改串口的初始化参数,再修改协议栈的参数,这是一个实战例程与纯教程的区别。 本文的总体思路:创建一些全局变量,用于存储通信参数,串口、定时器、Freemodbus协议栈的初始化,都采用这些变量,需要更改的时候,只需改变这些变量的值即可。另外,这些通信参数后续计划存储在flash里面,定时器的参数会随波特率的不同而改变,这些在后续的文章中会阐述。 2.代码修改 2.1关于assert的说明 在Freemodbus的协议栈中,很多地方使用了assert,因此,在Cube生成工程的时候,孔丙火(微信公众号:孔丙火)认为,最好使能enable assert选项,如果没有使能,编译的时候可能出错,这个时候可以勾掉MicroLIB,因为MicroLIB不支持assert。 2.2宏的修改 (1)MB_RTU_ENABLED、MB_ASCII_ENABLED、MB_TCP_ENABLED 这里我们实现的是RTU,只需将MB_RTU_ENABLED定义为1即可。孔丙火(微信公众号:孔丙火)顺便说一句,在基于串口的Modbus中,RTU是必选项,ASCII是可选项,也就是说RTU是必须要实现的。 (2)ENTER_CRITICAL_SECTION( )和EXIT_CRITICAL_SECTION( ) 这两个函数是进入和退出关键进程,这里定义为__disable_irq()和__enable_irq(),后边两个函数是ARM内核函数,作用是禁止中断和使能中断。 #define ENTER_CRITICAL_SECTION( ) __disable_irq() #define EXIT_CRITICAL_SECTION( ) __enable_irq() 2.3协议栈初始化 在main.c文件中的while(1)之前,调用eMBInit( )完成协议栈的初始化,然后调用eMBEnable( )使能协议栈。eMBInit( )的定义如下:eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ),共5个参数,eMode模式:RTU或ASCII,ucSlaveAddress从站地址,ucPort单片机的串口号,ulBaudRate波特率,eParity校验方式,我们这里串口参数是在Cube生产的函数里初始化的,而且串口接收或发送的数据HAL库函数已经处理好了,因此,孔丙火(微信公众号:孔丙火)认为,有用的参数只有eMode,ucSlaveAddress,ulBaudRate,其余两个设错了,其实也没影响(仅限于本文的实现方法)。 在modbus_app.c中定义如下全局变量: uint8_t ucUsrSlaveAddress;//从站地址 uint32_t ulUsrBaudRate;//波特率 eMBParity eUsrParity;//校验方式 uint8_t ucUsrStopBits;//停止位 新建modbus_app.h,在其中进行extern定义,一般在其他c文件中使用。 最终,在main.c中做如下调用: eMBInit( MB_RTU, ucUsrSlaveAddress, 1, ulUsrBaudRate, eUsrParity ); 2.4串口参数初始化 在MX_USART2_UART_Init()中修改,此函数是Cube自动生成的,在usart.c中。 最终函数如下: huart2.Instance = USART2; huart2.Init.BaudRate = ulUsrBaudRate; if(eUsrParity == MB_PAR_NONE)huart2.Init.WordLength = UART_WORDLENGTH_8B; else huart2.Init.WordLength = UART_WORDLENGTH_9B; if(ucUsrStopBits == 1)huart2.Init.StopBits = UART_STOPBITS_1; else if(ucUsrStopBits == 2)huart2.Init.StopBits = UART_STOPBITS_2; if(eUsrParity == MB_PAR_NONE)huart2.Init.Parity = UART_PARITY_NONE; else if(eUsrParity == MB_PAR_EVEN)huart2.Init.Parity = UART_PARITY_EVEN; else if(eUsrParity == MB_PAR_ODD)huart2.Init.Parity = UART_PARITY_ODD; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } 波特率使用了全局变量,单片机的数据位个数和停止位根据全局变量确定。 3.总结 这种方法让让串口和Modbus初始化的参数同步起来了,也方便通信参数的在线修改,更接近实战。 ———————————————— 文章在微信公众号同步推出: 孔丙火 ,关注欣赏更多系列文章。-----单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 2
    2021-1-19 22:47
    513 次阅读|
    0 个评论
    原文: https://zhuanlan.zhihu.com/p/342410727 第一部分:基本概念 1.关键字static的作用 (1)在函数体内:一个被声明为静态的变量,在这一函数被调用过程中,维持其值不变。(该变量值初始化一次) (2)在模块内,函数体外:一个被声明为静态的变量,可以被模块内所用函数访问,但不能被模块外其他函数访问。 (3)在模块内,一个被声明为静态的函数,该函数只能被这一模块内其他函数调用,其他模块无法调用。 2.引用与指针的区别: (1)引用必须初始化,指针不用 (2)引用初始化后不能修改,指针可以改变所指对象 (3)不存在指向空的引用,指针存在空值 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。 引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。 关-注-公-众-号:嵌入式基地 回-复:【面试】 获-取-面-试-资-料 3..h头文件中的#ifndef/#define/#endif作用: 防止头文件被重复引用 4.#include 与#include “”区别: 是从系统指定的路径下寻找;””是先从当前路径下寻找 5.描述实时系统的基本特性: 在特定时间内完成特定的任务,实时性与可靠性。 6.全局变量与局部变量是否存在区别? 全局变量存在静态数据区,局部变量存在栈中 7.堆栈溢出一般是由于什么原因? (1)没有回收垃圾资源 (2)层次太深的递归调用 8.冒泡排序算法的时间复杂度 O(n^2) 9.什么函数不能声明为虚函数? 构造函数 10.队列与栈的区别: 队列:先进先出;栈:先进后出 11.不能做switch()的参数类型 实型 12.局部变量是否能和全局变量重名? 可以。局部变量会屏蔽全局变量。若要使用全局变量,使用::。 在函数内引用该重名变量时,会使用同名的局部变量,而不会使用全局变量(就近原则)。 对一些编译器而言,同一函数内可以定义多个同名的局部变量,例如:在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在该循环内。 13.如果引用一个已经定义过的全局变量? 可以包含其头文件,也可使用关键字extern。 引用头文件:通过引用头文件的方式来引用某个头文件中声明的变量,假如将该变量写错了,在编译阶段会报错。 Extern:通过extern的方式引用,若将变量写错,编译阶段编译器不会报错,在链接阶段会进行报错提示。 14.全局变量能不能定义在可被多个.c文件包含的头文件中? 可以。在不同的C文件中以static的形式来声明同名全局变量。 可以在不同的C文件中声明同名的全局变量,前提是只能在一个C文件中对变量赋初值,此时链接不会报错。 15.for(;1;)有什么问题?什么意思? 与while(1)相同,无限循环 16.do…while()与while…do有什么区别? 前者循环一遍后再判断;后者先判断后循环 17.static全局变量、局部变量、函数与普通全局变量、局部变量、函数有什么区别? (1)static全局变量与非static全局变量区别: 存储方式上相同,都是静态存储。 作用域:非static全局变量的作用域在各个文件中都是有效的;static的全局变量作用域只限于当前源文件中,只初始化一次。 (2)static函数与普通函数作用域不同。Static函数只能在当前源文件中使用,而非Static函数可以在其他源文件中使用。 (3)static局部变量只初始化一次,下次调用使用上次的数值。 18.程序的内存分配 C/C++编译的程序占用的内存分为以下几部分。 (1) 栈区,编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。 (2) 堆区,由程序员分配释放。若程序员没有进行资源回收,程序结束时,可能会由OS回收。与数据结构中的堆是两回事。 (3) 全局区(静态区),全局变量与静态变量是存储在一起的。初始化的全局变量与初始化的静态变量在一块区域,未初始化的全局变量与未初始化的静态变量存储在一起。程序结束后由OS释放 (4) 常量区,存储常量,字符串。程序结束后由OS回收。 (5) 程序代码区,存放函数体的二进制代码。 示例: int a = 0; //全局初始化区域 char *p1; //全局未初始化区域 int main(int argc, char const *argv = “ssssss”; Char *s2 = “bbbbbb”; aaaaaa是在运行时赋值的,bbbbbb是在编译时确定的。 但在以后的存取中,栈上的数组比指针指向的字符串(堆)块。 void main() { char a = 1; char c ; a = p ; return; } 对应的汇编代码 10:a=c ; 004010678A4DF1movcl,byteptr 0040106A884DFCmovbyteptr ,cl 11:a=p ; 0040106D8B55ECmovedx,dwordptr 004010708A4201moval,byteptr 004010738845FCmovbyteptr ,al 第一种:在读取时,直接将字符串中的元素读到寄存器c1中 第二种:先将指针读取到edx中,再根据edx读取字符。 20.什么是预编译,什么时候需要预编译? 预编译又称为预处理,是做代码文本的替换工作,处理#开头的指令,比如拷贝#include包含的头文件代码;#define宏定义的替换。在程序开始编译之前进行。 C语言编译系统在对程序编译之前,先进行预处理。预处理主要提供以下功能: (1)宏定义(2)头文件包含(3)条件编译 21.关键字const含义 const 只读 应用: const int a; //常整型数 int const a; //常整型数 const int *a; //a是一个指向常整型数的指针(指针可以修改,整型数不可修改) int * const a; //a是一个指向整型数的常指针(指针不可以修改,整型数可以修改) const int * const a; //a是一个指向常整形数的常指针(都不可修改) 使用const关键字的理由: (1) 为读代码的人提供非常有用的信息,实际上应用一个参数为常量是为了告诉用户这个参数的应用目的。 (2) 通过给优化器一些附加信息,使用关键字const也许能够产生更紧凑的代码 (3) 合理使用const可以使编译器很自然的保护那些不希望被修改的参数,防止其被无意的代码修改。 22.关键字volatile含义?应用例子: 含义: 一个被定义为volatile的变量是说这个变量可能会被意想不到的改变,这样编译器就不会去假设这个变量的值了。准确的说,优化器在用到这个变量时,必须每次都小心点重新读取这个变量的值,而不是直接使用寄存器中的备份值。 应用: (1)并行设备的硬件寄存器 (2)一个中断服务子程序中会访问到的非自动变量。 (3)多线程应用中被几个任务共享的变量 其他问题: 1. 一个参数既可以是const还可以是volatile吗?为什么? 2. 一个指针可以是volatile吗 3. 下面函数有什么错误 Int square(volatile int *ptr) { Return *ptr * *ptr; } (1) 可以,一个例子为只读的状态寄存器。它是volatile是应为他可能被意想不到的改变。它是const是应为程序不因该去修改它。 (2) 是的,虽然不常见。一个例子为中断服务子程序修改一个指向一个buffer的指针时。 (3) 这段代码的目的是为了返回指针*ptr的平方,但是由于*ptr是一个指向volatile类型的参数,因此编译器将产生类似于下面的代码: int square(volatile int *ptr) { int a, b; A = *ptr; B = *ptr; Return a * b; } 由于*ptr的值可能被意想不到的改变,因此,a与b的值可能不同,结果这段代码返回的结果与预期可能并不相同。 正确代码: int square(volatile int *ptr) { int a; A = *ptr; Return a * a; } 23.三种基本数据模型 按照数据结构类型的不同,将该数据模型划分为层次模型、网状模型、关系模型 24.结构体与联合体有什么区别 共用体,允许在相同的内存地址存储不同的数据类型;可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同内存地址的方式 共用体,占用的内存应该足够存储共用体中最大的成员。 结构体,占用内存是内部所有变量所占空间之和。结构体,每个成员变量都有自己的内存地址。 25.#define与const区别: 1.const有数据类型,宏定义没有数据类型;编译器可以对前者进行安全检查,对于后者不能进行安全检查,只能进行字符替换。 2.有些调试工具可以对const进行调试,而宏定义无法调试。 3.const定义的常量有作用域,而#define不重视作用域,默认定义处到文件结尾。 26.数组与指针区别: 数组要么在静态数据区被创建,要么在栈上被创建。 指针可以随时指向任意类型的内存块。 (1)修改内容上的差别: char a = ‘x’; Char *p = “world”;//p指向的是常量字符串 P = ‘x’;//编译器无法发现该错误,运行时会报错 (2)使用sizeof()运算符计算容量: 数组可以使用sizeof计算出容量,而sizeof(p)计算得到的是一个指针变量的字节数,一般为4个字节,而不是p所指向的内存容量。 注意:当数组作为函数形参进行传递时,该数组自动退化为同类型指针: void TestBufferSize1(char a ) { printf("buffer size = %d \r\n", sizeof(a)); } int main(){ char b = "12345"; printf("b size = %d \r\n", sizeof(b)); TestBufferSize1(b); TestBufferSize2(b); system("pause"); } 27.分别写出bool、int、float、指针类型的变量a与零比较的语句: (1)bool: If(!a) or if(a) (2)int If(a == 0) (4)float: Const EXPRESSION EXP = 0.000001 If(a -EXP) (5)指针: If(a != NULL) or if(a == NULL) 28.如何判断一段程序是由c编译还是由c++编译程序编译的? #ifdef __cplusplus cout << "c++"; #else printf("c"); #endif 29.讨论含参数的宏与函数优缺点: 带参宏 函数 处理时间 编译时 程序运行时 参数类型 没有参数类型问题 定义实参,形参类型 处理过程 不分配内存 分配内存 程序长度 变长 不变 运行速度 不占用运行时间 调用和返回占用运行时间 30.使用两个栈来实现一个队列的功能 设两个栈A,B,并将其初始化为空 入队: 将新元素push入栈A; 出队: (1) 判断栈B是否为空 (2) 若不为空,则将栈A中的所有元素依次pop出,并push到栈B (3) 将栈B的栈顶元素pop出 这样的实现,入队与出队的平摊复杂度都为O(1) 31.位操作 给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a的bit 3。在以上操作中,保持其它位不变。 (1) 通过宏定义,以及bit masks操作。这种方法可移植性高 #define BIT3 (0x01 << 3) static int a; void SetBit3(void) { a |= BIT3; } void ClearBit3(void) { a &= ~BIT3; } (2) 使用bit fields。Bit fields是被扔到C语言死角的方式。这种方法保证了你的代码在不同编译器之间是不可移植的,同时也保证代码是不可重用的。 32.访问固定的内存位置 嵌入式编程中,经常会去访问某个特定内存位置的数据。在某个工程中,需要设置一个绝对地址为0x56a3的整型变量的值为0x3344。编译器是一个纯粹的ANSI编译器。 (1) 方法一: int *ptr = NULL; ptr = (int *)0x56a3; *ptr = 0x3344; (2) 方法二: *(int * const)(0x56a3) = 0x3344; 推荐使用第一种。 33.中断 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供了一种扩展—让标准C支持中断。 具体代表的事实是,产生了一个新的关键字__interrupt。 下面这段代码使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评价这段代码。 __interrupt double compute_area(double radius) { double area = PI * radius * radius; printf("\nArea = %f", area); return area; } (1) ISR不能返回一个值 (2) ISR不能传递参数 (3) 在许多编译器中,浮点数一般是不可重入的。并且,ISR应该是短而有效的,在ISR中做浮点数运算是不明智的。 (4) Printf()经常有重入与性能上的问题 34.typedef Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事情。 例: #define dPS struct s * Typedef struct s *tPS; 以上两种情况都是要定义dPS和tPS作为一个指向结构s的指针。哪种方法更好? 答:typedef更好,原因请看下面示例。 dPS s1,s2; tPS s3,s4; 第一个展开为struct s *s1,s2;此时,s1指向的是结构的指针,而s2是一个实际的结构。 35.写一个“标准”的宏 (1)交换两个参数的宏定义 #define SWAP(a, b)\ (a) = (a) + (b);\ (b) = (a) - (b);\ (a) = (a) - (b);\ (2)输入两个参数,输出较小的参数 #define MIN(a, b) (((a) < (b)) ? (a) : (b)) (3)1年中有多少秒?(忽略闰年) #define SECONDS_OF_YEAR (60 * 60 * 24 * 365)UL (4)已知一个数组table,使用宏定义求出数组元素的个数 #define TABLE_SIZE (sizeof(table) / siezof(table ))
  • 热度 3
    2020-12-26 14:07
    734 次阅读|
    0 个评论
    关键词:Modbus FreeModbus STM32F103C8T6 CubeMX 移植 摘要:STM32F103、FreeModbus从站设计(5)-如何将FreeModbus代码加入keil。详细阐述如何获取freemodbus代码,代码的大体结构,如何将freemodbus加入keil工程。讲解了注意事项,全实战演示,实际可用。 1.前提 (1)管脚配置 图1 (2)外设资源 图2 2.FreeModbus协议代码添加 (1)获取代码 最新的FreeModbus协议代码可以在其官网下载,孔丙火(微信公众号:孔丙火)提醒,是下载免费的那个版本,仅支持从站,支持主站的版本是收费的。 图3 (2)添加代码到工程 Freemodbus的代码中需要复制的是modbus和demo两个文件夹,把modbus文件夹整个拷贝到工程目录中,把demo-bare中的port文件夹拷贝到工程目录中,把demo-bare中的demo.c拷贝到工程目录的Src文件夹中,孔丙火(微信公众号:孔丙火)把它改名为:modbus_app.c,如图4。 图4 在keil工程中新建一个Group,命名为:modbus,把modbus文件夹中所有文件添加到其中,然后新建一个名为prot的Group,把port文件夹中所有文件添加到其中,把modbus_app.c添加到名为Application/User的已有Group中,如图5。 图5 (3)头文件包含路径修改 在工程的魔术棒选项中,把添加到工程中的所有子文件夹添加到包含路径中,如图6所示。 (4)编译 把modbus_app.c中的main函数注掉,因为一个工程中只能有一个main函数,然后编译,没有错误,有4个warning,这个在后续的代码修改后,会消除。 图6 单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。 文章在微信公众号(孔丙火)同步推出,欢迎欣赏更多精彩文章。
相关资源
广告