1. 实验目的
1.串口助手发送一个数(发送的形式是ascii码),最后除以100展示这个数,如发送一个-29987,最后要展示出-299.87。
2.串口助手发送一个数(发送的形式是16进制),最后除以100展示这个数,如发送一个-3,最后要展示出-0.03。
其中串口是USART1,其端口是GPIOA,引脚是PIN9、PIN10,一个用来收数据,一个用来发收据。
2. 实验流程
初始化串口;
编写数据转换函数函数;
编写接收数据中断函数和空闲中断函数。
2.1 初始化串口
//配置中断函数,这个函数下面有调用
void EXTI_NVIC_Config(void){
//NVIC初始化结构体
NVIC_InitTypeDef NVIC_InitStruct;
//设置中断优先级的分组
//就是设置主抢占优先级和子抢占优先级各是几,这里是分组为1,代表主优先级可以是0和1(就是1个位来设置主优先级),子优先级是0-7,是2的3次方
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
//配置USART为中断源
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
//配置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
//配置子优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
//使能中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
//串口初始化函数
void USART_Config(void){
//1.初始化GPIO(PA9(接串口1的TX引脚),这里是PA10(接串口1的RX引脚))
//初始化结构体 GPIO_InitStruct
//里面是GPIO的速度,上下拉,输出类型等
GPIO_InitTypeDef GPIO_InitStruct;
//USART结构体
USART_InitTypeDef USART_InitStruct;
//打开GPIOA时钟(一般开时钟要放到前面的位置,然后再是设置上拉,输出这些)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能时钟必须放到前面,不然后面的操作不会使灯点亮
//打开USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//复位串口1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); //PA9 复用为 USART1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //PA10 复用为 USART1
//驱动是哪个引脚 PA9/PA10
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;
//模式是复用功能
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
//输出的速度
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
//推挽复用输出
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
//上拉
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
//变量获取它的指针,取地址就行(&)
GPIO_Init(GPIOA,&GPIO_InitStruct);
//2.初始化串口
//使能串口时钟 (放在最上面了)
//配置波特率
USART_InitStruct.USART_BaudRate = 115200; //设置波特率115200
//配置针数据字长
USART_InitStruct.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
//配置停止位
USART_InitStruct.USART_StopBits = USART_StopBits_1; //设置为一个停止位
//配置校验位
USART_InitStruct.USART_Parity = USART_Parity_No; //无奇偶校验位
//配置硬件流控制
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不使用硬件流控制
//配置工作模式
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; //收发模式
//完成串口的初始化配置
USART_Init(USART1,&USART_InitStruct);
//串口中断优先级配置(初始化)
EXTI_NVIC_Config();
//使能串口接收中断(中断配置函数) 这是使能哪种中断,比如在接收到数据的时候(RXNE 读数据寄存器非空),我们要产生中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //生成串口中断 接收到数据就产生了中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 开启空闲中断
//使能串口(串口使能函数)
USART_Cmd(USART1,ENABLE);
}
2.2 编写数据转换函数函数
在写转换函数之前,先要看这个ASCII码中的0123456789对应的是什么。在空闲中断里面打印接收到的数据,下面是部分代码,详细代码参见2.3。
for(i = 0;i < 10; i++){
printf("value_10 = %x\n",rx_buff[i]);
}
memset(rx_buff,0,sizeof(rx_buff)); //清空数组
rx_cnt = 0; //数组指针置0
得到结果如下图所示:
所以我们通过判断接收过来的字节,判断这个值是哪个数字。
先找规律,这里是假设发送的数字是-29987,函数中传入的值一个是数组,一个是数组的总索引值,这里取到的字节首先要减去0X30,得到这个数字是几,然后再乘以10的几次方,2在万位上,需要乘以10000,它的索引值对应的是1,传来的索引总数是6,所以乘的10次方应该是:10的次方数 = 6(总索引值) - 1(2所在的索引值) - 1;9在千位上,需要乘以1000,它的索引值对应的是2,所以乘的10次方应该是:10的次方数 = 6(总索引值) - 2(9所在的索引值) - 1;第二个9在百位上,需要乘以100,它的索引值对应的是3,所以乘的10次方应该是:10的次方数 = 总索引值 - 3(9所在的索引值) - 1;以此类推。
如果是正数的话,假如是29987,2在万位上,需要乘以10000,它的索引值对应的是1,传来的索引总数是5,所以乘的10次方应该是:10的次方数 = 5(总索引值) - 1(2所在的索引值) - 1;9在千位上,需要乘以1000,它的索引值对应的是2,传来的索引总数是5,所以乘的10次方应该是:10的次方数 = 5(总索引值) - 1(2所在的索引值) - 1;以此类推。
数据转换函数函数(发送ASCII的形式的)如下:
void Value_Show(uint8_t* array, uint8_t rx_cnt) //传入数组地址,数组的索引
{
uint8_t i;
uint8_t j;
uint16_t value_fu = 0;
uint16_t temp = 0;
uint8_t rx_buff1[10] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
if(array[0] == '-'){ //判断接收的第一个字节是不是负数,这里写if(array[0] == 0X2d)也是可以的,负号的16进制就是0X2d
for(i = 1;i < rx_cnt; i++){
for(j= 0;j < sizeof(rx_buff1)/sizeof(rx_buff1[0]);j++){ //循环的次数是10
if(array[i] == rx_buff1[j]){ //判断接收到的数据是那一个
value_fu += (rx_buff1[j] - 0X30) * pow(10,rx_cnt - i -1);
break;
}
}
}
printf("value_10 = %.2f\n",(float)-value_fu/100);
}else{ //接收的是正数
for(i = 0;i < rx_cnt; i++){
for(j= 0;j < sizeof(rx_buff1)/sizeof(rx_buff1[0]);j++){
if(array[i] == rx_buff1[j]){
value_fu += (rx_buff1[j] - 0X30) * pow(10,rx_cnt - i -1);
break;
}
}
}
printf("value_10 = %.2f\n",(float)value_fu/100);
}
}
数据转换函数函数(发送的是16进制的)如下:
计算机中存放的整型数据都是按补码的形式存放的,负数的补码是其本身绝对值的原码取反再加1。
~本身绝对值 + 1 = 负数;现在求本身绝对值就是:本身绝对值 = ~(负数 - 1);对应下面代码的temp = ~(value - 1);
void Value_Show_16(uint8_t* array, uint8_t rx_cnt){
value = (rx_buff[0]<<8)|(rx_buff[1]); //拼接数据
//最高位为1代表的是负数 &同为1的时候才为1
if(value &(1<<15) != 0){
temp = ~(value - 1);
printf("value_10 = %.2f\n",(float)-temp/100);
}
}
2.3 编写接收数据中断函数和空闲中断函数
//接收数据中断函数
void USART1_IRQHandler(void){
uint8_t i;
unsigned int data;
if(USART_GetITStatus(USART1,USART_IT_RXNE)){ //每当接收到1个字节,会产生USART_IT_RXNE中断
rx_buff[rx_cnt] = USART_ReceiveData(USART1); //把这个数据放到数组中去
rx_cnt++;
}
//空闲中断函数
if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET){ //当接收到一帧数据,就会产生USART_IT_IDLE中断
data = USART1->SR; // 清空闲中断
data = USART1->DR; //空闲中断是在检测到在数据收受后,总线上在一个字节的时间内没有再接收到数据时发生
usart_idle_flag = 1; //产生空闲中断,没有用到
//Value_Show(rx_buff,rx_cnt); //ascii转换函数
Value_Show_16(rx_buff,rx_cnt); //16进制转换函数
memset(rx_buff,0,sizeof(rx_buff)); //清空数组
rx_cnt = 0; //数组索引置0
}
}
3. 实验结果
以ASCII发送正数和负数如下图所示:
以16进制发送正数和负数如下图所示: