STM32 đọc dữ liệu cảm biến nhiệt độ và độ ẩm AHT10

STM32 sử dụng phần cứng IIC để đọc dữ liệu cảm biến nhiệt độ và độ ẩm AHT10 và hiển thị lên màn hình OLED 0,96 inch.

Tôi dùng vi điều khiển STM32F103C8T6, chương trình được viết bằng thư viện chuẩn ST.

STM32 sử dụng I2C phần cứng để đọc cảm biến nhiệt độ và độ ẩm SHTC3: https://blog.zeruns.com/archives/692.html

Nhóm trao đổi kỹ thuật điện tử/vi điều khiển: 2169025065

Ảnh kết quả thực hiện


Giới thiệu giao thức I2C

Giao thức giao tiếp I2C (Inter-Integrated Circuit) được phát triển bởi công ty Philips, với ít chân, triển khai phần cứng đơn giản, khả năng mở rộng cao, không cần thiết bị thu phát ngoài như USART, CAN (các IC chuyển đổi mức logic), hiện nay được sử dụng rộng rãi trong giao tiếp giữa các IC trong hệ thống.

I2C chỉ có một bus dữ liệu SDA (Serial Data Line), truyền dữ liệu từng bit một, thuộc loại giao tiếp nối tiếp, bán song công.

Giao tiếp bán song công: có thể truyền hai chiều, nhưng không đồng thời, phải luân phiên; có thể hiểu là giao tiếp đơn công có thể đổi chiều, mỗi thời điểm chỉ truyền một chiều, chỉ cần một dây dữ liệu.

Đối với giao thức I2C, người ta chia thành lớp vật lý và lớp giao thức: lớp vật lý quy định đặc tính cơ khí, điện tử (phần cứng), đảm bảo truyền dữ liệu thô trên phương tiện vật lý; lớp giao thức quy định logic giao tiếp, thống nhất cách đóng gói và giải nén dữ liệu giữa bên gửi và bên nhận (phần mềm).

Lớp vật lý I2C

Cách kết nối thường dùng giữa các thiết bị giao tiếp I2C

(1) Là bus hỗ trợ nhiều thiết bị. “Bus” là tín hiệu dùng chung. Trên một bus I2C có thể nối nhiều thiết bị, hỗ trợ nhiều master và nhiều slave.

(2) Một bus I2C chỉ dùng hai dây: SDA (Serial Data Line) hai chiều và SCL (Serial Clock Line). Dây dữ liệu dùng để biểu thị dữ liệu, dây clock dùng đồng bộ thu phát.

(3) Bus được kéo lên qua điện trở pull-up. Khi thiết bị I2C rảnh sẽ ở trạng thái trở kháng cao, khi tất cả rảnh và đều ở trạng thái đó thì điện trở pull-up kéo bus lên mức cao.

Khi giao tiếp I2C, chân GPIO của vi điều khiển phải đặt ở chế độ ra mở (open-drain), nếu không có thể gây chập.

Đọc thêm chi tiết về I2C trên STM32 tại: https://url.zeruns.com/JC0Ah

Tôi không giải thích kỹ ở đây nữa.

Cảm biến nhiệt độ và độ ẩm AHT10

Giới thiệu

AHT10 là cảm biến nhiệt độ và độ ẩm sản xuất trong nước, giá rẻ, độ chính xác cao, kích thước nhỏ.

AHT10 tích hợp ASIC thiết kế mới, phần tử cảm biến độ ẩm dạng MEMS cải tiến và phần tử nhiệt độ chuẩn trên chip; hiệu năng vượt trội và độ tin cậy cao hơn thế hệ trước, hoạt động ổn định hơn trong môi trường khắc nghiệt.

Tải datasheet AHT10: https://url.zeruns.com/EDEwF


Tóm tắt từ datasheet:

  • Nhiệt độ: –40 ℃ ~ 85 ℃
  • Sai số nhiệt: ±0,3 ℃
  • Độ ẩm: 0 % ~ 100 %
  • Sai số ẩm: ±2 %
  • Điện áp hoạt động: 1,8 V ~ 3,6 V
  • Giao tiếp: I2C
  • Tần số clock: 100 kHz và 400 kHz

Địa chỉ thiết bị và lệnh đọc/ghi

Trong thực tế, địa chỉ thiết bị AHT10 kết hợp với bit hướng đọc/ghi thành một byte gửi đi; bit thấp nhất là bit hướng, 7 bit cao là địa chỉ thiết bị.

Để ghi dữ liệu/lệnh: sau tín hiệu START gửi “0111 0000” (0x70), bit thấp “0” báo ghi.

Để đọc dữ liệu: sau START gửi “0111 0001” (0x71), bit thấp “1” báo đọc.

Tóm lại: 0x70 = ghi, 0x71 = đọc. Khi dùng I2C phần cứng STM32 chỉ cần nhập 0x70, bit thấp thư viện chuẩn sẽ tự xử lý.

Đọc dữ liệu nhiệt độ và độ ẩm


Một chu kỳ đo gồm ba bước:

  1. Gửi lệnh đo
  2. Chờ đo xong
  3. Đọc dữ liệu

Tóm tắt:

  1. Gửi lệnh đo: gửi 0x70, tiếp theo 0xAC, rồi tham số 0x33, 0x00.
  2. Chờ đo xong: datasheet ghi 75 ms, chờ lâu hơn cũng được.
  3. Đọc dữ liệu: gửi 0x71, nhận liên tiếp 6 byte. Byte đầu là trạng thái: kiểm tra bit 3 (calibration enable), nếu 0 thì gửi lệnh khởi tạo; kiểm tra bit 7 (busy), nếu 0 là đo xong.
  4. Xử lý chuyển đổi dữ liệu.

Tính toán dữ liệu

Theo datasheet AHT10:

Ví dụ: độ ẩm thu được 0x0C6501 = 812 289 decimal
→ Độ ẩm = 812 289 × 100 / 1 048 576 = 77,46 %

Nhiệt độ thu được 0x056A00 = 354 816 decimal
→ Nhiệt độ = (354 816 × 200 / 1 048 576) – 50 = 17,67 ℃

Linh kiện cần dùng

Bo mạch STM32 tối thiểu: https://s.click.taobao.com/bqMwZRu

Mô-đun AHT10: https://s.click.taobao.com/gIF09Ru

Mô-đun OLED: https://s.click.taobao.com/aNlvZRu

Dây nối Dupont: https://s.click.taobao.com/xAkAJRu

Breadboard: https://s.click.taobao.com/ShJAJRu

ST-LINK V2: https://s.click.taobao.com/C8ftZRu

Chương trình

Chỉ đăng ba file chính: main.c, AHT10.c và OLED.c; các file khác xin tải gói nén bên dưới.

Toàn bộ project: https://url.zeruns.com/AHT10

Mô-đun AHT10 và OLED: SCL nối PB6, SDA nối PB7.

Dùng VSCode thay Keil để lập trình STM32 và 8051: https://blog.zeruns.com/archives/690.html

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "IWDG.h"
#include "AHT10.h"


uint16_t numlen(uint16_t num);

int main(void)
{
	IWDG_Configuration();	//Khởi tạo watchdog
	OLED_Init();			//Khởi tạo OLED
	AHT10_Init();			//Khởi tạo AHT10
	
	OLED_ShowString(1, 1, "T:");
	OLED_ShowString(2, 1, "H:");

	uint32_t a=0;
	uint16_t err_count=0;
	
	while (1)
	{
		a++;
		OLED_ShowNum(3, 1, a, 9);	//Hiển thị bộ đếm để kiểm tra chương trình
		if(a==999999999)a=0;

		float Temp,Hum;				//Biến lưu nhiệt độ, độ ẩm
		
	/*
	https://blog.zeruns.com
	*/

		if(ReadAHT10(&Hum,&Temp))	//Đọc nhiệt độ, độ ẩm
		{
			if(Temp>=0)
			{
				char String[10];
				sprintf(String, "+%.2fC", Temp);	//Định dạng chuỗi
				OLED_ShowString(1, 3, String);		//Hiển thị nhiệt độ

				sprintf(String, " %.2f%%", Hum);
				OLED_ShowString(2, 3, String);		//Hiển thị độ ẩm
			}else
			{
				char String[10];
				sprintf(String, "-%.2fC", Temp);
				OLED_ShowString(1, 3, String);		//Hiển thị nhiệt độ
				
				sprintf(String, " %.2f%%", Hum);
				OLED_ShowString(2, 3, String);		//Hiển thị độ ẩm
			}
		}
		else
		{
			err_count++;
			OLED_ShowNum(4,1, err_count, 5);	//Hiển thị số lần lỗi
		}

		Delay_ms(100);		//Trì hoãn 100 ms

		IWDG_FeedDog();		//“Cho ăn” watchdog (nếu quá 1 s không feed sẽ reset)
	}
}
```**AHT10.c**

```c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

/*Địa chỉ AHT10*/
#define AHT10_ADDRESS 0x38<<1 //Địa chỉ thiết bị nô lệ là 7 bit, bit cuối cùng là bit hướng truyền, nên dịch trái 1 bit

/*Chọn I2C nào để sử dụng*/
#define I2Cx I2C1

/*
https://blog.zeruns.com
*/

/*Gửi tín hiệu START*/
void AHT10_I2C_START(){
    while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));//Chờ tổng tuyến rảnh
	I2C_GenerateSTART(I2Cx, ENABLE);//Gửi tín hiệu START
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//Kiểm tra sự kiện EV5
}

/*Gửi tín hiệu STOP*/
void AHT10_I2C_STOP(){
    I2C_GenerateSTOP(I2Cx, ENABLE);//Gửi tín hiệu STOP
}

/**
  * @brief  Gửi 3 byte dữ liệu
  * @param  cmd byte lệnh
  * @param  DATA0 tham số thứ 0
  * @param  DATA1 tham số thứ 1
  * @retval Không
  */
void AHT10_WriteByte(uint8_t cmd, uint8_t DATA0, uint8_t DATA1)
{
	AHT10_I2C_START();  //Gửi tín hiệu START
	
	I2C_Send7bitAddress(I2Cx, AHT10_ADDRESS, I2C_Direction_Transmitter);    //Gửi địa chỉ ghi thiết bị
	while(I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);  //Kiểm tra sự kiện EV6

    I2C_SendData(I2Cx, cmd);//Gửi lệnh
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Kiểm tra sự kiện EV8

    I2C_SendData(I2Cx, DATA0);//Gửi byte cao 8 bit tham số lệnh
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Kiểm tra sự kiện EV8
   
	I2C_SendData(I2Cx, DATA1);//Gửi byte thấp 8 bit tham số lệnh
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Kiểm tra sự kiện EV8
	
	I2C_GenerateSTOP(I2Cx, ENABLE);//Gửi tín hiệu STOP	
}

/**
  * @brief  Gửi lệnh đọc trạng thái AHT10
  * @retval Byte trạng thái đọc được
  */
/*uint8_t AHT10_ReadStatus(void){
    AHT10_I2C_START();//Gửi tín hiệu START  
	I2C_Send7bitAddress(I2Cx,AHT10_ADDRESS,I2C_Direction_Receiver);//Gửi địa chỉ đọc thiết bị
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//Kiểm tra sự kiện EV6
    while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//Kiểm tra sự kiện EV7
	I2C_AcknowledgeConfig(I2Cx, DISABLE); //Tắt tín hiệu ACK
	uint8_t status = I2C_ReceiveData(I2Cx);//Đọc dữ liệu và trả về
	AHT10_I2C_STOP();   //Gửi tín hiệu STOP
	I2C_AcknowledgeConfig(I2Cx,ENABLE);//Mở lại tín hiệu ACK
	return status;
}*/

/**
  * @brief  Đọc dữ liệu
  * @retval Byte dữ liệu đọc được
  */
uint8_t AHT10_ReadData(void)
{
    while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//Kiểm tra sự kiện EV7
	return I2C_ReceiveData(I2Cx);//Đọc dữ liệu và trả về
}

/*Reset mềm AHT10*/
void AHT10_SoftReset(void)                    
{
    AHT10_I2C_START();  //Gửi tín hiệu START
	I2C_Send7bitAddress(I2Cx, AHT10_ADDRESS, I2C_Direction_Transmitter);    //Gửi địa chỉ ghi thiết bị
	while(I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);  //Kiểm tra sự kiện EV6
    I2C_SendData(I2Cx, 0xBA);//Gửi lệnh reset mềm
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Kiểm tra sự kiện EV8
    I2C_GenerateSTOP(I2Cx, ENABLE);//Gửi tín hiệu STOP
	Delay_ms(20);
}

/*Khởi tạo chân*/
void AHT10_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Bật clock I2C1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Bật clock GPIOB
 
	/*STM32F103 chip hardware I2C1: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;               //Định nghĩa cấu trúc cấu hình GPIO
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//Thiết lập chế độ ra là Open-Drain, cần điện trở kéo lên
	GPIO_Init(GPIOB, &GPIO_InitStructure);              //Khởi tạo GPIO
	
	I2C_DeInit(I2Cx);	//Đặt lại thanh ghi ngoại vi I2C về mặc định
	I2C_InitTypeDef  I2C_InitStructure;                 //Định nghĩa cấu trúc cấu hình I2C
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Chế độ làm việc
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Tỷ lệ xung clock, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x88;	//Địa chỉ I2C của master, không dùng thì điền gì cũng được
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Bật bit ACK
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Đặt độ dài địa chỉ 7 bit
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//Tốc độ truyền I2C, 400K, xem datasheet chip bạn dùng hỗ trợ tốc độ nào.
	I2C_Init(I2Cx, &I2C_InitStructure);         //Khởi tạo I2C

	I2C_Cmd(I2Cx, ENABLE);  //Kích hoạt I2C
	Delay_ms(20);//Trễ sau khi cấp nguồn
	AHT10_WriteByte(0XE1,0X08,0x00);//Gửi lệnh khởi tạo
	Delay_ms(20);

}

/**
  * @brief  Đọc dữ liệu AHT10
  * @param  *Hum độ ẩm
  * @param  *Temp nhiệt độ
  * @retval 1 - đọc thành công; 0 - đọc thất bại
  */
uint8_t ReadAHT10(float *Hum,float *Temp)
{
    uint8_t Data[5];//Khai báo biến chứa dữ liệu đọc được

	AHT10_WriteByte(0XAC,0X33,0x00);//Gửi lệnh kích hoạt đo

	Delay_ms(70);	//Trễ 70 ms chờ đo xong

    AHT10_I2C_START();//Gửi tín hiệu START  
	I2C_Send7bitAddress(I2Cx,AHT10_ADDRESS,I2C_Direction_Receiver);//Gửi địa chỉ đọc thiết bị
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//Kiểm tra sự kiện EV6
	
	uint8_t i;
	for(i=0;i<6;i++)//Lặp 6 lần đọc 6 byte dữ liệu
	{
		if (i == 5)	//Khi đọc byte cuối thì tắt tín hiệu ACK
		{
			I2C_AcknowledgeConfig(I2Cx, DISABLE); //Tắt tín hiệu ACK
		}
		Data[i] = AHT10_ReadData();	//Đọc dữ liệu
		if (i == 5)
			I2C_GenerateSTOP(I2Cx, ENABLE); //Gửi tín hiệu STOP
	}
	I2C_AcknowledgeConfig(I2Cx,ENABLE);//Mở lại tín hiệu ACK

	if( (Data[0]&0x08) == 0 )//0x08(00001000) kiểm tra bit 3 byte trạng thái (bit cho phép hiệu chuẩn) có bằng 0 không
	{
		AHT10_WriteByte(0XE1,0X08,0x00);	//Gửi lệnh khởi tạo
		Delay_ms(20);
		return 0;
	}
	else if( (Data[0]&0x80) == 0 )//0x80(10000000) kiểm tra bit 7 byte trạng thái (bit báo bận) có bằng 0 không
	{
		
		uint32_t SRH = (Data[1]<<12) | (Data[2]<<4) | (Data[3]>>4);	//Xử lý dữ liệu độ ẩm
		uint32_t ST = ((Data[3]&0x0f)<<16) | (Data[4]<<8) | Data[5];//Xử lý dữ liệu nhiệt độ

		*Hum = (SRH * 100.0) / 1024.0 / 1024;	   //Chuyển đổi dữ liệu độ ẩm theo công thức datasheet
		*Temp = (ST * 200.0) / 1024.0 / 1024 - 50; //Chuyển đổi dữ liệu nhiệt độ theo công thức datasheet

		return 1;
	}

	I2C_GenerateSTOP(I2Cx, ENABLE);//Gửi tín hiệu STOP
	return 0;	

}

/*
https://blog.zeruns.com
*/
```**OLED.c**

```c
#include "stm32f10x.h"
#include "OLED_Font.h"

/*Địa chỉ màn hình OLED*/
#define OLED_ADDRESS 0x78

/*Chọn I2C nào để sử dụng*/
#define I2Cx I2C1

/*
https://blog.zeruns.com
*/

/*Khởi tạo chân*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Bật clock I2C1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Bật clock GPIOB
 
	/*I2C cứng của STM32F103: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//Chế độ ra mở (open-drain), cần điện trở kéo lên
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2Cx);	//Đưa thanh ghi I2C về giá trị mặc định
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Chế độ làm việc
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Tỷ lệ tín hiệu clock, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x88;	//Địa chỉ I2C của master, không dùng thì để tùy, không ảnh hưởng
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Bật bit ACK
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Độ dài địa chỉ 7 bit
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//Tốc độ truyền I2C, 400K, xem datasheet chip để biết tốc độ hỗ trợ.	
	I2C_Init(I2Cx, &I2C_InitStructure);

	I2C_Cmd(I2Cx, ENABLE);
}

void I2C_WriteByte(uint8_t addr,uint8_t data)
{
	
	while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
	
	//Gửi tín hiệu START
	I2C_GenerateSTART(I2Cx, ENABLE);
	//Chờ sự kiện EV5
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	//Gửi địa chỉ thiết bị (ghi)
	I2C_Send7bitAddress(I2Cx, OLED_ADDRESS, I2C_Direction_Transmitter);
	//Chờ sự kiện EV6
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//Gửi địa chỉ thanh ghi bên trong thiết bị
	I2C_SendData(I2Cx, addr);
	//Chờ sự kiện EV8_2
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	
	I2C_SendData(I2Cx, data);//Gửi dữ liệu
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	//Gửi tín hiệu STOP
	I2C_GenerateSTOP(I2Cx, ENABLE);
}
 
/**
  * @brief  Ghi lệnh OLED
  * @param  Command lệnh cần ghi
  * @retval Không
  */
void OLED_WriteCommand(unsigned char Command)//ghi lệnh
{
	I2C_WriteByte(0x00, Command);
}
 
/**
  * @brief  Ghi dữ liệu OLED
  * @param  Data dữ liệu cần ghi
  * @retval Không
*/
void OLED_WriteData(unsigned char Data)//ghi dữ liệu
{
	I2C_WriteByte(0x40, Data);
}

/**
  * @brief  Đặt vị trí con trỏ OLED
  * @param  Y tọa độ theo chiều dọc, gốc trên-trái, 0~7
  * @param  X tọa độ theo chiều ngang, gốc trên-trái, 0~127
  * @retval Không
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);					//đặt Y
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//4 bit cao X
	OLED_WriteCommand(0x00 | (X & 0x0F));			//4 bit thấp X
}

/**
  * @brief  Xóa toàn bộ màn hình OLED
  * @param  Không
  * @retval Không
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * @brief  Xóa một phần màn hình OLED
  * @param  Line hàng, 1~4
  * @param  start cột bắt đầu, 1~16
  * @param  end cột kết thúc, 1~16
  * @retval Không
  */
void OLED_Clear_Part(uint8_t Line, uint8_t start, uint8_t end)
{  
	uint8_t i,Column;
	for(Column = start; Column <= end; Column++)
	{
		OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//nửa trên
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00);		//xóa nửa trên
		}
		OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//nửa dưới
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00);		//xóa nửa dưới
		}
	}
}

/**
  * @brief  Hiển thị một ký tự lên OLED
  * @param  Line hàng, 1~4
  * @param  Column cột, 1~16
  * @param  Char ký tự ASCII cần hiển thị
  * @retval Không
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//nửa trên
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//hiển thị nửa trên
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//nửa dưới
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//hiển thị nửa dưới
	}
}

/**
  * @brief  Hiển thị chuỗi lên OLED
  * @param  Line hàng bắt đầu, 1~4
  * @param  Column cột bắt đầu, 1~16
  * @param  String chuỗi ASCII cần hiển thị
  * @retval Không
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
  * @brief  Hàm lũy thừa cho OLED
  * @retval Trả về X^Y
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
  * @brief  Hiển thị số (thập phân, dương)
  * @param  Line hàng bắt đầu, 1~4
  * @param  Column cột bắt đầu, 1~16
  * @param  Number số cần hiển thị, 0~4294967295
  * @param  Length độ dài số, 1~10
  * @retval Không
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  Hiển thị số (thập phân, có dấu)
  * @param  Line hàng bắt đầu, 1~4
  * @param  Column cột bắt đầu, 1~16
  * @param  Number số cần hiển thị, -2147483648~2147483647
  * @param  Length độ dài số, 1~10
  * @retval Không
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
  * @brief  Hiển thị số (thập lục phân, dương)
  * @param  Line hàng bắt đầu, 1~4
  * @param  Column cột bắt đầu, 1~16
  * @param  Number số cần hiển thị, 0~0xFFFFFFFF
  * @param  Length độ dài số, 1~8
  * @retval Không
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)							
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
  * @brief  Hiển thị số (nhị phân, dương)
  * @param  Line hàng bắt đầu, 1~4
  * @param  Column cột bắt đầu, 1~16
  * @param  Number số cần hiển thị, 0~1111 1111 1111 1111
  * @param  Length độ dài số, 1~16
  * @retval Không
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)							
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
  * @brief  Khởi tạo OLED
  * @param  Không
  * @retval Không
  */
void OLED_Init(void)
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//trì hoãn sau khi cấp nguồn
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();			//khởi tạo chân
	
	OLED_WriteCommand(0xAE);	//tắt hiển thị
	
	OLED_WriteCommand(0xD5);	//tần số chia clock/osc
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//tỷ lệ MUX
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//độ lệch hiển thị
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//dòng bắt đầu hiển thị
	
	OLED_WriteCommand(0xA1);	//chiều trái-phải, 0xA1 bình thường, 0xA0 đảo
	
	OLED_WriteCommand(0xC8);	//chiều trên-dưới, 0xC8 bình thường, 0xC0 đảo

	OLED_WriteCommand(0xDA);	//cấu hình chân COM
	OLED_WriteCommand(0x12);
	
	OLED_WriteCommand(0x81);	//điều khiển độ tương phản
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9);	//chu kỳ nạp trước
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//mức VCOMH
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//bật/tắt toàn bộ đèn

	OLED_WriteCommand(0xA6);	//hiển thị thường/đảo

	OLED_WriteCommand(0x8D);	//bơm nguồn
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF);	//bật hiển thị
		
	OLED_Clear();				//xóa màn hình
}

Đọc thêm

1 Lượt thích