学习嵌入式入门(十一)电容触摸实验及OLED 显示实验

一、电容触摸实验

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 闪烁 */ 
 } 
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值