笔记-嵌入式实时系统

本文是嵌入式实时系统的笔记整理,涵盖伪代码、通信模块、定时器模块等内容。详细介绍了IIC、UART通信协议,还阐述了防抖、迟滞等概念。此外,包含历年卷的定义题及答案,如内存映射与端口映射区别、同步与异步通信区别等。

嵌入式实时系统笔记整理

作者: Aiden, Chase


如果内容有误又或是未讲述清楚,可以在评论区提出,笔者会对文章进行完善;
历年卷以及课件的Github下载链接已置于文末,需要的可以进行下载;


文章目录

I. 伪代码

1. PIC 比较器(220)

Main
    setup
    ...
    loop forever
    	pollComparator()
    	...
    	delay SUPERLOOP TICK
setup()
    // details depend on use of hardware of software
    
//Polling a single comparator is like polling a digital port, e.g
pollComparator()
    // details depend on use of hardware or software hysteresis
    // and whether we need to react on change, react coninuously
    //whie above/below threshold etc.
    

2. 迟滞(Hysteresis)

Software hysteresis
pollComparator()
    static prevComparatorOut = LOW// or HIGH depending on needs
    Boolean comparatorChanged = FALSE
    
    // only change voltage reference if the comparator output changes
    if comparatorOut differs from preComparatorOut
        set comparatorChanged = TRUE
        prevComparatorOutput = comparatorOutput
    
    // change the threshold according to the just changed
    // comparator output high or low
    if comparatorOut is HIGH
        set voltage reference level to COMPARATION_THRESHOLD_HIGH
    else //comparatorOut is LOW
        set voltage reference level to COMPARATOR_THRESHOLD_LOW
hardware hysteresis
setup()
	configure port tristate
	configure comparator mode and external voltage reference
	choose initial comparator and multiplexed input (if needed)
	configure comparator output to appear on output pin (for voltage ref)
    
// No need to change comparator mode and reference
// And polling the comparator is like polling a digital port
pollComparator() // Version 1: act every loop when signal > threshold
    
// Version 2: act only when signal crosses threshold from low to high
pollComparator()

3. 位触发(Bit banging)

Main...

loop()
    tansimitByte('.')
    delay(ONE_SECOND)
    
setup()
    configure a pin of I/O port for output and call this pin Tx
    set Tx = MARK// initially at the idle or stop level
    
// blocking calls: transmit complete Byte before returning
transimitByteCompletely(val)
    // start bit, 8 data bits,no parity,stop bit
set Tx = SPACE
delay(SYMBOL_PERIOD)
for i = 0 to 7
    if bit i of val is 1, set Tx = MARK,else set TX = SPACE
    delay(SYMBOL_PERIOD - LOOP_OVERHEAD)
Tx = MARK // stop bit
delay(SYMBOL_PERIOD)
    
// to extract the bit value at bitIndex in the value x
bitvalue”(x RSHIPT by bitIndex) Bitwi BeAND 0b00000001

// the equivalent C code fragment 1s
bitValue = (x >> bitIndex) & 0x1;

// Therefore the C code to examine each bit in a value, x,
// and take some action based on the bit values i...
for (bitIndex = 0; bitIndex < 8; bitIndex++){
	// what is the value of bit numbered bitIndex in x
	bitValue = (x>>bitIndex)&0x1;
	if (bitValue) // 12 bitValue is a 1,do something
		...
}

4. 防抖

// Simple edge trigger algorithm (there are many possible variations)
// Verify signal is stable using a counter
// DEBOUNCE_TICKS should be chosen to give enough time for bounces
// to disappear (often 10-20ms but depends on button mechanics)
// checkSwitch would be called once per superloop
global gDebouncedSwitchState = OPEN // default value at start of app
// get debounced state of switch and indicate whether it has changed
checkSwitch()
	static debounceCounter = DEBOUNCE_TICKS // initial state
	debouncedSwitchChanged = FALSE // default unless change verified
	rawSwitchState = read switch input pin from IO port
        
	if rawSwitchState == gDebouncedSwitchState // switch state stable?
		debounceCounter = DEBOUNCE_TICKS // prepare to debounce next change
	else // switch state changed, so wait for it to become stable
		decrement debounceCounter
		if debounceCounter is 0 // debouncing all done?
			gDebouncedSwitchState = rawSwitch // accept the change
			debounceCounter = DEBOUNCE_TICKS // prepare to debounce next change
			debounceSwitchChanged = TRUE
        
return debounceSwitchChanged

5. UART

wait before transmit(1 Byte)
Main..

setup()
	configure baud rate, serial port, transmission
	
loop()
	transmitByte(random(256))
	
transimitByte(val)
	wait until TXIF is set // until TXREG is available
	set TXREG = val
	// return before val in TXREG has necessarily been transmitted
transmit completely(1 byte)
Main...

setup()
	configure baud ratem serial port, transmission
	
loop()
	transmitByteCompletely(random(256))
    
transmitByteCompletely(val)
	set TXREG = val
	wait until TRMT is clear // fully transmitted
transmit a string(busy wait polling)
Main...

setup()
	...
loop()
	transmitString("hello")
	otherwork()
	
transmitString(str)
	while not at end of str
		wait until TXIF is set
		set TXREG = next character in str
transmit a string, multitask polling
global gTxBuffer

Main...

setup()...

loop()
	tryTransmitString("hello")
	pollTx()
	otherWork()
	
tryTransmitString(str)
	if gTxBuffer empty/sent
		startTx(str)
		return TRUE
	else
		return FALSE

startTX(str)
	copy str to gTxBuffer
	
pollTx()
	if not at end of gTxBuffer
		if TXIF is set
			set TXREG = next char from gTxBuffer

6. ADC

// General app structure
loop:
	pollADC()
	…
	delay SUPERLOOP_TICK // usually equals 	ADC_SAMPLE_PERIOD

setup:
	configure analogue and vref inputs
	select ADC clock, ADC format
	select ADC channel and turn on ADC
	// if using multiple channels, select channel in the poll function
	// instead

// polling the ADC for a single sample
pollADC:
	select ADC channel if necessary
	delay for acquisition time if necessary
	start ADC conversion and busy-wait poll until done
	read ADC value (8 bit (1 register) or 10 bit (2 registers))// process value further if desired (e.g. peak detect)
	// act on processing outcome as necessary
typedef unsigned int Uint16;
typedef unsigned char Uint8;
#define ADC_CHANNEL_MASK = 0b00111000
// NOTE: below are code fragments that you can use as needed –
// these are NOT all part of one function!
------------------------------------------------------------------
// select ADC channel to be sampled next
	ADCON0 = (ADCON0 & ~ADC_CHANNEL_MASK) | (channel << 3);
------------------------------------------------------------------
// sample the ADC and place the 10 bit result in a 16 bit variable
Uint16 adcValue;
ADGO = 1; // initiate I/O operation: start ADC conversion
while (ADGO) continue; // “wait until done” polling
adcValue = (ADRESH << 8) + ADRESL; // store the 10 bit sample
------------------------------------------------------------------
// sample the ADC and place 8 bit result in an 8 bit variable
// NOTE: must configure ADC to use left justified results
	Uint8 adcValue;
	ADGO = 1; // initiate I/O operation: start ADC conversion
	while (ADGO) continue; // “wait until done” polling
		adcValue = ADRESH; // store the 8 bit sample

II. 通信模块

1.IIC(同步通信)

IIC总共只有两条线,分别是串行数据线(SDA)和串行时钟线(SCL)

半双工特性:由于只有一条数据线,所以控制器和外设不能同时传输数据。

上拉电阻至高电平:SDC和SCL都使用上拉电阻,默认情况下都“浮空高电平”,即没有信号驱动的时候,通过上拉电阻被拉**至高电平。**在II2C通信过程中,控制器通常控制时钟线(SCL),但外设也可以将时钟线拉低来暂停数据传输,这种情况被称为“时钟延展(clock stretching)。

通信读写:所有的通信都被分为读或者写,读(控制器从外设请求数据),写(控制器向外设发送数据)

确认应答:接收设备必须确认收到所有数据,这可以通过发送一个确认应答(ACK)或非应答信号(NAK)来完成。

使用设备地址:控制器在任何读或者写的部分都会传输一个IIC的地址,用于选定要通信的设备,因为在I方C总线上可能连接多个设备。

I2C协议允许使用**7位(128个设备)或者10位(1024个设备)**的寻址方案来选择设备。
在这里插入图片描述

数据的变化:首先,SDA****线一次只传递一个比特位,所以发送方的二进制数据被串行按照时序发送。

在这里插入图片描述

假设ADC是发送方,微控器是接收方。当微控器已经接收到某次ADC发送的数据的时候会给其一个ACK应答,且将SCL置低来允许对方修改SDA。当ADC收到ACK的时候则得知数据已经被接收则可以发送下一位数据。

/*
Q. How does the raw bit rate compare to the SCL frequency? E.g. If SCL frequency is 100KHz, what is 
the raw bit rate (based on when SDA levels can change)?
*/
Sol:原始位速率与时钟频率为1:1,即每个SCL时钟对应着一个比特位的传输。如果SCL频率为100kHz则代表每秒钟
时钟切换10^5次,所以原始位速率为100kbps(每秒钟传输100k个比特位)

更深入地:

我们约定,微控器为主设备,与微控器进行I方C通信的设备称为从设备。

微控器在整个通信过程中占主导地位,也就是说通信的开始与结束都是微控器来决定的,从设备(slave)不能主动发起通信。

过程如下:

前提:主设备已经完成了与某个设备通信,从新的通信开始。

  1. 主设备发送起始条件信号(起始信号是广播给所有设备,且没有特别的意义,仅仅是标记一次新的开始)

轮询 {

  1. 主设备发送从设备地址以及读(写)标志位【1表示读,0表示写】给从设备。
  2. 如果这个从设备想要发送消息则对主设备的请求进行确认应答(ACK/NACK),如果没有数据要发送则保持静默。
  3. 此后二者握手成功,直到数据传输完毕。

}

  1. 注设备发送结束条件信号

起始条件与结束条件

回忆,初始状态的时候SDA和SCL都被上拉至高电平,SDA跳变到低电平代表一次新的开始(起始条件)

结束状态时,主设备在数据传输完毕后多等待一个时钟周期,然后SCL被上拉至高电平,由于NACK的作用SDA在SCL置高后才会从低被拉高,所以SDA总是置后于SCL变高(结束条件)

ACK**/NACK**

接收方在收到每个字节数据之后都要发送ACK或NACK来确认应答。ACK表示数据成功接收,将SDA下拉至逻辑0;当接收方不愿意接收更多数据或者出现错误会发送NACK,将SDA上拉至逻辑1。

测试

Q1:Explain how the ACK and NACK signals are used in the I2C protocol?

ACK:接收方收到发送方的数据的时候会将SDA下拉至低电平表示确认应答ACK

NACK:用来表示从设备出现错误,或者当主设备不再期望有更多数据来自从设备的时候则会发送NACK和结束条件

Q2:Explain the I2C protocol?

IIC协议规定了一个完整的通信流程包括主设备先发出起始条件,然后发出从设备地址和读写标志位,如果从设备要通信则进行应答,确认应答之后双方就可以进行通信,当通信要结束的时候主设备发送结束条件来代表本轮通信结束。

Q3:With the aid of sketches, show the Start and Stop condition in the I2C synchronous

protocol?

草图回顾前文。

Q4: Show how two microcontrollers could be connected using I2C?

在这里插入图片描述

Q5:Discuss the main differences between I2C and SPI

  • 在通信总线方面:I方C使用两个线,分别为SCL(时钟线)和SDA(数据线);而SPI最少使用三根线,输出数据线(SDO),输出数据线(SDI),时钟线(SCK),此外对应每个设备还有单独的片选线(SS)。
  • 在选址方面:I方C支持多个主设备和多个从设备,使用7位或者10位的定址方式来确定不同设备的地址;而SPI通常由单一主设备控制,且没有原生地址概念,每个从设备与主设备之间都有独立的片选线。
  • 在数据传输速度方面:SPI >> I方C
  • 在通信方面:I方C是半双工,而SPI是全双工,二者都通过自己独特的方式实现数据传输的可靠性。

测试

Q1:A Controller reads the 2 byte value 0x0142 from an I2C peripheral (Peripheral). Draw

and carefully label the complete timing diagram explaining any assumptions made.

如下图所示。

Q2:Assume the serial clock is 100 kHz. How fast (bps) can data be received from

Peripheral…

\1) If up to 32 data bytes can be read per operation?

Sol:在I方C通信中,每个数据字节的传输通常涉及9个时钟周期,8个时钟周期用于发送数据,1个时钟周期用来发ACK /NACK。

所以每字节需要9个时钟周期,100kHz代表每秒钟有10^5个时钟周期,有:
b p s = 每字节位数 ∗ 每次操作字节数 ∗ 频率 每字节位数 + A C K / N A C K 应答位数 bps = \frac{每字节位数 * 每次操作字节数 * 频率}{每字节位数 + ACK/NACK应答位数} bps=每字节位数+ACK/NACK应答位数每字节位数每次操作字节数频率
所以bps = (8 * 10^5) / 9

如果每次可操作32字节数则bps = (8 * 32 * 10^5) / 9

\2) As above, except that there is an additional 100 microsec delay before a subsequent read can be

started

Sol:

仅需在如下图修改:DATA处有两个字节,第一个字节(0x01)结束用的ACK,第二个字节(0x42)结束用的NACK

在这里插入图片描述

2. UART(异步通信)

TX /RX

全双工特性:既有Tx也有Rx,代表一个UART器件能同时进行接收数据和发送数据

在这里插入图片描述

时序关系如上图所示:UART1用作数据发送,UART2用所数据接收,起始阶段RTS与CTS都处于低电平,当RTS置高的时候代表UART1要发送数据,当UART2读取到这个RTS的时候就知道UART1准备好发送数据了,如果此刻自己具有接收数据的能力,则回复UART1(将CTS置高)。也就是说二者同时为高的时刻就是数据传输的起始时刻。

UART模块的组成与特性

一个起始位:表示数据包的开始

  • 8(或者9)位数据数据集(可通过配置寄存器来实现)
  • 一个结束位:表示数据包的结束
  • 软件奇偶校验:如果使用了9位数据集,则第9位可用作奇偶校验位,需要人为手动计算
  • 不同波特率:通过分频系统振荡器频率(fosc)来实现
  • 允许通过轮询或中断来同步输入输出操作

Tx****工作流程

在这里插入图片描述

  1. 首先TXREG是离程序员最近的寄存器之一,因为是程序写入数据的地方

{

  1. TXIE在程序初始化的时候被设置,该标志位表示允许使用中断
  2. TXIF标志TXREG是否处于空闲状态,如果没有数据则被设置;如果有数据则触发中断让CPU进行将数据写入TXREG的操作

}

  1. 然后根据URTA自己的逻辑,选择合适的时候将TXREG中的数据移到TSR

{

  1. TXEN被设置说明使能传输,这个时候对SPBRG进行配置则是用来确定发送每位数据的速率的时钟信号(波特率)。当且仅当BPG Output**(Shift Clock)处于上升沿的时候,数据从****TSR**发送到引脚。
  2. TX9是第九位比特位的使能位,TX9D被设置则代表使用第九位作为奇偶校验
  3. TSR为空的时候TRMT被设置来监控TSR是否处于空闲状态

}

  1. SPEN:串行化使能位,如果被设置则代表数据将串行化输出给引脚,否则数据不被发送。

时序线中TXIF位的变化理解:开始时TXREG没有数据,TXIF拉高,然后word1写入TXREG后,短时间内TXIF置低,然后在合适的时机UART将数据移动到TSR中,此时TXREG被清空,TXIF再次被拉高。当word2到来的时候TXIF持续为低,直到TSR中数据被处理完,然后将TXREG中的word2移动到TSR中,TXREG再次为空,TXIF拉高。


Rx 工作流程

在这里插入图片描述

  1. 首先需要将串行数据要被并行化后写入RSR中。
  2. Fosc表示系统振荡器的频率,是计算波特率的基础,SPBRG为波特率生成器,其使用Fosc和设定的值来确定UART的波特率。x64和x16是倍率选择,决定了波特率时钟是Fosc的64倍还是16倍。

SPEN是串行化使能位,设置即能读取串行化数据或者发送串行化数据。

串行化数据到来的时候,Data Revovery与波特率共同将串行化数据还原为并行数据。

  1. RCREG是两倍数据字(一个数据字8个比特位)的大小,遵循先进先出原则(FIFO)。且当RCREG存满的时候如果RSR中还有一个数据字,则OERR被设置,代表溢出错误
  2. CREN:表示连续接收使能位,如果开启UART就会自动监听引脚上的信号来将数据填入RSR/RCREG,这个过程是自动的,无需CPU介入(这里的介入指的是无需CPU来处理数据,在连续接收模式中CPU主要任务就是响应中断和处理错误);在非连续接收模式下,UART硬件不会自动接收数据,这时候就需要CPU的介入(通过轮询或者设置单次接收操作来控制数据接收)。

值得注意的是:启用**CREN的时候就不会因为溢出而报OERR,所以通常情况下我们是需要设置这个位的,以减少上层代码额外处理数据的逻辑和降低CPU的资源消耗。**

  1. Rx中的RX9D与Tx一样,都是用于表征奇偶校验是否启用。
  2. FREE:如果在接收数据的停止位错误地被检测为“空格”(space)而不是“标记”(mark),则会设置这个帧错误标志表示数据不完整或损坏。

在这里插入图片描述

III. 定时器模块

1. Timer

定时器可以配合中断使用:当定时器计数到达特定值的时候,它可以生成特殊信号,并且可以触发中断(如果有)。

软件使用:在代码层面,可以使用定时器生成的信号来进行定时任务的执行。

预分频器:预分频器位于源时钟信号和定时器之间,预分频器的值为N代表每N个时钟周期后才进行一次计数。

特别地,当预分频因子为1(即每个时钟源信号周期产生一个tick),这种情况下和没有使用预分频器的效果是一样的。预分频器的主要用途就是用来扩展时间范围,因为8位或者16位计数器终归是有上限的,使用预分频器则能够拓展时间范围。

后分频器:后分频器位于定时器重置和定时器事件信号之间。后分频器的值为M则代表只有在定时器重置M次后才会发出定时器信号。后分频器功能与预分频器类似。

在这里插入图片描述

超时结果:超时的时候可能会生成一个超时信号,也可能产生一个中断。如果是中断,则CPU立即打断当前任务去执行超时任务;如果是超时信号,则CPU不会打断当前任务,而是正常调度,未来用这个超时信号来完成某些任务。

代码编写方面:首先要初始化寄存器的值以及使用的预分频和后分频因子;然后一般采用中断(代替轮询)作为服务例程,尤其是在处理实时任务方面。


设置定时器回绕

假设有一个B位定时器

  1. 定时器的数值范围[0, 2^B - 1],当数值到2^B的时候发生回绕变成0,此时会发生一个定时器事件。
  2. 为了能够让在我们需要的指定秒数后触发定时器事件,所以我们要改变定时器初值。
  3. 假定我们需要在N秒后触发定时任务,则将初值设置为2^B - N

例如,如果要在5个ticks后发生一个8位定时器的回绕,你需要将定时器寄存器设置为 256−5=251256−5=251。这样,在接下来的5个ticks(251, 252, 253, 254, 255, 0)之后,定时器就会回绕,并且在那时产生一个定时器事件。


如何仅使用软件层就能完成定时任务的周期设置?

思路:

  1. 首先需要计算出调用中断到重置计时器所需要花费的时间Nreset
  2. 假定需要在N秒后执行任务,由于中断需要花费Nreset,所以定时器的初值要设置为N - Nreset

举例来说,如果想每秒执行一次任务,并且ISR需要10毫秒来完成,那么需要将定时器设置为990毫秒超时,这样加上ISR的执行时间,总周期仍然是1秒。

当硬件有提供比较和重置的功能的时候:

  1. 将定时器寄存器设置为0
  2. 将周期定时比较寄存器设置为N - 1 ticks,并配置定时器以周期性地重置
  3. 定时器在每个tick上正常递增,并与周期值进行比较,当等于N - 1的时候,定时器将在下一个tick自动重置为0并生成中断。

总结一下:

要想在N秒后执行超时任务,有两种策略 {

  1. 使用定时器回绕:设置定时器寄存器初值为2^B - N即可
  2. 使用周期匹配:如果定时器有周期寄存器并且可以设置周期性重置,那么我们将定时器寄存器初值设置为0,然后设置周期寄存器(PR)为N - 1

}

我们定义:

p r e s c a l e r = 定时器增量 源时钟周期 prescaler = \frac{定时器增量}{源时钟周期} prescaler=源时钟周期定时器增量

例如1:4或者4x代表:一个定时器增量需要4个源时钟周期,则有: T I N C = 4 ∗ T S R C T_{INC} = 4 * T_{SRC} TINC=4TSRC

则N个增量所需的超时时间为: T E X P = T I N C ∗ N T_{EXP} = T_{INC} * N TEXP=TINCN

p o s t s c a l e r = 定时器中断 定时器回绕或周期匹配 postscaler = \frac{定时器中断}{定时器回绕或周期匹配} postscaler=定时器回绕或周期匹配定时器中断

例如1:2或者2x代表:一次中断触发需要定时器回绕/周期匹配2次,则有: T I N T R = 2 ∗ T I N C T_{INTR} = 2 * T_{INC} TINTR=2TINC

​ N次 T I N T R = T E X P ∗ 2 T_{INTR} = T_{EXP} * 2 TINTR=TEXP2

判断所需要的超时时间是否需要分频:

首先计算所需的比例 desiredScale = T I N T R T S R C ∗ 2 B \frac{T_{INTR}}{T_{SRC} * 2 ^ B} TSRC2BTINTR

如果desiredScale < 1则无需分频,反之则需要分频

//假设你正在使用一个8位的定时器,源时钟频率TSRC是1MHz,即每个时钟周期是1微秒(1μs)。
//希望定时器每10毫秒(10000μs)产生一个中断
8位寄存器,则范围[0, 255],所以最大计数时间位255μs, 而10000μs > 255μs则需要分频
desiredScale = Tintr / Tsrc * 2^B = 39.22
所以需要一个大于39的预分频因子,最接近的预分频因子可能是64(不同设备不同规格)
所以使prescale = 64x,即一个定时器增量需要64个时钟源
所以10000 / 64 = 156.25 ~ 156
所以让定时器256 - 156 = 100开始计数

计算超时时间百分比误差:

回忆一个定时增量: T I N C = p r e s c a l e r ∗ T S R C T_{INC} = prescaler * T_{SRC} TINC=prescalerTSRC, N个定时增量: T I N C = p r e s c a l e ∗ T S R C ∗ N T_{INC} = prescale * T_{SRC} * N TINC=prescaleTSRCN

实际执行N个中断所需时间: T I N T R − c a c u l a t e d = p o s t s c a l e r ∗ T E X P T_{INTR-caculated} = postscaler * T_{EXP} TINTRcaculated=postscalerTEXP

∴ e r r o r = 100 % ∗ T I N T R − c a c u l a t e d T I N T R − d e s i r e d \therefore error = 100\% * \frac{T_{INTR-caculated}}{T_{INTR-desired}} error=100%TINTRdesiredTINTRcaculated


PIC****与Timer

PIC中有3个定时器模块

  • Timer0:是一个8位定时器,有 2 k 2^k 2k(k = 0, 1, 2, …, 8)倍的预分频,且具有回绕功能。
  • Timer1:是一个16位定时器(有两个寄存器),预分频有:2,4,6,8倍。Timer1不仅支持回绕功能,还提供了周期功能,其通过两个比较(捕获/PWM)模块又叫做CPP来定制周期。当Timer1达到周期值得时候,CPP1模块可以重置定时器并生成中断,CPP2模块可以重置定时器并自动启动A/D转换生成中断。
  • Timer2:为周期性设定的8位定时器,有1,4,16倍预分频,且有1~16倍后分频,使用PR(8位寄存器)来指定周期值。

测试

题目:Assume FOSC = 4MHz, TCYC = 4 * TOSC and the source clock is TCYC

Sol:TOSC=1/FOSC 和 TCYC = 4 * TOSC

Tosc = 0.25us, Tcyc = 4Tosc = 1us

Q1:What is the maximum duration that can be timed using 1x, 8x, and 256x prescaling?

1x:Tcyc * 2^8 * 1x = 256,所以最大为255us

8x:Tcyc * 2^8 * 8x,最大为2047us

256x:65280us

Q2:How would you time a duration of 1 ms?

1000us > 255us,所以需要分频,1000/255 = 3.9,所以4x预分频就可以满足,映射为1000/4 = 250us

Q3:How would you time a duration of 1001 microsecs?

一个4x预分频8位定时器就能解决

真题演练

题目:On the PIC 16F877 microcontroller, Timer0 is an incrementing 8-bit timer that generates an interrupt on rollover (from 0xFF to 0x00). Timer0 also incorporates a prescaler which can be configured such that the timer increments on every 1, 2, 4, 8, 16, 32, 64, 128, or 256 instruction cycles.

Consider a system using the PIC 16F877 with an oscillator frequency of 4 MHz. The system must use its internal ADC to sample analogue input at a rate of 2000 samples/second.

Q1:For this system, what is the maximum timeout period supported by Timer0 with the minimum and maximum prescaling factors applied?

Sol:with the minimum prescaler: 1x * 2^8 * 1us = 255us

with the maximum prescaler: 256x * 2^8 * 1us = 65280us

Q2: Choose an appropriate prescaling factor and Timer0 start value to achieve a timeout equal to the

required sampling period of this system.

Sol:每秒需要2000次采集,说明每1/2000 = 500us就需要进行一次采集,500 / 255 ~ 2

所以选择2倍的预分频就行。

题目演变:Same question as previous worked exam question, but assume that it takes

2 microseconds to reset the timer in the interrupt handler

如果有2us的Nreset,那么前面的流程就需要498us才能刚好凑够500us,而498us同样可以使用2x预分频解决,定时器寄存器值设置为7。

题目演变:Same question as previous worked exam question, but use Timer2 with

prescaler AND postscaler

回忆Timer2有1,4,16倍预分频和1~16倍后分频,后分频的精细度比预分频高。

255 * 2 = 510us 255 * 4 = 1020us。

我们可以选用1x预分频和2x后分频,或2x预分频和1x后分频

伪代码

//定时任务的执行
static bool gTimeExpire = false;
set_up()
loop() {
    doSomeTask();
    doTimeConsumingTask();
    doOtherTask();
    delay SuperLoop_Tick;
}
doTimeConsumingTask() {
    if(gTimeExpire)
        //TODO
}
isr() {
    if(time interrupt) {
        reset timer;
        set gTimeExpire = true;
     }
}
//定时任务的执行
static bool gTimeExpire = false;
set_up()
loop() {
    doSomeTask();
    doTimeConsumingTask();
    doOtherTask();
    delay SuperLoop_Tick;
}
doTimeConsumingTask() {  //如果任务大放在中断外面执行
    if(gTimeExpire)
        //TODO
}
isr() {
    if(time interrupt) {
        reset timer;
        set gTimeExpire = true;
     }
     do_quick_in_response_to_timer(); //如果任务小直接在中断中执行
}

2. Interrupt

流程:当一个IO设备需要服务的时候,它会在CPU的IRQ(中断请求线)或引脚上引起一个状态变化。如果是内部设别,则一般在内部的IRQ线上。这时候CPU会立即停止主程序的执行,去执行中断服务例程(ISR),执行完之后在去执行主程序的代码。

//定时器驱动中断
static bool gTimeExpired = false;
setup() {
 set TMRO = TMRO_STAET_VALUE
}

loop() {
 while gTimeExpired is false, do nothing //wait
 doWork ...
 set gTimerExpired = false;
}
isr():
 if TMOIF interrupt
     set TMOIF = 0 //清除中断标志位
     set TMR0 = TMR0_START_VALUE //重置计时器寄存器
     set gTimeExpired = true;

//UART驱动中断
static bool gCommandComplete = false;
setup():
 initinal configure device/peripheral settings
 enable Rx interrupt
loop(): 
 processSerialCommand();
 delay superloop_tick
processSerialCommal():
 if gCommandComplete is true;
     doWrok...
 set gCommandComplete is false
isr():
 if UARE Rx interrupt
 buffer[i] = value of RCREG
 if buffer[i] is End_Of_Command
     set gCommandComplete = true
 increment i

适合使用中断的场景

  1. 不频繁或者不可预测时间发生的IO
  2. 在后台发生的异步IO

优缺点

  • 优点:软件不会花额外的“时间”去轮询设备,减少了CPU的资源消耗
  • 缺点:增加代码的复杂度

中断使能

在这里插入图片描述

当在一个系统中有多个中断的时候,可以通过中断使能寄存器(也称掩码寄存器)来启用或者禁用。每个中断源(IRQ0IRO7)都可以通过相应的控制位(IRQ0EIRQ7E)在中断使能寄存器中单独控制。如果相应的中断使能寄存器值为1则允许该中断;为0,则禁止该中断。

在这里插入图片描述

图中可以看到IRQ线管理8个IO设备,可是使用中断使能寄存器来控制每个IO设备是否能进行中断。当允许多个设备中断的时候,通过不断轮询IRQ0~IRQ7(有使用的位置)来判断当前中断是否发生。以此就可以实现软件层面的中断优先级控制(哪个if在前,哪个优先)

//伪代码
isr():
    if SSP interrupt flag set 
        handle SSP Interrupt 
    else if TMR0 interrupt flag set 
        handle Timer0 Interrupt 
    else if ADC interrupt flag set 
        handle ADC Interrupt 
一般地,并不建议在中断函数中调用其他函数,而是直接将逻辑写在中断函数中,以减少调用函数消耗的额外资源

中断向量表

先前都是为了实现软件层面的中断优先级控制,为了实现硬件层面中断优先级控制,引入中断向量表。每个IO设备都被分配唯一可以确定的地址编号,称为中断向量。

简单理解就是,当硬件触发中断,CPU能遍历中断向量表来找到该地址设备然后执行中断服务例程。

//伪代码
ssp_isr(): 
    handle SSP Interrupt 
tmr0_isr 
    handle Timer0 Interrupt 
adc_isr 
    handle ADC Interrupt
//无需轮询去判断产生中断信号的是哪个设备

在这里插入图片描述

硬件中断服务的基本流程

  1. 某时刻当某个IO设备需要服务,它就会触发(assert)中断请求线(IRQ)
  2. CPU响应该设备的中断服务请求,发送中断确认信号(INTA),设备收到信号,至此双方通信可以正常通信
  3. 设备通过数据总线将自己的中断向量放置在上面
  4. 一旦中断被处理完毕,设备会重置IRQ线,以便它再次触发中断
  5. CPU从数据总线上读取中断向量,并且执行ISR

在这里插入图片描述

菊链控制中断向量优先级
在这里插入图片描述

假设设备2和设备3同时生成中断,那么都会触发IRQ线

  1. CPU响应这个中断请求,并发出中断确认信号(INTA)
  2. 此时INTA途径第一个设备,第一个设备没有待处理的中断,所以将INTA往后传递
  3. 设备2有待处理的中断,它将自己的中断向量放在数据总线,CPU读取这个中断向量然后进行中断处理,中断处理完之后,设备2的中断标志位被取消代表已经被处理过。
  4. 接着INTA继续往后传递,然后设备3响应…

这就是在硬件层面(通过菊链)如何控制不同IO设备中断的优先级。

IV.同步IO的方法总结

  • 轮询(polling):CPU定期检查I/O设备是否准备好数据交换。
  • 握手(handshaking):使用信号之间的直接通信来协调数据传输,确保发送方和接收方同步。
  • 中断(interrupt):I/O设备在需要时通过中断信号直接通知CPU,允许CPU停止当前任务来处理数据传输。
  • 直接访问内存(DMA):DMA控制器可以在不需要CPU介入的情况下,直接从I/O设备传输数据到内存。

这些方法在嵌入式系统中可以单独使用,也可以联合使用,根据不同的使用场景来区别。

轮询的特点

  • 实现简单
  • 设备优先级容易改变(改变if else顺序即可)
  • CPU需要为不断的轮询消耗额外资源,特别是当IO时间发生不是很频繁或者IO时间难以预测的时候

握手的特点

  • 通常用于与较慢的IO设备同步的硬件方法(经常与慢速存储器进行接口)
  • 对程序员来说比轮询更加简单
  • 需要CPU额外的引脚/线路,例如WAIT/READY
  • 当CPU处于等待状态的时候不能执行任何操作(与blocking polling类似)
    在这里插入图片描述

这个图就清楚反映了握手流程:

  1. 首先CPU发出地址请求(请求该地址上设备的数据)
  2. Data Bus上高电平表示设备此时正处于数据交互状态还没有准备好数据
  3. 对应此时Data Request上低电平就表示外设数据一直没有就绪,当高电平的时候就代表外设数据就绪了
  4. Wait线高电平代表持续让CPU等待,低电平代表数据就绪。

这整个流程都是由硬件完成的,即在硬件层面实现的同步,没有软件参与。图中蓝色区域就代表握手建立的过程。

中断的特点

  • CPU无需“浪费”时间去轮询,查看IO设备是否数据就绪
  • 增加了代码的复杂度

直接访问内存的特点

  • 专门设计用来内存和IO设备之间进行高速数据IO的处理器(适合数据密集的场景)
  • 可以自主完成数据传输,无需CPU参与

IV. CPU to memory

单位转换:

在这里插入图片描述

地址计算

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

处理器接口时域图

在这里插入图片描述

内存映射与端口映射

内存映射(Memory-mapped I/O)和端口映射(Port-mapped I/O)是两种不同的方法,用于将输入/输出(I/O)设备与计算机的内存系统连接起来,以实现对这些设备的控制和数据传输。

  1. 内存映射(Memory-mapped I/O):
    • 内存映射是一种将 I/O 设备的寄存器与计算机的内存地址空间关联起来的方法。
    • I/O 设备的寄存器被映射到一定的内存地址,通过读写这些内存地址来进行对设备的控制和数据传输。
    • 访问内存映射 I/O 时,使用的是和访问普通内存相同的指令,因为这些设备寄存器被看作是内存的一部分。
    • 由于使用相同的地址空间,程序员可以使用指针或直接访问内存的方式来操作这些设备。
  2. 端口映射(Port-mapped I/O):
    • 端口映射是一种将 I/O 设备的寄存器与专用的 I/O 地址空间(通常称为 I/O 端口)关联起来的方法。
    • I/O 设备的寄存器被映射到一定的 I/O 端口地址,通过特殊的 I/O 指令(通常是 INOUT 指令)来进行对设备的控制和数据传输。
    • 访问端口映射 I/O 时,使用专门的 I/O 指令而不是普通的内存读写指令。
    • 端口映射提供了对 I/O 操作的独立地址空间,避免了与内存地址的冲突。

总体而言:

  • 内存映射和端口映射是将 I/O 设备与计算机连接的两种不同的方法。
  • 内存映射使用内存地址空间,而端口映射使用专用的 I/O 地址空间。
  • 内存映射可以使用普通的内存读写指令,而端口映射需要使用专门的 I/O 指令。

V. 题目信息读取

1. Asynchronous serial specification

  • BaudRate
  • numDataBits/word
  • parity
  • num StopBits

V. 信息传输图

在这里插入图片描述

异步传输TX/RX

在这里插入图片描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对应习题

在这里插入图片描述

在这里插入图片描述

历年卷定义题:

1112

1.描述嵌入式软件开发环境的主要元素和工具。

Cross compiler/IDE/Simulation

2.防抖
(i)简要说明开关防抖所要解决的问题的性质以及产生该问题的根本原因。

Natural Reason: Switch mechanical structure of friction, spring, contact baddly, etc.

性质:开关状态的不稳定性.

(ii)借助图表描述了开关防抖的硬件解决方案。解释图中所有组件的用途。

在这里插入图片描述

R1: keep high impendence;

R2,C: low pass filter;

Schmitt trigger: Make the waveform rectangular;

(iii)画出按下按钮后的电路响应草图,适当地显示电路中关键点的中间信号,以解释其操作。
(v)解释为什么总线感知设备必须具有高阻抗状态,并指出如果没有高阻抗状态的设备连接到总线上可能会发生什么。

高阻抗状态意味着设备的输出电路对电流的流动提供了极高的电阻,因此它不会对总线上的电压信号产生明显的影响,有助于维持总线的稳定性和数据的可靠传输。

  1. 信号冲突: 如果两个或更多设备同时试图将不同的信号发送到总线上,可能会导致信号冲突,使总线上的电压信号变得混乱不清。
  2. 电流浪涌: 当一个设备处于低阻抗状态并试图将信号发送到总线上时,它可能引起电流浪涌。这可能导致总线上的电流急剧增加,对其他设备和总线结构造成不必要的负担。
  3. 数据损坏: 在没有高阻抗状态的情况下,设备可能会对总线上的信号产生额外的负载,从而降低信号质量并可能导致数据损坏。
3.Hysteresis

比较器电路中经常使用迟滞现象。简要解释这试图解决的问题以及滞后如何解决这个问题。

To prevent oscillation near reference level, basic solution is to implement hysteresis using 2 thresholds: low and high.

在这里插入图片描述

6.列出软件轮询(Software polling)、轮询中断(polled interrupt)和矢量中断(Vector interrupt)之间的关键区别。
特征软件轮询轮询中断矢量中断
触发方式主动轮询外部事件触发外部事件触发
资源占用占用CPU资源占用CPU资源仅在中断发生时占用CPU资源
实时性低实时性中等实时性高实时性
响应时间不确定,取决于轮询频率立即响应,中断优先级可调整立即响应,中断优先级可调整
效率低效率相对高效相对高效
稳定性相对不稳定相对稳定相对稳定
中断处理程序不适用,无中断处理程序中断处理程序存在中断处理程序存在
复杂性相对简单相对简单相对复杂
示例轮询传感器状态等待外部设备的输入处理定时器溢出中断

软件轮询是通过主动轮询来检查状态变化,而轮询中断是通过中断来响应外部事件。矢量中断是一种特殊的中断,具有可调整的中断优先级,能够提供高实时性。

4.对于使用轮询中断的系统,请展示(使用伪代码)如何按照给定的顺序对来自以下设备的中断进行优先级排序:Timer、ADC和UART。

(略)

5.解释看门狗定时器的工作原理,以及它如何帮助系统从某些故障中恢复。

它的工作原理涉及定期重置,如果系统正常运行,定时器会被周期性地重置。如果系统发生故障或崩溃,导致无法及时重置定时器,那么看门狗定时器就会触发,并执行预定义的恢复操作,以使系统返回到安全状态。

1314

1.给出内存映射(memory mapped)I/O和端口映射(port mapped)I/O之间的4个区别。
2.简要比较和对比专用指令处理器(ASIP)和单用途处理器(SPP)的特点和操作。
3.借助适当的例子,解释如何以及何时使用以下每种轮询方式

1415

1.同步通信与异步通信的区别
2.SPI与I2C的区别
  • I2C是半双工通信,SPI是全双工通信。
  • I2C支持多主多从,SPI支持单主。
  • I2C是双线协议,SPI是四线协议。
  • I2C支持时钟扩展,SPI不支持时钟扩展。
  • 同规模下I2C比SPI慢。
  • I2C有额外的开销启动和停止位,SPI没有任何启动和停止位。
  • I2C在传输的每个字节之后都有一个确认位。
  • I2C有上拉电阻要求。

I2C代表内部集成电路。I2C是一种简单的双线串行协议,用于在嵌入式系统中的两个设备或芯片之间进行通信。I2C有SCL和SDA两条线,SCL用于时钟,SDA用于数据

3. 上拉电阻的作用
  • 提高电路稳定性,避免引起误动作。图1中的按键如果不通过电阻上拉到高电平,那么在上电瞬间可能就发生误动作,因为在上电瞬间电路引脚电平是不确定的,上拉电阻R的存在保证了其引脚处于高电平状态,就不会发生误动作;
  • 提高输出管脚的带载能力。受其他外围电路的影响,电路在输出高电平时能力不足,达不到VCC状态,这会影响整个系统的正常工作,上拉电阻的存在就可以使管脚的驱动能力增强。

1516

1.在处理嵌入式系统时,交叉编译器和设备程序员的目的是什么?
2. 列出软件轮询和基于中断的输入/输出之间的两个关键区别。
3. 简要说明嵌入式计算机系统中总线感知和非总线感知的I/O设备之间的区别。
4.借助于一个草图解释电阻的作用(上拉或拉下适当)在一个主动高的按钮开关电路。
5. 简要解释定时器预分频和后分频的操作。

1617

1. 微控制器和微处理器的主要区别是什么?它如何帮助嵌入式系统设计者?
2. 什么是哈佛架构?为什么有时它被用于微控制器?
3.在使用哈佛体系结构时,处理器的操作码大小和本机字长不同有什么好处?
4.借助于一个草图,使用中断优先级的菊花链简要描述向量化中断的操作。

1718

1. 简要描述从ROM读取一个字节的数据到CPU所需的信号和事件序列。
1. 绘制一个高层电路,显示CPU和外围设备之间的所有主要连接。

1819

1. 防抖
(a)基本上所有的机械开关都会弹跳,需要一些脱壳处理,以避免在非常快的连续中记录多个开关状态转换。交换机脱扣可以通过硬件或软件实现。
(i)一个实现有源高电平开关并结合硬件脱线的硬件电路草图。清楚说明电路中所有元件的用途。
(ii)画出按下按钮后的电路响应草图,适当地显示电路中关键点的中间信号,以解释其操作。
(iii)解释为什么总线感知设备必须具有高阻抗状态,并指出如果没有高阻抗状态的设备连接到总线上可能会发生什么。

高阻抗状态意味着设备的输出电路对电流的流动提供了极高的电阻,因此它不会对总线上的电压信号产生明显的影响,有助于维持总线的稳定性和数据的可靠传输。

  1. 信号冲突: 如果两个或更多设备同时试图将不同的信号发送到总线上,可能会导致信号冲突,使总线上的电压信号变得混乱不清。
  2. 电流浪涌: 当一个设备处于低阻抗状态并试图将信号发送到总线上时,它可能引起电流浪涌。这可能导致总线上的电流急剧增加,对其他设备和总线结构造成不必要的负担。
  3. 数据损坏: 在没有高阻抗状态的情况下,设备可能会对总线上的信号产生额外的负载,从而降低信号质量并可能导致数据损坏。

VIII. 历年卷答案清晰版

1617

updateLd()
    const NEXT_FLASH_TICKS = 350
    const FLASH_DURATION_TICKS = 10
    static nextFlashColor = 1
    static ledOnColor = 0
    
    detect nextFlashColor
    if (nextFlashColor==0)
        set LED = HIGH
        set ledOnColor = FLASH_DURATION_TICKS
        set nextFlashColor = NEXT_FLASH_TICKS
     else if (ledOnColor > 0)
         decrease ledOnColor
         if(ledOnColor == 0)
             set LED = LOW
Protocol element:
    Start Condition(Master)
    Send LCD Address(Master)
    SendControlByte(Write;Master)
    ACK(Slave)
    LCD register(0x03;Master)+ACK(Slave)
    Send'H'(Master)+ACK(Slave)
    Send'I'(Master)+ACK(Slave)
    Stop Condition(Master)

Timer

A u s s u m e   t h a t   t h e   p o s t s c a l e   i s   1 : 1 F o s c = 20 M H z F s r c = F o s c 4 = 5 M h z = > T s r c = 1 F F s t c = 0.2 μ s T r e s u l t = p r e s c a l e ∗ T s r c ∗ N ∗ p o s t s c a l e N m a x = 2 16 = 65536 T h e   m i n i m u m   T i m e o u t   p e r i o d : 0.2 ∗ 1 ∗ 65536 ∗ 1 = 13.1072   m s T h e   m a x i m u m   T i m e o u t   p e r i o d : 0.2 ∗ 8 ∗ 65536 ∗ 1 = 1021.8576   m s r e s o l u t i o n = 0.2 ∗ 8 = 1.6   μ s Aussume\ that\ the\ postscale\ is\ 1:1\\ F_{osc}=20MHz\\ F_{src}=\frac{F_{osc}}{4}=5Mhz\\ =>T_{src}=\frac{1}{F_{Fstc}}=0.2 \mu s\\ T_{result}=prescale*T_{src}*N*postscale\\ N_{max}=2^{16}=65536\\ The\ minimum\ Timeout\ period: 0.2*1*65536*1 = 13.1072\ ms\\ The\ maximum\ Timeout\ period: 0.2*8*65536*1 = 1021.8576\ ms\\ resolution = 0.2*8=1.6\ \mu s Aussume that the postscale is 1:1Fosc=20MHzFsrc=4Fosc=5Mhz=>Tsrc=FFstc1=0.2μsTresult=prescaleTsrcNpostscaleNmax=216=65536The minimum Timeout period:0.21655361=13.1072 msThe maximum Timeout period:0.28655361=1021.8576 msresolution=0.28=1.6 μs

  • Tsrc: Source period or time (possibly the period of an input clock or signal).

  • prescale: A prescaler value of N means a timer tick takes place every N clock cycles instead

    of every clock cycle

  • N: A prescaler value of N means a timer tick takes place every N clock cycles instead

    of every clock cycle

  • post-scale: A post-scaler value of M, means a timer event will be signalled only after the timer resets M times instead of each time it resets

  • image-20231231032859051

30 H z   r a t e   = >   T i m e = 1 30   s =   33.33   m s A l l o w   1.5   μ s f o r   r e s e t t i n g   t i m e r = > d e s i r e d   r e s t   t i m e = 33333.3   μ s − 1.5   μ s = 33331.833   μ s d e s i r e d s c a l e : 33331.8 0.2 ∗ 65536 = 2.54 N e a r e s t   p r e s c a l e   v a l u e   i s   1 : 4 N i n r r = r o u n d ( 33331.8 0.2 ∗ 4 ∗ 1 ) = r o u n d ( 41664.75 ) = 41665 T i m e r   r e g i s t e r   v a l u e = 2 16 − N i r r = 65536 − 41665 = 23871 30Hz\ rate\ =>\ Time = \frac{1}{30}\ s =\ 33.33\ ms\\ Allow\ 1.5\ \mu s for\ resetting\ timer\\ => desired\ rest\ time =33333.3\ \mu s-1.5\ \mu s \\ =33331.833\ \mu s\\ desired scale:\frac{33331.8}{0.2*65536}=2.54\\ Nearest\ prescale\ value\ is\ 1:4\\ N_{inrr}=round(\frac{33331.8}{0.2*4*1})=round(41664.75)=41665\\ Timer\ register\ value = 2^{16}-N_{irr}=65536-41665=23871 30Hz rate => Time=301 s= 33.33 msAllow 1.5 μsfor resetting timer=>desired rest time=33333.3 μs1.5 μs=33331.833 μsdesiredscale:0.26553633331.8=2.54Nearest prescale value is 1:4Ninrr=round(0.24133331.8)=round(41664.75)=41665Timer register value=216Nirr=6553641665=23871

(iv)
A c h i e v e d   t i m e   = 0.2 ∗ 41665 ∗ 4 ∗ 1 = 33332 μ s F a c t u a l = 1 ( 33332 + 1.5 ) ∗ 1 0 − 6 = 29.99985 H z p e r c e n t a g e   e r r o r = 1 − 29.99985 30 ∗ 100 % = 0.0005 % Achieved\ time\ =0.2*41665*4*1=33332\mu s\\ F_{actual}=\frac{1}{(33332+1.5)*10^{-6}}=29.99985Hz\\ percentage\ error=1-\frac{29.99985}{30}*100\% =0.0005\% Achieved time =0.24166541=33332μsFactual=(33332+1.5)1061=29.99985Hzpercentage error=13029.99985100%=0.0005%
Achieving the desired frequency exactly is challenging due to the limitations imposed by integer division, prescaler values, and the 16-bit nature of the timer.

Since resolution as well as the predivision frequency is 1:4, it must be an integer multiple of 0.8 microseconds to achieve.

课件以及历年卷链接

课件-Github
历年卷以及部分历年卷答案-Github

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值