十年前接触生物电子让我对电子产生浓厚的兴趣,让我感到电子科技的博大精深无所不能。最近用stm32和C#实现心电监测,分享给大家一起探讨,我也把这些技术资料整理下。

原理图

心电前端采集电路采用仪表放大器,仪表放大器对于共模干扰有很强的抑制力,适合做心电采集前端电路。传输部分采用USB实现虚拟串口和上位机对接,具体电路如下所示

PCB

上位机程序

实物如下

单片机采用stm32内部AD采集,关键程序如下

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "usb_lib.h"
#include "hw_config.h"
#include "usb_pwr.h"
#include "usb_prop.h"
#include "bsp_usart.h"
#include "bsp_adc.h"extern char USB_TX_data[512],USB_RX_data[512];
extern u8 USB_Tx_Counter,USB_Rx_Counter,USB_TX_flag,USB_RX_flag;
extern char U1_TX_data[512],U1_RX_data[512];
extern u8 U1_Tx_Counter,U1_Rx_Counter,U1_TX_flag,U1_RX_flag;extern short    AD_BUF[1024];
extern unsigned int TIM3_count;
extern char TIM3_flag;void RCC_HSI_Configuration(void)
{RCC_DeInit();//??? RCC?????????RCC_HSICmd(ENABLE);//??HSI  while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)//??HSI????{}if(1){FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);FLASH_SetLatency(FLASH_Latency_2);RCC_HCLKConfig(RCC_SYSCLK_Div1);   RCC_PCLK1Config(RCC_HCLK_Div2);RCC_PCLK2Config(RCC_HCLK_Div1);RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_12); RCC_PLLCmd(ENABLE);//??PLL???????,????????while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);  while(RCC_GetSYSCLKSource() != 0x08){}}
}int main(void){     u16 i;u16 temp;u8 usbstatus=0;    RCC_HSI_Configuration();delay_init();            //ÑÓʱº¯Êý³õʼ»¯     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //ÉèÖÃNVICÖжϷÖ×é2:2λÇÀÕ¼ÓÅÏȼ¶£¬2λÏìÓ¦ÓÅÏȼ¶LED_Init();USART1_Config();ADC_Config();delay_ms(10000);USB_Port_Set(0);   delay_ms(50);USB_Port_Set(1);   Set_USBClock();   USB_Interrupts_Config();    USB_Init();   while(1){if(TIM3_flag==1){GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);TIM3_flag=0;temp=ADC_GetConversionValue(ADC1);USB_USART_SendData(0x55);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSBUSB_USART_SendData(0xaa);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSBUSB_USART_SendData(temp>>8);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSBUSB_USART_SendData(temp);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSB USB_USART_RX_STA=0;GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);}if(USB_RX_flag==1){USB_RX_flag=0;}if(usbstatus!=bDeviceState)//USBÁ¬½Ó״̬·¢ÉúÁ˸ıä.{usbstatus=bDeviceState;//¼Ç¼ÐµÄ״̬}}
}
void ADC_Config(void)
{GPIO_InitTypeDef  GPIO_InitStructure;NVIC_InitTypeDef   NVIC_InitStructure;ADC_InitTypeDef   ADC_InitStructure;TIM_TimeBaseInitTypeDef         TIM_TimeBaseInitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1  , ENABLE );RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3 , ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//Ä£ÄâÊäÈëGPIO_Init(GPIOA, &GPIO_InitStructure);ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  ADC_InitStructure.ADC_ScanConvMode = ENABLE;  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1;  ADC_Init(ADC1, &ADC_InitStructure);  ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);ADC_Cmd(ADC1, ENABLE);  ADC_ResetCalibration(ADC1);  while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1);  while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); TIM_DeInit(TIM3);        //½«ÍâÉèTIM3¼Ä´æÆ÷ÖØÉèΪȱʡֵ  TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1 ;    //ÉèÖÃÁËʱÖÓ·Ö¸î(Tck_tim) TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up ;   //Ñ¡ÔñÁ˼ÆÊýÆ÷ģʽ(TIMÏòÉϼÆÊýģʽ)  TIM_TimeBaseInitStruct.TIM_Period = 999 ;       //É趨¼ÆÊýÆ÷×Ô¶¯ÖØ×°Öµ,È¡Öµ·¶Î§0x0000~0xFFFF   TIM_TimeBaseInitStruct.TIM_Prescaler = 47 ;    //ÉèÖÃÓÃÀ´×÷ΪTIM3ʱÖÓƵÂʳýÊýµÄÔ¤·ÖƵֵΪ(7199+1),È¡Öµ·¶Î§0x0000~0xFFFF TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct ) ;       TIM_ClearFlag(TIM3, TIM_FLAG_Update);         //Çå³ýTIM3µÄ´ý´¦Àí±ê־λ    TIM_ITConfig(TIM3, TIM_IT_Update,ENABLE);     //ʹÄÜTIM3ÖÐ¶Ï  TIM_Cmd(TIM3, ENABLE);         //ʹÄÜTIM3ÍâÉè  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //NVIC_Group:ÏÈÕ¼ÓÅÏȼ¶2룬´ÓÓÅÏȼ¶2λ  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;    //ÅäÖÃΪTIM3ÖÐ¶Ï  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //ÏÈÕ¼ÓÅÏȼ¶Îª1  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;   //´ÓÓÅÏȼ¶Îª2  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;      //ʹÄÜÖжÏͨµÀ  NVIC_Init(&NVIC_InitStructure);
}

上位机采用C#,C#开发window系统应用程序非常方便,程序如下

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace WindowsFormsApplication4
{public partial class Form1 : Form{private StringBuilder sb = new StringBuilder();     //为了避免在接收处理函数中反复调用,依然声明为一个全局变量long AD_num = 12;long LD_num = 1;long MD_num = 11;long QP_num = 0;int QP_flag = 0;long uart_count = 0;private const int Unit_length = 32;//单位格大小private const int X_End = 1024+512+256+48;//Y轴最大数值private const int Y_End = 512+256+128;//Y轴最大数值private const int X_Start = 48;//Y轴最大数值private const int Y_Start = 128;//Y轴最大数值private const int MaxStep = 33;//绘制单位最大值private const int MinStep = 1;//绘制单位最小值private const int StartPrint = 100;//点坐标偏移量private List<int> DataList = new List<int>();//数据结构----线性链表private Pen TablePen = new Pen(Color.FromArgb(0x80, 0x00, 0x00));//轴线颜色private Pen LinesPen = new Pen(Color.FromArgb(0x00, 0x80, 0x80));//波形颜色public Form1(){this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint |ControlStyles.AllPaintingInWmPaint,true);//开启双缓冲this.UpdateStyles();InitializeComponent();System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;TablePen.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDotDot;SearchAndAddSerialToComboBox(serialPort1, comboBox1);}private void SearchAndAddSerialToComboBox(SerialPort MyPort, ComboBox MyBox){                                                               //将可用端口号添加到ComboBoxstring Buffer;                                              //缓存comboBox1.Items.Clear();                                        //清空ComboBox内容//int count = 0;for (int i = 1; i < 30; i++)                                //循环{try                                                     //核心原理是依靠try和catch完成遍历{Buffer = "COM" + i.ToString();MyPort.PortName = Buffer;MyPort.Open();                                      //如果失败,后面的代码不会执行// MyString[count] = Buffer;comboBox1.Items.Add(Buffer);                            //打开成功,添加至下俩列表MyPort.Close();                                     //关闭}catch{}}}private void button1_Click(object sender, EventArgs e){try{//将可能产生异常的代码放置在try块中//根据当前串口属性来判断是否打开if (serialPort1.IsOpen){//串口已经处于打开状态serialPort1.Close();    //关闭串口button1.Text = "打开串口";button1.BackColor = Color.ForestGreen;comboBox1.Enabled = true;uart_count = 0;}else{//串口已经处于关闭状态,则设置好串口属性后打开comboBox1.Enabled = false;serialPort1.PortName = comboBox1.Text;serialPort1.BaudRate = 115200;serialPort1.DataBits = 8;serialPort1.Parity = System.IO.Ports.Parity.None;serialPort1.StopBits = System.IO.Ports.StopBits.One;serialPort1.Open();     //打开串口button1.Text = "关闭串口";button1.BackColor = Color.Firebrick;}}catch (Exception ex){//捕获可能发生的异常并进行处理//捕获到异常,创建一个新的对象,之前的不可以再用serialPort1 = new System.IO.Ports.SerialPort();//刷新COM口选项comboBox1.Items.Clear();comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());//响铃并显示异常给用户System.Media.SystemSounds.Beep.Play();button1.Text = "打开串口";button1.BackColor = Color.ForestGreen;MessageBox.Show(ex.Message);}}private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e){int num = serialPort1.BytesToRead; byte[] received_buf = new byte[num];int[] show_buf = new int[num];float show_data = 1;serialPort1.Read(received_buf, 0, num);           if (num == 4){uart_count = uart_count + 4;show_data =  ((long)(received_buf[2] << 8) + (long)(received_buf[3]))*768/4096;show_buf[0] = (int)show_data;DataList.Add(show_buf[0]);//链表尾部添加数据Invalidate();            //刷新显示sb.Clear();try{//因为要访问UI资源,所以需要使用invoke方式同步uithis.Invoke((EventHandler)(delegate{textBox1.Clear();textBox1.AppendText(uart_count.ToString("F2"));}));}catch (Exception ex){//响铃并显示异常给用户System.Media.SystemSounds.Beep.Play();MessageBox.Show(ex.Message);}}}private void Form1_Paint(object sender, PaintEventArgs e)//画{String Str = "";System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();e.Graphics.FillRectangle(Brushes.White, e.Graphics.ClipBounds);//Draw Y 纵向轴绘制for (int i = 0; i <= (X_End - X_Start) / Unit_length; i++){e.Graphics.DrawLine(TablePen, X_Start + i * Unit_length, Y_Start, X_Start + i * Unit_length, Y_End);//画线gp.AddString(i.ToString(), this.Font.FontFamily, (int)FontStyle.Regular, 12, new RectangleF(X_Start + i * Unit_length - 7, Y_End + 4, 400, 50), null);//添加文字}//Draw X 横向轴绘制for (int i = 0; i <= (Y_End - Y_Start) / Unit_length; i++){e.Graphics.DrawLine(TablePen, X_Start, Y_Start + i * Unit_length, X_End, Y_Start + i * Unit_length);//画线// if (i == 17)           break;gp.AddString((((12 - i) * Unit_length).ToString() ), this.Font.FontFamily, (int)FontStyle.Regular, 14, new RectangleF(X_Start - 50, Y_Start + i * Unit_length - 8, 400, 50), null);//添加文字}e.Graphics.DrawPath(Pens.Black, gp);//写文字if (DataList.Count - 1 >= (X_End - X_Start))//如果数据量大于可容纳的数据量,即删除最左数据{DataList.RemoveRange(0, DataList.Count - (X_End - X_Start) - 1);}for (int i = 0; i < DataList.Count - 1; i++)//绘制{e.Graphics.DrawLine(LinesPen, X_Start + i, Y_End - DataList[i], X_Start + (i + 1), Y_End - DataList[i + 1]);}}}
}

stm32实现心电监测-原理图单片机程序C#上位机程序相关推荐

  1. stm32f103上位机程序编写笔记

    CSDN话题挑战赛第2期 参赛话题:学习笔记 学习之路,长路漫漫,写学习笔记的过程就是把知识讲给自己听的过程.这个过程中,我们去记录思考的过程,便于日后复习,梳理自己的思路.学习之乐,独乐乐,不如众乐 ...

  2. 详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,以及常见问题或注意事项解答,本文主要以匿名上位机为例,适合新手和小白

      本文主要内容:详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,帮助我们调节一些参数,比如电机PID的调节.波形融合等,以及在我们写通信协议的时候 ...

  3. STM32学习之旅④ USART串口和上位机通信

    STM32系列博客: STM32学习之旅① 开发环境搭建 STM32学习之旅② 固件库的使用及工程模板的建立 STM32学习之旅③ 从点灯到代码移植 STM32学习之旅④ USART串口和上位机通信 ...

  4. STM32开发 -- Visual Studio C++编写串口上位机

    打算使用Visual Studio编写一个串口上位机程序,然后进行测试. 原来的上位机各种BUG,受不了了.自己写一个得了. 一.创建 MFC 工程 选择MFC应用: 应用程序类型选择 基于对话框: ...

  5. 以太网采集欧姆龙PLC DM数据并存入ACCESS 使用C#编写上位机程序

    以太网采集欧姆龙PLC DM数据并存入ACCESS 使用C#编写上位机程序,通过以太网使用FinsTCP协议读取欧姆龙PLC DM区数据. 附图是程序界面,只要输入PLC IP地址.DM区起始地址号和 ...

  6. PLC上位机程序开发

    目标: 1.通过MC协议进行PLC上位机程序的编写 2.将学到的内容:异常捕获,tkinter显示图像 异常捕获十分方便程序开发时,当未每一个函数添加抛出异常时,可以方便判定错误出现的函数 3.生成e ...

  7. 选煤厂集控程序 含s7-300plc程序+wincc上位机组态程序

    选煤厂集控程序 含s7-300plc程序+wincc上位机组态程序 画面设计美观 程序优化的非常好 编号:4780651684444967unlock

  8. 自己用C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序

    自己用C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序,PLC源程序也附上,是学习C#和三菱PLC通信的好例子,有对辅助继电器M,对单字,双子D的读写,IO的监控,报 ...

  9. C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序

    自己用C#写的控制三菱FX5U PLC(三菱任何系列都通用,网口,串口都行)的上位机程序,PLC源程序也附上,是学习C#和三菱PLC通信的好例子,有对辅助继电器M,对单字,双子D的读写,IO的监控,报 ...

最新文章

  1. GridView 72 般绝技
  2. 游戏中的AI及实用算法逻辑
  3. [导入]Ajax使用初步
  4. Django框架-Form组件
  5. 央视牵手搜狗,AI合成主播为3·15晚会预热
  6. matlab 神经网路,matlab神经网络的工程实例(超级详细)
  7. DHCPV6地址获取交互
  8. 高速PCB电路板的信号完整性设计
  9. 积水识别 工地积水识别
  10. mysql 监控_【MySQL】MySQL监控工具 mysql-monitor
  11. 买家用投影仪应该关注哪些数据?
  12. 2022年全球注释软件行业分析报告
  13. [渝粤教育] 郑州工程技术学院 大学计算机基础 参考 资料
  14. 空间三角形_如何改造三角形小屋,营造舒适空间?
  15. 花了几万写了一份几十页的商业计划书投资人会看吗?
  16. 解决城市交通压力,浅析车载监控技术在公交监管中的应用
  17. vertx 异步编程指南 step8-使用RxJava进行反应式编程
  18. 职称计算机技巧集锦,PowerPoint2003使用技巧集锦(7)
  19. 树莓派安装ubuntu18.04
  20. ReLuSeLu其他

热门文章

  1. 关于汽车悬挂系统减震控制的进一步研究(自动控制原理课程设计小论文)
  2. 三款软件,让你的文字转语音更简单
  3. ES8中对字符串补白的方式
  4. C语言--实现汉诺塔【图文讲解,附代码】
  5. 大数据Hadoop之——智能数据分析可视化BI软件 FineBI
  6. 无需Avatarify 无需剪辑工具 一键生成多人版 “蚂蚁呀嘿“视频
  7. week6:Diagnosing Bias vs. Variance难点记录
  8. C语言中abs和fabs的区别
  9. 企业项目文档库管理系统推荐
  10. sendgrid html text,在Node.js中的SendGrid的“发件人”字段中添加名称