实验课题
(1)自定义通信规约,采用java或C++编写简单的PC端上位机软件,实现采集器与PC机的通信。实验可在DW710C-PC工程下进行。
(2)实现LCD显示字符、数字、汉字和简单的图像,并能根据上位机发送的命令做相应的显示。此实验需要掌握LCD屏幕的显示原理(可参考LCD屏幕指导手册),并编写程序控制LCD显示(可参考工程DW710C-LCD);知道如何用字模提取软件提取字模;另外还要修改采集器端接收到的命令的解析程序,实现不同命令显示不同内容。
实验说明
采集器的一个485接口与RS-485与RS-232转换模块的485端相连,RS-485与RS-232转换模块的232端通过串口线与PC的232串口相连,我们通过编写上位机串口通信软件和运行在采集器中的程序实现二者的通信。并在此基础上实现LCD根据PC传来的不同信息进行相应的图形化动态显示。
实验步骤
(1)编写PC端上位机串口通信软件。
我们利用MFC进行图形化界面开发,利用windows串口函数实现PC与采集器通信的收发。涉及到的串口编程函数有:
函数名 | 功能 |
CreateFile | 打开端口 |
SetCommMask | 设置事件掩码,当指定事件发生时应用程序会收到通知 |
SetCommState | 设置串口状态 |
WriteFile | 发送数据 |
ReadFile | 接收数据 |
void CComplDlg::OnReseved()
{
DWORD length=0;
unsigned char Com_Recv_Buf[256];
ReadFile(hCom,Com_Recv_Buf,20,&length,NULL); //读取串口内容
m_sReseved = 150; //电量
//电表地址,十六进制
m_sAddr1.Format("0x%02x", Com_Recv_Buf[4]);
m_sAddr2.Format("0x%02x", Com_Recv_Buf[5]);
m_sAddr3.Format("0x%02x", Com_Recv_Buf[6]);
m_sAddr4.Format("0x%02x", Com_Recv_Buf[7]);
m_sAddr5.Format("0x%02x", Com_Recv_Buf[8]);
m_sAddr6.Format("0x%02x", Com_Recv_Buf[9]);
UpdateData(false);
SetCommMask(hCom,EV_TXEMPTY);
UpdateData(false);
}
void CComplDlg::OnSend()
{
UpdateData(true);
DWORD length=0;
unsigned char Com_Send_Buf[10];
Com_Send_Buf[0]=m_sSend;
if(WriteFile(hCom,Com_Send_Buf,1,&length,NULL))
{
m_cReseved.EnableWindow(true);
}
else
{
MessageBox(TEXT("数据发送失败!请重试!"),TEXT("提示"),MB_OK);
}
}
void CComplDlg::OnSetupcom()
{
UpdateData(true);
SetupComm(hCom,1024,1024);
COMMTIMEOUTS Timeouts;
//DCB dcb
GetCommState(hCom,&dcb);
dcb.BaudRate=38400;//m_nSetupbt;
dcb.ByteSize=8;
dcb.StopBits=ONESTOPBIT;
dcb.Parity=NOPARITY;
SetCommState(hCom,&dcb);
UpdateData(false);
m_cSend.EnableWindow(true);
m_cSend.SetFocus();
m_cSetupcomm.EnableWindow(false);
m_cEditsend.SetFocus();
}
(2)同样采集器端也要有串口函数进行收发,我们利用既有的实验工程中给出的采集器端用来与PC通信的串口接口进行开发,主要涉及串口接收函数的修改,在其中我们定义自己的通信规则,并返回自定义信息。
void main(void)
{
Str711_Init(); //对主芯片STR711进行初始化
/*设置初始的与PC通信的波特率*/
Base_ParaMeter.Baud_to_Pc = BAUD_UART_PC_ORDER_38400;
/*因为更改了初始的与PC通信的波特率所以再将数据重新写回到SPI_Flash中去*/
SPI_Write_161d(BASE_ADDR_BASE_PARA,(u8*)&Base_ParaMeter,sizeof(Base_ParaMeter));
/*配置与PC通信的UART*/
UART_Config(UART_PC, BAUD_UART_PC_38400, UART_EVEN_PARITY, UART_1_StopBits, UARTM_8D_P);
//主循环
while(1)
{
WDG_CntRefresh(); //刷新看门狗的计数器值
//*****************************************************************************/
/*发送一个数据包过去,请求读电量*/
/* if(Global_Task_Flag &TASK_FLAG_BEGIN_LUNXUN)
{
Global_Task_Flag &=(~TASK_FLAG_BEGIN_LUNXUN);
if((Global_Task_Flag&TASK_FLAG_LUNXUN_ING)==0)
{
WDG_CntRefresh();//刷新看门狗的计数器值
LunXun_Start(); //开始轮询
}
}*/
//*****************************************************************************/
if(Global_Task_Flag&TASK_FLAG_RX_PC_BIT_OK)
{//串口PC的有效数据帧被收到, 执行上位机的相关命令
Global_Task_Flag &= (~TASK_FLAG_RX_PC_BIT_OK);
WDG_CntRefresh(); //刷新看门狗的计数器值
Send_Ack_Or_Data_To_Host_Uart_PC();
}
if(Global_Task_Flag&TASK_FLAG_25MS_TASK)
{//25MS执行一次的任务
Global_Task_Flag &=(~TASK_FLAG_25MS_TASK);
WDG_CntRefresh(); //刷新看门狗的计数器值
Task_2();
}
WDG_CntRefresh(); //刷新看门狗的计数器值
Task_3();
}
}
/*********************************************************************************************
*任务函数: Task2()
*功能: 25ms 执行一次的任务
*********************************************************************************************/
void Task_2()
{
WDG_CntRefresh(); //刷新看门狗的计数器值
Parse_Com_Data(3); //串口PC
}
/*********************************************************************************************
*任务函数: Task3()
*功能: 判断串口数据的发送是否完成,如果完成将状态转换为接收状态
*********************************************************************************************/
void Task_3()
{
//判断串口PC的发送是否完成,如果完成,则将状态转换为接收状态
if(Com_Task_Flag& TASK_FLAG_COM_PC_SEND_COMPLETE)
{
Com_Task_Flag &= (~TASK_FLAG_COM_PC_SEND_COMPLETE);
Com_PC_Send_Total_Len =0;
Com_PC_Send_Pos =0;
}
}
/*********************************************************************************************
*函数名称: Send_Ack_Or_Data_To_Host_Uart_PC()
*功能描述: 对收到串口PC 的上位机命令进行应答
*********************************************************************************************/
void Send_Ack_Or_Data_To_Host_Uart_PC()
{
u32 i;
u8 ch;
/*自定义返回格式与内容,参照DLT 645-1997多功能电能表通信规约的通信协议*/
for(int index=0; index<10; index++)
Com_PC_Send_Buf[index] = index;
//向上位机发送应答数据帧
Com_PC_Send_Data_Len = 3;
Com_PC_Send_Check_Sum = 0;
//对数据域加 0x33
for(i=0;i<Com_PC_Send_Data_Len;i++)
{
Com_PC_Send_Buf[10+i] +=0X33;
}
for(i=0;i<10+Com_PC_Send_Data_Len;i++)
{
Com_PC_Send_Check_Sum += Com_PC_Send_Buf[i];
}
Com_PC_Send_Buf[i]= Com_PC_Send_Check_Sum;
Com_PC_Send_Buf[i+1] = 0x16;
Com_PC_Send_Buf[i+2] = 0x16;
Com_PC_Send_Buf[i+3] = 0x16;
Com_PC_FE_Number =0;
//关闭接收中断,取得发送的总长度
Com_PC_Send_Total_Len = Com_PC_Send_Data_Len +14; //包括校验和以及0X16
Com_PC_Send_Pos =0;
ch = 0XFE;
PC_TX_ENABLE; //PC发送使能
UART_ByteSend(UART_PC,&ch );
UART_ByteSend(UART_PC,&ch );
UART_ByteSend(UART_PC,&ch );
UART_ByteSend(UART_PC,&ch );
do
{
if(Com_PC_Send_Pos<Com_PC_Send_Total_Len)
{
UART_ByteSend(UART_PC,&Com_PC_Send_Buf[Com_PC_Send_Pos]);
Com_PC_Send_Pos++;
}
else
{
break;
}
}while (!(UART_FlagStatus(UART_PC) & UART_TxFull));
Com_Task_Flag |= TASK_FLAG_COM_PC_SEND_TIME;
UART_ItConfig(UART_PC,UART_TxEmpty|UART_TxHalfEmpty,ENABLE); //发送中断使能
UART_ItConfig(UART_PC,UART_RxHalfFull|UART_TimeOutNotEmpty,DISABLE); //接收中断禁止
}
/***************************************************************************************************
* FunctionName : Parse_Com_Data
* Description : 解析COM口是否有一个完整的数据帧收到
* Parameter(s) :
* Com_Number : 是哪一个COM口 2为下行的485口有;3为上行的PC口
*
* Return : void
***************************************************************************************************/
void Parse_Com_Data(u8 Com_Number)
{
u8* Com_Recv_Buf; /*指向串口接收缓冲区的指针*/
u8 Com_Data_Len; /*记录数据帧的数据域长度*/
u8* Com_Recv_Buf_Ptr_W; /*串口接收缓冲区的写指针*/
u8* Com_Recv_Buf_Ptr_R; /*串口接收缓冲区的读指针*/
u16 COM_RECV_BUF_SIZE; /*接收缓冲区的大小*/
u32 TASK_FLAG_COM_RX_OK; /*接收到一个完整的帧的标志位*/
u8* Com_Process_Buf; /*如果接收的帧完整则将这一帧数据转存到这个处理缓冲区中为后面处理做准备*/
u16 i=0;
switch(Com_Number)
{
case 3:
Com_Recv_Buf = Com_PC_Recv_Buf;
Com_Recv_Buf_Ptr_W = &Com_PC_Recv_Buf_Ptr_W;
Com_Recv_Buf_Ptr_R = &Com_PC_Recv_Buf_Ptr_R;
Com_Process_Buf = Com_PC_Process_Buf;
COM_RECV_BUF_SIZE = COM_RECV_BUF_SIZE_HW_PC;
TASK_FLAG_COM_RX_OK = TASK_FLAG_RX_PC_BIT_OK;
break;
default:
return;
}
/*若发过来的数据是0x99,则视为可以通信,进行应答*/
if(Com_Recv_Buf[0] == 0x99)
{
Com_Process_Buf[0]=Com_Recv_Buf[0];
//清除缓冲区中所有的数据.
memset(Com_Recv_Buf,0,COM_RECV_BUF_SIZE);
//读写指针清零也可以. 暂时先不清零吧
*Com_Recv_Buf_Ptr_R = 0;
*Com_Recv_Buf_Ptr_W = 0;
Global_Task_Flag |= TASK_FLAG_COM_RX_OK;
} //设置收到串口1有效数据帧标志
return;
}
在采集器端25ms进行一次串口数据读取,通过判断接收到的数据是否为0x99,决定是否进行应答。通过UART_ByteSend函数进行发送应答信息。
(3)至此我们实现了PC与采集器的通信,接下来采集器要根据PC传来的不同信号进行LCD动态显示。为了实现该功能,我们首先将PC与采集器的通信部分与LCD显示部分整合到一起。其中main()变成如下:
void main(void)
{
u8 Year_Mon_Day_tmp[3];
u8 Hour_Min_Second_tmp[3];
Str711_Init(); /*对主芯片STR711进行初始化*/
Back_Light_On(); /*将液晶屏的背光灯打开*/
WritMeterParaToFlash(); /*将电表的基本参数写入到外部的Flash中*/
ReadMeterParaFromFlash();/*将电表的基本参数从外部的Flash中读出来*/
displayfirst(2); /*先让液晶显示屏显示第一屏,这个参数2没有起到作用*/
/*设置初始的与PC通信的波特率*/
Base_ParaMeter.Baud_to_Pc = BAUD_UART_PC_ORDER_38400;
/*因为更改了初始的与PC通信的波特率所以再将数据重新写回到SPI_Flash中去*/
SPI_Write_161d(BASE_ADDR_BASE_PARA,(u8*)&Base_ParaMeter,sizeof(Base_ParaMeter));
/*配置与PC通信的UART*/
UART_Config(UART_PC, BAUD_UART_PC_38400, UART_EVEN_PARITY, UART_1_StopBits, UARTM_8D_P);
/*主循环函数*/
while(1)
{
WDG_CntRefresh(); /*刷新看门狗的计数器值*/
RTC_Read_Date_Time(&Time_Rtc);//读取RTC 当前的日期
//将表的资产编号、当前电表电量、时间在液晶屏上显示
Year_Mon_Day_tmp[0] = (ptim.tm_year)%100;
Year_Mon_Day_tmp[0] = Dec_2_BCD(Year_Mon_Day_tmp[0]);
Year_Mon_Day_tmp[1] = Dec_2_BCD(ptim.tm_mon+1);
Year_Mon_Day_tmp[2] = Dec_2_BCD(ptim.tm_mday);
Hour_Min_Second_tmp[0] = Dec_2_BCD(ptim.tm_hour);
Hour_Min_Second_tmp[1] = Dec_2_BCD(ptim.tm_min);
Hour_Min_Second_tmp[2] = Dec_2_BCD(ptim.tm_sec);
displaysecond(0x1,Meter_Current_Dl,Year_Mon_Day_tmp,Hour_Min_Second_tmp);
//**************************/
if(Global_Task_Flag&TASK_FLAG_RX_PC_BIT_OK)
{//串口PC的有效数据帧被收到, 执行上位机的相关命令
Global_Task_Flag &= (~TASK_FLAG_RX_PC_BIT_OK);
WDG_CntRefresh();
//刷新看门狗的计数器值
//Task_1();
Send_Ack_Or_Data_To_Host_Uart_PC();
displaysecond(0x1,Meter_Current_Dl,Year_Mon_Day_tmp,Hour_Min_Second_tmp);
}
if(Global_Task_Flag&TASK_FLAG_25MS_TASK)
{//25MS执行一次的任务
Global_Task_Flag &=(~TASK_FLAG_25MS_TASK);
WDG_CntRefresh(); //刷新看门狗的计数器值
Task_2();
}
WDG_CntRefresh(); //刷新看门狗的计数器值
Task_3();
}
为了实现LCD动态显示,修改displaysecond()函数,修改后如下:
void
displaysecond(u16 Meter_Number,u8 *elec,u8 *date,u8 *time)
{
memset(lcd_buf,0x00,1024);
int k=0;
for(int i=0; i<10; i++)
{
find_asc(0x4000F0E0);
write_lcd_buf_ascii(row, k);
write_lcd_buf_ascii(row, k+4);
write_lcd_buf_ascii(row, k+8);
write_lcd_buf_ascii(row, k+12);
LCD_Show(lcd_buf);
k += 16;
if(k >108)
k = 0;
}
}
其中row是一个u8类型的全局变量,用来接收PC端发来的控制信号,即显示行数,从而实现动态控制。该变量的接收是在PC与采集器通信的基础上进行修改实现的,修改部分如下:
Com_Process_Buf[0]=Com_Recv_Buf[0];
row = Com_Recv_Buf[0];
//清除缓冲区中所有的数据.
memset(Com_Recv_Buf,0,COM_RECV_BUF_SIZE);
//读写指针清零也可以. 暂时先不清零吧
*Com_Recv_Buf_Ptr_R = 0;
*Com_Recv_Buf_Ptr_W = 0;
Global_Task_Flag |= TASK_FLAG_COM_RX_OK;
} //设置收到串口1有效数据帧标志
return;
}
然后在main函数的主循环函数里面,
if(Global_Task_Flag&TASK_FLAG_RX_PC_BIT_OK)
{//串口PC的有效数据帧被收到,执行上位机的相关命令
Global_Task_Flag&=(~TASK_FLAG_RX_PC_BIT_OK);
WDG_CntRefresh(); //刷新看门狗的计数器值
//Task_1();
Send_Ack_Or_Data_To_Host_Uart_PC();
displaysecond(0x1,Meter_Current_Dl,Year_Mon_Day_tmp,Hour_Min_Second_tmp);
}
该部分在接收到PC端的命令后,即row值发生了改变,再次调用displaysecond函数,LCD在PC端发送的行数显示,至此完成了PC控制LCD动态显示的功能。
(4)GPRS与采集器通信
首先修改采集器串口波特率38400为9600
BAUD_UART_PC_38400àBAUD_UART_PC_9600
采集器端设置通信规约,只有当手机未向gprs发送信息时,点亮LCD背光灯。
Gprs通过AT指令集进行状态设置,常态串口输出为
****>SENDOK****nomsg****
采集器判断接收到的信息是否为以上信息,如果是则不点亮背光灯,当手机向gprs发送信息时,串口输出更改,此时采集器捕捉到信息修改,触发点亮。
体会与感悟
在几周时间里,通过对“基于低压电量采集平台DW710C”的摸索研究,并且自己动手实现了一些功能,例如PC端与采集器通信、PC端控制LCD动态显示,真正地了解了嵌入式开发的基本流程。由于时间较短,我们没有做的很完美,但是我们解决问题的思路和方法都是正确的。