实验课题
(1)自己定义通信规约,採用java或C++编写简单的PC端上位机软件,实现採集器与PC机的通信。实验可在DW710C-PCproject下进行。
(2)实现LCD显示字符、数字、汉字和简单的图像,并能依据上位机发送的命令做对应的显示。此实验须要掌握LCD屏幕的显示原理(可參考LCD屏幕指导手冊)。并编敲代码控制LCD显示(可參考projectDW710C-LCD)。知道怎样用字模提取软件提取字模;另外还要改动採集器端接收到的命令的解析程序,实现不同命令显示不同内容。
实验说明
採集器的一个485接口与RS-485与RS-232转换模块的485端相连,RS-485与RS-232转换模块的232端通过串口线与PC的232串口相连,我们通过编写上位机串口通信软件和执行在採集器中的程序实现二者的通信。
并在此基础上实现LCD依据PC传来的不同信息进行对应的图形化动态显示。
实验步骤
(1) 编写PC端上位机串口通信软件。
我们利用MFC进行图形化界面开发。利用windows串口函数实现PC与採集器通信的收发。涉及到的串口编程函数有:
函数名 |
功能 |
CreateFile |
打开port |
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) 相同採集器端也要有串口函数进行收发。我们利用既有的实验project中给出的採集器端用来与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指令集进行状态设置,常态串口输出为
****> SEND OK****no msg****
採集器推断接收到的信息是否为以上信息,假设是则不点亮背光灯,当手机向gprs发送信息时。串口输出更改,此时採集器捕捉到信息改动。触发点亮。
体会与感悟
在几周时间里,通过对“基于低压电量採集平台DW710C”的摸索研究,而且自己动手实现了一些功能。比如PC端与採集器通信、PC端控制LCD动态显示。真正地了解了嵌入式开发的基本流程。因为时间较短,我们没有做的非常完美,可是我们解决这个问题的思路和方法是正确的。
版权声明:本文博客原创文章,博客,未经同意,不得转载。