一、电容触摸实验
1.电容触摸按键简介
电容式触摸按键已经广泛应用在家用电器、消费电子市场,其主要优势有:无机械装置, 使用寿命长;非接触式感应,面板不需要开孔;产品更加美观简洁;防水可以做到很好。
与机械按键不同,这里我们使用的是检测电容充放电时间的方法来判断是否有触摸,图 中的 A、B 分别表示有无人体按下时电容的充放电曲线。其中 R 是外接的电容充电电阻, Cs 是没有触摸按下时 TPAD 与 PCB 之间的杂散电容。而 Cx 则是有手指按下的时候,手指与 TPAD 之间形成的电容。图中的开关是电容放电开关(实际使用时,由 STM32F1 的 IO 代替)。
先用开关将 Cs(或 Cs+Cx)上的电放尽,然后断开开关,让 R 给 Cs(或 Cs+Cx)充电, 当没有手指触摸的时候,Cs 的充电曲线如图中的 A 曲线。而当有手指触摸的时候,手指和 TPAD 之间引入了新的电容 Cx,此时 Cs+Cx 的充电曲线如图中的 B 曲线。从上图可以看出,A、B 两 种情况下,Vc 达到 Vth 的时间分别为 Tcs 和 Tcs+Tcx。
2.硬件设计
3.程序流程图
4. 程序解析
tpad.h
#define TPAD_GPIO_PORT GPIOA
#define TPAD_GPIO_PIN GPIO_PIN_1
#define TPAD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define TPAD_TIMX_CAP TIM5
#define TPAD_TIMX_CAP_CHY TIM_CHANNEL_2 /* 通道 Y, 1<= Y <=4 */
#define TPAD_TIMX_CAP_CHY_CCRX TIM5->CCR2 /* 通道 Y 的捕获/比较寄存器 */
#define TPAD_TIMX_CAP_CHY_CLK_ENABLE() \
do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0) /* TIM5 时钟使能 */
首先,编写设置 TPAD 电容触摸按键的定时器输入捕获功能函数 tpad_timx_cap_init(),代 码如下:
static void tpad_timx_cap_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_IC_InitTypeDef timx_ic_cap_chy;
TPAD_GPIO_CLK_ENABLE(); /* TPAD 引脚 时钟使能 */
TPAD_TIMX_CAP_CHY_CLK_ENABLE(); /* 定时器 时钟使能 */
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的 GPIO 口 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD 引脚下拉输入 */
g_timx_cap_chy_handle.Instance = TPAD_TIMX_CAP; /* 定时器 5 */
g_timx_cap_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/* 向上计数模式 */
g_timx_cap_chy_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_cap_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/* 不分频*/
HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */
timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;/* 映射 TI1 */
timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 配置输入不分频 */
timx_ic_cap_chy.ICFilter = 0; /* 配置输入滤波器,不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy,
&timx_ic_cap_chy, TPAD_TIMX_CAP_CHY);/* 配置 TIM5 通道 2 */
HAL_TIM_IC_Start(&g_timx_cap_chy_handle,TPAD_TIMX_CAP_CHY);/* 使能输入捕获 */
}
接下来,我们通过控制变量法,每次先给 TPAD 放电(STM32 输出低电平)相同时间,然 后释放,监测 VCC 每次给 TPAD 的充电时间,由此可以得到一个充电时间,操作的代码如下:
static void tpad_reset(void)
{
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的 GPIO 口 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct);
/* TPAD 引脚输出 0, 放电 */
HAL_GPIO_WritePin(TPAD_GPIO_PORT, TPAD_GPIO_PIN, GPIO_PIN_RESET);
delay_ms(5);
g_timx_cap_chy_handle.Instance->SR = 0; /* 清除标记 */
g_timx_cap_chy_handle.Instance->CNT = 0; /* 归零 */
gpio_init_struct.Pin = TPAD_GPIO_PIN; /* 输入捕获的 GPIO 口 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_NOPULL; /* 浮空 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(TPAD_GPIO_PORT, &gpio_init_struct); /* TPAD 引脚浮空输入 */
}
/**
* @brief 得到定时器捕获值
* @note 如果超时, 则直接返回定时器的计数值
* 我们定义超时时间为: TPAD_ARR_MAX_VAL - 500
* @param 无
* @retval 捕获值/计数值(超时的情况下返回)
*/
static uint16_t tpad_get_val(void)
{
uint32_t flag = (TPAD_TIMX_CAP_CHY== TIM_CHANNEL_1)?TIM_FLAG_CC1:\
(TPAD_TIMX_CAP_CHY== TIM_CHANNEL_2)?TIM_FLAG_CC2:\
(TPAD_TIMX_CAP_CHY== TIM_CHANNEL_3)?TIM_FLAG_CC3:TIM_FLAG_CC4;
tpad_reset();
while (__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle ,flag) == RESET)
{ /* 等待通道 CHY 捕获上升沿 */
if (g_timx_cap_chy_handler.Instance->CNT > TPAD_ARR_MAX_VAL - 500)
{
return g_timx_cap_chy_handle.Instance->CNT; /* 超时了,直接返回 CNT 的值 */
}
}
return TPAD_TIMX_CAP_CHY_CCRX; /* 返回捕获/比较值 */
}
得到充电时间后,接下来我们要做的就是获取没有按下 TPAD 时的充电时间,并把它作为 基准来确认后续有无按下操作,我们定义全局变量 g_tpad_default_val 来保存这个值,通过多 次平均的滤波算法来减小误差,编写的初始化函数 tpad_init 代码如下。
uint8_t tpad_init(uint16_t psc)
{
uint16_t buf[10];
uint16_t temp;
uint8_t j, i;
tpad_timx_cap_init(TPAD_ARR_MAX_VAL, psc - 1);/* 以 72/(psc-1)Mhz 的频率计数 */
for (i = 0; i < 10; i++) /* 连续读取 10 次 */
{
buf[i] = tpad_get_val();
delay_ms(10);
}
for (i = 0; i < 9; i++) /* 排序 */
{
for (j = i + 1; j < 10; j++)
{
if (buf[i] > buf[j]) /* 升序排列 */
{
temp = buf[i];
buf[i] = buf[j];
buf[j] = temp;
}
}
}
temp = 0;
for (i = 2; i < 8; i++) /* 取中间的 6 个数据进行平均 */
{
temp += buf[i];
}
g_tpad_default_val = temp / 6;
printf("g_tpad_default_val:%d\r\n", g_tpad_default_val);
if (g_tpad_default_val > TPAD_ARR_MAX_VAL / 2)
{
return 1; /* 初始化遇到超过 TPAD_ARR_MAX_VAL/2 的数值,不正常! */
}
return 0;
}
得到默认的充电初始值后,我们需编写一个按键扫描函数,以便在需要监控 TPAD 的地方 调用,代码如下:
uint8_t tpad_scan(uint8_t mode)
{
static uint8_t keyen = 0; /* 0, 可以开始检测; >0, 还不能开始检测; */
uint8_t res = 0;
uint8_t sample = 3; /* 默认采样次数为 3 次 */
uint16_t rval;
if (mode)
{
sample = 6; /* 支持连按的时候,设置采样次数为 6 次 */
keyen = 0; /* 支持连按, 每次调用该函数都可以检测 */
}
rval = tpad_get_maxval(sample);
if (rval > (g_tpad_default_val + TPAD_GATE_VAL))
{ /* 大于 tpad_default_val+TPAD_GATE_VAL,有效 */
if (keyen == 0)
{
res = 1; /* keyen==0, 有效 */
}
//printf("r:%d\r\n", rval); /* 输出计数值, 调试的时候才用到 */
keyen = 3; /* 至少要再过 3 次之后才能按键有效 */
}
if (keyen) keyen--;
return res;
}
main.c
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化 HAL 库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为 115200 */
led_init(); /* 初始化 LED */
tpad_init(6); /* 初始化触摸按键 */
while (1)
{
if (tpad_scan(0)) /* 成功捕获到了一次上升沿(此函数执行时间至少 15ms) */
{
LED1_TOGGLE(); /* LED1 取反 */
}
t++;
if (t == 15)
{
t = 0;
LED0_TOGGLE(); /* LED0 取反 */
}
delay_ms(10);
}
}
二、OLED 显示实验
1.OLED 简介
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display,OLED)。
OLED 可按发光材料分为两种:小分子 OLED 和高分子 OLED(也可称为 PLED)。
OLED 显示模块及其使用方法,该模块有以下特点:
1)模块有单色和双色两种可选,单色为纯蓝色,而双色则为黄蓝双色(分区域的双色,前 16 行为黄色,后 48 行为蓝色,且黄蓝色之间有一行不显示的间隔区。)。
2)尺寸小,显示尺寸为 0.96 寸,而模块的尺寸仅为 27mm*26mm 大小。
3)高分辨率,该模块的分辨率为 128*64。
4)多种接口方式,该模块提供了总共 4 种接口包括:6800、8080 两种并行接口方式、4 线 SPI 接口方式以及 IIC 接口方式(只需要 2 根线就可以控制 OLED 了!)。
5)不需要高压,直接接 3.3V 就可以工作了。
该模块不和 5.0V 接口兼容,所以请大家在使用的时候一定要小心, 别直接接到 5V 的系统上去,否则可能烧坏模块。以下 4 种模式通过模块的 BS1 和 BS2 设置, BS1 和 BS2 的设置与模块接口模式的关系如表所示:
2.模块的原理图
只用了 15 条,有一个是悬空的。15 条线中,电源和地线占了 2 条,还剩下 13 条信号线。在不同模式下, 我们需要的信号线数量是不同的,在 8080 模式下,需要全部 13 条,而在 IIC 模式下,仅需要 2 条线就够了!这其中有一条是共同的,那就是复位线 RST(RES),RST 上的低电平,将导致 OLED 复位,在每次初始化之前,都应该复位一下 OLED 模块。
3.硬件驱动接口模式
(1)先我们介绍一下模块的 8080 并行接口,8080 并行接口的发明者是 INTEL,该总线也被 广泛应用于各类液晶显示器,正点原子 OLED 模块也提供了这种接口,使得 MCU 可以快速的 访问 OLED。正点原子 OLED 模块的 8080 接口方式需要如下一些信号线:
CS:OLED 片选信号。
WR:向 OLED 写入数据。
RD:从 OLED 读取数据。
D[7:0]:8 位双向数据线。
RST(RES):硬复位 OLED。
DC:命令/数据标志(0,读写命令;1,读写数据)。
模块的 8080 并口读的过程为:先根据要写入的数据的类型,设置 DC 为高(数据)/低(命 令),设置 RD 起始电平为高,然后拉低片选 CS 信号,选中 SSD1306,接着我们在整个读时序 上保持 WR 为高电平,然后类似写时序,同样的,在 RD 的上升沿,使数据锁存到数据线(D[7: 0])上。
SSD1306 的 8080 并口读时序图如图 所示:
SSD1306 的 8080 接口方式下,控制脚的信号状态所对应的功能如表:
2. SPI 模式
代码同时兼容 SPI 方式的驱动,如果你使用的是这种驱动方式,则应该把代码中的 宏 OLED_MODE 设置为 0,但对于硬件,则需要查看 PCB 背面的电阻设置以确定当前使用的 是否为 SPI 模式:
#define OLED_MODE 0 /* 0: 4 线串行模式 */
OLED 显存
可以看出,SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 128*64 的点阵大 小。当 GRAM 的写入模式为页模式时,需要设置低字节起始的列地址(0x00~0x0F)和高字 节的起始列地址(0x10~0x1F),芯片手册中给出了写入 GRAM 与显示的对应关系,写入列地 址在写完一字节后自动按列增长,如图 所示:
只修改 STM32F103 上的 GRAM(实际上就是 SRAM),在修改完成后 一次性把 STM3F103 上的 GRAM 写入到 OLED 的 GRAM。当然这个方法也有坏处,一个对于 那些 SRAM 很小的单片(比如 51 系列)不太友好,另一个是每次都写入全屏,屏幕刷新率 会变低。
SSD1306 的命令比较多,这里我们仅介绍几个比较常用的命令,这些命令如表 :
第 1 个命令为 0XAE/0XAF。0XAE 为关闭显示命令;0XAF 为开启显示命令。
第 2 个命令为 0X8D,该指令也包含 2 个字节,第一个为命令字,第二个为设置值,第二 个字节的 BIT2 表示电荷泵的开关状态,该位为 1,则开启电荷泵,为 0 则关闭。在模块初始化 的时候,这个必须要开启,否则是看不到屏幕显示的。
第 3 个命令为 0XB0~B7,该命令用于设置页地址,其低三位的值对应着 GRAM 的页地址。
第 4 个指令为 0X00~0X0F,该指令用于设置显示时的起始列地址低四位。
第 5 个指令为 0X10~0X1F,该指令用于设置显示时的起始列地址高四位。
OLED 显示需要的相 关设置步骤如下:
1)设置 STM32F103 与 OLED 模块相连接的 IO。
2)初始化 OLED 模块。
3)通过函数将字符和数字显示到 OLED 模块上。
4.程序流程图
5.OLED 驱动代码
oledfont.h
/* 12*12 ASCII 字符集点阵 */
const unsigned char oled_asc2_1206[95][12]={ ...这里省略字符集库... };
/* 16*16 ASCII 字符集点阵 */
const unsigned char oled_asc2_1608[95][16]={ ...这里省略字符集库... };
/* 24*24 ASICII 字符集点阵 */
const unsigned char oled_asc2_2412[95][36]={ ...这里省略字符集库... };
/* OLED 模式设置
* 0: 4 线串行模式 (模块的 BS1,BS2 均接 GND)
* 1: 并行 8080 模式 (模块的 BS1,BS2 均接 VCC)
*/
#define OLED_MODE 1 /* 默认使用 8080 并口模式 */
/* 命令/数据 定义 */
#define OLED_CMD 0 /* 写命令 */
#define OLED_DATA 1 /* 写数据 */
oled.c 文件的驱动源码介绍。
void oled_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOC_CLK_ENABLE(); /* 使能 GPIOC 时钟 */
__HAL_RCC_GPIOD_CLK_ENABLE(); /* 使能 GPIOD 时钟 */
__HAL_RCC_GPIOG_CLK_ENABLE(); /* 使能 GPIOG 时钟 */
/* PC0 ~ 7 设置 */
gpio_init_struct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|
GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* 中速 */
HAL_GPIO_Init(GPIOC, &gpio_init_struct); /* PC0 ~ 7 设置 */
gpio_init_struct.Pin = GPIO_PIN_3|GPIO_PIN_6; /* PD3, PD6 设置 */
HAL_GPIO_Init(GPIOD, &gpio_init_struct); /* PD3, PD6 设置 */
gpio_init_struct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
HAL_GPIO_Init(GPIOG, &gpio_init_struct); /* WR/RD/RST 引脚模式设置 */
OLED_WR(1);
OLED_RD(1);
OLED_CS(1);
OLED_RS(1);
OLED_RST(0);
delay_ms(100);
OLED_RST(1);
oled_wr_byte(0xAE, OLED_CMD); /* 关闭显示 */
oled_wr_byte(0xD5, OLED_CMD); /* 设置时钟分频因子,震荡频率 */
oled_wr_byte(80, OLED_CMD); /* [3:0],分频因子;[7:4],震荡频率 */
oled_wr_byte(0xA8, OLED_CMD); /* 设置驱动路数 */
oled_wr_byte(0X3F, OLED_CMD); /* 默认 0X3F(1/64) */
oled_wr_byte(0xD3, OLED_CMD); /* 设置显示偏移 */
oled_wr_byte(0X00, OLED_CMD); /* 默认为 0 */
oled_wr_byte(0x40, OLED_CMD); /* 设置显示开始行 [5:0],行数. */
oled_wr_byte(0x8D, OLED_CMD); /* 电荷泵设置 */
oled_wr_byte(0x14, OLED_CMD); /* bit2,开启/关闭 */
oled_wr_byte(0x20, OLED_CMD); /* 设置内存地址模式 */
/* [1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认 10; */
oled_wr_byte(0x02, OLED_CMD);
oled_wr_byte(0xA1, OLED_CMD); /* 段重定义设置,bit0:0,0->0;1,0->127; */
/* 设置 COM 扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
oled_wr_byte(0xC8, OLED_CMD);
oled_wr_byte(0xDA, OLED_CMD); /* 设置 COM 硬件引脚配置 */
oled_wr_byte(0x12, OLED_CMD); /* [5:4]配置 */
oled_wr_byte(0x81, OLED_CMD); /* 对比度设置 */
oled_wr_byte(0xEF, OLED_CMD); /* 1~255;默认 0X7F (亮度设置,越大越亮) */
oled_wr_byte(0xD9, OLED_CMD); /* 设置预充电周期 */
oled_wr_byte(0xf1, OLED_CMD); /* [3:0],PHASE 1;[7:4],PHASE 2; */
oled_wr_byte(0xDB, OLED_CMD); /* 设置 VCOMH 电压倍率 */
/* [6:4]000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */
oled_wr_byte(0x30, OLED_CMD);
oled_wr_byte(0xA4, OLED_CMD); /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
oled_wr_byte(0xA6, OLED_CMD); /* 设置显示方式;bit0:1,反相显示;0,正常显示 */
oled_wr_byte(0xAF, OLED_CMD); /* 开启显示 */
oled_clear();
}
该数组值与 OLED 显存 GRAM 值一一对应。在操作的时候我们只需要先修改该数组的值, 然后再通过调用 oled_refresh_gram 函数把数组的值一次性刷新到 OLED 的 GRAM 上即可。 oled_refresh_gram 函数定义如下:
void oled_refresh_gram(void)
{
uint8_t i, n;
for (i = 0; i < 8; i++)
{
oled_wr_byte (0xb0 + i, OLED_CMD); /* 设置页地址(0~7) */
oled_wr_byte (0x00, OLED_CMD); /* 设置显示位置—列低地址 */
oled_wr_byte (0x10, OLED_CMD); /* 设置显示位置—列高地址 */
for (n = 0; n < 128; n++)
{
oled_wr_byte(g_oled_gram[n][i], OLED_DATA);
}
}
}
oled_refresh_gram 函数还调用了 oled_wr_byte 这个函数,也就是我们接着要介绍的函数: 该函数和硬件相关,8080 并口模式下该函数定义如下:
static void oled_wr_byte(uint8_t data, uint8_t cmd)
{
oled_data_out(data);
OLED_RS(cmd);
OLED_CS(0);
OLED_WR(0);
OLED_WR(1);
OLED_CS(1);
OLED_RS(1);
}
8080 并口模式下的 oled_wr_byte 函数还调用 oled_data_out 函数,其定义如下:
static void oled_data_out(uint8_t data)
{
GPIOC->ODR = (GPIOC->ODR & 0XFF00) | (data & 0X00FF);
}
使用 SPI 模式,则操作模块时会调用的函数接口按以下的软件 SPI 方式实现:
static void oled_wr_byte(uint8_t data, uint8_t cmd)
{
uint8_t i;
OLED_RS(cmd); /* 写命令 */
OLED_CS(0);
for (i = 0; i < 8; i++)
{
OLED_SCLK(0);
if (data & 0x80)
OLED_SDIN(1);
else
OLED_SDIN(0);
OLED_SCLK(1);
data <<= 1;
}
OLED_CS(1);
OLED_RS(1);
}
OLED 画点函数,其定义如下:
void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot)
{
uint8_t pos, bx, temp = 0;
if (x > 127 || y > 63) return; /* 超出范围了 */
pos = y / 8; /* 计算 GRAM 里面的 y 坐标所在的字节, 每个字节可以存储 8 个行坐标 */
bx = y % 8; /* 取余数,方便计算 y 在对应字节里面的位置,及行(y)位置 */
temp = 1 << bx; /* 高位表示高行号, 得到 y 对应的 bit 位置,将该 bit 先置 1 */
if (dot) /* 画实心点 */
{
g_oled_gram[x][pos] |= temp;
}
else /* 画空点,即不显示 */
{
g_oled_gram[x][pos] &= ~temp;
}
}
下面根据取模的方式来编写 显示字符 oled_show_char 函数,其定义如下:
void oled_show_char(uint8_t x,uint8_t y,uint8_t chr,uint8_t size,uint8_t mode)
{
uint8_t temp, t, t1;
uint8_t y0 = y;
uint8_t *pfont = 0;
/* 得到字体一个字符对应点阵集所占的字节数 */
uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);
chr = chr - ' '; /* 得到偏移后的值,因为字库是从空格开始存储的,第一个字符是空格 */
if (size == 12) /* 调用 1206 字体 */
{
pfont = (uint8_t *)oled_asc2_1206[chr];
}
else if (size == 16) /* 调用 1608 字体 */
{
pfont = (uint8_t *)oled_asc2_1608[chr];
}
else if (size == 24) /* 调用 2412 字体 */
{
pfont = (uint8_t *)oled_asc2_2412[chr];
}
else /* 没有的字库 */
{
return;
}
for (t = 0; t < csize; t++)
{
temp = pfont[t];
for (t1 = 0; t1 < 8; t1++)
{
if (temp & 0x80)oled_draw_point(x, y, mode);
else oled_draw_point(x, y, !mode);
temp <<= 1;
y++;
if ((y - y0) == size)
{
y = y0;
x++;
break;
}
}
}
}
main.c
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化 HAL 库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为 115200 */
led_init(); /* 初始化 LED */
oled_init(); /* 初始化 OLED */
oled_show_string(0, 0, "ALIENTEK", 24);
oled_show_string(0, 24, "0.96' OLED TEST", 16);
oled_show_string(0, 40, "ATOM 2020/4/21", 12);
oled_show_string(0, 52, "ASCII:", 12);
oled_show_string(64,52, "CODE:", 12);
oled_refresh_gram(); /* 更新显示到 OLED */
t = ' ';
while (1)
{
oled_show_char(36, 52, t, 12, 1); /* 显示 ASCII 字符 */
oled_show_num(94, 52, t, 3, 12); /* 显示 ASCII 字符的码值 */
oled_refresh_gram(); /* 更新显示到 OLED */
t++;
if (t > '~')t = ' ';
delay_ms(500);
LED0_TOGGLE(); /* LED0 闪烁 */
}
}