STM32 MCU reads AM2320 temperature and humidity sensor data via software I²C and displays it on a 0.96-inch OLED screen.
My board is STM32F103C8T6 and the code is written with the ST Standard Peripheral Library.
STM32 uses hardware I²C to read SHTC3 sensor: https://blog.zeruns.com/archives/692.html
STM32 reads AHT10 sensor data: https://blog.zeruns.com/archives/693.html
Electronics / MCU QQ group: 2169025065
Result Pictures

I²C Protocol Brief
The Inter-Integrated Circuit (I²C) protocol was developed by Philips. It needs few pins, is easy to implement in hardware, highly extensible, and does not require external transceivers like USART or CAN (level-shifters). It is now widely used for on-board communication among ICs.
I²C has only one data line SDA (Serial Data Line), so data must be sent bit by bit—serial, half-duplex.
Half-duplex: bidirectional but not simultaneous; direction must be switched. Only one data line is needed.
We divide I²C into the physical layer (hardware) and the protocol layer (software rules).
I²C Physical Layer
Typical I²C connection
- Multi-device bus: several devices share the same signal lines. One I²C bus can connect multiple masters and slaves.
- Two wires only: bidirectional SDA and serial clock SCL.
- Pull-up resistors to VCC. When idle, devices output high-impedance, so the resistors pull the bus high.
MCU GPIO must be configured as open-drain, otherwise a short-circuit may occur.
More STM32 I²C info: https://url.zeruns.com/JC0Ah — not detailed here.
AM2320 Temperature & Humidity Sensor
Introduction
AM2320 is a factory-calibrated digital sensor with excellent long-term stability. It combines a capacitive humidity element, a high-accuracy temperature element and a high-performance MCU. Features outstanding quality, ultra-fast response, strong anti-interference ability and high cost-performance. Supports both single-bus and standard I²C. Tiny size, ultra-low power, 20 m+ signal distance. Both interfaces output temperature-compensated humidity, temperature and CRC checksum; no extra calculation or compensation needed. 4-pin package, easy wiring.
Datasheet download: https://url.zeruns.com/74o6F
Key specs from the datasheet:
- Temperature range: –40 °C ~ 80 °C
- Temperature accuracy: ±0.5 °C
- Humidity range: 0 % ~ 99.9 %
- Humidity accuracy: ±3 %
- Supply: 3.1 V ~ 5.5 V
- Interface: I²C or single-bus
- Clock ≤ 100 kHz
Device Address & R/W Command
The 7-bit address plus R/W bit form one byte.
To write: after START send 0xB8 (addr+W).
To read: after START send 0xB9 (addr+R).
In short: 0xB8 = write, 0xB9 = read.
Reading Temperature & Humidity
One read cycle has three steps:
- Wake sensor
- Send read command
- Read returned data
Summary:
- Wake: START + send
0xB8+ wait (>800 µs) + STOP - Command: START +
0xB8(SLA) +0x03(function) +0x00(start addr) +0x04(length) + STOP - Receive: send read command (
0xB9), read 8 bytes continuously:
Length + Humidity_H + Humidity_L + Temp_H + Temp_L + CRC_L + CRC_H - Convert the data.
Data Conversion
From the datasheet:
Example:
Humidity raw = 0x01F4 = 500 → 500 / 10 = 50.0 %
Temperature raw = 0x00FA = 250 → 250 / 10 = 25.0 °C
Required Parts
- STM32 minimum system board: https://s.click.taobao.com/M2LgRPu
- AM2320: https://s.click.taobao.com/t7jgRPu
- OLED module: https://s.click.taobao.com/w67u9Pu
- Jumper wires: https://s.click.taobao.com/anyfRPu
- Breadboard: https://s.click.taobao.com/7Vlu9Pu
- ST-LINK V2: https://s.click.taobao.com/dtbt9Pu
- Resistors: https://s.click.taobao.com/vbQu9Pu
- Logic analyzer: https://s.click.taobao.com/y2br9Pu
Code
Only main.c, AM2320.c and OLED.c are shown below; download the full project at the link.
Complete project: https://url.zeruns.com/AM2320
Wiring: AM2320 and OLED SCL → PB12, SDA → PB13.
If AM2320 uses other pins, add 5 kΩ pull-ups.
Using VS Code instead of Keil for STM32 & 51 development: https://blog.zeruns.com/archives/690.html
main.c```c
#include “stm32f10x.h” // Device header
#include “Delay.h”
#include “OLED.h”
#include “AM2320.h”
#include “IWDG.h”
int main(void)
{
IWDG_Configuration(); //Initialize watchdog
AM2320_I2C_Init();
OLED_Init();
OLED_ShowString(1, 1, "T:");
OLED_ShowString(2, 1, "H:");
uint16_t i = 0;
uint16_t err_count = 0;
while (1)
{
OLED_ShowNum(4, 1, i, 5);
float Temp, Hum; //Declare variables to store temperature and humidity data
if (ReadAM2320(&Hum, &Temp)) //Read temperature and humidity data
{
if (Temp >= 0)
{
char String[10];
sprintf(String, "+%.2fC", Temp); //Format string output to string variable
OLED_ShowString(1, 3, String); //Display temperature
sprintf(String, " %.2f%%", Hum); //Format string output to string variable
OLED_ShowString(2, 3, String); //Display humidity
}
else
{
char String[10];
sprintf(String, "-%.2fC", Temp); //Format string output to string variable
OLED_ShowString(1, 3, String); //Display temperature
sprintf(String, " %.2f%%", Hum); //Format string output to string variable
OLED_ShowString(2, 3, String); //Display humidity
}
}
else
{
err_count++;
OLED_ShowNum(3, 1, err_count, 5); //Display error count
}
Delay_ms(100);
i++;
if (i >= 99999)
i = 0;
if (err_count >= 99999)
err_count = 0;
IWDG_FeedDog(); //Feed the dog (watchdog, auto-reset if not fed within 1 second)
}
// blog.zeruns.com
}
### AM2320.c
```c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
/*
Author blog: https://blog.zeruns.com
WeChat public account: zeruns-gzh
Bilibili homepage: https://space.bilibili.com/8320520
*/
/*AM2320 address*/
#define AM2320_ADDRESS 0xB8
/*Pin configuration*/
#define AM2320_SCL GPIO_Pin_12
#define AM2320_SDA GPIO_Pin_13
#define AM2320_W_SCL(x) GPIO_WriteBit(GPIOB, AM2320_SCL, (BitAction)(x))
#define AM2320_W_SDA(x) GPIO_WriteBit(GPIOB, AM2320_SDA, (BitAction)(x))
#define AM2320_R_SDA() GPIO_ReadInputDataBit(GPIOB, AM2320_SDA)
#define AM2320_R_SCL() GPIO_ReadInputDataBit(GPIOB, AM2320_SCL)
/*When STM32 GPIO is configured as open-drain output mode, it can still read
the input data register of the GPIO to get the external input level of the pin, meaning it also has the function of floating input mode*/
/**
* @brief CRC checksum calculation
* @param *ptr Byte data to be calculated (stored as array variable)
* @param len Number of bytes to be calculated (array length)
* @retval CRC checksum
*/
unsigned short CRC16(unsigned char *ptr, unsigned char len)
{
unsigned short crc = 0xFFFF;
unsigned char i;
while (len--)
{
crc ^= *ptr++;
for (i = 0; i < 8; i++)
{
if (crc & 0x01)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/**
* @brief I2C start
* @param None
* @retval None
*/
void AM2320_I2C_Start(void)
{
AM2320_W_SDA(1);
Delay_us(2); //Delay 2 microseconds
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SDA(0);
Delay_us(3);
AM2320_W_SCL(0);
Delay_us(5);
}
/**
* @brief I2C stop
* @param None
* @retval None
*/
void AM2320_I2C_Stop(void)
{
AM2320_W_SDA(0);
Delay_us(3);
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SDA(1);
Delay_us(4);
}
/**
* @brief I2C send one byte
* @param Byte One byte to be sent
* @retval None
*/
void AM2320_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
AM2320_W_SDA((Byte << i) & 0x80);
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SCL(0);
Delay_us(5);
}
AM2320_W_SDA(1); //Release SDA bus
}
/**
* @brief Wait for acknowledge signal
* @param None
* @retval 1-NACK, 0-ACK
*/
uint8_t WaitAck(void)
{
uint8_t ret;
AM2320_W_SCL(1);
Delay_us(4);
if (AM2320_R_SDA())
{
ret = 1;
}
else
{
ret = 0;
}
AM2320_W_SCL(0);
Delay_us(5);
return ret;
}
/**
* @brief I2C read one byte
* @param NACK 1-NACK, 0-ACK
* @retval Byte data read
*/
uint8_t AM2320_I2C_ReadByte(uint8_t NACK)
{
uint8_t i, Byte = 0;
AM2320_W_SDA(1); //Release SDA bus
for (i = 0; i < 8; i++)
{
AM2320_W_SCL(1);
Delay_us(4);
Byte = Byte | (AM2320_R_SDA() << (7 - i));
AM2320_W_SCL(0);
Delay_us(5);
}
AM2320_W_SDA(NACK); //Send ACK/NACK signal
AM2320_W_SCL(1);
Delay_us(4);
AM2320_W_SCL(0);
Delay_us(5);
AM2320_W_SDA(1); //Release SDA bus
return Byte;
}
/*Wake up sensor*/
void AM2320_Wake(void)
{
AM2320_I2C_Start();
AM2320_I2C_SendByte(AM2320_ADDRESS);
WaitAck();
Delay_us(1000); //Delay 1000 microseconds
AM2320_I2C_Stop();
}
/*Pin initialization*/
void AM2320_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //Enable GPIOB clock
GPIO_InitTypeDef GPIO_InitStructure; //Define structure to configure GPIO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //Open-drain output mode
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = AM2320_SCL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = AM2320_SDA;
GPIO_Init(GPIOB, &GPIO_InitStructure);
AM2320_W_SCL(1);
AM2320_W_SDA(1);
AM2320_Wake(); //Wake up sensor
}
/**
* @brief Read AM2320 data
* @param *Hum Humidity
* @param *Temp Temperature
* @retval 1 - Read success; 0 - Read failure
*/
uint8_t ReadAM2320(float *Hum, float *Temp)
{
uint8_t Data[8];
AM2320_I2C_Start(); //Send start signal
AM2320_I2C_SendByte(AM2320_ADDRESS);
if (WaitAck()) //Check ACK signal
{
AM2320_I2C_Stop(); //Send stop signal
Delay_us(50);
//Try reading again
AM2320_I2C_Start(); //Send start signal
AM2320_I2C_SendByte(AM2320_ADDRESS);
if (WaitAck()) //Check ACK signal
{
Delay_us(20);
AM2320_I2C_Stop(); //Send stop signal
return 0;
}
else
{
Delay_us(20);//AM2320 will inexplicably pull SCL low for a while causing data transmission errors, so delay 20 microseconds to wait for AM2320 to release SCL before continuing
AM2320_I2C_SendByte(0x03); //Send function code
WaitAck(); //Wait for ACK signal
AM2320_I2C_SendByte(0x00); //Send register start address to read
WaitAck(); //Wait for ACK signal
AM2320_I2C_SendByte(0x04); //Send register length to read
WaitAck(); //Wait for ACK signal
Delay_us(20);//AM2320 will inexplicably pull SCL low for a while causing stop signal transmission failure, so delay 20 microseconds to wait for AM2320 to release SCL before continuing
AM2320_I2C_Stop(); //Send stop signal
}
}
else
{
Delay_us(20);//AM2320 will inexplicably pull SCL low for a while causing data transmission errors, so delay 20 microseconds to wait for AM2320 to release SCL before continuing
AM2320_I2C_SendByte(0x03); //Send function code
WaitAck(); //Wait for ACK signal
AM2320_I2C_SendByte(0x00); //Send register start address to read
WaitAck(); //Wait for ACK signal
AM2320_I2C_SendByte(0x04); //Send register length to read
WaitAck(); //Wait for ACK signal
Delay_us(20);//AM2320 will inexplicably pull SCL low for a while causing stop signal transmission failure, so delay 20 microseconds to wait for AM2320 to release SCL before continuing
AM2320_I2C_Stop(); //Send stop signal
}
Delay_ms(2); //Delay 2 milliseconds
AM2320_I2C_Start();
AM2320_I2C_SendByte(AM2320_ADDRESS | 0x01); //Send read command
WaitAck();
Delay_us(35);
uint8_t i;
for (i = 0; i < 8; i++)
{
if (i != 7)
{
Data[i] = AM2320_I2C_ReadByte(0);
}
else
{
Data[i] = AM2320_I2C_ReadByte(1); //Send NACK when reading last byte
}
}
AM2320_I2C_Stop();
if (CRC16(Data, 6) == (Data[6] | (Data[7] << 8))) //Verify data
{
*Hum = ((((uint16_t)Data[2]) << 8) | Data[3]) / 10.0; //Calculate humidity data
if (Data[4] >> 7) //Check if temperature value is negative
{
*Temp = ((((uint16_t)(Data[4] && 0x7F) << 8)) | Data[5]) / -10.0; //Calculate negative temperature
}
else
{
*Temp = ((((uint16_t)Data[4]) << 8) | Data[5]) / 10.0; //Calculate positive temperature
}
return 1;
}
return 0;
}
```### OLED.c
```c
#include "stm32f10x.h"
#include "OLED_Font.h"
/*Pin configuration*/
#define OLED_SCL GPIO_Pin_12
#define OLED_SDA GPIO_Pin_13
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, OLED_SCL, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, OLED_SDA, (BitAction)(x))
/*Pin initialization*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = OLED_SCL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = OLED_SDA;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C start
* @param None
* @retval None
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C stop
* @param None
* @retval None
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C send one byte
* @param Byte byte to send
* @retval None
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SDA(1); //Release SDA bus
OLED_W_SCL(1); //Extra clock, ignore ACK
OLED_W_SCL(0);
}
/**
* @brief OLED write command
* @param Command command to write
* @retval None
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //Slave address
OLED_I2C_SendByte(0x00); //Write command
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED write data
* @param Data data to write
* @retval None
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //Slave address
OLED_I2C_SendByte(0x40); //Write data
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED set cursor position
* @param Y coordinate from top-left, range: 0~7
* @param X coordinate from top-left, range: 0~127
* @retval None
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //Set Y position
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //Set X low nibble
OLED_WriteCommand(0x00 | (X & 0x0F)); //Set X high nibble
}
/**
* @brief OLED clear screen
* @param None
* @retval None
*/
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 OLED partial clear
* @param Line row position, range: 1~4
* @param start column start position, range: 1~16
* @param end column end position, range: 1~16
* @retval None
*/
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); //Set cursor to upper half
for (i = 0; i < 8; i++)
{
OLED_WriteData(0x00); //Clear upper half
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //Set cursor to lower half
for (i = 0; i < 8; i++)
{
OLED_WriteData(0x00); //Clear lower half
}
}
}
/**
* @brief OLED show one character
* @param Line row position, range: 1~4
* @param Column column position, range: 1~16
* @param Char character to display, range: ASCII visible characters
* @retval None
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //Set cursor to upper half
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //Display upper half
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //Set cursor to lower half
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //Display lower half
}
}
/**
* @brief OLED show string
* @param Line starting row, range: 1~4
* @param Column starting column, range: 1~16
* @param String string to display, range: ASCII visible characters
* @retval None
*/
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 OLED power function
* @retval returns X to the power of Y
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED display number (decimal, positive)
* @param Line starting row, range: 1~4
* @param Column starting column, range: 1~16
* @param Number number to display, range: 0~4294967295
* @param Length length of number to display, range: 1~10
* @retval None
*/
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 OLED display number (decimal, signed)
* @param Line starting row, range: 1~4
* @param Column starting column, range: 1~16
* @param Number number to display, range: -2147483648~2147483647
* @param Length length of number to display, range: 1~10
* @retval None
*/
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 OLED display number (hexadecimal, positive)
* @param Line starting row, range: 1~4
* @param Column starting column, range: 1~16
* @param Number number to display, range: 0~0xFFFFFFFF
* @param Length length of number to display, range: 1~8
* @retval None
*/
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 OLED display number (binary, positive)
* @param Line starting row, range: 1~4
* @param Column starting column, range: 1~16
* @param Number number to display, range: 0~1111 1111 1111 1111
* @param Length length of number to display, range: 1~16
* @retval None
*/
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 OLED initialization
* @param None
* @retval None
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //Power-on delay
{
for (j = 0; j < 1000; j++)
;
}
OLED_I2C_Init(); //Port initialization
OLED_WriteCommand(0xAE); //Display off
OLED_WriteCommand(0xD5); //Set display clock divide ratio/oscillator frequency
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //Set multiplex ratio
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //Set display offset
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //Set display start line
OLED_WriteCommand(0xA1); //Set segment re-map, 0xA1 normal 0xA0 reversed
OLED_WriteCommand(0xC8); //Set COM output scan direction, 0xC8 normal 0xC0 reversed
OLED_WriteCommand(0xDA); //Set COM pins hardware configuration
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //Set contrast control
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //Set pre-charge period
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //Set VCOMH deselect level
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //Set entire display on/off
OLED_WriteCommand(0xA6); //Set normal/inverse display
OLED_WriteCommand(0x8D); //Set charge pump
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //Display on
OLED_Clear(); // OLED clear screen
}
Timing Diagram
Actual AM2320 timing diagram for sending commands and reading data.
Recommended Reading
- High cost-effective and cheap VPS/Cloud server recommendations: https://blog.vpszj.cn/archives/41.html
- Build an intranet penetration server with NPS, with Web panel: https://blog.vpszj.cn/archives/748.html
- Linux website building tutorial: https://blog.vpszj.cn/archives/1094.html
- Minecraft server setup tutorial: https://blog.zeruns.com/tag/mc/
- Ultrasonic distance measurement with STM32 and HC-SR04 module: https://blog.zeruns.com/archives/680.html
- ESP8266 development environment setup and project demo: https://blog.zeruns.com/archives/526.html













