基于C#的软件大杂烩(1.3)——串口助手的优化
前端时间做的串口示波器做了许多优化,其代码量也到达了将近2000行 在原本的基础上做了很多内容的增加,私信回复《串口助手》即可获取发布链接(还有很多内容尚未完善),欢迎朋友们提出改进意见以及点赞分享。 包括但不限于: 控件提示:将鼠标悬停于控件上可以提示相关控件信息。 接收设置优化: 利用定时器更新UI控件防止当串口收发速度过快的时候导致UI进程被占用无法使用。 串口详细信息显示: 显示串口的详细信息包括蓝牙,CH340等详细内容的显示,方便我们在连接蓝牙设备,CH340设备的多串口选择。 热拔插刷新:在使用串口的过程中拔插串口或者在软件启动后加入新的串口时会刷新串口列表以及对状态进行修改。 布局优化:使用Panel容器优化界面布局,是的波形显示和接收显示界面分开可以自由来回切换。 波形显示逻辑: 修改波形显示逻辑,我们可以在使用串口的同时选择是否开启波形显示(之前是在打开串口前进行选择) 滚轮控制显示区域:增添了利用滚轮控制波形的显示范围,之前只能利用滑条来控制区域。 傅里叶变换:增加了对指定区域的傅里叶变换功能,将时域图转换成频域图。 保存图表: 可以保存我们的波形图到我们的文件夹中。 数据统计: 增加了统计波形显示区域内的最大值与最小值(后续可能更新更多的统计数据) 此外还有许多地方尚未完成,例如自定义数据帧解析,多行发送,高速模式下时间戳的使用等等~欢迎大家提出建议,会多多吸取意见并修改。 以下展示部分源码: private void Chart1_MouseEnter(object sender, EventArgs e)
{
chart.MouseWheel += Chart1_MouseWheel;
}
private void Chart1_MouseLeave(object sender, EventArgs e)
{
chart.MouseWheel -= Chart1_MouseWheel;
}
private void Chart1_MouseWheel(object sender, MouseEventArgs e)
{
try
{
if (e.Delta [removed] 0) // 向上滚动
{
// 减小 chartLength 的值,可以根据需要调整减小的步长
ChartLenth -= 100;
if (ChartLenth [removed] 0)
{
using (SaveFileDialog saveFileDialog = new SaveFileDialog())
{
saveFileDialog.Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
saveFileDialog.DefaultExt = "png";
saveFileDialog.Title = "Save Chart Image";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
SaveChartImage(chart, saveFileDialog.FileName);
MessageBox.Show("图表已成功保存为图片!", "成功", MessageBoxButtons.OK, MessageBoxcon.Information);
}
}
}
}
private void SaveChartImage(Chart chart, string filePath)
{
try
{
// 使用 SaveImage 方法保存图表为图片
chart.SaveImage(filePath, ChartImageFormat.Png);
}
catch (Exception ex)
{
// 处理保存异常
MessageBox.Show("保存图片时发生错误:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxcon.Error);
}
}
图片保存部分的代码,启用资源管理器来获取我们的保存信息,之后利用chart控件自带保存控件的方法来保存我们的控件。 private void FuChange_Click(object sender, EventArgs e)
{
int Xmax = int.Parse(MAXArea.Text);
int Xmin = int.Parse(MinArea.Text);
if (Xmax < Xmin || Xmin < 0)
{
MessageBox.Show("请输入正确的范围");
return;
}
if (chart.Series.Count == 0)
{
MessageBox.Show("图表中没有可用的系列");
return;
}
// 获取选定范围内的数据点
List[removed] selectedDataPoints = new List[removed]();
foreach (DataPoint dataPoint in chart.Series[0].Points)
{
if (dataPoint.XValue >= Xmin && dataPoint.XValue <= Xmax)
{
selectedDataPoints.Add(dataPoint);
}
}
// 将数据点转换为复数数组
Complex[] complexData = new Complex[selectedDataPoints.Count];
for (int i = 0; i < selectedDataPoints.Count; i++)
{
complexData[i] = new Complex(selectedDataPoints[i].YValues[0], 0);
}
// 执行傅里叶变换
Fourier.Forward(complexData, FourierOptions.Matlab);
// 将结果显示在 FuChart 上
FuChart.Series.Clear();
WindowsFormsSeries fuSerial = new WindowsFormsSeries("傅里叶变换结果");
fuSerial.ChartType = SeriesChartType.FastLine;
fuSerial.BorderWidth = 3;
FuChart.Series.Add(fuSerial);
// 计算频率间隔
double frequencyInterval = 1.0 / (selectedDataPoints[1].XValue - selectedDataPoints[0].XValue);
// 显示正频率部分
for (int i = 0; i [removed]
基于C#和ESP32的远程示波器(6)—— 合理的利用数据缓存区来消除数据丢失
啊~我的天!好久没更新这个系列了,主要是在琢磨怎么彻底的消除数据不连续的问题。 这个星期从硬件到软件到通讯协议,试过了很多方法来解决数据,不连续的问题。光通讯协议就用了HTTP,UDP,WebSockerts以及TCP还为每一个通讯协议专门写了数据处理的代码和示波器(每天写到吐了、无数的BUG和Error) 最后我们解决的方法还是:定时器控制采样率、HTTP将数据打包上传、利用标志位来交替写入缓存区。 上期问题阐述与解决 我简单的阐述一下上一期的代码出现的问题: 首先是我们在任务中运行ADC采集,这样子会导致我们无法控制我们的采样率,这样子对于信号采集来说是不应该出现的。 其次我们的数据在Data = PostData中会有一个数据传递的过程,又可以造成不必要的耗时。 因此我们选择的方法是使用定时器来定时触发我们的ADC采样。 并且使用两个数据缓存区PostData1和PostData2,定时器交替向这两个缓存区写入数据。 主要代码 我们首先加入定时器的头文件(利用的也是FreeRTOS)以及声明其句柄。#include [removed]
TimerHandle_t timer;
接着在主函数中加入定时器的配置timer = xTimerCreate("DataTimer", pdMS_TO_TICKS(2), pdTRUE, NULL, onTimer);
xTimerStart(timer, 0);
该定时器的名字命名为:DataTimer,2ms触发一次,自动装填,回调函数为onTimer 接着我们定义我们的回调函数的内容。int QueueFlag = 1;
void IRAM_ATTR onTimer(TimerHandle_t xTimer) {
static int j = 0;
int ADCVlaue[4];
for (int i = 0; i [removed]
基于C#的软件大杂烩(1.2)——串口示波器
上一期我们实现了串口助手收发数据之后,这期我们为我们的串口助手加上我们的波形图功能。 我们在前面有一期中介绍如何在C#中使用chart画图库 本期我们利用我们上一期制作的串口助手来实现我们的串口示波器。 准备工作 首先我们在上一期的基础上加上我们的chart库using System.Windows.Forms.DataVisualization.Charting;
其次,简单的布置一下我们的界面。 其中图表和按钮的默认状态是隐藏的,当我们勾选波形显示时显示。 private void ChartShow_CheckedChanged(object sender, EventArgs e)
{
if (ChartShow.Checked == true)
{
Receive.Height = 680/2;
chart.Show();
ClearChart.Show();
}
else
{
Receive.Height = 680;
chart.Hide();
ClearChart.Hide();
}
}
我们勾选按钮的时候,将接收区的高度减少一半,让按键和图表显形。 数据解析与存放 我们希望我们的数据格式是类似“a:xxx,b:xxx”等等。因此,我们需要解析我们的数据private Dictionary[removed] ParseData(string input)
{
Dictionary[removed] result = new Dictionary[removed]();
// 按逗号分割字符串
string[] pairs = input.Split(',');
foreach (string pair in pairs)
{
// 再按冒号分割键值对
string[] keyValue = pair.Split(':');
if (keyValue.Length == 2)
{
string key = keyValue[0].Trim();
string value = keyValue[1].Trim();
result[key] = value;
}
}
return result;
}
我们定义一个解析键值对函数,利用逗号分隔字符串,冒号分隔将解析的结果返回,这样子我们就可以获取我们的键值对。 // 添加解析数据的代码
string[] lines = data.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
lines = lines.Skip(1).Take(lines.Length - 2).ToArray();
由于我们的数据可能有很多行,因此我们在更新图表的代码中需要将数据按照换行符分开,并且由于第一行和最后一行的数据可能不完整,我们需要删除第一行和最后一行的数据。foreach (string line in lines)
{
// 对每一行进行解析
Dictionary[removed] parsedData = ParseData(line);
foreach (var kvp in parsedData)
{
// 继续处理解析后的数据
int existingIndex = chart.Series.IndexOf(kvp.Key);
if (existingIndex != -1)
{
//如果该系列的图表存在
chart.Series[existingIndex].Points.AddXY(chart.Series[existingIndex].Points.Count, double.Parse(kvp.Value));
}
else
{
//如果该系列的图表不存在
string seriesName = new string(kvp.Key.TakeWhile(char.IsLetter).ToArray()); // 获取字符串中的所有字母前缀
Series newSeries = new Series(seriesName);
newSeries.ChartType = SeriesChartType.Spline; // 设置图表类型为 Spline
newSeries.Points.AddXY(0, double.Parse(kvp.Value));
chart.Series.Add(newSeries);
}
}
}
我们将我们解析的数据放入图表,按照数据的前缀来划分数据。 判断图表是否存在,如果不存在就添加相应的系列。 效果展示 接着让我们看看效果
基于C#的软件大杂烩——手搓串口助手
作为一个有着伟大理想,崇高信念21青年,在我平时用着串口助手、计算器的时候。我总是寻思着,我能不能自己去实现做一个软件,能实现我日常想要做的功能,例如串口助手啦,图片助手啦,文件转换啦等等。 于是打算从0.5开始手搓一个大杂烩软件。 首先的工具我就想到了串口助手,那么本期我们就介绍如何使用C#制作串口助手。相关库安装 首先,我们需要在NuGet包管理中,找到我们的关于端口的包 我们搜索这个包并且安装这个包,这样子我们就可以在C#中调用我们的端口类了。 页面布局 通常我们的串口助手最基本的数据接收和发送,打开和关闭串口以及选择端口设置波特率等等,所以我们需要若干个TextBox文本框,Label标签以及Button按钮来实现我们的基本功能最后还需要有一个下拉式的ComboBo来选择我们的串口 我们在左边的空间库中寻找我们需要的控件,放置到我们的设计器中。 基础编写private void LoadAvailablePorts()
{
// 获取可用端口
string[] ports = SerialPort.GetPortNames();
// 将端口添加到ComboBox中
COMChoose.Items.AddRange(ports);
// 如果需要,默认选择第一个端口
if (COMChoose.Items.Count > 0)
{
COMChoose.SelectedIndex = 0;
}
}
我们首先编写获取端口的代码,并且在FromLoad函数中调用这个代码,这样子我们就可以加载端口显示在下拉框中CheckBit.SelectedIndex = 0;
我们在这些选项框中使用默认第一个数据。private void Connect_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
// 串口已经打开,关闭串口
serialPort.Close();
Connect.Text = "打开串口";
// 启用控件
COMChoose.Enabled = true;
PortText.Enabled = true;
DataBit.Enabled = true;
StopBit.Enabled = true;
CheckBit.Enabled = true;
}
else
{
// 串口未打开,尝试打开串口
try
{
// 配置串口
serialPort.PortName = COMChoose.SelectedItem.ToString();
serialPort.BaudRate = int.Parse(PortText.Text); // 获取波特率
serialPort.DataBits = int.Parse(DataBit.SelectedItem.ToString()); // 获取数据位
// 设置停止位
switch (StopBit.SelectedItem.ToString())
{
case "1":
serialPort.StopBits = StopBits.One;
break;
case "1.5":
serialPort.StopBits = StopBits.OnePointFive;
break;
case "2":
serialPort.StopBits = StopBits.Two;
break;
default:
serialPort.StopBits = StopBits.One;
break;
}
// 设置校验位
switch (CheckBit.SelectedItem.ToString())
{
case "None":
serialPort.Parity = Parity.None;
break;
case "Odd":
serialPort.Parity = Parity.Odd;
break;
case "Even":
serialPort.Parity = Parity.Even;
break;
default:
serialPort.Parity = Parity.None;
break;
}
// 打开串口
serialPort.Open();
Connect.Text = "关闭串口";
// 禁用控件
COMChoose.Enabled = false;
PortText.Enabled = false;
DataBit.Enabled = false;
StopBit.Enabled = false;
CheckBit.Enabled = false;
serialPort.DataReceived += DataReceivedHandler;
}
catch (Exception ex)
{
// 打开串口失败,显示错误消息
MessageBox.Show("串口打开失败" + ex.Message);
}
}
}
我们在点击按钮的时候设置的代码是:点击按钮改变按钮的文本,并且配置按钮的各项数据,禁用/开启端口选择..等等的使用以及创建新的回调函数(DataReceivedHandler)在这个函数中编写我们收到的代码信息。private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
try
{
// 从串口读取数据
string data = serialPort.ReadExisting();
// 将数据追加到 Receive 文本框中
UpdateReceiveTextBox(data);
}
catch (Exception ex)
{
MessageBox.Show("接收失败" + ex.Message);
}
}
private void UpdateReceiveTextBox(string data)
{
if (Receive.InvokeRequired)
{
// 如果不在 UI 线程上,通过 Invoke 在 UI 线程上更新文本框
Receive.Invoke(new Action[removed](UpdateReceiveTextBox), data);
}
else
{
// 在 UI 线程上更新文本框
Receive.AppendText(data);
}
}
我们在串口回调函数中简单的添加一些内容,我们将收到的串口打印到我们的TextBox上(UpdateReceiveTextBox这个函数也是我们自己定义的)作用是用来区分当前程序是不是在UI线程上,如果不在UI线程上,我们需要用另一种方式来更新我们的控件。 接收串口数据成功,我们也添加了时间戳的功能,用来为我们的代码接收添加时间。 // 在 UI 线程上更新文本框
if (TimeTik.Checked)
{
// 如果 TimeTik 被勾选,加上时间戳
data = $"{DateTime.Now:HH:mm:ss} -> {data}";
}
Receive.AppendText(data);
接着我们来编写发送部分的代码private void TransBotton_Click(object sender, EventArgs e)
{
try
{
// 获取要发送的文本
string dataToSend = TransText.Text;
// 判断是否勾选了 NewLine 复选框
if (NewLine.Checked)
{
// 添加新行
dataToSend += Environment.NewLine;
} // 发送数据
serialPort.Write(dataToSend);
}
catch (Exception ex)
{
MessageBox.Show("串口发送失败");
}
}
其实这个东西运行起来还是有不少BUG的,需要我们不断的优化,感兴趣的朋友可以后台私信联系。
基于C#和ESP32的远程示波器(5)—— 利用FreeRTOS双核运行减少数据丢失
在我们上一期代码中,我们利用HTTP将我们的数据上传到局域网,但是由于我们的程序是串行程序,即ADC采集完需要等待数据上传成功才能继续运行我们的代码,因此我们的数据就会出现很严重的数据丢失。 而我们在之前学习过STM32中的FreeRTOS即实时操作系统,将不同的任务按照不同的优先级分别运行。因此我们尝试使用FreeRTOS将ADC采集和数据上传的任务按照不同级别的优先级进行划分。 关于FreeRTOS的介绍可以查看前几期的内容基于STM32F407的FreeRTOS学习笔记(13)—— 阶段性的总结与后续 FreeRTOS库准备 FreeRTOS在ESP32中的环境配置比在STM32中简单许多,我们只需要在库管理中搜索FreeRTOS即可安装我们的FreeRTOS 我们直接安装最新版本的FreeRTOS#include[removed]#include [removed]
#include[removed]#include [removed]//FreeRTOS头文件
#include [removed]//任务头文件
#include [removed]//信号量头文件
之后我们放入相关头文件包含信号量和任务头文件还有FreeRTOS的基本头文件。 任务创建 任务创建可以参考我们的公众号文章基于STM32F407的FreeRTOS学习笔记(2)——任务的创建与删除TaskHandle_t Task1, Task2;
xTaskCreatePinnedToCore(
Task1Code, // 任务函数
"Task1", // 任务名称
10000, // 任务堆栈大小
NULL, // 任务参数
1, // 任务优先级
&Task1, // 任务句柄
0); // 核心编号
// 创建任务2,绑定到核心1
xTaskCreatePinnedToCore(
Task2Code, // 任务函数
"Task2", // 任务名称
10000, // 任务堆栈大小
NULL, // 任务参数
2, // 任务优先级
&Task2, // 任务句柄
1); // 核心编号
我们创建任务1和任务2 的句柄,并且由于ESP32是双核的MPU,比起STM32我们可以将不同的任务绑定到不同的核心进行使用,我们设置两个任务,并且任务1的优先级大于任务2的优先级。 我们将任务1用于ADC采集,任务2用于数据上传。String postData = "";
String Data;
int dataArray[1000];
// 任务1的函
void Task1Code(void *parameter) {
for (;;) {
for (int i = 0; i < 1000; i++) {
dataArray[i] = analogRead(34);
postData += String(dataArray[i]);
if (i [removed] 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
String response = http.getString();
Serial.println(response);
} else {
Serial.print("HTTP Request failed. Error code: ");
Serial.println(httpResponseCode);
}
}
delay(1000);
}
}
接着我们定义我们的任务函数,关于HTTP的部分参考上一期公众号内容。 我们每次采集好数据之后释放信号量,任务2等待信号量的释放,当任务1释放信号量之后我们上传我们的数据,需要注意的是,此时任务2上传数据时postData依旧在进行改变,所以我们需要用一个新的变量来接受PostData。 效果展示 我们来看看我们的运行效果
基于C#和ESP32的远程示波器(4)—— ESP32在局域网中利用HTTP协议代替MQTT协议
上一期我们使用MQTT协议连接到百度云并且向C#的客户端上传了我们的数据。 我之后也成功使用ESP32上传数据到MQTT服务器,在C#中显现折线图,但是随之而来的我们发现了新的问题:这消息量好像有点庞大了 短短的测试环节我就发送了将近20w条消息(幸亏我关的早),于是当机立断选择放弃使用MQTT协议,改用其他协议来传输数据。 MQTT协议适合小型数据量的传输,那么对于我们这么庞大的数据量来说确实不太适合,于是我们选择常用的超文本传输协议:HTTP来传输我们的数据。 HTTP简单介绍 HTTP由三个部分组成: 起始行(start line):描述请求或响应的基本信息; 头部字段集合(header):使用 key-value 形式更详细地说报文; 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。 起始行中我们需要确定我们的HTTP是进行请求还是发送。 这里不细细赘述如何具体使用HTTP报文的详细,我们简单的介绍一下在ESP32中如何使用HTTP。 HTTP发送 首先,我们在Arduino IDE中安装我们的HTTP相关的库,我们在库管理中搜索HttpClient安装相关库。 其次按win+R调出命令窗口,输入cmd并运行调出终端面板,之后输入ipconfig查询我们的电脑IP 在IPv4的地址中(任意一个)查看我们的局域网IP地址 我们在ESP32中保存我们的Wifi用户名,Wifi密码还有我们刚才的IP地址,注意加上http://还有确定我们使用的端口号,我们使用8081端口,最后一定一定记得加上斜杠void WIFIConnect()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.println("Connected to WiFi");
}
我们定义一个函数来连接我们的Wifi,因为我们使用的ESP32的定时器2,使用Wifi的时候不能 使用ESP32的ADC2,因此我们使用ADC2的时候我们需要关闭我们的Wifi。 String postData = "";
for (int i = 0; i < 1000; i++) {
dataArray[i] = analogRead(13);
postData += String(dataArray[i]);
if (i [removed] ListenForHttpRequests());
}
接着我们定义我们的监听函数,我们在监听函数中解析我们的数据。
private void HandleHttpRequest(HttpListenerContext context)
{
try
{
// 从请求中获取数据
Stream body = context.Request.InputStream;
Encoding encoding = context.Request.ContentEncoding;
StreamReader reader = new StreamReader(body, encoding);
if (context.Request.HasEntityBody)
{
string postData = reader.ReadToEnd();
Console.WriteLine($"Received data from ESP32: {postData}");
// 将以逗号分隔的多个数值解析为数组
string[] values = postData.Split(',');
Invoke((MethodInvoker)delegate
{
for (int i = 0; i [removed]
基于C#和ESP32的远程示波器(3)—— 使用C#连接MQTT百度云服务器
上期我们介绍了C#中的折线工具,本期我们介绍C#中利用MQTT协议连接baidu服务器。在使用MQTT之前,我们需要获得我们的MQTT服务器地址和相关信息,往期有介绍关于如何使用百度云创建MQTT服务器并获取连接信息的文章,可以参考百度云物联网之MQTT协议创建设备及获取信息。关于ESP32连接MQTT的部分可以参考下的(哥们能不水就不水)ESP32物联网教程之MQTT(Ardunio IDE)但是有一点要注意的是,WIFI的使用会占用ADC2导致ADC2不能用,所以不能使用ADC2,或者先采集数据,再连接WIFI,再上传数据,之后再断开连接·相关库在C#中有许多库是可以使用MQTT的,使用较多的有MQTTnet,M2Mqtt等,我们选择的是M2Mqtt库(MQTTnet我没用明白)我们打开NuGet程序包管理。在其中搜索M2Mqtt安装最新版即可。连接MQTT服务器接着我们在代码中连接我们的MQTT服务器。首在全局中创建我们的MQTT句柄,用以连接我们的MQTT服务器。接着加入如下代码,连接MQTT服务器并且配置我们的回调函数 private void InitializeMqttClient()
{
// 服务器地址
string brokerAddress = "你的服务器地址";
// 你的用户名和密码
string username = "你的用户名";
string password = "你的密码";
// 创建MqttClient实例
mqttClient = new MqttClient(brokerAddress);
// 设置用户名和密码
mqttClient.Connect(Guid.NewGuid().ToString(), username, password);
// 订阅主题
string topic = "你的主题";
mqttClient.Subscribe(new string[] { topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE });
// 设置接收消息的回调函数
//这个Client_MqttMesgPublishReceived是我自己定义的
mqttClient.MqttMsgPublishReceived += Client_MqttMsgPublishReceived;
Console.WriteLine($"Connected to {brokerAddress} with username {username}");
}
服务器的地址用户名和密码,参考开头的那期有讲解如何获取相关信息的。接着我们在Form函数中调用这个函数,这样子我们在窗口加载的时候就可以连接到我们的MQTT服务器 public Form1()
{
InitializeComponent();
InitializeMqttClient();
}
接着我们点击运行,前往百度云的后台查看是否有设备连接并运行。首先我们可以看到我们的控制台提示:我们已经连接到了MQTT服务器,并且打印了服务器地址以及用户名。之后在运维管理中,我们也可以看到我们当前有一个设备正在使用。接收MQTT消息并显示连接好设备之后,我们需要编写我们的相关回调函数,我们之前定义了我们的回调函数这里要对其做具体定义。我们在上一期的时候介绍了如何使用chart控件显示折线图,我们在消息回调函数中,利用chart控件,将我们的信打印出来。 private void Client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
// 处理接收到的消息
string message = Encoding.UTF8.GetString(e.Message);
number++;
Console.WriteLine(message);
// 在图表中添加新数据点
this.Invoke((MethodInvoker)delegate
{
Series series = chart1.Series[0];
series.Points.AddXY(number,int.Parse(message));
// 如果超过一定数量的数据点,删除最旧的数据点,保持图表显示的数据量有限
if (series.Points.Count > 50)
{
series.Points.RemoveAt(0);
}
});
}
这里我还定义了一个number变量,用以确定折线图的下标。需要注意的是,我们收到的数据应该使用Encoding.UTF8.GetString方法转化成字符串否则会报错。接着我们在百度云中,创建一个新的实例,然后模拟设备,进入MQTT服务端我们需要订阅和我们程序中连接的服务端一样的主题,之后设置我们的消息,点击发布(Publish)可以看到我们成功的发布了消息。同样的我们C#中的服务器也接收到了消息并且在折线图中打印了出来。这样子我们就成功的让我们的C#服务器连接到MQTT云平台啦。
基于STM32系列的气压传感器MS5611——获取温度数据和气压数据
MS5611是一款由瑞士公司MEAS推出的高分辨率气压传感器。它具有以下特点:接口:支持SPI和I²C总线接口。(通过PS脚来选择)内部结构:包括高线性度的压力传感器和超低功耗的24位累加模数转换器(工厂校准系数)。功能:提供精确的24位数字压力值和温度值,适用于高度计、温度计等功能。尺寸:仅有5.0毫米×3.0毫米×1.0毫米的小尺寸,适合集成在移动设备中。 这款传感器采用领先的MEMS技术,具有高稳定性和非常低的压力信号滞后。它在移动高度计、气压计系统、自行车电脑、智能手机等领域有广泛的应用准备工作由于芯片上支持IIC通讯,因此我们打算使用STM32的硬件IIC,我的STM32为STM32F407。我们打开CUBEMX,选择好我们的时钟还有其他设置之后,我们打开我们的硬件IIC并且我们打开串口来进行调试,我们选择IIC1,可以发现是PB6/PB7。并且我们在USART.C文件中加入我们的串口重定向代码,方便我们使用printf函数struct __FILE
{
int handle;
};
struct __FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, struct __FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (unsigned char) ch;
return ch;
}
利用这串代码,我们就可以把我们的串口发送函数重定向到我们的printf函数上。之后我们测试一下,我们的串口发送。我们在MS5611的官方手册中可以看到,MS5611的数据主要由C1~C6这六个校准系数以及D1,D2两个数据组成。我们在向MS5611发送数据转换命令的时候,模块会把对应的信号转换好之后存入PROM寄存器中,我们需要读取PROM的数据来读取这些数据,PROM的地址从0XA0~0XAE 代码编写#define MS5611_I2C_ADDR 0xEE //PS拉高的地址
#define CMD_ADC_READ 0x00 //ADC转换开始
#define CMD_ADC_CONV_D1 0x40 //D1读取命令
#define CMD_ADC_CONV_D2 0x50 //D2读取命令
#define CMD_PROM_RD 0xA0 //PROM基地址
typedef struct {
double temperature;//温度
double pressure;//气压
uint32_t D1;
uint32_t D2;
uint16_t C1;
uint16_t C2;
uint16_t C3;
uint16_t C5;
uint16_t C6;
} MS5611Data;
首先我们定义模块的命令宏定义以及我们的结构体用来存放我们的数据。extern I2C_HandleTypeDef hi2c1;
void ReadMS5611Data(MS5611Data *ms5611Data);
其次定义我们的hi2c1句柄,由于这个句柄的实际定义是在i2c.hd文件中,因此我们需要加上extern来修饰我们的变量接着我们来定义我们的ReadMS5611Data函数,我们分步讲解。 HAL_I2C_Mem_Write(&hi2c1, MS5611_I2C_ADDR, CMD_ADC_CONV_D1, I2C_MEMADD_SIZE_8BIT, NULL, 0, 100);
HAL_Delay(10);
uint8_t buffer[3];
HAL_I2C_Mem_Read(&hi2c1, MS5611_I2C_ADDR, CMD_ADC_READ, I2C_MEMADD_SIZE_8BIT, buffer, 3, 100);
ms5611Data->D1 = ((uint32_t)buffer[0] << 16) | ((uint32_t)buffer[1] <[removed]D2 = ((uint32_t)buffer[0] << 16) | ((uint32_t)buffer[1] << 8) | (uint32_t)buffer[2];
这部分也是同理,我们用这个方式获取D2的值。uint8_t coef_data[2];
for (int i = 1; i [removed]C1 = (coef_data[0] <[removed]C2 = (coef_data[0] <[removed]C3 = (coef_data[0] <[removed]C5 = (coef_data[0] <[removed]C6 = (coef_data[0] <[removed]D2 - ((int32_t)ms5611Data->C5 *256);
double temp = 2000+((double)dt/ 8388608)*ms5611Data->C6;
接着我们按照手册中的方法计算我们的数据,需要注意的是,temp的类型最好选择是double型,否则dt/8388608(1<[removed]C2 * 65536) + ((int64_t)ms5611Data-> * dt) / 128;
int64_t sens = ((int64_t)ms5611Data->C1 <[removed]C3 * dt) / (1 <[removed]D1 * sens/2097152 - off) / (1 <[removed]
基于C#和ESP32的远程示波器(1)—— C#的简单介绍与使用
最近在做一个项目,关于远程采集数据并上传的一套装置, 为此也决定做一期合集用以记录制作流程。 关于硬件方面我选择了ESP32,ESP32有双核240MHZ的主频,并且自带ADC以及DAC,非常方便我们的数据采集与云端上传。 关于上位机方面,我选择了C#,C#作为基于C语言的图形化编程语言,可以有效的帮助我们编写相关的上位机。我也是曾经使用C#完成过一个远程LED点阵控制系统的制作。 本期我们的主要内容是介绍一下C#的简单使用。C#环境 我所使用的C#是在Visual Studio中安装的, 在创建项目中,我们在右边选项中拉到底,点击安装多个工具和功能既可安装我们的C#环境 选择通用Windows平台开发,安装即可。 简单使用 我们创建工程时,选择Windows窗体应用。 进入工程界面,有我们的窗体,左边的工具栏,右边的项目资源管理器。 我们可以从左边的工具栏选择常用的工具到我们的窗体上。 右下角则是关于这些控件的属性,例如大小,位置,字体大小,名称等等。 我们放置一个标签和一个按钮,并修改他们的字体大小。 接着我们双击我们的控件就可以进入相应的编写代码,例如我们双击我们的按钮。 我们可以看到这时候我们创建了一个函数,函数的名称是button1_Click,我们可以理解为这个函数是我们按下按钮后调用的函数,我们在这个函数中编写,标签的值改变。 再点击上方的运行按钮。 这时候就会跳出我们的窗口了。 然后我们点击Hello按钮。 我们可以发现标签的文本发生了改变。 这样子就简单的使用了C#,C#中有很多强力的工具和控件,我们合理的使用这些控件可以很好的帮助我们实现上位机软件的编程。
手机也能电路仿真?PE端的仿真软件推荐
电路仿真是我们在学习电路中起到非常大的帮助,好用的电路仿真软件不少例如Multisim,Protues。但是这些软件都是在电脑中使用,那么有没有什么软件能在手机上很好的使用呢? 方法就是:把手机连上电脑 电路模拟器就是很好的一款能在手机上使用的电路仿真软件(私信 "电路模拟器软件 "获取APK仅安卓用户) 打开电路模拟器,我们在工作区中创建新的电路图就可以使用我们的电路模拟器啦。 在电路图界面有许多元器件我为他们一一做了标识以及左下角的启动按钮。 接着我们简单的测试一下,使用信号源还有电阻构成一个分压电路并运行。 连接好电路之后我们点击左下角运行按钮。 之后选择电路结点,左下角会出现“眼睛”符号,我们点击👀,即可观察改点的波形图 接着我们测试其他的电路,例如RC滤波电路。 可以看到,信号中的交流分量在进过RC低通滤波器之后明显的衰减了。 我们再用这个电路模拟一下运算放大器的使用,构建一个同向比例运算放大器。 可以看到,利用同相比例放大电路我们将峰峰值为2V的正弦信号转化成峰峰值为4V的正弦信号。 除此之外,在主页面中软件也给我们提供了许多事例。这些事例展示了相关器件的使用以及一些经典电路图。 例如电流镜就是一个现实中非常有用的一种电路,可以为我们的电路提供精准稳定的恒流源。 以上就是本期我们分享的软件啦,喜欢的朋友记得👍加关注
友友们有什么开源项目推荐嘛
最近放假回家想做点简单的开源项目玩玩
CCS下载与安装及MSP432的SDK环境配置
全国大学生电子设计大赛中必然有一道题指定为TI公司的单片机,去年2022年浙江省大学生电子设计大赛中控制题中的双车跟随系统就是指定TI公司的单片机。 我们去年选择的是MSP430单片机,额.....MSP430,不好评价懂得都懂,1MHZ的主频,只能说去年车子能跑完正常的圈子实属奇迹了。 TI公司除了MSP430之外还有许多的单片机,而MSP432单片机更是号称对标32,性能指标超过STM32单片机的一款,因此今年为了预防在仪器仪表题上对我们限制单片机,我们决定采用MSP432P401R型号的单片机,去年我也是接触过这个单片机,因此在编程上也有经验。 编程MSP432我们选择的是CCS,定速巡航系统,Code Composer Studio™ 集成式开发环境 (IDE)。 CCS是官方推荐的编译工具,也可以对芯片一键烧录和运行,MSP432上有板载DAP,所以只需要一根数据线即可调试和运行。 CCS的下载可以去Ti公司的官网(CCSTUDIO IDE、配置、编译器或调试器 | TI.com.cn),在设计和开发中选择CCS,之后选择第二个选项,下载对应的系统版本即可。 下载完成后找到对应的安装文件,双击进行安装,其他步骤与正常软件安装时无异,但是注意的是在选择产品中,一定要勾选MSP432这个选项后再NEXT。 安装完CCS,我们还需要安装MSP432SDK,这样子我们就可以使用官方的例程和库文件了。 在这个链接中SIMPLELINK-MSP432-SDK Software development kit (SDK) | TI.com打开MSP432SDK下载地址(打不开的话去CSDN搜一下相关教程)之后安装MSP432SDK,安装完成后,在安装目录下就会有这些文件,比如example就是官方的例程,source是一些驱动文件等等。 有了这个MSP432SDK,我们就可以借此打开例程,选择example文件,由于我们是裸机开发,因此打开nortos,driverlib文件夹中即是我们可能需要的例程,其中有Keil的例程,也有CCS的例程。 我们打开CCS,在Project中选择import CCS Project ,导入CCS例程。选择我们的例程文件夹,点击finished即可导入工作区完成,之后就可以使用我们的例程了。 可以看到资源管理器中,工作区主要包含着MSP432的系统头文件,启动文件,以及主函数,其中例程的主函数均以该例程的名称命名。
百度云物联网之MQTT协议创建设备及获取信息
随着科技的不断进步,智能家居的广泛应用与发展,物联网技术在生活及生产中越来越重要。 物联网(Internet of Things,简称IoT)是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器、激光扫描器等各种装置与技术,实时采集任何需要监控、 连接、互动的物体或过程,采集其声、光、热、电、力学、化学、生物、位置等各种需要的信息,通过各类可能的网络接入,实现物与物、物与人的泛在连接,实现对物品和过程的智能化感知、识别和管理。物联网是一个基于互联网、传统电信网等的信息承载体,它让所有能够被独立寻址的普通物理对象形成互联互通的网络(以上转载自百度百科) 而如何去做物联网,目前网上的教程并不是很多,并且教程时间也都是好几年前的,百度云也更新换代了好多,因此当时的自己摸索了好多,总结了一个使用方法。 MQTT是一种简单常用方便的消息协议,云端设备和云下设备可以在物联网中发布和订阅主题,在主题中发布消息,使得所有订阅该主题的设备均可以收到消息,是一种快捷方便的传输协议。 首先,我们登录百度智能云,在左上角产品目录中,选择物联网 进入界面后选择立即使用,第一次使用物联网核心套件时可能会需要询问付费模式,由于百度智能云每个月前一百万条消息是不会收费的,因此可以选择预收费模式。 之后,我们进入界面后,选择创建IoT Care,创建新的云端设备。 设置好名称,选择按需付费,之后购买产品(只要消息数量不超限制,就不会产生费用)。 点击右边模板,我们选择新建模板来生成我们的设备所需要的协议。 创建好模板后,进入我们创建的模板,除了默认的响应和遗嘱主题之外,我们创建一个新的One(名称任意)主题。模式我们选择高级模式。 返回设备列表,选择新建设备,输入名称后,认证方式选择 密钥认证 ,添加我们刚刚创建的模板后点击创建设备,就会给我们设备的密钥信息。 进入设备后,我们也可以看到我们的设备所有的信息。 下一步我们去生成MQTT服务器所需要的密码和地址。 在百度云的文档中,找到物联网的部分。选择物联网核心套件的文档。在快速入门中,选择“获取连接信息”。 点击蓝色字体,MQTT连接生成器。时间戳填入0,输入我们的设备密钥,一定一定一定注意检查有没有空格,可能我们复制粘贴前,前面会多一个空格,之后就会自动生成MQTT的服务器地址和用户名,我们可以用服务器地址和用户名来链接我们的云上设备,使得设备之间可以通讯。
HC-05蓝牙模块实现双机通讯详细指南
蓝牙技术在我们的生活中应用广泛,在嵌入式的领域中蓝牙技术也可以为我们的通讯,调试起到巨大的作用,因此学会调试和使用蓝牙模块是不可或缺的一项技能。 HC-05是一种双向全工蓝牙串口通讯模块,可以为用户提供蓝牙通讯的功能。 HC-05具有两种工作模式:命令响应工作模式和自动模式。在自动模式下又可以分为主模式(M),从模式(S)以及回环模式(L),在自动模式下,模块将以提前设定好的设置进行工作。 在命令响应工作模式下,模块可以执行所有的AT指令,用户可以利用AT指令向模块发出命令获得模块各式信息以及修改模块各项参数。 如下图连接蓝牙模块,供电选择3.3V,防止电压过高烧坏芯片。 按住蓝牙模块上的EN按钮上电,进入AT指令模式,如果蓝牙模块上的灯慢闪,则进入AT模式成功,此时将串口波特率调制38400,发送“AT”,并且勾选“发送新行”,其他型号的蓝牙模块有些可能不需要勾选发送新行,具体应当查看手册。 正确进入AT模式后,我们发送AT,串口会接收到OK。即AT模式进入成功,我们此时对可以查看AT指令表对蓝牙模块进行配置。 根据AT指令表,我们修改蓝牙模块的名称以及连接密钥,可以看到模块返回的是OK,查询模块波特率,发现其波特率是9600。 此时我们在手机上下载蓝牙串口助手(应用商店搜索即可),下载完蓝牙串口助手后,连接蓝牙并发送数据。注意的是此时我们应该重新对模块上电,使其进入工作模式。 我们利用串口助手,发送ASCLL码为30 31 32 33 34(对应字符为“01234”),观察PC端上蓝牙接收的数据。 当使用两个蓝牙模块互相连接时,我们需要设置主机与从机,我们让蓝牙模块进入AT模式,输入AT+ROLE?此模块当前主从模式,发现当前模式属于从模式(1:主模式,0:从模式,2:回环模式)。 为了两个蓝牙可以互相通讯,我们需要将一个蓝牙设置为主模式,另一个蓝牙设置为从模式,我们发送AT+ROLE=1,将此蓝牙设置为主模式。 我们按照上述的步骤,将另一块蓝牙模块设置为从机。 利用蓝牙模块连接蓝牙模块时,我们采用的是地址链接,在其中一块蓝牙模块上,我们输入AT+ADDR?查询从机蓝牙的地址。 在主机蓝牙中,利用AT+BIND=xxxx,xx,xxxxxx 注意的是利用逗号分开,设置主机蓝牙需要绑定的地址,并使用AT+CMODE=0,将蓝牙设置为指定地址链接模式。 此时断开电源,重新上电,两块蓝牙几秒后进入慢闪,即表示两块蓝牙配对成功,此时我们利用串口助手分别发送数据即可实现两块蓝牙之间的互相通讯。 双机通讯成功。
AD9854数字频率合成器
DDS信号发生器在电子设计大赛中应用十分广泛,而AD9854则是一块功能强大,用途广泛的数字频率合成器。 AD9854内部包括一个具有48位相位累加器、一个可编程时钟倍频器、一个反sinc滤波器、两个12位300MHz DAC,一个高速模拟比较器以及接口逻辑电路。 如图可以看出,AD9854支持100MHZ并行编程和10MHZ串行编程,由于我们使用STM32单片机是利用模拟SPI串行写入数据,因此需要注意的是我们查看的寄存器应当是串行寄存器。 手册中提到,我们需要将SP引脚接到GND,使得芯片工作在串行编程模式。并且A0,A1作为SPI的SDIO和SDO。 我们将A0,A1,A2以及其他控制引脚接入单片机,并将IO口设置成输出模式,利用软件模拟SPI,以向AD9854写入命令。 查阅手册寄存器列表,注意的是列表第一列地址是并行模式寄存器地址,列表第二列是串行模式寄存器地址。 观察寄存器列表,其中串行地址7是配置相关启动参数,我们设置成0x00,即打开比较,默认模式后。配置时钟为倍频。 利用 多位相乘算法 ,将十进制的频率转换为4字节方便后续写入频率。 根据芯片手册,将二进制频率数据写入地址0x02频率字段,并将幅度二进制数据分别写入0x08,0x09 ,I通道以及Q通道幅度字段,使得AD9854可以输出频率幅度可调的正弦信号。 之后DDS就可以正常工作了,同样的,需要配置FM,AM,FSK信号也可以按照芯片手册进行相关配置,这里就不作展示了。
C语言的门牌号——指针
C语言可以说是工科学生必不可少的编程语言了,尤其是嵌入式的应用中C语言更是大厦之基,甚至在我的评价里他相比于数学对于工科生的作用等价。 在学习C语言之初就有一个说法是:学明白了指针,就学明白了C语言。那么什么是指针? 在 计算机 中,所有的数据都是存放在存储器中的,不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间,每一个字节单元对应着一个独一的编号,这个编号被称为内存单元的地址。 如果说把存放数据的空间当作我们家的小区,那么地址就像是门牌号,每家每户的地址都是唯一且固定不变的。 此时如果有一名访客来找你,你可以告诉这位访客 你家的地址,也就是门牌号。访客可以直接通过门牌号来找到你。 在C语言中我们可以通过 & 来获得门牌号也就是变量的地址。并且可以通过 * 去访问这个地址,并进行修改,这叫做引用 与 解引用。 当然也有除了直接寻找门牌号之外,还有一种方式找到你家——问路当你到你朋友的小区时,你不知道朋友住哪里,但是朋友刚刚玩完钢铁雄心没注意时间现在睡着了,那么你可能会询问门口保安:NT的家住哪?保安会说:NT住某某幢某零某。此时这个NT的家起到了和一个门牌号一样的作用或者说NT从某种意义上来说他和NT家的地址是等价的。 而C语言中,NT的家等价于NT家的地址,这叫做指针。指针是一种类型,他的作用就是指向变量的地址并可以读取和修改。 首先,使用指针的前提是变量已经存在(还有一种空指针NULL),就像如果NT不在小区买房,那么门口保安肯定是不知道NT的家在那的甚至会不让你进小区(系统报错)。 此时int * NT = &home 就像是小区业主在登记信息,向大伙说明,这个地址是NT的家,你可以直接问NT的家在哪来访问我。 使用 变量类型 + * + 指针名 = & 变量名 的方式来定义和声明变量,注意的是,指针类型必须进行初始化,哪怕是你说不进去找人,只是空逛逛小区,也就是空指针 如 int * NT = NULL; 既然可以通过NT这个别称来寻找到NT的家,那么同理一个地址可以被多个指针所指向(可能是NT老婆的名字,NT儿女的名字) 此时int * NT = &home 就像是小区业主在登记信息,向大伙说明,这个地址是NT的家,你可以直接问NT的家在哪来访问我。 使用 变量类型 + * + 指针名 = & 变量名 的方式来定义和声明变量,注意的是,指针类型必须进行初始化,哪怕是你说不进去找人,只是空逛逛小区,也就是空指针 如 int * NT = NULL; 既然可以通过NT这个别称来寻找到NT的家,那么同理一个地址可以被多个指针所指向(可能是NT老婆的名字,NT儿女的名字) 那么指针的作用是什么呢? 指针最基本的一个作用是值传递和地址传递。通常我们在定义一个函数的时候,其变量类型是数据变量,如下图所示,x和y的类型都是int,这个a和b叫做行参,即我们在定义函数的时候写的参数,而当我们调用这个函数的时候,传入的参数x和y叫做实参。 那么a,b,x,y到底是什么关系呢?其实我们可以观察他们的地址,我们发现,实际上我们将x,y传入函数的时候,函数新创建了两个变量a 和b 来存放我们x和y的数据并进行运算,等到这个函数运行完毕再释放掉a和b这两个数据(拆迁) 而当我们在函数中改变a和b的值的时候, x和y 的值并没有发生改变(抓周树人,管我鲁迅什么事? ) 然鹅,当我们把函数的数据类型换成指针时,我们就可以在函数中改变外部实参的值。事实上函数中也创建了一个形参,不过这个形参的是一个指针类型的(和实参类型相同)而他和我们传入的实参所指向的地址是同一个地址,因此我们通过解引用行参来修改实参的值。我们把这种叫做地址传递,意思是将指针(变量的地址)作为数据传入函数。
运放输出电阻探究(1)
探究完WL01涡流传感器,上午刚准备去退货,中午模块被旁边的同学玩坏了。工作电压5V,工作电流达到了0.4A。发热量严重。 我仔细询问了其使用情况。发现了几个问题: 第一:其模块供电是由电源供电,输出端接STM32单片机PA0,PA0设置为模拟,无上下拉电。 第二:其PA0口空置存在1.2V左右空置电压。 第三:存在电源还未上电但输出端接PA0的情况。 如图是去年自己设计的一块运放(LCZ-2201)与现今许多运放的结构相似。其输出端是推挽输出的三极管。并且在此电路外部也一般会串联两个二极管至正负电源已起到保护电路的作用,防止电压过大,烧毁芯片。 当学生电源接入电路而不开启时,我们可以用万用表的通断档进行测量,发现其内部是导通。 因此,当我们连接上电源,但不但不打开时,其内部结构等效如下: 此时如果使用者将输出端接入单片机,而单片机IO上存在高于导通压降的电压,就会导致二极管导通,造成过高的电压,也可能使内部输出端电流过大导致内部结构损坏。 所以在使用过程中我们一定要符合规范,需要先供电,再将其接入信号或者输出否则很可能导致芯片因为这个问题导致烧毁。 那么有什么办法可以避免这样子的问题呢? 这时候我们就可以为运放添加输入电阻与输出电阻。 在输入输出端加入两个电阻(跟随器为相同电阻,放大器为接地端电阻等于反馈电阻并联的值)可以有效抑制输入电流,还可以对运算放大器进行电源保护,在不正规操作下,保护运算放大器不被外部电压窜入或者静电烧毁。 中午批评了实验室的学弟一通,无论是使用模块还是芯片还是单片机,出现问题并不可怕,可怕的是出现问题后逃避而不去解决问题,对问题寻根问底以后才能避免这样子的错误再次发生而不是将问题抛给电路抛给模块,从自身出发寻找问题的解决方法才是正确的道路。
多位数据的分组存储与计算与其中的计算机原理探究——补码的意义
今日在某一技术群里水群的时候,看到群友问了一个很有意思的问题如下所示:如果xx和yy是48位变量,将xx和yy分别用XX[]和YY[]来将这些数据分为六段进行保存我们该如何运算其差值。 强调一下仅限于人家是八位运算,不考虑用什么long来运算这些~~ 那么最简单的想法也许就是 aa[i] = xx[i] - yy[i]了 但是这就会遇到一个最大的问题就是—— 假如xx[i] - yy[i] 是负数呢,而aa ,xx,yy都是u8即unsigned char 无符号字符类型的数据,那么假如xx[i]-yy[i]小于零必然会导致程序崩溃。 于是我提出了用一个额外的数组来专门存储这个符号位,众所周知,有符号的八位数据类型,其最高位是符号位,1代表负数,0代表正数,其数据范围是-128~127,而无符号字符型其最高位也用于计数,其范围为 0~256。 看似这个问题解决了,但是实际是不行的,在后续的计算中我们发现蕴含着巨大的问题。 如果我们想要计算石榴煎酒(16-9),6-9不够借1当十,16减9,6减9不够借1当十,16减9..... 好吧言归正传,以16-9举例,我们将数据四个字节四个字节运算,那么高四位就是0001 - 0000 就是 0001 ,低四位是0000-1001 那么我们把符号置1,用高四位的结果减符号,就是0000,低四位是1001-0000是1001就是十进制的9。那么最后的结果就是9啦~~ 很显然,0111才是正确的结果,那么0111和1001是什么关系呢。哦!!!1001是0111的补码。突然间恍然大悟(死去的计算机原理正在攻击我)。 其实在计算机中,负数并不是简单的将数字最高位加上1 来表示的,而是用补码的形式表示的,补码的所有位翻转后+1就是原码了。 哦又想到刚才我们的操作,其实我们将6-9后得到的-3是一个负数,或者说0000-1001得到的负的1001是一个负数,而负数在计算机中的存储方式应该是以其补码的方式存在的。那么我们判断正负之后,如果是正数,那么应该用他的原码格式,如果是一个负数,那么我们就应该保存它的补码。 那么如此我们就可以完美的解决这个问题了。! 但这就结束了嘛(可恶字数才800还能水) 但是,为什么会有补码这么看起来反人类的东西呢??? 这才是这期内容的主题——全减器 几乎所有编程里面的书都会向你介绍补码,反码,原码的概念,但是我几乎没有看到这些书对其做出解释(虽然好像也不重要)。但是当你了解到补码的意义时才会感慨道人类的智慧竟是如此伟大。 这是一个全加器,结构非常简单,Ai和Bi分别是加数作为输入端, Ci-1是上一个全加器的进位,Si作为本位,Ci作为给下一个全加器的进位,第一个全加器的Ci-1是0,再加上七个这样子的全加器就构成了一个八位加法器。结构非常的巧妙,被大量的运用在了计算机的底层,本质上计算机就是再做复杂的运算。 那么如何做减法呢??? 我们可以利用138译码器来实现全减器,亦或者其他的结构。但是总不能我八位减法器就得用八个138译码器吧?? 我们再来看回减法,减法的本质也是加法,比方说3-5其实就是3+-5那么我们能不能用全加器来实现加法呢? 当我们的数据采用补码时,我们即可实现将减法运算利用加法来实现,用补码来表示计算机的负数,使得硬件上可以节省减法器的开发,而我们前面提到,将 补码转换成原码的方式是取反加1 ,那么同样的我们需要计算减法时,只需要对其中的一个加数将其转换成补码,即 减一再取反 ,并且通过逻辑运算单元ALU来控制就可以实现减法运算了。 写到这不由感慨这些知识本是这学期初想和mlm手搓一块CPU而学习来的,可是这个学期事情繁多最终并未付诸实践。几个月前两个少年再研究CPU架构的时候,命运的齿轮就开始转动,也许教育和学习就是有延时性的,并不能当时获得满足,可是当这一切积累起来的时候,总有一个时刻总会为之前了解过的东西而感到惊讶。 这也让我悟了,其实本来没指望在大学的课堂中学到什么,但是没学明白和不了解是两码事,课堂上我所学的不仅仅是知识,而是新的视野。
DCDC电源推荐
有没有什么便宜好用的单电源转双电源方案推荐[看]
WL01涡流传感器
昨天才刚 写完LDC1314电感传感器以为可以轻松等涡流传感器来的,结果今天下午涡流传感器就到驿站了。那么晚上就可以开始动工操作涡流传感器来做硬币检测了。 我们将一元、五角、一角分别置于涡流传感器上,其ADC的值由单片机读到的电压值还是非常稳定且差异较大。我们利用数组分别将采集到的电压做差与这三个值进行比较即可以实现三个种类硬币的判断。 图2 不同同种类硬币的电压值 由下图虚拟示波器也可以看出当我们放上一个一元硬币时,其电压下降明显。 同样,B类盲盒即硬币叠加也可以用相同办法解决。后续视频放视频号中。C类盲盒由于我们只买了一个这样子的模块,等到其他组到了我们尝试二维检测。 开始让我吐槽这个模块! 虽然~我知道电子模块的暴利,也知道其实其中的知识才是宝贵的,但是当我看到这个模块的原理图的时候,着实没绷住。 几个电容一个三极管加一个线圈,虽然P2的丝印和型号都没有标,但是结合4脚GND,8脚VCC,7脚6脚接一起作为输出,5脚输入的结构,我也不难猜出这是一块双运放由于前级是三极管的集电极,因此加上跟随器使得输出信号可以进入单片机后正常工作。 就这些器件,成本撑死5块钱o(TヘTo)。所以要自己掌握技术,才能打破垄断!! 我给前级电路换了一个画法。。 然后,我看着这图,越看越眼熟,越看越眼熟,越看越眼熟,这BYD不是三点式电容振荡器嘛。 其相较于传统三点式电容振荡器有一个输入电容C1(对电路结果影响不大可能是为了滤掉一些直流信号。 但是推导这个电路的时候,不对啊!! 发射级电流高达43mA,同样着代表集电极电压超出电源电压,因此该电路只能工作在饱和区。并且Multisim上得到了验证 三极管PN结均正偏,三极管处于放大饱和状态~于是再看回之前的拍的模块图。很明显的可以看到模块上有四个电容四个电阻。其中102电阻应该是用于发光二极管的限流电阻。那么这个102、103还有01A就是用于三极管正常工作的电阻。我测,有🐕,BYD店家骗我(我说前几天我嫖来原理图没做成功) 我将01A加入电路,但是电路依旧不起作用,发射极和集电极依旧反偏,于是我按照自己的理解重新绘制电路。 于是,在修改电路图之后,并于面包板中搭建。三点式振荡成功。 之后我们只需要加上跟随器,使用 派出 绿波电龙 就可以将交流信号转化成直流信号进入单片机内部ADC采集。 第一 次遇到这么🐕的商家,给客户提供错的原理图,随着研究其原理也让自身获得了很大的提升,更是感受到了只有掌握其核心技术才能防止被垄断(割韭菜)。 好了,我是韭菜,下午就去退款了。
LDC1314电感-数字转换器(1)
在集训中我们尝试挑战2022年Ti杯十月邀请赛中的硬币测量题。题目大致上是在做的一件事就是对于电感的测量。虽然这题的硬件部分我们采用现成的模块使用,但是在搜集资料的过程中有一块芯片却引起了我的兴趣——LDC1314 LDC 1314 具有: 测量多达4个传感器与一个集成电路 12位电感数字转换器(LDC)感应传感器 从引脚说明图中得知,可以通过ADDR引脚的高低来配置LCD1314对芯片地址进行配置。 SD脚为控制端口。 INTB为中断处理引脚。 手册中特地强调了要求DAP引脚接地已为更好的电气特性。 在IIC说明部分中强调要将IIC上拉至高电平(众所周知) LDC1314具有四个通道,可以进行单通道采集也可以进行多通道轮换采样模式,具体模式在后续寄存器部分介绍。 INTB可以用以单通道转换完毕与多通道转换完毕的终端标记。 为了让LC振荡网络稳定,可以设置启动时间,通过设置SETTLECOUNTx寄存器设置启动时间。 该部分讲述了关于LDC1314的驱动电流设置,支持从16uA到1.6mA的驱动电流设置。 值得注意的是LDC1314的IIC是16位设备IIC,因此发送完一次之后要重新启动,继续发送一段数据。 后续就是寄存器的作用与说明,这里不予赘述。
基于电流镜的高精度稳定电流源设计
前言:在前面负载网络测量中,我们需要进行短路点测量。这本质上是在测量一个小电阻。我们的方案是设计一个恒流源,这个恒流源的要求是可以输出一个稳定的电流(50mA),电流不能太大防止导线过热,电流也不能太小使得导线电压过小。 其次电流必须稳定,我们采用仪表放大器对其放大时防止电压变化过大。 为了使电流稳定,我们采用了运算放大器负反馈的方式稳定电流源。 使用运放负反馈电路可以使得负载RL(R4)上有一个稳定的电流,根据运放虚短原理U+ = U- ,即流经R3的电流为(3.3-1.65)/ 1 = 1.65mA 三极管的发射极电流等于基极加上集电极电流之和,因此IE = (1+β) IC 二者电流近似相等。 但是电流1.60mA这个电流对于我们来说实在太小了,我们需要将电流放大。通常放大电流的方法我们会想到使用三极管。可是事实上三极管作为晶体管其电气特性受温度影响很大,那么有什么方法能够精确的放大电流呢?? 这时我就想起曾经设计运放的时候学过的“电流镜”,我们在电流镜的基础上可以设计成比例电流镜,使得电流等比例的进行放大,并且保持电流的稳定(电流镜如其名,像镜子一样复制电流) 恒流源输出后采用比例威尔逊电流镜,使得在兼顾厄利效应的前提下将电流初步放大。 目前还是有两个问题:电流源的负载并不是接地的,而是浮于半空,这是一个浮纳型电流镜。电流的放大倍数依旧不够因此下一步我们需要做的是一个地吐型比例电流镜 但是,当我们放大电流的时候,不得不考虑的是三极管的发热量(虽然不大,保险起见)将输出的三极管换做功率三极管(达林顿管)可以加上散热片散热 在实际测试中,调整比例电阻后,该电流源可以恒定输出60mA的电流长时间工作。 值得注意的是,理论上的恒流源是不能开路的,而我们的ADC测得是R5的电压,当负载开路时R5的电压接近电源电压,如果电源电压过高则会导致ADC和芯片损坏。因此选择电源电压为3.3V也可以间接起到保护电路的作用。理论上该电流源可以工作在更高的电压空间。在调整电流电压和比例电阻后理论输出电压甚至可以高达5.5A.(没实践过,只试过3.3 60mA的)
有源二分频放大电路的软件实现法
我们需要制作一个预处理电路,使得信号源在100HZ~2KHZ、10mv~100mv变化时,C点的频率随信号源频率变化,而幅度保持不变。为此队友在努力设计移相器电路,AGC压控增益电路。 而我有一个异想天开的想法:采频电路配合DDS完成预处理电路,使用数字电位器实现高通滤波器和低通滤波器。 前面几篇文章介绍了AD5933具有频率分辨率0.1HZ,四档稳定输出的DDS,因此我们只需要获得信号源的频率,将频率写入AD5933即可获得一个频率随信号源变化,幅度不变的信号。 频率采集电路的实现: 由于输入电压很小:10mv,因此我们的设计思路可以是将输入信号经由一个BJT放大电路,使用AD817(普通运放也可)跟随后由施密特触发器进行整形,利用单片机的输入捕获进行采集。 其中BJT选用S8050 NPN三极管,施密特触发器选用74LS14(NE555也可)经过选用电阻后,输入信号在5mv~3V 间均可实现方波输出,频率5HZ~5MHZ间波形不失真。 PS:该电路本是今年四月份用于我们学院院赛,因此设计出来是为了正弦信号转方波信号,并且使用范围是>1MHZ的,AD817跟随主要是为了阻抗匹配消除方波的振铃现象 由单片机输入捕获后,将频率写入AD5933,使5933输出相应电压。 数字滤波器采用前段时间购买的MCP41010数字芯片。 MCP41010是单路8位分辨率数字电位器,通过 SPI接口 可控制电位器阻值分配。 更具要求,我们需要在2KHZ时衰减-3DB,4KHZ时衰减-24DB,8KHZ时衰减-48DB,因此我们更具衰减要求,在数字电位器部分代码进行分段。 我们使用数字电位器,在不同频率下对数字电位器的阻值进行不同的调整,使得其可以对应我们的衰减量。 可以看到2KHZ的时候,有效值为1.67V,而1KZH时为2V,根据计算衰减约为0.83而-3DB衰减约为0.81,二者极为接近。 在4K频率下,有效值为0.141mv,而理论-24DB的衰减下,2V的信号衰减后约为0.144V,所以成功利用数字电位器制作出符合要求的低通滤波器。 误差分析: 由于该数字电位器是八位精度的,精度控制方面稍欠无疑。此外我们的数据未经过校准,数字电位器的理论阻值在0~10k,实际使用过程中发现实际阻值应该在25~9.6k,中间阻值在4.88K,因此如果我们要更加精确的进行衰减,应当先对数字电位器的阻值进行校准匹配之后计算。 我们可以使用这种方式实现题目的所有要求,但是这就失去了电子竞赛的魅力~可是随着时代的发展越来越多的功能集成到了一块芯片上面,电子竞赛中也出现了许多更强大的嵌入式如linux,FPGA,AI也在电赛中大放异彩,因此跟随时代的潮流,掌握基本功才是新时代的立身之本!!
Tensorflow(深度学习)踩坑大全(1)
按照网上的流程配置号Anaconda和下载好Tensorflow之后,打开NoteBook之后尝试导入pandas库总是提示No module named . 尝试过在Anaconda Promtp中使用pip install pandas之后并没有效果, 使用import sys 导入本地路径也没有效果。 解决办法: 在Anaconda中进入虚拟环境使用 conda install pandas解决 使用成功! 同样的,在之后使用maplotlib也出现了同样的错误,我们使用同样的方法重新下载matplotlib 使用正常,因此我们在使用Anaconda的时候使用pip安装库的时候,应该进入虚拟环境后使用Conda install ....安装,确保我们的库安装到了虚拟环境中。
阻抗转换器AD5933使用总结及分析(2)
前言: 上一篇制作完AD5933的使用后,成功的使用AD5933完成RCL负载网络的测试以及网络类型的判断。重新研读AD5933的数据手册时,我们产生了一个大胆的想法——即利用AD5933的硬件资源直接帮我们对指定信号进行离散傅里叶变换。 在AD5933的手册中我们可以发现,这块芯片是由可编程DDS产生激励信号,经过负载后进入反相放大器、PGA增益、低通滤波器后由内部进行AD采样与1024-Point DFT,其中系统相位误差主要由低通滤波器引起,RFB充当反馈电阻用来放大输入信号。 于是根据上述这些特征,我们尝试将信号直接输入至RFB引脚使观察AD5933寄存器返回数据。 输入波形为10k 500mVp-p时,通过串口打印的数据,我们发现其在10K的幅度最大,且经过拟合后幅值为472+mv 我们将信号改至30KHZ,1500mVp-p 得到的数据为: 结果显而易见,我们可以利用AD5933给定的信号进行DFT频谱分析获得我们想要的信号,于是进行了更多的实验。 我们尝试使用AM调制信号 如图,输入信号为5KAM信号载波为30K 1500Vp-p 在matlab上面或许看不出来,但是我们仔细观察数据: 我们会发现,尖峰出现在了30K+-5K的频率上,符合AM信号的频域特征,为了区别明显,我们使用在20K频率上调制进50K信号进行分析. 我们也可以清楚的看到在50-20K和50+20K的两个频率上面有着峰值,因此,我们可以使用AD5933对信号进行对应的特征提取。 在10K的方波信号中,我们发现对其DFT后,方波频率的每一个基波上均是符合傅里叶变换的 在10K与50K相加的信号中,也可以较好的分离出10K信号和50k信号 AD5933其硬件功能强大,我们可以妥善的使用这块芯片来帮助我们获得信号的频谱信息并从中获得我们想要的信息。
阻抗转换器AD5933使用总结及分析(1)
2023年6月,在我们学校电子设计竞赛校赛中我们设计一个RCL测量仪,使用AD9833进行扫频后,利用异或门得到前后信号的相位差,以及使用峰值检波获得有效值。 老师也评价我们的作品像是重庆立交 但是在实际使用后测量精度感人,但是这种扫频的思想确实大有可说。AD5933模块的制作 AD5933是ADI公司发布的一款集12位/1Mbs ADC、12位信号发生器、内部硬件1024个DFT、标准I2C通讯协议的阻抗转换器。 于是我们决定使用AD5933进行网络分析,AD5933的芯片价格在49+左右,但其模块在TB上一块的价格可以高达498元,综合考虑(我的钱包替我考虑)我们觉得采购一块芯片自己制版。 经过某一次教训,这次的制板上在电源部分考量极深,将数字地与模拟地,数字电源与模拟电源均进行了隔离并进行了0欧姆电阻分离(因为芯片的引脚上数模分开了)但是在实际使用过程中发现,数字地与模拟地的合并对芯片功能影响不大。于是焊接过程中改用磁珠连接。 AD5933使用 如果可以,我不想再经历这几天调模块的过程了!!! 在使用过程中,参考芯片手册进行I2C对芯片进行启动 参照芯片手册的寄存器部分对0x80寄存器写入0x20启动频率扫描,设置寄存器DDS输出电压为2Vp-p 但是这BYD死活不输出,经过了大半天的折磨,感谢我的好队友买的逻辑分析仪(事实证明这玩意,一万年不用,一用就是省一万年!)我发现在I2C时序上,通讯地址、命令、数据均正确,但是Stop停止信号并不能正常触发。于是我翻看商家(其他组有买498的模块)给的例程中发现竟是其I2C时序出现了问题,于是在修改I2C时序后,芯片可以正常的输出电压。证明芯片调试正确。AD5933数据处理 在AD5933的芯片手册中提到,芯片返回了阻抗测量结果的实部、虚部我们通过对实部虚部进行处理获得了导纳,取倒数得到阻抗,但是这个得到的阻抗需要进行校准,官方提供了如下的校准过程以及二点法校准过程。 我们在校准过程中发现,阻抗的输出结果与实际的阻抗结果成线性关系,于是研究了其原理后,我们使用了matlab测量纯阻性数据后进行了拟合,获得了阻抗数据以及对阻抗结果和频率的关系进行了数据修正 在实际使用中测量阻抗过程,纯阻 性 器件的测量结果与实际结果误差小于5%。并且我们也发现了测量不同种类的器件的时候扫频结果具有很大差异!! 通过对扫频结果的分析,加上数据处理,可以成功的判断出器件的类型以及精确的测量出器件的标值,精度在1%左右!
STM32软件中断——数组越界异常中断
这两天遇到一个问题,大概就是程序跑一半嗝屁了,然后嗝屁在了一个非常奇怪的地方。 首先呢,我是怀疑变量出问题了,因为很显然,我们卡在了赋值这里,难不成是double类型的赋值出现了问题? 首先我尝试将double类型修改为了float类型,这里虽然觉得很大的可能并不是这个的原因,但是没有办法,程序确确实实卡在了这么个奇怪的地方,我当然也怀疑单片机在处理双精度浮点数是不是出问题了。 不过随着我修改完数据类型,问题并没有得到有效的解决,程序依旧是在这里卡住。 不过有一点非常奇怪,程序是在这个地方卡住没有错,但是卡住的位置会在这两个语句之间切换,就是可能是这条语句,也可能下一条语句!这时候我已经有一点懵逼了,不过为了确认程序卡在了哪里(通常这个情况是进异常中断了)我们暂停程序,查看程序指针当前的位置。 void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
我发现程序指针所在的位置非常的奇怪,程序指针这时候卡在的地方是一个异常中断没错,但是这个中断回调函数的所在.c文件居然是dma的附属文件,没错这份工程是开了ADC的DMA采样的,采样的数据是传输进入DMA的。 这说明问题的主要的原因和DMA有关系,并且查看关于这个中断的说明造成这个中断的主要原因是:未完成硬件初始化而使用硬件数据越界野指针 其实这三个的问题都可以归类为指针问题,而由于我的程序是初始化部分是正常执行的,那么可以排除硬件初始化未完成导致的异常。 至于野指针也几乎没有出现使用指针而未声明的情况。 因此大概率是数组越界导致的原因,结合出现在DMA文件的报错,猜测可能是由于DMA传输的时候,缓存区和缓存区大小不匹配,导致传入数据的时候,索引超出发生数组越界错误。HAL_UART_Transmit (&huart1,(uint8_t*)message1,strlen(message1),100);
end();
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
HAL_ADCEx_Calibration_Start(&hadc1); //ad校准
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)ADC_Value,1024);
之后去查看DMA的缓存区,发现其数组大小是100(其实这时候我就知道了)因为我之前使用ADC信号的缓存区大小通常是1024的,再不济也是2的倍数,因为这样子好方便计算机处理。 所以这里的主要原因还是在于DMA传输的时候数组越界问题。 而ADC的触发采样则正好是定时器触发,定时器触发越界之后刚好卡在了那段话。 这类也提醒大家,一定要注意编程习惯,如果在编程中我们习惯性的使用宏定义的方式来编程,就能即方便而又能有效的规避掉这类的错误。 同样的关于STM32的中断源除了我们常见的硬件中断例如定时器,外部中断等等。 还不能忽略了其软件中断,包括一些硬件错误,未知命令错误以及我们程序员自己出现的错误,因此关注我们的中断错误类型可以有效地避免我们的程序出现很多异常的错误。也可以帮助我们更快地修改我们的异常代码。
STM32基于CubeMX的多通道ADC采样
之前的文章陆陆续续介绍了STM32中的ADC采样与基于DMA的快速采样,本期我们介绍使用ADC的多通道采样。(好耶一篇文章拆开成三篇我真是天才) 首先我们需要明白一个ADC外设通常有许多个通道,合理的使用这些通道可以实现多个采样点的采样和利用多通道弥补采样率不足的问题。 首先我们需要知道,我们并不能直接获取某个通道的值。 在CubeMX中我们开启两个(或多个)ADC通道 开启扫描模式和离散转换模式,重点是这个离散转换模式。 允许在单个触发事件中执行多个ADC转换,而不是在每个触发事件中执行一次转换。这对于需要连续获取多个通道的数据时非常有用,因为可以在单个触发事件中获取多个通道的数据,而不需要多次触发。 这里的触发事件我们可以选择软件触发 这样子我们调用HAL_ADC_Start的时候就会开启ADC转化。 在下面的规则中规定我们的转换规则,这里我们是先测量Channel13再测量Channel11,我们调用HAL_ADC_GetValue的时候就会按照13->11->13->11.....的顺序获取我们的ADC的不同通道的值。 所以假如我们开启了4个通道,那么我们的多通道采样代码即可这样子写: uint32_t adc_values[4]; // 存储4个通道的ADC值
while (1)
{
// 开始ADC转换
HAL_ADC_Start(&hadc1);
// 依次转换4个通道
for (int i = 0; i < 4; i++)
{
// 等待转换完成
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
// 获取ADC值
adc_values[i] = HAL_ADC_GetValue(&hadc1);
}
HAL_ADC_Start(&hadc1);//再次开启采样
// 在这里处理ADC值
// adc_values数组包含了4个通道的ADC值
}
}
这样子我们就可以获取四个通道的ADC采样的值了。 同样,如果当我们配置了多通道DMA的时候,并且设置定时器实现采样率设置。 我们需要明白的是,DMA只是帮助我们为ADC的数据提供存储区,这样子就不需要利用MPU进行读取数据存放数据的操作了。 因此配置DMA并不影响ADC的采样规则,只是存入DMA缓存区的顺序发生了变化。 for(int i = 0;i[removed]
在思考中进步,设计一款基于STM32迷你三极管放大倍数测量仪(仅用4个器件)
今天突发奇想,想到了以前测三极管放大倍数的电路,觉得异常的抽象,于是想重新做一下测三极管放大倍数的电路。 于是想用一个恒流源来测量三极管的放大倍数。 我们可以给予一个恒定的电流源,经过三极管放大一定倍数之后,测量Rc的电阻压降来计算放大倍数。 需要注意的是,这里的电阻Rc不能太大,否则会导致提前饱和。 这里关于恒流源的实现本来想使用很早很早的一期文章中的电路。 但是一想,为什么需要电流镜呢,我只需要前面的电流源的部分就可以了,于是对这个电路进行简化,只取前面的电流源的部分。 不过这里我又出现了一个疑问,这个三极管是为了增大运放的驱动能力,但是我并不需要强大的驱动能力呀,我希望给三极管的电流甚至不到1mA,那么是不需要这个三极管的呀。 于是就是用了运放作为跟随器提供一个稳定的电压源,但是都稳定的电压源了,而且还是小电流,为什么不选择使用稳压二极管???? 于是乎只用了四个器件的三极管放大倍数测试装置就完成啦。 这里解释一下为什么我们去测R3的电压,而不是在集电极上拉一个电阻去测集电极的电压。 假如我们直接测量集电极电压,那么就会发现当我们不接入三极管的时候,这个点的电压是电源电压,就会导致电压过高,可能损坏单片机的IO口,因此我们选择测量三极管的发射极电压。 之后我们使用STM32配置双通道采集,即可实现采集两路的电压值,之后根据我们的公式来计算放大倍数,当然也可以算一下导通压降,或者判断导通状态等。 while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
float Vb,Ve,Mag,Ib,Ie;
Ve = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Start(&hadc1);
HAL_Delay(50);
Vb = HAL_ADC_GetValue(&hadc1);
Ve = Ve*3.3f/4096;
Vb = Vb*3.3f/4096;
Ib = (2.87-Vb)/15;
Ie = (Ve)/0.15;
Mag = Ie/Ib - 1;
printf("Mag:%.4f\r\n",Mag);
HAL_ADC_Start(&hadc1);
HAL_Delay(1000);
}
利用万用表的三极管档测量一下三极管的放大倍数。 本来想写误差在百分之十左右的,突然想起来,上面串口的数据是在室温下测量的,但是写文章的时候万用表是开了空调,温度降了蛮多了,然后我就拿出板子又测了一遍,果然还是比较精确的。
单片机如何驱动直流电机
直流电机是很多初学者都会使用的一个电子元件,我们常说的马达也就是一种直流电机。 本期我们介绍一下单片机如何驱动一个直流电机。 常见的直流电机由内部的转子和定子组成,通电线圈产生磁场利用磁场间的相互作用力转子转动。因此根据安培力公式,F = BIL。 我们知道电机的转速和工作电流有关,而电流的方向则会影响转子的受力方向也就是旋转方向。 然而,单片机的输出电流有限,通常只有几MA,而常见的电机通常需要消耗非常大的电流,例如我手上的一款直流减速电机,其空载(没有任何负载)的电流大小为0.05A,这已经超出了单片机可以提供的电流范围,其满载电流大小能到达1.2A。 因此为了解决单片机无法提供过大电流的问题,我们需要引入电机驱动。 电机驱动的种类很多,但其其目的还是为了提供足够的电流大小使得直流电机可以正常的工作。 其内部结构通常是H桥,即全桥驱动。 通过操控四个MOS管的打开顺序,我们可以使得有足够大小的电流通过电机。 全桥电路可以通过MOS管的打开顺序来控制电机的旋转方向,例如Q1和Q4同时导通时电流方向是从左往右经过电机。 而当Q3和Q2同时导通的时候,就会让电流的方向改变。 因此我们就可以通过单片机的IO高低电平来控制电流方向了,而前面我们说过,直流电机本质上没有正负的区别,其电流方向影响转动方向。 因此利用全桥电流即可以通过单片机的IO来控制电机的正反转,也保证了足够的电流大小使得电机可以正常工作。 但是全桥电路有一个致命的缺点,其Q1,Q2以及Q3和Q4并不能同时导通 否则电流会通过两个MOS管直接导通到地,使得MOS管烧毁,因此为了避免这种情况的出现我们通常会在硬件或者软件上面进行避免,硬件上面可以采用非门来防止Q1和Q2同时导通的情况,软件上面可以采用互补PWM波来避免MOS管同时导通的情况。 例如STM32中就有互补PWM波的设置,这里说明一下采用PWM波的原因是利用改变PWM的占空比来实现对于电机的调速,例如我们的电机是高电平转,那么占空比越低,那么代表着高电平的事件越少,相对的就是我们的电机转的时间越少来降低电机速度。 互补PWM的存在就可以使其两个引脚输出完全相反的PWM。 这个PWM会出现死区(以后再讲) 然而随着集成芯片的广泛使用,现在的大部分电机驱动也用集成芯片来控制。例如L298N,TB6662等等驱动芯片都被广泛的使用,不过在挑选的过程中也需要根据我们实际的使用电流来选择驱动型号。不然也会出现电流不够的情况。 同样的直流电机有许多的类别,例如步进电机相比普通的直流电机具有更高的控制精度,伺服电机的闭环控制,直流减速电机通过减速齿轮箱获得更大的扭矩等等。