题目分析
比赛题目
原题参考蓝桥杯大赛—十一届蓝桥杯物联网
硬件分析
蓝桥杯物联网基于STM32L071KBU微控制器和LoRa收发器。LoRa终端集成了CMSIS DPA Link 编程调试工具,与目标微控制器STM32L071KBU连接。调试器内部实现了USB转串口功能,与目标控制器的USART2连接。使用虚拟串口时,需要短接P5的1,2脚和3,4脚。蓝桥杯物联网的套装产品,其外设有以下几个:
- OLED显示屏
引脚 | 功能 |
---|---|
PA8 | I2C3_SCL |
PB4 | I2C3_SDA |
PB5 | 电源控制引脚 |
- 继电器
引脚 | 功能 |
---|---|
PA0 | RELAY1 |
PA1 | RELAY2 |
- LoRa模块
引脚 | 功能 |
---|---|
PA10 | LoRa数据收,发中断输出 |
PA9 | R复位,低电平有效 |
PA4 | SPI片选信号 |
PA5 | SPI时钟信号 |
PA6 | SPI主器件输入从器件输出信号 |
PA7 | SPI主器件输出从器件输入信号 |
- 通用接口 :矩阵键盘,模拟电压输出模块,温度传感器模块
引脚 | 功能 |
---|---|
PB0 | GPIO/EXTI/ADC/TIM3_CH3 |
PB1 | GPIO/EXTI/ADC/TIM3_CH4 |
PB4 | GPIO/EXTI/TIM2_CH2/I2C3_SDA |
PB6 | GPIO/EXTI/I2C1_SCL/USART1_TX |
PB7 | GPIO/EXTI/I2C1_SDA/USART1_RX |
PB8 | GPIO/EXTI |
硬件配置
- 在LoRa终端A上配置温度传感器
- 在LoRa终端B上配置键盘模块
软件配置
LoRa终端A
STM32CubeMx 配置
- Clock选择PLLCLK,HCLK为24MHz;
- Connectivity配置I2C1,I2C3,SPI1,USART2,引脚配置如上硬件分析;
- 继电器PA0,PA1选择GPIO_Output;
- 配置SPI的片选信号PA4为GPIO_Output,OLED电源控制引脚PB5配置为GPIO_Output;
- PC14配置为GPIO_Input,模式为上拉。
按键检测函数
读取按键状态,将当前按键状态与上一次按键状态进行异或,如果为1,按键状态发生改变,再与上上一次按键状态,如果为1,表示按键松开,上一次按键状态为按下。
uint8_t UserKey(void)
{
static uint8_t key_o = 0x00, key_n = 0x00;
uint8_t x = 0;
if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_14) == 0)
key_n = 0x01;
else
key_n = 0x00;
x = (key_n ^ key_o) & key_o;
key_o = key_n;
return x;
}
当串口接收到数据时产生中断,将接收的数据存入RxBuffer数组中。
串口中断函数
void USART2_IRQHandler(void)
{
static uint16_t ch = 0;
if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE)!=RESET))
{
ch=( uint16_t)READ_REG(huart2.Instance->RDR);//接收字符
RxBuffer[CountRx++] = ch;
}
}
串口数据检测函数
当串口接收五个字节后,进行指令判断。
void Check(uint8_t * cmd , uint8_t *dataMax , uint8_t *dataMin)
{
if(cmd[0] == 'M' && cmd[1] == 'A' && cmd[2] == 'X')
{
HAL_UART_Transmit(&huart2,(uint8_t *)"ok",2,100);
*dataMax = ((cmd[3]-0x30)*10)+(cmd[4]-0x30);
sprintf(g_lcdLine_2st_line, "Tmax:%.0F ",(float) *dataMax);
}
else if(cmd[0] == 'M' && cmd[1] == 'I' && cmd[2] == 'N')
{
HAL_UART_Transmit(&huart2,(uint8_t *)"ok",2,100);
*dataMin = ((cmd[3]-0x30)*10)+(cmd[4]-0x30);
sprintf(g_lcdLine_3st_line, "Tmin:%.0F ",(float) *dataMin);
}
else
HAL_UART_Transmit(&huart2,(uint8_t *)"error",5,100);
}
滴答定时器
每隔20ms检测一次按键并判断OLED显示状态;每隔100msSensor_Flag置1,200msg_Radio_Recv_Flag置1。
static uint16_t temp_num2 = 0;
static uint16_t temp_num3 = 0;
static uint16_t temp_num4 = 0;
temp_num2++;
temp_num3++;
temp_num4++;
if(temp_num2 >= 20)
{
temp_num2 = 0;
if(UserKey()) //判断User是否按下
switch(key) //判断OLED显示状态
{
case WenDu: key = Shangxian; break;
case Shangxian: key = WenDu; break;
}
}
if(temp_num3 >= 100)
{
temp_num3 = 0;
Sensor_Flag = 1; //温度读取以及更新标志
}
if(temp_num4 >= 200)
{
temp_num4 = 0;
g_Radio_Recv_Flag = 1; //LoRa接收终端B数据标志
}
继电器控制函数
根据温度是上下限,控制两个继电器不同状态。
void tempControl(char temp ,uint8_t dataMax , uint8_t dataMin)
{
if(temp>dataMax)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
else if(temp<dataMin)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
}
}
main.c文件while(1)程序
Task_Recv函数是对终端B接收的数据并执行相关的数据发送。
if(CountRx>=5)
{
CountRx = 0;
Check(RxBuffer,&max,&min);
RxBuffer[0] = RxBuffer[1] = RxBuffer[2] = RxBuffer[3] = RxBuffer[4] = 0;
}
tempControl(temper,max,min);
switch(key)
{
case Shangxian:
OLED_ShowString(0, 0, (uint8_t *)g_lcdLine_2st_line, 16);
OLED_ShowString(0, 2, (uint8_t *)g_lcdLine_3st_line, 16);
break;
case WenDu:
OLED_ShowString(0, 0, (unsigned char *)"Temperature", 16);
OLED_ShowString(0, 2, (uint8_t *)g_lcdLine_1st_line, 16);
break;
}
if(Sensor_Flag)
{
Sensor_Flag = 0;
temper = Get_Temperature();
sprintf(g_lcdLine_1st_line, " %.1FC ", temper);
}
if (g_Radio_Recv_Flag == 1)
{
g_Radio_Recv_Flag = 0;
Task_Recv(handle);
}
}
LoRa终端B
通过赛点提供的包对终端B的程旭进行修改。
按键任务
判断哪个按键按下执行相关程序
#include "key_task.h"
extern radio_handle_t Radio_handle;
extern char g_lcdLine_1st_line[16];
extern char g_lcdLine_2st_line[16];
extern uint8_t g_Key_Value;
uint8_t Cmd_B1[5] = {0x0A, 0x0B, 0xB1, 0xEE, 0xEF};
uint8_t Cmd_B2[5] = {0x0A, 0x0B, 0xB2, 0xEE, 0xEF};
uint8_t Cmd_B3[5] = {0x0A, 0x0B, 0xB3, 0xEE, 0xEF};
uint8_t Cmd_B4[5] = {0x0A, 0x0B, 0xB4, 0xEE, 0xEF};
uint8_t Radio_State = 0;
void TaskKey_init(void)
{
Keyboard_Init();
}
void Task_Key(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_SET);
switch (g_Key_Value)
{
case B1:
radio_buf_send(Radio_handle, Cmd_B1,5); //发送读取温度cmd
radio_mode_set(Radio_handle, RX_MODE);//进入接收模式
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_RESET);
g_Key_Value = 0;
break;
case B2:
radio_buf_send(Radio_handle, Cmd_B2,5); //发送读取温度上下限cmd
radio_mode_set(Radio_handle, RX_MODE);//进入接收模式
g_Key_Value = 0;
break;
case B4:
radio_buf_send(Radio_handle, Cmd_B3,5); //发送读取继电器K1状态cmd
radio_mode_set(Radio_handle, RX_MODE);//进入接收模式
g_Key_Value = 0;
break;
case B5:
radio_buf_send(Radio_handle, Cmd_B4,5); //发送读取继电器K2状态cmd
radio_mode_set(Radio_handle, RX_MODE);//进入接收模式
g_Key_Value = 0;
break;
default :
;
break;
}
}
数据接收任务
终端A发送数据时,终端B产生中断并在中断回调函数中接收数据并判断终端A发送的数据。Compare_Cmd函数是将数据存入oled显示缓存数据。
uint8_t Compare_Cmd(uint8_t *recv_cmd_buf, uint8_t recv_buf_num)
{
uint8_t cmd_Num = 0;
float temper = 0;
switch (recv_cmd_buf[2])
{
case 0xA1 :
temper = (float)(((float)(recv_cmd_buf[5]*10+recv_cmd_buf[6]))/(float)10);
sprintf(g_lcdLine_1st_line, "Temperature ");
sprintf(g_lcdLine_2st_line, " %.1FC ",(float)temper);
break;
case 0xA2 :
sprintf(g_lcdLine_1st_line, "Tmax:%.0f ",(float)recv_cmd_buf[5]);
sprintf(g_lcdLine_2st_line, "Tmin:%.0f ",(float)recv_cmd_buf[6]);
break;
case 0xA3 :
sprintf(g_lcdLine_1st_line, "K1:Status ");
sprintf(g_lcdLine_2st_line, " %.0f ",(float)recv_cmd_buf[5]);
break;
case 0xA4 :
sprintf(g_lcdLine_1st_line, "K2:Status ");
sprintf(g_lcdLine_2st_line, " %.0f ",(float)recv_cmd_buf[5]);
break;
default : cmd_Num = 0; break;
}
return cmd_Num;
}
主函数
每隔一段时间对OLED进行更新。
Task_Key();
if (g_OLED_Flag == 1)
{
g_OLED_Flag = 0;
OLED_ShowString(0, 0, (uint8_t *)g_lcdLine_1st_line, 16);
OLED_ShowString(0, 2, (uint8_t *)g_lcdLine_2st_line, 16);
}