精华内容
下载资源
问答
  • Linux串口驱动程序(6)-串口驱动实现

    千次阅读 2019-06-13 11:23:35
    当然具备移植、修改驱动能力的基础是能够读懂驱动程序,同时需要对这个驱动程序的核心功能非常了解。接下来开始编写发送中断处理程序和接收中断处理程序的代码。 1.发送中断处理程序的设计 下面我们就开始对s3c24...

    在Linux驱动开发中,一般都不会从0开始写,我们做的更多的是移植和修改,有时候还需要对驱动程序进行优化。当然具备移植、修改驱动能力的基础是能够读懂驱动程序,同时需要对这个驱动程序的核心功能非常了解。接下来开始编写发送中断处理程序和接收中断处理程序的代码。

    1.发送中断处理程序的设计

    • 下面我们就开始对s3c24xx_serial_tx_chars进行重新的编写,编写的依据就是我们之前分析出来的发送流程。最终的目的是,编写的代码,在编译进内核之后可以正确的通过串口输出和输入信息。
    // 发送中断处理程序
    static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
    {
    	struct s3c24xx_uart_port *ourport = id;
    	struct uart_port *port = &ourport->port;
    	struct circ_buf *xmit = &port->state->xmit;  // 循环缓冲
    	int count = 256;
    
    	// 1、判断x_char里面是否有数据,如果有数据把它发送然后退出。
    	if (port->x_char) 
    	{
    		wr_regb(port, S3C2410_UTXH, port->x_char);
    		goto out;
    	}
    	
    	// 2、判断循环缓冲是否为空,或者串口不允许发送,则把中断关闭
    	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) 
    	{
    		s3c24xx_serial_stop_tx(port);
    		goto out;
    	}
    	
    	// 3、利用while循环发送数据
    	while (!uart_circ_empty(xmit) && count-- > 0)   // 循环的条件是:1.循环缓冲不为空,2.发送的数据量最多为256字节
    	{
    		// 3.1 判断发送的fifo是否满了(UFSTAT寄存器14位),如果满了要退出
    		if (rd_regl(port, S3C2410_UFSTAT) & (1 << 14))
    			break;
    	
    		// 3.2 没有满就从循环缓冲的尾部去除数据,写入S3C2410_UTXH。从循环缓冲中取出数据,用tail
    		wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
    		
    		// 3.3 调整循环缓冲的位置
    		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
    		port->icount.tx++;   // 发送的数据量加1
    	}
    	
        // 4、如果循环缓冲里面的数据小于WAKEUP_CHARS(256),则唤醒之前阻塞的发送进程
    	if (uart_circ_chars_pending(xmit) < 256)
    		uart_write_wakeup(port);
    		
    	// 5、同时如果循环缓冲为空了,把发送中断关闭。
    	if (uart_circ_empty(xmit))
    		s3c24xx_serial_stop_tx(port);
    	
     out:
    	return IRQ_HANDLED;   // 驱动被执行
    }	
    • 当然在编写过程中,应该尽量不参考原来代码的实现,而是参考之前分析的流程,碰到不会的函数、结构、可以参考内核里面的其他代码。

    2.接收中断处理程序的设计

    • 接收中断处理函数是s3c24xx_serial_rx_chars,编写过程方法和上面的也类似。
    // 接收中断处理程序
    s3c24xx_serial_rx_chars(int irq, void *dev_id)
    {
    	struct s3c24xx_uart_port *ourport = dev_id;
    	struct uart_port *port = &ourport->port;
    	struct tty_struct *tty = port->state->port.tty;
    	unsigned int ufcon, ch, flag, ufstat, uerstat;
    	int max_count = 64;
    
    	while (max_count-- > 0)
    	{	
    		// 1.判断接收fifo是否为空,如果为空,退出
    		ufstat = rd_regl(port, S3C2410_UFSTAT);  // 读取UFSTAT寄存器状态,
    		if ((ufstat & 0x3f) == 0)  // UFSTAT寄存器0到5位保存接收fifo的数据量
    			break;
    	
    		// 2.读取错误状态寄存器
    		uerstat = rd_regl(port, S3C2410_UERSTAT);
    		
    	    // 3.取出接收到的字符 
    	    ch = rd_regb(port, S3C2410_URXH);
    		
    		// 6.如果接收到的是sysrq这个特殊字符,则进行特殊处理
    		uart_handle_sysrq_char(port, ch);
    		
    		flag = TTY_NORMAL;
    		
    		// 7.把接收到的字符送到串口驱动的buf中
    	    uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
    	}
    	
    	// 8.把串口驱动中的数据送到read_buf中
    	tty_flip_buffer_push(tty);
    	
    	return IRQ_HANDLED;
    }


     

    展开全文
  • 在当前数字信息技术和网络技术高速发展的后PC(Post-PC)时代,嵌入式...Windows CE.net是微软公司推出的32位嵌入式操作系统,具备多任务、实时性、模块化及可伸缩性、强大的通信能力以及图形界面友好等优点。将操作
  • 在当前数字信息技术和网络技术高速发展的后PC(Post-PC)时代,嵌入式...Windows CE.net是微软公司推出的32位嵌入式操作系统,具备多任务、实时性、模块化及可伸缩性、强大的通信能力以及图形界面友好等优点。将操作
  • Arduino A4950 驱动直流电机对于自己做车的大部份同学来说,我和大家一样,用的最多的就是L298N驱动器,这次在家想试着自己研究一个驱动能力更好的更加方便的电机驱动,而网上使用Arduino L298N 驱动小车的例子非常的多 ...

    Arduino A4950 驱动直流电机

    对于自己做车的大部份同学来说,我和大家一样,用的最多的就是L298N驱动器,这次在家想试着自己研究一个驱动能力更好的更加方便的电机驱动,而网上使用Arduino L298N 驱动小车的例子非常的多 A4950 的一篇也没有,那好吧,希望这篇文章能给L298N用腻了的同学提供一个更好更新的选项

    1.实验准备

    1.Arduino 系列单片机

    2.直流电机

    3.A4950驱动器

    2.A4950芯片简介

    一个A4950驱动器可驱动两个直流电机

    驱动板工作电压范围:7.6V~30V

    A4950引脚

    对应引脚

    VCC

    单片机5V

    GND

    单片机GND

    VM

    驱动电源7.6~30V

    AIN1

    控制A电机的1号PWM引脚

    AIN2

    控制A电机的2号PWM引脚

    AOUT1

    电机A正极

    ATOU2

    电机A负极

    BN1

    控制A电机的1号PWM引脚

    BIN2

    控制A电机的2号PWM引脚

    BOUT1

    电机B正极

    BTOU2

    电机B负极

    一个模块上有两组 VCC GND VM 至少接一组

    单片机 A4950 驱动电源 记得共地

    A4950 驱动是通过比较两个控制引脚输出PWM的大小关系来确定电机方向的

    两个控制引脚输出PWM的差值决定电机的转速

    3.程序设计

    我们接下来的代码都已驱动一个电机为例,剩下一个如法炮制就可以了

    3.1简易驱动板

    unsigned int Motor_AIN1=2; //控制A电机的PWM引脚 一定改成自己用的

    unsigned int Motor_AIN2=3;

    char Motor_Order; //定义一个字符型变量存储串口输入命令

    void setup()

    {

    Serial.begin(9600); //打开串口

    Serial.println("/*****开始驱动*****/");

    pinMode(Motor_AIN1,OUTPUT); //设置两个驱动引脚为输出模式

    pinMode(Motor_AIN2,OUTPUT);

    }

    void loop()

    {

    while(Serial.available()>0) //检测串口是否有命令

    {

    Motor_Order=Serial.read(); //将命令存储在变量中

    switch(Motor_Order)

    {

    //发送字符1电机正转

    case '1' : analogWrite(Motor_AIN1,250); analogWrite(Motor_AIN2,0);Serial.println("/*****电机正传*****/");break;

    //发送字符2电机反转

    case '2' : analogWrite(Motor_AIN1,0); analogWrite(Motor_AIN2,250);Serial.println("/*****电机反转*****/");break;

    //发送其他字符电机停转

    default : analogWrite(Motor_AIN1,0); analogWrite(Motor_AIN2,0);Serial.println("/*****停转****/");break;

    }

    }

    }

    烧录之后打开串口分别输入指令就可以了

    3.2串口输入调速版接上位机版(高级版)

    好吧这个对萌新来说看起来好像有一点复杂

    简单说功能就是在串口按照输入协议:

    控制A电机的1号引脚PWM值.控制A电机的2号引脚PWM值

    注:控制A电机的1号引脚PWM值(一个英文的点)控制A电机的2号引脚PWM值

    例: 在串口输入 10.255

    功能:控制A电机的1号PWM引脚输出0,控制A电机的1号PWM引脚输出255

    实现:电机反转,控制速度的PWM差值为 255-10=245

    先把完整的代码贴上,我会逐个部分讲解

    先讲一下逻辑实现的步骤

    1.从串口接收一个包含被一个点分开的两个数字的字符串

    2.从一个完整字符串中截取出两个数字字符串

    3.将数字字符串转换成整形

    4.通过PWM引脚将转换好的数值输出

    第二步详细实现:1.获取分割符号(既:点的位置)2.根据点的位置前后截取

    unsigned int Motor_AIN1=2;

    unsigned int Motor_AIN2=3;

    String Motor_Order,String_Motor_AIN1_Value,String_Motor_AIN2_Value;

    unsigned int Motor_AIN1_Value,Motor_AIN2_Value,Point_desepote;

    void setup()

    {

    // put your setup code here, to run once:

    Serial.begin(9600);

    Serial.println("/*****开始测试*****/");

    pinMode(Motor_AIN1,OUTPUT);

    pinMode(Motor_AIN2,OUTPUT);

    }

    /*****************获取截止位****************/

    unsigned int Motor_Point_desepote(String Motor_Order)

    {

    unsigned int desepote,point_desepote;

    for(desepote=0;desepote

    if (Motor_Order[desepote]=='.')

    {

    point_desepote=desepote+1;

    Serial.print("点的位置为:");

    Serial.println(point_desepote);

    break;

    }

    return point_desepote;

    }

    /****************截取字符串函数***************/

    String String_fragment(String Complete_information,unsigned int Intitial_Position,unsigned int Final_Position)

    {

    String Fragment;

    unsigned int location;

    for(location=Intitial_Position-1;location

    Fragment+=Complete_information[location];

    return Fragment;

    }

    /******************电机驱动函数**************/

    void Drive_Motor(unsigned int Motor_AIN1_Value,unsigned int Motor_AIN2_Value)

    {

    analogWrite(Motor_AIN1,Motor_AIN1_Value);

    analogWrite(Motor_AIN2,Motor_AIN2_Value);

    Serial.println("/*****驱动电机*****/");

    }

    void loop()

    {

    // put your main code here, to run repeatedly:

    while(Serial.available()>0)

    {

    Motor_Order=Serial.readString();

    Point_desepote=Motor_Point_desepote(Motor_Order);

    String_Motor_AIN1_Value=String_fragment(Motor_Order,1,Point_desepote-1);

    String_Motor_AIN2_Value=String_fragment(Motor_Order,Point_desepote+1,Motor_Order.length());

    // Serial.print("String_Motor_AIN1_Value:");

    // Serial.println(String_Motor_AIN1_Value);

    // Serial.print("String_Motor_AIN2_Value:");

    // Serial.println(String_Motor_AIN2_Value);

    Motor_AIN1_Value=constrain(String_Motor_AIN1_Value.toInt(),0,255);

    Motor_AIN2_Value=constrain(String_Motor_AIN2_Value.toInt(),0,255);

    Serial.print("Motor_AIN1_Value:");

    Serial.println(Motor_AIN1_Value);

    Serial.print("Motor_AIN2_Value:");

    Serial.println(Motor_AIN2_Value);

    if(Motor_AIN1_Value==Motor_AIN2_Value)

    {

    Drive_Motor(Motor_AIN1_Value,Motor_AIN2_Value);

    Serial.println("电机停转");

    }

    else if(Motor_AIN1_Value>Motor_AIN2_Value)

    {

    Drive_Motor(Motor_AIN1_Value,Motor_AIN2_Value);

    Serial.println("电机正转");

    }

    else if(Motor_AIN1_Value

    {

    Drive_Motor(Motor_AIN1_Value,Motor_AIN2_Value);

    Serial.println("电机反转");

    }

    }

    }

    3.2.1 定义部分

    unsigned int Motor_AIN1=2;

    unsigned int Motor_AIN2=3;

    String Motor_Order,String_Motor_AIN1_Value,String_Motor_AIN2_Value;

    unsigned int Motor_AIN1_Value,Motor_AIN2_Value,Point_desepote;

    变量名称

    功能

    Motor_AIN1

    控制A电机的1号PWM引脚号

    Motor_AIN2

    控制A电机的2号PWM引脚

    Motor_Order

    用来存储串口处的完整指令

    String_Motor_AIN1_Value

    存储控制A电机的1号PWM值字符串变量

    String_Motor_AIN2_Value

    存储控制A电机的2号PWM值字符串变量

    Motor_AIN1_Value

    存储控制A电机的1号PWM值整形变量

    Motor_AIN2_Value

    存储控制A电机的2号PWM值整形变量

    Point_desepote

    存储初始命令点的位置

    3.2.2获取点的位置

    /*****************获取截止位****************/

    unsigned int Motor_Point_desepote(String Motor_Order)

    {

    unsigned int desepote,point_desepote;

    for(desepote=0;desepote

    if (Motor_Order[desepote]=='.')

    {

    point_desepote=desepote+1;

    Serial.print("点的位置为:");

    Serial.println(point_desepote);

    break;

    }

    return point_desepote;

    }

    功能:检索字符串中点的位置并返回

    注: 字符串长度表示方式 : 字符串名.length()

    例: Motor_Order.length()

    3.2.3字符串截取函数

    /****************截取字符串函数***************/

    String String_fragment(String Complete_information,unsigned int Intitial_Position,unsigned int Final_Position)

    {

    String Fragment;

    unsigned int location;

    for(location=Intitial_Position-1;location

    Fragment+=Complete_information[location];

    return Fragment;

    }

    功能:从指定字符串中截从第(Intitial_Position)位截取到第(Final_Position)位

    并作为字符串变量返回

    注:使用方法 : String_fragment(要截取字符串变量名,截取的初始位,截取的终止位)

    3.2.4电机驱动函数

    void Drive_Motor(unsigned int Motor_AIN1_Value,unsigned int Motor_AIN2_Value)

    {

    analogWrite(Motor_AIN1,Motor_AIN1_Value);

    analogWrite(Motor_AIN2,Motor_AIN2_Value);

    Serial.println("/*****驱动电机*****/");

    }

    这个不多说了只要分别输入数值就可以了

    3.2.5初始化设置函数

    void setup()

    {

    // put your setup code here, to run once:

    Serial.begin(9600); //打开串口

    Serial.println("/*****开始测试*****/");

    pinMode(Motor_AIN1,OUTPUT); //设置两控制引脚为输出模式

    pinMode(Motor_AIN2,OUTPUT);

    }

    3.2.6主函数实现

    void loop()

    {

    // put your main code here, to run repeatedly:

    while(Serial.available()>0) //判断串口是否接收到数据

    {

    Motor_Order=Serial.readString(); //将串口的字符串存到Motor_Order中

    Point_desepote=Motor_Point_desepote(Motor_Order);//获取Motor_Order字符串中点的位置

    String_Motor_AIN1_Value=String_fragment(Motor_Order,1,Point_desepote-1);//截取存储控制A电机的1号PWM值字符串变量(从第一位截取到点的前一位)

    String_Motor_AIN2_Value=String_fragment(Motor_Order,Point_desepote+1,Motor_Order.length());//截取存储控制A电机的2号PWM值字符串变量(从点的后一位截取到最后一位)

    // Serial.print("String_Motor_AIN1_Value:");

    // Serial.println(String_Motor_AIN1_Value);

    // Serial.print("String_Motor_AIN2_Value:");

    // Serial.println(String_Motor_AIN2_Value);

    Motor_AIN1_Value=constrain(String_Motor_AIN1_Value.toInt(),0,255);//将截取存储控制A电机的1号PWM值字符串变量转换成整形,并设定值约束其在0~255范围内

    Motor_AIN2_Value=constrain(String_Motor_AIN2_Value.toInt(),0,255);

    //将截取存储控制A电机的2号PWM值字符串变量转换成整形,并设定值约束其在0~255范围内

    Serial.print("Motor_AIN1_Value:"); //将数值打印以便观察

    Serial.println(Motor_AIN1_Value);

    Serial.print("Motor_AIN2_Value:");

    Serial.println(Motor_AIN2_Value);

    //驱动控制部分

    if(Motor_AIN1_Value==Motor_AIN2_Value) //如果Motor_AIN1_Value=Motor_AIN2_Value Motor_AIN1_Value,Motor_AIN2_Value 两PWM差值为0 电机停转

    {

    Drive_Motor(Motor_AIN1_Value,Motor_AIN2_Value);

    Serial.println("电机停转");

    }

    else if(Motor_AIN1_Value>Motor_AIN2_Value)//如果Motor_AIN1_Value>Motor_AIN2_Value , Motor_AIN1_Value-Motor_AIN2_Value 两PWM差值为为正 电机正转

    {

    Drive_Motor(Motor_AIN1_Value,Motor_AIN2_Value);

    Serial.println("电机正转");

    }

    else if(Motor_AIN1_ValueMotor_AIN2_Value , Motor_AIN1_Value-Motor_AIN2_Value 两PWM差值为为负 电机反转

    {

    Drive_Motor(Motor_AIN1_Value,Motor_AIN2_Value);

    Serial.println("电机反转");

    }

    }

    }

    打开串口输入命令效果如图

    到这里就全部结束了,还有一个要说的

    我为什么一定要转换成字符串型之后再转换成整形,而不是在一个函数里直接解决,因为arduino官方库没找到类似指定位置的字符串截取函数,单独列出来方便理解,方便类似功能移植

    4. 后言

    本文章是通过Arduino系列单片机代码实现功能,同样的如果看懂原理,用其他单片机当然也可以实现,A4950的模块详细资料好多我就不贴了,希望有帮到大家

    展开全文
  • STM32 串口

    2020-07-22 22:59:38
    主要用于工业设备的通信,高的电平差有较高的容错能力。 USB转串口 :电平转换芯片有CH340,PL2303,CP2102;需要安装驱动。指南者上的是CH340,通过跳帽连接USART1; 串口通信数据包组成 起始位:一个

    USART 通信协议

      所谓协议,就是一种交换信息的规定。想要传输信息,必须按照一定的规则才能识别。USART是串行,全双工,异步通信

    RS-232与TTL电平

    TTL: 0~3.3/5V 一般芯片输出的都是TTL;
    RS-232:-15~15V,但-15V对应逻辑1,+15V对应逻辑0。主要用于工业设备的通信,高的电平差有较高的容错能力。

    USB转串口 :电平转换芯片有CH340,PL2303,CP2102;需要安装驱动。指南者上的是CH340,通过跳帽连接USART1;

    串口通信数据包组成

    起始位:一个逻辑0表示
    结束位:一般由一个1个逻辑1表示,也可选择其他方式。
    有效数据位:数据位后面就是数据位,stm32中为9个位。
    校验位:可以使用校验来提高稳定性。

    1. 偶校验:添加校验位后,校验位和有效数据位的1有偶数个。
    2. 奇校验:添加校验位后,校验位和有效数据位的1有奇数个。

    USART功能框图讲解

    引脚

    TX:数据发送。
    RX:数据接收。
    SCLK:时钟,仅同步通信使用(一般使用异步)
    nRTS: 请求发送
    nCTS: 允许发送
    这些引脚外设可以在数据手册 3 Pinouts and pin descriptions 查到,下面是VET6芯片的引脚总结。
    在这里插入图片描述这里列出的是串口复用功能的默认端口,在重映射下,这些功能可以对应其他端口,这样同样可以在数据手册这一章查到。在参考手册 8.3 有更集中的总结。

    注意这里只有USART1是挂在在APB2总线下的,其于串口在APB1总线下,打开时钟的时候需要注意。

    寄存器

    状态寄存器(USART_SR)

      其反应了发送过程中,发送状态的改变

    数据寄存器(USART_DR)

      9位有效,其包含一个发送数据寄存器TDR和接收数据寄存器RDR。

    控制寄存器(USART_CR)

      一共有3个控制寄存器,这三个寄存器完成对USART通信模式的控制,如使能,字长,校验等。并且其中有对发送和接收中断的配置。
      发送时,起始的配置位有 CR1UE,TE,RE 位。

    波特比率寄存器(USART_BRR)

    在这里插入图片描述

    配置波特率,这个配置需要配置整数和小数部分。这样是因为配置波特率的公式中需要用到小数。
      stm32波特率计算公式是:
    baud=fCK16USARTDIV baud = \frac{f_{CK}}{16*USARTDIV}
    baudbaud是波特率,fCKf_{CK}是时钟频率,USARTDIVUSARTDIV就是要配置寄存器写入值
    如:想要波特率为115200,配置时钟为72M,可以算出,USARTDIV=39.0625。
      如何配置呢?对于整数部分,直接写入39,16进制为0x17
      对于小数,寄存器中留有四个二进制位,可以表示16个数,也就是1被16等分,0001就表示116\frac{1}{16},因此想要得到0.625,就应该写入0.625/116\frac{1}{16}=0x01。如果为0.1875,0.1875/116\frac{1}{16}=0x03
      最终,向寄存器写入0x171表示39.0625.若写入0x173则表示39.1875。(注意这里表示为16进制数)

    发送过程中寄存器的变化

    1. 发送数据
      从CPU或DMA读取数据到TDR,放到发送移位寄存器,一位一位从TX发送出去。
      SR寄存器的 TXE 会置1,表示数据已经移动到移位寄存器。
      发送完成 TC 置1。
    2. 接收数据
      数据从 RX传来,接收移位寄存器接收,传给RDR,传给CPU。
      SR寄存器的 RXNE 置1,表示接收到数据可以读取。

    固件库编程

    结构体

    其实真正的寻找应该从函数开始,到结构体。但那样太乱,这先介绍结构体。

    初始化结构体 USART_InitTypeDef

    typedef struct
    {
      uint32_t USART_BaudRate;             //配置波特率
    
      uint16_t USART_WordLength;           //字长   
      uint16_t USART_StopBits;            //终止位个数
    
      uint16_t USART_Parity;              //校验位
      uint16_t USART_Mode;               //模式:发送,接收
    
      uint16_t USART_HardwareFlowControl;  //硬件控制流
    } USART_InitTypeDef;
    

    固件库函数

    这里列出一些重要的函数。

    • USART_Init:初始化串口,其传入了对应串口和初始化结构体,因此配置了结构体中的内容。对应了CR,BRR寄存器的置位。
    • USART_Cmd:使能对应串口,对应CR寄存器中的置位操作。
    • USART_ITConfig:中断使能,对应CR寄存器中的置位操作。
    • USART_SendData,USART_ReceiveData:发送,接收;对应于DR寄存器的操作。
    • USART_GetITStatus:获取标志位,对应SR寄存器的操作。

    开始编程(一)

    两个编程任务,第一实现串口发送;第二,实现接收返回,和串口控制led灯。第一部分完成串口的发送。

    初始化

      使用USART接收电脑发送的数据,并且返回这个数据。使用中断完成

    GPIO

      因为USART是复用功能,从GPIO原理框图可以看出,必须初始化GPIO。PA9为发送,初始化为推挽输出;PA10为输入,初始化为浮空输入;打开时钟;

    static void USART_GPIO_Config(void)
    {
    	GPIO_InitTypeDef GPIO_InitStruct;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;
    	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);
    	
    	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_10;
    	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
    	GPIO_Init(GPIOA,&GPIO_InitStruct);
    }
    

    USART

    初始化USART,配置波特率115200,字长8,停止位1,无校验位,无硬件流。
    这里注意
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    这个骚操作,直接把输入和输出模式一起配置了。
    注意,最后要使能 USART

    static void USART_Init_Config(void)
    {
    	USART_InitTypeDef USART_InitStruct;
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    	
    	USART_InitStruct.USART_BaudRate   = 115200;
    	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    	USART_InitStruct.USART_StopBits   = USART_StopBits_1;
    	USART_InitStruct.USART_Parity     = USART_Parity_No;
    	USART_InitStruct.USART_Mode       = USART_Mode_Tx | USART_Mode_Rx;
    	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    	
    	USART_Init(USART1,&USART_InitStruct);
    	USART_Cmd(USART1,ENABLE);
    }
    

    中断配置

    中端的配置为了第二个实验的接收

    配置外设中断

    USART的中断配置十分简单,一个函数就可以解决。

    	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    

    对串口1配置,接收中断。

    配置中断优先级分组

    配置分组为1

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    

    配置 NVIC 寄存器

    这里要注意 NVIC_IRQChannel 这个结构体成员变量可赋值的查找。详细请见STM32 EXTI外部中断小结 配置 NVIC 寄存器 部分。

    static void USART_NVIC_Config(void)
    {
    	NVIC_InitTypeDef NVIC_InitStruct;
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//配置优先级分组
    	
    	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    	
    	NVIC_Init(&NVIC_InitStruct);
    }
    

      下面应该写中断配置函数,在中断配置函数中读取输入数据,然后发送回电脑。这部分内容放在实验二,现在先把上面的代码整合一下。

    整合配置代码

      这样所有的初始化工作完成,编写一个函数集成上面的函数。

    void USART_Config(void)
    {
    	USART_GPIO_Config();    //配置GPIO端口
    	USART_Init_Config();    //配置USART模式
    	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);  //使能中断,接收中断
    	USART_NVIC_Config();    //配置中断管理
    }
    

    配置到这里,程序已经可以使得芯片接收数据,并且跳转到中断中了。

    发送数据

      为了完成在中断中读取数据和发送数据我们必须编写函数。
      在USART库中有函数 USART_SendData,这个函数实际上已经可以发送一些数据了,这里对其进行包装。使用函数检测是否发送完成。其实这里不用检测。直接使用USART_SendData也可完成这项工作。

    void USART_SentByte(USART_TypeDef* pUSARTx, uint8_t data)
    {
    	USART_SendData(pUSARTx, data);
    	while( USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
    }
    

      函数需要传入USART外设的指针和要发送的数据。
      另外注意。虽然USART_SendData 的第二个参数可以传入16位数据,但实际上只有低8位有效,也就是说,使用一次USART_SendData 只能发送一个字节的数据。
      注意:单片机发出的信号其实是一串二进制码,串口调试助手默认是以ASCII码的方式显示。在keil中,发送的数据也以ASCII码的形式编码。

      这是一张ASCII码对应表,具体可见ASCII码对照表
      如:USART_SentByte(USART1, 0x41),这里单片机把 0x41 编码为2进制 0100 0001 ,通过串口发送出去,串口调试助手默认把这串二进制码用ASCII解释,显示A
      如: USART_SentByte(USART1,'A') ,这里keil把 A 按ASCII码编码为0100 0001,通过串口发送出去,串口调试助手认把这串二进制码用ASCII解释,显示A。但如果选中16进制显示,则会显示FF41
      因为只能发送一个字节,我们在编写一个可以发送两个字节。

    void USART_Sent2Byte(USART_TypeDef* pUSARTx, uint16_t data)
    {
    	uint8_t temp_h,temp_l;
    	temp_h = (data >> 8) & 0x00ff;
    	temp_l = data&0x00ff;
    	
    	USART_SendData(pUSARTx, temp_h);
    	while( USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
    	
    	USART_SendData(pUSARTx, temp_h);
    	while( USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
    }
    

    重新定向printf

    其实实际使用中用的最多的是使用重新定向后的printf。记得要在头文件中引用C库 #include <stdio.h>

    //重新定向C库函数,以便于使用printf
    int fputc(int ch, FILE *f)
    {
    		USART_SendData(USART1, (uint8_t) ch);
    		while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);		
    		return (ch);
    }
    
    //重新定向C库函数,以便于使用scanf
    int fgetc(FILE *f)
    {
    		while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
    		return (int)USART_ReceiveData(USART1);
    }
    
    

    更具体的这些函数做到了什么,我也没学,没法解释。但实验的结果可以看出,在printf() 的括号中填入任何长度的字符,都可打印出来。

    用宏定义封装

    为了使得代码有较好的移植性,我们使用宏定义对代码进行封装。

    1. GPIO部分
      GPIO肯定是要对引脚和GPIOx进行封装。对时钟函数中的参数封装。这里使用和野火教程中一样的封装。
    // GPIO
    #define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
    #define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
    #define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
    #define  DEBUG_USART_RX_GPIO_PORT       GPIOA
    #define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10
    
    1. USART部分
      要封装USARTx,因不同的USART会对应不同的总线,因此不仅要对时钟函数的参数封装,还要封装时钟函数;另外为了以后便于调节波特率,把波特率也用宏定义封装。
    //USART
    #define  DEBUG_USARTx                   USART1
    #define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
    #define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
    #define  DEBUG_USART_BAUDRATE           115200
    

    3.中断部分
    初始化NVIC中,channel的选择和中断服务函数的编写用到了USARTx

    #define  DEBUG_USART_IRQ                USART1_IRQn
    #define  DEBUG_USART_IRQHandler         USART1_IRQHandler
    

    这样,整个程序就有了对于USARTx之间的移植性,只要修改宏定义中的参数,就可以开启不同的USART。

    开始编程(二)

    第二部分完成数据的接收返回和控制LED。

    编写中断服务函数

    上面已经完成中断配置的前三个步骤了,这里完成最后一个上面已经完成中断配置的前三个步骤了,这里完成最后一个。
    中断服务函数最好写在stm32f10x_it.c中,其名称必须为startup_stm32f10x_hd.s 中定义的名称。

    void DEBUG_USART_IRQHandler(void)
    {
    	uint8_t Sentback;
    	//进入中断后再次检测是否真的置位
    	if( USART_GetFlagStatus(DEBUG_USARTx,USART_IT_RXNE) != RESET)
    	{
    		Sentback = USART_ReceiveData(DEBUG_USARTx);
    		USART_SendData(DEBUG_USARTx,Sentback);//使用这个函数把得到的数据返回
    		//也可用printf("%d",Sentback);代替
    	}
    }
    

    控制LED

    直接修改中断服务函数,使用 getchar() 获得输入;使用 switch构成判断。

    void DEBUG_USART_IRQHandler(void)
    {
    	uint8_t LED_Common;
    	LED_Common = getchar();
    	
    	switch(LED_Common)
    	{
    		case '1':LED_R(ON);printf("LED_R ON!\n");Infor_LED[0]=1;break;
    		case '2':LED_G(ON);printf("LED_R ON!\n");Infor_LED[1]=1;break;
    		case '3':LED_B(ON);printf("LED_R ON!\n");Infor_LED[2]=1;break;
    		
    		case '4':LED_R(OFF);printf("LED_R OFF!\n");Infor_LED[0]=0;break;
    		case '5':LED_G(OFF);printf("LED_R OFF!\n");Infor_LED[1]=0;break;
    		case '6':LED_B(OFF);printf("LED_R OFF!\n");Infor_LED[2]=0;break;
    	}
    

    发送和接收过程中的编码

      上面提到,如果使用USART_SentByte(USART1, A),函数会把发送的数据以ASIIC码编码,通过串口发送,A 的 ASIIC码 为0100 0001。串口调试助手默认以 ASIIC码 解码 显示 A。若选择 以16进制显示 则显示 41

      同样对于电脑端发送的数据,串口调试助手也以 ASIIC码 编码发送。如发送 A,会编码 0100 0001 发送。若选择以16进制发送,则会发送 1010

    sizeof

      有了串口程序,stm32和电脑就可以通信了,这样我们就可以展开一些实验,如这里介绍的sizeof。
      sizeof可以返回对应值的长度,在不同的机器上返回值不同。在stm32上写下如下代码。

    #include "stm32f10x.h"
    #include "bsp_usart.h"
    #include <stdio.h>
    
    int text(int a[])
    {
    	uint8_t i =sizeof(a);
    	return i;
    }
    
    int main(void)
    {	
    	int a[5] = {1,2,3,4,5};
    	uint8_t b    = sizeof(int);
    	USART_Config();
      
      	printf("b=%d\n",b);
    	printf("sizeof a = %d\n",sizeof(a));
      	printf("fun:sizeof a = %d",text(a));
      	while(1)
    	{	
    		
    	}	
    }
    

    返回值为:

    b=4
    sizeof a = 20
    fun:sizeof a = 4
    

      这里我们调用 sizeof 查看 int 的数据宽度为4(b对应的值)。在main中调用 sizeof 查看数组 a 的宽度为20,查看函数 text 宽度为4.
      这和普通PC返回的值不一样。在64位电脑上,使用VScode运行上面这段代码,获得的结果如下:

    b=4
    sizeof a = 20
    fun:sizeof a = 8
    

      先解释 b=4;这一点stm32和电脑返回值一致,因为int类型占用的就是4个数据位。
      sizeof a,查看main函数中a数组的宽度。返回20,表示4×5=20(数据宽度×数据个数)。
      fun:sizeof a,查看函数返回的a的数据宽度。这里PC返回8,stm32返回4。和上面直接在main函数中查看不同。这是因为,向函数传输数组时,传输的不是数组本身,而是数组的首地址(指针)。在32位机中,指针长度为4,64位机中,指针长度为8.
      这也提示我们,在函数中,我们无法使用sizeof得到传入数组的大小。

    展开全文
  • 虚拟串口程序.exe

    2020-08-06 09:21:50
    一个真实串口分成多路虚拟串口,可轻松管理物理和虚拟串行端口。它具有自定义端口参数和创建复杂端口捆绑的能力,使其成为许多不同情况的理想解决方案。虚拟串行端口驱动程序PRO功能包括将单个物理COM端口拆分为几个...
  • STM32串口通信

    2020-12-05 14:35:31
    1.两种电平标准TTL标准:当电平处于2.4~5V之间时,表示逻辑1;当电平处于 0 ~0.5V时,表示逻辑0。 RS-232标准:当电平处于-15~-3V之间时,表示逻辑1;当电平处于3 ~15V时,表示...3.原生的串口串口 主要是控制器跟串

    串口通信简介

    1.两种电平标准TTL标准:当电平处于2.4~5V之间时,表示逻辑1;当电平处于 0 ~0.5V时,表示逻辑0。
    RS-232标准:当电平处于-15~-3V之间时,表示逻辑1;当电平处于3 ~15V时,表示逻辑0。

    2.RS-232标准的传输距离及抗干扰能力更好。重点是两种标准的转换。
    USB转串口通讯
    USB转串口主要是设备跟电脑通信,该过程需要电平转换芯片来实现,常用的芯片有CH340,PL2303,CP2102,FT232。使用的时候需要安装电平转换芯片的驱动。

    3.原生的串口到串口
    主要是控制器跟串口设备或者传感器通信,不需要电平转换芯片来转换电平,直接使用TTL电平通信。例如GPS模块。

    4.波特率与比特率
    波特率即每秒钟传输的码元个数,便于对信号进行解码。常用的波特率4800,9600,115200。比特率即每秒钟传输的二进制位数。

    5.通讯的起始和停止信号
    起始信号由逻辑0的数据位表示,停止信号可由0.5,1.5,1或2个1的数据位来表示。双方自行约定。

    6.校验
    通过校验码来避免数据在传输过程中,受到外部干扰而发生偏差。常采用奇偶校验,只能检测出发生偏差位的1位。

    STM32串口通信

    (1)设置波特率为115200,1位停止位,无校验位。
    (2)STM32系统给上位机(win10)连续发送“hello windows!”,上位机接收程序可以使用“串口调试助手“,也可自己编程。
    (3)当上位机给stm32发送“Stop,stm32”后,stm32停止发送。
    参考程序:原程序
    1.这程序和我们的要求还不符合,我们还需要改一下
    具体改一下main函数,中断函数和发送函数。
    main函数 修改如下

    #include "stm32f10x.h"
    void Delay_ms(volatile unsigned int t)	
    {
    	unsigned int i,n;
    	for(n=0;n<t;n++)
    		for(i=0;i<800;i++);
    }
    
    
    int main(void)
    {
    
    	User_USART_GPIO_Config();
    	User_NVIC_Config();
    	User_USART_Config();
    
    	User_UART_Send_String(USART1, "abcdefg!\n");
    
    	while(1)
    	{
         User_UART_Send_String(USART1, "hello windows!\n");
         Delay_ms(5000);
    	}
    
    
    
    
    
    
    
    
    }
    

    中断函数修改如下

    void SysTick_Handler(void)
    {
    }
    
    
    //中断服务函数,前面在User_USART.c  void User_USART_Config(void)函数中
    //USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);使能了数据接收中断,所以
    //将处理接收数据时的中断服务
    	int i=0;
    	uint8_t a[11];
    void USART1_IRQHandler(void)
    {
    
    	//uint8_t temp;
    	
    	
    	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    	{
    		a[i++] = USART_ReceiveData(USART1);
    		//USART_SendData(USART1, a[i-1]);
    			
    
    	}
    	if(a[0]=='S'&&a[1]=='t'&&a[2]=='o'&&a[3]=='p'&&a[4]==','&&a[5]=='s'&&a[6]=='t'&&a[7]=='m'&&a[8]=='3'&&a[9]=='2')
    		while(1);
    }
    

    发送函数修改如下

    void User_USART_Send_Byte(USART_TypeDef* pUSARTX, uint8_t Data)
    {
    
    	//向数据寄存器写入8bit数据
    	pUSARTX->DR = (Data & (uint16_t)0x01FF);	
    
    	//USART_GetFlagStatus检查数据是否发送完成
    	while(USART_GetFlagStatus(pUSARTX, USART_FLAG_TXE) == RESET);
    	
    }
    
    
    //向串口发送一个字符串数据,即可以发送包含多个字节的数据,char类型为8bit,其字符串中的每个字符都可用一个int数表示,即ASCII标准
    void User_UART_Send_String(USART_TypeDef* pUSARTX, char* str)
    {
      unsigned int i = 0;
    	do	
    	{
    		User_USART_Send_Byte(pUSARTX, *(str + i));
    		i++;
    	}
    	while(*(str+i)!='\0');
    
    		//USART_GetFlagStatus检查多个数据是否发送完成
    		while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
      
    
    	{
    
    }
    

    引用

    本文引鉴自
    https://blog.csdn.net/m0_47159351/article/details/110395567

    展开全文
  • RS-485采用平衡发送和差分接收,因此具有抑制共模干扰的能力。RS232传输的距离在15米以下,... 485驱动=串口驱动+GPIO的字符驱动,串口驱动一般都用系统提供的,不需要自己写,需要自己写的只有GPIO字符驱动,并在...
  • 有的说RS485总线只能挂接32个节点,这是由它自身的驱动能力决定的。而到网上搜索发现有人说可以支持128个,也有说能支持256个,甚至400个......不管是支持32个、128个、256个都没有错,但是这些都是理论值,实际负载...
  • STM32的USART串口通信

    2020-11-30 19:43:54
    2.RS-232标准的传输距离及抗干扰能力更好。重点是两种标准的转换。 USB转串口通讯 USB转串口主要是设备跟电脑通信,该过程需要电平转换芯片来实现,常用的芯片有CH340,PL2303,CP2102,FT232。使用的时候需要安装...
  • 串口和USB的区别

    2020-04-13 15:20:56
    串口成本低,而且更主要的是,COM口对开发者和使用者而言,不需要去专门开发和安装驱动,这样又省了软件成本。 ②COM虽然速度慢,使用繁杂,但它的抗干扰能力是远远超过USB的,在同等高频干扰情况下,使用USB...
  • 2.4 GHz以太网/串口/USB网关zip,2.4 GHz无线网口/串口/USB网关IP2421B是由一个可靠的高速率2.4Ghz FHSS跳频电台驱动的。凭借同时提供稳定可靠的RS232/485串口、USB和IP/以太网数据无线传输能力,得以成为适合各种...
  • linux 与 单片机 串口通信

    千次阅读 2012-11-14 11:47:11
    本人最近在尝试在linux下用串口与单片机通信,虽然说网上资料例程五花八门,但是缺乏严格的注释或者完整的实例,或许本人能力有限,某些问题无法理解,在实际过程中遇到不少问题,无从下手,特此写篇文章请求各位...
  • IP920B 900MHz 以太网/串口/USB网关zip,900MHz无线以太网/串口/USB网关IP920B是由一个可靠的900MHz FHSS跳频电台驱动的。凭借同时提供稳定可靠的RS232/485串口、USB和IP/以太网数据无线传输能力,得以成为适合各种...
  • IP921B 900MHz 以太网/串口/USB网关zip,900MHz无线以太网/串口/USB网关IP921B是由一个可靠的900MHz FHSS跳频电台驱动的。凭借同时提供稳定可靠的RS232/485串口、USB和IP/以太网数据无线传输能力,得以成为适合各种...
  • RS-485总线为两线半双工串口总线,使用双绞线以平衡差分方式传送数据,其主要特点有:传送速率快,最快达每秒10M位;传送距离远,最远达1200米;抗干扰能力强,在噪声环境下长距离驱动32个节点。
  • 在网上找了许久,发现FPGA用串口驱动LCD12864程序很少,基本上没有。刚开始窃喜,中间郁闷,最后还是高兴,为什么这样说呢!头一回在没有参考程序的情况下,完全是照时序图写(自信),中间调试过程遇到一点小插曲...
  • 为扩展应用范围,EIA又于1983年在RS422接口基础上制定了RS-485标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为TIA/...
  • 同时,还针对流行的PCI驱动程序、USB驱动程序、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书*的特色在于每一节的例子都是经过精挑细选的,具有很强的针对性。力求让读者通过亲自动手实验...
  • 具有友好的人机操作界面、强大的IO设备端口驱动能力,可与各种PLC、智能仪表、智能模块、板卡、变频器等实时通讯。由于在检测大量模拟量的工业现场使用PLC与组态软件通讯势必增加产品成本。而单片机接口丰富,与A/D...
  • RS422串口通信协议

    万次阅读 2008-12-11 21:56:00
    由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Salve),从设备之间不能通信,所以RS-422支持点对...
  • 单片机是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、...
  • 得实ds 200打印机驱动

    2020-07-06 21:55:41
    得实ds200驱动是专为得实DS200针式打印机提供的驱动程序,让您的计算机和打印设备更好的连接,提供更优质的...得实DascomDS-200打印机参数型号:DS200打印针数:24针复写能力:1+6张接口类型:串口、IE,欢迎下载体验
  • cpdkp770驱动是同型号打印机的驱动程序,本驱动支持windows系统,在使用这款打印机若...cpdkp770参数介绍打印机类型:24针106列平推式票据打印机打印速度:330字符/秒复写能力:1+6打印接口:并口,串口,欢迎下载体验
  • 在移植好的UCOsII项 目中添加串口、LCD的驱动程序 学 习在UCOSII下 ,多应用任务的简单编程实例 实验设备 EL-RAM-860教 学 实验 箱 ,PentiumII以上 的 PC机 ,仿 真 调试 电缆 ,串口直 连 电 缆 。 ...
  • 、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书最大的特色在于每一节 的例子都是经过精挑细选的,具有很强的针对性。力求让读者通过亲自动手实验,掌握各类Windows驱动 程序的开发技巧...
  • 点击上方电工电气学习,关注并星标专业的电工电气领域自媒体,不容错过欢迎转发朋友圈,欢迎文末留言壹概述PLC 的数字量输入接口并不复杂,PLC 为了提高抗干扰能力,输入接口都采用光电耦合器来隔离输入信号与内部...
  • 3、8路12位高精度模拟量信号输出:采样频率为1KHZ,模拟量输出精度为12位,模拟量输出范围是[0V-10V],驱动电流最大为20mA;可用于控制比例阀、变频器、直流电机、激光器等外部器件。4、抗强电磁干扰能力:采用...
  • 开发一套设备驱动同时具备串口和网络通讯能力,通讯接口在逻辑上是统一的,在此基础上串口和网络也有自己的IO通讯特点,根据不同的通讯方式,可以把IIOChannel实例转换成ISessionSocket或ISessionCom实例。...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 121
精华内容 48
关键字:

串口驱动能力