STM32 đọc cảm biến nhiệt độ và độ ẩm SHT3x, thư viện chuẩn và thư viện HAL

STM32 đọc dữ liệu cảm biến nhiệt độ và độ ẩm SHT3x (SHT30, SHT31, SHT35) và hiển thị trên màn hình OLED 0.96 inch.

Dưới đây tôi cung cấp hai phiên bản mã, một phiên bản sử dụng thư viện chuẩn với I2C phần cứng, phiên bản còn lại sử dụng thư viện HAL với I2C mô phỏng phần mềm.

Tôi sử dụng vi điều khiển STM32F103C8T6, cảm biến nhiệt độ và độ ẩm là SHT30.

STM32 I2C phần mềm đọc dữ liệu cảm biến nhiệt độ và độ ẩm AM2320: https://blog.zeruns.com/archives/695.html
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
STM32 vi điều khiển đọc dữ liệu cảm biến nhiệt độ và độ ẩm AHT10: https://blog.zeruns.com/archives/693.html

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

Hiệu ứng thực hiện


Giới thiệu giao thức I2C

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

I2C chỉ có một đường dữ liệu SDA (Serial Data Line), đường dữ liệu nối tiếp, chỉ có thể gửi dữ liệu từng bit một, thuộc về truyền thông nối tiếp, sử dụng truyền thông bán song công

Truyền thông bán song công: có thể thực hiện truyền thông hai chiều, nhưng không thể diễn ra đồng thời trên hai hướng, phải lần lượt thay phiên nhau, thực tế cũng có thể hiểu là một loại truyền thông đơn công có thể chuyển đổi hướng, tại cùng một thời điểm chỉ có thể truyền theo một hướng, chỉ cần một đường dữ liệu.
Đối với giao thức truyền thông I2C, chia nó thành lớp vật lý và lớp giao thức. Lớp vật lý quy định các đặc tính của các bộ phận có chức năng cơ học và điện tử trong hệ thống truyền thông (phần phần cứng), đảm bảo truyền dữ liệu gốc trên phương tiện vật lý. Lớp giao thức chủ yếu quy định logic truyền thông, thống nhất tiêu chuẩn đóng gói và giải nén dữ liệu của cả hai bên gửi và nhận (lớp phần mềm).

Lớp vật lý I2C

Phương pháp kết nối thường được sử dụng giữa các thiết bị truyền thông I2C

(1) Đây là một tuyến bus hỗ trợ nhiều thiết bị. “Tuyến bus” chỉ các đường tín hiệu được chia sẻ bởi nhiều thiết bị. Trong một tuyến bus truyền thông I2C, có thể kết nối nhiều thiết bị truyền thông I2C, hỗ trợ nhiều máy chủ truyền thông và nhiều máy tớ truyền thông.

(2) Một tuyến bus I2C chỉ sử dụng hai đường tuyến bus, một đường dữ liệu nối tiếp hai chiều SDA (Serial Data Line), một đường xung nhịp nối tiếp SCL (Serial Data Line). Đường dữ liệu được sử dụng để biểu thị dữ liệu, đường xung nhịp được sử dụng để đồng bộ hóa gửi và nhận dữ liệu

(3) Tuyến bus được kết nối với nguồn điện thông qua điện trở kéo lên. Khi thiết bị I2C ở trạng thái rảnh rỗi, nó sẽ xuất ra trạng thái trở kháng cao, và khi tất cả các thiết bị đều ở trạng thái rảnh rỗi, đều xuất ra trạng thái trở kháng cao, điện trở kéo lên sẽ kéo tuyến bus lên mức điện áp cao.

Khi giao tiếp I2C, chân GPIO của vi điều khiển phải được đặt thành đầu ra cửa hở, nếu không có thể gây ra ngắn mạch.

Để biết thêm thông tin liên quan đến I2C của STM32 và phương pháp sử dụng, bạn có thể xem bài viết này: https://url.zeruns.com/JC0Ah

Tôi sẽ không giải thích chi tiết ở đây.

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

Địa chỉ tải xuống bảng dữ liệu SHT3x:

Phiên bản gốc: https://url.zeruns.com/uWNnP Mã trích xuất: icqu

Phiên bản tiếng Trung (dịch máy): https://url.zeruns.com/n9b9t Mã trích xuất: vedy

Bằng cách xem bảng dữ liệu, có thể hiểu được thông tin cơ bản, SHT30 là một cảm biến có thể phát hiện nhiệt độ và độ ẩm,
Phạm vi nhiệt độ: -40℃~125℃
Phạm vi độ ẩm: 0%~100%
Điện áp hoạt động: 2.4v~5.5v
Phương pháp truyền thông: i2c
Tần số xung nhịp: 0 ~ 1000kHz

Tìm thấy một số thông tin chính sau

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

Địa chỉ của SHT3x có thể được đặt bằng cách kết nối chân thứ 2 với mức điện áp cao hoặc thấp, mô-đun cảm biến tôi mua trên Taobao có chân 2 được kết nối với VCC thông qua một điện trở, tức là mặc định là 0x44.

Trong quá trình sử dụng thực tế, địa chỉ thiết bị SHT3x cần được kết hợp với bit hướng dữ liệu/lệnh đọc ghi để tạo thành một byte được gửi cùng lúc, bit thấp nhất của byte là bit hướng dữ liệu/lệnh đọc ghi, 7 bit cao là địa chỉ thiết bị SHT3x.

Nếu muốn ghi dữ liệu hoặc lệnh cho SHT3x thông qua I2C, sau tín hiệu khởi động I2C, cần gửi “1000 1000”, tức là 0x88 cho SHT3x, ngoài việc định địa chỉ thông qua 7 bit cao “1000 100” (0x44) của địa chỉ thiết bị, còn thông báo cho SHT3x thông qua bit thấp nhất “0” rằng tiếp theo là hoạt động ghi dữ liệu hoặc lệnh.

Nếu muốn đọc dữ liệu từ SHT3x thông qua I2C, sau tín hiệu khởi động I2C, cần gửi “1000 1001”, tức là 0x89 cho SHT3x, ngoài việc định địa chỉ thông qua 7 bit cao “1000 100” của địa chỉ thiết bị, còn thông báo cho SHT3x thông qua bit thấp nhất “1” rằng tiếp theo là hoạt động đọc dữ liệu.
Nói một cách đơn giản, 0x88 biểu thị ghi dữ liệu, 0x89 biểu thị đọc dữ liệu. Tuy nhiên, khi sử dụng I2C phần cứng của STM32, chỉ cần nhập 0x88, thư viện chuẩn sẽ xử lý bit thấp nhất.

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

Có thể thấy rằng, các lệnh khác nhau, cách lấy dữ liệu khác nhau, có chế độ đo lường một lần và chế độ đo lường định kỳ, cũng có sự khác biệt giữa Clock Stretching Enable và Disable.

Clock Stretching là kéo dãn xung nhịp. Nếu sử dụng lệnh Clock Stretching Enable, sau khi gửi lệnh đo lường, trong quá trình SHT3x đo lường dữ liệu nhiệt độ và độ ẩm, SHT3x sẽ kéo xuống đường xung nhịp I2C SCL, bằng cách này để cấm máy chủ gửi lệnh cho SHT3x, chỉ khi SHT3x hoàn thành đo lường dữ liệu nhiệt độ và độ ẩm, SHT3x mới sẽ giải phóng đường xung nhịp SCL.

Nếu sử dụng lệnh Clock Stretching Disable, trong quá trình SHT3x đo lường dữ liệu, SHT3x sẽ không kéo xuống đường xung nhịp I2C SCL, chỉ là nếu máy chủ gửi lệnh hoặc dữ liệu trong quá trình SHT3x đo lường dữ liệu, gửi lệnh đọc SHT3x sẽ trả về tín hiệu không xác nhận, chỉ khi đo lường hoàn thành mới gửi lệnh đọc sẽ trả về tín hiệu xác nhận.

Chế độ đo lường định kỳ có thể cho phép cảm biến tự động đo lường và lưu trữ dữ liệu, có thể đặt để đo lường 0.5/1/2/4/10 lần mỗi giây, sau đó thông qua lệnh đọc 0xE000 có thể đọc kết quả đo lường mới nhất.

Từ bảng dữ liệu có thể biết rằng, một chu kỳ đo lường bao gồm 2 bước:

  1. Gửi lệnh đo lường
  2. Đọc dữ liệu sau khi đo lường hoàn thành.

Các lệnh đo lường và lệnh đọc ở trên có thể được tra cứu trong bảng dữ liệu.

Tóm tắt như sau:

  1. Gửi lệnh đo lường: trước tiên gửi lệnh ghi (0x88), sau đó gửi byte cao của lệnh đánh thức (0x2C), sau đó gửi byte thấp của lệnh đánh thức (0x0D).
  2. Đọc dữ liệu và chờ đo lường hoàn thành: gửi lệnh đọc (0x89), chờ máy tớ giải phóng tuyến bus SCL.
  3. Nhận dữ liệu: liên tục nhận 6 byte dữ liệu. 2 byte đầu tiên của 6 byte này là giá trị nhiệt độ, byte thứ 3 là kiểm tra nhiệt độ. Byte thứ 4-5 là giá trị độ ẩm, byte thứ 6 là kiểm tra độ ẩm. Sau khi nhận byte cuối cùng, gửi tín hiệu không xác nhận.
  4. Xử lý dữ liệu: thực hiện kiểm tra CRC và xử lý dữ liệu.

Tính toán dữ liệu

Từ bảng dữ liệu SHT3x có thể biết

Ví dụ: giá trị độ ẩm được thu thập là 0x6501, chuyển đổi sang thập phân là 25857.
Khi đó: Độ ẩm = 100 * 25857 / (65536 - 1) = 39.45 (đơn vị: %)
Giá trị nhiệt độ được thu thập là 0x6600, chuyển đổi sang thập phân là 26112.
Khi đó: Nhiệt độ = -45 + 175 * 26112 / (65536 - 1) = 24.72 (đơn vị: ℃)

Các linh kiện cần sử dụng

Bảng hệ thống tối thiểu STM32: https://s.click.taobao.com/AJEGiNu
Mô-đun SHT3x: https://s.click.taobao.com/55xGiNu
Mô-đun OLED: https://s.click.taobao.com/0dlG0Ou
Dây Dupont: https://s.click.taobao.com/xAkAJRu
Bảng mạch: https://s.click.taobao.com/ShJAJRu
ST-LINK V2: https://s.click.taobao.com/C8ftZRu

Chương trình

Ở đây chỉ đưa ra phiên bản thư viện chuẩn của main.c, SHT3x.c và oled.c, ba tệp mã chính, các tệp khác vui lòng tải xuống gói nén từ liên kết dưới đây.

Phiên bản thư viện chuẩn: https://url.zeruns.com/a49EX Mã trích xuất: 8nn5

Phiên bản thư viện HAL: https://url.zeruns.com/p3og4 Mã trích xuất: v9wc

SCL của mô-đun SHT3x và OLED kết nối với PB6, SDA kết nối với PB7.

Sử dụng VSCode thay thế Keil để phát triển STM32 và vi điều khiển 51: https://blog.zeruns.com/archives/690.html

main.c

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


uint16_t numlen(uint16_t num);

int main(void)
{
	IWDG_Configuration();	//khởi tạo watchdog
	OLED_Init();	 		//khởi tạo màn hình OLED
	SHT3x_Init();	 		//khởi tạo SHT3x
	
	/*OLED_ShowString(1, 1, "T:");
	OLED_ShowString(3, 1, "H:");*/
	
	uint8_t Chinese[]={0,1};
  	OLED_ShowChinese(1,1,Chinese,2);//nhiệt độ
  	uint8_t Chinese1[]={2,3};
	OLED_ShowChinese(3,1,Chinese1,2);//độ ẩm
  	uint8_t Chinese2[]={4};
	OLED_ShowChinese(1,11,Chinese2,1);//℃

	uint32_t a=0;
	uint16_t err_count=0;
	
	while (1)
	{
		a++;
		OLED_ShowNum(2, 14, a, 2);
		if(a==99)a=0;

		float Temp,Hum;	 		//khai báo biến lưu trữ dữ liệu nhiệt độ và độ ẩm

``````c
if(ReadSHT3x(&Hum,&Temp))	//Đọc dữ liệu nhiệt độ và độ ẩm
	{
		if(Temp>=0)
		{
			char String[10];
			sprintf(String, "+%.2f", Temp);//Định dạng chuỗi và xuất ra biến chuỗi
			OLED_ShowString(1, 5, String);	//Hiển thị nhiệt độ

			sprintf(String, " %.2f%%", Hum);	//Định dạng chuỗi và xuất ra biến chuỗi
			OLED_ShowString(3, 5, String);	//Hiển thị độ ẩm
		}else
		{
			char String[10];
			sprintf(String, "%.2f", Temp);//Định dạng chuỗi và xuất ra biến chuỗi
			OLED_ShowString(1, 5, String);	//Hiển thị nhiệt độ
			
			sprintf(String, " %.2f%%", Hum);	//Định dạng chuỗi và xuất ra biến chuỗi
			OLED_ShowString(3, 5, String);	//Hiển thị độ ẩm
		}
	}
	else
	{
		err_count++;
		OLED_ShowNum(1,14, err_count, 2);	//Hiển thị số lần lỗi
	}
	
	/*
	https://blog.zeruns.com
	*/

	Delay_ms(200);	//Trễ 200 mili giây

	IWDG_FeedDog();	//Cho chó ăn (watchdog, nếu không thực hiện cho chó ăn trong hơn 1 giây thì tự động đặt lại)
}
}

/**
  * @brief  Tính độ dài của số nguyên
  * @param  num Số nguyên cần tính độ dài
  * @retval Giá trị độ dài
  */
uint16_t numlen(uint16_t num)
{
    uint16_t len = 0;        // Độ dài ban đầu là 0
    for(; num > 0; ++len)    // Kiểm tra xem num có lớn hơn 0 không, nếu không thì độ dài + 1
        num /= 10;	         // Sử dụng phép chia để tính toán, cho đến khi num nhỏ hơn 1
    return len;              // Trả về giá trị độ dài
}

SHT3x.c

#include "stm32f10x.h"
#include "Delay.h"

/*Địa chỉ SHT3x*/
#define SHT3x_ADDRESS 0x44<<1	//Địa chỉ slave là 7 bit, vì vậy dịch trái một bit

/*Đặt sử dụng I2C nào*/
#define I2Cx I2C1

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

/**
  * @brief  Kiểm tra CRC, đa thức CRC là: x^8+x^5+x^4+1, tức là 0x31
  * @param  DAT Dữ liệu cần kiểm tra
  * @retval Mã kiểm tra
  */
uint8_t SHT3x_CRC_CAL(uint16_t DAT)
{
	uint8_t i,t,temp;
	uint8_t CRC_BYTE;

	CRC_BYTE = 0xFF;
	temp = (DAT>>8) & 0xFF;

	for(t = 0; t < 2; t++)
	{
		CRC_BYTE ^= temp;
		for(i = 0;i < 8;i ++)
		{
			if(CRC_BYTE & 0x80)
			{
				CRC_BYTE <<= 1;
				CRC_BYTE ^= 0x31;
			}
			else
			{
				CRC_BYTE <<= 1;
			}
		}

		if(t == 0)
		{
			temp = DAT & 0xFF;
		}
	}

	return CRC_BYTE;
}

/*Gửi tín hiệu bắt đầu*/
void SHT3x_I2C_START(){
    while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));//Chờ bus rảnh
	I2C_GenerateSTART(I2Cx, ENABLE);//Gửi tín hiệu bắt đầu
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//Phát hiện sự kiện EV5
}

/*Gửi tín hiệu dừng*/
void SHT3x_I2C_STOP(){
    I2C_GenerateSTOP(I2Cx, ENABLE);//Gửi tín hiệu dừng
}

/**
  * @brief  Gửi hai byte dữ liệu
  * @param  MSB 8 bit cao
  * @param  LSB 8 bit thấp
  * @retval Không có
  */
void SHT3x_WriteByte(uint8_t MSB,uint8_t LSB)
{
	SHT3x_I2C_START();  //Gửi tín hiệu bắt đầu
	
	I2C_Send7bitAddress(I2Cx, SHT3x_ADDRESS, I2C_Direction_Transmitter);    //Gửi địa chỉ ghi thiết bị
	while(I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);  //Phát hiện sự kiện EV6

    I2C_SendData(I2Cx, MSB);//Gửi dữ liệu 8 bit cao
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Phát hiện sự kiện EV8
   
	I2C_SendData(I2Cx, LSB);//Gửi dữ liệu 8 bit thấp
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Phát hiện sự kiện EV8
	
	I2C_GenerateSTOP(I2Cx, ENABLE);//Gửi tín hiệu dừng
	
}

/**
  * @brief  Đọc dữ liệu
  * @retval Dữ liệu byte được đọc
  */
uint8_t SHT3x_ReadData()
{
    while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//Phát hiện sự kiện EV7
	return I2C_ReceiveData(I2Cx);//Đọc dữ liệu và trả về
}

/*Đặt lại mềm SHT3x*/
void SHT3x_SoftReset(void)                    
{
    SHT3x_WriteByte(0x30,0xA2);    //Khởi tạo lại SHT3x
}

/*Khởi tạo chân*/
void SHT3x_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Bật xung nhịp I2C1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Bật xung nhịp GPIOB
 
	/*I2C1 phần cứng của chip STM32F103: 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;		//Đặt chế độ đầu ra là đầu ra hở, cần kéo lên điện trở
	GPIO_Init(GPIOB, &GPIO_InitStructure);              //Khởi tạo GPIO
	
	I2C_DeInit(I2Cx);	//Đặt lại thanh ghi ngoại vi I2C về giá trị 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ế độ hoạt động
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Chu kỳ xung nhịp, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//Địa chỉ I2C của master, nếu không dùng thì viết bất kỳ, không ảnh hưởng
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Bật bit xác nhận
	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, kiểm tra sổ tay chip của bạn để xem tốc độ được hỗ trợ.	
	I2C_Init(I2Cx, &I2C_InitStructure);         //Khởi tạo I2C

	I2C_Cmd(I2Cx, ENABLE);  //Bật I2C

	//SHT3x_WriteByte(0X27,0X21); //Chế độ thu thập dữ liệu định kỳ (10 lần mỗi giây, Medium Repeatability)

	Delay_us(300);//Trễ 300 micro giây
}

/**
  * @brief  Đọc dữ liệu SHT3x
  * @param  *Hum Độ ẩm
  * @param  *Temp Nhiệt độ
  * @retval 1 - Đọc thành công; 0 - Đọc thất bại
  */
uint8_t ReadSHT3x(float *Hum,float *Temp)
{
    uint16_t HumData,TempData,HumCRC,TempCRC;//Khai báo biến để lưu trữ dữ liệu được đọc
    
    //SHT3x_WriteByte(0XE0,0X00);   //Gửi lệnh, lấy dữ liệu, dùng cho chế độ thu thập dữ liệu định kỳ

    SHT3x_WriteByte(0X2C,0X0D); //Gửi lệnh đo lường một lần (bật phần mở rộng xung nhịp, Medium Repeatability)
	
    SHT3x_I2C_START();//Gửi tín hiệu bắt đầu
    
	I2C_Send7bitAddress(I2Cx,SHT3x_ADDRESS,I2C_Direction_Receiver);//Gửi địa chỉ đọc thiết bị
	
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//Phát hiện sự kiện EV6

    TempData = SHT3x_ReadData();//Đọc dữ liệu 8 bit cao của nhiệt độ
    TempData=TempData<<8;       //Dịch trái 8 bit
	TempData |= SHT3x_ReadData();//Đọc dữ liệu 8 bit thấp của nhiệt độ
    TempCRC = SHT3x_ReadData(); //Đọc dữ liệu kiểm tra CRC của nhiệt độ
	
    HumData = SHT3x_ReadData(); //Đọc dữ liệu 8 bit cao của độ ẩm
    HumData=HumData<<8;         //Dịch trái 8 bit
	HumData |= SHT3x_ReadData();//Đọc dữ liệu 8 bit thấp của độ ẩm
    I2C_AcknowledgeConfig(I2Cx,DISABLE);    //Tắt tín hiệu xác nhận
    HumCRC = SHT3x_ReadData();  //Đọc dữ liệu kiểm tra CRC của độ ẩm

    SHT3x_I2C_STOP();   //Gửi tín hiệu dừng
    I2C_AcknowledgeConfig(I2Cx,ENABLE); //Bật tín hiệu xác nhận
	

    if( SHT3x_CRC_CAL(HumData)==HumCRC && SHT3x_CRC_CAL(TempData)==TempCRC ){   //Kiểm tra CRC dữ liệu nhận được
       *Hum = (float)HumData*100/(65536-1);        //Chuyển đổi dữ liệu nhị phân 16 bit nhận được thành dữ liệu độ ẩm thập phân
       *Temp = (float)TempData*175/(65536-1)-45;   //Chuyển đổi dữ liệu nhị phân 16 bit nhận được thành dữ liệu nhiệt độ thập phân
       return 1;
    }
    return 0;
}

oled.c

#include "stm32f10x.h"
#include "OLED_Font.h"

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

/*Đặt sử dụng I2C nào*/
#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 xung nhịp I2C1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Bật xung nhịp GPIOB
 
	/*I2C của chip 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;		//Đặt chế độ đầu ra là đầu ra hở, cần kéo lên điện trở
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2Cx);	//Đặt lại thanh ghi ngoại vi I2C về giá trị mặc định
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Chế độ hoạt động
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Chu kỳ xung nhịp, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//Địa chỉ I2C của master, nếu không dùng thì viết bất kỳ, không ảnh hưởng
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Bật bit xác nhận
	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, kiểm tra sổ tay chip của bạn để xem tốc độ đượ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 bắt đầu
	I2C_GenerateSTART(I2Cx, ENABLE);
	//Phát hiện sự kiện EV5
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	//Gửi địa chỉ ghi thiết bị
	I2C_Send7bitAddress(I2Cx, OLED_ADDRESS, I2C_Direction_Transmitter);
	//Phát hiện sự kiện EV6
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//Gửi địa chỉ bên trong thiết bị cần vận hành
	I2C_SendData(I2Cx, addr);
	//Phát hiện 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 dừng
	I2C_GenerateSTOP(I2Cx, ENABLE);
}
 
/**
  * @brief  Ghi lệnh OLED
  * @param  Command Lệnh cần ghi
  * @retval Không có
  */
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 có
*/
void OLED_WriteData(unsigned char Data)//Ghi dữ liệu
{
	I2C_WriteByte(0x40, Data);
}

/**
  * @brief  Đặt vị trí con trỏ OLED
  * @param  Y Với góc trên cùng bên trái làm gốc, tọa độ theo hướng xuống, phạm vi: 0~7
  * @param  X Với góc trên cùng bên trái làm gốc, tọa độ theo hướng phải, phạm vi: 0~127
  * @retval Không có
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);	 			//Đặt vị trí Y
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//Đặt 4 bit thấp vị trí X
	OLED_WriteCommand(0x00 | (X & 0x0F));	 		//Đặt 4 bit cao vị trí X
}

/**
  * @brief  Xóa màn hình OLED
  * @param  Không có
  * @retval Không có
  */
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);
		}
	}
}
``````c
/**
  * @brief  Xóa một phần màn hình OLED
  * @param  Line Vị trí hàng, phạm vi: 1~4
  * @param  start Vị trí bắt đầu cột, phạm vi: 1~16
  * @param  end Vị trí bắt đầu cột, phạm vi: 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);	//Đặt vị trí con trỏ ở phần trên
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00);	//Hiển thị nội dung phần trên
		}
		OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//Đặt vị trí con trỏ ở phần dưới
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00);	//Hiển thị nội dung phần dưới
		}
	}
}

/**
  * @brief  Hiển thị một ký tự trên OLED
  * @param  Line Vị trí hàng, phạm vi: 1~4
  * @param  Column Vị trí cột, phạm vi: 1~16
  * @param  Char Ký tự cần hiển thị, phạm vi: Ký tự ASCII có thể nhìn thấy
  * @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);	//Đặt vị trí con trỏ ở phần trên
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]);	//Hiển thị nội dung phần trên
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//Đặt vị trí con trỏ ở phần dưới
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);	//Hiển thị nội dung phần dưới
	}
}

/**
  * @brief  Hiển thị một ký tự Trung Quốc trên OLED
  * @param  Line Vị trí hàng, phạm vi: 1~4
  * @param  Column Vị trí cột, phạm vi: 1~16
  * @param  Chinese Vị trí của ký tự Trung Quốc cần hiển thị trong mảng bộ phông chữ
  * @retval Không
  */
void OLED_ShowWord(uint8_t Line, uint8_t Column, uint8_t Chinese)
{      	
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i]);	
	}
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8 + 8);
	for (i = 1; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i+8]);	
	}
	OLED_SetCursor((Line - 1) * 2 +1, (Column - 1) * 8);
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i+16]);
	}
	OLED_SetCursor((Line - 1) * 2 +1, (Column - 1) * 8 + 8);
	for (i = 1; i < 8; i++)
	{
		OLED_WriteData(OLED_F16x16[Chinese][i+16+8]);
	}
}

/**
  * @brief  Hiển thị một chuỗi ký tự Trung Quốc trên OLED
  * @param  Line Vị trí hàng, phạm vi: 1~4
  * @param  Column Vị trí cột, phạm vi: 1~16
  * @param  Chinese[] Vị trí của ký tự Trung Quốc cần hiển thị trong mảng bộ phông chữ, mảng chứa vị trí của mỗi ký tự
  * @param	Length Độ dài của ký tự Trung Quốc cần hiển thị, phạm vi: 1~8
  * @retval Không
  */
void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t *Chinese,uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)
	{
		OLED_ShowWord(Line, Column + i*2,Chinese[i]);
	}
}

/**
  * @brief  Hiển thị chuỗi ký tự trên OLED
  * @param  Line Vị trí hàng bắt đầu, phạm vi: 1~4
  * @param  Column Vị trí cột bắt đầu, phạm vi: 1~16
  * @param  String Chuỗi ký tự cần hiển thị, phạm vi: Ký tự ASCII có thể nhìn thấy
  * @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 OLED
  * @retval Giá trị trả về bằng X mũ 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ố trên OLED (thập phân, số dương)
  * @param  Line Vị trí hàng bắt đầu, phạm vi: 1~4
  * @param  Column Vị trí cột bắt đầu, phạm vi: 1~16
  * @param  Number Số cần hiển thị, phạm vi: 0~4294967295
  * @param  Length Độ dài của số cần hiển thị, phạm vi: 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ố trên OLED (thập phân, số có dấu)
  * @param  Line Vị trí hàng bắt đầu, phạm vi: 1~4
  * @param  Column Vị trí cột bắt đầu, phạm vi: 1~16
  * @param  Number Số cần hiển thị, phạm vi: -2147483648~2147483647
  * @param  Length Độ dài của số cần hiển thị, phạm vi: 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ố trên OLED (thập lục phân, số dương)
  * @param  Line Vị trí hàng bắt đầu, phạm vi: 1~4
  * @param  Column Vị trí cột bắt đầu, phạm vi: 1~16
  * @param  Number Số cần hiển thị, phạm vi: 0~0xFFFFFFFF
  * @param  Length Độ dài của số cần hiển thị, phạm vi: 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ố trên OLED (nhị phân, số dương)
  * @param  Line Vị trí hàng bắt đầu, phạm vi: 1~4
  * @param  Column Vị trí cột bắt đầu, phạm vi: 1~16
  * @param  Number Số cần hiển thị, phạm vi: 0~1111 1111 1111 1111
  * @param  Length Độ dài của số cần hiển thị, phạm vi: 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ễ khi cấp điện
	{
		for (j = 0; j < 1000; j++);
	}
	
	OLED_I2C_Init();	//Khởi tạo cổng
	
	OLED_WriteCommand(0xAE);	//Tắt hiển thị
	
	OLED_WriteCommand(0xD5);	//Đặt tỷ lệ chia tần số hiển thị/tần số dao động
	OLED_WriteCommand(0x80);
	
	OLED_WriteCommand(0xA8);	//Đặt tỷ lệ ghép nối
	OLED_WriteCommand(0x3F);
	
	OLED_WriteCommand(0xD3);	//Đặt độ lệch hiển thị
	OLED_WriteCommand(0x00);
	
	OLED_WriteCommand(0x40);	//Đặt hàng bắt đầu hiển thị
	
	OLED_WriteCommand(0xA1);	//Đặt hướng trái phải, 0xA1 bình thường 0xA0 đảo ngược trái phải
	
	OLED_WriteCommand(0xC8);	//Đặt hướng trên dưới, 0xC8 bình thường 0xC0 đảo ngược trên dưới

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

	OLED_WriteCommand(0xD9);	//Đặt chu kỳ tiền sạc
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB);	//Đặt mức hủy chọn VCOMH
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4);	//Đặt toàn bộ hiển thị bật/tắt

	OLED_WriteCommand(0xA6);	//Đặt hiển thị bình thường/đảo ngược

	OLED_WriteCommand(0x8D);	//Đặt bơm sạc
	OLED_WriteCommand(0x14);

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

Đọc thêm

1 Lượt thích