单片机学习小组
直播中

王健

7年用户 901经验值
私信 关注

如何利用stm32f103rct6去配置uart中断呢

如何利用STM32f103rct6去配置uart中断呢?有哪些配置步骤?

回帖(1)

许青云

2022-2-17 11:10:42
本人使用的设备/驱动:



  • Windows10
  • 串口助手 4.3.25(其实啥都行)
  • 桃饱随处可买的u***-ttl(ch340G)
  • 桃饱随处可买的stlink
  • mpu6050(一个板载,一个通过I2C接插件连接外置)
  • cubeMX 5.6.1
  • PlatformIO
  • pycharm community2019
  • stm32f103rct6的storm32BGC

使用stm32f103rct6,配置uart中断,配合pyqt在线调参
本文直接从项目中加入相关功能说起,环境以及其他外设配置:

CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,
CubeMX配合PlatformIO开发STM32,

每改一次pid都要重新烧太麻烦了,能在线调多美好啊

这是设计好的gui,核心部分是上面的滑轨以及右边显示的两列lcd,左边一列lcd是实时lcd,反应滑轨;右边一列lcd是lcd_saved,显示storm开发板中pid的参数。
大概的交互逻辑大致如下:

首先搞定显示的问题:
确定模块的功能设置:

class GuiController(QWidget):
    def initUI(self):
##################参数P调整#########################
        self.PLcd_text = QLCDNumber(self)
        self.PLcd_text.setSegmentStyle(QLCDNumber.Filled)
        self.PLcd_text.display('P')#固定显示“P”这个字符

        self.PSlider = QSlider(Qt.Horizontal, self)
        self.PSlider.setFocusPolicy(Qt.NoFocus)
        self.PSlider.setMinimum(0)#设置滑轨最小值以及最大值
        self.PSlider.setMaximum(300)
        self.PSlider.setSingleStep(2)#滑轨的调整间隔
        self.PSlider.setTickPosition(QSlider.TicksBelow)
        self.PSlider.valueChanged.connect(self.PChange_temp)#与一个函数PChange_temp连接,在滑轨值变化时调用这个函数

        self.PLcd = QLCDNumber(self) #设置一个lcd,PChange_temp的改变反应到这上面来
        self.PLcd.setSegmentStyle(QLCDNumber.Flat)
        self.PLcd.setSmallDecimalPoint(True)
        self.PLcd.setDigitCount(3)

##################参数I调整#########################

        self.ILcd_text = QLCDNumber(self)
        self.ILcd_text.setSegmentStyle(QLCDNumber.Filled)
        self.ILcd_text.display(1)

        self.ISlider = QSlider(Qt.Horizontal, self)
        self.ISlider.setFocusPolicy(Qt.NoFocus)
        self.ISlider.setMinimum(0)
        self.ISlider.setMaximum(300)
        self.ISlider.setSingleStep(2)
        self.ISlider.setTickPosition(QSlider.TicksBelow)
        self.ISlider.valueChanged.connect(self.IChange_temp)

        self.ILcd = QLCDNumber(self)
        self.ILcd.setSegmentStyle(QLCDNumber.Flat)
        self.ILcd.setSmallDecimalPoint(True)
        self.ILcd.setDigitCount(3)


##################参数D调整#########################

        self.DLcd_text = QLCDNumber(self)
        self.DLcd_text.setSegmentStyle(QLCDNumber.Filled)
        self.DLcd_text.display(0)

        self.DSlider = QSlider(Qt.Horizontal, self)
        self.DSlider.setFocusPolicy(Qt.NoFocus)
        self.DSlider.setMinimum(0)
        self.DSlider.setMaximum(300)
        self.DSlider.setSingleStep(2)
        self.DSlider.setTickPosition(QSlider.TicksBelow)
        self.DSlider.valueChanged.connect(self.DChange_temp)

        self.DLcd = QLCDNumber(self)
        self.DLcd.setSegmentStyle(QLCDNumber.Flat)
        self.DLcd.setSmallDecimalPoint(True)
        self.DLcd.setDigitCount(3)


        self.Select_motor =QComboBox(self) #下拉菜单
        self.Select_motor.addItem('Pitch')
        self.Select_motor.addItem('Roll')
        self.Select_motor.addItem('Yaw')
        # self.Select_motor.setCurrentIndex('Pitch')
        self.Select_motor.currentIndexChanged[str].connect(self.motor_selected)  # 条目发生改变,发射信号,传递条目内容

        self.SetPIDButton = QPushButton("SetPID") #设置PID,将pid传入bgc,并显示到最右栏
        self.SetPIDButton.clicked.connect(self.SetPIDCB)
这些功能都只在class中相互调用:如Xlcd显示XSlider的值;而上面说到的lcd_saved(写入板子的PID值)需要被多个class调用,如读到串口数据要更改,按下按钮“setPID”也要更改,所以写在代码开头,方便被各个class调用

#因为要跨类调用,所以定义在函数外部作全局用
PLcd_saved = QLCDNumber()
PLcd_saved.setSegmentStyle(QLCDNumber.Flat)
PLcd_saved.setSmallDecimalPoint(True)
PLcd_saved.setDigitCount(3)

ILcd_saved = QLCDNumber()
ILcd_saved.setSegmentStyle(QLCDNumber.Flat)
ILcd_saved.setSmallDecimalPoint(True)
ILcd_saved.setDigitCount(3)

DLcd_saved = QLCDNumber()
DLcd_saved.setSegmentStyle(QLCDNumber.Flat)
DLcd_saved.setSmallDecimalPoint(True)
DLcd_saved.setDigitCount(3)
回到gui的class下,在grid形式下编程:确定模块的位置

        self.grid.addWidget(self.PLcd_text, 2, 0)#显示“P”字符
        self.grid.addWidget(self.PSlider, 2, 1, 1, 1)#滑轨
        self.grid.addWidget(self.PLcd, 2, 2)#滑轨上的参数
        self.grid.addWidget(PLcd_saved, 2, 3)#已保存到bgc的
        self.grid.addWidget(self.ILcd_text, 3, 0)
        self.grid.addWidget(self.ISlider, 3, 1, 1, 1)
        self.grid.addWidget(self.ILcd, 3, 2)
        self.grid.addWidget(ILcd_saved, 3, 3)
        self.grid.addWidget(self.DLcd_text, 4, 0)
        self.grid.addWidget(self.DSlider, 4, 1, 1, 1)
        self.grid.addWidget(self.DLcd, 4, 2)
        self.grid.addWidget(DLcd_saved, 4, 3)

        self.grid.addWidget(self.Select_motor, 5, 1)
        self.grid.addWidget(self.SetPIDButton, 5, 0)
各个模块调用的功能函数,包括实时显示滑轨,下拉菜单选择的电机,按下setPID按钮的反应

    def PChange_temp(self):
        value = float(self.PSlider.value())#获取滑轨的值
        print("P:", value)
        self.PLcd.display(value)#在lcd中显示


    def IChange_temp(self):
        value = float(self.ISlider.value())
        print("I:", value)
        self.ILcd.display(value)

    def DChange_temp(self):
        value = float(self.DSlider.value())
        print("D:", value)
        self.DLcd.display(value)

    def motor_selected(self):
        global motor_num
        value = self.Select_motor.currentText()#当前下拉栏中选择的文字。注意,刚初始化的没有选择过的下拉列表value读不到值
        if value == "Pitch":
            motor_num = 0
        elif value == "Roll":
            motor_num = 1
        else:
            motor_num = 2


    def SetPIDCB(self): #将PID传输出去,并更改显示
        global P,I,D
        global origin_pid_flag
        global set_PID_flag
        if origin_pid_flag==1:#如果是开机读到的pid数据
            PLcd_saved.display(origin_P)
            ILcd_saved.display(origin_I)
            DLcd_saved.display(origin_D)
            origin_pid_flag=0
        else:
            P = float(self.PSlider.value())#将滑轨数据保存下来
            I = float(self.ISlider.value())
            D = float(self.DSlider.value())
            set_PID_flag = 1 #使能向bgc的发送,防止乱跳
新建一个class,完成串口发送以及确认的功能。如果发送的数据的返回值跟发送值不同,则不停重发,有点暴力的协议但是实际还是有用的。

class sendThread(QThread):  #send bytes
    def __init__(self):
        super().__init__()

    def run(self):
        global P, I, D
        global conn_flag, set_PID_flag
        global write_success_flag
        while True:
            time.sleep(0.01)
            if (conn_flag == 0) & (set_PID_flag == 1):#当串口连接以及按下写入按钮后执行
                # data = [int(0), float(vmax),
                #        float(angle), float(0.0)]
                while write_success_flag==0: #确定是否写入成功
                    data = [int(motor_num), float(P),
                            float(I), float(D)]
                    serial.writeData(data) #这是一个自己写的函数,通过串口发送数据,顺序为time yaw roll pitch                     
                    print("write:", motor_num, "P:", P, "I:", I, "D:", D)
                    time.sleep(0.005)
               
                print("Motor:", motor_num, "P:", P, "I:", I, "D:", D)
                PLcd_saved.display(P) #显示到最右侧的LCD上去
                ILcd_saved.display(I)
                DLcd_saved.display(D)
                write_success_flag=0
                set_PID_flag=0
注意,因为gui中需要这个功能,所以还需要在guicontroller的类下:

    def __init__(self, length):
        super().__init__()
        self.sendThread = sendThread()
接下来进入pio中,首先使能usart3的中断

允许usart接受中断:
HAL_UART_Receive_IT(uart3_it.uart,&(uart3_it.tmp_buff),1);
在gui发来数据后触发中断,进入回调函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART3)
    {
        if (uart3_it.rx_bytes_count >= 18)//judge whether length is matched
        {
            uart3_it.rx_bytes_count = 0;
            memset(&(uart3_it.data), 0, sizeof(Serial_Receive_Stream_typeDef));
            uart3_it.is_compelete = 0;
        }
        else
        {
            uint8_t *ptr = (uint8_t *)(&(uart3_it.data));
            ptr[uart3_it.rx_bytes_count++] = uart3_it.tmp_buff;//read buffer
            if (ptr[uart3_it.rx_bytes_count - 2] == 'r' && ptr[uart3_it.rx_bytes_count - 1] == 'n')//last 2bytes
            {
                if(uart3_it.rx_bytes_count==18){//if length is matched,then enable a flag which means data is available
                    uart3_it.is_compelete = 1;
                    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_13);
                    uart3_it.rx_bytes_count = 0;
                }else{
                    uart3_it.rx_bytes_count = 0;
                    memset(&(uart3_it.data), 0, sizeof(Serial_Receive_Stream_typeDef));
                    uart3_it.is_compelete = 0;
                }
            }
            else
            {
                uart3_it.is_compelete = 0;
                HAL_UART_Receive_IT(uart3_it.uart, &(uart3_it.tmp_buff), 1);// enable interrupt
            }
        }
            
    }
}
主函数中根据传输成功的标志位进入函数:

if(uart3_it.is_compelete){
      uart3_it.is_compelete = 0;
      // updateEncoder(&roll_encoder);
      motor_num= uart3_it.data.time_stamp;
      if(sendpid(motor_num,&uart3_it.data.yaw,&uart3_it.data.roll,&uart3_it.data.pitch))
      {
        serial_data.time_stamp = uart3_it.data.time_stamp;
        serial_data.yaw = uart3_it.data.yaw;
        serial_data.roll = uart3_it.data.roll;
        serial_data.pitch = uart3_it.data.pitch;
        SerialPrintTransmit(&serial_data);
        printf("transmited_to_clientrn");
      }
      printf("transmited_to_clientrn");
      HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_12);
    }
给PID赋值(通过返回标志位表示完成赋值)

/* USER CODE BEGIN 4 */
int sendpid(int motornum,float *get_P, float *get_I, float *get_D)
{
  static int PID_transmit_complete=0;
  PID_transmit_complete=0;
  if(motornum==0)
  {
      pitchPPara=*get_P;
      pitchIPara=*get_I;
      pitchDPara=*get_D;
      printf("storm_pitch:P:%f,I:%f,D:%frn",pitchPPara,pitchIPara,pitchDPara);
  }
  else if(motornum==1)
  {
      rollPPara=*get_P;
      rollIPara=*get_I;
      rollDPara=*get_D;
      printf("storm_roll:P:%f,I:%f,D:%frn",rollPPara,rollIPara,rollDPara);
  }
  else
  {
      yawPPara=*get_P;
      yawIPara=*get_I;
      yawDPara=*get_D;
      printf("storm_yaw:P:%f,I:%f,D:%frn",yawPPara,yawIPara,yawDPara);
  }
  
  PID_transmit_complete=1;
  return PID_transmit_complete;
}
/* USER CODE END 4 */
通过串口发回去后,gui会判断是否停止发送。
至此,逻辑闭环
举报

更多回帖

发帖
×
20
完善资料,
赚取积分