二、stm32+spi驱动lcd+电机数据采集及显示
erer本实验内容较多,大体上可以分为3个部分,分别是:SPI驱动LCD,定时器输入捕获,以及数据正确显示,其中第一和第三个部分的内容为重点。
因为之前没有学过SPI通信所以会顺带着学一下它的通信方式。
一、LCD屏幕模块介绍
首先这是一个市面上常用的一种LCD屏幕,虽然不大,但是对于做简单的显示任务而言,已经足够了。除了这一款常用的LCD屏以外,还有一种0.96寸的OLED屏幕,不过那款屏幕需要用IIC方式驱动,而且屏幕面积也实在太小,个人不喜。
不过其实那块屏幕其实相较本款LCD而言其实有很明显的优势:引脚数量少。因为整个实验是以STM32F103C8T6为核心来开发,这款板子引出来的引脚数量少,所以那款OLED屏幕其实更适合本实验。
1.1 SPI通信模式介绍
串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置
成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。
和usart相比:一般最少需要4根数据线||通信距离一般较短||同步通信||通信速度快||多设备通信
与USART相似,如图中标红部分,在芯片内部同样有RX、TX缓冲区,移位寄存器。
不同之处在于,串口一般为低位先行(LSB),也即是说移位寄存器会先将低位的数据移出给IO,而SPI则一般为高位先行(MSB),当然也可以如图中所示利用LSBFIRST控制位改变这种模式。其次,还有一个非常显著的不同就是,SPI的RX和TX缓冲寄存器使用同一个移位寄存器,那么对于主设备而言,将要发送的一帧数据的最后一位也即是第0位后边紧跟的则是接收数据的最高位(也即是第7位)。
如图中黄色框中所示,一般SPI通信模式需要使用以上四个引脚,MOSI和MISO是设备的接收或发送引脚,这里和串口通信的不同之处在于,对于串口而言是设备1的TX和RX引脚与设备2的RX和TX引脚相连,而在这里,则是设备的同名引脚相连。对于主设备而言,MISO(便捷记忆:M-Master,SO- 收,主设备接收)是主设备的接收引脚,而对从设备则是发送引脚,那么MOSI对于主设备就是发送引脚,相应地它也是从设备的接收引脚。而为了提供多种通信模式(因为SPI的特性,SPI通信模式易受到干扰,所以常用于短距离传输,如单片机与传感器或者屏幕之间进行通信),如只提供MISO或者只提供MOSI引脚,对于某个特定的设备而言,只需要进行数据的传输或者接收。
SCK引脚则是由从设备输出时钟信号给从设备的引脚,既为同步通信方式,则需要统一设备的时钟。
NSS引脚为片选引脚,类似于玉玺的作用,多设备之间进行连接时,如何从软件上得知哪个设备是主设备,一般就是通过NSS引脚决定,当某个设备的NSS置低电平后,该设备变为主设备。这是基于当两个设备的NSS引脚相连时的状态,但一般可以直接将主设备的NSS引脚接地,而将从设备的的NSS印角拉高。此外若该引脚由软件进行配置,当传输数据时,对该引脚进行高低电平切换,会导致数据或命令发送失败,若还需继续发送,则需等待NSS引脚电平进入正确状态后重新发送被失败的数据。
另外需要清楚的是CPOL和时钟相位 CPHA,这两位共同决定了主从设备进行数据传输时,如何鉴别是否是有效数据,在此之前,还需要知道一个事情,这里所说的时钟实际上不是学习其他外设时所以为的时钟(由某个振荡器发出),这里的时钟更类似于信号(可以由SPI外设的所属的专用引脚发出,也可以由用户指定的引脚发出),在主设备没有发送数据时,SCK引脚一般保持在一个状态(CPOL=0时,SCK引脚的空闲状态为低电平),当开始进行数据时,SCK的电平状态开始改变,这里注意的是SCK的状态也并不是保持在一个状态(发送数据时),而是不停的改变。而CPHA位决定的是当SCK电平从空闲状态开始改变时,从第一个边沿开始(CPOL=0时,那第一个边沿就应该是上升沿)的下一个边沿对应数据的某一位(CPOL=0,CPHA=0,则SCK引脚电平开始改变时的下降沿将对应着数据帧的每一位)。
那么这里可以得到启发就是,当自行用其他引脚来进行软件模拟SPI时序时,(比如模拟CPOL=1,CPHA=0)平时应该保持SCK引脚为低电平,当要发送数据时,应先改变SCK为高电平再利用MOSI引脚置高或置低来模拟数据帧x位为1或0的数据内容,同时当这一位发送完成后应当重新置SCK为低电平,这用GPIO_SET或GPIO_RESET函数很容易做到。
STM32内置的SPI外设可以通过相应的控制寄存器中对应位来设置数据传输的波特率以及数据帧格式,当主设备设置好这些参数后,从设备也会与之对应,如即使改变从设备的波特率参数,但实际还是会以主设备的波特率参数为准。
如以STM32的内置SPI外设进行通信(用户自己不使用软件模拟SPI时序),也需要按照一定步骤配置参数,最后使能SPI,这一点与使用其他外设一样,相应地,也可以对某些位进行设置从而使能中断。
以配置主设备为例:
1. 通过SPI_CR1寄存器的BR[2:0]位定义串行时钟波特率。
2. 选择CPOL和CPHA位,定义数据传输和串行时钟间的相位关系(见图212)。
3. 设置DFF位来定义8位或16位数据帧格式。
4. 配置SPI_CR1寄存器的LSBFIRST位定义帧格式。
5. 如果需要NSS引脚工作在输入模式,硬件模式下,在整个数据帧传输期间应把NSS脚连接
到高电平;在软件模式下,需设置SPI_CR1寄存器的SSM位和SSI位。如果NSS引脚工作
在输出模式,则只需设置SSOE位。
6. 必须设置MSTR位和SPE位(只当NSS脚被连到高电平,这些位才能保持置位)。
1.2模拟SPI时序驱动LCD屏幕简单介绍
而以软件模拟SPI时序则有些不同,以驱动LCD屏幕为例:
1.2.1 选择自定义引脚连接LCD相应引脚
假设我便选择以PA0-5,六个引脚来控制LCD屏幕。
首先应先对相应的GPIO初始化
void LCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能A端口时钟
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5);
}
虽然ST7735S芯片也接出了许多引脚,但是对于不同地LCD厂家有自己的设计逻辑, 因为我使用的LCD是采用的4引脚(SDA\SCL\DC\CS)SPI时序通信,所以应参考如下图所示的方式进行数据传输。
(采用的网站上的机翻,还算够用,后文我会给出ST7735S地手册原文和译文PDF)
图中所示,可以得知这款LCD屏应采用的是CPOL=1,CPHA=1的模式进行数据传输的。
这里可能一开始有点疑惑,似乎这里更与CPHA=0的模式符合,也即第一个边沿开始捕获数据,而仔细查看在SCL线附近标注的5个时间Tdcs、Tsds、Tshr、Tsdh、Tdch。
虽然STM手册上写的是在边沿上进行数据捕获,但是毕竟边沿的电平是不稳定的,同时在接收设备感知到边沿时,也需要一个准备时间,所以实际上数据捕获应当是边沿之后的一段SCL电平相对稳定的阶段进行数据读取。
正如五个时间的解释:
1.Tdcs:命令设置时间
2.Tsds:数据设置时间
3.Tshr:SCL读取数据或命令保持时间
4.Tsdh:数据保持时间
5.Tdch:命令保持时间
看时序也可大致得知,传输数据时,各引脚的电平改变时间(各个控制或数据引脚的设置时间顺序)为:
先改变SCL为低电平——–再改变CS引脚为低电平———再设置DC引脚(或高或低)———然后设置SDA引脚的电平(或高或低)———-最后设置SCL引脚为高电平
再SCL引脚为高电平后,才开始读取DC和SDA上的数据(或者说电平状态)。
那么可以得知数据传输的函数应当如下:
void LCD_SDA_Write_Byte(uint8_t data)
{
uint8_t i;
LCD_CS_Low();
for(i=0;i<8;i++)
{
LCD_SCL_Low();
if(data&0x80) //从高位读取MSB
{
LCD_SDA_High();
}
else
{
LCD_SDA_LOw();
}
LCD_SCL_High(); //再这个SCL置高后,从设备已经做好了准备,然后才开始读取SDA的数据和DC引脚的电平状态。
data<<=1; //不断左移,不断读取后一位的数据
}
LCD_CS_High();
}
函数里SCL和CS开始置低的时间顺序和上文说的是相反的,一方面是为了方便在for循环中不断更新SCL为低电平,一方面读手册的后文可知:CS为高电平时为初始化状态,SCL和DC引脚的状态不起作用,但是也说到,在CS为高电平时也可以改变SCL和DC的状态。
将通俗点就是:我CS虽然为高时,我不认可你的数据,但是我不阻止不作废你们所做出的改变,并且再我改变为低电平后(初始化后),你们先前已经改变好的电平状态,我也还是认可的。
1.2.2 命令写入模式
因为LCD数据传输仅有一条MOSI(SDA)引脚,并且采用4引脚控制方式,所以命令写入方式需要参考上图,每条数据帧仅包含8位,DC置高位时,则数据内容指向命令(有的命令有参数,有的无需参数),置低时则为数据(这里可能时显示信息数据也可能时命令的参数数据)。
此处还提到,当片选引脚置高时,为初始化状态,SCL和SDA的信号或脉冲是无效的,这也可以和上文提到的NSS引脚置高时可以打断数据传输对应上。
以RDDST命令为例,可以看到一个完整的命令读取时序:
先传输8位命令数据,命令的最后一位数据发送时,同时也开始读取DC引脚的命令,这个命令标识是下一条数据是命令/数据标识,同时也不用改变CS的电平状态,紧接着发送数据即可(这里的数据是前边命令的参数)。
1.2.3 颜色命令写入模式
这里解释几个概念,65k色表示一个像素点需要16bits,2Bytes个数据位来表示其颜色,2^16=65536,也即65k.而其中前5位表示Red的信息,中间6位表示Green的信息,后5位表示Blue的信息,由这3原色的信息共同表示一个像素点的颜色信息。
这图里可能有一个错误:IM2=“1”,而其实在源文档的Page 15中明确说了IM2=0时才采用串行接口进行数据传输。
而IM0 和IM1的位共同决定了并行接口数据传输时具体是多少位,IM2决定的是采用串行或并行方式传输数据。,这几个接口由引脚直接高低电平决定,应该是不需要软件配置的。
3AH这个命令,决定的是每个像素点(Pixel)由多少位颜色数据位来表示,而3AH的参数为011(二进制)时即等于03时,表示的是4K色,而为101(二进制)即为5时才是65K色,这里是我截图不小心截错了。
1.2.4 显示区域设置
这是正常显示模式,当GM[1:0]=11时,就决定了要使用128*160的分辨率,如果要使用这全部的区域进行显示,那么Mx/y/z=0,SMx/y=0,即可。
而通过对PSL[7:0]和PEL[7:0]指定某些值可以设置在128*160区域中的某一块区域为有效显示区域。如图所示当PSL[7:0]=04h,PEL[7:0]=9Bh时,则指定了显示区域的行起始指针为04,也即图中的4x,结束指针为9B,总共有效的行数为9Bh-04h+1=98h=152行。
根据这里所言,在要往RAM中写入颜色信息,即要显示内容之前需要先划定一片区域,这个区域是不允许超出上面所指定的有效显示区域的。区域位置的指定由XS、XE,YS、YE决定。
1.2.5 寻址方式
寻址方式这里,依据MV(实际上一共由个参数决定)的值不同有不同的方式,当它为1是,采用的是自增模式,当像素点的行或列指针指向地址的最后一位的下一位时,指针需要重置为0.
下图是一部分的寻址方式(限制篇幅考虑,只给出一部分,具体参考原文的Page 69)
1.2.6 滚动显示功能
这里还有一个非常有趣的功能,滚动显示。
这里因为用的机翻,所以不太好,简单概括就是,像指定部分区域显示功能一样,采用行参数来指定,这里同样使用行参数来指定。
具体的命令由33h和37h指定,由图可以看出图片既可以是向上滚动也可以是向下滚动。
TFA、BFA、VSA是三块区域,其中TFA(Top Fixed Area)和BFA(Buttom Fixed Area)指顶部和底部保留的区域,此区域内不显示内容。
可以看到TFA、BFA、VSA的数值都等于其行数,SSA为起始地址,即一开始图片的顶部应该在第6行显示出来。MADCTR控制的是显示的方向之类的。由这些位进行控制可以灵活的显示一个简单的动画效果。比如一个小游戏的场景滚动之类的。
比如上图,当 MADCTR=1时,改变了方向,看图中SSA依然等于6,然而它却在底下,并且也改变了滚动的方向。
除此外还有一些其他的滚动方式,具体参看原文档Page 71,总之是非常有意思的。
1.2.7 初始化操作步骤
这里写了复位操作的时序,这里其实并没有太看懂。一开始RES为高电平,然后置低又置高又置低。但是看表里的标注:Tresw——重置脉冲持续时间(RES置低电平时间)最小时间为10us,但是第一段置低时间低于5us,也就是说第一段置低电平是不能达到有效重置目的的。那么这一段时间可能是为了消除毛刺或者什么的之类的,猜的。第二段置低电平才是真正的有效重置(有效复位),这个时间之后,将RES置高电平时,芯片会开始进行一些初始化操作,这段时间保持不超过120ms,在这个时间之后才建议开始写入命令或操作。
那么激活LCD的一个大致步骤应当是:
先进行复位及等待初始化完成(各可以给较长的一段时间如120ms)。
在这个时间之后再进行一些如软件复位(SWREST)退出休眠时间的操作(SLEEPOUT)。
随后进行显示参数(如之前提到的MDCTR,MV之类的)和显示位置的操作。
最后开启显示内容功能(DisplayOn)。
显示测试 :OK
对于大小不一致的屏幕,初始化的代码基本可以通用,但是效果不是很好,比如将代码从128*128移植到这块*160的屏上会出现闪屏情况。
初始化代码如下
void LCD_Init(void)
{
LCD_GPIO_Init();//初始化GPIO
LCD_RES_Clr();//复位
HAL_Delay(100);
LCD_RES_Set();
HAL_Delay(100);
LCD_BLK_Set();//打开背光
HAL_Delay(100);
LCD_WR_REG(0x11); //Sleep out
HAL_Delay(120); //Delay 120ms
//------------------------------------ST7735S Frame rate-------------------------------------------------//
LCD_WR_REG(0xB1); //Frame rate 80Hz
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x35);
LCD_WR_DATA8(0x36);
LCD_WR_REG(0xB2); //Frame rate 80Hz
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x35);
LCD_WR_DATA8(0x36);
LCD_WR_REG(0xB3); //Frame rate 80Hz
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x35);
LCD_WR_DATA8(0x36);
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x35);
LCD_WR_DATA8(0x36);
//------------------------------------End ST7735S Frame rate-------------------------------------------//
LCD_WR_REG(0xB4); //Dot inversion
LCD_WR_DATA8(0x03);
//------------------------------------ST7735S Power Sequence-----------------------------------------//
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0xA2);
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x84);
LCD_WR_REG(0xC1);
LCD_WR_DATA8(0xC5);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x00);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x8D);
LCD_WR_DATA8(0x2A);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x8D);
LCD_WR_DATA8(0xEE);
//---------------------------------End ST7735S Power Sequence---------------------------------------//
LCD_WR_REG(0xC5); //VCOM
LCD_WR_DATA8(0x0a);
LCD_WR_REG(0x36);
if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x08);
else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC8);
else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x78);
else LCD_WR_DATA8(0xA8);
//------------------------------------ST7735S Gamma Sequence-----------------------------------------//
LCD_WR_REG(0XE0);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x1C);
LCD_WR_DATA8(0x10);
LCD_WR_DATA8(0x18);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x2C);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x2F);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x10);
LCD_WR_REG(0XE1);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x1C);
LCD_WR_DATA8(0x10);
LCD_WR_DATA8(0x18);
LCD_WR_DATA8(0x2D);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x23);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x26);
LCD_WR_DATA8(0x2F);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x10);
//------------------------------------End ST7735S Gamma Sequence-----------------------------------------//
LCD_WR_REG(0x3A); //65k mode
LCD_WR_DATA8(0x05);
LCD_WR_REG(0x29); //Display on
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
}
有一些命令在手册上说是由厂家设置,所以这块基本无法改动,只能稍作修改。
二、定时器输入捕获电机编码值
2.1 霍尔编码器检测原理
一般而言编码器可以按照监测原理分为霍尔编码器和广电编码器,但大致上最后都是输出一个脉冲数结果,即电机旋转一圈一共输出N个脉冲。这个参数厂家会给出,一般将其描述为线数。
除此外,还可以根据编码计数原理上分还可以分为绝对式编码器和增量式编码器,电机停留在某个位置时,所输出的脉冲数是一个定值时,这种编码器为绝对式编码器,意为可以输出电机轴的绝对位置,而增量式编码器则不会输出,它只会输出电机轴的转动增量。
而因为电机通常的输出转矩会比较低,因此在出厂时厂家会给配有一个电机减速器(假设减速比为i),降低转速从而增加输出转矩。同时,因为最终是使用减速器的输出轴作为最终的整个机构的一个输入轴,所以此时轴转动一圈所输出的脉冲数N'=N*i,加入电机和相应IC的精度理想,那么电机的分辨率为360°/N'。
如上图所示,电机轴和码盘轴同轴,而电机轴和减速器输出轴之间一定的减速比关系(一般内部为行星轮机构),每当码盘旋转一周,霍尔元件A和B都会输出相同数量的脉冲。
行星轮减速器机构(左侧是减速器部分,右侧是电机部分)
而假设电机的旋转方向(从减速器往编码器方向看)为顺时针,对于码盘上的某一个磁极(从电脑外向电脑看,磁极在电机旋转开始靠内一侧)来说,它应该会先经过A后经过B,如果假设当该磁极经过霍尔元件时,霍尔元件会输出一个高电平,而没有磁极经过时,霍尔元件输出低电平。而码盘上均匀地分布着13个磁极,那么电机旋转一周(这里不是相对减速器的输出轴,而以电机实际的轴来说)霍尔元件最终会输出13个方波脉冲。
那么最终A相和B相线输出的脉冲信号应当如下图所示
当在A相线上检测到上升沿,启动检测,当在A为高电平期间检测到了B的上升沿,则可以在软件代码上定义当前转向为顺时针(或正转方向),如果情况恰好反过来,那么定义为逆时针(或反转方向)。 所以后续检测电机正反转敲代码的思路就是如此。
而电机的转速测定一般有两种方法:测周法和测频法。测周法适用于低速转动的电机,而测频法适用于高速转动的电机。
测周法原理和测频法原理:
测频法:在一定的时间段T内,计算电机总共输出的脉冲数N,由于电机此时是高速转动,即使计时单位和脉冲数有一定的误差,最后得到的电机转速n误差并不会很大。
从公式理解: n=N*60/(T*Nr) n的单位是转/每分钟 ,Nr是一转对应的脉冲数。
当N很大如6000,而误差Nerror比如说为2、3这种,对实际结果影响并不会很大
测周法:计算相邻两个脉冲之间所经历的时间,由于电机转速慢,所以两个相邻脉冲之间所经历的时间会相对较长,当单片机的计时单位足够大时,其中存在的一些误差对最后结果造成的影响有限。
从公式理解:n=60*Tr/tn*13 Tr代表1秒时间内,单片机滴答计数器的计数,这里是单片机的时钟频率,tn是两个相邻上升沿之间,滴答计数器的计数,13代表码盘中所有的磁极数(这里只是假设),当电机有减速比时,分母还应该乘减速比。
当Tr很大时,定时器一般为MHz,计数误差对最后的转速结果影响很小。
2.2 STM32编码器模式
进入的TI的检测信号经过滤波器后输出为TIxF信号,信号中的上下边沿都会被边沿检测器检测,此时上下沿信号都会触发一定的事件(可以选择不触发),当上下边沿信号经过极性选择后,变成TI1FP1信号,同时在TI2上也会生成TI2FP2。
生成的TI1FP1和TI2FP2进入定时器的编码器接口。这里注意只有TI1和TI2通道的信号才可以进入编码器接口,而3和4是不可以的,因为之前使用了定时器2的输出通道2作为电机的PWM输出通道,所以后续代码需要做一些调整。
从图中可以看出,当TI1比TI2超前1/4个周期时,TI1和TI2的任何一次边沿信号都会触发计数,当1通道超前2时,计数器向上计数。当顺序相反时,计数器向下计数。
这里需要将ARR设置为电机旋转一周所转过的脉冲数,当计数值等于ARR寄存器的值时,代表电机已经旋转完了一周,此时计数器会被自动清零,这也就实现了绝对式编码器的目的。
需要得到电机的旋转速度时,可以先用HAL库内置的函数得到T1时刻和T2时刻的时间间隔,并且再在T1时刻清零计数值的值,那么电机的旋转速度用T2时刻的计数值除以时间间隔就可以计算。
这里需要注意的是ARR计数器的最大值为65535,如果在得到的计数值不进行分频,那么意味着电机一圈最大的脉冲数将是16383,这里需要把传动比计算在内。
而需要得到电机的转动方向,只需要读取CR1寄存器的DIR值即可。
2.3 代码
修改:将PWM输出引脚改为PA6引脚(定时器3的通道1)。
电机的编码器A和B相分别接入定时器2的通道1和2。
LCD的SCL至BLK引脚分别按对应顺序接入PB4到9。
思路:将获得的脉冲数/相见隔时间 用作电机转速(并不严格),将该值在LCD屏上显示。
头文件中声明两个全局变量:电机旋转方向和电机转速
extern uint8_t MOTOR_DIRECTION;
extern uint16_t MOTOR_SPEED;
用CubeMx生成基础配置后
编码器初始化函数
/*定时器编码器模式初始化*/
void Encoder_Init(void)
{
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_2);
}
获取电机旋转方向
/*获得电机的旋转方向*/
uint8_t Motor_Get_Direction(void)
{
/*返回定时器CR1寄存器的DIR值*/
MOTOR_DIRECTION=__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
return MOTOR_DIRECTION;
}
获取电机旋转速度,这里只是做一下测试,正常应当再用一个定时器没隔一段时间测电机旋转速度。
/*获得电机的速度*/
uint32_t Motor_Get_Speed(void)
{
uint16_t Encoder_Pulse_Number=0;
static uint32_t LastTime=0;
static uint32_t Time=0;
/*获得当前时刻us时间*/
LastTime = TIM_Get_us();
/*清空计数器数值*/
__HAL_TIM_SET_COUNTER(&htim2, 0);
/*获得1ms后的us时间以及编码器计数值*/
HAL_Delay(1);
Time = TIM_Get_us();
Encoder_Pulse_Number=__HAL_TIM_GET_COUNTER(&htim2);
/*编码值除以us级时间 获得速度 后续根据精度不同获得不同数值*/
MOTOR_SPEED = (Encoder_Pulse_Number*1000)/(Time-LastTime);
return MOTOR_SPEED;
}
STM32us级时间获取
uint32_t Get_Current_Micros(void)
{
uint32_t ms = HAL_GetTick();
//获取嘀嗒定时器重装载值
const uint32_t tms = SysTick->LOAD + 1;
//获取当前滴答定时器计数值
__IO uint32_t u = tms - SysTick->VAL;
//返还对应的值
return (ms * 1000 + (u * 1000) / tms);
}
uint32_t TIM_Get_us(void)
{
return (Get_Current_Micros());
}
main函数中进行简单显示测试
while (1)
{
/* USER CODE END WHILE */
/*TB6612电机测试部分*/
TB6612_MOTOR_DIRECTION_FORWARD();
//TB6612_MOTOR_DIRECTION_BACKWARD();
HAL_Delay(1);
/*设置电机速度,这里预分频器值71,ARR值999*/
TB6612_MOTOR_SET_SPEED(200);
/*LCD屏幕显示测试部分*/
/*显示电机速度和旋转方向*/
/*MSPD=Motor Speed*/
LCD_ShowString(0,0,"MSPD:",RED,WHITE,16,0);
/*MDIR=Motor Direction*/
LCD_ShowString(0,16,"MDIR:",RED,WHITE,16,0);
LCD_ShowIntNum(40,0,Motor_Get_Speed(),11,RED,WHITE,16);
LCD_ShowIntNum(40,16,Motor_Get_Direction(),11,RED,WHITE,16);
HAL_Delay(200);
/* USER CODE BEGIN 3 */
}
测试OK
下篇进行优化,这篇类似做笔记,篇幅太长。
ST7735s原文以及译文资料如下:
通过网盘分享的文件:ST7735S驱动IC数据手册(1).pdf等2个文件
链接: https://pan.baidu.com/s/1h4wNIgrzMr6TStXGaMwsyw?pwd=s56k 提取码: s56k
作者:嵌入式码喽