项目地址https://github.com/ilanschnell/bitarray
其中有一个大小端的转换
简单的讲就是1字节8比特,大小端相互转换时就是将每个字节的8位比特,也就是8位的二进制,“翻转”操作得到转换结果。

from bitarray import bitarray
>>> bit_array = bitarray(endian="big")
>>> bit_array
bitarray()
>>> bit_array.frombytes(b"fdsp")
>>> bit_array
bitarray('01100110011001000111001101110000')
>>> bitarray(bit_array, endian="little")
bitarray('01100110001001101100111000001110')

在md5计算中,在填1补0这一步之后,所有操作都是基于小端的。

通用I/O数字输入接口设计

I/O输入接口硬件设计要点

根据系统外围电路输入的电信号形式,可以把输入信号分为以下几种形式。

  • 模拟信号和数字信号
    传感器将某个外部参数的变化转换为电信号。如果传感器输出电信号的幅度变化特征代表了外部参数的变化,例如电压的升高/下降表示温度高/低变化,那么这个传感器就是模拟传感器,它产生的是模拟信号。由于MCU是数字化的,因此模拟信号要转换成数字信号才能由MCU处理。这个转换电路称为模/数转换。A/D转换是嵌入式系统重要的外围接口电路之一,用途广泛。在系统硬件设计中,可以选取专用的A/D转换芯片作为模拟传感器与单片机之间的接口,也可以选取片内A/D转换功能的单片机,以简化硬件电路的设计。大多数型号的AVR单片机在片内都集成有A/D接口。
  • 电压信号和电流信号
  • 单次信号和连续信号

I/O输入信号软件设计要点

  • 正确使用AVR的I/O口要注意:先正确设置DDRx方向寄存器,再进行I/O口的读写操作。
  • AVR的I/O口复位后的初始状态全部为输入工作方式,内部上拉电阻无效。因此,外部引脚呈现三态高阻输入状态。
  • 用户程序需要首先对要使用的I/O口进行初始化设置,根据实际需要设定使用I/O口的工作方式(输出还是输入),当设定为输入方式时,还要考虑是否使用内部上拉电阻。
  • 在硬件电路设计时,如果能利用AVR内部I/O口的上拉电阻,则可以节省对外部的上拉电阻。
  • I/O口用于输出时,应设置DDRx = 1或DDRx,n = 1,输出值写入PORTx或PORTx,n中。
  • I/O口用于输入时,应设置DDRx = 0或DDRx,n = 0,读取外部引脚电平时,应读取PINx,n的值,而不是PORTx,n的值,此时PORTx,n = 1表示该I/O内部的上拉电阻有效,而PORTx,n=0表示不使用内部上拉电阻,外部引脚呈现三态高阻输入状态。
  • 一旦将I/O口的工作方式由输出设置成输入方式后,必须等待1个时钟周期后才能正确读到外部引脚PINx,n的值。

P81206-141542(1).jpg
ATmega16的T/C1是一个16位多功能定时/计数器,上图为该16位定时/计数器的结构框图。主要特点如下:

  • 真正的16位设计
  • 2个独立的输出比较匹配单元
  • 双缓冲输出比较寄存器
  • 1个输入捕捉单元
  • 输入捕捉噪声抑制
  • 比较匹配清0计数器(自动重装特性)
  • 可产生无输出抖动(Glitch-free)的、相位可调的脉宽调制(PWM)信号输出
  • 周期可调的PWM波形输出
  • 频率发生器
  • 外部事件计数器
  • 10位时钟预分频
  • 4个独立的中断源(TOV1、OCF1A、OCF1B和ICF1)
    图中给出了MCU可操作的寄存器及相关的标志位,其中计数器寄存器TCNT1、输出比较寄存器OCR1A、OCR1B和输入捕捉寄存器ICR1都是16位寄存器。T/C1所有的中断请求信号TOV1、OCF1A、OCF1B和ICF1都可以在定时/计数器T/C1所有的中断标志寄存器TIFR找到,而在定时器中断屏蔽寄存器TIMSK中,可以找到与它们相对应的4个相互独立的中断屏蔽控制位TOIE1、OCIE1A、OCIE1B和TICIE1。TCCR1A和TCCR1B为2个8位寄存器,是T/C1的控制寄存器。

T/C1时钟源的选择由T/C1的控制寄存器TCCR1B中的3个标志位CS1[2:0]确定,共有8种选择。其中包括无时钟源(停止计数),外部引脚T1的上升沿或下降沿,以及内部系统时钟经过一个10位预定比例分频器分频的5种频率的时钟信号(1/1、1/8、1/64、1/256、1/1024)。
T/C1的基本工作原理和功能与8位定时/计数器相同,常规的使用方法也是类同的。但与8位的T/C0、T/C2相比,T/C1不仅位数增加到16位,而且功能也更加强大。

16位T/C1增强功能介绍

16计数器

由于T/C1是16计数器,因此它的计数器宽度、计时长度大大增加。配合一个独立的10位预定比例分频器,在系统时钟为4MHz条件下,16位T/C1最高计时精度为0.25μs,而最长的时宽可达到16.777216s(精度为256μs),这是其他8位单片机所做不到的。

16位寄存器的读/写操作步骤

由于AVR内部数据总线为8位,因此读/写16位寄存器需要分两次操作。为了能够同步读/写16位寄存器,每一个16位寄存器分别配有一个8位临时辅助寄存器,读/写操作应遵循以下步骤:

  1. 16位寄存器的读操作:当MCU读取16位寄存器的低字节(低8位)时,16位寄存器低字节内容被送到MCU,而高位字节(高8位)内容在读低字节操作的同时被置于临时辅助寄存器(TEMP)中。当MCU读取高字节时,读到的是TEMP寄存器中的内容。因此,要同步读取16位寄存器,应先读取寄存器的低位字节,再立即读取其高位字节。
  2. 16位寄存器的写入操作:当MCU写入数据到16位寄存器的高位字节时,数据是写入到TEMP寄存器中。当MCU写入数据到16位寄存器的低位字节时,写入的8位数据与TEMP寄存器中的8位数据组合成一个16位数据,同步写入到16位寄存器中。因此,要同步写16位寄存器,应先写入该寄存器的高位字节,再立即写入它的低位字节。

用户编写汇编程序时,如果要对16位寄存器进行读/写操作,则应遵循以上特定的步骤。此外,再对16位寄存器操作时,最好将中断响应屏蔽,防止在主程序读/写16位寄存器的两条指令之间插入一个含有对该寄存器操作的中断服务。如果这种情况发生,那么中断返回后,寄存器中的内容已经发生改变,会造成主程序中对16位寄存器的读写失/误。

更加强大和完善的PWM功能

输入捕捉功能

定时/计数器基本工作特点如下:

  • 对一个序列的脉冲信号进行计数,而且计数过程由硬件自己完成,不需要软件干预。
  • 一旦计数值到达某个值,通常是MAX(0xFF)、BOTTOM(0x00)或TOP时,可以产生中断申请,通知MCU进行处理。
  • 仔细确定使用哪个定时/计数器。ATmega16一共配置了2个8位和1个16位共3个定时/计数器,不仅长度不同而且功能也不同。要选择合适的定时/计数器使用。
  • 脉冲信号源。脉冲信号源是指输入到定时.计数器的计数脉冲信号。用于定时/计数器计数的脉冲信号可以由外部输入引脚提供,也可以由单片机内部提供。当使用内部计数脉冲信号时,应选择合适的分频比例与计数值的配合。
  • 计数器的工作模式和触发方式的选择。
  • 中断服务程序的正确设计。定时/计数器的使用通常是与中断程序相结合在一起使用的,因此要非常清楚中断产生的条件,以及在中断服务程序中正确的进行中断处理及其相关的设置。

外部事件计数器

T/C0作为外部事件计数器使用时,是指其计数脉冲信号来自外部引脚的T0(PB0)。
通常,对外部输入脉冲信号的基本处理有以下两种:

  • 对外部脉冲信号进行计数,即记录脉冲的个数,一旦记录的脉冲个数达到一个设定值时,就进行必要的处理。
  • 对外部脉冲信号的频率(周期)进行测定。

2N分频系统设计(一)

1)硬件电路
2N分频系统要实现的功能是对T0引脚输入的方波信号进行偶数次的分频,以获得频率低于T0输入的方波信号。
本设计的硬件电路非常简单,将实验板上的250Hz的方波信号输出与ATmega16的T0引脚连接,作为T/C0计数器的外部输入。另外将PA0作为分频后的脉冲输出引脚,用PA0控制一个LED的显示,通过LED的亮暗变化可以简单地观察方波的频率,当然最好的方法是用示波器观察PA0的输出。
2)软件设计
首先考虑使用T/C0的普通模式(WGM0[0:1]=0),采用T0上升沿触发(CS0[2:0]=111),并设置TCNT0的初值为0xFF。当T0引脚输入电平出现一个上跳变时,T/C0的TCNT0回到0x00,并产生溢出中断,在溢出中断服务程序重新设置TCNT0位0xFF,并改变PA0口的输出电平(取反输出)。当T0引脚输入电平出现第二个上跳变时,又会如此,这样PA0上就得到T0的2分频输出信号。同理,如果将TCNT0的初值设定为0xFE,则在PA0上得到T0的4分频输出信号,0xFD→6分频,0xFC→8分频...而当TCNT0的初值设置为0x00时,可实现最大512分频的输出。
下面给出在PA0上输出1Hz方波(LED亮0.5s,暗0.5s)的设计和程序。由于T0输入的频率为250Hz,所以分频洗系数为250,因此TCNT0的初值=255-124(0x83),即T/C0计数125次时PA0的电平改变一次。
P81204-133109(1).jpg
利用CVAVR的程序生成向导生成一个程序框架后,再加入自己的程序并进行必要的修改。程序代码如下:

/******-
File name            :demo_8_1.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>

//Timer 0溢出中断服务
interrrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
    TCNT0 = 0x83;
    PORTA.0 = ~PORTA.0;
}

void main(void)
{
    PORTA = 0x01;
    DDRA = 0x01;        //设置PA0输出方式
    
    PORTB = 0x01;
    DDRB = 0x00;        //设置PB0(T0)为输入方式
    
    //T/C0初始化
    TCCR0 = 0x07;        //T/C0工作于普通模式,T0上升沿触发
    TCNT0 = 0x83;
    OCR0 = 0x00;
    TIMSK = 0x01;        //允许T0溢出中断
    #asm("sei");        //使能全局中断
    while(1)
    {
        //palce your code here
    };
}

思考与实践:

  • 为什么在中断服务程序中要重新设置TCNT0的初值?
  • 如何计算TCNT0的初值,使得PA0输出0.5Hz的方波?

2N分频系统设计(二)

1)硬件电路
同2N分频系统设计(一)
2)软件设计
2N分频系统设计(一)中使用了T/C0的普通模式,因此在中断服务程序中必须重新对TCNT0进行初始化。其实更方便的方法是使用T/C0的CTC模式,利用T/C0的自动重装特性。当T/C0工作在CTC模式时,计数器TCNT0的值与OCR0的值比较,一旦相等,就在下一次计数脉冲序列到来时清0 TCNT0,并产生T/C0的比较匹配中断。此时在比较匹配中断服务程序中改变PA0的输出即可。代码如下:

/******-
File name            :demo_8_2.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>

//Timer 0溢出中断服务
interrrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    PORTA.0 = ~PORTA.0;        //PA0取反输出
}

void main(void)
{
    PORTA = 0x01;
    DDRA = 0x01;
    
    PORTB = 0x01;
    DDRB = 0x00;
    
    //T/C0初始化
    TCCR0 = 0x0F;        //T/C0工作于CTC模式,T0上升沿触发
    TCNT0 = 0x00;
    OCR0 = 0x7C;        //设置OCR0的比较值为124(0x7C)
    TIMSK = 0x02;        //允许T0比较匹配中断
    #asm("sei");        //使能全局中断
    while(1)
    {
        //palce your code here
    };
}

思考与实践:

  • 比较demo_8_1.c和demo_8_2.c中T/C0两种方式的特点。
  • 如何利用T/C0实现N分频?

N分频系统设计

1)硬件电路
同2N分频系统设计,但在PA0上得到N分频的输出。
2)软件设计
实际上,利用T/C0的比较匹配的特点,可以实现N分频的系统。代码如下:

/******-
File name            :demo_8_3.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>

//Timer 0溢出中断服务
interrrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
    TCNT0 = 0xFB;            //重新设置TCNT0的初值
    PORTA = ~PORTA;            //PA0取反输出
}

//Timer 0比较匹配中断服务
interrrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    PORTA = ~PORTA;            //PA0取反输出
}

void main(void)
{
    PORTA = 0x00;
    DDRA = 0x01;
    
    PORTB = 0x01;
    DDRB = 0x00;
    
    //T/C0初始化
    TCCR0 = 0x07;            //T/C0工作于普通模式,T0上升沿触发
    TCNT0 = 0xFB;
    OCR0 = 0xFD;            //设置OCR0的比较值,大于TCNT0的初始值,小于0xFF
    TIMSK = 0x03;            //允许T/C0溢出和比较匹配中断
    #asm("sei");            //使能全局中断
    while(1)
    {
        //palce your code here
    };
}

定时器应用设计

采用T/C0硬件定时器的时钟系统。

1)硬件电路
在之前例子(6.5)中,采用调用CVAVR中软件延时函数的方法给出了一个使用6个数码管组成的时钟系统。采用软件延时,时钟是不准确的,因为一旦系统中使用了中断,就可能打断延时程序的执行,使延时时间发生变化。另外,使用软件延时的方法,也降低了MCU的效率。
而7.2中,系统时钟的基准信号来自外部的标准方波信号源,这样尽管定时时间比采用软件延时方式要准确得多,但由于采用外部标准方波信号源而增加了系统的成本。
实际上,更加方便和简单的方式是采用系统本身的时钟信号,并配合T/C0产生时钟系统的定时信号。下面给出采用T/C0硬件定时器实现的时钟系统设计。时钟系统的硬件与图6-15相同。
2)软件设计
C语言代码如下:

/******-
File name            :demo_8_4.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>
flash unsigned char led_7[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7E,0x6F};//段选码,对应0~9
flash unsigned char position[6]={0x6e,0xfd,0xfb,0xf7,0xef,0xdf};//位选码,确定哪一个LED导通
unsigned char time[3];//时分秒计数单元
unsigned char dis_buff[6];//缓冲区,保存要显示的6个字符的段码值
int time_counter;//中断次数计数单元
unsigned char posit;
bit point_on, time_1s_ok;

void display(void)//LED灯动态扫描函数
{
    PORTC = 0xff;
    PORTA = led_7[dis_buff[posit]];
    if (point_on && (posit == 2 || posit == 4)) PORTA |= 0x80;
    PORTC = position[posit];
    if (++posit >= 6) posit = 0;
}

//Timer 0比较匹配中断服务
interrrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    display();                        //调用LED扫描显示
    if (++time_counter >= 500)
    {
        time_counter = 0;
        time_1s_ok = 1;
    }
}

void time_to_disbuffer(void)        //时钟时间送显示缓冲区
{
    unsigned char i,j = 0;
    for (i=0;i<=2;i++)
    {
        dis_buff[j++] = time[i] % 10;
        dis_buff[j++] = time[i] / 10;
    }
}

void main(void)
{
    PORTA = 0x00;//显示控制I/O口初始化
    DDRA = 0xFF;
    PORTC = 0x3F;
    DDRC = 0x3F;
    //T/C0初始化
    TCCR0 = 0x0B;            //内部时钟,64分频(4MHz/64 = 62.5kHz) CTC模式
    
    TCNT0 = 0x00;
    OCR0 = 0x7C;            //OCR0 = 0x7C(124),(124+1)/62.5kHz=2ms
    TIMSK = 0x02;            //允许T/C0比较匹配中断
    
    time[2] = 23;time[1]=58;time[0]=55;//设定初始的时间
    posit = 0;
    time_to_disbuffer();//时间送缓冲区
    #asm("sei")            //使能全局中断
    while(1)
    {
        if (time_1s_ok)            //1s到
        {
            time_1s_ok = 0;
            point_on = ~point_on;//亮一下点,表明时间过了一秒
            if (++time[0] >= 60)//下面是对时间增加1s后,时分秒的处理
            {
                time[0] = 0;//过了60s则秒数又从0开始
                if (++time[1] >= 60)//分钟先自增1,判断有没有到60分钟
                {
                    time[1] = 0;//分钟数到了60则重置为0
                    if (++time[2] >= 24) time[2] = 0;//类似上方,略...
                }
            }
            time_to_disbuffer();//新调整好的时间送显示缓冲区
        }
    };
}

该程序中的LED动态扫描,时间调整与例7.2相同,所不同的是使用了T/C0硬件定时。T/C0工作在CTC模式,采用系统时钟经过64分频的信号作为计数器的计数脉冲。4MHz系统时钟经过64分频后62.5KHz,周期为16μs。T/C0的比较寄存器OCR0的值为124(0x7C),因此T/C0每计数125次产生一次比较匹配中断,中断的间隔时间为16*125=2ms。
在T/C0的比较匹配中断服务中,中断服务的内容同例7.2,首先进行LED的扫描,即每位LED的扫描间隔(点亮时间)为2ms,然后中断次数计数器time_counter加1.当time_counter加到500时,置位秒标志time_1s_ok,表示1s时间到。
主程序中循环检测秒标志time_1s_ok,当秒标志置位时,进行时间的调整,然后将新的时间值送到显示缓冲区中。
思考与实践:

  • 将该程序与6.5和7.2进行分析比较,比如它们各自的优点、缺点及MCU的利用率等。