转自 黑鸟论坛
地址:
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 上传
好吧,就这样了,通讯基本都讲完了,其实也没讲什么,主要就是告诉大家如何在现有的基础例程上面添加新的功能,多谢大家!
最后附上笔记和源码
一周热门 更多>