本例程采用了HAL库进行项目开发(主要使用软件CubexMX和keil5),文章末尾会有代码开源,欢迎各位对文章进行指正和探讨。
硬件组成:stm32f103c8t6最小系统板;0.96寸LED12864(I2C通讯模式);智能小车12v移动电源;25GA370直流减速电机(带霍尔编码器);JDY-31蓝牙模块;L298N电机驱动模块;杜邦线若干;1个面包板;
图片如下:



1.模块可驱动两路直流电机,输出A和B各接一直流电机即可;
2.若使用12V供电,将12V供电端口及GND接上电源正负即可,同时5V供电端可以作为最小系统板的输入电源;
3.若不需要使用PWM调速,只需要控制电机正反转,则逻辑A与B跳线帽插上即可,相当于始终使能;
4.若需要使用PWM调速,需将跳线帽拔起,将使能端接上单片机IO口。(定时器IO口,PWM输出模式);
5.逻辑输入四个端口IN1、IN2、IN3、IN4接单片机四个IO口,每两个端口控制的一路电机。
温馨提示: 特别不建议新手或者资金有限的情况下,使用电机驱动模块直连成品开发板,很容易烧坏。
原因:(1) 由于电机的特性,电机在堵转或者高负载下,电流会增大,可能会影响到单片机。(2)新手玩单片机可能出现短路等情况,很容易板子冒烟;
L298N的转动逻辑图:



(1)目前市面主要分为OLED与LCD这2种屏幕;
(2)OLED自发光特性,LCD都要背光,而OLED不需要,因为它是自发光。这样同样的显示,OLED效果要来得好一些;
(3)多种接口方式:6800,8080两种并行接口方式,4线的穿行SPI接口,IIC接口方式(2线);
(4)不要接过高电压,3.3V就可以正常工作了;
(5)OLED不足之处是做大之后成本较高。
本实验采用了0.96寸OLED的屏幕(通讯方式IIC),4个接线柱(SCL,SDA,GND,VCC); IIC通讯实现方式: IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。高速 IIC 总线一般可达 400kbps 以上。
模拟IIC通讯:
I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备了。SDA 和SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。其余的 I2C 从器件都挂接到 SDA 和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C设备。
I2C 协议:(1)起始位;(2)停止位;(3)数据传输;(4)应答信号;(5)I2C 写时序;(6)I2C 读时序
I2C 写时序:

1)、开始信号。
2)、发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决
定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为
1 的话表示这是一个读操作,为 0 的话表示这是一个写操作。
3)、 I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作。
4)、从机发送的 ACK 应答信号。
5)、重新发送开始信号。
6)、发送要写写入数据的寄存器地址。
7)、从机发送的 ACK 应答信号。
8)、发送要写入寄存器的数据。
9)、从机发送的 ACK 应答信号。
10)、停止信号。
I2C 读时序:

I2C 单字节读时序比写时序要复杂一点,读时序分为 4 大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是 I2C 从器件输出要读取的寄存器值,我们具体来看一下这几步。
1)、主机发送起始信号。
2)、主机发送要读取的 I2C 从设备地址。
3)、读写控制位,因为是向 I2C 从设备发送数据,因此是写信号。
4)、从机发送的 ACK 应答信号。
5)、重新发送 START 信号。
6)、主机发送要读取的寄存器地址。
7)、从机发送的 ACK 应答信号。
8)、重新发送 START 信号。
9)、重新发送要读取的 I2C 从设备地址。
10)、读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据。
11)、从机发送的 ACK 应答信号。
12)、从 I2C 器件里面读取到的数据。
13)、主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了。
14)、主机发出 STOP 信号,停止 I2C 通信。


市场上蓝牙模块有很多,常见的JDY-xx,HC-xx等系列。其实看似高级的蓝牙功能背后就是简单的串口通讯;
USART 的全称是 Universal Synchronous/Asynchronous Receiver/Transmitter,也就是同步/异步串行收发器。相比 UART 多了一个同步的功能,在硬件上体现出来的就是多了一条时钟线。一般 USART 是可以作为 UART 使用的,也就是不使用其同步的功能。
串口通讯协议:
数据包:串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备得RXD接口,在协议层中规定了数据包的内容,具体包括起始位、主体数据(8位或9位)、校验位以及停止位,通讯的双方必须将数据包的格式约定一致才能正常收发数据。
具体如图所示:

波特率:由于异步通信中没有时钟信号,所以接收双方要约定好波特率,即每秒传输的码元个数,以便对信号进行解码,常见的波特率有4800、9600、115200等。STM32中波特率的设置通过串口初始化结构体来实现。
注意:MCU设置的波特率大小要与蓝牙APP设置的大小一致!
4、6线减速电机(带编码器)模块:
市面上电机有很多,常用的有步进电机,直流减速电机,伺服电机等等; 编码器:用来测量电机转速的仪器元件,常见的有:霍尔编码器,光电编码器等 电机的驱动原理很简单,给电压差即可使得电机转动,调速则利用PWM调节发。

编码器原理: 编码器是一种将角位移或者角速度转换成一串电数字脉冲的旋转式传感器。 编码器工作原理: 霍尔编码器是有霍尔马盘和霍尔元件组成。霍尔马盘是在一定直径的圆板上等分的布置有不同的磁极。霍尔马盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。


注意:通过判断A与B相哪一位在前,即可判断出正转还是反转
使用的MCU为stm32f103c8t6:

RCC:

SYS:

注意:Debug这里一定要设置成Serial Wire否则可能出现芯片自锁
GPIO设置:

定时TIM2用来测速与测量正转反转(计数器模式)

定时3:PWM调节

I2C:
USART1:

之后按照自己习惯生成初始化文件
自动生成的:

需要自己编写的:

I2C代码:
#include "oled.h"
#include "asc.h"
#include "main.h"
void WriteCmd(unsigned char I2C_Command)//???
{
HAL_I2C_Mem_Write(&hi2c2,OLED0561_ADD,COM,I2C_MEMADD_SIZE_8BIT,&I2C_Command,1,100);
}
void WriteDat(unsigned char I2C_Data)//???
{
HAL_I2C_Mem_Write(&hi2c2,OLED0561_ADD,DAT,I2C_MEMADD_SIZE_8BIT,&I2C_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 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 com pins hardware configuration
WriteCmd(0x12);
WriteCmd(0xdb); //--set vcomh
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_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++)
{
WriteDat(fill_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++)
WriteDat(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++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteDat(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++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
OLED_SetPos(x,y + 1);
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
}
// ????????????????,????????“??——???——????”??????ascll.h?????(????)
//???????:x:?????
// y:???(??0-7)
// begin:????????????????ascll.c???????
// num:????????
// ????“??”,??????????????????0,1,???0,??????,??:x:0,y:2,begin:0,num:2
void OLED_ShowCN_STR(u8 x , u8 y , u8 begin , u8 num)
{
u8 i;
for(i=0;i<num;i++){OLED_ShowCN(i*16+x,y,i+begin);} //OLED????
}
// 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++)
{
WriteDat(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++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
}
else {
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(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);
}
}
UART代码:
#include "uart.h"
uint8_t USART1_RX_BUF[USART1_REC_LEN];//????,??USART_REC_LEN???.
uint16_t USART1_RX_STA=0;//??????//bit15:??????,bit14~0:??????????
uint8_t USART1_NewData;//?????????1????????
extern int flag;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//????????
{
if(huart ==&huart1)
{
if((USART1_RX_STA&0x8000)==0)//?????
{
if(USART1_NewData==0x5A)//????0x5A
{
USART1_RX_STA|=0x8000; //?????,?USART2_RX_STA??bit15(15?)?1
}
else
{
USART1_RX_BUF[USART1_RX_STA&0X7FFF]=USART1_NewData;
if(USART1_RX_BUF[1] == 0x01)
{
flag = 2;
}
USART1_RX_STA++; //???????1
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//??????,??????
}
}
HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);
}
}
常规的编写如上,但是本人的MCU存在问题,单片机并未接收到预设的数据。
所以,本人项目中采用了下方代码:
#include "uart.h"
uint8_t USART1_RX_BUF[USART1_REC_LEN];//????,??USART_REC_LEN???.
uint16_t USART1_RX_STA=0;//??????//bit15:??????,bit14~0:??????????
uint8_t USART1_NewData;//?????????1????????
extern int flag;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//????????
{
if(huart ==&huart1)
{
USART1_RX_BUF[USART1_RX_STA&0X7FFF]=USART1_NewData;
USART1_RX_STA++; //???????1
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//??????,??????
if(USART1_RX_BUF[USART1_RX_STA-4] == 0xA0)
{
flag = 1;
}
if(USART1_RX_BUF[USART1_RX_STA-4] == 0x90)
{
flag = 2;
}
if(USART1_RX_BUF[USART1_RX_STA-4] == 0xD0)
{
flag = 3;
}
if(USART1_RX_BUF[USART1_RX_STA-4] == 0x88)
{
flag = 4;
}
if(USART1_RX_BUF[USART1_RX_STA-4] == 0x48)
{
flag = 5;
}
HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);
}
}
如果大家自己使用的花,可以根据自己的蓝牙APP写这段程序,有问题欢迎留言
Motor代码:
#include "motor.h"
void MOTOR_GO()
{
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1,3000);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
}
void MOTOR_BACK()
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
}
void MOTOR_STOP()
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
}
void MOTOR_UP()
{
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1,1);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
}
void MOTOR_DOWN()
{
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1,400);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
}
PID:
PID算法:
PID分为位置型和增量型
增量型即通过 u(k)-u(k-1) 从而得出式子:
公式的第一部分是比例式 是为了让值按一定比例达到目标值;
第二部分是积分值,正值,在计算的过程中往往会受到环境等一些其他因素的影响,导致值不能到达目标值;
第三部分是微分值,通常是负值,后一次偏差值往往小于前一次偏差值,目的是为了防止值增加过大,通常起一个阻碍的作用;

PID代码:
#include "pid.h"
#include "tim.h"
#include "main.h"
#include "math.h"
#include "i2c.h"
#include "oled.h"
unsigned int MotorSpeed; //È«¾Ö±äÁ¿£¬µç»úµ±Ç°×ªËÙ
int SpeedTarget = 750; //Ä¿±êתËÙ
int MotorOutput; //µç»úÊä³ö
//1.ÀûÓÃTIM2¼ÆËãµç»úתËÙ
void GetMotorSpeed(void)
{
// int CaptureNumber = (short)__HAL_TIM_GET_COUNTER(&htim2); //HAL¿âº¯Êý¼ÆËãÂö³å´ÎÊý
//
// //µç»úתËÙת»»Speed=1sÄÚµÄÂö³åÊý/44(һȦ11¸öÐźţ¬4±¶Æµ·¨)/34¼õËÙ±È
// int MotorSpeed=CaptureNumber*20/44/34*2*3.14*3;
// OLED_ShowNum(40,0,MotorSpeed,4,16);
//
// __HAL_TIM_GET_COUNTER(&htim2) = 0; //¼ÆÊýÆ÷ÇåÁã
int CaptureNumber = (short)__HAL_TIM_GET_COUNTER(&htim2); //???????
__HAL_TIM_GET_COUNTER(&htim2) = 0;
// int Speed=CaptureNumber*5/44/34*2*3.14*3;
int Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
if(Direction == 1)
{
CaptureNumber -= 65535;
}
MotorSpeed=CaptureNumber;
OLED_ShowNum(40,0,MotorSpeed,4,16);
HAL_Delay(100);
OLED_CLS();
// __HAL_TIM_GET_COUNTER(&htim2) = 0;
}
//2.ÔöÁ¿Ê½PID¿ØÖÆÆ÷£¨PID³£¼û·ÖΪλÖÃPIDºÍÔöÁ¿Ê½PID£©
int Error_Last,Error_Prev; //ÉÏ´ÎÎó²î£¬ÉÏÉÏ´ÎÎó²î
int Pwm_add,Pwm; //PWMÔöÁ¿,PWMÕ¼¿Õ±È
int Kp = 5, Ki = 3, Kd = 1;//PIDË㷨ϵÊý£¬¸¡µãÀàÐÍ£¬Ð¾Æ¬¼ÆËãÄÜÁ¦Ò»°ãʱ½¨ÒéÕûÐÍ£¬»òÕß*1024
int SpeedInnerControl(int Speed,int Target) //ËÙ¶ÈÄÚ»·¿ØÖÆ
{
int Error = Target - Speed; //Îó²î = Ä¿±êËÙ¶È - ʵ¼ÊËÙ¶È
Pwm_add = Kp * (Error - Error_Last) + //±ÈÀý
Ki * Error + //»ý·Ö
Kd * (Error - 2.0f * Error_Last + Error_Prev); //΢·Ö
Pwm += Pwm_add; //Êä³öÁ¿=ÔʼÁ¿+ÔöÁ¿
Error_Prev = Error_Last; //±£´æÉÏÉÏ´ÎÎó²î
Error_Last = Error; //±£´æÉÏ´ÎÎó²î
if(Pwm > 4999) Pwm = 3000; //ÏÞÖÆÉÏÏÂÏÞ£¬·ÀÖ¹PWM³¬³öÁ¿³Ì
if(Pwm <-4999) Pwm =-3000;
return Pwm; //·µ»ØÊä³öÖµ
}
//3.µç»úתËÙÓë·½ÏòµÄº¯Êý£¨PID¿ØÖÆ£©
void SetMotorVoltageAndDirection(int Pwm)
{
if(Pwm < 0) //Èç¹ûPWMСÓÚ0
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
Pwm = (-Pwm); //PWMÖ»ÄÜÈ¡ÕýÖµ£¬Èç¹ûΪ¸ºÊý£¬Ö±½ÓÈ¡·´
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, Pwm); //PWMµ÷ËÙ
} else
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, Pwm); //PWMµ÷ËÙ
}
}
void ModePID()
{
GetMotorSpeed();
MotorOutput = SpeedInnerControl(MotorSpeed,SpeedTarget);
SetMotorVoltageAndDirection(MotorOutput);
}
主函数代码:
while (1)
{
switch(flag)
{
case(1):MOTOR_GO();break;
case(2):MOTOR_BACK();break;
case(3):MOTOR_STOP();break;
case(4):MOTOR_UP();break;
case(5):ModePID();break;
default:break;
}
/* USER CODE END WHILE */
if(flag != 5)
{
int CaptureNumber = (short)__HAL_TIM_GET_COUNTER(&htim2); //???????
__HAL_TIM_GET_COUNTER(&htim2) = 0;
// int Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
//µç»úתËÙת»»Speed=1sÄÚµÄÂö³åÊý/44(һȦ11¸öÐźţ¬4±¶Æµ·¨)/34¼õËÙ±È
// int Speed=CaptureNumber*5/44/34*2*3.14*3;
int Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
if(Direction == 1)
{
CaptureNumber -= 65535;
}
int Speed=CaptureNumber;
OLED_ShowNum(40,0,Speed,5,16);
HAL_Delay(100);
OLED_CLS();
}
int Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
OLED_ShowCN_STR(0,0,0,3);
// OLED_ShowNum(40,0,Speed,4,16);
OLED_ShowStr(90,0,"cm/s",2);
OLED_ShowCN_STR(0,3,3,2);
if(Direction==0)
{
OLED_ShowCN_STR(40,3,5,2);
}
if(Direction==1)
{
OLED_ShowCN_STR(40,3,7,2);
}
// HAL_Delay(1000);
// OLED_CLS();
/* USER CODE BEGIN 3 */
}
蓝牙APP源代码以及技术论文:链接:https://pan.baidu.com/s/1-rbicxuyLVCq6rglCWcJTg
提取码:huzm
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新rubygems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
在我的Character模型中,我添加了:字符.rbbefore_savedoself.profile_picture_url=asset_path('icon.png')end但是,对于数据库中已存在的所有角色,它们的profile_picture_url为nil。因此,我想进入控制台并遍历所有这些并进行设置。在我试过的控制台中:Character.find_eachdo|c|c.profile_picture_url=asset_path('icon.png')end但这给出了错误:NoMethodError:undefinedmethod`asset_path'formain:O
当我进入Rails控制台时,我已将pry设置为加载代替irb。我找不到该页面或不记得如何将其恢复为默认行为,因为它似乎干扰了我的Rubymine调试器。有什么建议吗? 最佳答案 我刚发现问题,pry-railsgem。忘记了它的目的是让“railsconsole”打开pry。 关于ruby-on-rails-带有Pry的Rails控制台,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/question