2015年第六届蓝桥杯—电压测量监控设备
功能简述
任务主体由LCD显示、USART串口通信、LED灯阵显示、E2PROM、Key、RTC、ADC等功能模块组成。完成电压采集并定时上报的功能
设计任务及代码实现
RTC时钟
STM32CubeMX配置
代码实现
//rtc时钟相关定义
RTC_TimeTypeDef Time;
RTC_TimeTypeDef Setting_Time;
RTC_DateTypeDef Data;
void Get_Time(void)
{
HAL_RTC_GetTime(&hrtc, &Time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &Data, RTC_FORMAT_BIN);
}
注意事项:此处一定要先获取RTC时间,再获取日期。如果两者相反,则有可能出现程序运行卡顿。
ADC采集
STM32CubeMX配置
由题意并结合G431芯片参考手册,选用ADC2的IN15。在STM32CubeMX配置如下
代码实现
- 此处采用12通道的ADC电压采集,采集电压的范围为0-3.3V。通过计算ADC的分辨率既可以得到采集后的Value值
void Adc(void)
{
HAL_ADC_Start(&hadc2);
Value = (HAL_ADC_GetValue(&hadc2)*3.3)/4096;
}
EEPROM存储k值(在EEPROM中对于浮点数进行存储)
说明:此处EEPROM采用软件IIC,在STM32CubeMX中不需要进行配置
代码实现
定义共用体用来存储浮点数
typedef union EEPROM_Float
{
float a;
uint8_t b[4];
}float_data;
软件IIC的读写操作函数
//i2c相关操作
void iic_24c02_write(uint8_t *pucBuf , uint8_t ucAddr , uint8_t ucNum)//写操作
{
//pucbuf代表存入数据的地址或者数组;ucAddr代表字节开始位置;ucNum代表存入的字节数
I2CStart();
I2CSendByte(0xa0);//0xa0为写操作时候的设备地址
I2CWaitAck(); //等待完成
I2CSendByte(ucAddr);//发送要存入数据的地址如0代表第一个字节存入第0号位;1代表第1个字节存入1号位
I2CWaitAck();//等待完成
while(ucNum--)//相当于一个字节一个字节去存储
{
I2CSendByte(*pucBuf++);
I2CWaitAck();
}
I2CStop();
HAL_Delay(50);//防止在短时间内多次存入数据
}
void iic_24c02_read(uint8_t *pucBuf , uint8_t ucAddr , uint8_t ucNum)//读操作指令
{
//ucNum为想要读取的自己数,pucbuf为读取出来的数据存入的目标数组
I2CStart();
I2CSendByte(0xa0);//0xa0为写操作时候的设备地址
I2CWaitAck(); //等待完成
I2CSendByte(ucAddr);//发送要存入数据的地址如0代表第一个字节存入第0号位;1代表第1个字节存入1号位
I2CWaitAck();//等待完成
I2CStart();
I2CSendByte(0xa1);//0xa1为读操作时候的设备地址
I2CWaitAck(); //等待完成
while(ucNum--)
{
*pucBuf++ = I2CReceiveByte();
if(ucNum)
I2CSendAck();
else
I2CSendNotAck();
}
I2CStop();
}
调用软件IIC的存储函数对于k值进行存储
float_data float_write;
void EEPROM_Store(void)
{
float_write.a = k;
iic_24c02_write(float_write.b , 0 , 4);
}
UART串行通讯设计
STM32CubeMX配置
为了方便数据的收发,此处采用IDLE+DMA的方式进行串口通信
代码实现
- UART变量定义
//UART串口变量
uint8_t Rx_Buf[1000],Rx_Buf_Temp[1000];
uint16_t Rx_Size=1000;
- 串口中断回调函数及字符处理
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1)
{
if(Size == 6)
{
memcpy(Rx_Buf,Rx_Buf_Temp,Size);
if(Rx_Buf[0]=='k' && Rx_Buf[1]=='0' && Rx_Buf[2]=='.'){
k = 1.*(Rx_Buf[3]-48)*0.1;
sprintf(str4,"ok \r\n");
HAL_UART_Transmit_IT(&huart1, str4, sizeof(str4));
}
}
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, Rx_Buf_Temp, Rx_Size);
}
}
- main函数中打开空闲中断
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, Rx_Buf_Temp, Rx_Size);
按键操作
代码实现
- 按键扫描函数
void Key_Scan(void)
{
if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == 0)
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == 0)
{
if(LED_ON_State == 0) LED_ON_State = 1;
else LED_ON_State = 0;
}
}
if(HAL_GPIO_ReadPin(KEY_2_GPIO_Port,KEY_2_Pin) == 0)
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_2_GPIO_Port,KEY_2_Pin) == 0)
{
if(LCD_State ==0) LCD_State =1;
else LCD_State =0;
}
}
if(HAL_GPIO_ReadPin(KEY_3_GPIO_Port,KEY_3_Pin) == 0)
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_3_GPIO_Port,KEY_3_Pin) == 0)
{
if(Setting_State<3)
Setting_State++;
if(Setting_State == 3)
Setting_State =0;
}
}
if(HAL_GPIO_ReadPin(KEY_4_GPIO_Port,KEY_4_Pin) == 0)
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_4_GPIO_Port,KEY_4_Pin) == 0)
{
if(Setting_State == 0)//设置秒
{
Setting_Time.Seconds++;
if(Setting_Time.Seconds == 60)
Setting_Time.Seconds = 0;
}
else if(Setting_State == 1)
{
Setting_Time.Minutes++;
if(Setting_Time.Minutes == 60)
Setting_Time.Minutes = 0;
}
else if(Setting_State == 2)
{
Setting_Time.Hours++;
if(Setting_Time.Hours == 24)
Setting_Time.Hours =0;
}
}
}
}
LCD显示(直接调用LCD显示库函数即可)
说明:
- 为了达到屏幕效果切换,在LCD显示界面设定了不同操作界面的状态;同时为了达到很好的显示效果,不造成LCD卡顿,此处通过输入空白行来达到清屏效果(没有使用
clear
函数)
void LCD_Show(void)
{
if(LCD_State == 0){ //显示界面1
sprintf(str2," V1: %.2f", Value);
LCD_DisplayStringLine(Line1, str2);
sprintf(str2," K:%.1f",k);
LCD_DisplayStringLine(Line2, str2);
if(LED_ON_State == 1) sprintf(str2," LED:ON ");
else sprintf(str2," LED:OFF ");
LCD_DisplayStringLine(Line3, str2);
sprintf(str1," T:%02d-%02d-%02d",Time.Hours,Time.Minutes,Time.Seconds);
LCD_DisplayStringLine(Line4, str1);
sprintf(str2," ");
LCD_DisplayStringLine(Line6, str2);
}
else if(LCD_State == 1)
{
sprintf(str2," ");
LCD_DisplayStringLine(Line1, str2);
LCD_DisplayStringLine(Line2, str2);
LCD_DisplayStringLine(Line4, str2);
sprintf(str2," Setting ");
LCD_DisplayStringLine(Line3, str2);
sprintf(str1," T:%02d-%02d-%02d",Setting_Time.Hours,Setting_Time.Minutes,Setting_Time.Seconds);
LCD_DisplayStringLine(Line6, str1);
}
}
LED显示函数
说明:
- STM32G431由于LCD与LED有引脚公用,所以每次对LED灯进行操作的时候需要对于锁存器进行使能与失能
代码实现
- LED开关灯函数
void LEDx_ON(uint16_t n)
{
LED_ALL&=(0xFEFF<<(n-1))|(0xFEFF>>(16-(n-1)));
GPIOC->ODR=LED_ALL;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
void LEDx_OFF(uint16_t n)
{
LED_ALL|=(0x0100<<(n-1))|(0x0100>>(16-(n-1)));
GPIOC->ODR=LED_ALL;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
void LED_All_Close(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
- LED闪烁函数
void Led1_Toggle(void)
{
if(LED_State==0)
{
LEDx_ON(1);
LED_State=1;
}
else
{
LEDx_OFF(1);
LED_State=0;
}
}
总工程设计
代码实现
- 变量定义
//rtc时钟相关定义
RTC_TimeTypeDef Time;
RTC_TimeTypeDef Setting_Time;
RTC_DateTypeDef Data;
//LCD显示相关定义
unsigned char str1[30],str2[30];
uint8_t LCD_State =0;//控制显示界面
uint8_t Setting_State = 0;//0修改秒 1修改分钟,2修改小时
//Adc相关定义
float k = 0.5;
float Value=0;
//LED闪烁状态变量
uint8_t LED_Option = 1;
uint8_t LED_State = 0; // 控制LED灯电平翻转达到闪烁
uint8_t LED_ON_State = 0; // LED状态标志位,1ON,0 OFF
//UART串口变量
uint8_t Rx_Buf[1000],Rx_Buf_Temp[1000];
uint16_t Rx_Size=1000;
unsigned char str4[30],str3[30],temp[30];
uint8_t Appear_Flag =0;//0代表上报完成,1代表上报未完成。防止上报多次
- 功能初始化
//LCD初始化
LCD_Init();
LCD_Clear(White);
//ADC初始化
HAL_ADC_Init(&hadc2);
//uwTick变量设定
uint32_t Time_uwTick = 0;
uint32_t LED_uwTick = 0;
uint32_t LCD_uwTick = 0;
uint32_t Key_uwTick = 0;
//USART串口悬起
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, Rx_Buf_Temp, Rx_Size);
//LED初始化
LED_All_Close();
//EEPROM初始化
I2CInit();
- 主循环
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(uwTick - Time_uwTick>100)
{
Time_uwTick = uwTick;
Get_Time();
Adc();
EEPROM_Store();
if(Value>3.3*k) LED_Option=1;
else{
LED_Option=0;
LEDx_OFF(1);
}
Reporting();
}
if(LED_Option==1 && LED_ON_State == 1)
{
if(uwTick-LED_uwTick>200)
{
LED_uwTick=uwTick;
Led1_Toggle();
}
}
if(uwTick - LCD_uwTick >100)
{
LCD_uwTick = uwTick;
LCD_Show();
}
if(uwTick - Key_uwTick>200)
{
Key_uwTick = uwTick;
Key_Scan();
}
}
注意事项
- 在使用IIC总线操作EEPROM的时候,一定要在main函数中使用
I2CInit();
函数。不然会导致数据无法存入EEPROM中 - 由于按键采用非阻塞式扫描,需要将按键的扫描周期拉长,方式由于扫描周期过短而造成效果不佳
- 工程文件详见
GitHub - dongjieHuo/STM32G431RBT6_ADC: 本项目为第六届蓝桥杯嵌入式省赛赛题,开发板采用STM32G431RBT6,采用C语言编写
第一次在CSDN中发表文章,求一键三连~~ 之后可能为大家带来蓝桥杯嵌入式真题讲解及CoppeliaSim的机器人仿真~~
本篇文章就到这里,谢谢大家~ 如有错误请批评指正