一、题目
二、题目分析
题目要求做一个电压测量监控设备,各个模块的使用都比较简单,但是当要利用各个模块完成一个简单的小项目,就要理清楚整个程序的大致框架。这里将整个功能分为三大部分显示界面,设置界面,串口接发。
1.界面
显示界面由外设读取显示,设置界面设置自动上报时间的。
界面的控制用一个变量LCD_GUI来控制。 若变量为0x00表示显示界面;若变量为0x1_,则为设置界面,并且0x10设置时,0x11设置分,0x12设置秒。
if(unKey_Down == 2) //界面控制
{
if(LCD_GUI == 0x00) //若是显示界面切换为设置界面
{
LCD_Clear(White); // 清屏
LCD_GUI = 0x10;
}
else //返回显示界面
{
LCD_Clear(White);
LCD_GUI=0x00;
time_ctrl[0] = time_disp[0]; //再次返回时,设置更新上报时间
time_ctrl[1] = time_disp[1];
time_ctrl[2] = time_disp[2];
}
}
在设置界面时,选择时分秒,并且闪烁选中的。这里用一个变量set_ctrl,当为1是将要显示字符串对于的时分秒设置为空字符,为0就为原来的。并且该变量在一定时间类翻转来达到闪烁效果。
if(LCD_GUI>>4==1 && unKey_Down ==3) //进入设置界面并且按下按键3
{
if(LCD_GUI==0x12)
{
LCD_GUI=0x10;
}
else
LCD_GUI++;
}
//设置界面
sprintf((char *)Lcd_Disp_String, "Setting");
LCD_DisplayStringLine(Line2, Lcd_Disp_String);
sprintf((char *)Lcd_Disp_String, "T:%02d-%02d-%02d",time_disp[0],time_disp[1],time_disp[2]);
LCD_DisplayStringLine(Line6, Lcd_Disp_String);
if((uwTick - uwTick_Set_time_Set_Point)>=500) //控制设置时分秒的闪烁时间
{
uwTick_Set_time_Set_Point = uwTick;
set_ctrl ^= 0x01;
}
if(set_ctrl == 0x01) // 将相应的时分秒设置为空字符
{
if(LCD_GUI == 0X10)
{
Lcd_Disp_String[2] = ' ';
Lcd_Disp_String[3] = ' ';
}
else if(LCD_GUI == 0X11)
{
Lcd_Disp_String[5] = ' ';
Lcd_Disp_String[6] = ' ';
}
else if(LCD_GUI == 0X12)
{
Lcd_Disp_String[8] = ' ';
Lcd_Disp_String[9] = ' ';
}
}
LCD_DisplayStringLine(Line6, Lcd_Disp_String);
设置更新时间,用两个数值来存储(用一个其实就够了的),一个专门用来显示,一个用来控制上报时间的。当按键4按下时,修改显示变量即可,然后再界面返回时,将显示变量值赋给控制变量
uint8_t time_disp[3]={0};
uint8_t time_ctrl[3]={0};
if(unKey_Down == 4) //值修改
{
if(LCD_GUI == 0X10) // hour
{
if(time_disp[0] >=23)time_disp[0] = 0;
else
time_disp[0]++;
}
else if(LCD_GUI == 0X11) // min
{
if(time_disp[1] >=59)time_disp[1] = 0;
else
time_disp[1]++;
}
else if(LCD_GUI == 0X12) // sed
{
if(time_disp[2]>=59)time_disp[2] = 0;
else
time_disp[2]++;
}
....
else //返回显示界面
{
LCD_Clear(White);
LCD_GUI=0x00;
time_ctrl[0] = time_disp[0]; //再次返回时,设置更新上报时间
time_ctrl[1] = time_disp[1];
time_ctrl[2] = time_disp[2];
}
....
界面的难点基本上就是上面的了,其他细节看后面的完整程序。
2.串口的接发
自动上报,直接在相等时刻发给串口即可,这里要控制在相等时刻只发一次,用一个变量来控制即可。(直接在发送字符后面适当的延时即可)
// 自动上报
if(time_ctrl[0]==sTime.Hours && time_ctrl[1]==sTime.Minutes && time_ctrl[2]==sTime.Seconds)
{
if(Ctrl_Uart_Send_Time_Data_Times ==0)
{
Ctrl_Uart_Send_Time_Data_Times=1;
sprintf(str, "%4.2f+%2.1f+%02d%02d%02d\n",v1, (k_int/10.),sTime.Hours,sTime.Minutes,sTime.Seconds);
HAL_UART_Transmit(&huart1,(unsigned char *)str, strlen(str), 50);
}
}
else
Ctrl_Uart_Send_Time_Data_Times = 0; //当时间变化或者控制值变化,两者不等的时候,恢复下一次数据发送允许。
本题的难点就是串口的接收,在发生错误的时候,设备不相应。发生的数据格式k0.x\n。转换为十六进制为:k0.1\n ==>6B 30 2E 31 5C 6E 。这里在串口中"\n"会被当成两个字符处理
发生错误可以从两种角度考虑:数据内容不匹配和数据长度不为6。
因此,可以在中断接收函数里面以字节为单位接收数据,若是第一个字节并且为6b,于是才开始接收下一个字节。
//串口接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(rx_buf == 0x6b && rx_buf_index ==0)//若数据为6b,并且是第一个数据
{
uwTick_Uart_TI_time_Set_Point= uwTick;//接收到第一个数据启动计时
Start_Flag = 1; //接收下一个字节
}
if(Start_Flag == 1) //满足则把数据接收完
{
rx_buffer[rx_buf_index++] = rx_buf;
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_buf, 1);
}
但是如果上一串数据是错的,但是下一串数据是对的,我们必须又恢复为原来还没开始接收数据前的样子,即需要在一个时间段内将数组的索引归0和开始接收标志归0,为一下接收做准备。所以用一个时间变量来控制串口接收处理时间,数据错误或者数据正确并处理完,时间到了就清0,为下一次接收做准备
// 设置k
// k0.1\n ==>6B 30 2E 31 5C 6E
200ms~300ms之内处理数据
if((uwTick - uwTick_Uart_TI_time_Set_Point) <=300 && (uwTick - uwTick_Uart_TI_time_Set_Point) >= 200)
{
if(rx_buf_index==6) //判断命令长度
{
// 比较内容处第三个
if(rx_buffer[0] == 0x6b && rx_buffer[1] == 0x30 && rx_buffer[2] == 0x2e && rx_buffer[4] == 0x5c && rx_buffer[5] == 0x6e)
{
// 第三个字节范围 1-9
if(rx_buffer[3]>=0x30 && rx_buffer[3]<=0x39)
{
k_int = rx_buffer[3] - 0x30;
sprintf(str, "ok\n");
HAL_UART_Transmit(&huart1,(unsigned char *)str, strlen(str), 50);
iic_24c02_write(&k_int, 0, 1);
}
}
}
// 清0为一次接收做准备
rx_buf_index =0;
Start_Flag =0;
}
三、程序
#include "main.h"
#include "RCC\bsp_rcc.h"
#include "KEY_LED\bsp_key_led.h"
#include "LCD\bsp_lcd.h"
#include "UART\bsp_uart.h"
#include "I2C\bsp_i2c.h"
#include "ADC\bsp_adc.h"
//#include "TIM\bsp_tim.h"
#include "RTC\bsp_rtc.h"
//***全局变量声明区
//*减速变量
__IO uint32_t uwTick_Key_Set_Point = 0;//控制Key_Proc的执行速度
__IO uint32_t uwTick_Led_Set_Point = 0;//控制Led_Proc的执行速度
__IO uint32_t uwTick_Lcd_Set_Point = 0;//控制Lcd_Proc的执行速度
__IO uint32_t uwTick_Usart_Set_Point = 0;//控制Usart_Proc的执行速度
__IO uint32_t uwTick_Set_time_Set_Point = 0;// 时分秒闪烁
__IO uint32_t uwTick_Uart_TI_time_Set_Point = 0;// 串口中断
_Bool Start_Flag;//起始位判断
__IO uint32_t uwTick_Led_Point = 0;
//*按键扫描专用变量
uint8_t ucKey_Val, unKey_Down, ucKey_Up, ucKey_Old;
//*LED专用变量
uint8_t ucLed=0;
//*LCD显示专用变量
uint8_t Lcd_Disp_String[21];//最多显示20个字符
//*串口专用变量
uint8_t str[40];
uint8_t rx_buffer[100];
uint8_t rx_buf;
uint8_t rx_buf_index = 0;//控制数据往buf里边存储的顺序。
//*EEPROM的相关变量
//uint8_t EEPROM_String_1[5] = {0x11,0x22,0x33,0x44,0x55};
//uint8_t EEPROM_String_2[5] = {0};
;
uint8_t k_int = 1;
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
uint8_t LCD_GUI = 0x00; // 默认显示界面
uint8_t time_disp[3]={0};
uint8_t time_ctrl[3]={0};
uint8_t set_ctrl =0;
uint8_t led_ctrl =1; // 0:关闭 1:打开
uint8_t Ctrl_Uart_Send_Time_Data_Times = 0;// 控制只允许到闹钟时间后只上报一次
//***子函数声明区
void Key_Proc(void);
void Led_Proc(void);
void Lcd_Proc(void);
void Usart_Proc(void);
float v1;
//***系统主函数
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/*bsp资源的初始化*/
KEY_LED_Init();
LCD_Init();
LCD_Clear(White);
LCD_SetBackColor(White);
LCD_SetTextColor(Blue);
UART1_Init();
I2CInit();
// ADC1_Init();
ADC2_Init();
RTC_Init();
/*外设使用基本配置*/
//*EEPROM测试
iic_24c02_read(&k_int,0,1);//去EEPOM中读取k的初值
//*串口接收中断打开
HAL_UART_Receive_IT(&huart1, (uint8_t *)(&rx_buffer), 4);
while (1)
{
Key_Proc();
Led_Proc();
Lcd_Proc();
Usart_Proc();
v1 = getADC2()/4096.0*3.3;
}
}
//***按键扫描子函数
void Key_Proc(void)
{
if((uwTick - uwTick_Key_Set_Point)<50) return;//减速函数
uwTick_Key_Set_Point = uwTick;
ucKey_Val = Key_Scan();
unKey_Down = ucKey_Val & (ucKey_Old ^ ucKey_Val);
ucKey_Up = ~ucKey_Val & (ucKey_Old ^ ucKey_Val);
ucKey_Old = ucKey_Val;
if(unKey_Down == 1)//LED控制
{
led_ctrl ^= 0x01;
}
if(unKey_Down == 2) //界面控制
{
if(LCD_GUI == 0x00) //若是显示界面切换为设置界面
{
LCD_Clear(White); // 清屏
LCD_GUI = 0x10;
}
else //返回显示界面
{
LCD_Clear(White);
LCD_GUI=0x00;
time_ctrl[0] = time_disp[0]; //再次返回时,设置更新上报时间
time_ctrl[1] = time_disp[1];
time_ctrl[2] = time_disp[2];
}
}
if(LCD_GUI>>4==1 && unKey_Down ==3) //进入设置界面并且按下按键3
{
if(LCD_GUI==0x12)
{
LCD_GUI=0x10;
}
else
LCD_GUI++;
}
if(unKey_Down == 4) //值修改
{
if(LCD_GUI == 0X10) // hour
{
if(time_disp[0] >=23)time_disp[0] = 0;
else
time_disp[0]++;
}
else if(LCD_GUI == 0X11) // min
{
if(time_disp[1] >=59)time_disp[1] = 0;
else
time_disp[1]++;
}
else if(LCD_GUI == 0X12) // sed
{
if(time_disp[2]>=59)time_disp[2] = 0;
else
time_disp[2]++;
}
}
}
//***LED扫描子函数
void Led_Proc(void)
{
if((uwTick - uwTick_Led_Set_Point)<200) return;//减速函数
uwTick_Led_Set_Point = uwTick;
if(led_ctrl==1)// on
{
if(v1>3.3*k_int*0.1) //亮
{
if((uwTick - uwTick_Led_Point)>=200)
{
uwTick_Led_Point = uwTick;
ucLed ^= 0x01;
}
}
else
ucLed = 0;
}
LED_Disp(ucLed);
}
void Lcd_Proc(void)
{
if((uwTick - uwTick_Lcd_Set_Point)<100) return;//减速函数
uwTick_Lcd_Set_Point = uwTick;
if(LCD_GUI == 0X00) // 主界面
{
sprintf((char *)Lcd_Disp_String, "V1:%.2fV",v1);
LCD_DisplayStringLine(Line2, Lcd_Disp_String);
sprintf((char *)Lcd_Disp_String, "k:%.1f", (k_int * 0.1));
LCD_DisplayStringLine(Line4, Lcd_Disp_String);
if(led_ctrl ==0)
sprintf((char *)Lcd_Disp_String, "LED:OFF");
else
sprintf((char *)Lcd_Disp_String, "LED:ON ");
LCD_DisplayStringLine(Line6, Lcd_Disp_String);
//*RTC内容显示
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);//读取日期和时间必须同时使用
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sprintf((char *)Lcd_Disp_String, "T:%02d-%02d-%02d",(unsigned int)sTime.Hours,(unsigned int)sTime.Minutes,(unsigned int)sTime.Seconds);
LCD_DisplayStringLine(Line8, Lcd_Disp_String);
}
else
{ //设置界面
sprintf((char *)Lcd_Disp_String, "Setting");
LCD_DisplayStringLine(Line2, Lcd_Disp_String);
sprintf((char *)Lcd_Disp_String, "T:%02d-%02d-%02d",time_disp[0],time_disp[1],time_disp[2]);
LCD_DisplayStringLine(Line6, Lcd_Disp_String);
if((uwTick - uwTick_Set_time_Set_Point)>=500) //控制设置时分秒的闪烁时间
{
uwTick_Set_time_Set_Point = uwTick;
set_ctrl ^= 0x01;
}
if(set_ctrl == 0x01) // 将相应的时分秒设置为空字符
{
if(LCD_GUI == 0X10)
{
Lcd_Disp_String[2] = ' ';
Lcd_Disp_String[3] = ' ';
}
else if(LCD_GUI == 0X11)
{
Lcd_Disp_String[5] = ' ';
Lcd_Disp_String[6] = ' ';
}
else if(LCD_GUI == 0X12)
{
Lcd_Disp_String[8] = ' ';
Lcd_Disp_String[9] = ' ';
}
}
LCD_DisplayStringLine(Line6, Lcd_Disp_String);
}
}
void Usart_Proc(void)
{
if((uwTick - uwTick_Usart_Set_Point)<30) return;//减速函数
uwTick_Usart_Set_Point = uwTick;
// 自动上报
if(time_ctrl[0]==sTime.Hours && time_ctrl[1]==sTime.Minutes && time_ctrl[2]==sTime.Seconds)
{
if(Ctrl_Uart_Send_Time_Data_Times ==0)
{
Ctrl_Uart_Send_Time_Data_Times=1;
sprintf(str, "%4.2f+%2.1f+%02d%02d%02d\n",v1, (k_int/10.),sTime.Hours,sTime.Minutes,sTime.Seconds);
HAL_UART_Transmit(&huart1,(unsigned char *)str, strlen(str), 50);
}
}
else
Ctrl_Uart_Send_Time_Data_Times = 0; //当时间变化或者控制值变化,两者不等的时候,恢复下一次数据发送允许。
// 设置k
// k0.1\n ==>6B 30 2E 31 5C 6E
200ms~300ms之内处理数据
if((uwTick - uwTick_Uart_TI_time_Set_Point) <=300 && (uwTick - uwTick_Uart_TI_time_Set_Point) >= 200)
{
if(rx_buf_index==6) //判断命令长度
{
// 比较内容处第三个
if(rx_buffer[0] == 0x6b && rx_buffer[1] == 0x30 && rx_buffer[2] == 0x2e && rx_buffer[4] == 0x5c && rx_buffer[5] == 0x6e)
{
// 第三个字节范围 1-9
if(rx_buffer[3]>=0x30 && rx_buffer[3]<=0x39)
{
k_int = rx_buffer[3] - 0x30;
sprintf(str, "ok\n");
HAL_UART_Transmit(&huart1,(unsigned char *)str, strlen(str), 50);
iic_24c02_write(&k_int, 0, 1);
}
}
}
// 清0为一次接收做准备
rx_buf_index =0;
Start_Flag =0;
}
}
//串口接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(rx_buf == 0x6b && rx_buf_index ==0)//若数据为6b,并且是第一个数据
{
uwTick_Uart_TI_time_Set_Point= uwTick;//接收到第一个数据启动计时
Start_Flag = 1; //接收下一个字节
}
if(Start_Flag == 1) //满足则把数据接收完
{
rx_buffer[rx_buf_index++] = rx_buf;
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_buf, 1);
}
void Error_Handler(void)
{
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
}