srkxrolz
发表于 6 天前
第十五集 计数器用法16位自动重装载+8位预分频=24位累加计数器 每个脉冲+1 8051u有t0 t1 t2 t3 t4 t11 6个定时器定时计数器功能选择位 0=定时器 1=计数器T0 T1 TMOD^2 (T0_CT)TMOD^6T2 AUXR^3T3 T4 T4T3M^2T4T3M^6T11 T11CR^6其实这很多的位都已经在头文件中定义好了的可以直接位寻址定时器的四种工作模式:0 16位自动重装载1 16位不可重装载2 8位自动装置载3 不可屏蔽中断的16位自动重装载t0支持四种T1不支持3T2只支持0 可以当波特率发生器T3 4 11 只支持0 跟t2一样 但是11不能当波特率发生器 t0工作在模式3的时候可以做systick 中断不可屏蔽且优先级最高 // t1做计时器P35来10个脉冲溢出一次void t1_init(){ u16 i = 0xff-10; P3M0 &= ~0x00; //设置为双向口即可 P3M1 &= ~0x00; P3PU |= 0x20; // 使能P35上拉电阻 T1_CT = 1; // 设置定时器1为计数器模式 T1_GATE = 0; // 禁止定时器1的门控功能 T1_M0 = 0; // 设置定时器1为16位自动重载模式 T1_M1 = 0; // 设置定时器1为16位自动重载模式 TH1 = i>>8;// 设置定时器1初值高8位 TL1 = i;// 设置定时器1初值低8位TR1 = 1; // 启动定时器1ET1=1; //打开中断 printf_usb("t1计数器初始化完成!"); }void t1_isr(void) interrupt 3{ int0_flag++;} void fun2(void){ u16 c = 0; c = TH1; c <<= 8; c += TL1; SEG7_ShowString("%u", int0_flag); printf("systick:%ucont:%u \r\n", systick, c); // 通过USB输出systick计数值}
srkxrolz
发表于 6 天前
第十六集 18B20
18B20 数字温度计可以使用IO电源,也可外接电源,一个IO口即可完成通信,采用时间片方式,没有硬件,所以只能参考时序图搞搞
#include "config.h"
#include "18b20.h"
void DS18B20_Reset()
{
CY = 1;
while (CY)
{
DQ = 0; //送出低电平复位信号
delay_us(480); //延时至少480us
DQ = 1; //释放数据线
delay_us(60); //等待60us
CY = DQ; //检测存在脉冲
delay_us(480); //等待设备释放数据线
}
}
u8 DS18B20_ReadByte()
{
u8 i;
u8 dat = 0;
for (i=0; i<8; i++) //8位计数器
{
dat >>= 1;
DQ = 0; //开始时间片
delay_us(1); //延时等待
DQ = 1; //准备接收
delay_us(1); //接收延时
if (DQ) dat |= 0x80; //读取数据
delay_us(60); //等待时间片结束
}
return dat;
}
void DS18B20_WriteByte(u8 dat)
{
char i;
for (i=0; i<8; i++) //8位计数器
{
DQ = 0; //开始时间片
delay_us(1); //延时等待
dat >>= 1; //送出数据
DQ = CY;
delay_us(60); //等待时间片结束
DQ = 1; //恢复数据线
delay_us(1); //恢复延时
}
}
u16 ReadTemperature()
{
u16 TempH, TempL, Temperature,MinusFlag;
DS18B20_Reset(); //设备复位
DS18B20_WriteByte(0xCC); //跳过ROM命令
DS18B20_WriteByte(0x44); //开始转换命令
while (!DQ); //等待转换完成
DS18B20_Reset(); //设备复位
DS18B20_WriteByte(0xCC); //跳过ROM命令
DS18B20_WriteByte(0xBE); //读暂存存储器命令
TempL = DS18B20_ReadByte(); //读温度低字节
TempH = DS18B20_ReadByte(); //读温度高字节
if(TempH & 0xf8) //判断是否位负数
{
MinusFlag = 1; //设置负数标志
Temperature = (TempH<<8) | TempL;
Temperature = ~Temperature + 1;
Temperature *= 0.625; //0.0625 * 10,保留1位小数点
}
else
{
MinusFlag = 0; //清除负数标志
Temperature = (((TempH<<8) | TempL) * 0.625); //0.0625 * 10,保留1位小数点
}
return Temperature;
}
srkxrolz
发表于 5 天前
第十七集串口
8051u提供了4个串口uasrt1和usart2 是同步/异步串口uart3和uart4是异步串口
起始位1->0一个位的时间数据位8的数据位停止位0->1 一个位的时间Stc-isp可以迅速生成串口配置与切换端口定义#include "usart.h"
u8 rec, len = 0;void usart_date_send(u8 *str){ for (; *str != 0; str++) {
S2BUF = *str; while (!S2TI); }}u8 usart_date_handle(void){ if (len > 5 && rec == '\n' && rec == '\r') { if (rec == 'o' && rec == 'p' && rec == 'e' && rec == 'n') { P21 = 0; len = 0; usart_date_send("P21_open \r\n"); } else if (rec == 'c' && rec == 'l' && rec == 'o' && rec == 's' && rec == 'e') { P21 = 1; len = 0; usart_date_send("P21_close \r\n"); } }
return 0;}void Uart2_Isr(void) interrupt 8{ if (S2CON & 0x02) // 检测串口2发送中断 { S2CON &= ~0x02; // 清除串口2发送中断请求位 } if (S2CON & 0x01) // 检测串口2接收中断 { rec = S2BUF; S2CON &= ~0x01; // 清除串口2接收中断请求位 }}void PortSwitch(void){ P_SW2 |= 0x01; // UART2/USART2: RxD2(P4.2), TxD2(P4.3)}
void Uart2_Init(void) // 9600bps@40.000MHz{ PortSwitch(); S2CON = 0x50; // 8位数据,可变波特率 AUXR |= 0x04; // 定时器时钟1T模式 T2L = 0xEE; // 设置定时初始值 T2H = 0xFB; // 设置定时初始值 AUXR |= 0x10; // 定时器2开始计时 IE2 |= 0x01;// 使能串口2中断}
Usart_data_handle() 这个函数丢到task里面去即可别忘了调用一次usart_init()
srkxrolz
发表于 5 天前
第十八集 串口的高级应用校验位奇偶校验: 串口的奇偶校验是数据8位+一个校验位总共9位中的1的个数奇校验数据中如果有双数个1 那么校验位=1如果数据单但数个1校验位=0偶校验数据中如果有双数个1 那么校验位=0如果数据单但数个1校验位=1 因为校验位会占用一位,通常使用校验的话会使用串口为波特率9位的模式串口在发送或者接收时候 TB8RB8就是第九位校验位 吧收到的数据交给acc就可以得到P的校验值 然后放到校验位就可以了奇校验需要取反,#include "usart.h" u8 rec, len = 0;void usart_date_send(u8 *str){ u8 dat; for (; *str != 0; str++) { dat = *str; ACC = dat; //数据交给acc得到p这里采用奇校验 if (P) { S2TB8 = 0;//判断p=1 就给校验位=0 } else { S2TB8 = 1;//判断p=0 就给校验位=1 } S2BUF = dat; //吧数据发出去 while (!S2TI) ; }}u8 usart_date_handle(void){ if (len > 5 && rec == '\n' && rec == '\r') { if (rec == 'o' && rec == 'p' && rec == 'e' && rec == 'n') { P21 = 0; len = 0; usart_date_send("P21_open \r\n"); } else if (rec == 'c' && rec == 'l' && rec == 'o' && rec == 's' && rec == 'e') { P21 = 1; len = 0; usart_date_send("P21_close \r\n"); } } return 0;}void Uart2_Isr(void) interrupt 8{ u8 dat; if (S2CON & 0x02) // 检测串口2发送中断 { S2CON &= ~0x02; // 清除串口2发送中断请求位 } if (S2CON & 0x01) // 检测串口2接收中断 { dat = S2BUF; ACC = dat; // 给到累加器得到P值 if (S2RB8 == !P) // 奇校验部分 rec = dat; S2CON &= ~0x01; // 清除串口2接收中断请求位 }}void PortSwitch(void){ P_SW2 |= 0x01; // UART2/USART2: RxD2(P4.2), TxD2(P4.3)} void Uart2_Init(void) // 9600bps@40.000MHz{ PortSwitch(); //S2CON = 0x50; // 8位数据,可变波特率 S2CON = 0xD0; //9位数据,可变波特率 有校验位了 AUXR |= 0x04; // 定时器时钟1T模式 T2L = 0xEE; // 设置定时初始值 T2H = 0xFB; // 设置定时初始值 AUXR |= 0x10; // 定时器2开始计时 IE2 |= 0x01;// 使能串口2中断} 串口超时寄存器UR2TOCR串口2接收超时寄存器
#include "usart.h" u8 rec, len = 0, rx_timeout = 0;void usart_date_send(u8 *str){ u8 dat; for (; *str != 0; str++) { dat = *str; ACC = dat; // 数据交给acc得到p这里采用奇校验 if (P) { S2TB8 = 0; // 判断p=1 就给校验位=0 } else { S2TB8 = 1; // 判断p=0 就给校验位=1 } S2BUF = dat; // 吧数据发出去 while (!S2TI) ; }}u8 usart_date_handle(void){ if (len > 5 && rec == '\n' && rec == '\r') { if (rec == 'o' && rec == 'p' && rec == 'e' && rec == 'n') { P21 = 0; len = 0; usart_date_send("P21_open \r\n"); } else if (rec == 'c' && rec == 'l' && rec == 'o' && rec == 's' && rec == 'e') { P21 = 1; len = 0; usart_date_send("P21_close \r\n"); } }if (rx_timeout==1){ usart_date_send("usart2 received timeout! \r\n"); rx_timeout=0;} return 0;}void Uart2_Isr(void) interrupt 8{ u8 dat; if (S2CON & 0x02) // 检测串口2发送中断 { S2CON &= ~0x02; // 清除串口2发送中断请求位 } if (S2CON & 0x01) // 检测串口2接收中断 { dat = S2BUF; ACC = dat; // 给到累加器得到P值 if (S2RB8 == !P) // 奇校验部分 rec = dat; S2CON &= ~0x01; // 清除串口2接收中断请求位 UR2TOCR = 0xe0; // 开启接受超市中断 } if (UR2TOSR & 0x01) // 检测是不是接收超时中断触发的 { UR2TOSR = 0x00; // 清除超时中断标志位 UR2TOCR = 0x00; // 关闭超时中断也就是用的时候开 不用的时候关掉 rx_timeout = 1; // 整一个全局变量作为标志,在handle中可以用作判断是不是很久没有接收到数据了 }}void PortSwitch(void){ P_SW2 |= 0x01; // UART2/USART2: RxD2(P4.2), TxD2(P4.3)} void Uart2_Init(void) // 9600bps@40.000MHz{ PortSwitch(); UR2TOCR = 0x00; // 关闭超时接受功能 UR2TOTL = 0x04; // 超时的计数时长 UR2TOTH = 0x3b; UR2TOTE = 0x01; // S2CON = 0x50; // 8位数据,可变波特率 S2CON = 0xD0; // 9位数据,可变波特率 有校验位了 AUXR |= 0x04; // 定时器时钟1T模式 T2L = 0xEE; // 设置定时初始值 T2H = 0xFB; // 设置定时初始值 AUXR |= 0x10; // 定时器2开始计时 IE2 |= 0x01;// 使能串口2中断}
srkxrolz
发表于 5 天前
第十九集ADC模数转换器 模拟信号 电压(可以随时间变化)数字信号 具体的电压数值(离散量,某一时间点的电压数值) 8051u有16个12位的adc, 第16个adc是专用测试内部1.19v参考信号的41 单片机的Vref+可以作为参考电压,也就是用这个电压进行12位分解,分成4096份进行逐次逼近测量.因此测量的输入电压不得大于Vref,如果要测高电压,需要接电阻进行分压到参考电压以下进行测量,最后将测量结果乘以分压系数. 一般在配置adc时候.先配置各项参数之后再打开adc,用完记得顺手关灯以降低功耗.Adc打开电源后等1ms,等电源稳定在测量. 用于adc测量的口 需要设置位高阻态#include "adc.h"void AdcSetRate(void) // 100KSPS@40.000MHz{ ADCCFG &= ~0x0f; ADCCFG |= 0x03; // SPEED(3) ADCTIM = 0xff;// CSSETUP(1), CSHOLD(3), SMPDUTY(31)} void adc_init(){ AdcSetRate();// 设定adc的转换速度 P1M0 &= ~0x01; // P10设置位高阻输入 P1M1 |= 0x01; ADCCFG |= 0x20; // 转换的结果右对齐 ADC_POWER = 1;// 打开adc电源 delay_ms(1);}u16 adc_st(u8 no) // 0-15{ u16 adc_value = 0; ADC_CONTR &= 0xf0; // adc通道选择清零 adc_contr的低四位 ADC_CONTR |= no; // 00就是p10 ADC_START = 1; // adc转换启动 _nop_(); _nop_(); while (!ADC_FLAG) ; // 等待转换完成标志位置1 ADC_FLAG = 0; adc_value = ADC_RES; // 获得高位 adc_value <<= 8; // 高位移位 adc_value += ADC_RESL; // 获得低位 return adc_value;} void fun1(void){ u8 cod; u32 adc_value=0; float v=0.0; P20 = ~P20; cod = 0x3f; // p0-p3 这八位从高到低代表 p7-p0 cod = P0; // P0^1 高电平 cod = P1; // P1^1 高电平 cod = P2; // P2^1 高电平 cod = P3; // P3^1 高电平 cod = P4; cod = P5; LED40_SendData(cod, 5); adc_value=adc_st(0x00); v=adc_value*5.0/4096; SEG7_ShowFloat(v); printf("adc_p10 val=%d V=%f \r\n",adc_value,v ); //SEG7_ShowString("%d", adc_value);}
srkxrolz
发表于 4 天前
第二十集 ADC采集NTC换算温度主要内容 1. ADC的用途分析测温:光敏电阻测声音:咪咪头测气体:MQ-7等气体传感器测温度:NTC热敏电阻测电流:电阻丝2. Adc采集NTC换算温度NTC热敏电阻常用温度换算公式:Rt=RT0*EXP(Bn*(1/T-1/T0))RT,RT0是温度位T,T0时候的电阻值,Bn位材料常数视频中只介绍了查表法 涉及到二分查找和等比例换算的过程u16 temp_cal(u16 adc){ u16 j, k, min, max; adc = 4096 - adc; // 获取ntc上的电压数值 if (adc < temp_table) return 0xffff; if (adc > temp_table) return 0xffff; min = 0; max = 160; for (j = 0; j < 5; j++) { k = (min + max) / 2; if (adc <= temp_table) { max = k; } else { min = k; } } if (adc == temp_table) return (min * 10) - 400; if (adc == temp_table) return (max * 10) - 400; while (min <= max) { min++; if (temp_table == adc) { j = (min * 10) - 400; break; } else if (adc < temp_table) { min--; j = temp_table -temp_table;//两档差值45min=1867max=1912 61 65 printf("check: %dmin=%d\r\n",j,min); j = (adc - temp_table) * 10 / j;//adc差值 1880-1867=13 也就是小数部分在45分之13位置 j = min * 10-400 + j; break; } }printf("adc:%d%d%d %d \r\n",adc,min,max,j); return j;} 3. 使用adc15测量内部1.19V信号源,反推电源电压不知道准不准 测出来3.57 但是万用表量出来是4.3, adc_value = adc_st(15); vcc_base = 4096 / adc_value * 1.19;
srkxrolz
发表于 4 天前
第二十一集 Flash模拟EEPROM
Flash EEPROM
按块读写 按字节读写
修改数据需要整块读出后擦掉然后在吧修改后的数据整块写入,略慢 可以擦写而单个字节,比较快
擦写寿命10w-100w 10-100w
容量大MB-GB 容量小KB-MB
写慢读快 读写都差不多
功耗略高 功耗低
常用于存储大容量数据:固件,操作系统,多媒体文件等 用于小容量存储:参数,配置文件等
A8051u有64K flash 512字节为一个块
EEPROM读写时只能把1写成0,如果需要写1 就需要整块擦除,擦除后就为1.
读写是按字节操作的也就是8个位,擦除操作是按块/扇区操作的512字节为一个块,
修改数据的操作就是读整个块------->>>擦除块---->>>>修改内存的数据----->>>吧内存数据写回去.
操作时间由系统自动控制,但是需要告诉系统你的频率是多少
IAP_TPS=系统时钟/1M // 小数四舍五入 尽量向上
IAP_DATE //读写数据保存在这个8位寄存器中
IAP_ADDR 里面由三个寄存器IAP_ADDRE,IAP_ADDRH,IAP_ADDRL
//由三个寄存器组成的24位地址
IAP_CMD //操作命令,四种命令 0=不操作 1=读字节 2写字节 3=擦除地址所在的扇区
IAP_TRIG //触发要读写擦操作,设置完cmdaddr data后往这个寄存器写5AH,A5H执行
IAP_CONTR// 使能IAP=1和CMD成功标志位,失败=1 触发软复位和复位到哪的选择
IAP_TPS //设置工作频率
stc-isp下载选项里面可以设置eeprom的大小,需要注意eeprom+程序代码需要<64K
单片机对这个EEprom的访问方式有两种,IAP方式可以读写擦,MOV方式只能读
Eeprom的储存空间划分是从后往前规划的,所以空间结束地址是FF:FFFFh
Iap是从前往后找
mov是从后往前找
代码移植 略 AIcebe已提供函数库
国学芯用
发表于 4 天前
现在建议,直接从下面这3个最简单的程序开始
https://www.stcaimcu.com/data/attachment/forum/202506/25/090335zgr5zerq59fl9nff.jpg
【新提醒】还是从 printf_usb("Hello World !\r\n") 开始,《单片机原理及应用》入门@Ai8051U - 51 发烧友,UAC,极致音频,大国工匠,艺术人生,乐林漫步 国芯技术交流网站 - AI32位8051交流社区