题解
电压采集与显示
按照题干要求我们每100ms使用任意一个ADC采集电压,但要记录实时电压与最大和最小电压,并显示电压趋势。
100ms就用定时器来处理吧,每100ms进入一次中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM1)//每100ms采样一次
{
ADC2_Value();
}
}
ADC如何配置就不讲了,前面文章有。
下面讲ADC2_Value()
void ADC2_Value()
{
HAL_ADC_Start_DMA(&hadc2,(uint32_t *)adc_buffer,10);
uint32_t value=0;
for(int i=0;i<10;i++)
{
value=value+adc_buffer[i];
}
if(first<2)
{
v_cur=3.3*(value/10.0)/(1<<12);
v_max=v_cur;
v_min=v_max;
v_last=v_cur;
first++;
trend=1;
}else{
v_cur=3.3*(value/10.0)/(1<<12);
}
}
首先我开启了ADC的DMA传输,然后使用均值滤波来处理数据,因为在采集的时候可能有采集值为0的情况,这样使用滤波后数据就更精确了,然后我设置了一个全局first用来控制进入初始数据设置代码段的次数,为什么设置进入两次?因为我在实际测试时发现进入一次是采集不到的,数据全是0,这样会影响最低电压的设置(因为我更新最大最小电压是通过比较的方式,一旦最低设置为0 ,就无法准确显示最低电压了,只能是0了)。我将每个数据(当前值最大最小值)都初始化成第一次采集到的当前电压值,(这也符合逻辑,没有电压变化,这三个值就是一样的)。全局变量trend是用来确定变化趋势的变量,初始设置为增加方向(>>)。
之后再进行数据采集就只是在这里更新当前电压。
然后是电压大小比较函数:
当前电压大于最大电压记录就更新,最小电压更新也是一样的道理。
void value_max_min()
{
v_max=v_cur>v_max ? v_cur:v_max;
v_min=v_cur<v_min ? v_cur:v_min;
}
下面是趋势判断函数:
//1-up,2-down,3-keep
void v_trend()
{
if((uint8_t)v_cur>(uint8_t)v_last)
{
v_last=v_cur;
trend=1;
}else if((uint8_t)v_cur<(uint8_t)v_last)
{
v_last=v_cur;
trend=2;
}else{
v_last=v_cur;
trend=trend;
}
}
获得的trend值会在LCD现实函数里控制显示哪一个趋势符号。
主函数的while函数:
对了这份代码我一起把DHT11的代码也加进去了。。。
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
value_max_min();
v_trend();
// DHT11_GetValue();
LCD_Display();
PWM_control();
}
/* USER CODE END 3 */
}
LCD显示
void LCD_Display()
{
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," DATA");
LCD_DisplayStringLine(Line1,LCD_String);
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," V:%1.1f",v_cur);
LCD_DisplayStringLine(Line3,LCD_String);
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," Vmax:%1.1f",v_max);
LCD_DisplayStringLine(Line4,LCD_String);
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," Vmin:%1.1f",v_min);
LCD_DisplayStringLine(Line5,LCD_String);
switch(trend)
{
case 1:
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," Vx:>>");
LCD_DisplayStringLine(Line6,LCD_String);
break;
case 2:
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," Vx:<<");
LCD_DisplayStringLine(Line6,LCD_String);
break;
default:break;
};
memset((void *)LCD_String,0,sizeof(LCD_String));
sprintf((char*)LCD_String," T:%2.1f",ds18b20_read()/16.0);
LCD_DisplayStringLine(Line7,LCD_String);
// memset((void *)LCD_String,0,sizeof(LCD_String));
// sprintf((char*)LCD_String," DH-H:%2.1f",DH_Humi);
// LCD_DisplayStringLine(Line8,LCD_String);
//
// memset((void *)LCD_String,0,sizeof(LCD_String));
// sprintf((char*)LCD_String," DH-T:%2.1f",DH_Temp);
// LCD_DisplayStringLine(Line9,LCD_String);
}
串口控制
串口工能我使用了DMA加空闲中断的方式,前文我也写过(STM32的串口收发补充,使用DMA+空闲中断)
main函数while之前:
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,USART1_RX,20);
中断函数里:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,USART1_RX,20);
/* USER CODE END USART1_IRQn 1 */
}
使用空闲中断回调处理:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance==USART1)
{
if (strcmp((char *)USART1_RX, "AT\r\n") == 0) {
HAL_UART_Transmit(&huart1,(uint8_t *)"AT\r\n",sizeof("AT\r\n"),10);
} else if (strncmp((char*)USART1_RX, "AT+V", 4) == 0 && strlen((char*)USART1_RX) == 9 && USART1_RX[5] >= '0' && USART1_RX[5] <= '2' && USART1_RX[7] == '\r') {
unsigned char response[20]={0};
float temp;
temp=(USART1_RX[5]=='1') ? v_max:((USART1_RX[5]=='2') ? v_min:v_cur);
sprintf((char*)response, "+V[%c]: %1.2f\r\n",USART1_RX[5],temp );
HAL_UART_Transmit(&huart1,(uint8_t *)response,sizeof(response),10);
}
memset(USART1_RX,0,sizeof(USART1_RX));
}
}
分段解释代码:
按题干要求我们收到:"AT\r\n"就回复"AT\r\n":
if (strcmp((char *)USART1_RX, "AT\r\n") == 0) {
HAL_UART_Transmit(&huart1,(uint8_t *)"AT\r\n",sizeof("AT\r\n"),10);
}
使用strcmp函数比较字符串是否相同。
按题干要求我们收到:"AT+V[0..2]\r\n"就回复"+V[0..2]:DATE\r\n":
比如收到"AT+V[0]\r\n"就要回复"+V[0]:DATE\r\n",DATE是数据‘[ ]’不要丢,
else if (strncmp((char*)USART1_RX, "AT+V", 4) == 0 && strlen((char*)USART1_RX) == 9 && USART1_RX[5] >= '0' && USART1_RX[5] <= '2' && USART1_RX[7] == '\r') {
unsigned char response[20]={0};
float temp;
temp=(USART1_RX[5]=='1') ? v_max:((USART1_RX[5]=='2') ? v_min:v_cur);
sprintf((char*)response, "+V[%c]: %1.2f\r\n",USART1_RX[5],temp );
HAL_UART_Transmit(&huart1,(uint8_t *)response,sizeof(response),10);
}
第二个指令判断比第一个略微复杂一点:首先检测"AT+V"这个是与上一个指令的区别点,然后我们再检测后面的数据帧对不对,比如长度和[ ]中的取值是否在范围,都符合进入下面的条件判断,赋给相应的值:
temp=(USART1_RX[5]=='1') ? v_max:((USART1_RX[5]=='2') ? v_min:v_cur);
然后组成新的帧发送:
sprintf((char*)response, "+V[%c]: %1.2f\r\n",USART1_RX[5],temp );
HAL_UART_Transmit(&huart1,(uint8_t *)response,sizeof(response),10);
最后不要忘了清空DMA缓存为下一次接收数据做准备:
memset(USART1_RX,0,sizeof(USART1_RX));
最后这是测评的图:
温度采集与PWM控制
温度如何采集我在这篇文章里写了:蓝桥杯——扩展板DS18B20和DHT11
PWM的配置与使用:蓝桥杯——定时器篇(2)
那么接下来就是简单的逻辑控制:
volatile double temp;
void PWM_control()
{
temp=ds18b20_read()/16.0;
if(temp<25.0)
{
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,100);
}else if(temp>=25.0&&temp<30.0)
{
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,400);
}else if(temp>=30.0)
{
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,800);
}
}