在 0.96 寸 OLED上显示汉字及采集显示温湿度数据(利用SPI协议)


前言

本文主要讲述了如何在 0.96 寸 OLED上显示汉字及采集显示温湿度数据


一、SPI协议是什么?

SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。

SPI总线是一种4线总线,因其硬件功能很强,所以与SPI有关的软件就相当简单,使中央处理器(Central Processing Unit,CPU)有更多的时间处理其他事务。正是因为这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如AT91RM9200。SPI是一种高速、高效率的串行接口技术。通常由一个主模块和一个或多个从模块组成,主模块选择一个从模块进行同步通信,从而完成数据的交换。SPI是一个环形结构,通信时需要至少4根线(事实上在单向传输时3根线也可以) [1] 。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。
接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。
时钟信号线SCLK只能由主设备控制,从设备不能控制。同样,在一个基于SPI的设备中,至少有一个主设备。这样的传输方式有一个优点,在数据位的传输过程中可以暂停,也就是时钟的周期可以为不等宽,因为时钟线由主设备控制,当没有时钟跳变时,从设备不采集或传送数据。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。芯片集成的SPI串行同步时钟极性和相位可以通过寄存器配置,IO模拟的SPI串行同步时钟需要根据从设备支持的时钟极性和相位来通讯。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
SPI的片选可以扩充选择16个外设,这时PCS输出=NPCS,说NPCS03接4-16译码器,这个译码器是需要外接4-16译码器,译码器的输入为NPCS03,输出用于16个外设的选择。

二、实验步骤

1.实验准备

野火 stm32 指南者开发板
ST-LINK V2 STM8/STM32仿真器编程器
0.96寸OLED显示屏模块0.91 1.3寸液晶屏供原理图12864屏 IIC/SPI
Keil5 MDK
野火串口调试助手
具体连接请参考博客:https://blog.csdn.net/ssj925319/article/details/111588662?spm=1001.2014.3001.5502

2.代码实现

首先下载源码:
链接:https://pan.baidu.com/s/1HS33ftk3Pb7nWJRhBTLqUw
提取码:57x8
解压缩后,在 1-Demo 下选择相应的项目,
这里我选择的是 Demo_STM32 下的 0.96inch_OLED_Demo_STM32F103ZET6_Hardware_4-wire_SPI ,
如图所示:
在这里插入图片描述
之后双击打开 PROJECT 下的工程 OLED.uvprojx 即可,如图所示:
在这里插入图片描述

接下来我们需要移植代码,首先移植温度数据代码中的bsp_i2c.h文件
代码如下:

#ifndef __BSP_I2C_H
#define __BSP_I2C_H

#include "sys.h"
#include "delay.h"
#include "usart.h"

 
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
//CRL = 0000 1111 1111 1111 1111 1111 1111 1111
//8<<28 = 1000 1111 1111 1111 1111 1111 1111 1111
//CRL = 1000 1111 1111 1111 1111 1111 1111 1111 = 0x8fffffff 表示 SDA 输入
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//CRL = 0x3fffffff 表示 SDA 输出


#define IIC_SCL    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //SDA 数据读取 7 管脚


void IIC_Init(void);
void  read_AHT20_once(void);
void  reset_AHT20(void);
void  init_AHT20(void);	
void  startMeasure_AHT20(void);
void  read_AHT20(void);
uint8_t  Receive_ACK(void);
void  Send_ACK(void);
void  SendNot_Ack(void);
void I2C_WriteByte(uint8_t  input);
uint8_t I2C_ReadByte(void);	
void  set_AHT20sendOutData(void);
void  I2C_Start(void);
void  I2C_Stop(void);

#endif

之后我们需要移植bsp_i2c.c文件(此处最好更改名称为 AHT20_sys.h,不然会重名)
代码如下:

#include "bsp_i2c.h"
#include "delay.h"
#include "string.h"

uint8_t   ack_status=0;
uint8_t   readByte[6];

uint32_t  H1=0;  //Humility
uint32_t  T1=0;  //Temperature

uint8_t  AHT20_OutData[4];

/****************
 *初始化 I2C 函数
 ****************/
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	//启用高速 APB (APB2) 外围时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );	
	
	//GPIO 定义
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//初始化 SCL(Pin6)高电平
	IIC_SCL=1;
	//初始化 SDA(Pin7)高电平
	IIC_SDA=1;
}

/*********************
 *AHT20 数据操作总函数
 *********************/
void read_AHT20_once(void)
{
	printf("读取数据中");
	
	//延时 10 微妙
	delay_ms(10);
	
  //传输数据前进行启动传感器和软复位
	reset_AHT20();
	delay_ms(10);
  
	//查看使能位
	init_AHT20();
	delay_ms(10);
	
  //触发测量
	startMeasure_AHT20();
	delay_ms(80);
  
	//读数据
	read_AHT20();
	delay_ms(5);
}


void reset_AHT20(void)
{
	//数据传输开始信号
	I2C_Start();
	
	//发送数据
	I2C_WriteByte(0x70);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
	{
		printf(">");
	}
	else
		printf("×");
	
	//发送软复位命令(重启传感器系统)
	I2C_WriteByte(0xBA);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//停止 I2C 协议
	I2C_Stop();
}

//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xE1 —> 看状态字的校准使能位Bit[3]是否为 1
//0x08 0x00 —> 0xBE 命令的两个参数,详见 AHT20 参考手册
void init_AHT20(void)
{
	//传输开始
	I2C_Start();

	//写入 0x70 数据
	I2C_WriteByte(0x70);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//写入 0xE1 数据
	I2C_WriteByte(0xE1);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//写入 0x08 数据
	I2C_WriteByte(0x08);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else 
		printf("×");
	
	//写入 0x00 数据
	I2C_WriteByte(0x00);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	//停止 I2C 协议
	I2C_Stop();
}

//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xAC —> 触发测量
//0x33 0x00 —> 0xAC 命令的两个参数,详见 AHT20 参考手册
void startMeasure_AHT20(void)
{
	//启动 I2C 协议
	I2C_Start();
	
	I2C_WriteByte(0x70);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0xAC);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0x33);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0x00);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_Stop();
}


void read_AHT20(void)
{
	uint8_t i;

	//初始化 readByte 数组
	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}

	I2C_Start();

	//通过发送 0x71 可以获取一个字节的状态字
	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();
	
	//接收 6 个 8 bit的数据
	readByte[0]= I2C_ReadByte();
	//发送 ACK 信号
	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();
	//发送 NACK 信号
	SendNot_Ack();

	I2C_Stop();

	//温湿度的二进制数据处理
	//0x68 = 0110 1000
  //0x08 = 0000 1000	
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		//H1 左移 8 位并与 readByte[2] 相或 
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		//H1 右移 4 位
		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("꧰üá?");

	}
	
	printf("完成!\n");
	printf("----温度:%d%d.%d °C\n",T1/100,(T1/10)%10,T1%10);
	printf("----湿度:%d%d.%d %%",H1/100,(H1/10)%10,H1%10);
	printf("\n\n");
}

//接收 ACK 信号
uint8_t Receive_ACK(void)
{
	uint8_t result=0;
	uint8_t cnt=0;

	//置 SCL 低电平
	IIC_SCL = 0;
	//设置 SDA 为读取数据模式
	SDA_IN();
	delay_us(4);

	//置 SCL 高电平
	IIC_SCL = 1;
	delay_us(4);

	//等待从机发送 ACK 信号,等待时间为 100 个循环
	while(READ_SDA && (cnt<100))
	{
		cnt++;
	}

	IIC_SCL = 0;
	delay_us(4);

	//如果在等待时间内,则结果为 1
	if(cnt<100)
	{
		result=1;
	}
	
	return result;
}

//发送 ACK 信号
void Send_ACK(void)
{
	//设置 SDA 为写数据模式
	SDA_OUT();
	IIC_SCL = 0;
	delay_us(4);

	//置 SDA 为低电平
	IIC_SDA = 0;
	delay_us(4);

	IIC_SCL = 1;
	delay_us(4);
	IIC_SCL = 0;
	delay_us(4);

	SDA_IN();
}

//发送 NACK 信号
void SendNot_Ack(void)
{
	//设置 SDA 为写数据模式
	SDA_OUT();
	
	IIC_SCL = 0;
	delay_us(4);

	IIC_SDA = 1;
	delay_us(4);

	IIC_SCL = 1;
	delay_us(4);

	IIC_SCL = 0;
	delay_us(4);

	IIC_SDA = 0;
	delay_us(4);
}

//发送一个字节数据
void I2C_WriteByte(uint8_t  input)
{
	uint8_t  i;
	//设置 SDA 为写数据模式
	SDA_OUT();
	
	//循环左移发送 8 bit数据
	for(i=0; i<8; i++)
	{
		IIC_SCL = 0;
		delay_ms(5);

		if(input & 0x80)
		{
			IIC_SDA = 1;
		}
		else
		{
			IIC_SDA = 0;
		}

		IIC_SCL = 1;
		delay_ms(5);

		input = (input<<1);
	}

	IIC_SCL = 0;
	delay_us(4);

	SDA_IN();
	delay_us(4);
}	

//循环检测 SDA 的电平状态并存储起来
uint8_t I2C_ReadByte(void)
{
	uint8_t  resultByte=0;
	uint8_t  i=0, a=0;

	IIC_SCL = 0;
	SDA_IN();
	delay_ms(4);

	//循环检测
	for(i=0; i<8; i++)
	{
		IIC_SCL = 1;
		delay_ms(3);

		a=0;
		if(READ_SDA)
		{
			a=1;
		}
		else
		{
			a=0;
		}

		resultByte = (resultByte << 1) | a;

		IIC_SCL = 0;
		delay_ms(3);
	}

	SDA_IN();
	delay_ms(10);

	return   resultByte;
}

//设置 I2C 协议开始
void I2C_Start(void)
{
	SDA_OUT();
	
	IIC_SCL = 1;
	delay_ms(4);

	//SDA 从 1 跳变为 0 的这个过程
	//表示起始信号
	IIC_SDA = 1;
	delay_ms(4);
	IIC_SDA = 0;
	delay_ms(4);

	//SCL 变为 0
	//表示 SDA 数据无效,此时 SDA 可以进行电平切换
	IIC_SCL = 0;
	delay_ms(4);
}

//设置 I2C 协议停止
void I2C_Stop(void)
{
	SDA_OUT();
	
	//SCL 高电平,SDA 高电平
	//停止时序
	IIC_SDA = 0;
	delay_ms(4);
	IIC_SCL = 1;
	delay_ms(4);

	//SDA 切换到高电平
	IIC_SDA = 1;
	delay_ms(4);
}

然后我们进行sys.h文件的移植
代码如下:

#ifndef __SYS_H
#define __SYS_H	

#include "stm32f10x.h"
 
#define SYSTEM_SUPPORT_UCOS		0
																	    
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)


void NVIC_Configuration(void);

#endif

之后我们还需要移植sys.c文件(此处最好更改名称为 AHT20_sys.c,不然会重名)
代码如下:

#include "sys.h"

void NVIC_Configuration(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}

最后我们经过添加修改相关的代码,得到了最终的代码:
代码链接:https://pan.baidu.com/s/1fWBFrqD7pWfxJk8GtWdVnA
提取码:tzrh

3.获取汉字字模

要想在 OLED 上显示英文、数字,可以直接输出显示,但要是想显示中文,就必须要对中文进行编码成点阵,因此我们需要安装以下的软件
字模软件下载链接:https://pan.baidu.com/s/1GRe2X3p2ETJJEFwXsnV1sw
提取码:fn8i
之后我们打开压缩包,点击.exe程序,如图所示:
在这里插入图片描述
然后我们在下面输入我们想看到的文字,例如:张博伦欢迎来到重庆交通大学
如图所示:
在这里插入图片描述
之后 我们需要将正向的文字左旋 90 °,然后再山下翻转,这样,OLED 上显示的文字才是正向的,如图所示:
在这里插入图片描述
在这里插入图片描述
然后我们点击生成字模,如图所示:
在这里插入图片描述
之后我们复制这段生成的代码,添加到gui.c 下有个 oledfont.h 头文件,打开后,将 cfont16[] 数组内的内容修改成自己的中文文字点阵即可(注意格式)
如图所示:
在这里插入图片描述
之后我们再把温度湿度等以同样的方式加入进去
然后我们进行编译并生成.hex文件
在这里插入图片描述
之后我们打开烧录程序,把程序烧进去
这个时候我是遇到了一些问题,应答的问题,解决方法呢,我们可以参照下图:
在这里插入图片描述
之后呢我们进行烧录,效果如下图所示:

在这里插入图片描述

总结

总的来说呢,这个修改程序的过程比较繁琐,需要添加很多文件和修改很多语句,不过有了大佬的帮助,代码用起来如鱼得水,效果也是非常明显

版权声明:本文为CSDN博主「BokLoen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/k5465456132/article/details/121407844


前言

本文主要讲述了如何在 0.96 寸 OLED上显示汉字及采集显示温湿度数据


一、SPI协议是什么?

SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。

SPI总线是一种4线总线,因其硬件功能很强,所以与SPI有关的软件就相当简单,使中央处理器(Central Processing Unit,CPU)有更多的时间处理其他事务。正是因为这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如AT91RM9200。SPI是一种高速、高效率的串行接口技术。通常由一个主模块和一个或多个从模块组成,主模块选择一个从模块进行同步通信,从而完成数据的交换。SPI是一个环形结构,通信时需要至少4根线(事实上在单向传输时3根线也可以) [1] 。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。
接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。
时钟信号线SCLK只能由主设备控制,从设备不能控制。同样,在一个基于SPI的设备中,至少有一个主设备。这样的传输方式有一个优点,在数据位的传输过程中可以暂停,也就是时钟的周期可以为不等宽,因为时钟线由主设备控制,当没有时钟跳变时,从设备不采集或传送数据。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。芯片集成的SPI串行同步时钟极性和相位可以通过寄存器配置,IO模拟的SPI串行同步时钟需要根据从设备支持的时钟极性和相位来通讯。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
SPI的片选可以扩充选择16个外设,这时PCS输出=NPCS,说NPCS03接4-16译码器,这个译码器是需要外接4-16译码器,译码器的输入为NPCS03,输出用于16个外设的选择。

二、实验步骤

1.实验准备

野火 stm32 指南者开发板
ST-LINK V2 STM8/STM32仿真器编程器
0.96寸OLED显示屏模块0.91 1.3寸液晶屏供原理图12864屏 IIC/SPI
Keil5 MDK
野火串口调试助手
具体连接请参考博客:https://blog.csdn.net/ssj925319/article/details/111588662?spm=1001.2014.3001.5502

2.代码实现

首先下载源码:
链接:https://pan.baidu.com/s/1HS33ftk3Pb7nWJRhBTLqUw
提取码:57x8
解压缩后,在 1-Demo 下选择相应的项目,
这里我选择的是 Demo_STM32 下的 0.96inch_OLED_Demo_STM32F103ZET6_Hardware_4-wire_SPI ,
如图所示:
在这里插入图片描述
之后双击打开 PROJECT 下的工程 OLED.uvprojx 即可,如图所示:
在这里插入图片描述

接下来我们需要移植代码,首先移植温度数据代码中的bsp_i2c.h文件
代码如下:

#ifndef __BSP_I2C_H
#define __BSP_I2C_H

#include "sys.h"
#include "delay.h"
#include "usart.h"

 
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
//CRL = 0000 1111 1111 1111 1111 1111 1111 1111
//8<<28 = 1000 1111 1111 1111 1111 1111 1111 1111
//CRL = 1000 1111 1111 1111 1111 1111 1111 1111 = 0x8fffffff 表示 SDA 输入
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//CRL = 0x3fffffff 表示 SDA 输出


#define IIC_SCL    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //SDA 数据读取 7 管脚


void IIC_Init(void);
void  read_AHT20_once(void);
void  reset_AHT20(void);
void  init_AHT20(void);	
void  startMeasure_AHT20(void);
void  read_AHT20(void);
uint8_t  Receive_ACK(void);
void  Send_ACK(void);
void  SendNot_Ack(void);
void I2C_WriteByte(uint8_t  input);
uint8_t I2C_ReadByte(void);	
void  set_AHT20sendOutData(void);
void  I2C_Start(void);
void  I2C_Stop(void);

#endif

之后我们需要移植bsp_i2c.c文件(此处最好更改名称为 AHT20_sys.h,不然会重名)
代码如下:

#include "bsp_i2c.h"
#include "delay.h"
#include "string.h"

uint8_t   ack_status=0;
uint8_t   readByte[6];

uint32_t  H1=0;  //Humility
uint32_t  T1=0;  //Temperature

uint8_t  AHT20_OutData[4];

/****************
 *初始化 I2C 函数
 ****************/
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	//启用高速 APB (APB2) 外围时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );	
	
	//GPIO 定义
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//初始化 SCL(Pin6)高电平
	IIC_SCL=1;
	//初始化 SDA(Pin7)高电平
	IIC_SDA=1;
}

/*********************
 *AHT20 数据操作总函数
 *********************/
void read_AHT20_once(void)
{
	printf("读取数据中");
	
	//延时 10 微妙
	delay_ms(10);
	
  //传输数据前进行启动传感器和软复位
	reset_AHT20();
	delay_ms(10);
  
	//查看使能位
	init_AHT20();
	delay_ms(10);
	
  //触发测量
	startMeasure_AHT20();
	delay_ms(80);
  
	//读数据
	read_AHT20();
	delay_ms(5);
}


void reset_AHT20(void)
{
	//数据传输开始信号
	I2C_Start();
	
	//发送数据
	I2C_WriteByte(0x70);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
	{
		printf(">");
	}
	else
		printf("×");
	
	//发送软复位命令(重启传感器系统)
	I2C_WriteByte(0xBA);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//停止 I2C 协议
	I2C_Stop();
}

//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xE1 —> 看状态字的校准使能位Bit[3]是否为 1
//0x08 0x00 —> 0xBE 命令的两个参数,详见 AHT20 参考手册
void init_AHT20(void)
{
	//传输开始
	I2C_Start();

	//写入 0x70 数据
	I2C_WriteByte(0x70);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//写入 0xE1 数据
	I2C_WriteByte(0xE1);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//写入 0x08 数据
	I2C_WriteByte(0x08);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else 
		printf("×");
	
	//写入 0x00 数据
	I2C_WriteByte(0x00);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	//停止 I2C 协议
	I2C_Stop();
}

//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xAC —> 触发测量
//0x33 0x00 —> 0xAC 命令的两个参数,详见 AHT20 参考手册
void startMeasure_AHT20(void)
{
	//启动 I2C 协议
	I2C_Start();
	
	I2C_WriteByte(0x70);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0xAC);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0x33);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0x00);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_Stop();
}


void read_AHT20(void)
{
	uint8_t i;

	//初始化 readByte 数组
	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}

	I2C_Start();

	//通过发送 0x71 可以获取一个字节的状态字
	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();
	
	//接收 6 个 8 bit的数据
	readByte[0]= I2C_ReadByte();
	//发送 ACK 信号
	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();
	//发送 NACK 信号
	SendNot_Ack();

	I2C_Stop();

	//温湿度的二进制数据处理
	//0x68 = 0110 1000
  //0x08 = 0000 1000	
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		//H1 左移 8 位并与 readByte[2] 相或 
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		//H1 右移 4 位
		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("꧰üá?");

	}
	
	printf("完成!\n");
	printf("----温度:%d%d.%d °C\n",T1/100,(T1/10)%10,T1%10);
	printf("----湿度:%d%d.%d %%",H1/100,(H1/10)%10,H1%10);
	printf("\n\n");
}

//接收 ACK 信号
uint8_t Receive_ACK(void)
{
	uint8_t result=0;
	uint8_t cnt=0;

	//置 SCL 低电平
	IIC_SCL = 0;
	//设置 SDA 为读取数据模式
	SDA_IN();
	delay_us(4);

	//置 SCL 高电平
	IIC_SCL = 1;
	delay_us(4);

	//等待从机发送 ACK 信号,等待时间为 100 个循环
	while(READ_SDA && (cnt<100))
	{
		cnt++;
	}

	IIC_SCL = 0;
	delay_us(4);

	//如果在等待时间内,则结果为 1
	if(cnt<100)
	{
		result=1;
	}
	
	return result;
}

//发送 ACK 信号
void Send_ACK(void)
{
	//设置 SDA 为写数据模式
	SDA_OUT();
	IIC_SCL = 0;
	delay_us(4);

	//置 SDA 为低电平
	IIC_SDA = 0;
	delay_us(4);

	IIC_SCL = 1;
	delay_us(4);
	IIC_SCL = 0;
	delay_us(4);

	SDA_IN();
}

//发送 NACK 信号
void SendNot_Ack(void)
{
	//设置 SDA 为写数据模式
	SDA_OUT();
	
	IIC_SCL = 0;
	delay_us(4);

	IIC_SDA = 1;
	delay_us(4);

	IIC_SCL = 1;
	delay_us(4);

	IIC_SCL = 0;
	delay_us(4);

	IIC_SDA = 0;
	delay_us(4);
}

//发送一个字节数据
void I2C_WriteByte(uint8_t  input)
{
	uint8_t  i;
	//设置 SDA 为写数据模式
	SDA_OUT();
	
	//循环左移发送 8 bit数据
	for(i=0; i<8; i++)
	{
		IIC_SCL = 0;
		delay_ms(5);

		if(input & 0x80)
		{
			IIC_SDA = 1;
		}
		else
		{
			IIC_SDA = 0;
		}

		IIC_SCL = 1;
		delay_ms(5);

		input = (input<<1);
	}

	IIC_SCL = 0;
	delay_us(4);

	SDA_IN();
	delay_us(4);
}	

//循环检测 SDA 的电平状态并存储起来
uint8_t I2C_ReadByte(void)
{
	uint8_t  resultByte=0;
	uint8_t  i=0, a=0;

	IIC_SCL = 0;
	SDA_IN();
	delay_ms(4);

	//循环检测
	for(i=0; i<8; i++)
	{
		IIC_SCL = 1;
		delay_ms(3);

		a=0;
		if(READ_SDA)
		{
			a=1;
		}
		else
		{
			a=0;
		}

		resultByte = (resultByte << 1) | a;

		IIC_SCL = 0;
		delay_ms(3);
	}

	SDA_IN();
	delay_ms(10);

	return   resultByte;
}

//设置 I2C 协议开始
void I2C_Start(void)
{
	SDA_OUT();
	
	IIC_SCL = 1;
	delay_ms(4);

	//SDA 从 1 跳变为 0 的这个过程
	//表示起始信号
	IIC_SDA = 1;
	delay_ms(4);
	IIC_SDA = 0;
	delay_ms(4);

	//SCL 变为 0
	//表示 SDA 数据无效,此时 SDA 可以进行电平切换
	IIC_SCL = 0;
	delay_ms(4);
}

//设置 I2C 协议停止
void I2C_Stop(void)
{
	SDA_OUT();
	
	//SCL 高电平,SDA 高电平
	//停止时序
	IIC_SDA = 0;
	delay_ms(4);
	IIC_SCL = 1;
	delay_ms(4);

	//SDA 切换到高电平
	IIC_SDA = 1;
	delay_ms(4);
}

然后我们进行sys.h文件的移植
代码如下:

#ifndef __SYS_H
#define __SYS_H	

#include "stm32f10x.h"
 
#define SYSTEM_SUPPORT_UCOS		0
																	    
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)


void NVIC_Configuration(void);

#endif

之后我们还需要移植sys.c文件(此处最好更改名称为 AHT20_sys.c,不然会重名)
代码如下:

#include "sys.h"

void NVIC_Configuration(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}

最后我们经过添加修改相关的代码,得到了最终的代码:
代码链接:https://pan.baidu.com/s/1fWBFrqD7pWfxJk8GtWdVnA
提取码:tzrh

3.获取汉字字模

要想在 OLED 上显示英文、数字,可以直接输出显示,但要是想显示中文,就必须要对中文进行编码成点阵,因此我们需要安装以下的软件
字模软件下载链接:https://pan.baidu.com/s/1GRe2X3p2ETJJEFwXsnV1sw
提取码:fn8i
之后我们打开压缩包,点击.exe程序,如图所示:
在这里插入图片描述
然后我们在下面输入我们想看到的文字,例如:张博伦欢迎来到重庆交通大学
如图所示:
在这里插入图片描述
之后 我们需要将正向的文字左旋 90 °,然后再山下翻转,这样,OLED 上显示的文字才是正向的,如图所示:
在这里插入图片描述
在这里插入图片描述
然后我们点击生成字模,如图所示:
在这里插入图片描述
之后我们复制这段生成的代码,添加到gui.c 下有个 oledfont.h 头文件,打开后,将 cfont16[] 数组内的内容修改成自己的中文文字点阵即可(注意格式)
如图所示:
在这里插入图片描述
之后我们再把温度湿度等以同样的方式加入进去
然后我们进行编译并生成.hex文件
在这里插入图片描述
之后我们打开烧录程序,把程序烧进去
这个时候我是遇到了一些问题,应答的问题,解决方法呢,我们可以参照下图:
在这里插入图片描述
之后呢我们进行烧录,效果如下图所示:

在这里插入图片描述

总结

总的来说呢,这个修改程序的过程比较繁琐,需要添加很多文件和修改很多语句,不过有了大佬的帮助,代码用起来如鱼得水,效果也是非常明显

版权声明:本文为CSDN博主「BokLoen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/k5465456132/article/details/121407844

生成海报
点赞 0

BokLoen

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

串口不定长接收

一、保留接收区和开启接收的语句    uint8_t buffer[5];HAL_UART_Transmit_IT(&huart1,buffer,3); 二、写入开启空闲中断的语句    __HAL_UART_ENABLE_IT(&huart

基于STM32单片机的电子密码锁设计

一.硬件方案 本设计采用STM32F103C8T6单片机作为主控芯片,结合外围的矩阵按键输入、LCD1602液晶显示、报警、开锁等电路模块实现开锁、上锁、报警、密码更改等功能,设计了一款可以多次修改密码并且具有报警