[转]STM32 网口与串口(232或485)通讯例程与学习笔记

2019-12-22 13:43发布


转自 黑鸟论坛
地址:http://www.heybird.net/read.php?tid=24&fid=4&page=1

科星F107开发板网络应用篇之网口与串口(232和485)通讯(TCP服务器模式)学习笔记.pd.pdf (990.7 KB, 下载次数: 95) 2014-5-21 16:52 上传 点击文件名下载附件

                        科星F107开发板网络应用篇之网口与串口(232和485)通讯 (TCP服务器模式)

硬件:
1、        电脑一台、
2、        USB转串口线一根
3、        USB转RS485转换器一个
4、        科星F107开发板一块
软件:
1、        串口调试助手
2、        TCP调试助手
3、        IAR5.4或者MDK4.2
4、        开发板程序:科星F107开发板网络应用篇之网口与串口(232和485)通讯(TCP服务器模式).rar 
PS:下面这段和“科星F107开发板网络应用篇之网口与串口通讯(TCP服务器模式)学习笔记”是一模一样的,期间的内容,我想了下还是不做修改了,直接在后面添加了,如果看过的话,请直接跳转到讲RS485的地方,加个链接试试
点我跳到485部分

*******************************************************************************
本程序实现了网口与串口之间的通信,是由科星原有例程tcp通信修改而来,在其基础上加入了中断方式的串口输入输出程序(也是科星的原有例程),在学习本例程之前,基础不是很好的朋友,希望先看下两个基本的例程,熟悉了那两个例程之后,这个例程就很容易理解了。
在串口中断判断一帧数据接收完成这里,还是和原来例程保持一致,使用定时器中断TIM2来做计时,当收到一个字节数据之后开始计时,在设定的时间内收到新数据则计时器清零,重新计时,直到当收到一个字节数据之后,在设定的时间内没有收到新数据则认为一帧数据的接收完成。
PS:程序中没有加入接收到的数据长度超过接收缓冲区时的操作,希望大家注意!超过时,怎么办?我相信大家都可以搞定,接收收据时有一个记录接收收据长度的变量,咱们去判断这个变量是否超过了,咱们设定的缓冲区的长度就好了,一个if就搞定,缓冲区接收满,发送一次,记录接收长度的量清零,就好了,相信大家都很容易实现,这里为了程序简单就没做太多完善性的工作,怕程序写的太大,不容易看懂,入门以后,那些对你们都不算事了。

前面两段话说的有点多,可能一些朋友没理解,下面我用小学数学给大家解释一下:
假设:
1、        接收数据data1的时间为t1
2、        接收数据data1的下一个字节数据data2的时间为t2
3、        咱们设定的一个固定的时间段为 t
当t2-t1>t时,认为data2和data1没啥关系,data2是下一帧数据了
当t2-t1<t时,认为data2和data1有关系,data1和data2是在同一帧数据里面

这个只是解释,实际不是这么操作的,实际的操作如下:
1、        接收到数据data1,定时器开始计时
2、        当定时器未触发中断,也就是计时的时间还没到咱们设定的一个固定的时间,这时data2来了,就认为data2和data1是一帧数据,然后计时清零,重新计时,重复这个操作
3、        比如接收完data12之后,计时达到咱们设定的时间,可恶的data13还没来,就认为data13不是一帧数据,然后data1和data2到data12这一帧数据就认为接收完成了
4、        这里咱们最好看一下,接收的长度,这里是12,但是如果咱们接收缓冲区设定的是10不就坏了么,数组溢出了,数组溢出后果很严重啊。所以,这里判断一下吧,我懒得加了,O(∩_∩)O哈哈~
说了这么多,还是不理解的话,那就问度娘吧,我无能为力了。

        前面说了这么多,就是个铺垫,因为要把前面的串口中断的例程加到tcp通讯的例程上,其实就是添加了串口和定时器的初始化。下面我们一起看一下程序:
看程序,当然先看main函数了,恍然一看,main函数结构简单粗暴,虽然没有注释应该也都知道每个函数都是干啥的,O(∩_∩)O哈哈~
int main(void)
{
    System_Setup();
    LwIP_Init();
    TcpServer_Init();
   
    while (1)
    {   
        RS232_to_TCP();
        LwIP_Periodic_Handle(LocalTime);
    }
}
函数System_Setup();就是初始化了,说实话,写的有点乱,大家凑活看,毕竟东拼西凑的程序,基本的初始化,没特殊的东西,就是把用到的串口、定时器、NVIC都给初始化好,其实就是从基础例程复制过来,这里需要注意的是NVIC的设置,优先级要搞好,否则TCP连接不上,我开始就随意写的NVIC的优先级,后来tcp通讯不上,改了下就好了。
下面函数是LwIP_Init();,也没啥说的,就是初始化LwIP协议栈,不清楚的看tcp通讯例程的笔记了,这里看一下IP就好了,如下:
IP4_ADDR(&ipaddr, 192, 168, 0, 8);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 192, 168, 0, 1);

再下面是函数TcpServer_Init();这个还是比较重要的,实现的功能是把tcp接收到的数据转到串口,然后从串口输出,下面我们看一下函数:
void TcpServer_Init(void)
{
    struct tcp_pcb *pcb;                                   
    /* Create a new TCP control block  */
    pcb = tcp_new();                                                  
    /* Assign to the new pcb a local IP address and a port number */
    /* Using IP_ADDR_ANY allow the pcb to be used by any local interface */
    tcp_bind(pcb, IP_ADDR_ANY, TcpServer_RS232_PORT);   //端口号默认 23   
    /* Set the connection to the LISTEN state */
    pcb = tcp_listen(pcb);                               
    /* Specify the function to be called when a connection is established */       
    tcp_accept(pcb, TcpServer_RS232_accept);                                                                          
}
这里的函数都有注释,就是新建一个TCP控制块,然后设定下端口号,然后监听,因为这里是服务器嘛。然后就是最重要的大家称为回调函数的TcpServer_RS232_accept,我们看一下回调函数里面都做了些什么:
/**
  * @brief  This function when the Telnet connection is established
  * @param  arg  user supplied argument
  * @param  pcb         the tcp_pcb which accepted the connection
  * @param  err         error value
  * @retval ERR_OK
  */
static err_t TcpServer_RS232_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{     
    tcp_setprio(pcb, 2); //设置回调函数优先级
    tcp_recv(pcb,TcpServer_RS232_recv); //设置TCP段到时的回调函数
    err = ERR_OK;
    return err;
}
其实这个函数主要看的还是调用的一个回调函数TcpServer_RS232_recv,函数如下:
static err_t TcpServer_RS232_recv(void *arg, struct tcp_pcb *pcb,struct pbuf *p,err_t err)
{
    if(p != NULL)
    {
        tcp_recved(pcb, p->tot_len);//获取数据长度 tot_len:tcp数据块的长度
        RS232_Send_Data(p->payload,p->tot_len);        // payload为TCP数据块的起始位置
        pbuf_free(p);                                                 /* 释放该TCP段 */
    }
    else
    {
        tcp_close(pcb); //如果客户端断开连接,则服务器也应断开
    }
    err = ERR_OK;
    return err;
}
这里就是将接收到tcp的数据,通过串口发送出去,串口发送的函数是
RS232_Send_Data(p->payload,p->tot_len); 串口发送的函数自己自己看一下就好了,咱们看一下这个里面的参数p,可能有些朋友没仔细看过,感觉有点晕,看一下p的定义“struct pbuf *p”,P是结构体指针,结构体pbuf的定义如下,主要用到了里面的两个量的解释如下:
struct pbuf {
  /** next pbuf in singly linked pbuf chain */
  struct pbuf *next;

  /** pointer to the actual data in the buffer */
  void *payload;
  /**
   * total length of this buffer and all next buffers in chain
   * belonging to the same packet.
   *
   * For non-queue packet chains this is the invariant:
   * p->tot_len == p->len + (p->next? p->next->tot_len: 0)
   */
  u16_t tot_len;
  
  /** length of this buffer */
  u16_t len;  

  /** pbuf_type as u8_t instead of enum to save space */
  u8_t /*pbuf_type*/ type;

  /** misc flags */
  u8_t flags;

  /**
   * the reference count always equals the number of pointers
   * that refer to this pbuf. This can be pointers from an application,
   * the stack itself, or pbuf->next pointers from a chain.
   */
  u16_t ref;
};

        这里函数TcpServer_Init();就过了一遍,总结下吧,建立一个tcp控制块,绑定到一个端口,然后把tcp接收到的数据通过串口发送出去。

        下面看下一函数RS232_to_TCP(); 实现的功能是把串口接收到的数据,通过tcp协议,发送出去,下面我们看函数内部:
void RS232_to_TCP(void)
{  
    struct tcp_pcb *cpcb;
    u32 RS232_TCP_send_counter=0;
   
    if(flag_rev_finish == 1)        //如果RS232接收一字节后,在规定时间间隔内,没有再接收到数据
    {
        flag_rev_finish = 0;
        RS232_TCP_send_counter = uart1_rev_count;//要发送到TCP上的数据长度即为RS232接收到的数据长度。
        uart1_rev_count = 0;                //RS232接收计数器清零,准备接收下一帧数据。
        
        // 向已经建立连接的所有tcp客户端发送RS232数据
        for(cpcb = tcp_active_pcbs;cpcb != NULL; cpcb = cpcb->next)
        {
            tcp_write(cpcb,RxBuffer1,RS232_TCP_send_counter,TCP_WRITE_FLAG_COPY);
            tcp_output(cpcb);
        }
    }
}


        变量flag_rev_finish 就是之前说的,串口接收一帧数据完成的标识,是在定时器中断中置1的,咱们看下定制器中断串口中断的操作:
void TIM2_IRQHandler(void)
{   
  /***清楚tim中断和关闭tim中断,在uart接受中断里  有相应的使能****/
   TIM_Cmd(TIM2, DISABLE);//触发定时器中断时,则定时器禁能,直到串口中断接收数据时再打开
   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
/****copy Uart0_rev_count的值****/
   uart1_rev_count=RxCounter1;
   RxCounter1=0;
   flag_rev_finish=1;
}

void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {   
        TIM_Cmd(TIM2, ENABLE);//串口有数据接收时打开定时器中断开始计时
        TIM2->CNT &= 0x0000;//定时器延时设置  每次接收清零 当接受间隔超过定时器定的值时  进入tim中断  认为一帧接受完成
        
        RxBuffer1[RxCounter1++] = USART_ReceiveData(USART1);//这里需要注意RxCounter1的值不要超过串口缓冲区RxBuffer1的长度,否则数组溢出,后果很严重
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}
        串口接收一帧数据的机制相比大家也看懂了吧,看不懂我也没法,还是问度娘吧,谷歌也行,我只能如此了,你们也别问为啥我不加那个溢出的判断,懒得改了,我都改好了,你们就没改的了,留点给你们改吧,O(∩_∩)O哈哈~,开个玩笑了。
        下面看一下tcp的发送,这里用的函数tcp_write
    // 向已经建立连接的所有tcp客户端发送RS232数据
    for(cpcb = tcp_active_pcbs;cpcb != NULL; cpcb = cpcb->next)
    {
        tcp_write(cpcb,RxBuffer1,RS232_TCP_send_counter,TCP_WRITE_FLAG_COPY);
        tcp_output(cpcb);
    }
        这里看一下变量tcp_active_pcbs,它是这样定义的:
/** List of all TCP PCBs that are in a state in which they accept or send data. */
/*所有在accept或者send数据状态的TCP PCB列表 */  
struct tcp_pcb *tcp_active_pcbs;
        也就是向已经建立连接的tcp客户端发送数据了,这个就讲到这里吧,本人才疏学浅,望大家见谅,程序借鉴了网友的一些东西,还望海涵,谢谢!
       


下面我们将程序烧录到开发板,测试一下:
1、        烧录不多说了,运行程序
2、        打开串口调试助手,选择好端口号,设置下波特率等参数
7.png (115.11 KB, 下载次数: 0) 下载附件 2014-5-21 16:54 上传
        好吧,就这样了,通讯基本都讲完了,其实也没讲什么,主要就是告诉大家如何在现有的基础例程上面添加新的功能,多谢大家!

最后附上笔记和源码
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。