智能车嵌入式编程入门教程

        以下是我参加学校智能车培训的做的编程部分的笔记,内容很详细,非常适合小白入门。培训主要以CH32V307为主控的编程学习,涵盖了智能车主要的核心例程讲解与使用,虽然有些智能车选择使用的芯片不同,但是编程逻辑的思路是相同的。

  • 1. example中的例程

    • 比如LED例程中,mrs文件夹是工程文件,点开后缀为.wvproj的文件
    • 2-LED Blink Demo是工程文件,include为包含的文件,code文件夹为添加自己的代码文件,doc文件夹为注释性文件文档,sdk文件夹内为库函数,user_c和user_h是用来放置用户的c文件和h文件,是底层文件和库函数,其中有isr文件时中断文件,即与中断相关的文件,
      • user文件夹下还有main.c文件,这是主文件,即程序在执行时是从main.c里面的函数开始执行。函数下有while(1),是主循环
      • 主循环来了,有突发事情,需要单片机停下当前工作,先去执行中断里面的代码,就是中断。中断:是主循环以外,必须不得不执行的,具有更高优先级的任务
      • 中断有很多种,比如说串口中断,定时器中断,IO中断,所有的中断写在对应中断服务函数里面。这样当中断来了以后,程序会自动的从当前的主循环里面暂停跳出去去执行中断点的代码,执行完以后再回来继续执行主循环
      • 如果想看某个变量是怎么定义的,可以全选中变量,按住ctril键,鼠标会变成点击,就会自动跳到变量对应文件解释
  • 2. 代码部分

    • clock_init为系统时钟设置,144M为单片机处理速度,越大处理速度越快,效率更高,但是会很耗电
    • debug为调试串口
    • gpio为设置引脚输出输入

  • 1) 编译,将代码输入到单片机上

    • 在左侧工程栏,右键点击选中需要输入的Demo后缀文件,点击Build Project。生成了二进制文件

  • 然后,将仿真器与单片机连接,将仿真器插在USB接口上
  • 然后,在上面工具栏,先进行编译rebuild,再Download下载

  • 2) PIT例程

    • 定时器模块,不需要单片机参与,产生定时的中断
    • TIM2是定时器二,每隔500ms,呼叫单片机执行一次。
    • 把上一个例程LED闪烁的程序添加到TIM2的中断服务中去
      • 还需要在main.c中将LED引脚调至输出模式

      • 点击左栏isr.c找到TIM2的函数,把GPIO控制LED的函数写到TIM2函数中

  • 3) PWM例程

    • 本质上是用定时器来做的,只不过在定时器的基础上加了中间的量,它可以来控制它不同占空比的信号,还可以配置闹钟多长时间响一次,也就对应的产生的信号频率
    • pwm_int函数,初始化pwm这里面有三个参数,第一个参数是哪个通道就用哪个引脚输出pwm输出方波信号;

      • 在main函数外定义了TIM2即定时器2的通道一对应配置A15这个引脚

      • PWM第二个参数是输出方波的频率,第三个参数是占空比,注释上有计算占空比公式,PWM_DUTY_MAX默认为10000
        • 当第三个参数为1000时,对应占空比是10%,10000对应占空比是100%
        • 当产生1KHz的方波占空比为10%,让它驱动LED灯亮,就是1秒钟闪烁1000次
      • 如何改PWM的输出引脚

        • 按住ctril,点击宏定义TIM2_PWM_CH1_A15,看LED对应的引脚哪个是PWM指定能有的引脚

LED的PB7对应的引脚真好是PWM指定的通道引脚,然后把main.c里对应宏定义的引脚通道改了

      • 最终需要PWM来驱动电机,控制电机的转速,只要刚好那几个引脚有就行
      • 如何改变占空比,亮灭频率

        • 在主循环while(1)中用了定义了一个PWM函数,里面有了duty的参数,一直在配置占空比

  • 如何调试单片机

    • 在上面工具栏中有Debug,然后CPU处于停止状态,等你运行
    • 在上面工具栏中有Run,点一下,灯就开始闪烁了,说明这时候程序在全速运行,出现的很多窗口是我们可以去调试的,左边的窗口是返回编,我们写的代码,第一步要先把它转成汇编代码,这是他编译完以后的汇编代码,汇编代码最终会再转成二进制代码,下面的窗口是控制台命令,右边的窗口可以放置变量,主要调试是在中间窗口源代码。

  • 实现呼吸灯

    • 思路:让单片机发出高频率的方波通过改变占空比改变亮度,因为速度低的话人眼是可以看到闪烁的,高速变化人眼看不到闪烁,比如占空比是10%,那么LED的亮度只有10%,通过控制占空比来得到调光的效果
    • system_delay是每隔500ms更新一次占空比
    • 只要实时改变duty的值,就能让亮度发生改变,那么可以让每次循环以1%的占空比递进
  • 使用按键控制(输入)

    • 使用IO接口前要进行配置条用了gpio_init函数,第一个参数是配置哪个IO引脚,第二个是GPO(general purpose output)就是输出模式,就只是定义了0还是1,第三个是GPIO_LOW,就是默认输出0,只是在初始状态下有影响,

      • 第四个是GPO_PUSH_PULL,按住Ctril查看定义,单片机的IO有有输入输出两种模式,细分下来还可以分成很多模式,

        • 四种输出模式
        • 第一种是PUSH_PULL,推挽输出,是输出最常用的模式,特点是输出电流可以最大,可以输出几十毫安的电流,
        • 第二种是开漏输出,特点是智能输出0,不能输出1,如果在不接外部电路的情况下,因为输出集单片机内部输出有一个晶体管或MOSS管开漏,它的漏极是断开的,漏极没有接上拉电阻,开漏,这时候智能输出0,不能输出1,如果要它输出1,会处于悬空状态;但是如果想在开漏模式下输出1,在外部可以给他加上上拉电阻,这个引脚通过一个电阻,另一端接到电源,比如3.3v,就能够输出高电平了。因为如果我们想让这个引脚输出5v电压,但是单片机供电是3.3v,在推挽输出下是不能输出5v的,最高只能输出到电源电压,但是如果在开漏模式下,在外面给它上拉到5v,就可以输出5v了,有特殊应用
        • 第三种AF,特殊功能PUSH_PULL,特殊功能的推挽和开漏,就是我们这个引脚不只是可以用它作为通用的输入输出,我们还可以配成一些其他的功能,配成串口的输出,配成总线,以太网,USB等各种外设的输出,这种情况下可以配成AF模式
        • 输入模式
        • 输出有四种模式,第一种是模拟输入,不只是可以识别0还是1,还可以识别到它是几伏的电压,模拟输入,
        • 第二种是floating悬空输入,
        • 目前输入默认使用下拉或上拉输入,输出默认使用通用的推挽输出模式,
    • 将LED的GPIO初始定义修改下,改成输入模式;然后用一个LED来检测按键是否按下了,让按键按下了输出高电平,没按输出低电平,

    • 看LED的例程,让E0引脚状态翻转,按住Ctrl,跳转到定义函数,看有没有其他的状态函数可以使用

set是设置引脚输出的状态,控制LED灯的亮灭,

get是引脚状态的获取,看引脚状态是1还是0

  • 4) ADC例程

    • 通过引脚检测外部输入的电压是多少伏,这个可以用到检测传感器传回来的电压,
    • 首先进行ADC的初始化,然后在主循环里面直接用adc_convert就可以把他读出来了,读完以后用printf把结果打印输出来,

    • 如果我们要改ADC通道的引脚,一定要按住Ctrl跳转到对应ADC指定引脚是怎么定义的,一定是要看ADC有指定对应的引脚才能使用

    • 可以定义一个变量来存放ADC检测到的电压

这样就每50ms会采集ADC电压放到变量里面,

    • 当没有传感器接口引脚没有接到主板时,这个引脚是处于悬空状态的,它的电压值采集到电压是随机的,当将引脚认为接到地,采集到的结果应该是0,如果接到3.3v,得到的结果是4088,理论值应该是4096,因为在adc_convert时,有12bit,配的是12位的adc意味着如果外部电压刚好等于它的参考电压时,
      • adc内部是一个个模块,它会把外部电压跟参考电压进行比较,也就是把参考电压分成多少位,比如12位就是分成2的12次方就是4096份,它会逐个比较你的外部电压是跟哪个刚好对上就在那两个中间,就取这个结果,有量化的过程,量化编码最终输出得到的结果,如果得到的电压是4095,那么说明它刚好等于参考电压3.3v,得到4096/2,说明外部等呀等于参考电压3.3v的一半,用ADC的时候可以通过一个公式去计算外部电压是多少。
      • 注意,不能直接去除得到结果,

我们在初始定义变量的时候要把变量定义成float型,因为adcconvert得到的是整型,4095也是整型,整型除以整型还是等于整数0,那么我们需要把他转化为有效数的,那么在分式前强制转换为float浮点型,强制类型转换

也可以把3.3v转化为3300mv精度转换,

      • ADC还可以配置成12bit的,8bit等,位数越高,精度越高,但是位数低,转化率会高,让adc转化的速度更快,还有就是,8bit只需占据一个字节,而10bit12bit需要两个字节,占据空间更大,因为单片机存储空间太小了,只有几k,可以节省空间,
  • 5) UART例程,串口

    • 串口:将电脑和设备进行通信,通过两根线,一个发送一个接受,高低电平实现互相通信,那么需要设计协议,比如10101为c,但是还需要约定发送数据的时间和速度,叫数据的波特率(发送速率),可以约定每秒钟发送多少个数据;波特率越高发送速率越快,但是太高的话容易受到干扰,且数据通距离不能太长,如果距离太长,速度会要求低一点,要不然容易出现误码,
    • 用的时候首先要明确,用的是单片机的串口几,然后串口对应连到单片机的哪个引脚上
    • 蓝牙模块

    • 也是用的串口,然后看RX和TX对应单片机的什么引脚,按住Ctrl键跳转,继续跳转,就可以看到串口指定对应引脚集合,我们要更改为串口指定的引脚,

      • 比如UART3的左列代表TX,右列代表RX,引用后改成这样,把所有涉及串口输出的都要改

中断服务函数也要改串口,把默认的串口三内中断函数加到串口二中

      • 然后,将仿真器上的RX和TX接到单片机上蓝牙模块对应的RX和TX引脚

      • 然后就可以用仿真器的串口和单片机的串口进行通信了,我们还需要再安装一个串口调试软件,
        • 首先,我们需要选择哪个串口,电脑的串口,选择电脑的设备管理器的端口,WCH-LINK对应的是COM26端口

所以,软件界面的端口号选择COM26,点击打开串口,电脑就开始接受数据了,没显示是因为单片机没有发送数据,在编程软件调试Debug中就可以了

      • 程序代码部分

        • 用uart_write_string输出数据在串口中,然后在主循环里面就是等待接受,做了回环,你给我发数据,我再给你发回去,如果收到数据(if),读出来,再原封不动的发送过来,

        • 可以试着把ADC的采集结果发回来,还可以用write_byte,只采集一个字节,那么在前面12bit要改成8bit

但是,发送回来的是字母,是因为接受到的是二进制0001,用字符串的形式显示,就是用ASCI二码来显示,就是显示0001对应的ASCI二码对应的数据,所以显示出来的是字符

我们想看到的是转化的结果,我们可以勾选以十六进制的方式显示,目前显示出来的就是当前采集的电压值,如果是0v,那么就是显示00,如果连接到3.3v,就是显示0Xff,就是1111,也可以以十进制的方式显示,

  • 控制舵机频率在50hz,电机频率在5000hz
    • PWM控制电机频率在5khz到10khz,可以让电机转速的声音变小,如果调太高的话,电机可能不支持高频率输出,
    • 由PWM控制电机改成控制舵机时,要注意频率
  • 控制电机转

    • 给电机一个引脚输出控制PWM高电平,另一个引脚输出低电平,控制他们的占空比,就可以控制他们的不同转速,
    • 听到很尖锐的声音,这是电机在以设定的频率振动,刚开始电机并不会转,是因为占空比太低了,要到差不多20%的时候,电机才会转动
    • 电机正负极接反了并不会烧,只是会让电机正转和反转,如果电机出现反转,我们可以让PD13由低电平改成高电平,那么此时如果PWM占空比是会由原来的20%变成80%时,所以在设定占空比的时候要反过来,原来是20%,要改成70%,

    • 电机在转的时候PWM波形,用示波器显示,
    • 使用示波器显示PWM波形 
      • 示波器的探头,黑色接在电源接口的GND,红色接在被测信号上,

      • 如果波形稳定不下来,(单片机1117的38:00)
        • 调一下触发按钮,

        • 把触发源改成ch1

        • 能看出来占空比一直在变,
        • 也可以去测量频率,按下频率键,还可以用示波器看很多的参数,
  • OLED

    • 原理
    • oled初始化,oled_int,一共有四根线,其中两根线PB13和PB15接的是I方C接口,这是一个串行总线接口,SCL是时钟,SDA是数据,也就是说这种总线通信的时候SCL来发八个时钟,与此同时SDA同步发数据,这时候单片机发(主),oled收(从),液晶屏会检测SCL电平的变化,检测到它从低变高了,这时时钟沿,他会去检测sda是0还是1,如果它是0,说明单片机给他发的数据是0,然后最后八个读出来的是00011,然后就知道它给发的数据是什么,通过单片机不停的给他发数据,它把数据收下来,也就把他显示出来,就是这么一个过程
    • 如何进行配置?
      • 首先配置PB13和PB15两个IO,然后延迟一段时间后,调用了很长的代码,write byte就是单片机给oled发数据

  • 配置完后,单片机才开始显示,color_turn就是让它正显还是反显
    • 正显就是你要显示的内容是白的,不显示的就是背景,
    • 然后如果把0改成1就是反显,就是不显示的地方是白的,显示的地方是黑的,

  • Display是反转显示,不用理他,
  • 后面还有oled_test,就是测试函数,
    • 有一个oled_show picture,显示一副图片,把显示的内容放在显存里面,

  •  
    • 这个函数还有几个变量,x和y表示起始坐标,可以让显示的图片全屏显示,也可以在某一个地方显示,
    • 后面就是size你要显示图片的大小,oled的分辨率是128*64,也就是横轴有128个点,纵轴有64个点,这个分辨率刚好合适,如果太高的话,单片机刷新不过来
    • 要显示的图片是bump1,这代表的是一个数组,就是你看到的图片数据,一共有128行,就是对应每一列的数据,每个点都是用十六进制数据表示,如果对应的那一列全是0,表示这一列都是处于熄灭状态,如果对应的全是ff,那么表示的是一条竖线,
      • 可以一个个去自己写,也可以用取字模软件,把图片输进去,然后它会自动转化为一个数组,
    • 最后靠refresh函数刷新出来的,然后delay1000ms,就clear清除掉了,所以我们实际看这个图像在那只显示了一秒钟,然后就没了

    • 最后有一个sprintf,定义了一个buf的字符串数组,把结果要显示的内容放在buf数组里面,执行完以后buf里面第一个量是d,第二个是u,第三个是t,把他打印到buf里面,然后用showstring函数把buf里面的内容在屏幕里面显示出来,最后还要有refersh
      • 在屏幕上看到duty数值在跟着变化,

    • 那么showstring函数中,第一个参数和第二个参数代表显示的位置,起始的坐标,(0,32)表示第零列,第32行开始显示
    • 针对每一个显示的字符,都定义了一个字模,在code文件夹下oled。found中定义的,也就对应每一个显示的字符大小高度都是固定的,

  • 通过定时器的方式把延时去掉,
    • 原理:定义一个变量,一毫秒定时标志位,unit8_t tim_1ms_flag=0;让他每隔一毫秒变成1一次,那么可以在主循环里面一直高速不停的去检测他,什么时候从0变成1了,就执行我想要的函数,然后再把他清零,这样主循环就可以不用delay,同时让它每隔1ms去精确的执行函数,

    • 每隔10ms,让duty+50,然后显示一次,实现主循环没有延迟,一直在高速运转,如果10ms为0的时候,判断不会进去,只要从0变成1,他马上就会执行到,并且保证只执行一次,再清零掉,

    • 如何让标志位变成1?使用定时器中断
      • 定时器10ms一到就会响,程序进去执行让标志位变成1,然后再返回,
      • 在函数定义定时器中断,然后再isr的对应定时器中断服务函数中添加,
      • 但是程序还是报错了,原因是,在main.c中定义了tim_10ms_flag,但是在isr.c里面也用到了这个变量,因为这个变量的有效作用的范围,
        • 在函数里面定义的变量时局部变量,他的作用仅仅是在这个函数内部有效,
        • 解决办法,在需要引用的地方(isr.c)定义变量的时候加extern,那么就不需要再给他赋值了,相当于我们手动声明这个变量的作用范围拓展到这个文件中,那么在这个文件中就可以去用了

    • 将cpu解脱出来的好处,
      • 防止将cpu随着delay时间的增加,执行频率降低,
    • 增加其他秒数置位,四种变量准时被置位,
      • 做一个全局维护时钟,心跳信号,system——time,系统运行时间

      • system time不需要清零,因为他的上限很大,
  • 在zf_device中给了摄像头的程序,编程难度较大,camera.c
  • 3. 算法思想

    • 电磁

      • 左右差 还可以用卡尔曼滤波

      • 差比和

不同电感会影响算法,红色竖的电感判断弯道的优先级要高于横电感,会提前预知弯道处理,比横向电感的变化率更大,这样就可以提前打方向,减速的效果

    • 舵机PID控制

      • 增量式,当目标速度与实际速度差值较大时,考虑加大积分项,让他很快达到目标速度,当达到目标速度后,减小积分项,这样保持稳定速度在赛道上行驶,

    • 环岛

      • 环岛是由一根电磁线进入环岛后,电磁线再从环岛另一侧出来,一根电磁线再环岛周围一定会有交叉点,这个点的电磁很特殊,特殊于其他元素的电感值,可以考虑判断里面单独加一个判断环岛的一个电感,当这个电感达到环岛的位置时,他相对于其他直道或者弯道会有一个猛增的趋势,这样就可以判定是一个环岛,再根据左边或者右边的普通电感来判断环岛的入环口是左环还是右环,然后进入环岛后再使用普通的PID去寻迹,最后当车达到出环口的时候,这个特殊电感又会采集到这个高的电感值,这时候就可以判定他为出环,
      • 在环岛交界处,一共有四条导线,所以电感信号会很大,

    • 三叉路口

      • 需要用到斜电感,电感垂直的时候信号最大,当探测线垂直的时候,信号最大,三叉路口正好是两个外八,正好把斜电感垂直了,所以信号最小,而再圆环的时候信号最大。而在平时的时候,斜电感信号比较小,
      • 水平电感在三叉路口电压会消失
    • 十字路口

      • 左右两边电感数值相同,直接就会冲过去,
    • 注意事项

      • 使用不同PID

        • 当在小弯的时候可能小车以正常速度拐弯能过去,当出现大弯的时候可能出现转向不及时的情况,然后导致车冲出赛道的现象,可以考虑使用不同的PID,比如车在直道上行驶的时候,电感是某个值,这时候可以考虑使用直道上的PID,当在弯道的时候可以适当让P给大点,增加转向密度,这样弯就可以转过去,当在直道上跑时,P较小,跑的很直,但是弯过不过去,当在弯道上时,P较大,弯可以拐过去,但直道跑得歪歪扭扭,
        • 调节电机时,当车从弯道出来后,想要车有一个很强的加速,让他冲过这个直道,所以为了达到加速目的,可以让积分项变大,达到预期的速度后再降回基本值,但是在增大的过程中,一定要防止积分项过于饱和,因为积分项过于饱和的话,他会在你的限幅之内,不超过限幅的情况下,会达到峰转情况,所以对于积分调节,还是需要去对他进行控制,不要让他过分的饱和,
  • 故障维修

    • 电路维修

      • 当电路出现故障的时候,当晶体管,电容炸掉的时候,我们可以用示波器,万用表进行测量,比如有些芯片击穿烧掉了,可以用万用表直接测两个引脚的通断,如果短路掉的话,万用表就会出现急急急的响声,这时候就可以判断芯片应该是被烧掉了,也有可能是焊接短路的现象,
      • 还有就是示波器就与的检测,比如电机,当你代码写进去的时候,你反复考究过你的电机是没有问题的,而且调用的函数没有问题,但是电机就是不转,这时候就可以考虑用示波器去测电机口的输出波形,看是否为你所需要的方波,对于他的控制,还有就是对于舵机可以转向,但是每次转向的力气不是很大,这时候就可以万用表或者示波器去看他的电压和波形,他有可能是因为他的波形不规整,不是所需要的方波,也可能是电压达不到他的额定工作电压,所以在电路设计的时候,电压是否分到你所需要的电压,这时候就需要对板子就行错误的排除,
      • 购买交代清洁剂

    • 软件故障

    • 电磁排布

  • 摄像头原理

    • 摄像头常见分析比例:144*120/80*60,这是像素点,就是4800个点去显示你的东西
    • 阈值就是灰与白之间,进行二值化处理,所以让黑色全部变成0,白色变成1,所以当数值为小于20的时候,输出为0,当数值80-120的时候输出为1,这样就从灰度图像变成二值化的黑白,使其功耗变小
      • 将图像建立坐标轴,x:60,y:80,然后从(0,40)和(60,40)画基准线,
      • 然后从基准线想左右递推,找黑点
      • 第三步,从x的0~60,每一行都进行上一步骤
      • 第四步,得到了边界线,然后在每行的边界点取中点(这是在弯道用的),
      • 每行都进行取中点,得到寻迹线(竖),
      • 自己设一个预判线(横),根据摄像头的高低去调,预判线与寻迹线的焦点,与同行基准线的偏差,
      • 得到error偏差,就是PID的P,舵机不需要I,舵机一般都是PD控制的,I会导致其延迟性太强,电机一般是
        • 有两种方式,一个是作差式,寻迹线与同行基准线的error控制舵机,一个是角度式子,寻迹线与底线中点连起来,与基准线形成的夹角,称为误差角,再给舵机打角度,
  • 舵机占空比:

    • 250-0度,1250-180度

  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林清海笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值