一、制作目标
以正点原子的miniSTM32F103RCT6开发板为主控,使用甲醛传感器检测环境空气中的甲醛含量(以mg/m^3为单位)、C02含量(以ppm为单位)和总有机挥发物含量TVOC(以mg/m^3为单位)在OLED显示。
二、实物结果
三、元件清单
1、正点原子miniSTM32F103RCT6开发板
2、OLED屏
3、甲醛传感器(三合一VOC/CO2/甲醛气体传感器空气质量监测模块气敏检测模组)
https://item.taobao.com/item.htm?id=756709656786&pisk=grQLOnNOrR2hGd7ps6riqr1NfWPMvlfF_95jrLvnVOBOeshhx3vkVUBVH3guYexJyO9G-pfHRQt5EtsHdMvoyaBNp20kRT4JFsSaapAh-bd5f93lx6vhXbLFSM0kKJ8RNtY8n-40o65euUw0n3Kbdh8XMvgIE3g6fUARj6inG65ezLGiFoj1TbK6vFzBNLN95QdkNUtIPdi6QQmBFT9BfAOvgUT5F3M6fBAqPXTWOdN9GInSdUiIfVOvgYi5FUN95BJ6FUtCF1G7ukpuIKuR0j_AkZern43RWBKpe4vtylFkTHdh9dej1NU2vK1BB4wcj0Wyed7bIlCNEM6H_TUYBEsVBaKXyyH29G1v5pYbRVTh86sXVZwrV6TepF_CDbg56epBXwKEpRpf81_25h4bM1_NK67OZbaWstv1TwT8lS8pRpT9_a2ZRLI1VwxezvwJUNC1RgIynZbvORMc5snQ65nr4HOZBakipsJIIIR9n5Vi40-OpCp065nr4HOw6KVN30oyX9C..&spm=tbpc.boughtlist.suborder_itemtitle.1.67b12e8d0IqUS6
四、甲醛传感器技术参数
五、硬件接线
如图,OLED屏的SDA和SCL分别接PB7、PB6,甲醛传感器的+5引脚接开发板的5V、G引脚接GND、A引脚接单片机的PA9、B引脚接单片机的PA10。
六、工程建立过程
1、cubeMX配置
(1)系统和时钟配置
(2)OLED屏的硬件IIC通信配置
(3)串口及DMA配置
2、程序代码片段
(1)main函数中
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
OLED_Clear();
// HAL_UART_Receive_IT(&huart1,rec_buf,9);//接收数据
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); /* 开启串口空闲中断 */
HAL_UART_Receive_DMA(&huart1, (uint8_t*)Uart1_RxBuff, UART_RX1_BUFFSIZE); /* 开启DMA传输 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if (Uart1Flag)
{
Uart1Flag = 0;
TVOC=(Uart1_Data[2]*256+Uart1_Data[3])*0.001;
CH2O=(Uart1_Data[4]*256+Uart1_Data[5])*0.001;
CO2=(Uart1_Data[6]*256+Uart1_Data[7]);
}
OLED_ShowCHinese(16, 0, 0);
OLED_ShowCHinese(32, 0, 1);
OLED_ShowCHinese(48, 0, 2);
OLED_ShowCHinese(64, 0, 3);
OLED_ShowCHinese(80, 0, 4);
OLED_ShowCHinese(96, 0, 5);//甲醛检测系统
// OLED_ShowString(8, 1, "CH2O Detect", 12);//OLED屏第一行显示烟雾检测字符串
OLED_ShowString(0, 3, "CO2: ppm", 12);//OLED屏第三行显示MQ2的ADC_Value采样值字符串
OLED_ShowNum(32, 3, CO2, 4, 12); //OLED屏第三行显示ADC采集值
OLED_ShowString(0, 4, "TVOC:", 12);//OLED屏第四行显示MQ2的ADC_Volt采样电压字符串
TVOC100=TVOC*100;//将采样电压扩大100倍
sprintf((char*)str_buff, "%d.%d%dmg/m^3", TVOC100/100, (TVOC100%100/10), TVOC100%10);//格式化输出扩大100倍的采样电压
OLED_ShowString(40, 4,(uint8_t *)str_buff,12);//OLED屏第四行显示采集的电压值
CH2O1000=CH2O*1000; //将烟雾含量值扩大100倍
OLED_ShowString(0, 5, "CH2O:", 12);//OLED屏第五行显示烟雾字符串
sprintf((char*)str_buff, "%d.%d%d%dmg/m^3", (CH2O1000/1000),(CH2O1000%1000/100),(CH2O1000%100/10),(CH2O1000%10));//格式化输出扩大100倍的烟雾浓度
OLED_ShowString(40, 5,(uint8_t *)str_buff,12);//OLED屏第五行显示显示烟雾ppm浓度值
HAL_Delay(500);//每隔500ms刷新一次数据显示
}
/* USER CODE END 3 */
}
(2)OLED.C(使用单片机的硬件IIC通信)
#include "oled.h"
#include "oledfont.h"
//OLED的显存
//存放格式如下.
//[0]0 1 2 3 ... 127
//[1]0 1 2 3 ... 127
//[2]0 1 2 3 ... 127
//[3]0 1 2 3 ... 127
//[4]0 1 2 3 ... 127
//[5]0 1 2 3 ... 127
//[6]0 1 2 3 ... 127
//[7]0 1 2 3 ... 127
/**
* @brief OLED写入命令
* @param cmd - 待写入命令
* @note 移植时,请使用自己的底层API实现
*/
static void OLED_Write_Cmd(uint8_t cmd)
{
uint8_t buf[2];
buf[0] = 0x00; //control byte
buf[1] = cmd;
//使用HAL库的API实现
HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, 0xFFFF);
}
/**
* @brief OLED写入数据
* @param cmd - 待写入数据
* @note 移植时,请使用自己的底层API实现
*/
static void OLED_Write_Dat(uint8_t dat)
{
uint8_t buf[2];
buf[0] = 0x40; //control byte
buf[1] = dat;
//使用HAL库的API实现
HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, 0xFFFF);
}
/**
* @brief OLED设置显示位置
* @param x - X方向位置
* @param y - Y方向位置
*/
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
OLED_Write_Cmd(0xb0+y);
OLED_Write_Cmd(((x&0xf0)>>4)|0x10);
OLED_Write_Cmd((x&0x0f)|0x01);
}
/**
* @brief OLED开启显示
*/
void OLED_Display_On(void)
{
OLED_Write_Cmd(0X8D); //SET DCDC命令
OLED_Write_Cmd(0X14); //DCDC ON
OLED_Write_Cmd(0XAF); //DISPLAY ON
}
/**
* @brief OLED关闭显示
*/
void OLED_Display_Off(void)
{
OLED_Write_Cmd(0X8D); //SET DCDC命令
OLED_Write_Cmd(0X10); //DCDC OFF
OLED_Write_Cmd(0XAE); //DISPLAY OFF
}
/**
* @brief OLED清屏函数(清屏之后屏幕全为黑色)
*/
void OLED_Clear(void)
{
uint8_t i,n;
for(i=0;i<8;i++)
{
OLED_Write_Cmd(0xb0+i); //设置页地址(0~7)
OLED_Write_Cmd(0x00); //设置显示位置—列低地址
OLED_Write_Cmd(0x10); //设置显示位置—列高地址
for(n=0;n<128;n++)
{
OLED_Write_Dat(0);
}
}
}
/**
* @brief OLED显示全开(所有像素点全亮)
*/
void OLED_On(void)
{
uint8_t i,n;
for(i=0;i<8;i++)
{
OLED_Write_Cmd(0xb0+i); //设置页地址(0~7)
OLED_Write_Cmd(0x00); //设置显示位置—列低地址
OLED_Write_Cmd(0x10); //设置显示位置—列高地址
for(n=0;n<128;n++)
{
OLED_Write_Dat(1);
}
}
}
/**
* @brief 在指定位置显示一个ASCII字符
* @param x - 0 - 127
* @param y - 0 - 7
* @param chr - 待显示的ASCII字符
* @param size - ASCII字符大小
* 字符大小有12(6*8)/16(8*16)两种大小
*/
void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr,uint8_t size)
{
uint8_t c=0,i=0;
c = chr-' ';
if(x > 128-1)
{
x=0;
y++;
}
if(size ==16)
{
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
{
OLED_Write_Dat(F8X16[c*16+i]);
}
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
{
OLED_Write_Dat(F8X16[c*16+i+8]);
}
}
else
{
OLED_Set_Pos(x,y);
for(i=0;i<6;i++)
{
OLED_Write_Dat(F6x8[c][i]);
}
}
}
/**
* @brief OLED 专用pow函数
* @param m - 底数
* @param n - 指数
*/
static uint32_t oled_pow(uint8_t m,uint8_t n)
{
uint32_t result=1;
while(n--)result*=m;
return result;
}
/**
* @brief 在指定位置显示一个整数
* @param x - 0 - 127
* @param y - 0 - 7
* @param num - 待显示的整数(0-4294967295)
* @param len - 数字的位数
* @param size - ASCII字符大小
* 字符大小有12(6*8)/16(8*16)两种大小
*/
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size)
{
uint8_t t,temp;
uint8_t enshow=0;
for(t=0;t<len;t++)
{
temp=(num/oled_pow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(size/2)*t,y,' ',size);
continue;
}else enshow=1;
}
OLED_ShowChar(x+(size/2)*t,y,temp+'0',size);
}
}
/**
* @brief 在指定位置显示一个字符串
* @param x - 0 - 127
* @param y - 0 - 7
* @param chr - 待显示的字符串指针
* @param size - ASCII字符大小
* 字符大小有12(6*8)/16(8*16)两种大小
*/
void OLED_ShowString(uint8_t x,uint8_t y,char *chr,uint8_t size)
{
uint8_t j=0;
while (chr[j]!='\0')
{ OLED_ShowChar(x,y,chr[j],size);
x+=8;
if(x>120){x=0;y+=2;}
j++;
}
}
/**
* @brief 在指定位置显示一个汉字
* @param x - 0 - 127
* @param y - 0 - 7
* @param no - 汉字在中文字库数组中的索引(下标)
* @note 中文字库在oledfont.h文件中的Hzk数组中,需要提前使用软件对汉字取模
*/
void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no)
{
uint8_t t,adder=0;
OLED_Set_Pos(x,y);
for(t=0;t<16;t++)
{
OLED_Write_Dat(Hzk[2*no][t]);
adder+=1;
}
OLED_Set_Pos(x,y+1);
for(t=0;t<16;t++)
{
OLED_Write_Dat(Hzk[2*no+1][t]);
adder+=1;
}
}
/**
* @brief 在指定位置显示一幅图片
* @param x1,x2 - 0 - 127
* @param y1,y2 - 0 - 7(8表示全屏显示)
* @param BMP - 图片数组地址
* @note 图像数组BMP存放在bmp.h文件中
*/
void OLED_DrawBMP(uint8_t x0, uint8_t y0,uint8_t x1, uint8_t y1,uint8_t BMP[])
{
uint16_t j=0;
uint8_t x,y;
if(y1%8==0)
{
y=y1/8;
}
else
{
y=y1/8+1;
}
for(y=y0;y<y1;y++)
{
OLED_Set_Pos(x0,y);
for(x=x0;x<x1;x++)
{
OLED_Write_Dat(BMP[j++]);
}
}
}
/**
* @brief OLED初始化
*/
void OLED_Init(void)
{
HAL_Delay(500);
OLED_Write_Cmd(0xAE);//--display off
OLED_Write_Cmd(0x00);//---set low column address
OLED_Write_Cmd(0x10);//---set high column address
OLED_Write_Cmd(0x40);//--set start line address
OLED_Write_Cmd(0x81); // contract control
OLED_Write_Cmd(0xFF);//--128
OLED_Write_Cmd(0xA1);//set segment remap
OLED_Write_Cmd(0xC8);//Com scan direction
OLED_Write_Cmd(0xA6);//--normal / reverse
OLED_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
OLED_Write_Cmd(0x3F);//--1/32 duty
OLED_Write_Cmd(0xD3);//-set display offset
OLED_Write_Cmd(0x00);//
OLED_Write_Cmd(0xD5);//set osc division
OLED_Write_Cmd(0x80);
OLED_Write_Cmd(0xD9);//Set Pre-Charge Period
OLED_Write_Cmd(0xF1);//
OLED_Write_Cmd(0xDA);//set com pin configuartion
OLED_Write_Cmd(0x12);//
OLED_Write_Cmd(0xDB);//set Vcomh
OLED_Write_Cmd(0x40);//
OLED_Write_Cmd(0x20);
OLED_Write_Cmd(0x02);
OLED_Write_Cmd(0x8D);//set charge pump enable
OLED_Write_Cmd(0x14);//
OLED_Write_Cmd(0xA4);
OLED_Write_Cmd(0xA6);
OLED_Write_Cmd(0xAF);//--turn on oled panel
OLED_Clear();
OLED_Set_Pos(0,0);
}
(3)串口空闲中断+DMA读取甲醛传感器的数据
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if (RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) // 判断是否是空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志
HAL_UART_DMAStop(&huart1); // 停止本次DMA传输
uartCnt = UART_RX1_BUFFSIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算接收到的数据长度
memcpy(Uart1_Data, Uart1_RxBuff, uartCnt);
Uart1Flag = 1;
HAL_UART_Receive_DMA(&huart1, (uint8_t *)Uart1_RxBuff, UART_RX1_BUFFSIZE); // 重启开始DMA传输
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
七、程序源码工程下载链接
https://download.csdn.net/download/jacklood/90804813