请选择 进入手机版 | 继续访问电脑版

智凡单片机论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1459|回复: 0

初学单片机时总结的想法及一些程序

[复制链接]

80

主题

80

帖子

288

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
288
发表于 2018-8-31 21:39:40 | 显示全部楼层 |阅读模式
一、单片机编程总结
1、要养成总结的好习惯,总结不仅是对自己学习的一个总结,还是对学习过程的一个回顾与加深,还可避免第二次犯错。

2、编写程序之前先要有一个对该项目熟悉的了解,做到心中有数,列一个大致框架。仔细推敲该怎么布局,怎样布局最合理,该步骤很重要。要分析先做哪个模块,具体到该模块的具体步骤,各个函数怎么命名,与其他模块的衔接等。最好拿张纸记下重要过程。

3、对于c语言的模块化编程,要先分好各个模块,一个模块一个模块的编程,确定一个顺序,按顺序来,该模块成功之后再编写下一个。对于头文件,当该模块编写好之后再编写该模块的头文件。

4、出现警告不要忽视,说明该程序一定有不合理之处,要弄清其来源,找到解决办法。找来源时要有针对性,可上网搜一下该方面的资料,或向别人请教。例如,居然把另一个工程内的main函数加入了这个工程。还有居然函数命名重复。还有根据实验现象分析原因,层层递进。还有端口定义时居然选错了接口。有时,实在解决不了就休息一下,在想也挺好的。再简单的地方也要注意一下,都有可能出错。



二、芯片操作简单总结
对芯片的操作主要是对芯片内寄存器的操作,芯片内寄存器在存储器上映射的都有自己的唯一地址,这也就是对相应的地址的操作。看芯片,首先看时序图,再了解相应的寄存器,了解是如何操作的,定义需要的端口(程序可以识别),编写写操作程序和读操作程序。

如何往芯片内写入数据,如何读出数据,通过哪个端口输入或读出(最主要的地方)。

通过总线连接芯片时,首先要了解该总线的协议。I2c总线连接的芯片,主要通过该总线去控制该芯片。

1、点阵中一个74hc595用于列的选择,令外两个用于颜色的选择,点阵相当于二极管的集合,

一端给高电平,另一端给低电平,二极管才能亮。只是一端选择不同时,亮不同的颜色。

定时器工作模式的选择:高四位是设置定时器T1,低四位设置T0。然后各模式的后两位设置工作模式。当设置两个定时器时,注意使用或(|)。当用中断时,注意进入中断后,该清零的要清零。

2、串口收发:波特率的设置一般用模式2(自动重装初值),因为不同的装置,处理数据的能力不同,设置波特率主要为了照顾低速装置及为了彼此间的通讯。中断标志位要软件清零。设置串口中断时,收发无论哪一个产生都能进入中断函数,因此要注意设置中断函数。(自我感觉一般设置一种功能,当做上位机或下位机)。

发送用中断的话,要解决第一次该怎么进入中断,因此首先要发送一次,此后就可以进入中断了。一次只能发一字节,而且只有在TI置一之后才能发送下一位。

3、Pcf8591ad转换,有四个通道的输入,读pcf8591时,选通哪一个通道,读的就是那个通道输入的电压,转换后的数据存储在该芯片内,再读出。读时先写芯片的地址,在写器件的子地址(0x40|通道号),然后就是读出的数据。

4、Da转换是先向芯片内写入器件地址,在写子地址(0x40),在写要转换的数字量。

器件地址芯片资料有介绍。

5、对于液晶显示,写入数据显示后,他会一直显示,不用持续刷新,要想改变,只有重新输入。

6、对于ds1302时钟芯片,读数据时是在写入数据时的第八个时钟下降沿就读出第一位数据的的,然后再为下次输出做准备,注意程序的写法,还要注意返回值放的位置。

7、Ds1302中先指明寄存器,再向其中写入数据。芯片资料上的寄存器标出的是地址。(写保护处程序还不大明白,不是一直都有写入吗?为什么还打开写保护?)

(根据前面的大侠,可以在初始化时间后设一标志,有此标志则不用再初始化时间。但是如果断电后,MCU的RAM是无法保存这个标志的,因此可以用DS1302的RAM保存该标志,待上电后读取该标志。我也是初学者,最近也打算用DS1302。不知说法对不,我也还没具体实施,多交流)

8、初始化最好还要写一下,以防以后忘记。有时注意读出或写入时,首先操作的是最低位还是最高位,可根据时序图判断出。

9、对于红外收发,接收时,他是根据两个下降沿之间的时间长短来确定是高电平还是低电平,写程序时,先用定时器确定时间长短,保存,然后再转化成二进制(该程序写法多看看,很好)。

10、步进电机:主要做开关用,步进电机的力矩随转速的升高而降低。主要用在机床上零部件加工的自动进给。对有较高精度的控制场所都可也使用。

步进电机是将电脉冲信号转变为角位移或线位移的开环控制元步进电机件。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度,称为“步距角”,它的旋转是以固定的角度一步一步运行的。可以通过控制脉冲个数来控制角位移量,从而达到准确定位的目的;同时可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。

11、伺服电机:(servo motor )是指在伺服系统中控制机械元件运转的发动机,是一种补助马达间接变速装置。伺服电机可使控制速度,位置精度非常准确,可以将电压信号转化为转矩和转速以驱动控制对象。伺服电机转子转速受输入信号控制,并能快速反应,在自动控制系统中,用作执行元件,且具有机电时间常数小、线性度高、始动电压等特性,可把所收到的电信号转换成电动机轴上的角位移或角速度输出。分为直流和交流伺服电动机两大类,其主要特点是,当信号电压为零时无自转现象,转速随着转矩的增加而匀速下降。

直流电机:范围较大,小车上都是。

12、汉字概览:

为了将汉字在显示器或打印机上输出,把汉字按图形符号设计成点阵图,就得到了相应的点阵代码(字形码)。

为在计算机内表示汉字而统一的编码方式形成汉字编码叫内码(如国标码),内码是惟一的(相当于该字的身份证号)。为方便汉字输入而形成的汉字编码为输入码,属于汉字的外码,输入码因编码方式不同而不同,是多种多样的。为显示和打印输出汉字而形成的汉字编码为字形码,计算机通过汉字内码在字模库中找出汉字的字形码,实现其转换。

机内码

    根据国标码的规定,每一个汉字都有了确定的二进制代码,但是这个代码在计算机内部处理时会与ASCII码发生冲突,为解决这个问题,把国标码的每一个字节的首位上加1。由于ASCII码只用7位,所以,这个首位上的“1”就可以作为识别汉字代码的标志,计算机在处理到首位是“1”的代码时把它理解为是汉字的信息,在处理到首位是“0”的代码时把它理解为是ASCII码。经过这样处理后的国标码(内码)就是机内码。

如果我们把这个“口”字图形的“.”处用“0”代替,就可以很形象地得到“口”的字形码:0000H 0004H 3FFAH 2004H 2004H 2004H 2004H 2004H 2004H 2004H 2004H2004H 3FFAH 2004H 0000H 0000H。计算机要输出“口”时,先找到显示字库的首址,根据“口”的机内码经过计算,再去找到“口”的字形码,然后根据字形码(要用二进制)通过字符发生器的控制在屏幕上进行依次扫描,其中二进制代码中是“0”的地方空扫,是“1”的地方扫出亮点,于是就可以得到“口”的字符图形。

汉字字模按国标码的顺序排列,以二进制文件形式存放在存储器中,构成汉字字模字库,亦称为汉字字形库,称汉字库

两种编码方法,见头文件

GB1616.h


1 //------------------ 汉字字模的数据结构定义 ------------------------//
2 struct  typFNT_GB16               //汉字字模数据结构
3 {
4      unsignedchar  Index[3];             //汉字内码索引  
5        unsignedchar   Msk[32];                      //点阵码数据
6 };
7
8/////////////////////////////////////////////////////////////////////////
9// 汉字字模表                                                        //
10 // 汉字库: 宋体16.dot,横向取模左高位,数据排列:从左到右从上到下        //
11 /////////////////////////////////////////////////////////////////////////
12 conststruct  typFNT_GB16 codeGB_16[]=         //数据表
13 {
14 /*------------------------------------------------------------------------------
15 ;  源文件 /文字 :徐
16 ;  宽×高(像素):16×16
17 ------------------------------------------------------------------------------*/
18 "徐",0x10,0x80,0x10,0x80,0x21,0x40,0x42,0x20,0x94,0x10,0x1B,0xEC,0x20,0x80,0x60,0x80,
19 0xAF,0xF8,0x20,0x80,0x22,0xA0,0x24,0x90,0x2A,0x88,0x21,0x00,0x00,0x00,0x00,0x00,

这个结构,很简单的:一个是内码,一个点阵序列,以前的点阵库是按内码顺序放的,不需要内码索引的,如果只放部分汉字,就需要内码索引了。(前面的汉字“徐”是为了要输出“徐”的时候找到该字的点阵序列,这个点阵序列是自己写的,当用1602显示时,因为该芯片内存在英文的点阵序列,所以就不用写了)一般内码两个字节就行了,多用1个字节是加了个尾0而已,这样,汉字内码处直接放汉字字符串就可;

codeGB_16[k].Index[0]

codeGB_16[k]说明有一个结构体typFNT_GB16的数组叫做codeGB_16

codeGB_16[k]是数组中第k+1个成员

index是结构体typFNT_GB16的成员,所以可以用codeGB_16[k].Index来进行引用

同时index又是个数组,所以可以index[0]



if((codeGB_16[k].Index[0]==c[0])&&(codeGB_16[k].Index[1]==c[1]))

&&是 逻辑与运算符

意思是 &&符号的两边的值都为真 &&的值才为真,也就是 true && true =true

这句的意思是

codeGB_16[k].Index[0]==c[0]  和 codeGB_16[k].Index[1]==c[1] 同时成立

if下面的语句才执行

codeGB_16[]是个结构体数组,codeGB_16[k].Index[0]是说结构体数组的第K个结构体的index成员的第0个元素值。



13、12864液晶:

每个显示点对应一位二进制数,1 表示亮,0 表示灭。存储这些点阵信息的RAM称为显示数据存储器。要显示某个图形或汉字就是将相应的点阵信息写入到相应的存储单元中。

绘图RAM的地址计数器(AC)只会对水平地址(X  轴)自动加一, 当水平地址=0FH  时会重新设为00H  但并不会对垂直地址做进位自动加一,故当连续写入多笔资料时,程序需

自行判断垂直地址是否需重新设定

1、绘图RAM(GDRAM)

绘图显示RAM提供128×8 个字节的记忆空间,在更改绘图RAM时,先连续写入水平与垂直的坐标值,再写入两个字节的数据到绘图RAM,而地址计数器(AC)会对水平地址(X 地址)自动加一,当水平地址为0XFH 时会重新设为00H ;不会对垂直地址做进位自动加 1. 。在写入绘图 RAM的期间,绘图显示必须关闭,

// 显示汉字  
voiddispString (uchar X, Y,uchar *msg)    //X为哪一行,Y 为哪一列。msg
为汉字
{
     if(X==0)       X = 0x80;        // 第一行,汉字显示坐标
    else if(X==1) X = 0x90;     // 第二行
     else if(X==2) X = 0x88;     // 第三行
     else       X = 0x98;        //第四行
     Y = X + Y;                //Y 为1 往右移一位
    write_com(Y);         // 写入坐标
while (*msg)
{
        write_data(*msg++); //显示汉字
}
}
//////////////////////////////// //////////////// ///////////////
// 显示图象
voiddisppicture(uchar code *adder)
{
    uint i,j;
//*******显示上半屏内容设置
    for(i=0;i<32;i++)                // 上半屏32个列地址
     {
           write_com(0x80 + i);    //SET  垂直地址 VERTICALADD
         write_com(0x80);          //SET   水平地址 HORIZONTAL ADD
         for(j=0;j<16;j++)
            {
                write_data(*adder);
                adder++;
            }
     }
//*******显示下半屏内容设置
    for(i=0;i<32;i++)               //
     {
           write_com(0x80 + i);     //SET 垂直地址 VERTICALADD
           write_com(0x88);           //SET  水平地址 HORIZONTAL ADD
           for(j=0;j<16;j++)
            {
                write_data(*adder);
              adder++;
          }
     }
}


对于C语言,定义的变量,自动为其分配空间,其地址为该变量的名称。通过该名称,可以在内存中招到该数据,经过运算得到新数据,而汇编中需要编程者自己定义存储空间及把数据送到累加器等进行运算,每一步都需要编程者操作。而C语言这些过程由编译器去完成。

百度搜索:

①、单片机C语言,其变量的内存开辟是如何进行的?难道是编译器,在编译过程中智能地加入分配与回收的代码?关键之处在于我所做的程序,如何保证其没有内存溢出错误?如果我进行的是递归运算,这样的话,内存需求是很难自己计算的。

②、单片机C语言在变量定义上是否会受到约束?比如浮点型数据的乘除运算,通过汇编还写,代码相当复杂,如果直接C语言来写,岂不过份简单?
③、单片机C语言生成的hex文件中,指令及数据的ROM的地址分布是否编译器自动分配?可否用户进行分配?
c语言写的单片机程序,先由1个程序(好像是c51.exe)编译,编译完成后,变量的存储空间大小已经安排好,只是还没分配具体地址(地址浮动),接下来有另一个程序(好像是a51.exe)进行连接,连接以后,具体地址确定
回收代码?应该是回收存储空间。
如果变量过多,编译会提示数据段too large,要保证其没有内存溢出错误,主要考虑堆栈是否溢出,要靠经验
单片机c语言一般禁止递归,一般都避免用递归运算,单片机毕竟不是PC,会影响速度的,要递归的话,用DSP芯片更合适,总之,要会挑合适的芯片
2:
变量的大小(位数)一般和芯片累加器的位数一样,比如51常用8位的,因为它是8位单片机
单片机可以定义位变量,但是不可以定义位数组
用c语言写只是看着简单,实际生成的代码量是最多的,用于控制的单片机几乎不用浮点数运算,不仅慢还麻烦还占地方,如果是DSP芯片,本身有适合的硬件结构,会好很多
3:
一般是自动分配的,
可以c语言和汇编语言混合编程,也可以用Keil C在线汇编
芯片与外部的数据交换都是通过端口进行的。

三、一些典型程序总结
1、一些延时函数(51单片机,具体使用时可先测试下)
void Delay(uint z)
{
       uintx,y;
       for(x=z;x>0;x--)
       for(y=110;y>0;y--);      
}
Z=1000时延时1秒
编写一段关于延时的函数,主要利用for循环,代码如下:
void delay_ms(unsigned int ms)
{
unsigned int i;
unsigned char j;
   for(i=0;i<ms;i++)
    {
       for(j=0;j<200;j++);
       for(j=0;j<102;j++);
    }

}
其中ms是输入参数,如果输入1,就是要求程序延时1ms。
j变量是调整程序运行的时间参数。调整j的数值,使1次循环的时间在1ms
#include "delay.h"
/*------------------------------------------------
uS延时函数,含有输入参数 unsigned char t,无返回值
unsigned char 是定义无符号字符变量,其值的范围是
0~255 这里使用晶振12M,精确延时请使用汇编,大致延时
长度如下 T=tx2+5 uS
------------------------------------------------*/
void DelayUs2x(unsigned char t)
{  
while(--t);
}
/*------------------------------------------------
mS延时函数,含有输入参数 unsigned char t,无返回值
unsigned char 是定义无符号字符变量,其值的范围是
0~255 这里使用晶振12M,精确延时请使用汇编
------------------------------------------------*/
void DelayMs(unsigned char t)
{

while(t--)
{
    //大致延时1mS
     DelayUs2x(245);
        DelayUs2x(245);
}
}
2、写数据、命令、16、8位之间转换

void Write_Data(unsigned char DH,unsignedchar DL)
{
      CS=0;
       RS=1;
       P0=DH;
       RW=0;
   RW=1;

       P0=DL;  
       RW=0;
       RW=1;
       CS=1;
}
void Write_Data_U16(uint y)
{
       ucharm,n;
       m=y>>8;
       n=y;
       Write_Data(m,n);
}
void Write_Cmd(uchar DH,uchar DL)
{
       CS=0;
       RS=0;

       P0=DH;
       RW=0;
       RW=1;

       P0=DL;

       RW=0;
       RW=1;
       CS=1;
}
void Write_Cmd_Data (uchar x,uint y)
{
       ucharm,n;
       m=y>>8;
       n=y;
       Write_Cmd(0x00,x);
       Write_Data(m,n);

}
3、很不错的想法

TH0=(65536-2000)/256;               //重新赋值 2ms
TL0=(65536-2000)%256;

void EX0_ISR (void) interrupt 0 //外部中断0服务函数
{
static unsigned char  i;             //接收红外信号处理
  static bit startflag;                //是否开始处理标志位

if(startflag)                        
   {
   if(irtime<63&&irtime>=33)//引导码TC9012的头码,9ms+4.5ms
           i=0;
                  irdata=irtime;//存储每个电平的持续时间,用于以后判断是0还是1
                  irtime=0;
                  i++;
                    if(i==33)
                       {
                            irok=1;
                             i=0;
                           }
   }
       else
              {
              irtime=0;
              startflag=1;
              }

}
4、串口通信(注意:不要打开定时器中断、发送与接收公用一个中断服务函数,注意设置。定时器是设置波特率的)
#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
uchar a,flag;
void init()
{
       TMOD=0x20;
       TH1=0xfd;
       TL1=0xfd;
       EA=1;
       TR1=1;
       SCON=0x50;
       ES=1;
       flag=0;
}
void main()
{
       init();
       while(1)
              {
                     if(flag==1)
                            {
                                   ES=0;
                                   flag=0;
                                   SBUF=a;
                                   while(!TI);
                                   TI=0;
                                   ES=1;
                            }
              }
}
void ser() interrupt 4
{
       RI=0;
       P1=SBUF;
       a=SBUF;
       flag=1;
}
5、红外蔽障

if(Right_Flag)   //利用数码管的a,和d 段的亮灭表示左右遮挡情况
           DataPort |=0x08;
         else
           DataPort &=0x01;

     if(Left_Flag)
           DataPort |=0x01;
         else
           DataPort &=0x08;
右遮挡时right—flag为一,左遮挡时left--flag为一。无遮挡时全亮(dataport全为0)。
哪个遮挡,对应的哪一段灭。
6、键盘扫描程序:
#include<reg51.h>
#define uint unsigned int
#define uchar unsigned char
void delay(uint);
void display(uchar);
uchar keyscan();
uchar code table[]={0xc0,0xf9,0xa4,0xb0,
                    0x99,0x92,0x82,0xf8,
                                   0x80,0x90,0x88,0x03,
                                   0xc6,0xa1,0x86,0x8e,0xff};

uchar num,temp,num1;
void main()
{     num=17;
   P0=0xff;
       while(1)
       {
              display    (keyscan());                                   
       }
}
uchar keyscan()
{     P1=0xfe;
   temp=P1;
       temp=temp&0xf0;
       while(temp!=0xf0)
       {     delay(5);
            temp=P1;
               temp=temp&0xf0;
             while(temp!=0xf0)
              {      
                  temp=P1;
                  switch (temp)
                       {
                                    case 0xee:num=1;
                                          break;
                                    case 0xde:num=2;
                                          break;
                                    case 0xbe:num=3;
                                          break;
                                    case 0x7e:num=4;
                                          break;
                      }
                           while(temp!=0xf0)
                                   {
                                          temp=P1;
                                          temp=temp&0xf0;
                                   }              
                }
}
       P1=0xfd;
   temp=P1;
       temp=temp&0xf0;
       while(temp!=0xf0)
       {     delay(5);
            temp=P1;
               temp=temp&0xf0;
             while(temp!=0xf0)
              {      
                  temp=P1;
                  switch (temp)
                       {
                                    case 0xed:num=5;
                                          break;
                                    case 0xdd:num=6;
                                          break;
                                    case 0xbd:num=7;
                                          break;
                                    case 0x7d:num=8;
                                          break;

                      }
                           while(temp!=0xf0)
                                   {
                                          temp=P1;
                                          temp=temp&0xf0;
                                   }               
                }
       }
       P1=0xfb;
   temp=P1;
       temp=temp&0xf0;
       while(temp!=0xf0)
       {     delay(5);
            temp=P1;
               temp=temp&0xf0;
             while(temp!=0xf0)
              {      
                  temp=P1;
                  switch (temp)
                       {
                                    case 0xeb:num=9;
                                          break;
                                    case 0xdb:num=10;
                                          break;
                                    case 0xbb:num=11;
                                          break;
                                    case 0x7b:num=12;
                                          break;

                      }
                           while(temp!=0xf0)
                                   {
                                          temp=P1;
                                          temp=temp&0xf0;
                                   }
        }
       }
       P1=0xf7;
   temp=P1;
       temp=temp&0xf0;
       while(temp!=0xf0)
       {     delay(5);
            temp=P1;
               temp=temp&0xf0;
             while(temp!=0xf0)
              {      
                  temp=P1;
                  switch (temp)
                       {
                                    case 0xe7:num=13;
                                          break;
                                    case 0xd7:num=14;
                                          break;
                                    case 0xb7:num=15;
                                          break;
                                    case 0x77:num=16;
                                          break;

                      }
                           while(temp!=0xf0)
                                   {
                                          temp=P1;
                                          temp=temp&0xf0;
                                   }
        }
       }
return num;
}
void delay(uint z)
{
       uintx,y;
       for(x=z;x>0;x--)
       for(y=110;y>0;y--);
}
void display(uchar aa)
{
        P2=table[aa-1];
}


7、一些程序截图


Center

Center

Center

Center 计数变量用static声明。


Center pwm调节程序片段。pwm_on为导通时间,此期间dc2要为1,其他为0.


位的输出方式

Center

Center

Center


自己写的BCD码转换。

Center 若用液晶显示则不处理较方便。


键盘另外一种显示方法

Center


显示多位整数

Center

另一种方法显示多位整数

Center


此法是转换后的先放在数组中。


用define定义的符号常量如果放在c源文件中,不可以用include引用,只有放在.h文件中才可以。如果在.h文件中定义的是变量,则每次引用,会产生重复定义。


</ms;i++)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|智凡单片机论坛

GMT+8, 2019-9-20 05:15 , Processed in 0.069494 second(s), 24 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表