Hephaestus 发表于 2022-10-26 20:18 教你一句成语——夜郎自大。 |
188610329 发表于 2022-10-26 20:02 教你一句成语——不屑一顾。 |
Hephaestus 发表于 2022-10-26 03:38 数一数你这编译出来多少字节, 在来讲话 先不说人家问的 CALL RET , 你 CALL 和 RET 呢? 忘带了? 其次, define 到底是什么? 你应该很清楚,没必要再这里%。 你怎么不把所有代码 define 只后,你还能 只要一句代码 就能把智能蔽障小车写出来呢? % |
newlined 发表于 2022-10-26 02:20 这不是“人外有人,天外有天”的问题,时间久了你就能明白最重要的学问是数学和英语。 |
Hephaestus 发表于 2022-10-25 18:44 您这个看起来更简单,俗话说人外有人,天外有天,但我现在是更看不懂 ![]() |
Hephaestus 发表于 2022-10-25 18:44 人家问 CALL 和 RET, 你发个 没CALL 没 RET 的 代码 |
188610329 发表于 2022-10-25 14:58
我这个效率比你的还要高,连LJMP都省掉了,你的汇编还没学到家。 我用的是Intel MCS-51 MACRO ASSEMBLER, V2.2,如果用Keil A51编译不通过请查手册。 |
楼上的例子有点特例了,像send_byte可以作为一个公用的子程序,如果用jmp,ret会不好控制,除非只有一个地方专门调用,即使是汇编,最好也是模块话,条理会清晰很多 |
newlined 发表于 2022-10-25 13:50 CALL 一次 就会把地址 存入栈 然后 跳转执行, 运行到 RET 就会从 栈中取出 地址, 返回CALL 的地方。 CALL 和 RET 应该是 成对的关系。没错吧? C 就是这么做的。 然后,你 CALL 一个函数, 这个函数,里面还要 调用 另一个函数, 这个时候,应该 CALL 然后, CALL 然后 RET 然后 RET 返回最初的地方对不对? 那么,我简单点,不要CALL 直接JMP可以么? CALL JMP RET 是不是结果一样? 再举个简单的例子吧 就是我之前举的那个例子: SEND_HALF_BCD: ANL A,#0FH ORL A,#30H SEND_BYTE: JNB TI,$ CLR TI MOV SBUF,A RET 这是一个复合函数, CALL SEND_HALF_BCD 就能把 半个BCD 转成 ASCCII 发出去。 CALL SEND_BYTE 就能直接把 一个字节 发出去,这个能理解么? 到这里为止,是不是没有问题? 能理解么? 能的话,我们往下看。 换个写法: SEND_HALF_BCD: ANL A,#0FH ORL A,#30H CALL SEND_BYTE RET SEND_BYTE: JNB TI,$ CLR TI MOV SBUF,A RET 这样,彻底拆成两个函数,你能看明白么? 作用和刚才一样,这是纯C的写法。 你比较喜欢这样写是么? 此时如果 CALL SEND_HALF_BCD 这就是你认为应该的, 两次CALL 两次RET 对吧? 然后,我们再换个写法: SEND_BYTE: JNB TI,$ CLR TI MOV SBUF,A RET SEND_HALF_BCD: ANL A,#0FH ORL A,#30H LJMP SEND_BYTE 这样, CALL SEND_HALF_BCD 有没有问题? 假如你说有问题…… 那么,我们换个个。 SEND_HALF_BCD: ANL A,#0FH ORL A,#30H LJMP SEND_BYTE SEND_BYTE: JNB TI,$ CLR TI MOV SBUF,A RET 这样总没问题了吧? 汇编之所以比C 效率高,就是可以避免无意义的 RET。 节约非必要的 RET 是提高效率的一种必要手段。这真的是基础中的基础了…… |
lcall和ljmp是不同的机制,lcall会压栈,ljmp不对栈操作,lcal必须有ret,ljmp一定不能有ret,51除了硬件自动压栈的以外,其他都是人工压栈的 |
newlined 发表于 2022-10-25 13:50 CALL 一次,就会把当前地址放入 栈 然后跳转,读到RET 从 栈里 读出地址, 然后返回 CALL 的地方。 |
188610329 发表于 2022-10-6 15:00 你好,这个例子,我一直试图理解,但我没有想通。我是这样理解的,LCALL 一个函数时,堆栈会保存当前调用时的地址,RET 时,这个地址会恢复到PC程序计数器中,如果LJMP到一个函数,堆栈不会保存当前地址,执行到RET时,堆栈里的两个字节,恢复到PC程序计数器中,这个应该是错误的吧。 |
newlined 发表于 2022-10-6 09:28 唉…… 这是基础中的基础啊…… 给你举个例子吧: 有个函数: SEND_HALF_BCD: ANL A,#0FH ORL A,#30H SEND_BYTE: JNB TI,$ CLR TI MOV SBUF,A RET 你要发送 一个BCD出去, 可以制作这么一个函数: _Send_BCD: ;通过R7传入 MOV A,R7 SWAP A LCALL SEND_HALF_BCD MOV A,R7 LCALL SEND_HALF_BCD RET 通常, 你觉得应该这么写对吧? 但是,这么写其实是完全没有 用到汇编的优势 。 所以,既然用了汇编,通常用如下写法: _Send_BCD: ;通过R7传入 MOV A,R7 SWAP A LCALL SEND_HALF_BCD MOV A,R7 LJMP SEND_HALF_BCD 这样写, 就避免了 无意义的 RET。 程序会依然回 到 LCALL _Send_BCD 的地方。而效率会变高 所以, 你要搞清楚 有时LCALL 有时LJMP, 到底是 BUG 还是你没理解 程序本身的 真正目的? |
188610329 发表于 2022-8-7 17:59 好的,您说的,我慢慢理会。这一段时间,正看程序,原程序中bug不少,比如,一个子程序,有时用LCALL调用,当然,这是对的,竟然有时用LJMP跳转过去,我都不知道最后执行了RET之后去了哪里。 |
newlined 发表于 2022-8-6 14:32 STC的 Eeprom 是把 Flash 空间模拟成 Eeprom 来操作,尤其是STC8系列,他在物理上就是 ROM的一部分。所以,只需要把它当 ROM来读就可以了。那么读取ROM的方式事就是: MOV DPTR,#16位绝对地址 CLR A MOVC A,@A+DPTR 就能读取了。整个过程没有 IAP相关寄存器的任何事情,因此,自然不需要 对 IAP相关寄存器做任何 保护工作。至于具体的 MOVC 读取时地址这块的可以详见 STC8系列的手册。 |
读指令movc可以不需要保护iap相关寄存器吗?我仔细揣摩下。 |
手机打字,我就长话短说了。 首先,trig指令发送前关中断,发送后开中断,是最低底线。 其次,我不太清楚你程序如何设计,stc8系列是支持movc读取eeprom的,所以,我的话,所有读指令都是movc完成的,写指令才会用iap指令,所以基本上是不需要保护iap相关寄存器,只需要保护dptr就可以的。 最后,牵涉到架构了,如果有可能,所有的写,擦eeprom的行为,集中在一个函数,或者一个中段中完成,对整个程序是最优的。 |
程序运行过程中,有是会出现问题,可能是这个原因,两条 0x5A, 和 0xA5 的启动指令之前,中断是关的,之后打开了,实际上这个是以前的程序,我后来补充了一些程序中,尤其是中断中用到了操作EEPROM的语句,没有对主程序中操作EEPROM的现场保护。我如果对DPTR,ISP_CMD, ISP_CONTR,ISP_ADDRH, ISP_ADDRL保护了,就没有问题了吧。 |
笔记本的键盘不好用,可能是我前一段时间摔了一下. |
188610329 发表于 2022-8-4 14:56 真是的,您提醒了我,ISP_ADDRH, ISP_ADDRL我没有保护,程序运行主程序*ISP_ADDRH, ISP_ADDRL |
newlined 发表于 2022-8-4 09:59 要说保护的话…… ISP_CMD, ISP_CONTR,之外,ISP_ADDRH, ISP_ADDRL 这些都要一起保护。 但是这些都没用。严格来讲,STC系列而言, ISP/IAP操作是唯一无法保护现场的操作。 因为: IAP_TRIG 这个启动指令, 需要连续发送两条 0x5A, 和 0xA5 的启动指令,这个是绝对不允许被打断的。所以,一般,会需要在程序中,先 CLR EA,禁止中断, 等所有 IAP操作完成 后 再次 SET EA 允许中断,这个才是关键。所以说,了解单片机的工作原理,还是重点中的重点。而如果在 主程序中已经禁止了中断,那么,相关操作就不需要 被PUSH和POP来现场保护了。 最后:IAP_TPS 是STC8系列用来设置 IAP操作速度的,理论上来说,只要你单片机不要不停的改变速度, MOV IAP_TPS,#0CH 之需要执行一次,里面的值是不会,也不需要再修改的。所以,通常来说,IAP_TPS 也是没有必要PUSH和POP的。 |
Y_G_G 发表于 2022-8-1 13:17 这个地址我基本明白了,但对地址的运用上,还是不行,我可能需要一些实践才能领会。 |
188610329 发表于 2022-8-1 17:21 这一段我还不理解,但我会尽量避免出现这个问题,以后我会慢慢领会。现在我遇到一个新的问题,就是单片机是STC的8H系列,您肯定知道,它对扇区的读写命令是类似这样的: ISP_CMD EQU 0C5H ;ISP命令寄存器 ISP_CONTR EQU 0C7H ;ISP控制寄存器 ISP_TPS EQU 0F5H ;ISP等待时间寄存器 MOV ISP_CONTR,#ENABLE_ISP MOV ISP_CMD,#01H ; 读扇区 MOV ISP_TPS,#0CH ; 这一段程序在主程序中用到了,在一个中断中也用到了,在中断中,对 ISP_CMD,ISP_CONTR进行压栈保护?就是 PUSH ISP_CMD,PUSH ISP_CONTR ,退出中断时 再 POP ISP_CONTR,POP ISP_CMD,这样保护能达到目的吗?编译是通过了,但我没有把握,您看可以吗? |
newlined 发表于 2022-8-1 08:21 AR7 不一定是 07H, 也可能是 0FH, 17H 或者 1FH。 这牵涉到另一条 伪指令: USING , 当代码向上搜索 找到USING 0 则编译的时候会把 PUSH AR7 当作 PUSH 07H 来编译, 如果找到 USING 1 则会在编译的时候 把 PUSH AR7 当作 PUSH 0FH 来编译。 所以 AR7 不是绝对的,是可变的,好处是,如果更换换寄存器组,你可以不需要一行行代码去把 代表R7地址手动更改。缺点是,USING 必需要控制好。 |
这么些天了,还没有搞明白这个? 00H-1FH 这就是R0-R7的几个工作组 20H-2FH 这是可以位寻址的,位地址是从00H开始的,字节地址也有00H,通过指令来区分是位操作还是字节操作 SETE 00H 这是位操作 MOV 00H,#255 这是字节操作 一般是编写代码的时候,00H-1FH留给工作组使用,20H-2FH留给位寻址用 所以,你一般看到的用到内存的,都是多30H开始的 |
Y_G_G 发表于 2022-7-19 01:18 在网上我搜到了这本书的下载网站,但好像需要注册,需要电话号码什么的。在淘宝上有一家卖30几元,但需要45天发货,不知道怎么回事。 |
188610329 发表于 2022-7-18 19:36 看了这个帖子,我明白了,AR7是不是已经宏定义成了07H?我以前对地址了解不透彻,看来我要对这方面仔细揣摩。 |
前一段时间,我母亲生病住院,我去陪床,我母亲出院那一天,我父亲很高兴,去做饭,又烫伤了脚,我没有时间仔细看这个帖子,很抱歉。 |
你要看的是这一本书,网上应该可以搜索到不要钱的PDF版本,也可以买二手的,马云家30块左右,超过这个价钱就不要买了,我买的时候才二十多块 汇编没有那么复杂的,不要纠结堆栈了,8051是软件堆栈,我从来都不去管它的,等到你用到了硬件堆栈,你才会体验到汇编的毛病而且8051是复杂指令,功能多得很,你要是用到精简指令,你就会更加的体会到汇编的无聊 现在的编译器效率已经非常不错了,优先选择C语言,实在不行了再用汇编 ![]() |
newlined 发表于 2022-7-18 16:53 AR0~AR7 是伪地址, 属于KEIL A51 专用,在KEIL的帮助里面可以找到,一般讲汇编的书,不会写,要讲KEIL的书才会写, 主要是为了弥补 A51 指令的空白, 你如果已经打了一点汇编基础,你会知道, 你如果要把R7 的东西复制给 R6, 是不能直接复制的,这个时候,就可以用到 “伪地址”: MOV R6,AR7 来达到目的, 此时的 AR7, 其实是一个 指向 R7 所在的直接地址。相当于: MOV R6,07H 这条指令,唯一的好处是,AR7 可以指代 4组通用寄存器的 任何一个组,编译的时候,会帮你自动 改直接地址。所以,同样道理,PUSH 只能PUSH 直接地址,我们可以用PUSH AR7来填补无法 PUSH R7的空白。 其实主要内容就这么点。你要还有兴趣 可以去KEIL的 帮助里面搜一下。 再跟你讲一下SP, SP其实就是指针,你仔细看51方面的书,会提到 3个8位地址指针,2个16位指针,分别对应的就是: R0,R1,SP,DPTR,PC 这5个指针。 其中: SP是半自动,PC 是全自动, R0,R1,DPTR是全手动。所以,基于SP是8位指针的特性,他的工作原理和 R0,R1 是一样的,就是访问的 IDATA 间接寻址内存。而PUSH 和 POP 指令你可以理解成:PUSH ACC PUSH PSW ........ POP PSW POP ACC RETI //以下代码不存在,是解释上面代码的动作。 MOV @SP,ACC INC SP MOV @SP,PSW INC SP .............. DEC SP MOV PSW,@SP DEC SP MOV ACC,@SP RETI 通过这个代码,我们可以看到。其实,你用 R0,R1 完全可以 软件模拟出 PUSH POP 指令。 反过来,指针不够用的时候, SP也可以临时拿来当 R0,R1 的替补用。 最后,说一下,代码是死的,就看你怎么用。说到底,还是要去体会和理解 单片机的工作原理。 |
也许看这些书需要一定的功力,我还远远不够。 |
@ 188610329,单片机是STC 8H |
188610329 发表于 2022-7-16 18:00 我知道堆栈是向上生长的,程序中以前就是那样设定的,我也不知道为什么,我设定的话起码会设在80H之前,我原以为堆栈的最大地址是7FH,80H到FFH是间接地址,不可以做堆栈。程序对DPTR保护后,运行了2天。没有再出以前的问题。在这里请教下,您前边的帖子里提到的指令 PUSH AR0 ,PUSH AR1 是在哪里能学到,一般的书里都没有。再就是这个帖子里的伪指令,在哪里可以学到?一般的书里也没有,谢谢。 |
datouyuan 发表于 2022-7-16 11:58 业务逻辑保证是正确的,里边用到的地址都改了,不会冲突。 |
datouyuan 发表于 2022-7-16 10:13 堆栈以前就是那样设定的。再就是提到堆栈可以安排在80H到FFH之间,我以前不知道,我以为这些地址只可以利用R0,R1间接寻址来访问,使用。 |
newlined 发表于 2022-7-16 08:42 堆栈不能这么搞, 堆栈是向上增长的,所以应该分配在内存的最末尾,而且,我不知道你用的什么单片机,最初的编写环境是怎么样的,照理你这代码 如果用的标准 A51 规范来写,你所有的变量声明不用 SEGMENT 来定的么? 不然,维护起来有多复杂?正常不应该是这么一个套路么?My_DATA SEGMENT DATA ;预约DATA 内存 RSEG My_DATA ABC: DS 1 EFG: DS 1 TEMP1: DS 1 TEMP2: DS 1 My_IDATA SEGMNET IDATA ;预约IDATA 内存 RSEG My_IDATA REV_BUF: DS 32 Disp_BUF: DS 8 ?STACK: DS 1 CSEG AT 0000H LJMP MAIN Main_PROG SEGMENT CODE RSEG Main_PROG MAIN: MOV SP,#?STACK-1 ;定位堆栈起点 SJMP $ END 你那个代码,到底是怎么折腾的,能够把 堆栈地址夹在那么奇怪的一个地方的? |