具体参考飞书文档 STM32版本、ESP32版本 两个版本 以stm32为主
课程内容:
基础部分资料:
1、先跑起来(点亮LED&打印Helloworld)
LED闪烁:
//头文件->可以理解为Arduino工具箱
#include <Arduino.h>
//初始化函数,只执行一次,把只需执行一次的代码放到setup
void setup() {
}
//循环执行函数,重复执行,把需要重复执行的代码放到loop
void loop() {
}
//这两个函数是arduino固定的,可以理解为代码运行的入口,每次开机都从setup开始运行
//头文件
#include <Arduino.h>
//定义LED灯引脚为常量,因为程序跑起来就不需要更改IO值
#define PIN_LED_15
void setup() {
//初始化引脚为输出
pinMode(PIN_LED,OUTPUT);
}
void loop() {
//设置为高电平(3.3V),1s后设置为低电平(0V),再1s后重复
digitalWrite(PIN_LED, HIGH);
delay(1000);
digitalWrite(PIN_LED, LOW);
delay(1000);
}
打印信息到电脑:
#include <Arduino.h>
void setup() {
//初始化串口,波特率位9600
Serial.begin(9600);
//打印“setup”字符到电脑,\n为换行符
Serial.printf("setup\n");
}
void loop() {
//打印“loop”字符到电脑,\n为换行符
Serial.printf("loop\n");
//延时1000ms
delay(1000);
}
2、LED是怎么被点亮的,LED是怎么被点亮的?
点灯原理:
插件led灯珠长引脚为正极,短引脚为负极。
LED(发光二极管)两端存在电压差,有一定的电流流过时会亮起。电流可以理解为水流,电压差可以理解为水位差,当两个点水位高度不一样时,水流会从高水位流向低水位。
//头文件
#include <Arduino.h>
//定义LED灯引脚为常量,因为程序跑起来就不需要更改IO值
#define PIN_LED_18;
void setup() {
//初始化引脚为输出
pinMode(PIN_LED,OUTPUT)
}
void loop() {
//设置为高电平(3.3V),1s后设置为低电平(0V),再1s后重复
digitalWrite(PIN_LED, HIGH);
delay(1000);
digitalWrite(PIN_LED, LOW);
delay(1000);
}
3、芯片的运行状态打印到电脑显示,但它的技术原理是怎么样的?(打印字符到电脑)
与电脑上位机软件通信、与Android工控机通讯
串口模块:以太网、WiFi、蓝牙、Zigbee、Lora等串口模块
什么是串口?
串行通讯端口,简称串口,也称COM口,串行接口的数据是通过一条线一位位地顺序传送。
并行接口,简称并口,是指8位数据同时通过8条并行线进行传送。
串口形容一下就是一条车道,而并口就是有8个车道同一时刻能传送8位(一个字节)数据。但是并不是并口快,由于8位通道之间的互相干扰。传输受速度就受到了限制。而且当传输出错时,要同时重新传8个位的数据。串口没有干扰,传输出错后重发一位就可以了。
什么是串口波特率?
波特率(bandrate),指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。比如每秒钟可以传输9600个二进制(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600。
串口的通信波特率不能随意设定,而应该再一些值中去选择。一般常见的波特率是9600或者115200(低端的单片机如51常用9600,高端的单片机和嵌入式SOC一般用115200)。
数据位:
1010 1010 -> 0xaa ->170
10*16+10
也就是说我们可以通过控制引脚按上面的高低电平,每104us改变一次,就可以完成一个字节的传输。
但是芯片已经帮我们做好了这些操作,我们要做的只是配置好波特率,然后填写数据即可。
参考代码:
//引入头文件
#include <Arduino.h>
uint8_t temp_data;
void setup() {
Serial.begin(9600);
// Serial.begin(115200);
Serial.print("setup\n");
}
void loop() {
delay(1000);
Serial.print("loop\n");
//判断串口是否可用
while(Serial.available()) {
//读取接收到的数据
temp_data = Serial.read();
if(temp_data == '$') {
Serial.printf("get data %d\n",temp_data);
}
}
}
/*
如果设置Serial.begin(115200);
则需要在platformio.ini中添加
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
*/
//可以尝试,使用电脑控制LED灯
4、如何读取按键状态?
通过IO读取引脚的电平,判断是否有信号触发。
什么是电压?什么是电平?高电平?低电平?
以TTL电路为例:
TTL电源电压是3.3V,高电平是2.4V--3.3V,低电平0V--0.8V,按照惯例,使用1来表示高电平,使用0表示低电平。
按键输入
#include <Arduino.h>
//34 35 36 39仅可以作为输入INPUT,不支持INPUT_PULLUP
#define PIN_KEY 5
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
//设置为上拉输入,初始化后默认电平为高
pinMode(PIN_KEY, INPUT_PULLUP);
Serial.print("setup\n");
}
void loop() {
// put your main code here, to run repeatedly:
// 此处进行按键去抖
if(digitalRead(PIN_KEY)==LOW){
Serial.print("KEY Click\n");
delay(1000);
}
}
按键消抖
#include <Arduino.h>
//34 35 36 39仅可以作为输入INPUT,不支持INPUT_PULLUP
#define PIN_KEY 5
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
//设置为上拉输入,初始化后默认电平为高
pinMode(PIN_KEY, INPUT_PULLUP);
Serial.print("setup\n");
}
void loop() {
// put your main code here, to run repeatedly:
// 此处进行按键去抖
if(digitalRead(PIN_KEY)==LOW){
delay(10);
if(digitalRead(PIN_KEY)==LOW){
Serial.print("KEY Click\n");
delay(1000);
}
}
}
5、计时与定时
时钟功能、设备工作时长计时功能、设备定时功能
#include <Arduino.h>
#include <Ticker.h>
Ticker timer1;
Ticker timer2;
Ticker timer3;
void timer1_callbackfun(){
Serial.println("timer1 is running...");
}
void timer2_callbackfun()
{
timer1.attach_ms(500,timer1_callbackfun);
Serial.println("timer2 is running...");
}
void timer3_callbackfun()
{
timer1.detach();
Serial.println("timer3 is running...");
}
void setup(){
Serial.begin(9600);
timer2.once_ms(1000, timer2_callbackfun); // 1000ms后开启timer1
timer3.once_ms(5000, timer3_callbackfun); // 3000ms后关闭timer1
}
void loop(){
}
6、电位器
应用场景
技术原理
前面我们无论是读取输入还是控制输出,都只有高低电平(0和1)的概念,这是我们常说的数字电路,而实际项目中,无论是温度、湿度、重量、电压往往是一个范围内的数值。
接下来我们来看看模拟电路,通过引脚读取一定范围内变化的值。
电位器属于无极性器件,可变电阻的一种,三个触点,通过旋转旋钮改变2号脚的位置,从而改变阻值的大小,1脚和3脚分别接开发板的3.3V和GND,2脚接模拟输入引脚。
无论是温度传感器、湿度传感器都是把环境的温湿度通过材料的特性,最终转换为引脚能够测量的电压值,所以,一旦学会读取电位器的电压,也就使用同样的办法读取温湿度、重量传感器的数据,仅仅是转换表或公式的不同而已。
#include <Arduino.h>
#define PIN_ADC 12
void setup() {
Serial.begin(9600);
//set the resolution to 12 bits (0-4096) 设置分辨率为12位
analogReadResolution(12);
}
void loop() {
// read the analog / millivolts value for pin 12: 读取引脚12的模拟量和电压值,注意,仅有部分引脚支持ADC
int analogValue = analogRead(PIN_ADC);
int analogVolts = analogReadMilliVolts(PIN_ADC);
// print out the values you read:
Serial.printf("ADC analog value = %d\n",analogValue);
Serial.printf("ADC millivolts value = %d\n",analogVolts);
delay(1000); // delay in between reads for clear read from serial
}
7、舵机控制
应用场景
舵机的控制原理
舵机是一种位置伺服的电机,与马达不同,我们需要马达提供的是旋转,控制的是转速和方向。而舵机不需要整圈的旋转,需要的是旋转角度并维持住。一般舵机旋转的角度范围是0 度到180 度。舵机引线为3线,分别用棕、红、橙三种颜色进行区分,舵机品牌和生产厂家不同,会有些许差异,使用之前需查看资料。我们使用的是最常见的舵机,棕、红、橙分别对应“电源负极,电源正极,控制信号”。
舵机控制
通过向舵机的信号信号线发送PWM信号来控制舵机的转动位置
下面举个例子;
当我们向舵机发送脉冲宽度为1.5毫秒(ms)的信号时,舵机的输出轴将移至中间位置(90度);
脉冲宽度为1ms时,舵机的输出轴将移至最小的位置(0度);
脉冲宽度为2ms时,舵机的输出轴将移至最小的位置(180度);
舵机连续转动原始IO实现:
#include <Arduino.h>
#define PIN_SERVO 23
void setup() {
//设置波特率
Serial.begin(9600);
pinMode(PIN_SERVO, OUTPUT);
}
/**
* @brief 输入角度值控制舵机转动
*
* @param angle
*/
void motor_run(int angle){
if(angle < 0 || angle > 180)
return;
float hight_time = 0.0;
float low_time = 0.0;
float all_time = 20.0;
//舵机90°时,基准脉宽为1.5ms
//hight_time = (angle / 180.0) + 1.0; //1ms-0度 2ms-180的舵机使用
hight_time = (angle / 90.0) + 0.5; //0.5ms-0度 2.5ms-180度的舵机使用
low_time = all_time - hight_time;
Serial.printf("h = %f l = %f\n",hight_time,low_time);
digitalWrite(PIN_SERVO, HIGH);
delayMicroseconds(hight_time*1000);
digitalWrite(PIN_SERVO, LOW);
delayMicroseconds(low_time*1000);
}
void loop() {
//原来代码有问题,需要按舵机电平持续发送才行,不能只发一次高低电平切换
// motor_run(45);
// delay(3000);
// motor_run(130);
// delay(3000);
int index = 0;
while(1){
motor_run(45);
index ++;
if(index > 100)
break;
}
index = 0
while(1){
motor_run(130);
index ++;
if(index > 100)
break;
}
}
舵机与电位器联动
#include <Arduino.h>
#include <ESP32Servo.h>
#define PIN_SERVO 23
#define PIN_SENSOR 12
Servo servo;
int angle = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.print("setup\n");
//set the resolution to 10 bits (0-1023)
analogReadResolution(10);
//频率设置50Hz 1s 50个脉冲 每个脉冲20ms
servo.setPeriodHertz(50);
// 例如,如果范围是1000us到2000us, 1000us等于0的角,1500us等于90度,2000us等于1800 度。
servo.attach(PIN_SERVO,1000,2000);
////部分舵机跑不到位置,这里1000改为500 2000改为2500
}
void loop() {
// put your main code here, to run repeatedly:
int val=analogRead(PIN_SENSOR);
//函数的使用率比较高,将某一区间的值转换为另外区间的值。
//数值转换,将[0,1023]产生的模拟值转换成[0,180]中的值
int angle=map(val,0,1023,0,180);
Serial.printf("val = %d angle = %d\n",val,angle);
//servo.write(45)表示舵机旋转到45°的位置
servo.write(angle);
//原理,通过计算脉冲宽度转换为控制角度,脉冲频率转换为控制速度
delay(100);
}
8、蜂鸣器
应用场景
大部分的电子产品、家电(风扇、空调、电水壶)都会有蜂鸣器,用于提示设备的工作状态
原理
蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号。
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。
蜂鸣器有正负极,顶部印有+号的为正极,若蜂鸣器引脚没剪,则长的为正极。
参考代码
有源蜂鸣器
#include <Arduino.h>
//蜂鸣器引脚
#define PIN_BUZZER 18
void setup()
{
pinMode(PIN_BUZZER, OUTPUT);
}
void loop()
{
digitalWrite(PIN_BUZZER, HIGH);
delay(2000);
digitalWrite(PIN_BUZZER, LOW);
delay(2000);
}
无源蜂鸣器
PWM:脉冲宽度调制
#include <Arduino.h>
//蜂鸣器引脚
#define PIN_BUZZER 18
int freq = 2000; //设置频率2000kHz
int channel = 0; //通道号,取值0 ~ 15
int resolution = 8; //分辨率,取值0~20,占空比duty最大取值为2^resolution-1
void setup() {
Serial.begin(9600);
//设置通道频率和分辨率
ledcSetup(channel, freq, resolution);
//将通道0与引脚15连接
ledcAttachPin(PIN_BUZZER, channel);
//输出指定音阶、音调
ledcWriteNote(channel,NOTE_D,5);
delay(3000);
ledcWrite(channel, 0);
}
void loop() {
//设置通道频率
ledcWriteTone(channel, 2000);
//动态修改占空比
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle = dutyCycle + 10) {
Serial.println(dutyCycle);
ledcWrite(channel, dutyCycle);
delay(1000);
}
//设置通道占空比 125/2^8 50%
ledcWrite(channel, 125);
//动态修改频率
for (int freq = 255; freq < 10000; freq = freq + 250) {
Serial.println(freq);
ledcWriteTone(channel, freq);
delay(1000);
}
}
10、屏幕显示
SSD1306 Oled显示模块共有4个引脚,标记为GND, VCC, SCL和SDA。这种Oled显示模块可以使用3.3V到5V轻松上电。
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#define SCL 22
#define SDA 23
// IIC version 方向 时钟信号 数据信号 复位数据
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, SCL, SDA, U8X8_PIN_NONE);
void setup(void)
{
u8g2.begin();
}
void loop(void)
{
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.drawStr(0, 24, "Hello World!");
u8g2.sendBuffer();
delay(1000);
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.drawStr(0, 24, "embeded!");
u8g2.sendBuffer();
delay(1000);
}
10、直流&步进驱动
什么是直流电机?
直流电机是最常见的电机类型。直流电动机通常只有两个引线,一个正极和一个负极。如果将这两根引线直接连接到电池正负极,电机将旋转,如果切换引线,电机将以相反的方向旋转,通过改变提供给电机的电压,您可以改变电机的速度。
什么是步进电机?
步进电机是一种将电脉冲信号转换成相应角位移的电机,它可以实现更精准的位置控制。
现在想象一个打印机。打印机内有很多移动部件,包括电机。一个电机用作进纸,当墨水开始印在纸上时旋转滚轴移动纸张。此电机需要能够将纸张移动一个精确的距离,以便能够打印下一行文本或图像的下一行。(论文用到)
直流电机与步进电机的对比:
直流电机:
优点:价格低、控制方便 缺点:由于电刷和换向器的存在,有刷电机的结构复杂,可靠性差,故障多,维护工作量大,寿命短,换向火花易产生电磁干扰。
步进电机:
优点:低速扭矩大,控制更精准,成本相对低; 缺点:步进电机存在空载启动频率,所以步进电机可以低速正常运转,但若高于一定速度时就无法启动,并伴有尖锐的啸叫声,同时,步进电机是开环控制,控制精度和速度都没有伺服电机那么高。
直流电机的控制
#include <Arduino.h>
int motorPin1 = 18;
void setup() {
pinMode(motorPin1, OUTPUT);
}
void loop() {
digitalWrite(motorPin1, HIGH);
delay(2000);
digitalWrite(motorPin1, LOW);
delay(2000);
}
控制转速
直流电机的速度可以通过改变其输入电压来控制,使用单片机控制转速可以通过PWM控制。
PWM是一种通过发送一系列ON-OFF脉冲来调整输入电压平均值的技术。该平均电压与脉冲的宽度成正比,称为占空比。
占空比越高,施加到直流电机的平均电压就越高,从而导致电机速度增加。占空比越短,施加到直流电机的平均电压越低,导致电机速度降低。
下图显示了具有各种占空比和平均电压的PWM技术。