一、要求
理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:
-
显示自己的学号和姓名;
-
显示AHT20的温度和湿度;
-
上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(使用硬件刷屏模式)。
二、什么是SPI
1、SPI的定义
SPI(Serial Peripheral Interface)就是串行外围设备接口。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚。SPI 是一个环形总线结构,由 ss(cs)、sck、sdi、sdo 构成,时序主要是在 sck 的控制下,两个双向移位寄存器进行数据交换。
上升沿发送、下降沿接收、高位先发送。
上升沿到来的时候,sdo 上的电平将被发送到从设备的寄存器中。
下降沿到来的时候,sdi 上的电平将被接收到主设备的寄存器中。
2、SPI连接方法
SS( Slave Select):从设备选择信号线,常称为片选信号线。
SCK (Serial Clock):时钟信号线,用于通讯数据同步。
MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。
MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。
三、0.96寸OLED显示屏显示数据
1、输入汉字得到字模
然后复制文本框里的字模。
2、将字模放入代码中
0x00,0x40,0x42,0x4C,0xC0,0x00,0x00,0x7A,0xCA,0xCA,0xBC,0xCA,0xCA,0xBC,0xCA,0xCA,
0xCA,0x7A,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x08,0x14,0x10,0x1F,0x16,0x16,0x16,
0x7E,0x16,0x16,0x1E,0x17,0x10,0x10,0x00,/*"谭",0*/
0x00,0x00,0x24,0x24,0xC4,0xFC,0xA4,0x22,0x00,0x00,0x7C,0x24,0x24,0x24,0x24,0x24,
0x7C,0xFC,0x00,0x00,0x00,0x10,0x0C,0x02,0x01,0x7F,0x00,0x03,0x40,0x41,0x49,0x49,
0x49,0x7F,0x49,0x49,0x49,0x40,0x40,0x00,/*"程",1*/
0x00,0x00,0x24,0x24,0x23,0x3F,0x25,0x44,0x00,0x00,0x3E,0x24,0x24,0x24,0x24,0x24,
0x3E,0x3F,0x00,0x00,0x00,0x08,0x30,0x40,0x80,0xFE,0x00,0xC0,0x02,0x82,0x92,0x92,
0x92,0xFE,0x92,0x92,0x92,0x02,0x02,0x00,/*"程",1*/
0x00,0x20,0x40,0x04,0x0C,0xE0,0x10,0x00,0xFC,0x94,0x94,0x94,0x94,0x94,0x94,0xFC,
0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x7C,0x43,0x40,0x7E,0x42,0x42,0x42,0x7E,0x42,
0x42,0x7E,0x42,0x42,0x7E,0x42,0x40,0x00,/*"温",0*/
0x00,0x20,0x62,0x44,0x8C,0x60,0x10,0x00,0xFC,0x94,0x54,0x54,0x54,0x94,0x54,0x54,
0xFC,0x00,0x00,0x00,0x00,0x04,0x34,0x7E,0x01,0x40,0x42,0x4C,0x58,0x40,0x7F,0x40,
0x40,0x7F,0x50,0x48,0x44,0x43,0x42,0x00,/*"湿",0*/
0x00,0x00,0x00,0xF8,0x28,0x28,0x28,0x28,0xF8,0x28,0x26,0x24,0x28,0xF8,0x38,0x28,
0x28,0x24,0x00,0x00,0x00,0x40,0x38,0x87,0x80,0x80,0x40,0x42,0x45,0x2B,0x33,0x13,
0x33,0x2B,0x46,0x40,0x40,0x40,0x40,0x00,/*"度",1*/
3、然后输入下面的代码
OLED.c
#include "stm32f10x.h"
#include "OLED_Font.h"
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/*引脚初始化*/
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 = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
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_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
//自学草稿:
// /*
// 例如,如果页面地址设置为B2h,下列地址为03h,上列地址为00h,
// 则表示起始列为PAGE2的SEG3。RAM访问指针的位置如图10-2所示。
// 输入的数据字节将被写入第3列的RAM位置
// */
//
// /*
// 关键理解:
// 页寻址模式 水平寻址 垂直寻址模式
//
// 共有8页 PAGE 0~8 页
// 每页有128列 SEG 0~127列,每列有8行
//
// OLED 128*64 = 可以理解为共有8大行,每大行代表1页,每页有8小行和128列
//
// /*看手册时这句话没理解
// 列的上起始位置 10~1F
// 通过命令00h~0Fh设置指针的 下 起始列地址
// 通过命令10h~1Fh设置指针的 上 起始列地址
// */
//
// 当在第一行第一列写入'1'时:
// 置上半身
// 设置第0页 0xB0;
// 设置下列起始位置 0x10
// 设置上列起始位置 0x00
// --------------------------
// 置下半身
// 设置第1页 0xB1;
// 设置下列起始位置 0x10
// 设置上列起始位置 0x00
//
// 当在第二行第二列写入'1'时:
// 置上半身
// 设置第2页 0xB2;
//
// 设置下列起始位置 0x10 //一直写到第2页的第16列
// 设置上列起始位置 0x08 //选择第2页,第8列开始写
// --------------------------
// 置下半身
// 设置第3页 0xb3;
// 设置下列起始位置 0x10 //一直写到第3页的第16列
// 设置上列起始位置 0x08 //选择第3页,第8列开始写
//
//
//
//
//
OLED_WriteCommand(0xB0 | Y); //设置Y位置 //选择第几页? Y=0~7页 PAGE0~PAGE7页//开始页
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置低4位 //选择下列起始位置//开始列
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置高4位 //选择上列起始位置//开始列 //通过命令10h~1Fh设置指针的上起始列地址。
//OLED_SetCursor((Line)+i,(Column)*8);
//OLED_SetCursor(Line,Column); //页,列
}
// * @brief OLED清屏
// * @param 无
// * @retval 无
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显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
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次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
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显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
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显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
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显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
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初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x20); //寻址模式
OLED_WriteCommand(0x02); //页寻址模式
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}
/*
入口参数:行;列;字模库中对应的数组成员;
*/
void OLED_ShowCN(uint8_t Line, uint8_t Column, uint8_t Num)
{
uint8_t i;
uint8_t wide=20;//字宽
OLED_SetCursor((Line-1)*2, (Column-1)*wide); //参数1:把光标设置在第几页. 参数2:把光标设置在第几列
for (i = 0; i < wide; i++)
{
OLED_WriteData(OLED_F10x16[Num][i]); //显示上半部分内容
}
OLED_SetCursor((Line-1)*2+1,(Column-1)*wide);
for (i = 0; i < wide; i++)
{
OLED_WriteData(OLED_F10x16[Num][i+wide]); //显示下半部分内容
}
}
/*
入口参数:行;列;值;整数长度;小数长度
*/
void OLED_Showfloat(uint8_t Line, uint8_t Column, float Num, uint8_t d_len, uint8_t f_len)
{
uint8_t Len = d_len+f_len; //总长度
char arr[Len+2]; //数组成员数;+2,因为要把'±' 和 '.'加上
uint8_t i,j; //这里坑死了,不赋值的话局部变量不知道为啥默认值不是0!!!!!
uint32_t temp;
//插入正负符号
i=0;
if(Num>=0)
{
arr[i]=43;// '+'
}else
{
arr[i]=45;// '-'.
Num=-Num;
}
i++;//游标+1
//将小数转为整数.
temp=(uint32_t)(Num*OLED_Pow(10,f_len));
//取整数
j=0;
while(j<d_len)
{
arr[i]=temp/OLED_Pow(10,Len-j-1)%10+'0';
j++;
i++;
}
arr[i] = 46;//在此处游标添加'.'
i++;
//取小数
while(j<Len)
{
arr[i]=temp/OLED_Pow(10,Len-j-1)%10+'0';
j++;
i++;
}
OLED_ShowString(Line,Column,arr);
}
点击编译后,生成hex文件。
然后将hex文件烧录到stm32芯片中。