Notebook of learning c51 mcu
学习视频简介中作者给出了 baidu 网盘,里面又 Keil 和 STC 的软件,可以直接安装。其中涉及到关闭 Windows 防火墙的步骤,不关闭的话,License 注册软件不能打开,过程可以参考这篇(博客)[https://blog.csdn.net/Mirecz/article/details/108961731]
- 在目标位置新建文件夹, 命名位 '01 点亮 led'
- 打开 keil, 选择 Project -> New uVersion Project -> 选择新建的文件夹 -> 文件名设为 Project -> 确定
- 此时会弹出选择芯片的弹框, STC 系列不在列表中,选择同型号的 Atmel 89c52 代替即可。弹出 copy 启动文件选项,选否即可
- 展开目录树中 Target 1,在 Source Group 1 上右键选择 'Add new item to ...', 再选中 C File,输入文件名 main 创建主文件
- 在 main 中写入逻辑代码(右键可以直接插入头文件,很方便)
- 在 Build 按钮的同一行,有一个 Options for target 按钮(看上去像魔术棒) 点击,选择 Output 选项卡,勾选 'Create HEX File' 用来生成烧录文件
- 点击 Build 按钮,生成文件,看控制台输出,显示生成位置在目标文件夹的 Objects 下
- 打开 STC-ISP 软件,选择单片机型号 'STC89C52RC/LE52RC'
- 选择串口号,一般会自动适配
- 点击 打开程序文件,选择生成的 .hex 文件
- 点击 下载/编程 按钮,同时给开发板上电,软件右下侧会显示烧录状态
#include <REGX52.H>
void main()
{
P1=0x55; //1111 1110
while(1)
{
}
}
过程和前面一样,这里额外介绍了一下 STC-ISP 软件的软件延时代码功能。
打开 STC,软件右侧有很多实用工具,其中一个是 软件延时计算器,根据开发板具体配置,选择 系统频率,指令集等参数,然后 CV 即可, 一秒延时示例代码如下
PS: 指令集那边选仔细了,不然结果会出问题,STC89C52RC 是在 STC-Y1 这个系列里的
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
需要注意的是,他在实现中调用了 _nop_()
方法,要使用这个还需要在引入另外一个头文件 #include <INTRINS.H>
这节主要的点是函数封装,没什么难度
#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delaynms(unsigned int nms)
{
while(nms)
{
Delay1ms();
nms--;
}
}
void main()
{
P1=0xFE; //1111 1110
Delaynms(500);
P1=0xFD; //1111 1101
Delaynms(500);
P1=0xFB; //1111 1011
Delaynms(500);
P1=0xF7; //1111 0111
Delaynms(500);
P1=0xEF; //1110 1111
Delaynms(500);
P1=0xDF; //1101 1111
Delaynms(500);
P1=0xBF; //1011 1111
Delaynms(500);
P1=0x7F; //0111 1111
Delaynms(500);
}
新增内容为通过读取按键状态控制灯状态
#include <REGX52.H>
/**
* left P1.0
* up P1.1
* down P1.2
* right P1.3
*
* led P1.7
*/
void main()
{
while(1)
{
if(P1_2==0 && P1_1==0)
{
// if down pushed, light the led
P1_7 = 0;
} else {
// turn off led
P1_7 = 1;
}
}
}
增加了按键去抖效果
/**
* left P1.0
* up P1.1
* down P1.2
* right P1.3
*
* led P1.7
* 轻触按键按下时会有 5-10ms 的抖动
*/
void main()
{
while(1)
{
if(P1_2==0)
{
Delaynms(20);
while(P1_2==0);
Delaynms(20);
P1_7=~P1_7;
}
}
}
声明一个 unsigned char LedNum=0 表示状态,按键按下时赋值给 P1 端口并取反达到效果,可惜我的开发板按键和 led 用的同一个 P 口,无法实现这个实验了
- 74HC245, 数据缓冲器,由于课程中的数码管时共阴极的,正极如果都接 IO 口,由于单片机 IO 口电流很弱,所以添加了这个 245 芯片,用来增强电流的。类似驱动电机的时候需要驱动芯片,原理时一样的
- TM1640 两根线就能控制16位数码管,节省 IO
- 74HC595 通过 3 根数据线 + 2 根电源线就可以控制 8 个数码管。串转并操作。三根数据线分别是电平线,时钟移位线和数据输出控制线。
代码实现中声明代码段的时候用了 code 关键字,这种设置可以将定义放到 ROM 中,避免占用 RAM 空间,每次使用的时候都去 ROM 中读取
根据开发板的教学视频,基于 74HC595 实现了动态扫描,发现硬件 debug 的能力还是不行,需要多实践
主界面选择扳手突变,在 Editor 的 encoding 中选择 UTF-8 支持中文编码
include 后面引用 '<>' 和 "" 双引号的区别,前者是在安装目录中寻找文件,后者是在当前项目下寻找。其实可以混用,这个区别是查找的优先级
本节要点,掌握模块化代码。可以将函数抽出存放在 .c 文件中,并为其创建 .h 头文件。在主文件中通过引用 .h 文件的方式,达到重用代码的效果,很赞。
#ifndef __NIXIE_H__
#define __NIXIE_H__
void SegmentDisplay(unsigned char pos, unsigned char val);
#endif
参考视频的代码,简单改一下端口映射就实现了代码迁移,调试很方便
这节中的 IO 口弱上拉,强下拉的点讲的很好。正是因为这种特定,所以通过检测低电平状态(0)来判断按下,如果检测 1 会有强电流输入的危险
PS:使用 LCD1602 检测的时候,发现 ShowString 有 bug, 显示字符串的时候,第一位不显示。等写了 1602 之后修一下
还是要对比视频的代码才能把逻辑改对,不是很 6,就机会多练习。
没啥新意,掌握了 P15 之后改改就行了
中断控制开关:
- EA:总开关
- ET0: Timer0 开关
- PT0:Timer0 优先级开关
寄存器 IE 和 XICON 控制系统中断源是否被允许。
IE 中断寄存器允许位寻址
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
IE | A8H | name | EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
XICON 辅助中断控制寄存器, 允许位寻址
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
XICON | C0H | name | PX3 | EX3 | IE3 | IT3 | PX2 | EX2 | IE2 | IT2 |
上电复位后 IE/XICON 被清 0
中断规则:
- 低优先级中断可被高优先级中断打断
- 任何中断,一旦得到响应就不会被同等级中断打断
基础的优先级控制寄存器 IP,允许位寻址
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
IP | B8h | name | - | - | PT2 | PS(串口) | PT1 | PX1 | PT0 | PX0 |
89c51 在只有两种中断优先级,89c52 在他的基础上扩展了一位,有四种优先级。通过 IPH(不可位操作) 和 XIOCON 寄存器控制
上电复位后 IP/IPH 均为 00H
定时器/计数器 0/1 的控制寄存器 TCON,这个寄存器中还有集成有中断开关位。允许位寻址
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
TCON | 88h | name | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TF0: timer flag, 中断标志位,当计数器溢出时置为 1 当中断响应执行结束时,由硬件置为 0 (也可自己软件查询置为0)
TR0: timer run, 运行控制位, 置为 1 开始运行。
I开头的是外部中断设置,和 timer 没关系
定时器/计数器的工作模式寄存器 TMOD
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
TMOD | 89h | name | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
管脚 P3.0/RxD, P3.1/TxD
主要寄存器
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
SCON | 98h | name | SM0/FE | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
01010011 |
- 可位寻址
- SM0, SM1 设置位 0 1 表示工作在 8 位 UART 模式下
- REN 允许接收数据
- TI/RI: 发送/接收完成中断开关,共用中断通道。复位后为 0 ,接收/发送完成硬件置 1, 处理完逻辑之后。我们需要软件置0
模式控制位比较奇葩,是和电源管理寄存器共享位置的
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
PCON | 87h | name | SMOD | SMOD0 | - | POF | GF1 | GF0 | PD | IDL |
- 不可位寻址
- SMOD:为 1 时,时串行通信方式 1,2,3 的波特率加倍。
- SMOD0 位 0 时,设置位串行通信模式
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
IE | A8h | name | EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
如果要使用中断,只需控制 EA 和 ES 中断即可
demo 优先级需求小,寄存器可以不设置
sfr: special function register, 特殊功能寄存器。e.g. sfr P0=0x80;
sbit: special bit, 特殊位声明。e.g. sbit P0_1=0x81; sbit p0_1=P0^1;
^=: 异或操作
单片机复位,所有 IO 口都是高电平
变量声明添加 code 关键字,将数据存到 ROM 中,节省 RAM 空间。缺点时这个变量不能在程序中更改。
我的开发板点阵布局和上面的不一样,不能实践。
实验时先用 LCD 打印看看 8F 这个地址的保护位状态,开启之后,不能写入数据
芯片中时间以 BCD 码存储,如果要正确显示时间,调用 LCD 的 ShowHexNum 方法
2位BCD码计算方式
- DEC=BCD/16*10 + BCD%16;
- BCD=DEC/10*16 + DEC%16;
Keil 中定义常量的语法 #define DS1302_SEC 0x80
没有分号,没有等号
最后说的那个定时器检测按键消抖的功能感觉上应该挺实用
通过 MODE 设置显示模式的这种处理方式很新颖
还有一个 bug, 选中的位一直在闪烁,也就是 MODE 0 的时候还会进到 SetTime 的 loop 中,好奇怪,明天 debug 一下 问题解决了,switch-case 里面忘了加 break 了,导致显示模式下,设置模式也会被执行
板子上时有源蜂鸣器,做不了,而且感觉这个也不酷,直接跳到 iic 章节
随机读模式,在读之前会有一个虚拟写的过程,这个时用来改变内部指针的,不然光一个读命令只能读出当前计数器指向的地址数据,不具备随机指定地址的能力
很多国产 AT24C02 地址线内部默认都是接地的,外部改了也没用。这个bug害我查看了1小时,最后看10年前附送的代码才发现的,真是日了狗了!!
C语言中静态局部变量作用返回时什么?Java中没有这种概念。大概时方法内共享的吧,且一直存在
补完了之前定时器消抖的坑,挺有意思
第一次写 init 函数的时候,从机响应那部分没做好,延时顺序搞错了。突然想到,我有没有可能通过单片级的计时器记录低电平时间,观察一下。等以后有了示波器之后,也可以检测一下个 init 过程
这个温度传感器时通过两个Byte存储信息的,中间涉及到一些类型转化取值的部分还是比较模糊,还有补码之类的内容。可能要等到看完视频学一下C语言之后再回头看看了
OneWire 通信对事件要求很严格,如果按键实用定时器中断,会打算总线通信,所有会闪屏,可以通过再总线操作函数中开关 EA 暂停计时器改善,不过这也导致定时器代码分散在各个模块中,是个小缺陷。IIC中线就没有这种问题。
LCD_ShowString 的时候指针处理不是很懂,要看C的书
ShowSignedNum 里面 Number=-Number 的一些显示,也只听懂了一个大概
呼吸灯和PWM呼吸灯完成了,不过我没有直流电机,做不了最后的实验
芯片不一样,都看完了再搜下资料实验一下
基础实验完成了,电机联调缺少器件,就不做了。总的来说还是收获了不少。暂时先歇一歇,51算入门了,后面还想巩固的话要重新学一下C语言,然后找一些小项目做做,读读51的文档之类的。下一步打算一边做俄罗斯方块小项目,一边复习C语言。
- Hello world sample - turn on led
- this setup cost me about 2 hours...
- Turn on/off relay
- Bee
- Button
- when handle with button event, add delay to avoid shake.
- 74HC595 用于扩展数码管
- pin9 级联输出
- A 数据输入位
- pin11 数据记录位,给一个高电平脉冲作为标记(0-1-0)
- pin12 数据输出位,一个高电平脉冲,引脚输出变为记录的样子
过程比较长,并没有想象的简单,但是可以通过慢慢的迭代来完成这个功能,比如先点亮数码管,到数码管显示两位,再到数码管根据参数显示。。。最后再到键盘显示
- C51单片机的语句运行时间是 us 级别的
- 通信规则:先发命令码,再读/写数据
- 命令码可以告诉芯片你想做什么
- 读出来的时间数值是十六进制的,比如读出来的秒位0x59 - 十进制显示为89, 需要在数码管显示的时候做下处理
- 在写之前记得关闭写保护,你不能确定之前的程序有没有关闭,不然可能不能进行写的动作
- data change only happened when SCL is low
- 数据读取的时候有四步,我一直以为只有三步的。。。
- 输入操作 w
- 输入地址
- 输入操作 r
- 读取返回值
基本和上节内容一样,也是用了IIC协议,可以重用很多函数。 呼吸灯里面的实现算法还是很巧妙的。。。
- 寄存器设置
- 波特率设置, 9600。 选定 11.0592M 的晶振,刚好整除
- 当数据接受完后, RI自动变1, 处理完后软件复位 0
很多概念还是很模糊,寄存器有哪些功能什么的,不过我还是打算先看下去,以后用需要的话再回头复习
Busy Check 的时候很坑的一点, 当 E 从 0 变为 1 时, BF flag 才会填充到IO口。。。
- STC-SPI 工具可以给出定时器的样码,简直骚操作 - 但是好像不怎么好使
- 16位定时器需要手动重置 TH0, TL0 的初始值
- MCU是单线程的。。。当进去EX0是其他中断是不运行的
- 引导码 + 用户码 + 数据码 + 数据反码 = 32 bit
- 起始码: 9ms 高电平 + 4ms 低电平
- 脉冲周期:1.125 - 0; 2.25 - 1
- 式例程序写的太垃圾了,把波段时常统计为放到了中间做重置,我楞是没读懂他的代码,做了几个中断实验才看出来,Fuck Damn It!!!
- 红外接收去收到的波形和遥控器发出来的刚好是反的,所以用下降沿触发的形式去检测刚好合适
- 驱动形式和LCD1602很像,只实现了最基本的功能,进阶版的功能以后有用再说。显示使用的北部芯片是ST7920
- 显示英文时,16x8, 中文 8x4
- 行地址,奇怪的设定,好像是为了图片实现才搞得这一套地址系统, 文档上给的第三四行地址还不对?擦擦擦 + 1st 80H-8FH + 2nd 90H-9FH + 3rd 88H-AFH + 4th 98H-BFH
- 很棒的位图制作教程,不需要任何额外的工具 How To,不过教程上有一点写错了,需要选择 Horizontal, 选 Vertical 会乱码的!!!软件名字 'LCDAssistant'
单总线通信,简直牛皮。51一个语句执行时间是多少?很神奇。
74595 芯片阅读:
通过上面的功能图可以比较直观的理解74595的工作原理:
- 使用时通过 DS 引脚串行输入数据
- SHCP - shift register clock pin, 相当于一个开关,出发时数据进入
- STCP 相当于一个闸,开启后数据统一输出到 Qn pin 脚
- Q7S,相当于芯片扩容,一片只能存8位,两片的话可以扩展到16位
引脚说明图:
试着用网上的教程走了一遍,都没能成功,菜菜菜。。。有机会再试试把,浪费一晚上时间了
- 习惯了 Java 语法,习惯性的没有把变量声明放到函数体第一排,排了半天的错,擦擦擦~
- 搜索一下怎么给C程序定制注释,让他看上去更专业