谁能使用只有384字节程序存储器的微控制器?


67

例如PIC10F200T

几乎所有您编写的代码都将比该代码大,除非它是单用途芯片。有什么办法可以从外部存储器中加载更多程序存储器吗?我只是很好奇,我不知道这怎么可能非常有用……但一定如此。


6
有大量的用于微小的微控制器的应用程序,从专用信号发生器,向协议转换器,对“节点”在一个更大的控制系统,等等,等等,
戴夫特威德

13
好象棋程序占用672个字节,所以这不好。en.wikipedia.org/wiki/1K_ZX_Chess
John Burton

8
以下是一些小型程序(小于256个字节)可以完成的操作的示例
2013年

9
您的意思是“除非有单一用途的芯片”?大多数嵌入式系统都是单一用途的。
珍妮·平达

6
回到大学后,我为组装的8085/8155计算机(最大256字节)构建了一个功能完备的交通信号灯程序。它有步行按钮,以及一些可以模拟车辆存在的传感器。
Zoredache

Answers:


133

你们孩子们,滚下我的草坪!

384字节的空间足以在汇编程序中创建相当复杂的内容。

如果您回顾历史,直到计算机像一间房间那么大,您会发现在<1k的时间内完成了一些令人惊叹的艺术技艺。

例如,阅读经典的《梅尔-一个真正的程序员的故事》。诚然,这些家伙有4096个单词可玩,,废异教徒。

还要看看一些古老的模拟竞赛,其中的挑战是将“介绍”放入软盘的启动块中,典型的目标是4k或40k,并且通常设法包含音乐和动画。

编辑添加:事实证明,您可以用320个单词实现世界上第一个100美元的科学计算器

编辑年轻的联合国:

  • 软盘=软盘。
  • Bootblock =引导时读取的软盘的第一个扇区。
  • Demoscene =黑客团体之间的编程竞赛。
  • 如果您太软而无法使用8个拨动开关和一个“存储”按钮,则Assembler =对设备进行编程的理想方式。

4
Atari 2600游戏机的ROM游戏卡带中只有4KB的存储空间(尽管有些游戏通过使用银行切换来访问4K以上来解决此限制)。
约翰尼,

1
Eons之前,我做了一个非常逼真的鸟鸣声(足以使人们寻找这只鸟而不是怀疑计算机),其中的胆量(但不是使它每次发出完全相同的声音的随机化代码)都会嘎嘎作响。大约384个字节,而且我还有其他限制,即没有可写地址,二进制文件中不允许有零字节。
罗伦·佩希特尔

2
我需要更多信息,回想起这一点-368
John U

7
为“梅尔的故事” +1。我整周读过的最伟大的事情之一。
贾斯汀·

1
@JohnU:Atari 2600上的前几款游戏都是2K。许多开发人员从未设计过任何超越4K的游戏,因为即使8K芯片价格合理(有些公司的购物车仅使用4K芯片的一半),却使用标准(低主动选择芯片)功能将银行切换添加到卡上。芯片将支持芯片的数量从一增加到三。
超级猫

59

微控制器足够便宜,以至于它们经常被用来做一些非常简单的事情,而在过去几年中,它们很可能是用离散逻辑来完成的。真的很简单。例如,人们可能希望设备每五秒钟打开一个输出一秒钟,这比555计时器能够做到的更精确。

  movwf OSCCON
mainLp:
  ; Set output low
  clrf  GPIO
  movlw 0xFE
  movwf TRIS
  clrwdt
  call  Wait1Sec
  clrwdt
  call  Wait1Sec
  clrwdt
  call  Wait1Sec
  clrwdt
  call  Wait1Sec
  ; Set output high
  bsf   GPIO,0
  clrwdt
  call  Wait1Sec
  goto  mainLp
Wait1Sec:
  movlw 6
  movwf count2
  movlw 23
  movwf count1
  movlw 17
  movwf count0
waitLp:
  decfsz count0
   goto   waitLp
  decfsz count1
   goto   waitLp
  decfsz count2
   goto   waitLp
  retlw  0

用不到32个字(48个字节)的代码空间,这将是一个真实,可用的应用程序。一个人可以轻松添加一些选项,使一些I / O引脚控制时序选项,并且仍有很大的余地,但是,即使所有的芯片正是上面显示的内容,它仍然比使用分立器件的任何替代方案更便宜,更容易。逻辑。顺便说一句,clrwdt指令可以移入子例程,但是这样做会使事情变得不那么健壮。按照书面规定,即使故障导致返回地址堆栈损坏,在执行返回主循环之前也不会监视看门狗。如果这从未发生,看门狗将在几秒钟后重置芯片。


9
老实说,您可以优化代码,为孩子们树立一个榜样-5个单独的调用wait1sec?败家子!;)
John U

9
@JohnU:仅供参考,该代码使用单独的调用,因为如果使用零计数计数器并且计数出现毛刺,则该循环可能运行255次而不是4次,同时每秒向看门狗发送一次。尽管可以通过检查每个循环的计数是否在范围内来防止这种情况,但这样做的代码最终要比五个调用和五个clrwdt指令更为复杂。这不是最绝对的故障安全计数器安排,但会考虑安全问题(例如,避免clrwdt在子例程中使用)。
supercat

10
@ coder543:在没有电源噪声之类的情况下,不是很好。另一方面,在没有掉电检测器的零件中,如果VDD下降到最小工作电压和地之间的电平,然后又恢复正常,则可能发生各种疯狂的事情。人们通常应该尝试确保设备可能会发现的任何状态都将在合理的时间内恢复正常。看门狗踢进去大约需要两秒左右,但是故障计数器要达到零需要四分钟。
supercat

10
@ coder543,它们在重要的演示中发生的频率比您想像的要高。在构建无法请求帮助或报告错误的深度嵌入的事物时,也需要这种思维。或无法访问(请考虑深海或外太空),即使确实发现了错误。
RBerteig

6
@JohnU:我确实注意到了这一点,但认为解释为什么像我那样写代码可能会有所帮助。顺便说一句,我还试图证明即使未完全完美地优化小型任务,它们也可以适合小型处理器。
supercat

26

“仅” 384个字节?

追溯到今天,我负责为服务于船舶,管道和炼油厂管理行业的专用计算机编写整个操作系统(由我自己)。该公司的首个此类产品基于6800,并已升级到6809,他们希望将新操作系统与6809搭配使用,以便消除原始操作系统的许可成本。他们还把引导ROM的大小从32个增加到了64个字节。如果我没记错的话-它大约在33年前!-我说服工程师给了我128个字节,这样我就可以将整个操作系统的设备驱动程序放到rom上,从而使整个设备更加可靠和通用。这包括:

  • 按键反跳的键盘驱动程序
  • 视频驱动
  • 磁盘驱动器驱动程序和基本文件系统(Motorola“ abloader格式”,IIRC),具有内置功能,可以将“存储”的内存视为真正快的磁盘空间。
  • 调制解调器驱动程序(它们使FSK向后,因此这些调制解调器仅相互通信)

是的,所有这些都一无所获,并且经过手动优化以消除每个不必要的循环,但是却非常易于维护和可靠。是的,我把所有这些都弄成可用字节了-哦,它还设置了中断处理,各种堆栈,并初始化了实时/多任务操作系统,提示用户启动选项,并启动了系统。

我的一个仍然与该公司有关系的朋友(其继承人)几年前告诉我,我的代码仍在使用中!

您可以做很多384字节...


2
您说启动rom,并提到将驱动程序移动到启动rom ...这向我表明存在辅助存储介质。在此讨论中,我们已经确定您无法从此PIC上的外部存储加载代码。
coder543 2013年

5
@ coder543遗漏了一点:384个字节足以完成很多工作!最初的问题听起来像是在抱怨384不足以做任何有用的事情-它超出了我的需要-很多-提供实时,多任务操作系统的所有基本组件...
理查德T


17

我为植物设计了一个湿度传感器,该传感器可跟踪植物的水量,并在植物需要水时使LED闪烁。您可以使传感器了解植物的类型,从而在运行时更改其设置。它检测电池上的低电压。我用光了闪存和ram,但能够用C代码编写所有内容,以使该产品完美运行。

我用了你提到的pic10f。


这是我为植物水分传感器编写的代码。我使用pic10f220是因为它具有ADC模块,它的内存与pic10f200相同,我将在明天尝试查找原理图。

该代码是西班牙语,但它非常简单,应易于理解。当Pic10F从睡眠模式唤醒时,它将重置,因此您必须检查它是上电还是重置,并采取相应的措施。工厂设置被保存在ram中,因为它从未真正关闭。

MAIN.C

/*
Author: woziX (AML)

Feel free to use the code as you wish. 
*/

#include "main.h"

void main(void) 
{  
    unsigned char Humedad_Ref;
    unsigned char Ciclos;
    unsigned char Bateria_Baja;
    unsigned char Humedad_Ref_Bkp;

    OSCCAL &= 0xfe;             //Solo borramos el primer bit
    WDT_POST64();                   //1s
    ADCON0 = 0b01000000;
    LEDOFF();
    TRIS_LEDOFF(); 

    for(;;) 
    {  
        //Se checa si es la primera vez que arranca
        if(FIRST_RUN())
        {
            Ciclos = 0;
            Humedad_Ref = 0;
            Bateria_Baja = 0;
        }

        //Checamos el nivel de la bateria cuando arranca por primera vez y cada 255 ciclos.
        if(Ciclos == 0)
        {
            if(Bateria_Baja)
            {
                Bateria_Baja--;
                Blink(2);
                WDT_POST128();
                SLEEP();
            }       

            if(BateriaBaja())
            {
                Bateria_Baja = 100;     //Vamos a parpadear doble por 100 ciclos de 2 segundos
                SLEEP();
            }
            Ciclos = 255;
        }   

        //Checamos si el boton esta picado
        if(Boton_Picado)
        {
            WDT_POST128();
            CLRWDT();
            TRIS_LEDON(); 
            LEDON();
            __delay_ms(1000);   
            TRIS_ADOFF();
            Humedad_Ref = Humedad();
            Humedad_Ref_Bkp = Humedad_Ref;
        }   

        //Checamos si esta calibrado. Esta calibrado si Humedad_Ref es mayor a cero
        if( (!Humedad_Ref) || (Humedad_Ref != Humedad_Ref_Bkp) )
        {
            //No esta calibrado, hacer blink y dormir
            Blink(3);
            SLEEP();
        }   

        //Checamos que Humedad_Ref sea mayor o igual a 4 antes de restarle 
        if(Humedad_Ref <= (255 - Offset_Muy_Seca))
        {
            if(Humedad() > (Humedad_Ref + Offset_Muy_Seca)) //planta casi seca
            {
                Blink(1);
                WDT_POST32();
                SLEEP();    
            }       
        }

        if(Humedad() >= (Humedad_Ref))  //planta seca
        {
            Blink(1);
            WDT_POST64();
            SLEEP();    
        }   

        if(Humedad_Ref >= Offset_Casi_Seca )
        {
            //Si Humedad_Ref es menor a Humedad, entonces la tierra esta seca. 
            if(Humedad() > (Humedad_Ref - Offset_Casi_Seca))  //Planta muy seca
            {
                Blink(1);
                WDT_POST128();
                SLEEP();    
            }
        }

        SLEEP();
    }  
} 

unsigned char Humedad (void)
{
    LEDOFF();
    TRIS_ADON();
    ADON();
    ADCON0_CH0_ADON();
    __delay_us(12); 
    GO_nDONE = 1;
    while(GO_nDONE);
    TRIS_ADOFF();
    ADCON0_CH0_ADOFF();
    return ADRES;
}   

//Regresa 1 si la bateria esta baja (fijado por el define LOWBAT)
//Regresa 0 si la bateria no esta baja
unsigned char BateriaBaja (void)
{
    LEDON();                
    TRIS_ADLEDON();
    ADON();
    ADCON0_ABSREF_ADON();
    __delay_us(150);        //Delay largo para que se baje el voltaje de la bateria 
    GO_nDONE = 1;
    while(GO_nDONE);
    TRIS_ADOFF();
    LEDOFF();
    ADCON0_ABSREF_ADOFF();  
    return (ADRES > LOWBAT ? 1 : 0);
}   

void Blink(unsigned char veces)
{
    while(veces)
    {
        veces--;
        WDT_POST64();
        TRIS_LEDON(); 
        CLRWDT();
        LEDON();
        __delay_ms(18); 
        LEDOFF();
        TRIS_ADOFF();
        if(veces)__delay_ms(320);   
    }   
}   

主站

/*
Author: woziX (AML)

Feel free to use the code as you wish. 
*/

#ifndef MAIN_H
#define MAIN_H

#include <htc.h>
#include <pic.h>

 __CONFIG (MCPU_OFF  & WDTE_ON & CP_OFF & MCLRE_OFF & IOSCFS_4MHZ ); 

#define _XTAL_FREQ              4000000
#define TRIS_ADON()             TRIS = 0b1101
#define TRIS_ADOFF()            TRIS = 0b1111
#define TRIS_LEDON()            TRIS = 0b1011
#define TRIS_LEDOFF()           TRIS = 0b1111
#define TRIS_ADLEDON()          TRIS = 0b1001


#define ADCON0_CH0_ADON()          ADCON0 = 0b01000001;     // Canal 0 sin ADON
#define ADCON0_CH0_ADOFF()       ADCON0 = 0b01000000;       // Canal 0 con adON
#define ADCON0_ABSREF_ADOFF()    ADCON0 = 0b01001100;       //Referencia interna absoluta sin ADON
#define ADCON0_ABSREF_ADON()     ADCON0 = 0b01001101;       //referencia interna absoluta con ADON

//Llamar a WDT_POST() tambien cambia las otras configuracion de OPTION
#define WDT_POST1()   OPTION = 0b11001000
#define WDT_POST2()   OPTION = 0b11001001
#define WDT_POST4()   OPTION = 0b11001010
#define WDT_POST8()   OPTION = 0b11001011
#define WDT_POST16()  OPTION = 0b11001100
#define WDT_POST32()  OPTION = 0b11001101
#define WDT_POST64()  OPTION = 0b11001110
#define WDT_POST128() OPTION = 0b11001111

#define Boton_Picado    !GP3
#define FIRST_RUN()     (STATUS & 0x10) //Solo tomamos el bit TO

//Offsets
#define Offset_Casi_Seca  5
#define Offset_Muy_Seca   5

 //Low Bat Threshold
#define LOWBAT                    73
/*
Los siguientes valores son aproximados
LOWBAT  VDD
50      3.07
51      3.01
52      2.95
53      2.90
54      2.84
55      2.79
56      2.74
57      2.69
58      2.65
59      2.60
60      2.56
61      2.52
62      2.48
63      2.44
64      2.40
65      2.36
66      2.33
67      2.29
68      2.26
69      2.23
70      2.19
71      2.16
72      2.13
73      2.10
74      2.08
75      2.05
76      2.02
77      1.99
78      1.97
*/


#define LEDON()                 GP2 = 0; //GPIO = GPIO & 0b1011
#define LEDOFF()                GP2 = 1; //GPIO = GPIO | 0b0100
#define ADON()                  GP1 = 0; //GPIO = GPIO & 0b1101
#define ADOFF()                 GP1 = 1; //GPIO = GPIO | 0b0010

unsigned char Humedad (void);
unsigned char BateriaBaja (void);
void Delay_Parpadeo(void);
void Blink(unsigned char veces);

#endif

如果您有任何问题,请告诉我,我将根据我记得的内容进行回答。我几年前编写了这个代码,所以不要检查我的编码技能,他们已经提高了:)。

最后说明。我使用了高科技C编译器。


3
在阅读您的操作方式时,我实际上会非常有趣。您在执行操作时是否记下了您不介意在网络上共享的记录?
RhysW

1
您好RhysW,我相信我仍然有代码。实际上,这真的很简单。如果您有兴趣,我可以将您的验证码发送给您。让我知道。我设计的电路非常简单和凉爽,只有3个电阻器,一个p沟道MOSFET(用于反向电池保护),一个100nF的电容和一个LED。我在pic10f中使用了内部二极管作为电池测量的参考,并保持ADC读数恒定。
2013年

1
这听起来像一个整洁的项目。您是否有可能在此处发布详细信息(或至少将其发布到某个地方并链接到它们)?
Ilmari Karonen

1
你好拉车!请,如果您要在答案中添加一些内容,请使用“编辑”链接而不是发布新答案,因为该网站使用投票功能,因此无法像论坛一样工作。
clabacchio

16

我未曾提及的一件事是:您提到的微控制器每100片的价格仅为0.34美元。因此,对于便宜的,批量生产的产品而言,处理如此有限的单元带来的额外编码麻烦是很有意义的。尺寸或功耗可能相同。


2
那正是我的第一个想法。另外:如果我是一个有一个好主意的创业公司,但是只有几百美元的收入,那么这样的事情可能意味着回到日常工作和退出工作之间的区别。
菲涅耳

14

高中时,我有一位老师坚持说,调光对于像我这样的学生来说太难了。

因此,面对挑战,我花了很多时间使用双向可控硅来学习和理解基于相位的调光,并通过微芯片对16C84进行编程以实现这一壮举。我最终得到了以下汇编代码:

'Timing info:
'There are 120 half-cycles in a 60Hz AC waveform
'We want to be able to trigger a triac at any of 256 
'points inside each half-cycle.  So:
'1 Half cycle takes 8 1/3 mS
'1/256 of one half cycle takes about 32.6uS
'The Pause function here waits (34 * 0xD)uS, plus 3uS overhead
'Overhead includes CALL PAUSE.
'This was originally assembled using Parallax's "8051 style" 
'assembler, and was not optimized any further.  I suppose
'it could be modified to be closer to 32 or 33uS, but it is
'sufficient for my testing purposes.

list 16c84

    movlw   0xFD     '11111101
    tris    0x5      'Port A
    movlw   0xFF     '11111111
    tris    0x6      'Port B
WaitLow:             'Wait for zero-crossing start
    btfss   0x5,0x0  'Port A, Bit 1
    goto    WaitLow  'If high, goto WaitLow
WaitHigh:            'Wait for end of Zero Crossing
    btfsc   0x5,0x0  'Port A, Bit 1
    goto    WaitHigh 'If low, goto waitHigh
    call    Pause    'Wait for 0xD * 34 + 3 uS
    bcf     0x5,0x1  'Put Low on port A, Bit 1
    movlw   0x3      'Put 3 into W
    movwf   0xD      'Put W into 0xD
    call    Pause    'Call Pause, 105 uS
    bsf     0x5,0x1  'Put High on Port A, Bit 1
    decf    0xE      'Decrement E
    movf    0x6,W    'Copy Port B to W
    movwf   0xD      'Copy W to 0xD
    goto    Start    'Wait for zero Crossing
Pause:               'This pauses for 0xD * 34 + 3 Micro Seconds
                     'Our goal is approx. 32 uS per 0xD
                     'But this is close enough for testing
    movlw   0xA      'Move 10 to W
    movwf   0xC      'Move W to 0xC
Label1:
    decfsz  0xC      'Decrement C
    goto    Label1   'If C is not zero, goto Label1
    decfsz  0xD      'Decrement D
    goto    Pause    'If D is not zero, goto Pause
    return           'Return

当然,您需要针对您提到的芯片进行修改,并可能添加一个廉价的串行例程进行输入,因为您的芯片没有8位宽的端口可以监听,但是这样做的目的是,看似复杂的工作可以只需很少的代码即可完成-您可以将上述程序的十个副本放入10F200中。

您可以在我的调光页面上找到更多项目信息。顺便说一句,我从来没有向老师展示过这个,但是最终却为我的DJ朋友做了许多照明设备。


12

好吧,几年前,我写了一个带有串行I / O的温度控制器(对串行I / O进行位冲击,因为MCU没有UART)和一个简单的命令解释器来与控制器对话。MCU是Motorola(现为飞思卡尔)MC68HC705K1,它具有高达504字节的程序存储器(OTPROM)和约32字节的RAM。不比您引用的PIC小,但我记得还剩一些ROM。17年后,我还剩下一些组装的单元。想买一个吗?

是的,至少在组装中可以做到。

无论如何,我最近编写了非常简单的C程序,经过优化可能会适合384个字节。并非所有内容都需要大型复杂的软件。


5

您可以为闪烁的LED写入384字节的程序存储器,甚至更多。

据我所知,不可能用外部芯片来扩展程序存储器(除非您要在384个字节中构建完整的ASM解释器,这会很慢)。不过,可以使用外部芯片(EEPROM,SRAM)扩展数据存储器。


1
用384字节构建图灵机模拟器并不难……
克里斯·斯特拉顿

@ChrisStratton我的意思是一个完整的解释器,这样“扩展程序存储器”将具有与通常相同的功能。

是的,这就是我建议的一种紧密实施的方法。剩下的只是编译器设计...
克里斯·斯特拉顿

7
如果希望将程序逻辑存储在外部EEPROM中,那么尝试模拟PIC指令集将不是解决之道。更好的方法是设计一个针对虚拟机使用而优化的指令集。实际上,这就是Parallax在1990年代采用其“ Basic STAMP”的方法。它是具有3072字节代码空间的PIC,与串行EEPROM芯片配对。
supercat

3
BTW,关于BASIC标记的附加说明:它是在相对较少使用基于闪存或基于EEPROM的微控制器,但串行EEPROM芯片相当便宜的时候引入的。对于不需要太多速度的应用,带有串行EEPROM部件的固定代码微控制器要比同等大小的EEPROM或基于闪存的微控制器便宜。BASIC Stamp的设计今天没有意义,但在引入时非常实用。
supercat

4

实际上比你想的还要糟。当链接的Mouser页指定此处理器具有384字节的程序存储器时,令人感到困惑。PIC10F200实际上具有256个12位的程序存储器。

那么,您该怎么办?PIC10F20 x器件使用的12位PIC指令集都是单字指令,因此,在减去一些用于处理器设置的指令后,剩下的空间足以容纳约250个步骤的程序。这对于许多应用程序来说已经足够了。例如,我可能可以在这种空间中编写洗衣机控制器。

我只是查看了可用的PIC C编译器,似乎其中大约有一半甚至不会尝试为PIC10F200发出代码。那些确实写出了很多样板代码的人可能只能在剩余的空间中编写一个LED闪光灯。您真的想在这样的处理器上使用汇编语言。


您对256个指令字是正确的。实际上,其中之一是振荡器校准常数所占用的,因此可获得255条可用指令。同样,10F200不使用通常的PIC 16 14位指令集。它使用PIC 12 12位指令集。但是,我同意你的基本前提。我在PIC 10F200上做了很多有用的事情。+1
Olin Lathrop 2015年

@OlinLathrop:我已经澄清了答案。我从datashseet的第51页获得了PIC16术语,但是我已经决定只引用“ 12位指令集”更为明确。部件前缀不是使用的指令集的可靠指南。
沃伦·杨

0

在我挥舞着拐杖的日子里,我们不得不用沙子蚀刻自己的碎片!

1976年(或其前后),Atari 2600 VCS系统是当时最受欢迎的“视频游戏平台”之一。其中,微处理器(MOSTEK 6507)的运行频率高达1 MHz,并具有**** 128字节的RAM **。

我回想起具有极其有限的RAM 〜128 字节)的微控制器的第二个例子是在DC-DC转换器上使用的PIC12F。该微型计算机还必须使用汇编语言才能运行。


4
OP并不是在谈论RAM,他是在谈论程序空间。Atari 2600中的程序空间位于盒式磁带中,而不位于RIOT芯片中。2600支持的程序ROM最高可达4 kiB,无需库切换。(有些商业墨盒确实进行了存储区切换!)就您的PIC12F示例而言,OP令您不胜其烦:PIC10F20x系列器件具有16或24字节的SRAM。
沃伦·杨
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.