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:
- Gửi lệnh đo lường
- Đọ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:
- 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).
- Đọ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.
- 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.
- 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
- Khuyến nghị VPS/máy chủ đám mây giá rẻ và hiệu suất cao: https://blog.vpszj.cn/archives/41.html
- Xây dựng máy chủ xuyên tường nội bộ bằng NPS, có bảng điều khiển Web: https://blog.vpszj.cn/archives/748.html
- Hướng dẫn xây dựng trang web trên Linux, hướng dẫn xây dựng trang web: https://blog.vpszj.cn/archives/1094.html
- Hướng dẫn xây dựng máy chủ Minecraft: https://blog.vpszj.cn/archives/tag/minecraft
- Thực hiện chức năng đo khoảng cách siêu âm dựa trên STM32 và mô-đun HC-SR04: https://blog.zeruns.com/archives/680.html
- Xây dựng môi trường phát triển ESP8266 và trình diễn dự án: https://blog.zeruns.com/archives/526.html






