Jul 15, 2013
Technology1. 稳压IC的作用:
稳压器IC就是使输出电压稳定的设备中的电子元器件。所有的稳压器,都利用了相同的技术实现输出电压的稳定输出电压通过连接到误差放大器(Error Amplifier)反相输入端(Inverting Input)的分压电阻(Resistive Divider)采样(Sampled),误差放大器的同相输入端(Non-inverting Input)连接到一个参考电压Vref。 参考电压由IC内部的带隙参考源(Bandgap Reference)产生。误差放大器总是试图迫使其两端输入相等。为此,它提供负载电流以保证输出电压稳定。
2. ICSP(In-circuit serial programming)
3. FT232: USB->UART
The FT232BM is the 2nd generation of FTDI’s popular USB UART device and the FT232BL is a lead free version of it. The FT232BQ provides the same functionality as the FT232BM and FT232BL in a QFN-32 lead free package.
4. ATmega328P Parameters:
Parameter Value
Flash (Kbytes): 32 Kbytes
Pin Count: 32
Max. Operating Frequency: 20 MHz
CPU: 8-bit AVR
# of Touch Channels: 16
Hardware QTouch Acquisition: No
Max I/O Pins: 23
Ext Interrupts: 24
USB Speed: No
USB Interface: No
SPI: 2
TWI (I2C): 1
UART: 1
Graphic LCD: No
Video Decoder: No
Camera Interface: No
ADC channels: 8
ADC Resolution (bits): 10
ADC Speed (ksps): 15
Analog Comparators: 1
Resistive Touch Screen: No
DAC Resolution (bits): 0
Temp. Sensor: Yes
Crypto Engine: No
SRAM (Kbytes): 2
EEPROM (Bytes): 1024
Self Program Memory: YES
External Bus Interface: 0
DRAM Memory: No
NAND Interface: No
picoPower: Yes
Temp. Range (deg C): -40 to 85
I/O Supply Class: 1.8 to 5.5
Operating Voltage (Vcc):1.8 to 5.5
FPU: No
MPU / MMU: no / no
Timers: 3
Output Compare channels: 6
Input Capture Channels: 1
PWM Channels: 6
32kHz RTC: Yes
Calibrated RC Oscillator: Yes
Watchdog: Yes
5. ATmega328P主要特性如下:
高性能、低功耗的 8 位AVR 微处理器
先进的RISC 结构
131 条指令 – 大多数指令执行时间为单个时钟周期
32 个8 位通用工作寄存器
全静态工作
工作于20 MHz 时性能高达20 MIPS
只需两个时钟周期的硬件乘法器
非易失性程序和数据存储器
32K字节的系统内可编程Flash
擦写寿命: 10,000 次
具有独立锁定位的可选Boot 代码区
通过片上Boot 程序实现系统内编程
真正的同时读写操作
1024字节的EEPROM
擦写寿命: 100,000 次
2K字节的片内SRAM
可以对锁定位进行编程以实现用户程序的加密
外设特点
两个具有独立预分频器和比较器功能的8位定时器/计数器
一个具有预分频器、比较功能和捕捉功能的16位定时器/计数器
具有独立振荡器的实时计数器RTC
六通道PWM
8路10 位ADC
可编程的串行USART
可工作于主机/ 从机模式的SPI 串行接口
基于字节的2-wire串行接口
具有独立片内振荡器的可编程看门狗定时器
片内模拟比较器
引脚电平变化可引发中断及唤醒MCU
特殊的微控制器特点
上电复位(POR) 以及可编程的掉电检测(BOD)
经过校准的片内RC 振荡器
片内、片外中断源
6种休眠模式:空闲模式、ADC 噪声抑制模式、省电模式、掉电模式、待机模式和延长待机模式
I/O 和封装
23个可编程的I/O 口
28引脚PDIP,32引脚TQFP,28引脚QFN/MLF,与32引脚QFN/MLF封装
工作电压
1.8 - 5.5V
工作温度范围:
-40℃至85℃
工作速度等级
0 - 20 MHz @ 1.8 - 5.5V
超低功耗
正常模式:
1 MHz, 1.8V, 25°C: 0.2 mA
掉电模式:
1.8V, 0.1 μA
省电模式:
1.8V, 0.75 μA
6. LED闪烁测试程序
/*
* Let the LED shinning per 1 seconds
*/
void setup()
{
// Arduino's port 13 has a LED
pinMode(13, OUTPUT);
}
void loop()
{
digitalWrite(13, HIGH); // LED is on
delay(1000); // Last for 1 second
digitalWrite(13, LOW); // LED is off
delay(1000); // Last for 1 second
}
在Arduino中程序运行时将首先调用 setup() 函数。用于初始化变量、设置针脚的输出\输入类型、配置串口、引入类库文件等等。每次 Arduino 上电或重启后,setup 函数只运行一次。
pinMode(): 将指定的引脚配置成输出或输入。详情请见digital pins。
在 setup() 函数中初始化和定义了变量,然后执行 loop() 函数。顾名思义,该函数在程序运行过程中不断的循环,根据一些反馈,相应改变执行情况。通过该函数动态控制 Arduino 主控板。
digitalWrite() 给一个数字引脚写入HIGH或者LOW。
如果一个引脚已经使用pinMode()配置为OUTPUT模式,其电压将被设置为相应的值,HIGH为5V(3.3V控制板上为3.3V),LOW为0V。
如果引脚配置为INPUT模式,使用digitalWrite()写入HIGH值,将使内部20K上拉电阻(详见数字引脚教程)。写入LOW将会禁用上拉。上拉电阻可以点亮一个LED让其微微亮,如果LED工作,但是亮度很低,可能是因为这个原因引起的。补救的办法是 使用pinMode()函数设置为输出引脚。
注意:数字13号引脚难以作为数字输入使用,因为大部分的控制板上使用了一颗LED与一个电阻连接到他。如果启动了内部的20K上拉电阻,他的电压将在1.7V左右,而不是正常的5V,因为板载LED串联的电阻把他使他降了下来,这意味着他返回的值总是LOW。如果必须使用数字13号引脚的输入模式,需要使用外部上拉下拉电阻。
delay(): 使程序暂定设定的时间(单位毫秒)。(一秒等于1000毫秒)
编译并烧入到开发板后,可以看到系统的LED灯开始闪烁。
7. 更多的LED
void setup()
{
for (int i=2; i<=7; i++) //通过循环的方式设置2-7号引脚为输出状态
{
pinMode(i,OUTPUT);
}
}
void loop()
{
for (int x=2; x<=7; x++)
//通过循环的方式依次让每个引脚的led在1秒内完成明灭
{
digitalWrite(x,HIGH);
delay(500);
digitalWrite(x,LOW);
delay(500);
}
}
Jul 15, 2013
Technology1. PWM概念:
PWM( Pulse Width Modulation).简单来说,在arduino中我们可以理解为就是通过调节占空比来实现不同电压输出。
图片:
2. analogWrite()
描述
从一个引脚输出模拟值(PWM)。可用于让LED以不同的亮度点亮或驱动电机以不同的速度旋转。analogWrite()输出结束后,该引脚将产生一个稳定的特殊占空比方波,直到下次调用analogWrite()(或在同一引脚调用digitalRead()或digitalWrite())。PWM信号的频率大约是490赫兹。
在大多数arduino板(ATmega168或ATmega328),只有引脚3,5,6,9,10和11可以实现该功能。在aduino Mega上,引脚2到13可以实现该功能。老的Arduino板(ATmega8)的只有引脚9、10、11可以使用analogWrite()。在使用analogWrite()前,你不需要调用pinMode()来设置引脚为输出引脚。
analogWrite函数与模拟引脚、analogRead函数没有直接关系。
通过读取电位器的阻值控制LED的亮度
int ledPin = 9; // LED连接到数字引脚9
int analogPin = 3; //电位器连接到模拟引脚3
int val = 0; //定义变量存以储读值
void setup()
{
pinMode(ledPin,OUTPUT); //设置引脚为输出引脚
}
void loop()
{
val = analogRead(analogPin); //从输入引脚读取数值
analogWrite(ledPin,val / 4); // 以val / 4的数值点亮LED(因为analogRead读取的数值从0到1023,而analogWrite输出的数值从0到255)
}
3. 调节PWM值的程序:
int n=0;
void setup ()
{
pinMode(4,INPUT);
pinMode(6,OUTPUT); //该端口需要选择有#号标识的数字口
pinMode(10,INPUT);
}
void loop()
{
int up =digitalRead(4); //读取4号口的状态
int down = digitalRead(10); //读取10号口的状态
if (up==HIGH) //判断4号口目前是否是高电平
{
n=n+5; //每次累加值为5
if (n>=255) {
n=255;
} //限定最大值为255
analogWrite(6,n); //使用PWM控制6号口输出,变量n的取值范围是0-255
delay (300);
}
if (down==HIGH) //减少亮度
{
n=n-5;
if (n<=0) {
n=0;
}
analogWrite(6,n);
delay (300);
}
}
需要选择#号标识的数字口是因为这些端口需要支持PWM功能。而后在loop()函数中,将修改后的n值输出到6号端口。
4. analogRead()
描述
从指定的模拟引脚读取数据值。
Arduino板包含一个6通道(Mini和Nano有8个通道,Mega有16个通道),10位模拟数字转换器。这意味着它将0至5伏特之间的输入电压映射到0至1023之间的整数值。这将产生读数之间的关系:5伏特/
1024单位,或0.0049伏特(4.9
mV)每单位。输入范围和精度可以使用analogReference()改变。
它需要大约100微秒(0.0001)来读取模拟输入,所以最大的阅读速度是每秒10000次。
语法
analogRead(PIN)
数值的读取
引脚:从输入引脚(大部分板子从0到5,Mini和Nano从0到7,Mega从0到15)读取数值
返回
从0到1023的整数值
5. 实现呼吸灯:
void setup ()
{
pinMode(11,OUTPUT);
}
void loop()
{
for (int a=0; a<=255;a++) //循环语句,控制PWM亮度的增加
{
analogWrite(11,a);
delay(16); //当前亮度级别维持的时间,单位毫秒
}
for (int a=255; a>=0;a--) //循环语句,控制PWM亮度减小
{
analogWrite(11,a);
delay(16); //当前亮度的维持的时间,单位毫秒
}
delay(800); //完成一个循环后等待的时间,单位毫秒
}
或者:
int n = 0; // n 从 1 至 255,控制led亮度
int i = 5; // 递进数
void setup()
{
pinMode( 11, OUTPUT); //设置11口为PWM输出端
}
void loop()
{
n += i; // n每次增加 i
if ( n == 255 || n == 0)
//在n升至255或者降至0时,i进行反转。这样led灯能在亮暗间转换
i = -i;
analogWrite( 11, n );
delay( 50 ); //延迟50ms,进行下一次亮度调整
if( n == 0)
delay(1800);
}
6. 实现温度计
主要器件LM315.
void setup() {
Serial.begin(9600); //使用9600速率进行串口通讯
}
void loop() {
int n = analogRead(A0); //读取A0口的电压值
float vol = n * (5.0 / 1023.0*100);
//使用浮点数存储温度数据,温度数据由电压值换算得到
Serial.println(vol); //串口输出温度数据
delay(2000); //等待2秒,控制刷新速度
}
Serial.begin()
将串行数据传输速率设置为位/秒(波特)。与计算机进行通信时,可以使用这些波特率:300,1200,2400,4800,9600,14400,19200,28800,38400,57600或115200。当然,您也可以指定其他波特率- 例如,引脚0和1和一个元件进行通信,它需要一个特定的波特率。
Serial.println()
打印数据到串行端口,输出人们可识别的ASCII码文本并回车 (ASCII 13, 或 ‘\r’) 及换行(ASCII 10, 或 ‘\n’)。此命令采用的形式与Serial.print ()相同 。
和DS18b20有什么区别?
DS18b20是数字的,数字的出来的是方波,用脉冲方波和协议来通讯,模拟的出来的是电压,利用AD转换(ARDUINO的模拟脚可以理解为就是数字脚+AD/DA转换模块,如果你需要大量的模拟脚但是不要求数字脚,可以直接外接AD/DA转换器来实现)来得到测量值并换算成温度
0-100度 对应0-5v 模拟口返回数值0-1024 所以。模拟口的值 1=0.48828125
7. 光敏电阻的程序改动:
“达文西的手电筒”,有光才能亮,没光,绝对不会亮!
int a =300; //此处需是环境基础亮度变量,请查看自己的亮度数值,
//填写到此处数值要略大于所测得的数据但小于灯光下的数据
void setup ()
{
Serial.begin(9600);
pinMode(13,OUTPUT);
}
void loop()
{
int n = analogRead(A0); //读取模拟口A0数值
Serial.println(n);
if (n>= a ) //对光线强度进行判断,如果比我们的预设值大
就点亮LED否则就关闭
{
digitalWrite(13,HIGH);
}
else
{
digitalWrite(13,LOW);
}
}
修改为符合逻辑的光控电路:
/* 光强度小于临界值 */
if ( n < a)
{
digitalWrite(13,HIGH); // 点亮LED
}
else
{
digitalWrite(13,LOW); // 超过临界值时,关闭
}
光敏三极管有凸起的一边为发射极,此端接A0检测口,同时并联一个10K欧姆的分压电阻到地线以扩展光敏三极管的灵敏度(此处电阻越小灵敏度越高)。另一极使用5V输入。
Jul 12, 2013
Technology1. 启动镜像:
$ qemu-system-i386 -hda hd.qcow2
2. 保存当前运行状态:
同时按下ctrl+alt+2切换到Qemu内建命令行,输入:
(qemu) savevm booted
如果需要即时回复到保存时状态
(qemu) loadvm booted
关闭Qemu运行窗口
3. 快速恢复到保存状态:
$ qemu-system-i386 -hda hd.qcow2 -loadvm booted
Jul 11, 2013
Technology1. 有关异常向量
前面的例子中存在一个大BUG,内存布局中的前8个全字是为异常向量而保留的。当异常发生时控制逻辑将转到这些位置以执行对应的异常处理代码。异常向量和它们的地址如下:
- Exception Address
- Reset 0x00
- Undefined Instruction 0x04
- Software Interrupt (SWI) 0x08
- Prefetch Abort 0x0C
- Data Abort 0x10
- Reserved, not used 0x14
- IRQ 0x18
- FIQ 0x1C
按理说,这些个异常向量应该对应到异常处理程序中,既然我们代码中不会有异常发生,索性就用死循环来代替,如下:
.section "vectors"
reset: b start
undef: b undef
swi: b swi
pabt: b pabt
dabt: b dabt
nop
irq: b irq
fiq: b fiq
对应的,为了确保这些指令被放置在异常向量地址中,链接脚本也需要做相应的改动:
SECTIONS {
. = 0x00000000;
.text : {
* (vectors);
* (.text);
...
}
...
}
异常向量需要放置在所有代码之前,这确保了代了向量是从0x0地址开始。
2. C启动代码
直接执行C代码会造成CPU直接重启,因为和汇编代码不同的是C语言需要初始化运行环境。
static int arr[] = { 1, 10, 4, 5, 6, 7 };
static int sum;
static const int n = sizeof(arr) / sizeof(arr[0]);
int main()
{
int i;
for (i = 0; i < n; i++)
sum += arr[i];
}
C语言运行环境需要设置
- 栈
- 全局变量: 已初始化的 && 未初始化的
- 只读数据
2.1 栈设置
栈被用来存储自动变量,传递函数变量,存储返回地址等等。ARM Architecture
Procedure Call Standard
(AAPCS)是ARM体系结构中用于生成栈的规则。r13被用于作栈指针。
对于特定的开发板来说,栈开始地址可能不同,对于connex开发板来说,地址可以用下面的代码来定义:
ldr sp, =0xA4000000
2.2 全局变量
C代码在编译时会把已初始化的全局变量放在.data段中,因而在初始化的汇编代码中,需要把.data段从Flash搬移到RAM中。
C代码确保未初始化的全局变量被初始化成0.
当C程序被编译时,独立的.bss段被用作未初始化的变量。因为未初始化的值都是0,我们无需将其存储在FLASH中。只不过在搬移的时候,我们需要在程序中将它们初始化为0而已。
2.3 只读数据
const常量会被初始化为.rodata, .rodata也被用于存储字符常量。
.rodata在运行时不会被改变,所以它们可以被直接放置在FLASH中。
2.4 启动代码
Linker脚本需要做下面的事:
- .bss部分代替
- vectors部分代替
- .rodata部分代替
SECTIONS {
. = 0x00000000;
.text : {
* (vectors);
* (.text);
}
.rodata : {
* (.rodata);
}
flash_sdata = .;
. = 0xA0000000;
ram_sdata = .;
.data : AT (flash_sdata) {
* (.data);
}
ram_edata = .;
data_size = ram_edata - ram_sdata;
sbss = .;
.bss : {
* (.bss);
}
ebss = .;
bss_size = ebss - sbss;
}
启动代码需要完成下列任务:
- 中断向量设置
- 将.data部分从FLASH拷贝到RAM
- 将.bss置0后拷贝到RAM
- 设置栈指针(stack pointer)
- 分支程序到main函数
.section "vectors"
reset: b start
undef: b undef
swi: b swi
pabt: b pabt
dabt: b dabt
nop
irq: b irq
fiq: b fiq
.text
start:
@@ Copy data to RAM.
ldr r0, =flash_sdata
ldr r1, =ram_sdata
ldr r2, =data_size
@@ Handle data_size == 0
cmp r2, #0
beq init_bss
copy:
ldrb r4, [r0], #1
strb r4, [r1], #1
subs r2, r2, #1
bne copy
init_bss:
@@ Initialize .bss
ldr r0, =sbss
ldr r1, =ebss
ldr r2, =bss_size
@@ Handle bss_size == 0
cmp r2, #0
beq init_stack
mov r4, #0
zero:
strb r4, [r0], #1
subs r2, r2, #1
bne zero
init_stack:
@@ Initialize the stack pointer
ldr sp, =0xA4000000
bl main
stop: b stop
我们将直接用arm-none-eabi-gcc来编译所有程序:
arm-none-eabi-gcc -nostdlib -o csum.elf -T csum.lds csum.c startup.s
-nostdlib选项用于指定标准C不应该被链接。
查看符号信息:
arm-none-eabi-nm -n csum.elf
00000000 t reset
00000004 A bss_size
00000004 t undef
00000008 t swi
0000000c t pabt
00000010 t dabt
00000018 A data_size
00000018 t irq
0000001c t fiq
00000020 T main
00000094 t start
000000a8 t copy
000000b8 t init_bss
000000d0 t zero
000000dc t init_stack
000000e4 t stop
00000100 r n
00000104 R flash_sdata
a0000000 d arr
a0000000 D ram_sdata
a0000018 D ram_edata
a0000018 D sbss
a0000018 b sum
a000001c B ebss
可以看到: 中断向量从0x0开始; 汇编代码从8个全字后开始(0x20==32==84);
只读数据n放在代码之后; arr,初始化后的数据,放在RAM中;
未初始化的数据,sum放在6个int之后64==24==0x18
转化成.bin二进制格式后,在Qemu中运行之,检查结果:
$ arm-none-eabi-objcopy -O binary csum.elf csum.bin
$ dd if=/dev/zero of=./flash.bin bs=4K count=4K
$ dd if=csum.bin of=flash.bin bs=4096 conv=notrunc
$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
(qemu) xp /6dw 0xa0000000
a0000000: 1 10 4 5
a0000010: 6 7
(qemu) xp /1dw 0xa0000018
a0000018: 33
我们可以看到,1, 10, 4, 5, 6, 7 分别为数组元素,而结果为33,
储存在0x18的地址。如果感兴趣,我们大可查找出别的数据地址,这里就不一一述说了。
Jul 10, 2013
Technology研究两个汇编程序, 通过研究这两个程序,初步了解ARM汇编的知识:
1. 数组求和
.text
entry: b start @ Skip over the data
arr: .byte 10, 20, 25 @ Read-only array of bytes
eoa: @ Address of end of array + 1
.align
start:
ldr r0, =eoa @ r0 = &eoa
ldr r1, =arr @ r1 = &arr
mov r3, #0 @ r3 = 0
loop: ldrb r2, [r1], #1 @ r2 = *r1++
add r3, r2, r3 @ r3 += r2
cmp r1, r0 @ if (r1 != r2)
bne loop @ goto loop
stop: b stop
.byte声明:
.byte声明的变量在内存中以连续的比特存在,.2byte和.4byte与之类似,分别用于存储16位值和32位值。联想到C语言中的内建数据结构定义,不难想象char/int/long int 预编译完是哪一种类型。
通用语法结构如下:
.byte exp1, exp2, ...
.2byte exp1, exp2, ...
.4byte exp1, exp2, ...
你可以指定数据的格式,二进制用前缀0b/0B修饰,八进制以前缀0修饰,十进制/十六进制以0x/0X开头。整数也可以用字符常量来表示,加上单引号即可,在这种情况下ASCII码会被用到。
也可以用C表达式,包含文字和其他符号的组合,如下例:
pattern: .byte 0b01010101, 0b00110011, 0b00001111
npattern: .byte npattern - pattern
halpha: .byte 'A', 'B', 'C', 'D', 'E', 'F'
dummy: .4byte 0xDEADBEEF
nalpha: .byte 'Z' - 'A' + 1
.align声明:
ARM指令需要32位对齐,指令的起始内存地址需要是4的倍数,所以用.align指令来插入无用的byte,来确保下一条指令的起始地址是4的倍数。在代码中存在byte或是半字(half words)的时候,需要用到这条指令。
编译&运行:
# assemble
$ arm-none-eabi-as -o sum.o sum.s
# link to elf file
$ arm-none-eabi-ld -Ttext=0x0 -o sum.elf sum.o
# form bin file
$ arm-none-eabi-objcopy -O binary sum.elf sum.bin
# form flash image
$ dd if=/dev/zero of=flash.bin bs=4k count=4k
$ dd if=sum.bin of=flash.bin bs=4K conv=notrunc
# emulate with flash image
$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
# examine the result
R00=00000007 R01=00000007 R02=00000019 R03=00000037
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=00000024
PSR=600001d3 -ZC- A svc32
FPSCR: 00000000
R03的值正是我们求和的结果: 0x37==55==10+20+25, 可以用一张流程图来表示程序的运行过程
2. 字符串长度
.text
b start
str: .asciz "Hello World"
.equ nul, 0
.align
start: ldr r0, =str @ r0 = &str
mov r1, #0
loop: ldrb r2, [r0], #1 @ r2 = *(r0++)
add r1, r1, #1 @ r1 += 1
cmp r2, #nul @ if (r1 != nul)
bne loop @ goto loop
sub r1, r1, #1 @ r1 -= 1
stop: b stop
运行结果:
(qemu) info registers
R00=00000010 R01=0000000b R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=0000002c
PSR=600001d3 -ZC- A svc32
FPSCR: 00000000
r1是用来存储字符串长度的,计算结果为11。
.asciz声明:
.asciz声明接受一个字符串作为参数,字符串以双引号修饰的字符表示。汇编器自动在字符串后面加nul字符(即\0字符)
.equ声明
汇编器维持一张符号表,符号表维持键->值的格式。当汇编器遇到一个标号时,汇编器将在符号表中自动建立一个条目。以后当汇编器遇到一个关于label的引用时,将自动替换为符号表中储存的label的地址。
使用汇编器指令.equ,我们可以手动在符号表中插入条目。
.equ通常这样定义:
.equ name, expression
.equ不会分配任何内存,它们只是在符号表中插入条目罢了。
bne的意思是(!=) , b means bit. bit not equal. ble (<=), beq (==), bge (>=), bgt (>), and bne (!=).