任务目标
本次教程将使用赤菟开发板上的DMA模块和LCD模块,通过温度传感器读取数据并通过LCD显示。单片机驱动温湿度模块测量温度,显示屏显示温度名称以及数值。
ADC 模块包含 2 个 12 位的逐次逼近型的模拟数字转换器,最高 14MHz 的输入时钟。支持 16 个外部通道和 2 个内部信号源采样源。可完成通道的单次转换、连续转换,通道间自动扫描模式、间断模式、外部触发模式、双重采样等功能。可以通过模拟看门狗功能监测通道电压是否在阈值范围内。
在赤菟开发板上有一块240*240的TFT彩色小屏幕。屏幕的控制芯片为ST7789, 可以使用SPI接口或8080接口,赤菟开发板使用了8080接口。
为了能够简单的使用这块TFTLCD我们将这块LCD的配置,操作等工作封装成了一系列的API存放在了lcd.c和lcd.h中,将英文和数字的字库放在了font.h中,其中存放了三种大小的字体分别是32、24和12大小。在使用时候只需要#include "lcd.h"即可。
lcd_init()是lcd初始化函数,需要在程序开始时执行一次,主要配置了lcd的相关端口、功能等等。配置好后就可以在lcd上显示字符串,数字,填充色块,绘制点、线、矩形、圆等。例如绘制点lcd_draw_point(u16 x,u16 y);在坐标x,y处绘制一个点,该点的颜色是由lcd_set_color(u16 back,u16 fore);决定的,也可以使用lcd_draw_point_color(u16 x,u16 y,u16 color。屏幕坐标x,y对应的屏幕实物上坐标关系。我们提供的lcd配置中屏幕的左上角为显示坐标原点。水平向右为x正方向,即x为列坐标。向下为y正方向,即y为行坐标。
在官方例程 CH32V307EVT中可以获得温度传感器的数据打印到串口里的代码。
在官方的芯片手册里可以看到对于温度传感器有如下的描述。
下图则是在单片机中计算温度所需的参数以及测量范围。
为了方便大家了解这个程序,我将系统框图放在下面以便大家理解。代码分为三个部分,第一部分的代码是初始化ADC的通信串口1并启动温度传感器,第二部分的代码则是获得ADC的采样值,最后一部分的代码则是将获得的数据在LCD上显示出来。
在上我们可以看到如图所示的引脚管图,所以我选择ADC1口作为读取数据的串口,实际上ADC0到15口都可以用,图中的PGA为放大器,作用是放大信号。
实现过程
开发流程将会分为以下部分讲解:
1.温度传感器
2.LCD的数据传输(DMA)
借鉴官方例程 CH32V307EVT 的 EXAM -> ADC -> Temperature-External channel。创建新工程,将 main.c 中的代码删除,从 Internal_Temperature 例程的 main.c 中复制过来 ADC_Function_Init(),Get_ADC_Val() 和 main() 这三个函数.
把温度传感器的数据传输到芯片中
把数据在显示屏里显示出来
创建新工程,先用代码初始化ADC的通信串口1和缓存,并重新启动这两个部分,以此来确保从ADC中获得的值没有问题。
void ADC_Function_Init(void)
{
ADC_InitTypeDef ADC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div8);//初始化ADC时钟,设置时钟为PCLK2的8分之1,最大时钟为14MHz
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//禁用多通道模式,启用单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//禁用连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不启用外部触发源,启动软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1;//要转换通道数量
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);//启动ADC1串口
ADC_BufferCmd(ADC1, DISABLE); //关闭缓存,测量ADC校准数据
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
Calibrattion_Val = Get_CalibrationValue(ADC1);
ADC_BufferCmd(ADC1, ENABLE); //启动缓存
ADC_TempSensorVrefintCmd(ENABLE);//启动温度传感器
}
之后获取ADC模块的值并获得平均值,这部分代码用于返回ADC1转换出的结果数据。温度传感器在单片机开启时即可使用,所以现在可以用以下代码从ADC1口拿到ADC的数据和其平均值。
u16 Get_ADC_Val(u8 ch)//ADC获得温度传感器的数值
{
u16 val;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//打开ADC1的串口
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
val = ADC_GetConversionValue(ADC1);//val的值为ADC1串口的值
return val;
}
u16 Get_ADC_Average(u8 ch,u8 times)
{
u32 temp_val=0;//初始化temp_val的值
u8 t;
u16 val;
for(t=0;t<times;t++)
{
temp_val+=Get_ADC_Val(ch);//每个时钟周期通过ADC串口获得temp_val的值
Delay_Ms(5);
}
val = temp_val/times;//获得平均值val
return val;//返回val的值到FPGA中
}
Get_ConversionVal()函数考虑了超出温度传感器范围的情况,超过4095返回4095,小于0则返回0,其他情况返回val+Calibrattion_Val的值。从温度传感器特性图里我们可以知道4095对应的是85C°,0对应的是-40℃。
/*********************************************************************
* @fn Get_ConversionVal
*
* @brief Get Conversion Value.
*
* @param val - Sampling value
*
* @return val+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)//获得转化值
{
if((val+Calibrattion_Val)<0|| val==0) return 0;
if((Calibrattion_Val+val)>4095||val==4095) return 4095;//判断FPGA里的值是否在额定范围内
return (val+Calibrattion_Val);//返回当前val的值
}
如下图所示,像贪吃蛇教程里所说的,将LCD所需要的几个文件放入users文件夹里,在代码里引用lcd.h。
然后在main函数里初始化各个部分,并运用代码将温度显示出来。
int main(void)
{
u16 ADC_val;
s32 val_mv;
SystemCoreClockUpdate();//时钟初始化
Delay_Init();//初始化延迟
USART_Printf_Init(115200);//串口初始化
ADC_Function_Init();
lcd_init();
lcd_fill(0,0,239,239,WHITE);//修改文字显示位置和显示屏背景
lcd_set_color(WHITE,BLACK);//修改文字和背景颜色
while(1)
{
ADC_val = Get_ADC_Average( ADC_Channel_TempSensor, 10 );
Delay_Ms(500);
ADC_val = Get_ConversionVal(ADC_val);
val_mv = (ADC_val*3300/4096);//计算温度
lcd_show_string(30, 120, 24, "temprature:%0d",TempSensor_Volt_To_Temper(val_mv));//显示温度的具体数值,位置以及字体大小
Delay_Ms(2);
}
}
如果有关于.h文件或.hex文件的报错,那么引用碎屏的工程文件并修改TempSensor_Volt_To_Temper里图中该部分的地址成0x1FFFF720(图片中为修改后的地址)
实验代码
/********************************** (C) COPYRIGHT *******************************
* File Name : main.c
* Author : WCH
* Version : V1.0.0
* Date : 2021/06/06
* Description : Main program body.
*******************************************************************************/
#include "debug.h"
#include "lcd.h"
/* Global Variable */
s16 Calibrattion_Val = 0;
/*********************************************************************
* @fn ADC_Function_Init
*
* @brief Initializes ADC collection.
*
* @return none
*/
void ADC_Function_Init(void)
{
ADC_InitTypeDef ADC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_BufferCmd(ADC1, DISABLE); //disable buffer
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
Calibrattion_Val = Get_CalibrationValue(ADC1);
ADC_BufferCmd(ADC1, ENABLE); //enable buffer
ADC_TempSensorVrefintCmd(ENABLE);
}
/*********************************************************************
* @fn Get_ADC_Val
*
* @brief Returns ADCx conversion result data.
*
* @param ch - ADC channel.
* ADC_Channel_0 - ADC Channel0 selected.
* ADC_Channel_1 - ADC Channel1 selected.
* ADC_Channel_2 - ADC Channel2 selected.
* ADC_Channel_3 - ADC Channel3 selected.
* ADC_Channel_4 - ADC Channel4 selected.
* ADC_Channel_5 - ADC Channel5 selected.
* ADC_Channel_6 - ADC Channel6 selected.
* ADC_Channel_7 - ADC Channel7 selected.
* ADC_Channel_8 - ADC Channel8 selected.
* ADC_Channel_9 - ADC Channel9 selected.
* ADC_Channel_10 - ADC Channel10 selected.
* ADC_Channel_11 - ADC Channel11 selected.
* ADC_Channel_12 - ADC Channel12 selected.
* ADC_Channel_13 - ADC Channel13 selected.
* ADC_Channel_14 - ADC Channel14 selected.
* ADC_Channel_15 - ADC Channel15 selected.
* ADC_Channel_16 - ADC Channel16 selected.
* ADC_Channel_17 - ADC Channel17 selected.
*
* @return none
*/
u16 Get_ADC_Val(u8 ch)
{
u16 val;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
val = ADC_GetConversionValue(ADC1);
return val;
}
/*********************************************************************
* @fn Get_ADC_Average
*
* @brief Returns ADCx conversion result average data.
*
* @param ch - ADC channel.
* ADC_Channel_0 - ADC Channel0 selected.
* ADC_Channel_1 - ADC Channel1 selected.
* ADC_Channel_2 - ADC Channel2 selected.
* ADC_Channel_3 - ADC Channel3 selected.
* ADC_Channel_4 - ADC Channel4 selected.
* ADC_Channel_5 - ADC Channel5 selected.
* ADC_Channel_6 - ADC Channel6 selected.
* ADC_Channel_7 - ADC Channel7 selected.
* ADC_Channel_8 - ADC Channel8 selected.
* ADC_Channel_9 - ADC Channel9 selected.
* ADC_Channel_10 - ADC Channel10 selected.
* ADC_Channel_11 - ADC Channel11 selected.
* ADC_Channel_12 - ADC Channel12 selected.
* ADC_Channel_13 - ADC Channel13 selected.
* ADC_Channel_14 - ADC Channel14 selected.
* ADC_Channel_15 - ADC Channel15 selected.
* ADC_Channel_16 - ADC Channel16 selected.
* ADC_Channel_17 - ADC Channel17 selected.
*
* @return val - The Data conversion value.
*/
u16 Get_ADC_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
u16 val;
for(t=0;t<times;t++)
{
temp_val+=Get_ADC_Val(ch);
Delay_Ms(5);
}
val = temp_val/times;
return val;
}
/*********************************************************************
* @fn Get_ConversionVal
*
* @brief Get Conversion Value.
*
* @param val - Sampling value
*
* @return val+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)//获得转化值
{
if((val+Calibrattion_Val)<0|| val==0) return 0;
if((Calibrattion_Val+val)>4095||val==4095) return 4095;
return (val+Calibrattion_Val);
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
u16 ADC_val;
s32 val_mv;
SystemCoreClockUpdate();//时钟初始化
Delay_Init();//初始化延迟
USART_Printf_Init(115200);//串口初始化
ADC_Function_Init();
lcd_init();
lcd_fill(0,0,239,239,WHITE);//修改文字显示位置和显示屏背景
lcd_set_color(WHITE,BLACK);//修改文字和背景颜色
while(1)
{
ADC_val = Get_ADC_Average( ADC_Channel_TempSensor, 10 );
Delay_Ms(500);
ADC_val = Get_ConversionVal(ADC_val);
val_mv = (ADC_val*3300/4096);//计算温度
lcd_show_string(30, 120, 24, "temprature:%0d",TempSensor_Volt_To_Temper(val_mv));//显示温度的具体数值,位置以及字体大小
Delay_Ms(2);
}
}
实验现象
如下图所示,在屏幕上可以看到热吹风机吹了之后显示的实时温度