搜档网
当前位置:搜档网 › Freemodbus RTU在stm32上的移植分析

Freemodbus RTU在stm32上的移植分析

Freemodbus RTU在stm32上的移植分析
Freemodbus RTU在stm32上的移植分析

Freemodbus RTU在stm32上的移植分析

最近用到free modbus,需要在stm32上进行移植,以作modbus-RTU之用。现成协议的东西用起来很方便,现成源码很快就可以为设计者所用,也是当初制定标准的初衷吧。首先下载最新的modbus源码,所谓技术更新换代的比较快,用就用最新的东西,协议嘛也要下载最新的,google一下,在https://www.sodocs.net/doc/f62271635.html,/index.php?idx=5下载最新的版本freemodbus-v1.5,下载最新的协议不仅可以防止被人改动导致自己做无用功,保持原生态也可以很好的与制定者进行交流。解压freemodbus-v1.5,目录结构很清晰,主要有四个文件件,分别是demo、modbus、tools、doc。其中tools为上位机测试modbus程序,doc 为一些说明文件先不讨论。有用的是demo以及modbus。打开demo,没有看到stm32的工程文件,有一个叫BARE的文件夹,是一些不包括任何处理器的部分源代码,我们就用这个建立工程文件。为了给以后移植modbus-TCP带来方便,这里直接打开之前测试好的基于ENC28J68的LwIP的stm32工程,在其中导入各个文件。

导入前的原始以太网测试工程将prot以及modbus文件夹拷贝到工程文件夹下,导入工程,将demo中的main 等几个函数拷贝到原先main.c中,注释掉原先的mian函数,就成了这个样子:

先理清所有依赖关系,肯定出现一大堆找不到头文件宏定义什么的错误,这个在keil中将文件夹的路径添加到include path中即可,非常方便。rebuild一下,发现有两个错误:

原来Keil4不支持inline这个关键字,直接将其删掉,编译出现了:..\Output\STM32-DEMO.axf: Error:L6218E: Undefined symbol __aeabi_assert (referred from mbascii.o).出现的这个问题,各种百度以及谷歌,找了半天也没找到解决方法。这里不得不说百度虽然本土化做得很好,可是以英文作为关键词时,往往搜出一大堆不相干的东西,基本上搜不到国外的网页;谷歌本土化做的不好,服务器响应比较慢。想起360新出了搜索引擎,赶紧去试试,还真的有惊喜,在一篇帖子中写道“MicroLib并不支援assert(),所以才会出现错误讯息”,原来原工程使用了微库,在target中钩掉USE MicroLIB编译就可以通过了。昨天看优库老友记采访周鸿祎说360做搜索引擎,作为360的忠实用户应该支持一下。任何一个公司想做的更好,必须注意用户体验。

下面开始正式的移植以及分析和测试工作:

一、对于时钟的移植:由于modbus RTU模式需要定时器的支持,所以第一步先移植与定时器相关的函数。在porttimer.c中添加BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )的实现,实现50us的基时时钟。添加打开和关闭时钟的函数void vMBPortTimersEnable( )以及voidvMBPortTimersDisable( ),还有超时中断函数voidTIM4_IRQHandler(void)

二、对串口通信的移植

无论是modbus ASCII还是RTU模式,都以串口通讯做为载体,需要添加串口的使能BOOL xMBPortSerialInit,收发中断的使能voidvMBPortSerialEnable( BOOLxRxEnable, BOOL xTxEnable ),发送以及接收BOOL xMBPortSerialPutByte( CHAR ucByte ),BOOL xMBPortSerialGetByte( CHAR * pucByte ),这几个函数没什么好说的,有两个中断函数我比较好奇,就是static void prvvUARTTxReadyISR( void )以及staticvoid prvvUARTRxISR( void ),就是一个发送中断一个接收中断,为什么是这样的名字呢,stm32串口发生中断怎么去调用它们呢,如果换成其他单片机,为什么是这样的一个名字呢?原来在freemodbus中并没有提供中断函数的具体名称,还需要根据自己使用的处理器自己添加中断处理函数voidUSART1_IRQHandler(void),在其中调用上述两个发送和接收的函数。

三、第一个功能单元——读写寄存的支持

FreeModbus源码包中BARE工程文件的main.c中定义了

eMBErrorCode eMBRegInputCB()、eMBErrorCode eMBRegHoldingCB()、eMBErrorCode eMBRegCoilsCB()以及eMBErrorCode eMBRegDiscreteCB()四个函数与从机寄存器做了接口,并给出了eMBErrorCode eMBRegInputCB()的例子,在工程中建立portreg.c来实现这几个函数。

其中eMBErrorCode eMBRegInputCB()为读寄存器的值,eMBErrorCode eMBRegHoldingCB()为向单

片机写入寄存器的值,eMBErrorCodeeMBRegCoilsCB()为读写多个开关量的函数,eMBErrorCodeeMBRegDiscreteCB()为读多个离散开关量的函数。

为了方便测试,我们只先实现第一个eMBErrorCodeeMBRegInputCB()读连续多个寄存器值的函数,比如实现读GPIOA-GPIOG的值。

定义

#define REG_INPUT_START 0

#define REG_INPUT_NREGS 7

在eMBErrorCode eMBRegInputCB()开始部分加入度寄存器值语句,以备查询时使用。

比如参数UCHAR *pucRegBuffer, USHORT usAddress = 0, USHORT usNRegs = 3 就是读取端口GPIOA-GPIOC的值

usRegInputBuf[0] =GPIO_ReadInputData(GPIOA);

usRegInputBuf[1] =GPIO_ReadInputData(GPIOB);

usRegInputBuf[2] =GPIO_ReadInputData(GPIOC);

usRegInputBuf[3] =GPIO_ReadInputData(GPIOD);

usRegInputBuf[4] =GPIO_ReadInputData(GPIOE);

usRegInputBuf[5] =GPIO_ReadInputData(GPIOF);

usRegInputBuf[6] =GPIO_ReadInputData(GPIOG);

这部分定义好,我们看一下freemodbus运行的流程。

四、freemodbus运行的软件仿真分析

在main函数中可以看到

int main( void ){

eMBErrorCode eStatus;

eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );

/* Enable the Modbus Protocol Stack. */

eStatus = eMBEnable( );

for( ;; ){

( void )eMBPoll( );

/* Here we simply count the number ofpoll cycles. */

usRegInputBuf[0]++;

}

}

(1)其中调用了

eMBErrorCode eMBInit( eMBMode eMode, UCHARucSlaveAddress,

UCHAR ucPort, ULONGulBaudRate, eMBParity eParity );

其目的就是选择要使用的模式是RTU、ASCII码还是TCP方式,如果选择的模式是RTU或ASCII模式其他都是串口的一些设置;如果选择的是TCP模式,需要调用到eMBErrorCode eMBTCPInit( USHORT usTCPPort )只需要制定端口号即可。这里我们先用RTU模式做测试。在这同时也对定时器进行了初始化。

(2)之后调用了

eMBErrorCode eMBEnable( void );来使能modbus协议栈,其中调用pvMBFrameStartCur( ),在eMBInit根据模式选择的不同,pvMBFrameStartCur( )会有不同的原型,这里选用的是RTU模式,那么将调用eMBRTUStart,其中调用了vMBPortSerialEnable vMBPortTimersEnable来时能串口和定时器,使能了超时定时器,故经过T35时间后,发生第一次超时中断,在中断中,向协议栈发送消息EV_READY(Startupfinished),并调用voidvMBPortTimersDisable( )关闭超时定时器,同时将eRcvState设为STATE_RX_IDLE。此时,协议栈可以接收串口数据。

(3)最后调用

eMBErrorCode eMBPoll( void )用来检测协议栈状态用于处理消息。

五,编译后进行软件仿真查看协议栈具体运行流程

打开keil软件仿真器,软件仿真进行单步运行,如下图所示。

对工程进行软件仿真

(1)执行main()中eStatus = eMBInit( MB_RTU, 0x0A, 0, 9600,MB_PAR_NONE );,我们进去看一下,可以看到,我们初始化的slave address是0x0A,modbus支持1-247个从地址。我们制定的是RTU模式,所以要对RTU一些参数进行赋值,如所调用的初始化函数为

pvMBFrameStartCur = eMBRTUStart;

pvMBFrameStopCur = eMBRTUStop;

peMBFrameSendCur = eMBRTUSend;

peMBFrameReceiveCur =eMBRTUReceive;

pvMBFrameCloseCur =MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;

pxMBFrameCBByteReceived =xMBRTUReceiveFSM;

pxMBFrameCBTransmitterEmpty =xMBRTUTransmitFSM;

pxMBPortCBTimerExpired =xMBRTUTimerT35Expired;

其中包括了modbus的启动、停止、发送以及接收数据的函数具体形式,其中比较关心的是xMBRTUReceiveFSM、xMBRTUTransmitFSM这两个函数,规定了modbus状态转换的状态机,其中xMBRTUReceiveFSM为接收状态机,如下图所示:

xMBRTUReceiveFSM实现的modbus接收状态机,可以这样描述以上状态转换状态,上电启动或复位进入STATE_RX_INIT状态,为了防止协议栈在初始化过程中就收到串口数据,要放弃这个无效的数据,要先等待一个T35时间,过了这个时间才进入STATE_RX_IDLE状态,开始接收数据。实际上只要注意系统启动顺序,这个问题还是可以避免的。进入STATE_RX_IDLE状态后,从收到第一个字符数据开始启动定时器进入STATE_RX_RCV状态,每接收一个字节都要重新复位定时器开始定时,直到出现一个T35超时。如果接收的数据符合规定的数据格式,那么整帧数据接收完毕,又回到STATE_RX_IDLE状态,如果数据过长那么就到STATE_RX_ERROR状态,等待整帧接收完毕,放弃整个帧然后进入STATE_RX_IDLE状态。当然实现所有状态的状换还需要BOOL xMBRTUTimerT35Expired(void )函数。

其中xMBRTUTransmitFSM为发送状态机,如下图所示:

xMBRTUTransmitFSM实现的modbus发送状态机,说到发送状态机就简单多了,因为不用去判断自己发送的时

候是否会超时,基本上就是没需求就在STATE_TX_IDLE状态,要发送就进入STATE_TX_XMIT状态。看到xMBRTUTimerT35Expired这里想到一个问题,在timer初始化的时候设定的时基是50us的时间就中断一次,那么modbus是如何实现不同波特率下3.5个字符时间判断的呢?我们看到在timer中断中调用了pxMBPortCBTimerExpired这个函数,但是实际上T35超时函数调用的是xMBRTUTimerT35Expired这个函数,它们之间是什么关系呢?发现在eMBRTUInit中对xMBPortTimersInit( ( USHORT ) usTimerT35_50us)进行了重新初始化,如果波特率大于19200,那么就采用固定的1800us时间,否则就用3.5个字符时间间隔。我觉得之所以在波特率大的时候才用固定的时间可能为了防止出现判断时间过小时误判了正常的线路延迟的原因。(2)运行到eStatus = eMBEnable( )这里。这个比较简单,就是激活协议栈以及串口和定时器而已。(3)运行到( void )eMBPoll( ),进去看一下。这里面比较关键的地方就是if( xMBPortEventGet( &eEvent ) == TRUE )控制了一个事件处理状态机,这就要问了,这个事件处理是个啥玩意?也许进去看看就可以知道了。原来在这个获取事件的玩意还有个叫事件队列的东西*eEvent = eQueuedEvent,原来这是上述两个状态机和一个超时函数中会反馈一些事件例如xMBPortEventPost( EV_FRAME_SENT ),来供调度。具体调度方法不去深究。

六、对modbus RTU模式的接收发送机制分析

明白了RTU模式的运行流程,下面对RTU模式的接收和发送模式的分析。

(1)我们用于使用的接收buf是哪个呢?什么状态时数据是有效的?

对于这个问题,我们还得从xMBRTUReceiveFSM入手,可以发现当进入STATE_RX_IDLE状态时可能是一帧数据传输完毕,到mbrtu.c 中的eMBInit里看一下,显然STATE_RX_IDLE接收了第一个字节后,STATE_RX_RCV将剩下的字节放到了ucRTUBuf中,usRcvBufferPos为接收的数据长度,那么肯定应该在经过一个T35的时间后结束的对不对,去看看就知道了。在xMBRTUTimerT35Expired函数中发现了以下语句:

case STATE_RX_RCV:

xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED );

break;

不就说明当eMBPoll 收到EV_FRAME_RECEIVED消息的时候,告诉eMBPoll新的一帧数据来了,要去ucRTUBuf中对usRcvBufferPos的数据进行处理吗,这就对了!我们在返回到eMBPoll这个函数中一探究竟。

现在再看事件探测器if( xMBPortEventGet( &eEvent ) == TRUE ) 就明朗多了,当触发case EV_FRAME_RECEIVED:时,调用peMBFrameReceiveCur去获取消息帧,这个函数的实现是谁呢?就是eMBRTUReceive!打开一看就一目了然。eMBRTUReceive把ucRTUBuf的数据信息取出来通过eStatus =peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); 重新赋给了从机地址ucRcvAddress、帧内容开始地址ucMBFrame、数据长度usLength(不包括地址位以及CRC校验位)。

(2)消息取出来了,怎么用?还是回到eMBPoll接着看case EV_FRAME_RECEIVED:接下来干啥了,检查消息是不是给我们的,要是不是广播的消息并且没有发生异常我们就进行处理,我们的地址在eMBInit中已经设定。于是就到了case EV_EXECUTE:状态,接下来就要看发来的数据帧是干什么的了,这要通过功能码来判断。

Freemodbus支持以下功能码:

Modbus支持的功能码

如果检测到该帧数据是协议栈支持的功能码,就调用相应的函数进行处理,比如说Read input register,就会调用在mb.c中定义的static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX]这个二维数组中注册的eMBFuncReadInputRegister函数进行处理。还记得我们之前实现的eMBRegInputCB函数有什么区别呢,为什么调用的不是这个函数呢,其实仔细看下eMBFuncReadInputRegister这个函数就行了,原来eMBFuncReadInputRegister调用了eMBRegInputCB这个函数!还以为之前搞错了。

看这个函数之前先看主机发送过来的代码格式:

查询输入寄存器命令格式

发送的数据帧里包括从机地址、功能码,寄存器起始地址、以及读寄存器的长度、CRC校验。eMBFuncReadInputRegister要做的事儿就是读出寄存器地址以及寄存器长度后调用eMBRegInputCB,读取成功后返回一个MB_ENOERR状态,表明没有错误发生。

(3)接着看eMBPoll中的case EV_EXECUTE:执行完

eException= xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );这个处理函数,寄存器值读出来了,帧格式也写好了,什么地方调用发送函数了呢?找了大半天,终于在串口中断这个地方看到了,原来当串口发送完成时调用了prvvUARTTxReadyISR,又调用了pxMBFrameCBTransmitterEmpty,也就是xMBRTUTransmitFSM 发送状态机,当满足STATE_TX_XMIT状态时,调用xMBPortSerialPutByte将数据发送出去。

那么是什么东西使发送状态机从STATE_TX_IDLE变化到了STATE_TX_XMIT状态呢?我们的eMBRTUSend似乎没有用到?那么到eMBRTUSend里面去看看他做了什么。原来它做的事情就是当状态机处于STATE_RX_IDLE接收空闲的时候把发送缓冲区pucSndBufferCur的数据填写好从机地址、对其中数据进行CRC16校验,把状态机置为STATE_TX_XMIT,使能串口。

到底是谁调用了eMBRTUSend呢?真是一个问题接着一个问题呀。搜一下peMBFrameSendCur这个函数,原来还是在eMBPoll中的case EV_EXECUTE:中,如果不是广播消息的话,我们就返回一条消息,于是调用了peMBFrameSendCur。

说了大半天可能都给搞糊涂了,整理下发送和接收的整体思路:

协议栈以及定时器初始化T35第一次超时—>eMBPoll STATE_RX_IDLE—>收到数据中断—>prvvUARTRxISR—>pxMBFrameCBByteReceived—>xMBRTUReceiveFSM接收数据—> STATE_RX_RCV—>T35超时—> eMBPollEV_FRAME_RECEIVED(peMBFrameReceiveCur->eMBRTUReceive)提取完整数据帧—> eMBPoll case EV_EXECUTE:xFuncHandlers[i].pxHandler(eMBRegInputCB)对接收的数据进行处理—> peMBFrameSendCur—>eMBRTUSend(&STATE_RX_IDLE)—>STATE_TX_XMIT 串口发送完成中断—> prvvUARTTxReadyISR—> FSMpxMBFrameCBTransmitterEmpty—>xMBRTUTransmitFSM(& STATE_TX_XMIT)—>xMBPortSerialPutByte—>发送数据。

这样是不是明白了很多,freemodbus状态机写的还是很巧妙的。

七、RTU模式的测试

(1)测试软件

测试软件采用串口调试助手或modbus调试精灵V1.024,外加一个CRC校验码计算工具,如下图所示:

串口助手测试modbus

(2)测试命令,测试读多个输入寄存器的值,即eMBFuncReadInputRegister

发送地址为:0A ,发送命令代码为04,寄存器开始地址为00 00 (GPIOA),寄存器个数为0001(GPIOA)

发送0a 04 00 00 00 01 30 B1,无反应,无任何数据返回。

看一下出现这个问题可能的原因。一是modbus接收不到数据帧,这个通过debug已经排除,可以接收到CRC

验证码正确的数据帧,那么一定是modbus不能发送数据,先看看简单串口能不能发送数据,在main里通过USART1Write()进行测试发现发送没问题。

在上一节的分析可以看出,只有满足两个条件MB协议栈才可以返回数据,一是当接收的数据处理完调用了eMBRTUSend(&STATE_RX_IDLE)—>STATE_TX_XMIT使接收状态机进入STATE_TX_XMIT状态;二是串口发送完成中断,从而调用发送状态机进行发送数据。那么先验证第一条,在eMBPoll:peMBFrameSendCur 位置设置断点,看看有没有调用eMBRTUSend。可以看到确实可以满足这一条件,由eMBRTUSend触发STATE_TX_XMIT 状态。

排除了这一可能,会不会进不到串口发送完成中断中断呢?同样在中断函数中设置断点查看仿真结果。果然问题出现在无法进入中断语句去调用发送状态机,那么这个中断触发需要满足什么条件呢?

//发生发送完成中断

if(USART_GetITStatus(USART1, USART_IT_TC) == SET)

{

prvvUARTTxReadyISR();

//清除中断标志

USART_ClearITPendingBit(USART1, USART_IT_TC);

}

网上搜了一下,发现在USART的发送端有2个寄存器,一个是程序可以看到的USART_DR寄存器,另一个是程序看不到的移位寄存器,对应USART数据发送有两个标志,一个是TXE=发送数据寄存器空,另一个是TC=发送结束。当USART_DR中的数据传送到移位寄存器后,TXE被设置,此时移位寄存器开始向TX信号线按位传输数据,所有位发送结束时(送出停止位后)硬件会设置TC标志。

试试改成USART_IT_TXE这个标准来引发中断,果然就好用了!

发送0a 04 00 00 0001 30 B1,返回0A 04 02 00 00 1C F1

那么为什么可以进入USART_IT_TXE中断就不能进入USART_IT_TC中断呢?

原来在vMBPortSerialEnable中断控制使能函数中控制的是USART_ITConfig(USART1, USART_IT_TXE, ENABLE)这个中断,并不是USART_IT_TC这个中断,没有使能进了中断才怪呢。

究其USART_IT_TC,即Transmission Complete,需要先发送一个字节后才进入中断,这里称为“发送后中断”。原来是发送完数据引发中断去处理一些事情的意思,而USART_IT_TXE,即发送寄存器空中断,当使能TXEIE 后,只要Tx DR空了,就会产生中断。所以,发送完字符串后必须关掉,否则会导致重复进入中断。这也是和TC 不同之处。数据是返回来了,但是读取GPIOA的数据怎么是全0呢,这显然不正确,再回到eMBRegInputCB()函数中去看一下,接收的地址怎么从1开始而不是设定的从0开始?去eMBFuncReadInputRegister看一下,原来不知道什么原因解析完地址以后出现了usRegAddress++,百度看别人也有类似的问题导致通信格式不正确,注释即可。到这里关于freemodbus RTU在STM32上的移植就算结束了,移植的工作不多,但想把协议栈是如何工作的搞清楚需要花一点时间,本文如有错误和不妥的地方还希望请多交流指正

0.版权声明:

本文著作权归属作者本人所有,提供广大网友学习分析用,如需在发表作品中引用,请联系作者本人。https://www.sodocs.net/doc/f62271635.html,

作者ID:smily,百度文库id:mcs3000,电子邮件:guhongliang2000@https://www.sodocs.net/doc/f62271635.html,。本人保留署名权。如需转载请包含本版权声明。如果本文有不准确之处,欢迎与作者讨论,QQ:83414576。本人不对使用文中技术造成的后果负责。本文分析基于FreeModbus1.50.可以与作者联系获取pdf或者docx格式文档。

1. FreeModbus协议分析

协议必须首先调用初始化功能eMBinit()函数。后调用eMBEnable(),最后,在循环体或者单独一个任务中调用eMBPoll()函数。

2.应用层协议

2.1.系统的启动

2.1.1. eMBInit()函数的源码分析

以RTU方式为例,首先,检查调用的地址是否合法。如不合法,返回错误。如果合法则继续执行,

首先,针对RTU方式还是ASCII方式,选择不同的编译模块。

对需要调用的函数指针进行复制。如果移植需要改变其他用途,则要修改相应的指针,包括如下赋值:

pvMBFrameStartCur = eMBRTUStart;

pvMBFrameStopCur = eMBRTUStop;

peMBFrameSendCur = eMBRTUSend;

peMBFrameReceiveCur = eMBRTUReceive;

pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;

pxMBFrameCBByteReceived = xMBRTUReceiveFSM;

pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;

pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;

然后调用eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );具体初始化通讯端口。

2.1.2. eMBRTUInit

eMBRTUInit这个函数主要干两件事:

第一,初始化串口:

if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )

{ eStatus = MB_EPORTERR;}

这个函数在portserial.c中,需要用户在移植的时候根据自己的处理器编写。

第二,初始化计时器:首先要根据波特率计算一下是3.5~5.0个字节周期的时间,然后再调用xMBPortTimersInit( ( USHORT ) usTimerT35_50us ),初始化计时器。这个函数在porttimer.c中,需要用户在移植的时候根据自己的处理器编写。

2.1.

3. eMBEnable源码分析

首先,看看Modbus功能是否是被关闭的,如果不是被关闭(可能是没有被初始化或者已经打开),就返回错误。

如果是disable状态,就干下面两件事:

●调用pvMBFrameStartCur()。由于这是个函数指针,在模块eMBInit中,指向了eMBRTUStart函数

?在源代码中有这样一段注释:,意思是,首先设置成STATE_RX_INIT,然后打开计时器,等待t3.5以

后,进入STATE_RX_IDLE状态。

?看源代码中,首先有设置Receiver的状态,后调用vMBPortSerialEnable,设置接收状态,然后打开

定时器。

?当定时器中断后,自动调用中断服务程序,在中断服务程序中,只调用了pxMBPortCBTimerExpired,

而这是一个函数指针,在RTU方式初始化时,被指向了xMBRTUTimerT35Expired()函数。

? xMBRTUTimerT35Expired函数在mbrtu.c中,在这里,我们只看第一种方式,就是进入初始化状态,

在t35时间以后,只调用了一个xNeedPoll = xMBPortEventPost( EV_READY );

? xMBPortEventPost函数就是在事件队列里加了一个EV_RDY事件。

●然后,将eMB状态改为使能状态,

●初始化结束。

2.2.总线侦听eMBPoll()

首先,判断系统是否被使能,如果没有,则返回错误值。

然后,检查是否有事件发生,如果有,则根据不同类型的事件响应:

●如果是EV_RDY,表示系统刚刚进入侦听状态,则什么都不做;

●如果状态为EV_FRAME_RECEIVED,也就是接收到完整的帧,做下面两件事情:

?调用eStatus=peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength )。这是一个函数指

针,在eMBInit中,被初始化指向eMBRTUReceive。

? eMBRTUReceive这个函数首先校验帧的长度和CRC,然后从协议中解析出地址、数据和长度。

?然后检查地址,如果是广播地址或者是本机地址,就调用xMBPortEventPost( EV-EXECUTE),将接收

器的状态更改为EV_EXECUTE。

●如果状态为EV_EXECUTE,就在函数列表中检查,有没有与命令字段相符合的函数来解析相应则执行该函数,

否则返回非法功能代码。

2.3.数据发送

发送数据通过指针eMBRTUSend,调用eMBRTUSend函数。

2.3.1. eMBRTUSend函数

这个函数的作用就是打包,将数据打包成帧。

●首先,检查接收状态。因为MODBUS是基于RS-485半双工通讯,所以当正在接收数据时,不发送该帧。

●如果总线空,就将数据打包,将地址和CRC加入数据帧

●将总线状态改为发送。

2.4.功能注册

●对于指定的功能代码,需要一个功能回调函数来处理,格式如下。

eMBException eMXXXXXX ( UCHAR * pucFrame, USHORT * usLen )

●需要通过函数eMBRegisterCB(功能代码,函数名)加到处理代码中。具体源码分析从略。

2.4.1. prvvUARTTxReadyISR()

总线状态改为发送后,会在发送缓冲时,自动调用prvvUARTTxReadyISR()中断服务程序。prvvUARTTxReadyISR()只调用了一个函数,就是pxMBFrameCBTransmitterEmpty ()。

2.4.2. pxMBFrameCBByteReceived ()

pxMBFrameCBTransmitterEmpty ()是一个指针,指向了xMBRTUTransmitFSM函数。

3.数据链路层协议数据链路层是最基本的打包部分,将数据打包成帧,送到应用层。在数据链路层协议中,使用

中断方式来接受。那么每次接收到字符就自动调用接收字符的ISR程序。按照规定,应该将中断服务程序安装给prvvUARTRxISR(void)函数。实际上这个函数只调用了一个函数:

pxMBFrameCBByteReceived(),这个指针调用了xMBRTUReceiveFSM函数。

3.1. xMBRTUReceiveFSM()函数函数首先检查是不是处于发送状态。如果处于发送状态,直接退出。

●首先调用xMBPortSerialGetByte( ( CHAR * ) & ucByte ),获取从串口读到的字符。

●然后检查接受状态:

?如果是错误状态或者处于初始化状态,那么直接等待,错过该帧。

?如果是STATE_RX_IDLE空闲状态,则将指针重置,将收到的第一个字节存储到缓冲区,并将状态改为

STATE_RX_RCV状态。

?如果处于接收状态,就判断,如果缓冲区未满,就将收到的字节放入缓冲区,否则改为错误状态。

●不管在任何状态,最后都开启了t35计时器。在t35结束的时候,通过指针调用了xMBRTUTimerT35Expired()

函数。

● xMBRTUTimerT35Expired()函数检查状态,如果是接收状态那就表明,已经有t35这么长的时间里,没有收

到任新字节,当前的帧结束。在队列里增加一个EV_FRAME_RECEIVED事件。

●如果是错误状态,什么都不做。

●然后关掉计时器,将状态改为空闲。

3.2. xMBRTUTransmitFSM()函数

xMBRTUTransmitFSM首先判断总线是否忙,如果忙,则终止。如果不忙,则继续,根据发送状态变量:

●如果当前为STATE_TX_IDLE(空闲)状态,则打开端口发送

●如果当前状态为STATE_TX_XMIT,则进一步判断发送队列是否为空,

?如果不空,则发送下一个字符

?如果空,说明发送完成,关闭发送端口,改为侦听,并将状态改为空闲。

4.传输控制

除了传输控制以外,还有传输控制的若干函数。通过下面几个指针来调用:

pvMBFrameStopCur()

pvMBFrameCloseCur()

4.1. pvMBFrameStopCur()函数

pvMBFrameStopCur是一个函数指针,在RTU方式下,它指向eMBRTUStop()函数。该函数做下面几件事情:

●关闭侦听和发送

●关闭定时器

4.2. pvMBFrameCloseCur()函数

这个指针指向一个叫做vMBPortClose()的函数,该函数目前只有在mbport.h中的声明,而没有实现。需要等到后面的版本再实现。

STM32学习笔记

输入模式初始化GPIOE2,3,4 ①IO口初始化:GPIO_InitTypeDef GPIO_InitStructure; ②使能PORTA,PORTE时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE); ③PE.2.3.4端口配置:GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; ④设置成(上拉)输入:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; ⑤GPIO_Init(GPIOE, &GPIO_InitStructure); 输出模式初始化 ①IO口初始化:GPIO_InitTypeDef GPIO_InitStructure; ②使能PB,PE端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); ③3LED0-->PB.5 端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; ④设置(推挽)输出模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; ⑤设置IO口速度为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; ⑥说明初始化哪个端口GPIO_Init(GPIOB, &GPIO_InitStructure); 在LED灯试验中初始为高电平灭GPIO_SetBits(GPIOB,GPIO_Pin_5); 再初始化相同发输出模式时③④⑤可省略例如(经实验初始化恰好为不同IO口相同IO序号③可省略,应该不规范吧) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出,IO口速度为50MHz GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高 1,头文件可以定义所用的函数列表,方便查阅你可以调用的函数; 2,头文件可以定义很多宏定义,就是一些全局静态变量的定义,在这样的情况下,只要修改头文件的内容,程序就可以做相应的修改,不用亲自跑到繁琐的代码内去搜索。 3,头文件只是声明,不占内存空间,要知道其执行过程,要看你头文件所申明的函数是在哪个.c文件里定义的,才知道。 4,他并不是C自带的,可以不用。 5,调用了头文件,就等于赋予了调用某些函数的权限,如果你要算一个数的N次方,就要调用Pow()函数,而这个函数是定义在math.c里面的,要用这个函数,就必需调用math.h 这个头文件。

STM32学习笔记_STM32F103ZET6

STM32F103 系列芯片的系统架构: 系统结构: 在每一次复位以后,所有除SRAM 和FLITF 以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR 来打开该外设的时钟。

GPIO 输入输出,外部中断,定时器,串口。理解了这四个外设,基本就入门了一款MCU。 时钟控制RCC: -4~16M 的外部高速晶振 -内部8MHz 的高速RC 振荡器 -内部40KHz低速RC 振荡器,看门狗时钟 -内部锁相环(PLL,倍频),一般系统时钟都是外部或者内部高速时钟经过PLL 倍频后得到 - 外部低速32.768K 的晶振,主要做RTC 时钟源

ARM存储器映像: 数据字节以小端格式存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。

存储器映像与寄存器映射: ARM 存储器映像 4GB 0X0000 00000X1FFF FFFF 0X2000 00000X3FFF FFFF 0X4000 00000X5FFF FFFF

寄存器说明: 寄存器名称 相对外设基地址的偏移值 编号 位表 读写权限 寄存器位 功能说明 使用C语言封装寄存器: 1、总线和外设基地址封装利用地址偏移 (1)定义外设基地址(Block2 首地址) (2)定义APB2总线基地址(相对外设基地址偏移固定) (3)定义GPIOX外设基地址(相对APB2总线基地址偏移固定)(4)定义GPIOX寄存器地址(相对GPIOX外设基地址偏移固定)(5)使用 C 语言指针操作寄存器进行读/写 //定义外设基地址 #define PERIPH_BASE ((unsigned int)0x40000000) 1) //定义APB2 总线基地址 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 2) //定义GPIOC 外设基地址 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 3) //定义寄存器基地址这里以GPIOC 为例 #define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00) 4) #define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04) #define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08) #define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C) #define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10) #define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14) #define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18) //控制GPIOC 第0 管脚输出一个低电平5) GPIOC_BSRR = (0x01<<(16+0)); //控制GPIOC 第0 管脚输出一个高电平 GPIOC_BSRR = (0x01<<0);

stm32学习 c语言笔记

这是前段时间做彩屏显示时候遇到的难题, *(__IO uint16_t *) (Bank1_LCD_C)这个就是将后面的数据转换为地址,然后对地址单元存放数据。可如下等效: __IO uint16_t *addr; addr = (__IO uint16_t *) Bank1_LCD_C; #ifdef和#elif连用,语法和if。。。else if语句一样 推挽输出增加驱动,可以驱动LED起来 static int count=0 count++ 这个语句中,count仅仅被初始化一次 以后加加一次期中的值就不会变化了 SysTick_CTRL(控制和状态寄存器) SysTick_LOAD(重装载寄存器) SysTick_VAL(当前值寄存器) SysTick_CALIB(校准值寄存器)

TFT经验:弄多大的相片,必须先把那个相片的尺寸改掉,再去取模,才可以,要不会有重影的嘿嘿嘿嘿 VBAT 是电池供电的引脚 VBAT和ADD同时都掉电时才能让备份区复位。 volatile一个变量的存储单元可以在定义该变量的程序之外的某处被引用。 volatile主要是程序员要告诉编译器不要对其定义的这个变量进行优化,防止其不能被引用,不能被改变。 VDDA>2.4V ADC才能工作 VDDA>2.7V USB才能工作 VDD(1.8-3.6v) VBAT=1.8-3.6v VSS VSSA VREF必须接到地线 没有外部电源供电时必须VBAT接上VDD 使用PLL时,VDDA必须供电

printf("abs(x)=%d\n",x<0?(-1)*x:x) 条件编译是问号前边为真则取冒号前边的值,为假的,则取后边的值。 所以说上边这条打印的语句是打印x的绝对值。 //stm32f10x_nvic.c stm32f10x_lib.c stm32f10x_gpio.c stm32f10x_flash.c stm32f10x_rcc.c TIM6 TIM7基本定时器 (只有这两个定时器不能产生PWM) TIM1 TIM8高级控制定时器 TIM2 TIM3 TIM4 TIM5为通用定时器 其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,这样,STM32最多可以同时产生30路PWM输出! 修改和自己写代码时候

STM32学习笔记

STM32学习笔记——时钟频率 ******************************** 本学习笔记基于STM32固件库V3.0 使用芯片型号:STM32F103 开发环境:MDK ******************************** 第一课时钟频率 STM32F103内部8M的内部震荡,经过倍频后最高可以达到72M。目前TI的M3系列芯片最高频率可以达到80M。 在stm32固件库3.0中对时钟频率的选择进行了大大的简化,原先的一大堆操作都在后台进行。系统给出的函数为SystemInit()。但在调用前还需要进行一些宏定义的设置,具体的设置在system_stm32f10x.c文件中。 文件开头就有一个这样的定义: //#define SYSCLK_FREQ_HSE HSE_Value //#define SYSCLK_FREQ_20MHz 20000000 //#define SYSCLK_FREQ_36MHz 36000000 //#define SYSCLK_FREQ_48MHz 48000000 //#define SYSCLK_FREQ_56MHz 56000000 #define SYSCLK_FREQ_72MHz 72000000 ST 官方推荐的外接晶振是8M,所以库函数的设置都是假定你的硬件已经接了8M 晶振来运算的.以上东西就是默认晶振8M 的时候,推荐的CPU 频率选择.在这里选择了: #define SYSCLK_FREQ_72MHz 72000000 也就是103系列能跑到的最大值72M 然后这个C文件继续往下看 #elif defined SYSCLK_FREQ_72MHz const uint32_t SystemFrequency = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_SysClk = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_AHBClk = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_APB1Clk = (SYSCLK_FREQ_72MHz/2); const uint32_t SystemFrequency_APB2Clk = SYSCLK_FREQ_72MHz; 这就是在定义了CPU跑72M的时候,各个系统的速度了.他们分别是:硬件频率,系统时 钟,AHB总线频率,APB1总线频率,APB2总线频率.再往下看,看到这个了: #elif defined SYSCLK_FREQ_72MHz static void SetSysClockTo72(void); 这就是定义72M 的时候,设置时钟的函数.这个函数被SetSysClock ()函数调用,而SetSysClock ()函数则是被SystemInit()函数调用.最后SystemInit()函数,就是被你调用的了

STM32学习笔记(5)通用定时器PWM输出

STM32学习笔记(5):通用定时器PWM输出 2011年3月30日TIMER输出PWM 1.TIMER输出PWM基本概念 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。一般用来控制步进电机的速度等等。 STM32的定时器除了TIM6和TIM7之外,其他的定时器都可以用来产生PWM输出,其中高级定时器TIM1和TIM8可以同时产生7路的PWM输出,而通用定时器也能同时产生4路的PWM输出。 1.1PWM输出模式 STM32的PWM输出有两种模式,模式1和模式2,由TIMx_CCMRx寄存器中的OCxM位确定的(“110”为模式1,“111”为模式2)。模式1和模式2的区别如下: 110:PWM模式1-在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。 111:PWM模式2-在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为有效电平,否则为无效电平。 由此看来,模式1和模式2正好互补,互为相反,所以在运用起来差别也并不太大。 而从计数模式上来看,PWM也和TIMx在作定时器时一样,也有向上计数模式、向下计数模式和中心对齐模式,关于3种模式的具体资料,可以查看《STM32参考手册》的“14.3.9 PWM模式”一节,在此就不详细赘述了。 1.2PWM输出管脚 PWM的输出管脚是确定好的,具体的引脚功能可以查看《STM32参考手册》的“8.3.7 定时器复用功能重映射”一节。在此需要强调的是,不同的TIMx有分配不同的引脚,但是考虑到管脚复用功能,STM32提出了一个重映像的概念,就是说通过设置某一些相关的寄存器,来使得在其他非原始指定的管脚上也能输出PWM。但是这些重映像的管脚也是由参考手册给出的。比如

STM32学习笔记

1、GPIO函数: 输出: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//此例以PA12口为例 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET); //此例以PA12口为例 HAL_GPIO_ TogglePin(GPIOA,GPIO_PIN_12); //此例以PA12口为例 2、串口函数: 1、串口发送/接收函数 HAL_UART_Transmit();串口轮询模式发送,使用超时管理机制 HAL_UART_Receive();串口轮询模式接收,使用超时管理机制 HAL_UART_Transmit_IT();串口中断模式发送 HAL_UART_Receive_IT();串口中断模式接收 HAL_UART_Transmit_DMA();串口DMA模式发送 HAL_UART_Transmit_DMA();串口DMA模式接收 2、串口中断函数 HAL_UART_TxHalfCpltCallback();一半数据发送完成时调用 HAL_UART_TxCpltCallback();数据完全发送完成后调用 HAL_UART_RxHalfCpltCallback();一半数据接收完成时调用 HAL_UART_RxCpltCallback();数据完全接受完成后调用 HAL_UART_ErrorCallback();传输出现错误时调用 例程:串口接收中断 uint8_t aTxStartMessages[] = "\r\n******UART commucition using IT******\r\nPlease enter 10 characters:\r\n"; uint8_t aRxBuffer[20]; 2、在main函数中添加两个语句通过串口中断发送aTxStartMessage数组的数据和接收数据10个字符,保存在数组aRxBuffer中 HAL_UART_Transmit_IT(&huart1 ,(uint8_t*)aTxStartMessages,sizeof(aTxStartMessages)); //sizeof()可读取目标长度 HAL_UART_Receive_IT(&huart1,(uint8_t*)aRxBuffer,10); 3、在main.c文件后面添加中断接收完成函数,将接收到的数据又通过串口发送回去。 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { UNUSED(huart); HAL_UART_Transmit(&huart1,(uint8_t*)aRxBuffer,10,0xFFFF);//(uint8_t*)aRxBuffer为字符串地址,10为字符串长度,0xFFFF为超时时可以在中间加任何可执行代码。 }

STM32学习心得笔记

STM32学习心得笔记 时钟篇 在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 ①、HSI是高速内部时钟,RC振荡器,频率为8MHz。 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~16MHz。 ③、LSI是低速内部时钟,RC振荡器,频率为40kHz。 ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。 ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍, 但是其输出频率最大不得超过72MHz。 其中40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外, 实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。 STM32中有一个全速功能的USB 模块,其串行接口引擎需要一个频率为48MHz的时

钟源。该时钟源只能 从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL 必须使能, 并且时钟频率配置为48MHz或72MHz。 另外,STM32还可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。 系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL 输出、HSI或者HSE。系统时钟最 大频率为72MHz,它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分 频。其中AHB分频器输出的时钟送给5大模块使用: ①、送给AHB 总线、内核、内存和DMA使用的HCLK时钟。 ②、通过8分频后送给Cortex的系统定时器时钟。 ③、直接送给Cortex的空闲运行时钟FCLK。 ④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz), 另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。

STM32学习笔记(18)-数据的保存和毁灭

数据的保存和毁灭(2) 和以前学到的有关数据保存不同,这里的数据保存还有“保密”之意,即一旦受到意外的侵入,STM32将毁灭数据。这是通过Tamper机制来实现的。 以下是数据手册中的有关说明: 5.3.1 侵入检测 当TAMPER引脚上的信号从0变成1或者从1变成0(取决于备份控制寄存器BKP_CR的TPAL 位),会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。然而为了避免丢失侵入事件,侵入检测信号是边沿检测的信号与侵入检测允许位的逻辑与,从而在侵入检测引脚被允许前发生的侵入事件也可以被检测到。 ●当 TPAL=0 时:如果在启动侵入检测TAMPER引脚前(通过设置TPE位)该引脚已经为高电平,一旦启动侵入检测功能,则会产生一个额外的侵入事件(尽管在TPE位置’1’后并没有出现上升沿)。 ●当 TPAL=1 时:如果在启动侵入检测引脚TAMPER前(通过设置TPE位)该引脚已经为低电平,一旦启动侵入检测功能,则会产生一个额外的侵入事件(尽管在TPE位置’1’后并没有出现下降沿)。 设置BKP_CSR寄存器的TPIE位为’1’,当检测到侵入事件时就会产生一个中断。 在一个侵入事件被检测到并被清除后,侵入检测引脚TAMPER应该被禁止。然后,在再次写入备份数据寄存器前重新用TPE位启动侵入检测功能。这样,可以阻止软件在侵入检测引脚上仍然有侵入事件时对备份数据寄存器进行写操作。这相当于对侵入引脚TAMPER进行电平检测。 注:当V DD电源断开时,侵入检测功能仍然有效。为了避免不必要的复位数据备份寄存器,TAMPER引脚应该在片外连接到正确的电平。 显然,Tamper需要硬件与之配合。以上数据手册描述了硬件配置时的一些注意事项。 (1)可以是把引脚由低电平到高电平认为是一次侵入,也可以把引脚从高电平变到低电平认为是一次侵入,这通过TPAL来设置。

STM32自学笔记

一、原子位操作: 原子位操作定义在文件中。令人感到奇怪的是位操作函数是对普通的内存地址进行操作的。原子位操作在多数情况下是对一个字长的内存访问,因而位号该位于0-31之间(在64位机器上是0-63之间),但是对位号的范围没有限制。 原子操作中的位操作部分函数如下: void set_bit(int nr, void *addr)原子设置addr所指的第nr位 void clear_bit(int nr, void *addr)原子的清空所指对象的第nr位 void change_bit(nr, void *addr)原子的翻转addr所指的第nr位int test_bit(nr, void *addr)原子的返回addr位所指对象nr位int test_and_set_bit(nr, void *addr)原子设置addr所指对象的第nr位,并返回原先的值 int test_and_clear_bit(nr, void *addr)原子清空addr所指对象的第nr位,并返回原先的值 int test_and_change_bit(nr, void *addr)原子翻转addr所指对象的第nr位,并返回原先的值 unsigned long word = 0; set_bit(0, &word); /*第0位被设置*/ set_bit(1, &word); /*第1位被设置*/ clear_bit(1, &word); /*第1位被清空*/ change_bit(0, &word); /*翻转第0位*/ 二、STM32的GPIO锁定: 三、中断挂起: 因为某种原因,中断不能马上执行,所以“挂起”等待。比如有高、低级别的中断同时发生,就挂起低级别中断,等高级别中断程序执行完,在执行低级别中断。四、固文件: 固件(Firmware)就是写入EROM(可擦写只读存储器)或EEPROM(电可擦可编程只读存储器)中的程序。 五、固件库:包含各个外设或者内核的驱动头文件和C文件。 六、TIx的输入捕获滤波器(消抖): 采样频率fSAMPLING,采样次数N,如果以采样频率对一脉冲进行采样时,如果在N个采样方波里该脉宽不变,则视为一次有效的脉冲,否则视为无效的脉冲。 七、高级定时器的PWM互补输出: 常用于X相电机驱动,其中的互补输出则防止电机的死区出现。

STM32学习LCD的显示

STM32学习LCD的显示 1.LCD/LCM的基本概念 液晶显示器(Liquid Crystal Display: LCD)的构造是在两片平行的玻璃当中放置液态的晶体,两片玻璃中间有许多垂直和水平的细小电线,透过通电与否来控制杆状水晶分子改变方向,将光线折射出来产生画面。 LCM(LCD Module)即LCD显示模组、液晶模块,是指将液晶显示器件,连接件,控制与驱动等外围电路,PCB电路板,背光源,结构件等装配在一起的组件。 在平时的学习开发中,我们一般使用的是LCM,带有驱动IC和LCD屏幕等多个模块。 2.FSMC的基本概念 在STM32上开发LCD显示,可以有两种方式来对LCD进行操作,一种是通过普通的IO 口,连接LCM的相应引脚来进行操作,第2种是通过FSMC来进行操作。 可变静态存储控制器(Flexible Static Memory Controller: FSMC) 是STM32系列中内部集成256 KB以上FlaSh,后缀为xC、xD和xE的高存储密度微控制器特有的存储控制机制。之所以称为“可变”,是由于通过对特殊功能寄存器的设置,FSMC能够根据不同的外部存储器类型,发出相应的数据/地址/控制信号类型以匹配信号的速度,从而使得STM32系列微控制器不仅能够应用各种不同类型、不同速度的外部静态存储器,而且能够在不增加外部器件的情况下同时扩展多种不同类型的静态存储器,满足系统设计对存储容量、产品体积以及成本的综合要求。 FSMC有很多优点: 1.支持多种静态存储器类型。STM32通过FSMC可以与SRAM、ROM、PSRAM、NOR Flash和NANDFlash存储器的引脚直接相连。 2.支持丰富的存储操作方法。FSMC不仅支持多种数据宽度的异步读/写操作,而且支持对NOR、PSRAM、NAND存储器的同步突发访问方式。 3.支持同时扩展多种存储器。FSMC的映射地址空间中,不同的BANK是独立的,可用于扩展不同类型的存储器。当系统中扩展和使用多个外部存储器时,FSMC会通过总线悬空延迟时间参数的设置,防止各存储器对总线的访问冲突。 4.支持更为广泛的存储器型号。通过对FSMC的时间参数设置,扩大了系统中可用存储器的速度范围,为用户提供了灵活的存储芯片选择空间。 5.支持代码从FSMC扩展的外部存储器中直接运行,而不需要首先调入内部SRAM。FSMC包含两类控制器: 1.1个NOR闪存/SRAM控制器,可以与NOR闪存、SRAM和PSRAM存储器接口。 2.1个NAND闪存/PC卡控制器,可以与NAND闪存、PC卡,CF卡和CF+存储器接口。控制器产生所有驱动这些存储器的信号时序: 1.16位数据线,用于连接8位或16位的存储器; 2.26位地址线,最多可连续64MB的存储器(这里不包括片选线); 3.5位独立的片选信号线; 4.1组适合不同类型存储器的控制信号线: -控制读/写操作 -与存储器通信,提供就绪/繁忙信号和中断信号 -与所用配置的PC卡接口:PC存储卡、PC I/O卡和真正的IDE接口 从FSMC的角度看,可以把外部存储器划分为固定大小为256MB的4个存储块 · 存储块1用于访问最多4个NOR闪存或者PSRAM存储设备。这个存储区被划分为4个

STM32学习笔记:LCD的显示

年月日显示 . 地基本概念 液晶显示器( : )地构造是在两片平行地玻璃当中放置液态地晶体,两片玻璃中间有许多垂直和水平地细小电线,透过通电与否来控制杆状水晶分子改变方向,将光线折射出来产生画面.文档来自于网络搜索 ( )即显示模组、液晶模块,是指将液晶显示器件,连接件,控制与驱动等外围电路,电路板,背光源,结构件等装配在一起地组件.文档来自于网络搜索 在平时地学习开发中,我们一般使用地是,带有驱动和屏幕等多个模块. . 地基本概念 在上开发显示,可以有两种方式来对进行操作,一种是通过普通地口,连接地相应引脚来进行操作,第种是通过来进行操作.文档来自于网络搜索 可变静态存储控制器( : ) 是系列中内部集成以上,后缀为、和地高存储密度微控制器特有地存储控制机制.之所以称为“可变”,是由于通过对特殊功能寄存器地设置,能够根据不同地外部存储器类型,发出相应地数据地址控制信号类型以匹配信号地速度,从而使得系列微控制器不仅能够应用各种不同类型、不同速度地外部静态存储器,而且能够在不增加外部器件地情况下同时扩展多种不同类型地静态存储器,满足系统设计对存储容量、产品体积以及成本地综合要求.文档来自于网络搜索 有很多优点: . 支持多种静态存储器类型.通过可以与、、、和存储器地引脚直接相连.文档来自于网络搜索 . 支持丰富地存储操作方法.不仅支持多种数据宽度地异步读写操作,而且支持对、、存储器地同步突发访问方式.文档来自于网络搜索 . 支持同时扩展多种存储器.地映射地址空间中,不同地是独立地,可用于扩展不同类型地存储器.当系统中扩展和使用多个外部存储器时,会通过总线悬空延迟时间参数地设置,防止各存储器对总线地访问冲突.文档来自于网络搜索 . 支持更为广泛地存储器型号.通过对地时间参数设置,扩大了系统中可用存储器地速度范围,为用户提供了灵活地存储芯片选择空间.文档来自于网络搜索 . 支持代码从扩展地外部存储器中直接运行,而不需要首先调入内部. 包含两类控制器: . 个闪存控制器,可以与闪存、和存储器接口. . 个闪存卡控制器,可以与闪存、卡,卡和存储器接口. 控制器产生所有驱动这些存储器地信号时序: . 位数据线,用于连接位或位地存储器; . 位地址线,最多可连续地存储器(这里不包括片选线); . 位独立地片选信号线; . 组适合不同类型存储器地控制信号线: 控制读写操作 与存储器通信,提供就绪繁忙信号和中断信号 与所用配置地卡接口:存储卡、卡和真正地接口 从地角度看,可以把外部存储器划分为固定大小为地个存储块 ·存储块用于访问最多个闪存或者存储设备.这个存储区被划分为个区,并有个专用地片选.文档来自于网络搜索·存储块和用于访问闪存设备,每个存储块连接一个闪存. ·存储块用于访问卡设备 每一个存储块上地存储器类型是由用户在配置寄存器中定义地. 注意:只是提供了一个控制器,并不提供相应地存储设备,至于外设接地是什么设备,完全是由用户自己选择,只要能用于控制,就可以,像本次实验中,我们接地就是.文档来自于网络搜索 . 本例中地使用 由于本例只是利用对进行操作,因此不用完全懂得地所有功能,而是懂得一部分相应地操作即可.文档来自于网络搜索 . 包括哪几个部分

STM32学习笔记-USART程序解释(原子)

USART程序分析 一 .H文件 #ifndef __USART_H #define __USART_H #include #include "stdio.h" extern u8 USART_RX_BUF[64]; //接收缓冲,最大63个字节.末字节为换行符extern u8 USART_RX_STA; //接收状态标记 //如果想串口中断接收,请不要注释以下宏定义 //#define EN_USART1_RX 使能串口1接收 void uart_init(u32 pclk2,u32 bound); #endif 解释:extern 作用域:如果整个工程由多个文件组成,在一个文件中想引用另外一个文件中已经定义的外部变量时,则只需在引用变量的文件中用extern关键字加以声明即可。可见,其作用域从一个文件扩展到多个文件了。 例子: 文件a.c的内容: #include int BASE=2; //变量定义 int exe(int x); //外部函数提前声明 int main(int argc, char *agrv[]) { int a=10; printf("%d^%d = %d\n",BASE,a,exe(a)); return 0; } 文件b.c的内容: #include extern BASE; //外部变量声明 int exe(int x) { int i; int ret=1; for(i=0;i

STM32学习笔记(初学者快速入门

STM32学习笔记(初学者快速入门 STM32 学习笔记 从51 开始单片机玩了很长时间了有51PICAVR 等等早就想跟潮 流玩玩ARM 但一直没有开始原因-----不知道玩了ARM 可以做什么对我自 己而言如果为学习而学习肯定学不好然后cortex-m3 出来了据说这 东西可以替代单片机于是马上开始关注也在第一时间开始学习可惜一开始 就有点站错了队选错了型仍是对我自己而言我希望这种芯片应该是满大 街都是随便哪里都可以买得到但我选的第一种显然做不到为此大概浪费

了一年多时间吧现在回到对我来说是正确的道路上来啦边学边写点东西 这里写的是我的学习的过程显然很多时候会是不全面的不系统的感 悟式的甚至有时会是错误的有些做法会是不专业的那么为什么我还要写 呢这是一个有趣的问题它甚至涉及到博客为什么要存在的问题显然博客 里面的写的东西其正确性权威性大多没法和书比可为什么博客会存在呢 理由很多我非专家只说我的感慨 我们读武侠小说总会有一些创出独门功夫的宗师功夫极高然后他的弟 子则基本上无法超越他我在想这位宗师在创造他自己的独门功

夫时必然会 有很多的次的曲折弯路甚至失败会浪费他的很多时间而他教给弟子时 则已去掉了这些曲折和弯路当然更不会把失败教给弟子按理说效率应该更 高可是没用弟子大都不如师为什么呢也许知识本身并不是最重要的获 取知识的过程才是最重要的也许所谓的知识并不仅仅是一条条的结论而是 附带着很多说不清道不明的东西如植物的根一条主根上必带有大量的小小的 触须 闲话多了些就权当前言了下面准备开始 一条件的准备

我的习惯第一步是先搭建一个学习的平台原来学51PICAVR 时都 是想方设法自己做些工具实验板之类现在人懒了直接购买成品了 硬件电路板火牛板 软件有keil 和iar 可供选择网上的口水仗不少我选keil理由很简单 这个我熟目前要学的知识中软硬件我都不熟所以找一个我有点熟的东西 就很重要在我相当熟练之前肯定不会用到IAR如果真的有一天不得不用I AR 相信学起来也很容易因为这个时候硬件部分我肯定很熟了再加上有ke il 的基础所以应该很容易学会了

STM32学习笔记(关于时钟)

STM32学习----时钟(转载) 在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 ①、HSI是高速内部时钟,RC振荡器,频率为8MHz。 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。 HSE/LSE时钟源 ③、LSI是低速内部时钟,RC振荡器,频率为40kHz。 ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。 ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。 其中40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。 STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz 的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。 另外,STM32还可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。 系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或者HSE。系统时钟最大频率为72MHz,它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用: ①、送给AHB总线、内核、内存和DMA使用的HCLK时钟。 ②、通过8分频后送给Cortex的系统定时器时钟。 ③、直接送给Cortex的空闲运行时钟FCLK。 ④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。

STM32学习笔记

FLASH篇: 一、闪存:主存储器,信息块和闪存存储器接口寄存器等3部分组成。 1、主存储器:存放代码与数据常量(编译器编译完成后CODE=,RO=,RW=,ZI=,依次为代码,常量,初始化变量,未初始化变量),即前面两个。B0,B1都为GND时,从0x08000000开始运行代码。 2、信息块:分为启动代码和用户选择字节。启动代码是生产线上少些进去的,用于USART1串口下载时使用,B0=1,B1=0时运行这段代码。用户选择字用于配制写保护杜保护等功能。 3、闪存存储器接口寄存器:控制闪存的读写,用于控制整个模块。 二、闪存编程/擦除控制器(FPEC) 对主存储器和信息块的写入管理。在执行写操作时,任何对闪存读操作都会锁住总线,在写操作完成后读操作才正确而执行。 包含有7个32位寄存器: 键寄存器FLASH_KEYR; 选择字节键寄存器FLASH_OPTKEYR; 闪存控制寄存器FLASH_CR; 闪存地址寄存器FLASH_AR; 闪存状态寄存器FLASH_SR; 选择字节寄存器FLASH_ORR; 写保护寄存器FLASH_WRPR; 复位后,FPEC模块是被保护的,不能写入FLASH_CR寄存器,通过写入特定的序列到FLASH_KEYR寄存器可以打开FPEC模块(写入KEY1和KEY2),当其中有一个写入错误,写入RDPRT进行解锁。 RDPRT=0X0000000A5; KEY1=0X45670123; KEY2=0XCDEF99AB; 注:每次必须写入16位,也只有写16位。 三、闪存读取 直接寻址方式,任何32为数据读操作都能访问闪存模块的内容并得到相应的数据。读操作时FLASH_ACR寄存器配置FLASH等待周期为2。 DATA=*(U16)*ADDR; 四、闪存的编程和擦除

牛人的STM32学习笔记(寄存器版本)

一、GPIO口的配置 STM32的DGPIO口最多可以有7组(GPIOa~GPIOg),而每一组GPIO口均有16个 双向IO组成。并且没个IO口均可配置成8种模式(4种输入模式,4种输出模式)。不管 配置哪个IO口也不论将其配置成哪种模式(但是配置成哪种模式要看具体应用,参考《中 文参考手册》第105页)都可以按以下步骤来进行配置: (1)使能PORTx(x=A~G)时钟 这里就得操作寄存器RCC_APB2ENR(32为寄存器)了 15 14 13 12 11 10 9 8 ADC3EN USART1EN TIM8EN SPI1EN TIM1EN ADC2EN ADC1EN IOPGEN 7 6 5 4 3 2 1 0 IOPFEN IOPEEN IOPDEN IOPCEN IOPBEN IOPAEN 保留AFIOEN RCC_APB2ENR的0~15位(06~32位保留) 第2~8分别是使能GPIOA~GPIOG时钟的,只要将其置“1”即可,如 RCC_APB2ENR|=1<<2;就是使能GPIOA的时钟;其余IO口的始终使能一次类推。 (2)对相应的IO模式进行配置,低8位配置GPIOx_CRL;高8位配置GPIOx_CRH 31 30 29 28 27 26 25 24 CNF7[1:0] MODE7[1:0] CNF6[1:0] MODE6[1:0] 23 22 21 20 19 18 17 16 CNF5[1:0] MODE5[1:0] CNF4[1:0] MODE4[1:0] 15 14 13 12 11 10 9 8 CNF3[1:0] MODE3[1:0] CNF2[1:0] MODE2[1:0] 7 6 5 4 3 2 1 0 CNF1[1:0] MODE1[1:0] CNF0[1:0] MODE0[1:0] GPIOx_CRL(x=A~G(端口配置低寄存器x=A…E) 该寄存器用于配置GPIOx的低8位,具体8种模式的配置见《中文参考手册》例如: GPIOD->CRL&=0XFFFFF0FF;GPIOD->CRL|=0X00000300;/PD.2推挽输出;其余IO口的 低8位以此类推。 31 30 29 28 27 26 25 24 CNF15[1:0] MODE15[1:0] CNF14[1:0] MODE14[1:0] 23 22 21 20 19 18 17 16 CNF13[1:0] MODE13[1:0] CNF12[1:0] MODE12[1:0] 15 14 13 12 11 10 9 8 CNF11[1:0] MODE11[1:0] CNF10[1:0] MODE10[1:0] 7 6 5 4 3 2 1 0 CNF9[1:0] MODE9[1:0] CNF8[1:0] MODE8[1:0] GPIOx_CRH(端口配置高寄存器x=A…E) 该寄存器用于配置GPIOx的高8位,具体8种模式的配置见《中文参考手册》例如: GPIOA->CRH&=0XFFFFFFF0;;GPIOA->CRH|=0X00000003;//PA8 推挽输出;其余IO口 的高8位以此类推。

STM32学习笔记系统时钟和SysTick定时器

STM32学习笔记(3):系统时钟和SysTick定时器 1.STM32的时钟系统 在STM32中,一共有5个时钟源,分别是HSI、HSE、LSI、LSE、PLL (1)HSI是高速内部时钟,RC振荡器,频率为8MHz; (2)HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围是4MHz – 16MHz; (3)LSI是低速内部时钟,RC振荡器,频率为40KHz; (4)LSE是低速外部时钟,接频率为32.768KHz的石英晶体; (5)PLL为锁相环倍频输出,严格的来说并不算一个独立的时钟源,PLL 的输入可以接HSI/2、HSE或者HSE/2。倍频可选择为2 – 16倍,但 是其输出频率最大不得超过72MHz。 其中,40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。 STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为 48MHz的时钟源。该时钟源只能从PLL端获取,可以选择为1.5分频或者1分频,也就是,当需使用到USB模块时,PLL必须使能,并且时钟配置为48MHz 或72MHz。 另外STM32还可以选择一个时钟信号输出到MCO脚(PA.8)上,可以选择为PLL输出的2分频、HSI、HSE或者系统时钟。 系统时钟SYSCLK,它是提供STM32中绝大部分部件工作的时钟源。系统时钟可以选择为PLL输出、HSI、HSE。系系统时钟最大频率为72MHz,它通过AHB分频器分频后送给各个模块使用,AHB分频器可以选择1、2、4、8、16、64、128、256、512分频,其分频器输出的时钟送给5大模块使用: (1)送给AHB总线、内核、内存和DMA使用的HCLK时钟; (2)通过8分频后送给Cortex的系统定时器时钟; (3)直接送给Cortex的空闲运行时钟FCLK; (4)送给APB1分频器。APB1分频器可以选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一

相关主题