你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

查看: 9540|回复: 37

基于freemodbus的控制器

[复制链接]

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
发表于 2015-1-8 13:15:58 | 显示全部楼层 |阅读模式
本帖最后由 mantishell 于 2015-2-13 09:20 编辑

一、方案名称:基于freemodbus的下位机控制器
二、方案概述    使用stm32f072rb开发板,基于freemodbus构建一个下位机控制器。
    一个复位按钮
    6路AD转换 另+2个内部AD
    一个RS232通信
    控制3个IO
    2个led指示灯
    1个电机正反转(交流电机)
    1个电机单向转(交流电机)
三、方案详述
    一个指示灯提示电源接通,一个指示灯闪烁表示通信中,熄灭表示无通信。
    通过modbus的协议可以读取AD值,可以控制IO开关,可以使电机正反装。




实现读取6个外部ADC,两个内部ADC,控制3个IO开关

实现读取6个外部ADC,两个内部ADC,控制3个IO开关

usart3_tim6_freemodbus.zip

下载

4.7 MB, 下载次数: 284, 下载积分: ST金币 -1

实现了pwm控制单向交流电机正反转,如有问题请与我联系:825013349

方案及引脚配置.pdf

下载

27.12 KB, 下载次数: 85, 下载积分: ST金币 -1

使用串口3

回复

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-9 15:37:30 | 显示全部楼层
今天使用adc时发现一种宏定义,感觉不赖,以防忘记,现记录如下:
#define VDD_APPLI                      ((uint32_t)3300)   /* Value of analog voltage supply Vdda (unit: mV) */
#define RANGE_12BITS                   ((uint32_t)4095)   /* Max value for a full range of 12 bits (4096 values) */
#define COMPUTATION_VOLTAGE(ADC_DATA)         ( (ADC_DATA) * VDD_APPLI / RANGE_12BITS)

电源使用3.3v,分辨率为12位。
比如,得到的电压值=COMPUTATION_VOLTAGE(ADC_DR);
回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-11 21:35:45 | 显示全部楼层
  今天接着弄,今天弄一个读取ADC的程序,从STM32CubeMX里生成工程代码

    首先打开软件STM32CubeMX,然后点击New Project--board Select,如图1,然后选择STM32F072RB如图2.双击打开进入图3界面,我使用pa.4的ADC_IN4,串口usart1的pa.9,pa.10如图4,虽然现在选择了pa.9和pa.10,但usart1还是没有使能,在左侧选择usart1的模式为异步如图5.然后在配置adc和usart1。
    usart1的配置如图7,图8,图9.adc的配置如图10,然后点击Project--generate code,填写工程名(ADC),我使用的是mdk,选择生成mdk的工程如图11,所示。
    最后在工程里修改代码。在PA.4接上电阻,就可以在串口输出转换的单个数据了如图12.
    说明因为是调试,所以代码放到ram中。
1.jpg
2.jpg
3.jpg
4.jpg
5.jpg
6.jpg
7.jpg
8.jpg
9.jpg
10.jpg
11.jpg
12.jpg

adc.zip

下载

1.09 MB, 下载次数: 39, 下载积分: ST金币 -1

回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-25 09:03:41 | 显示全部楼层
本帖最后由 mantishell 于 2015-1-25 11:36 编辑

网上一片不错的freemodbus的移植详细步骤,网址http://www.tuicool.com/articles/iQF7ba

记录一下移植的分析:使用RTU模式,需要移植串口和定时器
一、对于时钟的移植
    首先移植与定时器相关的函数。
    在porttimer.c中添加BOOL    xMBPortTimersInit( USHORT usTim1Timerout50us ),添加50us时基时钟的调用,添加时钟的开关的函数 void vMBPortTimersEnablle()和void vMBPortTimersDisable,超时中断函数void TIM6_DAC_IRQHandler(void)。

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
        TIM_MasterConfigTypeDef sMasterConfig;
        TIM_ClockConfigTypeDef sClockSourceConfig;
        uint16_t uwPrescalerValue = 0;
        /* Initialize TIMx peripheral as follows:
       + Period = 35
       + Prescaler = (SystemCoreClock/20000) - 1
       + ClockDivision = 0
       + Counter direction = Up
  */
          /* Compute the prescaler value to have TIMx counter clock equal to 20kHz */
        uwPrescalerValue = SystemCoreClock/20000-1;
        
    htim6.Instance = TIM6;                                                      //使用定时器tim6
    htim6.Init.Prescaler = uwPrescalerValue;                             //PSC=SystemCoreClock/20000-1,也就是说定时器是20KHz
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim6.Init.Period = usTim1Timerout50us;                         //我采用的是115200,所以这里的时间是固定的,否则为T3.5
    HAL_TIM_Base_Init(&htim6);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim6, &sClockSourceConfig);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig);
    return TRUE;
}
inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
        __HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);                //清除中断位
        __HAL_TIM_ENABLE_IT(&htim6,TIM_IT_UPDATE);                //使能中断位
        __HAL_TIM_SetCounter(&htim6,0);                                        //设置定时器计数为0
        __HAL_TIM_ENABLE(&htim6);                                                //使能定时器
}
inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
        __HAL_TIM_DISABLE(&htim6);
        __HAL_TIM_SetCounter(&htim6,0);
        __HAL_TIM_DISABLE_IT(&htim6,TIM_IT_UPDATE);
        __HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
}
void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */
        HAL_NVIC_ClearPendingIRQ(TIM6_DAC_IRQn);//清除中断申请位
  /* USER CODE END TIM6_DAC_IRQn 0 */
        
  HAL_TIM_IRQHandler(&htim6);                                //进入中断处理函数
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断处理回调函数
{
          prvvTIMERExpiredISR( );
}
二、串口的移植
    使用串口usart1,有几个函数需要添加
    串口的使能
UART_HandleTypeDef huart1;
BOOL  xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
        huart1.Instance = USART1;                                                                                
  huart1.Init.BaudRate = ulBaudRate;                                       
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = eParity;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_8;
  huart1.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED ;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  HAL_UART_Init(&huart1);
        return TRUE ;
}
    收发中断使能函数
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
        if (xRxEnable)
        {
                __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
        }
        else
        {
                __HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
        }
        if (xTxEnable)
        {
                __HAL_UART_ENABLE_IT(&huart1,UART_IT_TXE);
        }
        else
        {
                __HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);
        }
}
    发送接收函数

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
        if(HAL_UART_Transmit (&huart1 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )
                return FALSE ;
        else
                return TRUE;
}
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
        if(HAL_UART_Receive (&huart1 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )
                return FALSE ;
        else
                return TRUE;
}

    中断处理函数
void USART1_IRQHandler(void)
{
        if(__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_RXNE)!=RESET)
        {
                prvvUARTRxISR();                                                        //接收中断
        }
        if(__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_TXE)!=RESET)
        {
                prvvUARTTxReadyISR();                                                //发送中断
        }
HAL_NVIC_ClearPendingIRQ(USART1_IRQn);
  HAL_UART_IRQHandler(&huart1);

}

现在的功能是03

现在的功能是03
回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-25 11:37:19 | 显示全部楼层

STM32F072RB Nucleo上移植freemodbus

本帖最后由 mantishell 于 2015-1-25 13:33 编辑

   一、对于时钟的移植

        由于modbus RTU模式需要定时器的支持,所以第一步先移植与定时器相关的函数。

在porttimer.c中添加 BOOL xMBPortTimersInit ( USHORT usTim1Timerout50us )的实现,实现50us的基时时钟。

添加打开和关闭时钟的函数 void vMBPortTimersEnable(  ) 以及 void vMBPortTimersDisable(  ) ,还有超时中断函数 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) ,在其中调用上述两个发送和接收的函数。

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

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[6] =GPIO_ReadInputData(GPIOG);

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

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

       在main函数中可以看到

       int  main( void ) {

         /* Enable the Modbus Protocol Stack. */

    {


      (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所示。


对工程进行软件仿真


      (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为接收状态机,如下图2所示:


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为发送状态机,如下图3所示:



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支持以下功能码如图4:



Modbus支持的功能码

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


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



查询输入寄存器命令格式

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

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

    eException= xFuncHandlers.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.pxHandler (eMBRegInputCB )对接收的数据进行处理—> peMBFrameSendCur—>eMBRTUSend(&STATE_RX_IDLE)—>STATE_TX_XMIT

串口发送完成中断—> prvvUARTTxReadyISR—> FSMpxMBFrameCBTransmitterEmpty—>xMBRTUTransmitFSM(& STATE_TX_XMIT)—>xMBPortSerialPutByte—>发送数据。

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

    七、RTU模式的测试

   (1)测试软件

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


图1.jpg
图2.jpg
图3.jpg
图4.jpg
图5.jpg
图6.jpg
回复 支持 反对

使用道具 举报

13

主题

106

回帖

0

蝴蝶豆

中级会员

最后登录
2018-4-5
发表于 2015-1-28 08:31:25 | 显示全部楼层
学习了…… 感谢分享…………
回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-2-13 12:41:49 | 显示全部楼层
硬件图纸

HardWareOfModbus.zip

下载

1.15 MB, 下载次数: 70, 下载积分: ST金币 -1

硬件图纸

回复 支持 反对

使用道具 举报

307

主题

3125

回帖

0

蝴蝶豆

论坛元老

最后登录
2020-7-17
发表于 2015-1-8 13:19:58 | 显示全部楼层
好滴,楼主尽快更新哦
回复 支持 反对

使用道具 举报

8

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
2020-1-17
发表于 2015-1-8 15:11:10 | 显示全部楼层
沐紫 发表于 2015-1-8 13:19
好滴,楼主尽快更新哦

沐紫帮我顶一个啊
回复 支持 反对

使用道具 举报

8

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
2020-1-17
发表于 2015-1-8 15:11:36 | 显示全部楼层
楼主,我也完了一年freemodbus,支持你。
回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-8 15:13:44 | 显示全部楼层
oipk 发表于 2015-1-8 15:11
楼主,我也完了一年freemodbus,支持你。

嘿嘿,谢谢,我学习编程才一年,做起来会有点吃力。
回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-8 15:19:04 | 显示全部楼层
沐紫 发表于 2015-1-8 13:19
好滴,楼主尽快更新哦

谢谢,我会的
回复 支持 反对

使用道具 举报

0

主题

3

回帖

0

蝴蝶豆

新手上路

最后登录
2015-1-8
发表于 2015-1-8 15:26:52 | 显示全部楼层
回复 支持 反对

使用道具 举报

11

主题

49

回帖

0

蝴蝶豆

中级会员

最后登录
1970-1-1
 楼主| 发表于 2015-1-8 15:34:23 | 显示全部楼层

难道你做的跟我的方案相似,看来我需要抓紧了
回复 支持 反对

使用道具 举报

5

主题

309

回帖

0

蝴蝶豆

金牌会员

最后登录
2019-5-24
发表于 2015-1-10 01:12:12 | 显示全部楼层
顶一个啊  
回复 支持 反对

使用道具 举报

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版