重点来了
这个课程设计中硬件方面一共会有两个重点
其中一个自然就是今天要做的OLCD屏幕的驱动
第二个是RFID标签的读写
由于我买的是野火的I2C OLCD屏幕,自然选择野火自带的例程进行修改,让其能够适配HAL库的开发
当然既然是I2C的OLCD,那必然离不开I2C协议
值得一提的是,在stm32要实现I2C,可以选择两种方式
1.硬件I2C
2.软件模拟I2C
虽然火哥在标准库的视频中说过硬件I2C可能会存在一定的问题,但是既然买了板子,当然要用哇,不用岂不是暴殄天物。
所以,我们还是在cubemx中对硬件进行配置,然后驱动OLCD屏幕
I2C是一种协议,当然其核心还是时序逻辑,软件I2C同样好用,但是对编程稍微较高,未来可能会在b站上录制相应的视频
stm32cubemx
首先还是查找硬件相关的手册
这里就不在截图了
总之,选择PB6、PB7引脚输出I2C,正好对应为I2C1的配置
在cubemx中进行配置
I2C的模式当然就是选择I2C
subus可以自己看看,按照火哥讲的,这个是另一种协议不在考虑范围内
下面就是I2C的详细配置
选择高速模式,这个是由外设决定
OLCD支持高速模式,所以可以这样设置
占空比选择2
延迟Disabled
地址位数7
Disabled
slave地址0(不选用主从模式)
然后生成代码
keil5
好!
工程变得越来越复杂了,我们细心的构建一下工程目录
除了系统自动生成的文件夹外,我们在自己创建一个
user
文件夹,用于存放自己编写的文件
工程目录如下:
其中**.ioc**文件当然就是cubemx文件(存放路径不要有中文名,要全英文)
core就是包含src和inc,即c文件和头文件的地方属于系统配置文件,包括着各种系统外设的初始化
Drivers芯片包文件
MDK-ARM工程文件以及各种Debug生成文件
User文件夹就是自己创建的文件
点进user文件夹中
oled就是所有有关oled的模块文件(OLCD)
bsp_system.h文件中包含着全部的自己创建的头文件
(未来大型工程,每次都要写一遍包含头文件太麻烦了)
codetable.h字模表,可以不用管,这是为了LCD中显示中文而用的,如果不用显示中文可以将这个头文件删除
买外设的时候必然会给你提供一定的例程文件
我这里就不再进行分析了,比较麻烦,有兴趣的自己看就行了
总之你需要在keil中有这几个文件
自己添加的就是这个OLED_I2C.c文件以及相关的头文件
在这个文件里面有所有有关OLED的函数
下面是OLED_I2C.c文件
#include "OLED_I2C.h"
#include "i2c.h"
#include "codetab.h"
#include "stm32f1xx_hal.h"
void WriteCmd(unsigned char I2C_OLED_COMmand)//写命令
{
HAL_I2C_Mem_Write(&hi2c1,OLED_ADD,OLED_COM,I2C_MEMADD_SIZE_8BIT,&I2C_OLED_COMmand,1,100);
}
void WriteOLED_DAT(unsigned char I2C_OLED_DATa)//写数据
{
HAL_I2C_Mem_Write(&hi2c1,OLED_ADD,OLED_DAT,I2C_MEMADD_SIZE_8BIT,&I2C_OLED_DATa,1,100);
}
void OLED_Init(void)
{
HAL_Delay(100); //这里的延时很重要
WriteCmd(0xAE); //display off
WriteCmd(0x20); //Set Memory Addressing Mode
WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
WriteCmd(0xc8); //Set OLED_COM Output Scan Direction
WriteCmd(0x00); //---set low column address
WriteCmd(0x10); //---set high column address
WriteCmd(0x40); //--set start line address
WriteCmd(0x81); //--set contrast control register
WriteCmd(0xff); //亮度调节 0x00~0xff
WriteCmd(0xa1); //--set segment re-map 0 to 127
WriteCmd(0xa6); //--set normal display
WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
WriteCmd(0x3F); //
WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
WriteCmd(0xd3); //-set display offset
WriteCmd(0x00); //-not offset
WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
WriteCmd(0xf0); //--set divide ratio
WriteCmd(0xd9); //--set pre-charge period
WriteCmd(0x22); //
WriteCmd(0xda); //--set OLED_COM pins hardware configuration
WriteCmd(0x12);
WriteCmd(0xdb); //--set vOLED_COMh
WriteCmd(0x20); //0x20,0.77xVcc
WriteCmd(0x8d); //--set DC-DC enable
WriteCmd(0x14); //
WriteCmd(0xaf); //--turn on oled panel
}
void OLED_SetPos(unsigned char x, unsigned char y) //设置起始点坐标
{
WriteCmd(0xb0+y);
WriteCmd(((x&0xf0)>>4)|0x10);
WriteCmd((x&0x0f)|0x01);
}
void OLED_Fill(unsigned char fill_OLED_DATa)//全屏填充
{
unsigned char m,n;
for(m=0;m<8;m++)
{
WriteCmd(0xb0+m); //page0-page1
WriteCmd(0x00); //low column start address
WriteCmd(0x10); //high column start address
for(n=0;n<128;n++)
{
WriteOLED_DAT(fill_OLED_DATa);
}
}
}
void OLED_CLS(void)//清屏
{
OLED_Fill(0x00);
}
void OLED_ON(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X14); //开启电荷泵
WriteCmd(0XAF); //OLED唤醒
}
void OLED_OFF(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X10); //关闭电荷泵
WriteCmd(0XAE); //OLED休眠
}
// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); ch[] -- 要显示的字符串; TextSize -- 字符大小(1:6*8 ; 2:8*16)
// Description : 显示codetab.h中的ASCII字符,有6*8和8*16可选择
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 1:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 126)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteOLED_DAT(F6x8[c][i]);
x += 6;
j++;
}
}break;
case 2:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteOLED_DAT(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteOLED_DAT(F8X16[c*16+i+8]);
x += 8;
j++;
}
}break;
}
}
// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); N:汉字在.h中的索引
// Description : 显示ASCII_8x16.h中的汉字,16*16点阵
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N;
OLED_SetPos(x , y);
for(wm = 0;wm < 16;wm++)
{
WriteOLED_DAT(F16x16[adder]);
adder += 1;
}
OLED_SetPos(x,y + 1);
for(wm = 0;wm < 16;wm++)
{
WriteOLED_DAT(F16x16[adder]);
adder += 1;
}
}
// Parameters : x0,y0 -- 起始点坐标(x0:0~127, y0:0~7); x1,y1 -- 起点对角线(结束点)的坐标(x1:1~128,y1:1~8)
// Description : 显示BMP位图
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0)
y = y1/8;
else
y = y1/8 + 1;
for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y);
for(x=x0;x<x1;x++)
{
WriteOLED_DAT(BMP[j++]);
}
}
}
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{
unsigned char c=0,i=0;
c=chr-' ';//得到偏移后的值
if(x>128-1){x=0;y=y+2;}
if(Char_Size ==16)
{
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteOLED_DAT(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteOLED_DAT(F8X16[c*16+i+8]);
}
else {
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteOLED_DAT(F6x8[c][i]);
}
}
u32 oled_pow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}
//显示2个数字
//x,y :起点坐标
//len :数字的位数
//size:字体大小
//mode:模式 0,填充模式;1,叠加模式
//num:数值(0~4294967295);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{
u8 t,temp;
u8 enshow=0;
for(t=0;t<len;t++)
{
temp=(num/oled_pow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
continue;
}else enshow=1;
}
OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
}
}
后面会有分析
OLED_I2C.h头文件
#ifndef __OLED_I2C_H
#define __OLED_I2C_H
#include "i2c.h"
#define u8 uint8_t
#define u32 uint32_t
#define OLED_ADD 0x78 // OLED的I2C地址(禁止修改)
#define OLED_COM 0x00 // OLED 指令(禁止修改)
#define OLED_DAT 0x40 // OLED 数据(禁止修改)
void WriteCmd(unsigned char I2C_Command);//写命令
void WriteDat(unsigned char I2C_Data);//写数据
void OLED_Init(void);//初始化
void OLED_SetPos(unsigned char x, unsigned char y);
void OLED_Fill(unsigned char fill_Data);//全屏填充
void OLED_CLS(void);
void OLED_ON(void);
void OLED_OFF(void);
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize);//显示字符串
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N);//显示汉字
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[]);//显示图片
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size);
u32 oled_pow(u8 m,u8 n);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2);//size2(16|12)
#endif
头文件主要是对c文件中定义的函数进行声明
同时定义一些变量
注意
#ifndef #define #endif
防止头文件被重复定义
bsp_system.h
#ifndef __BSP_SYSTEM_H
#define __BSP_SYSTEM_H
#include "stdio.h"
#include "OLED_I2C.h"
#endif /* __BSP_SYSTEM_H */
最后是main.c文件
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_system.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t send_date[64] = {0};
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
OLED_Init();
OLED_CLS();
OLED_ShowStr(0,0,(unsigned char*)"Hello world",2);
OLED_ON();
while (1)
{
//串口调试
sprintf((char *)send_date,"test date\n");
HAL_UART_Transmit(&huart1, send_date, sizeof(send_date), 100);
//HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
好,代码都上传完了,那么接下来就是代码解释
main文件和那些头文件没啥可说的
主要是程序移植过程中的OLED_I2C.c文件中的内容
看起来函数很多实际上就一两个函数需要修改
最底层的函数只有2个
void WriteCmd(unsigned char I2C_OLED_COMmand)//写命令
{
HAL_I2C_Mem_Write(&hi2c1,OLED_ADD,OLED_COM,I2C_MEMADD_SIZE_8BIT,&I2C_OLED_COMmand,1,100);
}
void WriteOLED_DAT(unsigned char I2C_OLED_DATa)//写数据
{
HAL_I2C_Mem_Write(&hi2c1,OLED_ADD,OLED_DAT,I2C_MEMADD_SIZE_8BIT,&I2C_OLED_DATa,1,100);
}
一个函数是写指令一个是写数据
驱动OLCD实际上还是对寄存器进行读写,特定的寄存器有特定的地址
所以我们在头文件中定义的了相对应的地址
#define OLED_ADD 0x78 // OLED的I2C地址(禁止修改)
#define OLED_COM 0x00 // OLED 指令(禁止修改)
#define OLED_DAT 0x40 // OLED 数据(禁止修改)
根据地址写入数据对屏幕进行驱动
HAL库相关函数为
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
阻塞模式直接对Mem进行读写
为什么野火例程会有那么长,你这里就这么点?
野火程序,是为了程序的健壮性,
例程例程,当然是给你最好的东西,防止出错,同时对时序进行一定判断
其实有部分程序已经在hal的函数中进行了集成,不需要自己考虑
很方便