1、单片机和嵌入式系统linux的区别 随着嵌入式行业硬件平台的性能增强,项目需求和功能日益复杂,ARM公司推出的 CORTEX-M3,更是让以往做单片机的工程师在芯片和技术选型面临两难选择,本专题将从芯片价格、整个系统的硬件软件设计及维护的成本等各个方面给您提供一个参考, 并从技术角度分析单片机和带操作系统的系统的软件开发的异同点。 ● 1.单片机与ARM等新处理器的价格比较 ● 2.带操作系统与不带操作系统的软件开发的区别 ● 2.1.驱动开发的区别 ● 2.2.应用程序开发的区别 1. 单片机与ARM等新
2、处理器的价格比较 表1 型号 架构 资源 价格(元) AT89S51 8051 最高频率33MHz 4KB Flash 128B内部RAM 32个可编程IO引脚 两个16bit的计数器 一个UART口 4 SST89E564RD 8051 最高频率40MHz 64KB Flash 1KB内部RAM 32个可编程IO引脚 三个16bit的计数器 一个UART口 一个SPI接口 35 STM32F103 CORTEX-M3 最高频率72MHz 64KB或128BK Flash 20KB SRAM 80个可编程I
3、O引脚 2个12bit的ADC 7通道DMA控制器 标准调试口(SWD和JTAG) 7个定时器 USB接口 2个I2C接口 3个UART 2个SPI接口 21 HI3510 ARM9+DSP双核 最高工作频率240MHz 视频处理单元,支持多种协议的实时编解码 图形处理单元 视音频接口 以太网接口 DDR控制器 USB、UART、IrDA、 I2C、SPI、GPIO等多种外设接口 80 S3C2440 ARM9 最高工作频率400MHz SDRAM控制器 LCD控制器 4通道DMA控制器 3个UART 2
4、个SPI 1个I2C接口 IIS音频接口 SD HOST接口 2个USB接口 8个10bit ADC 摄像头接口 Camera接口 40 从表1里面各种芯片的资源,大概就可以猜知它们的应用场合。51单片机通常被用来做一些比较简单的控制,比如采集信号、驱动一些开关。AT89S51的Flash只有4K,一个稍微复杂的程序就不止4K了。SST89E564RD是一种扩展的51单片机,它的Flash达到64KB,可以外接最多64KB的SRAM。在SST89E564RD上的程序可以写得更复杂一些,但是它对外的接口也比较少。 CORTEX-M3系列的处理器,对外接口极其
5、丰富,这使得它的应用面更广,但是限于它的Flash、内存还是比较小,一般不在上面运行操作系统,它算是一个性能非常突出的单片机。 HI3510是海思半导体公司的一款用于监控设备的芯片,一般上面运行Linux系统,通过摄像头采集数据、编码,然后通过网络传输。另一端接收到数据之后,再解码。在上面运行的程序非常复杂,有漂亮的图片界面、触摸屏控制、数据库等等。对声音图像的编解码更是用到DSP核。 S3C2440是一款通用的芯片,它与“高级单片机”STM32F103相比,多了存储控制器和NAND控制器──这使得可以外接更大的Flash、更大的内存;多了内存管理单元(MMU)──这使得它可以进行地址映身
6、虚拟地址、物理地址之间的映射)。可以在S3C2440上运行Linux系统,运行更大更复杂的程序。 在具体工作中,怎么选择这些芯片呢?一句话:成本!进行任何产品的开发都要考虑性价比,一切应该从“成本”出发。成本不仅包括芯片的价格,也包括整个系统的硬件、软件设计及维护的难易。 芯片价格可以在电子市场问到,也可以在上找到有卖这种芯片的柜台,然后电话咨询。 基于不同的应用,处理器和其他外设的选择是要统一考虑的,如果要实现一个简单的U盘读写功能,那么可以选择带USB控制器的CORTEX-M3芯片,也可以选择8051外接一个USB控制器比如SL811,就看哪种方案成本更低。进行芯片选型时,必须基于
7、整个系统来考虑。 员工的偏好和知识结构也是一个很重要的因素,如果他对ATMEL的芯片比较熟,他就不会倾向于三星;如果他不会Linux等操作系统,那么选型时就不会有操作系统的概念。选择自己不熟悉的芯片和技术,最后的成本也可能更高。 2. 带操作系统与不带操作系统的软件开发的区别 用通俗的话来说,一个处理芯片不运行操作系统,我们就把它称为单片机,而单片机编程就是写裸板程序,这个程序直接在板子上运行;相对的,另一种程序就是基于操作系统的程序,说得简单点就是,这种程序可以通过统一的接口调用“别人写好的代码”,在“别人的基础上”更快更方便地实现自己的功能。 2.1. 驱动开发的区别 驱动开发的
8、区别我总结有两点:能否借用、是否通用。 2.1.1 能否借用 基于操作系统的软件资源非常丰富,你要写一个Linux设备驱动时,首先在网上找找,如果有直接拿来用;其次是找到类似的,在它的基础上进行修改;如果实在没有,就要研究设备手册,从零写起。而不带操作系统的驱动开发,一开始就要深入了解设备手册,从零开始为它构造运行环境,实现各种函数以供应用程序使用。 举个例子,要驱动一块LCD,在单片机上的做法是: ① 首先要了解LCD的规格,弄清楚怎么设置各个寄存器,比如设置LCD的时钟、分辨率、象素 ② 划出一块内存给LCD使用 ③ 编写一个函数,实现在指定坐标描点。比如根据x、y坐标在这块
9、内存里找到这个象素对应的小区域,填入数据。 基于操作系统时,我们首先是找到类似的驱动,弄清楚驱动结构,找到要修改的地方进行修改。 下面是单片机操作LCD的代码: ① 初始化: void Tft_Lcd_Init(int type) { /* * 设置LCD控制器的控制寄存器LCDCON1~5 * 1. LCDCON1: * 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
10、 * 选择LCD类型: TFT LCD * 设置显示模式: 16BPP * 先禁止LCD信号输出 * 2. LCDCON2/3/4: * 设置控制信号的时间参数 * 设置分辨率,即行数及列数 * 现在,可以根据公式计算出显示器的频率: * 当HCLK=100MHz时, * Rate = 1/[{(VSPW+1)+(VBPD
11、1)+(LIINEVAL+1)+(VFPD+1)}x * {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x * {2x(CLKVAL+1)/(HCLK)}] * = 60Hz * 3. LCDCON5: * 设置显示模式为16BPP时的数据格式: 5:6:5 * 设置HSYNC、VSYNC脉
12、冲的极性(这需要参考具体LCD的接口信号): 反转 * 半字(2字节)交换使能 */ LCDCON1 = (CLKVAL_TFT_320240<<8) | (LCDTYPE_TFT<<5) | \ (BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0); LCDCON2 = (VBPD_320240<<24) | (LINEVAL_TFT_3
13、20240<<14) | \ (VFPD_320240<<6) | (VSPW_320240); LCDCON3 = (HBPD_320240<<19) | (HOZVAL_TFT_320240<<8) | (HFPD_320240); LCDCON4 = HSPW_320240; // LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8)
14、 | \ // (HWSWP<<1); LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | (VDEN_INV << 6) | \ (HWSWP<<0); /* * 设置LCD控制器的地址寄存器LCDSADDR1~3
15、 * 帧内存与视口(view point)完全吻合, * 图像数据格式如下: * |----PAGEWIDTH----| * y/x 0 1 2 239 * 0 rgb rgb rgb ... rgb * 1 rgb rgb rgb ... rgb * 1. LCDSADDR1:
16、 * 设置LCDBANK、LCDBASEU * 2. LCDSADDR2: * 设置LCDBASEL: 帧缓冲区的结束地址A[21:1] * 3. LCDSADDR3: * OFFSIZE等于0,PAGEWIDTH等于(240*2/2) */ LCDSADDR1 = ((LCDBUFFER>>22)<<21) | LOWER21BITS(LCDBUFFE
17、R>>1); LCDSADDR2 = LOWER21BITS((LCDBUFFER+ \ (LINEVAL_TFT_320240+1)*(HOZVAL_TFT_320240+1)*2)>>1); LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_320240*2/2); /* 禁止临时调色板寄存器 */ TPAL = 0; fb_base_add
18、r = LCDBUFFER; bpp = 16; xsize = 320; ysize = 240; } ② 描点: /* * 画点 * 输入参数: * x、y : 象素坐标 * color: 颜色值 * 对于16BPP: color的格式为0xAARRGGBB (AA = 透明度), * 需
19、要转换为5:6:5格式 * 对于8BPP: color为调色板中的索引值, * 其颜色取决于调色板中的数值 */ void PutPixel(UINT32 x, UINT32 y, UINT32 color) { UINT8 red,green,blue; switch (bpp){ case 16: {
20、 UINT16 *addr = (UINT16 *)fb_base_addr + (y * xsize + x); red = (color >> 19) & 0x1f; green = (color >> 10) & 0x3f; blue = (color >> 3) & 0x1f;
21、 color = (red << 11) | (green << 5) | blue; // 格式5:6:5 *addr = (UINT16) color; break; } case 8: { UINT8 *addr = (UINT8 *
22、)fb_base_addr + (y * xsize + x); *addr = (UINT8) color; break; } default: break; } } 下面是在Linux的LCD驱动里修改的地方(arch\arm\mach-s3c2440\mac
23、h-smdk2440.c): /* 320x240 */ static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = { .regs = { .lcdcon1 = S3C2410_LCDCON1_TFT16BPP | \ S3C2410_LCDCON1_TFT | \ S3C24
24、10_LCDCON1_CLKVAL(0x04), .lcdcon2 = S3C2410_LCDCON2_VBPD(1) | \ S3C2410_LCDCON2_LINEVAL(239) | \ S3C2410_LCDCON2_VFPD(5) | \ S3C2410_LCDCON2_VSPW(1), .lcdcon3 = S3C241
25、0_LCDCON3_HBPD(36) | \ S3C2410_LCDCON3_HOZVAL(319) | \ S3C2410_LCDCON3_HFPD(19), .lcdcon4 = S3C2410_LCDCON4_MVAL(13) | \ S3C2410_LCDCON4_HSPW(5), .lcdcon5 = S3C2410_LC
26、DCON5_FRM565 | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVV | S3C2410_LCDCON5_INVVDEN | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSW
27、P, }, .gpccon = 0xaaaa56aa, .gpccon_mask = 0xffffffff, .gpcup = 0xffffffff, .gpcup_mask = 0xffffffff, .gpdcon = 0xaaaaaaaa, .gpdcon_mask = 0xffffffff, .gpdup = 0xffffffff,
28、 .gpdup_mask = 0xffffffff, .fixed_syncs = 1, .type = S3C2410_LCDCON1_TFT, .width = 320, .height = 240, .xres = { .min = 320, .max = 320, .defval =
29、320, }, .yres = { .max = 240, .min = 240, .defval = 240, }, .bpp = { .min = 16, .max = 16, .defval
30、 = 16, }, }; 这并不表示代码Linux的驱动程序就比单片机的驱动程序好写,怎么在几万个文件中找到要修改的代码,这也是需要艰苦的学习的。基于操作系统的驱动开发,既要懂得芯片的具体操作,也要理解操作系统的软件结构。 2.1.2 是否通用 有些单片机厂家也给客户提供了大量的驱动程序,比如USB HOST驱动程序,这可以让客户很容易就可以在它的上面编写程序读写U盘。但是客户写的这些程序,只能在这种芯片、这个驱动程序上使用;更换另一种芯片后,即使芯片公司也提供了驱动程序,但是接口绝对不一样,客户又得重新编写应用程序。 基于操作系统的
31、驱动程序要遵循统一的接口,比如对于不同的芯片的USB HOST驱动,它们都要向上提供一个相同的数据结构,在里面实现了各自的USB操作。 下面是S3C2410/S3C2440的USB驱动向上层提供的数据结构: static const struct hc_driver ohci_s3c2410_hc_driver = { .deion = hcd_name, .product_desc = "S3C24XX OHCI", .hcd_priv_size = sizeof(struct ohci
32、hcd), /* * generic hardware linkage */ .irq = ohci_irq, .flags = HCD_USB11 | HCD_MEMORY, /* * basic lifecycle operations */ .start = ohci_s3c2410_start,
33、 .stop = ohci_stop, .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources */ .urb_enqueue = ohci_urb_enqueue, .urb_dequeue = ohci_urb_dequeue, .endpoi
34、nt_disable = ohci_endpoint_disable, /* * scheduling support */ .get__number = ohci_get_, /* * root hub support */ .hub_status_data = ohci_s3c2410_hub_status_data,
35、hub_control = ohci_s3c2410_hub_control, .hub_irq_enable = ohci_rhsc_enable, #ifdef CONFIG_PM .bus_suspend = ohci_bus_suspend, .bus_resume = ohci_bus_resume, #endif .start_port_reset = ohci_start_port_reset,
36、 }; 下面是ATMEL公司的ARM芯片的USB驱动向上层提供的数据结构: /*-------------------------------------------------------------------------*/ static const struct hc_driver ohci_at91_hc_driver = { .deion = hcd_name, .product_desc = "AT91 OHCI", .hcd_priv_size = sizeof(st
37、ruct ohci_hcd), /* * generic hardware linkage */ .irq = ohci_irq, .flags = HCD_USB11 | HCD_MEMORY, /* * basic lifecycle operations */ .start = ohci_at91_start,
38、 .stop = ohci_stop, .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources */ .urb_enqueue = ohci_urb_enqueue, .urb_dequeue = ohci_urb_dequeue, .
39、endpoint_disable = ohci_endpoint_disable, /* * scheduling support */ .get__number = ohci_get_, /* * root hub support */ .hub_status_data = ohci_hub_status_data, .h
40、ub_control = ohci_hub_control, .hub_irq_enable = ohci_rhsc_enable, #ifdef CONFIG_PM .bus_suspend = ohci_bus_suspend, .bus_resume = ohci_bus_resume, #endif .start_port_reset = ohci_start_port_reset, }; 基于通
41、用性,即使是你自己写的Linux驱动,简单到只是点亮一个LED,基于“通用性”,这个驱动也要向上提供统一的接口。下面是单片机LED驱动程序和Linux下的LED驱动程序的部分代码。 单片机LED驱动程序: void led_init(void) { GPBCON = GPB5_out; // 将LED对应的GPB5引脚设为输出 } void led_on(void) { GPBDAT &= ~(1<<5); } void led_off(void
42、) { GPBDAT |= (1<<5); } Linux的LED驱动程序: #define DEVICE_NAME "leds" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */ #define LED_MAJOR 231 /* 主设备号 */ /* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */ #define IOCTL_LED_ON 0 #define IOCTL_LED_OFF
43、1 /* 用来指定LED所用的GPIO引脚 */ static unsigned long led_table [] = { S3C2410_GPB5, S3C2410_GPB6, S3C2410_GPB7, S3C2410_GPB8, }; /* 用来指定GPIO引脚的功能:输出 */ static unsigned int led_cfg_table [] = {
44、S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP, }; /* 应用程序对设备文件/dev/leds执行open(...)时, * 就会调用s3c24xx_leds_open函数 */ static int s3c24xx_leds_open(struct inode *inode, struct file *file)
45、 { int i; for (i = 0; i < 4; i++) { // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能 s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]); } return 0; } /* 应用程序对设备文件/dev/led
46、s执行ioclt(...)时, * 就会调用s3c24xx_leds_ioctl函数 */ static int s3c24xx_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { if (arg > 4) {
47、 return -EINVAL; } switch(cmd) { case IOCTL_LED_ON: // 设置指定引脚的输出电平为0 s3c2410_gpio_setpin(led_table[arg], 0); return 0; case IOCTL_LED_OFF:
48、 // 设置指定引脚的输出电平为1 s3c2410_gpio_setpin(led_table[arg], 1); return 0; default: return -EINVAL; } } /* 这个结构是字符设备驱动程序的核心 * 当应用程序操作设备文件时所调用的open、read、write等函数,
49、 * 最终会调用这个结构中指定的对应函数 */ static struct file_operations s3c24xx_leds_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = s3c24xx_leds_open, .ioctl = s3c24xx_leds_ioctl, }; /* * 执行insmod命令时就会调用这个函数 */ static int __init s3c24xx_leds_init(void) { int ret; /* 注册字符设备 * 参数为主设备号、设备名字、file_operations结构; * 这样,主设备号就和具体的file_operations结构联系起来了, * 操作主设备为LED_MAJOR的设备文件时,
©2010-2025 宁波自信网络信息技术有限公司 版权所有
客服电话:4009-655-100 投诉/维权电话:18658249818