一、引言
关于工作室未来可能需要发展一些学习硬件的同学,在我本人接触一年多的硬件分享一下个人的学习路线,以及推荐的学习方式,希望能够帮助工作室的同学快速入门嵌入式,能够学习到一些硬件知识,在学习中也能不那么枯燥,做一些小的实物。
二、嵌入式学习的所需技能
1、C语言基础
学习C语言基础内容,具备一定编程能力。C语言的学习是一个持续性的过程,需要持之以恒的学习。学习C语言的途径有很多,无论是在b站还是在网上找到的一些资源都可以先学会简单的C语言的语法知识。前期的简单学习即可完成一些小的项目。
C语言编写嵌入式也有以下优势:
低级别控制:嵌入式系统通常需要对硬件资源进行直接控制,如寄存器、内存映射等。C语言提供了指针和位操作等低级别的编程功能,使得程序员能够更好地进行底层硬件控制,从而优化系统性能和资源利用。
跨平台支持:C语言的标准库几乎可以在任何嵌入式系统上运行,具有很强的跨平台特性。这意味着你可以在不同的嵌入式平台上复用相同的C代码,减少开发和维护的工作量。
资源效率:嵌入式系统通常资源有限,包括内存和处理器速度。C语言是一种相对轻量级的语言,不像高级语言那样消耗大量内存和处理器资源,因此能更好地适应嵌入式系统的资源限制。
高性能:C语言直接映射到机器指令,允许对程序进行高度优化,从而获得更好的执行速度。对于嵌入式系统,这点非常重要,因为有时候需要实时响应和高性能的处理。
可访问硬件特性:嵌入式系统需要直接与硬件进行交互,读取传感器数据、控制外设等。C语言提供了丰富的位操作、指针和结构体等功能,能够方便地访问和操作硬件特性。
大量资料和支持:C语言在嵌入式领域被广泛应用,有大量的资料、工具和社区支持。你可以很容易地找到相关的书籍、教程、示例代码和解决方案,有助于学习和解决实际问题。
2、单片机基础
学习嵌入式系统时学习单片机基础是非常重要的,因为单片机是嵌入式系统中最常见和基础的组成部分。以下是学习单片机基础的一些重要原因:
嵌入式系统常见平台:单片机是嵌入式系统最常见的处理器平台之一。它们在许多应用领域中得到广泛应用,如家电、汽车、工业控制、医疗设备等。掌握单片机基础为你在不同行业中参与嵌入式系统开发提供了必要的基础。
硬件编程理解:学习单片机基础可以让你更好地理解硬件编程和嵌入式系统的运作原理。通过单片机,你可以直接与硬件进行交互,学习如何读取和控制外设,设置时钟和定时器等。这对于开发复杂的嵌入式应用至关重要。
低成本开发:相比于一些高级嵌入式处理器平台,单片机是一种低成本的解决方案。学习单片机基础,你可以以较低的成本进行嵌入式系统的原型开发和测试,这对于学习和实验是非常有利的。
三、单片机学习路线
单片机的种类有很多,有简单易学习的,有复杂难使用的。在我们实际做项目中要考虑到使用成本,开发成本,体积,性能等参数再决定使用什么单片机,不同单片机设计的时候侧重点不同,所以会使用较多种类的单片机是很重要的,为了让大家能够快速入门,我推荐以下单片机学习路线。
1、ARDUNIO
可以将其定义为一种开源硬件和软件平台,用于创建嵌入式系统和物联网应用的单板微控制器。Arduino项目起源于2005年,由意大利的Arduino LLC创建。它的设计初衷是为了让爱好者、学生和创造者能够以简单易懂的方式快速构建各种互动项目。
Arduino板由一个单片机(通常是Atmel AVR系列)和一些输入输出引脚组成。这些引脚可用于连接各种外部电子元件,如传感器、执行器、通信模块等。通过编程,用户可以控制这些引脚的状态和交互,从而实现各种功能和应用。
以上是对arduino的介绍,在学习arduino时,我们通常使用c语言在arduino ide编译器中对arduino编程,学习一些专用的语句即可实现一些例如点亮一个led灯。学习这款单片机,可以快速实现一些项目的制作,提高学习者的学习兴趣。在学习arduino中可以快速配置例如GPIO,ADC,UART等等外设,减少了学习成本。
2、STM8
有很多人建议新手优先学习51单片机,但是这里我更推荐STM8单片机,虽然说51和STM8单片机都是8位机,但是都是为了后期学习STM32而做准备,而STM8单片机不仅能让学者学会8位机的使用,更能学会寄存器操作,学会操作寄存器从而可以帮助学者了解单片机的底层,在后期的使用开发中能更好的解决问题。学习他的途径我建议是B站上龙顺宇老师讲的课直接搜索即可。
3、STM32
STM32是STMicroelectronics(意法半导体)推出的一系列32位ARM Cortex-M处理器核心的微控制器。STM32系列广泛应用于各种嵌入式系统和物联网应用,涵盖从低功耗、高性能到高度集成的产品线,适用于多种应用领域,包括工业控制、汽车电子、智能家居、医疗设备、消费类电子产品等。
ARM Cortex-M内核:STM32系列单片机采用32位ARM Cortex-M内核,提供高性能和丰富的处理能力。
主频:不同型号的STM32单片机具有不同的主频范围,从几十MHz到几百MHz不等。
存储器:STM32单片机配备闪存(Flash)存储器,用于存储程序代码。此外,还可以包含SRAM和EEPROM用于数据存储。
I/O接口:提供多个通用输入输出引脚(GPIO),用于连接外部器件和实现数据交换。
定时器和计数器:STM32单片机支持多个定时器和计数器,用于实现各种定时、计数和PWM(脉冲宽度调制)功能。
通信接口:STM32单片机通常配备多种常用的通信接口,如UART、SPI、I2C、CAN等,用于与其他设备进行通信。
ADC和DAC:通常配备模数转换器(ADC)和数字模拟转换器(DAC),用于模拟信号的采集和转换。
外设接口:STM32单片机提供丰富的外设接口,如USB、以太网、LCD控制器、SD卡接口等,用于实现更复杂的应用。
低功耗模式:STM32单片机支持多种低功耗模式,以降低功耗,延长电池寿命,适用于依赖电池供电的应用。
开发生态系统:STMicroelectronics为STM32系列提供了丰富的开发生态系统,包括开发板、集成开发环境(如STM32CubeIDE)、编程器等工具,以帮助开发者快速上手和开发应用。
四、一些小项目制作
在硬件学习中光学习是没有意思的也不能保证你能学会记住,在项目中快速成长是非常有必要的,制作出来的实物不仅可以使学者获得成就感,还可以积累实践经验。
1、arduino寻迹小车
经典寻迹小车,总造价可能在200-400之间,在第一阶段学习完arduino即可开始此实验,
需要购买:
2-3块18650电池,电池盒,灰度传感器(3-5个),自制车模,电机*2,万向轮,l298n电机驱动模块,面包板(选用)。
动手将车组装,根据网上资料可以编写一个简单的二值化的循迹小车。代码逻辑非常简单,网上随便找找就有。如果有同学想进阶学习一些算法可以接触pid算法,看看什么是pid,pid怎么使用。我这里放一些我当时参加寻迹小车比赛的代码,仅供参考
在这里插入代码片/*函数:
误差 传感器输入(模拟量) 寻迹*/
#include <Servo.h>
#define YY 8
#define LED 2
#define IN1 6//左电机pwm
#define IN2 9
#define IN3 10 //右电机pwm
#define IN4 11
#define HDL A3 //左传感器
#define HDM A4 //判断传感器
#define HDR A5 //右传感器
#define servopin 3 //定义舵机接口数字接口 也就是舵机的橙色信号线。B方案 接数字口
float error = 0;
// float Kp =1.62;
float Kp = 1.468;
float Ki = 0;
float Kd = 0.01;
int B_speed = 80;
int P = 0;
int D = 0;
int I = 0;
int n4 = 0;
int n3 = 0;
int flag = 0;
int line = 0;
int n = 0;
int R_SENSOR = 0;
int P_SENSOR = 0;
int single = 0;
int pre_dig = 0;
int dig = 0;
int previous_error = 0;
float PID_value = 0;
int pos = 0;
int init123 = 0;
int SENSOR[3]={0,0,0};
void setup() //初始化函数
{
Serial.begin(9600); //串口初始化
Track_INit(); //传感器初始化
Motor_INit(); //电机初始化
pinMode(LED,OUTPUT);
pinMode(YY,OUTPUT);
digitalWrite(LED,LOW);
digitalWrite(YY,HIGH);
Servo_Init_B();
Servo_Contorl_B(0);
}
void Track_INit() //传感器初始化
{
pinMode(HDL,INPUT);
pinMode(HDM,INPUT);
pinMode(HDR,INPUT);
}
void Motor_INit() //电机初始化
{
pinMode(IN1,OUTPUT);
pinMode(IN2,OUTPUT);
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
}
void Binary() //右傳感二值化,黑綫是1 白綫是2
{
if(SENSOR[2] <= 10)
{
R_SENSOR = 1;
}
else
{
R_SENSOR = 0;
}
if(P_SENSOR-R_SENSOR < 0)
{
single++;
}
P_SENSOR = R_SENSOR;
}
void loop() //主函数循环
{
Sensor_read(); //持续读取寻迹模块PWM模拟量输入
error_value();
pid();
xun_ji(); //寻迹函数
// xiu_zheng();
Position();
Serial.println(SENSOR[2]);
// Serial.print(" ");
// Serial.println(single);
// Sensor_read();
// Binary();
// Serial.print(single);
// Serial.print(" ");
// Serial.println(R_SENSOR);
}
void Position()
{
if(pre_dig - dig > 0)
{
n++;
if(n <5)
{
Motor_Speed(0,0,0,0);
delay(100); //停顿
while(1)
{
Motor_Speed(20,0,120,0);
if(n == 4 && n4==0)
{
Motor_Speed(20,0,120,0);
delay(180);
while(1)
{
Sensor_read();
if(SENSOR[0]<25)
{
break;
}
}
n4=1;
break;
}
Sensor_read();
if(SENSOR[2]<15)
{
break;
}
}
if(n == 3 && n3 == 0)
{
Motor_Speed(0,0,0,0);
// B_speed = 70;
delay(100); //方法一
// while(1) //方法二
// {
// Motor_Speed(90,0,90,0);
// delay(100);
// Sensor_read();
// if(SENSOR[1] == 1)
// {
// Motor_Speed(0,0,0,0);
// n++;
// break;
// }
// }
// Motor_Speed(20,0,120,0);
// delay(500);
// if(SENSOR[2] < 15) //方法三
// {
// delay(200);
// }
// while(single <= 30)
// {
// Sensor_read();
// Binary();
// if(SENSOR[2] <= 20)
// {
// Motor_Speed(20, 0, 60, 0);
// }
// else
// {
// Motor_Speed(60, 0, 20, 0);
// }
// }
// while(single <= 30)
// {
// Sensor_read();
// Binary();
// error_value_2();
// pid();
// xun_ji();
// if(SENSOR[1]==1)
// {
// n++;
// break;
// }
// }
n3 = 1;
}
}
if(n == 5)
{
if(SENSOR[0] > 60 && SENSOR[2] > 60)
{
while(1)
{
Motor_Speed(20,0,120,0);
Sensor_read();
if(SENSOR[2]<20)
{
break;
}
}
}
// if(SENSOR[0]<60 || SENSOR[2]<60)
// {
// Motor_Speed(100,0,20,0);
// delay(200);
// }
}
if(n>9)
{
Motor_Speed(0,0,0,0);
for (pos = 0; pos <= 45; pos += 1) { //pos+=1等价于pos=pos+1
Servo_Contorl_B(pos);
delay(15);
}
digitalWrite(LED,HIGH);
// digitalWrite(YY,LOW);
delay(2300);
while(1);
}
}
pre_dig=dig;
}
void error_value() //误差
{
error = (SENSOR[2]) - (SENSOR[0]);
}
void error_value_2() //入环误差
{
error = (SENSOR[2]) - (SENSOR[0]);
if(error>0)
{
error = 0;
}
}
void Sensor_read() //滤波 传感器读入
{
int a=0,b=0,c=0;
b = digitalRead(A4);
for(int i = 0;i<5;i++)
{
a=a+analogRead(A3);
c=c+analogRead(A5);
}
SENSOR[0]=a/5;
SENSOR[1]=b;
SENSOR[2]=c/5;
dig = SENSOR[1];
}
void pid()
{
P = error;
D = error - previous_error;
I = error + I;
PID_value = ((Kp * P) + (Kd * D))/2;
previous_error = error;
}
void Motor_Speed(int L1_SP,int L2_SP,int R1_SP,int R2_SP) //电机控制0~255(左正,左反,右正,右反)
{
analogWrite(IN1,R1_SP);
analogWrite(IN2,R2_SP);
analogWrite(IN3,L1_SP);
analogWrite(IN4,L2_SP);
}
// int flag_judge() //判断最后运动状态并标记
// {
// if(SENSOR[0] > 50 && SENSOR[2] > 50)
// {
// xiu_zheng();
// }
// else if(SENSOR[0] < 10)
// {
// flag = 3;
// return flag;
// }
// else if(SENSOR[2] < 10)
// {
// flag = 1;
// return flag;
// }
// }
void xiu_zheng() //修正回线
{
if(SENSOR[2]>60 && SENSOR[0]>60)
{
while(1)
{
Motor_Speed(20,0,120,0);
Sensor_read();
Serial.print("xiuzheng");
if(SENSOR[2]<20 || SENSOR[0]<20)
{
break;
}
}
}
}
void xun_ji() //寻迹
{
if(SENSOR[0]<= 50 || SENSOR[2] <= 50)
{
Motor_Speed(B_speed+PID_value,0,B_speed-PID_value,0);
}
}
void Servo_Init_B()
{
pinMode(servopin,OUTPUT);//设定舵机接口为输出接口
Servo_Contorl_B(103);
}
void servopulse(int angle)//定义一个脉冲函数
{
int pulsewidth=(angle*11)+500; //将角度转化为500-2480的脉宽值
digitalWrite(servopin,HIGH); //将舵机接口电平至高
delayMicroseconds(pulsewidth); //延时脉宽值的微秒数
digitalWrite(servopin,LOW); //将舵机接口电平至低
delayMicroseconds(20000-pulsewidth);
}
void Servo_Contorl_B(int angle)
{
//把值的范围映射到0到165左右
/*for(int i=0;i<50;i++) //发送50个脉冲
{
servopulse(angle); //引用脉冲函数
}
delay(100); */
servopulse(angle); //引用脉冲函数
}
2、stm8传参给匿名上位机绘制波形
第二个实验项目可以使用stm8的uart资源向匿名上位机传参,并绘制波形,对于新手来说通讯协议会很困难,但是网上资料很多,需要大家慢慢学习,学会传参之后,大家就可以结合第一个实验,尝试吧pid数据传到上位机绘制波形后调参,会有意想不到的结果。我分享一下我当时传参的代码,仅供参考:
#include <iostm8s208mb.h>
#include <math.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define BYTE0(dwTemp) (*(char *)(&dwTemp))
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
void delay(u16 Count);
void ANODT_SendFI(uint16_t a,uint16_t b,uint32_t c);
void UART1_Init(void);
void UART1_Send_NIMING();
uint8_t cnt;
uint8_t DataToSend[100];
int main()
{
CLK_CKDIVR=0x00;
delay(100);
UART1_Init();
float u=0;
int u1=0;
while(1)
{
for(u16 i=0;i<360;i++)
{
u=sin(i * 3.1415926535/180);
u1=(int)(u*10000);
ANODT_SendFI(0,u1,0);
UART1_Send_NIMING();
}
}
return 0;
}
void delay(u16 Count)
{
u8 i,j;
while(Count--)
{
for(i=0;i<=50;i++)
for(j=0;j<=20;j++);
}
}
void ANODT_SendFI(uint16_t a,uint16_t b,uint32_t c) //存储数组
{
cnt = 0;
DataToSend[cnt++] = 0xAA;
DataToSend[cnt++] = 0xFF;
DataToSend[cnt++] = 0xF1;
DataToSend[cnt++] = 8;
DataToSend[cnt++] = BYTE1(a);
DataToSend[cnt++] = BYTE0(a);
DataToSend[cnt++] = BYTE1(b);
DataToSend[cnt++] = BYTE0(b);
DataToSend[cnt++] = BYTE3(c);
DataToSend[cnt++] = BYTE2(c);
DataToSend[cnt++] = BYTE1(c);
DataToSend[cnt++] = BYTE0(c);
for (int i=0; i<DataToSend[3]+4; i++)
{
sumcheck += DataToSend[i];
addcheck += sumcheck;
}
DataToSend[cnt++] = sumcheck;
DataToSend[cnt++] = addcheck;
}
void UART1_Init(void)
{
UART1_CR1 = 0x00;
UART1_CR3 = 0x00;
UART1_BRR2 = 0x03; //9600
UART1_BRR1 = 0x68;
UART1_CR2 = 0x2C;
}
void UART1_Send_NIMING()
{
u8 i=0;
for(i=0;i<cnt;i++){
UART1_DR = DataToSend[i];
while((UART1_SR&0x40)==0); //0xAA
UART1_SR&=0xBF;
}
}