基于I2C/SPI总线的温湿度采集与OLED显示
一、协议简述
1、I2C 协议
I2C「Inter-integrated Circuit」总线支持设备之间的短距离通信,用于处理器和一些外围设备之间的接口,它只需要两根信号线来完成信息交换。I2C最早是飞利浦在1982年开发设计。
I2C是同步传输信号,关于同步和异步这个经常在面试的时候会考到,同步和异步的核心的,我把消息发出去,我要关心你有没有收到,你收到了之后,我才会发下一条消息。异步就是,我发消息给你,我不管你收到消息没有,我都直接发下一条消息。所以SPI和I2C是同步通讯,UART是异步通讯。
I2C协议把传输的消息分为两种类型的帧:一个地址帧 「用于master指明消息发往哪个slave」 一个或多个数据帧 「 在SDA线上传输的数据帧,每一帧是8-bit的数据」。
数据在SCL处于低电平时放到SDA上,在SCL变为高电平后进行采样,也就是说在时钟上升沿的时候,数据是有效的。
I2C数据传输的时序图如下:
2、SPI协议
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器等芯片,还有数字信号处理器和数字信号解码器之间。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。SPI的四条通讯线是SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)。
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。
时序详解:
CPOL 时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平
CPHA 时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样
Mode 0 SCLK 空闲是为低电平,数据在上升沿有效
Mode 1 SCLK 空闲是为低电平,数据在下降沿有效
Mode 2 SCLK 空闲是为高电平,数据在下降沿有效
Mode 3 SCLK 空闲是为高电平,数据在上升沿有效
4种工作模式波形时序如下图:
二、AHT20采集温湿度传至上位机
1、 完整代码
链接:https://pan.baidu.com/s/1ThpSQpZpWdY3sN0-uzoEkw
提取码:zxcv
一些主要代码
main.c
#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
int main(void)
{
delay_init(); //?óê±oˉêy3?ê??ˉ
uart_init(115200); //′??ú3?ê??ˉ?a115200
IIC_Init();
while(1)
{
printf("温度湿度显示");
read_AHT20_once();
delay_ms(1500);
}
}
usart.c
#include "sys.h"
#include "usart.h"
//STM32F103o?D?°?ày3ì
//?aoˉêy°?±?ày3ì
/********** mcudev.taobao.com 3??· ********/
//
//è?1?ê1ó?ucos,?ò°üà¨????μ?í·???t?′?é.
#if SYSTEM_SUPPORT_UCOS
#include "includes.h" //ucos ê1ó?
#endif
//
//STM32?a·¢°?
//′??ú13?ê??ˉ
//
//
//?óè?ò???′ú??,?§3?printfoˉêy,??2?Dèòa????use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//±ê×??aDèòaμ??§3?oˉêy
struct __FILE
{
int handle;
};
FILE __stdout;
//?¨ò?_sys_exit()ò?±ü?aê1ó?°??÷?ú?£ê?
void _sys_exit(int x)
{
x = x;
}
//???¨ò?fputcoˉêy
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//?-?··¢?í,?±μ?·¢?ííê±?
USART1->DR = (u8) ch;
return ch;
}
#endif
/*ê1ó?microLibμ?·?·¨*/
/*
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}
return ch;
}
int GetKey (void) {
while (!(USART1->SR & USART_FLAG_RXNE));
return ((int)(USART1->DR & 0x1FF));
}
*/
#if EN_USART1_RX //è?1?ê1?üá??óê?
//′??ú1?D??·t??3ìDò
//×¢òa,?áè?USARTx->SR?ü±ü?a?a??????μ?′í?ó
u8 USART_RX_BUF[USART_REC_LEN]; //?óê??o3?,×?′óUSART_REC_LEN??×??ú.
//?óê?×′ì?
//bit15£? ?óê?íê3é±ê??
//bit14£? ?óê?μ?0x0d
//bit13~0£? ?óê?μ?μ?óDD§×??úêy??
u16 USART_RX_STA=0; //?óê?×′ì?±ê??
void uart_init(u32 bound){
//GPIO???úéè??
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //ê1?üUSART1£?GPIOAê±?ó
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //?′ó?í?íìê?3?
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//????ê?è?
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Usart1 NVIC ????
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//?à??ó??è??3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //×óó??è??3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQí¨μàê1?ü
NVIC_Init(&NVIC_InitStructure); //?ù?Y???¨μ?2?êy3?ê??ˉVIC??′??÷
//USART 3?ê??ˉéè??
USART_InitStructure.USART_BaudRate = bound;//ò?°?éè???a9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//×?3¤?a8??êy?Y??ê?
USART_InitStructure.USART_StopBits = USART_StopBits_1;//ò???í£?1??
USART_InitStructure.USART_Parity = USART_Parity_No;//?T????D£?é??
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//?Tó2?têy?Yá÷????
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //ê?·¢?£ê?
USART_Init(USART1, &USART_InitStructure); //3?ê??ˉ′??ú
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//?a???D??
USART_Cmd(USART1, ENABLE); //ê1?ü′??ú
}
void USART1_IRQHandler(void) //′??ú1?D??·t??3ìDò
{
u8 Res;
#ifdef OS_TICKS_PER_SEC //è?1?ê±?ó?ú??êy?¨ò?á?,?μ?÷òaê1ó?ucosIIá?.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //?óê??D??(?óê?μ?μ?êy?Y±?D?ê?0x0d 0x0a?á?2)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //?áè??óê?μ?μ?êy?Y
if((USART_RX_STA&0x8000)==0)//?óê??′íê3é
{
if(USART_RX_STA&0x4000)//?óê?μ?á?0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//?óê?′í?ó,??D??aê?
else USART_RX_STA|=0x8000; //?óê?íê3éá?
}
else //?1??ê?μ?0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//?óê?êy?Y′í?ó,??D??aê??óê?
}
}
}
}
#ifdef OS_TICKS_PER_SEC //è?1?ê±?ó?ú??êy?¨ò?á?,?μ?÷òaê1ó?ucosIIá?.
OSIntExit();
#endif
}
#endif
2、编译烧录
3、运行结果
三、基于SPI的OLED显示
资料包下载0.96inch SPI OLED Module - LCD wiki
#1 、显示学号姓名
(1)完整代码
链接:https://pan.baidu.com/s/1cUwwWg2jEvk_q-TPmqzDmA
提取码:zxcv
(2) 修改代码
test.c—>TEST_MainPage—>GUI_ShowString,GUI_ShowCHinese
(3)汉字取模
软件链接:https://pan.baidu.com/s/1sQ_JEmo1F2EYmMw3DiFheQ
提取码:zxcv
设置字模输出选项
将生成的子模添加到oledfont.h中16x16里
然后去生成hex文件
(4)编译烧录
(5)显示结果
2、显示温湿度
(1)主要代码
#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{
delay_init(); //延时函数初始化
uart_init(115200);
IIC_Init();
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init(); //初始化OLED
OLED_Clear(0);
while(1)
{
read_AHT20_once();
OLED_Clear(0);
delay_ms(200);
}
}
读取温湿度并显示的函数代码
void read_AHT20(void)
{
uint8_t i;
for(i=0; i<6; i++)
{
readByte[i]=0;
}
//-------------
I2C_Start();
I2C_WriteByte(0x71);
ack_status = Receive_ACK();
readByte[0]= I2C_ReadByte();
Send_ACK();
readByte[1]= I2C_ReadByte();
Send_ACK();
readByte[2]= I2C_ReadByte();
Send_ACK();
readByte[3]= I2C_ReadByte();
Send_ACK();
readByte[4]= I2C_ReadByte();
Send_ACK();
readByte[5]= I2C_ReadByte();
SendNot_Ack();
//Send_ACK();
I2C_Stop();
//--------------
if( (readByte[0] & 0x68) == 0x08 )
{
H1 = readByte[1];
H1 = (H1<<8) | readByte[2];
H1 = (H1<<8) | readByte[3];
H1 = H1>>4;
H1 = (H1*1000)/1024/1024;
T1 = readByte[3];
T1 = T1 & 0x0000000F;
T1 = (T1<<8) | readByte[4];
T1 = (T1<<8) | readByte[5];
T1 = (T1*2000)/1024/1024 - 500;
AHT20_OutData[0] = (H1>>8) & 0x000000FF;
AHT20_OutData[1] = H1 & 0x000000FF;
AHT20_OutData[2] = (T1>>8) & 0x000000FF;
AHT20_OutData[3] = T1 & 0x000000FF;
}
else
{
AHT20_OutData[0] = 0xFF;
AHT20_OutData[1] = 0xFF;
AHT20_OutData[2] = 0xFF;
AHT20_OutData[3] = 0xFF;
printf("lyy");
}
printf("\r\n");
printf("温度:%d%d.%d",T1/100,(T1/10)%10,T1%10);
printf("湿度:%d%d.%d",H1/100,(H1/10)%10,H1%10);
printf("\r\n");
t=T1/10;
t1=T1%10;
a=(float)(t+t1*0.1);
h=H1/10;
h1=H1%10;
b=(float)(h+h1*0.1);
sprintf(strTemp,"%.1f",a); //调用Sprintf函数把DHT11的温度数据格式化到字符串数组变量strTemp中
sprintf(strHumi,"%.1f",b); //调用Sprintf函数把DHT11的湿度数据格式化到字符串数组变量strHumi中
//printf(strTemp);
//printf("/r/n");
GUI_ShowCHinese(16,00,16,"温湿度显示",1);
GUI_ShowCHinese(16,20,16,"温度",1);
GUI_ShowString(53,20,strTemp,16,1);
GUI_ShowCHinese(16,38,16,"湿度",1);
GUI_ShowString(53,38,strHumi,16,1);
delay_ms(2000);
}
2、结果
3、左右滑动显示(上方显示学号代码修改即可)
左右水平滑动:
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x26,OLED_CMD); //水平向左或者右滚动 26/27
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
OLED_WR_Byte(0x07,OLED_CMD); //终止页 7
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
垂直和水平滚动:
OLED_WR_Byte(0x2e,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x29,OLED_CMD); //水平垂直和水平滚动左右 29/2a
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
OLED_WR_Byte(0x07,OLED_CMD); //终止页 1
OLED_WR_Byte(0x01,OLED_CMD); //垂直滚动偏移量
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
(1)修改代码
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{
delay_init(); //延时函数初始化
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init(); //初始化OLED
OLED_Clear(0); //清屏(全黑)
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x26,OLED_CMD); //水平向左或者右滚动 26/27
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
OLED_WR_Byte(0x07,OLED_CMD); //终止页 7
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
TEST_MainPage();
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
while(1)
{
}
}
test.c
加入字模,同上
2、编译烧录
(步骤同上一样)
3、显示结果