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:
- Gửi lệnh đo
- Chờ đo xong
- Đọc dữ liệu
Tóm tắt:
- Gửi lệnh đo: gửi 0x70, tiếp theo 0xAC, rồi tham số 0x33, 0x00.
- Chờ đo xong: datasheet ghi 75 ms, chờ lâu hơn cũng được.
- Đọ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.
- 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
- Giới thiệu VPS/đám mây giá rẻ, hiệu năng cao: https://blog.vpszj.cn/archives/41.html
- Dùng NPS dựng máy chủ穿透 mạng nội bộ, có giao diện Web: https://blog.vpszj.cn/archives/748.html
- Hướng dẫn dựng website trên Linux: https://blog.vpszj.cn/archives/1094.html
- Hướng dẫn máy chủ Minecraft: https://blog.vpszj.cn/archives/tag/minecraft
- Đo khoảng cách siêu âm với STM32 và HC-SR04: https://blog.zeruns.com/archives/680.html
- Lập trình ESP8266 – dựng môi trường và demo dự án: https://blog.zeruns.com/archives/526.html








