STM32+12864实现RGB灯亮度的菜单调节
写在前⾯
这是我第⼀次写⽂章,可能有很多错误或者描述不到位的地⽅,希望⼤家多多包涵。如果有前辈发现我的⽂章中有什么问题,欢迎指出,谢谢。同时,这篇⽂章也是⽤作我⾃⼰以后查询⼀些资料之⽤,所以我会分析很多的源码、协议等。不需要的朋友可以⾃⾏省略。程序上参考了野⽕和中景园的部分例程。
昨天,做了个⼩实验,使⽤STM32配合OLED12864实现对多个LED发光⼆极管的亮度管理。具体的要求如下:能够在OLED上显⽰出的LED的种类,如红灯、绿灯、蓝灯等。并在后⾯显⽰当前板⼦上对应的LED的实际亮度。利⽤两个按键进⾏调节。其中第⼀个按键⽤于选择LED,第⼆个按键实现对当前选中的LED的亮度进⾏调节。调节LED亮度的⼿段为PWM,调节的亮度等级为0-9。
硬件⽅案
我使⽤了野⽕霸道V-2开发板,板上的芯⽚为STM32F103ZET6。板上外设搭载了⼀个RGB灯,可以分别控制红⾊、绿⾊、蓝⾊光的亮度。红、绿、蓝分别接在板⼦的PB5、PB0、PB1引脚。原理图如下:
12864采⽤了OLED的版本,⽀持SPI通讯协议。
SPI通讯协议
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串⾏外围设备接⼝。最早由摩托罗拉公司提出,是⼀种⾼速的,全双⼯,同步的通信总线。现⼴泛的⽤于ADC、LCD等设备。
SPI的优缺点:
优点:1.传输速度快
2.⽤4条线完成数据传输,节约芯⽚管脚
缺点:1没有指定的流控制,没有应答机制确认是否接收到数据
SPI的硬件组成
SPI由四条数据线组成:
1.SCK:时钟线,由主机控制
2.MISO: M代表主机,I代表INPUT,S代表从机,O代表OUTPUT,即主收从发数据线
3.MOSI: M代表主机,O代表OUTPUT,S代表从机,I代表INPUT,即主发从收数据线
4.CS:⽚选信号线。0代表被选中,1代表没被选中
关于SPI协议本⽂只做基本的介绍,更多详细的了解⼤家可以在CSDN上查其他的笔记,也可以在b 站搜索SPI协议,查相关视频进⾏学习。
12864显⽰屏
12864简介
12864显⽰屏是⼀种统称,并⾮指专门的某⼀型号的显⽰屏。12864代表的含义是横向有128个像素,纵向有64个像素,即128*64⼤⼩的液晶屏。类似的命明规则包括1602(16代表⼀⾏可以显⽰16个字符,02表⽰有两⾏),1601(16代表⼀⾏可以显⽰16个字符,01表⽰有⼀⾏)等。不同的⼚商⽣产的12864具体的使⽤⽅法可能不同,所以需要⼤家注意。
OLED12864简介
我选⽤的12864是OLED12864,没有带字库,所以需要我们利⽤取模软件对汉字进⾏取模后,才可以显⽰出汉字。12864⽀持SPI协议。可以利⽤SPI协议进⾏写⼊。与标准的SPI协议不同,我们只需要
主机发送数据到OLED12864上即可。⽽且在SPI总线上只挂载了⼀个外设,所以我们将CS线硬件接地,保持选中状态。所以,我们实际上只使⽤了SPI协议中的MOSI线和SCLK线。由于我的PCB板上的丝印分别对应的接⼝为SDA和SCL,所以我在程序中使⽤了SDA和SCL来表⽰。OLED12864上还有两个接⼝,分别为RES和D/C,RES⽤于OLED的重置,D/C线⽤于控制向OLED12864发送的是命令还是数据。
OLED12864的控制
要想控制好12864,⾸先需要查阅其数据⼿册。根据数据⼿册可以得知,在每次使⽤OLED12864之前,⾸先需要对屏幕进⾏初始化。⽽对屏幕初始化则需要使⽤SPI协议进⾏数据传输,所以,我⾸先分析如何使⽤SPI进⾏数据传输。需要说明的是,我采⽤了ARM软件模拟的⽅法实现SPI传输,没有使⽤STM32的⽚上外设。
void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{
u8 i;
if(cmd)
OLED_DC_GPIO_DATA;
else
OLED_DC_GPIO_COM;
for(i=0;i<8;i++)
{
OLED_SCLK_Clr();
if(dat&0x80)
OLED_SDA_HIGH;
else
OLED_SDA_LOW;
OLED_SCLK_Set();
大陆三小龙
dat<<=1;
}
OLED_DC_GPIO_DATA;
e}
这个⾃定义函数模拟了SPI协议,⽤于向 OLED12864发送数据。其中第⼀个形参⽤于填写发送数据的具体值。第⼆个形参⽤与选择发送的是数据,还是指令。所谓数据,⼤家可以理解为需要写⼊的⼗六进制数,⽽指令,则可以理解为当把数据写⼊oled的显存中后,让其显⽰出来。接下来,我们简单分析⼀下这个函数的具体实现原理。根据OLED12864的数据⼿册可知。当D/C线接为⾼电平时,代表向
OLED12864写⼊数据,D/C线为低电平时写⼊指令。当调⽤该函数的时候,程序⾸先读取第⼆个形参com的数据。根据com形参的值,选择D/C线的值。OLED_DC_GPIO_DATA; 和 OLED_DC_GPIO_COM;两句我在头⽂件中定义。
选择好发送是数据还是指令。接下来就要发送形参1data中储存的具体的值,输输⼊的值⼀般为8bit。根据SPI协议,OLED12864在时钟线上升沿时接收数据。所以,我们⾸先调⽤OLED_SCLK_Clr();拉低时钟线。接下来是SPI发送的核⼼。0x80转化为⼆进制数为10000000(b),将我们要发送的数据
和1000000(b)做与运算之后,就可以需要发送数据的最⾼位写的是0还是1.如果最⾼位为0,则SDA线发送低电平,代表逻辑0,反之,发送⾼电平,代表逻辑1。发送完数据后,拉⾼时钟线,产⽣⼀个上升沿。使OLED12864读取数据。这⼀个过程,完成了⼀个bit的发送。发送完⼀个bit的数据。将bata中的值左移⼀位。在重复上⾯的过程。⼀次需要发送8个bit即要重复8次上述过程。所以,我们将该部分程序放在for循环中。实现⼀个字节(8bit)数据的发送。
利⽤该函数,我们就可以向OLED12864写⼊数据或指令,实现OLED12864的控制。
OLED12864内容显⽰
在程序的最开始,定义了⼀个⼆维数据,⽤于存放OLED12864每⼀个点上的内容。可以理解为OLED12864的显存。
uint8_t OLED_GRAM[128][8];
这个⼆维数组的定义和其采⽤的主控有关。下⾯引⽤⼀下数据⼿册的内容。
/********************************************************************************/
本屏所⽤的驱动 IC 为 SSD1306;其具有内部升压功能;所以在设计的时候不需要再专⼀设计 升压电
路;当然了本屏也可以选⽤外部升压,具体的请详查数据⼿册。SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 12864 的点阵⼤⼩。
/******************************************************************************/
驱动IC内部把OLED12864横向的64个像素点,以8个为⼀组,分成8组。每⼀组中纵向有128列。在讲解OLED12864控制时讲过,我们每次发送数据是8bit为⼀个单位进⾏传输。之所以这样设计,和内部IC驱动将其分为8组,每⼀组中有8个bit是相关的。也就是说,我们想要在OLED上显⽰内容,只需要把我们想显⽰的内容写⼊在OLED_GRAM[128][8]数组。在将数组中的值⼀次发送到驱动IC即可。接下来介绍将数组的数据发送到驱动IC的函数。
void OLED_Refresh(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte(0xb0+i,OLED_CMD);//设置⾏起始地址
OLED_WR_Byte(0x00,OLED_CMD);//设置低列起始地址
OLED_WR_Byte(0x10,OLED_CMD);//设置⾼列起始地址
for(n=0;n<128;n++)
OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
}
}
这⼀个函数采⽤for循环嵌套的⽅法实现。变量i控制当前写⼊的是第⼏组,变量n控制当前写的是第⼏列。0xb0、0x00、0x10等属于OLED12864芯⽚定义好的指令,想要了解的朋友可以⾃⾏查阅数据⼿册。
了解了OLED12864如何显⽰内容。我们就可以在OLED12864上显⽰出我们想要的内容了。但是直接修改OLED_GRAM[][]数组中的值⾮常⿇烦。所以我们可以利⽤⼀些专门的函数来帮助我们。接下来介绍⽤于实现汉字显⽰的函数。
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1)
{
u8 i,m,n=0,temp,chr1;
u8 x0=x,y0=y;
u8 size3=size1/8;
while(size3--)//⾏数控制
{
chr1=num*size1/8+n;
n++;
for(i=0;i<size1;i++)//列数控制
{
if(size1==16)
{temp=Hzk1[chr1][i];}//调⽤16*16字体
else if(size1==24)
{temp=Hzk2[chr1][i];}//调⽤24*24字体
else if(size1==32)
{temp=Hzk3[chr1][i];}//调⽤32*32字体
else if(size1==64)
{temp=Hzk4[chr1][i];}//调⽤64*64字体
else return;
for(m=0;m<8;m++)//把temp变量的值赋值给
{
if(temp&0x01)OLED_DrawPoint(x,y);
else OLED_ClearPoint(x,y);
temp>>=1;
y++;
}
x++;
if((x-x0)==size1)
{x=x0;y0=y0+8;}
y=y0;
}
}
}
由于 OLED12864内部没有中⽂字库,所以实现需要利⽤取模软件对汉字进⾏取模。再将取模的结果存放于⼀个⼆维数组中。本例程以中⽂字模存放于Hzk?数组中为例。
这个函数有四个形参,前两个代表了需要写⼊汉字的起始坐标(x,y),第三个形参num代表储存在Hzk?[][]数组中的下标。第四个形参sizel代表显⽰汉字的⼤⼩,可输⼊的值有:16、24、32、64,数字代表汉字需要占据的长宽数据。如输⼊16,则表⽰汉字的占据了横向16个,纵向16个,即16*16个像素的位置。
函数内部变量,x0、y0分别记录了汉字起始的X、Y坐标,size3由size1/8得来。⽤于控制汉字的纵向长度。这⼀点有些朋友可能不太理解,我做个简单的说明。在前⾯的讲解中我提到,OLED12864在纵向的64个点被分成了8组。每次写⼊时都是8bit的数据进⾏写⼊。所以我们将其除以8,实现控制纵向上需要写⼊多少个组。num⽤于控制需要HZK?[][]数组的下标。
该程序的实现同样使⽤了循环的嵌套,最外层的while循环的判断条件中写⼊了size3–,实现纵向控制。内部嵌套了以个for循环。⽤于写⼊每⼀祖中的每⼀列的值。汉字的⼤⼩由size1控制。所以for循环运⾏的条件为 i<size1。for循环内部的if—else语句则⽤于控制需要写⼊不同的⼤⼩时汉字所对应的数组。将数组中的值赋给变量temp。由于数组中存放的值为8个⼆进制位。所以进⼊⼀个重复8次的循
环。将temp的值和0X01相与。即如果temp中的最低位为0时,执⾏OLED_DrawPoint(x,y);,否则执⾏ OLED_ClearPoint(x,y);。完成后将temp的值右移⼀位。重复上述内容。OLED_DrawPoint(x,y)函数可简单理解为显⽰OLED12864上的(x,y)坐标对应的点。⽽
OLED_ClearPoint(x,y);代表清除该点。
除了汉字显⽰,还有另⼀个函数⽤于显⽰数字,英⽂等,原理类似,就不再分析,感兴趣的朋友可以⾃⼰去研究研究。
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1)
{芙蓉帐暖度春宵
u8 i,m,temp,size2,chr1;
u8 y0=y;
size2=(size1/8+((size1%8)?1:0))*(size1/2);//得到字体⼀个字符对应点阵集所占的字节数
chr1=chr-' ';//计算偏移后的值
for(i=0;i<size2;i++)
{
if(size1==12)
{temp=asc2_1206[chr1][i];}//调⽤1206字体
喻言十四岁在人人网上的发言
else if(size1==16)
{temp=asc2_1608[chr1][i];}//调⽤1608字体
else if(size1==24)
郭德纲妻子胡中惠照片
{temp=asc2_2412[chr1][i];}//调⽤2412字体
else return;
for(m=0;m<8;m++)//写⼊数据
{
if(temp&0x80)OLED_DrawPoint(x,y);
else OLED_ClearPoint(x,y);
temp<<=1;
y++;
if((y-y0)==size1)
{
y=y0;
x++;
break;
股票a股是什么意思
}
}
}
}
STM32PWM输出
STM32PWM简介
PWM是英⽂“ Pulse Width Modulation” 的缩写,简称脉宽调制,是利⽤微处理器的数字输出来对模拟电路进⾏控制的⼀种⾮常有效的技术。简单⼀点,就是对脉冲宽度的控制。可以简单的理解为占空⽐。即⼀段限定的时间内,低电平占据的百分⽐是多少。其作⽤主要⽤于电机调速与LED的亮度控制。
STM32PWM呼吸灯实验实现
STM32中共有三类定时器,包括基本定时器——TIM6和TIM7,通⽤定时器TIM2、TIM3、TIM4、TIM5,⾼级定时器TIM1、TIM8。除了基本定时器以外都可以⽤于PWM输出。其中通⽤定时器可以输出4路,⾼级定时器可以输出8路。
我们实验中采⽤的是TIM3。对应的引脚为PB0、PB1、PB5。其中因为PB5属于复⽤功能。所以在使⽤程序时需要先进⾏重映射。
STM32的固件库中有四个和定时器相关的库函数。其中使⽤PWM需要配置其中的两个即可: 时 基 初 始 化 结 构 体
TIM_TimeBaseInitTypeDef 、 输 出 ⽐ 较 初 始 化 结 构 体 TIM_OCInitTypeDef。具体如何配置不做介绍,感兴趣的朋友可以直接看我最后放上来的程序。
在配置完成TIM的配置之后,我们只需要直接将需要写⼊的PWM值写⼊到对应的CCR寄存器中即可。为了⽅便使⽤,采⽤函数的⽅式进⾏写⼊。
闫妮和倪妮void SetColorValue(uint8_t r,uint8_t g,uint8_t b)
{
//根据颜⾊值修改定时器的⽐较寄存器值
COLOR_TIMx->COLOR_RED_CCRx = r;
COLOR_TIMx->COLOR_GREEN_CCRx = g;
COLOR_TIMx->COLOR_BLUE_CCRx = b;
}
菜单
菜单的内容设计