Einfache Verwendung der Funktionsblöcke eines STM32-F103
Oft stellt man sich die Frage, wie die einzelnen Funktionsblöcke des Controllers zu programmieren sind. Die vielen Beispiele aus dem Internet helfen oft nicht weiter und man benötigt viel Zeit für die Programmierung des I/O-Funktionen. Hier eine Zusammenstellung der wichtigsten I/O-Funktionen:
In den Beispielen finden die Standard-Bibliotheken Verwendung, nach dem gleichen Prinzip erfolgt die Programmierung mit den HAL-Bibliothken.
SYSTICK-TIMER:
Er kann als Taktgeber für die Steuerung der Programmabläufe dienen. Die eigene Interrupt-Routine muss in der Interrupttabelle eingetragen werden!
//Systick-Timer auf ein Intervall von 20msec einstellen
void InitSysTickTimer()
{
unsigned long ticks;
SysClkDiv = 0;
ticks = (SYSTEMGFREQUENCY / 1000 * 20) - 1;
SysTick_Config(ticks);
return;
}
Auszug aus der Interrupttabelle (hier: startup_stm32f10x_md.c)
...
PendSV_Handler, // PendSV Handler
My_TickInterruptProgramm,
//SysTick_Handler, // SysTick Handler
...
USART1 .. USART3:
Die asynchronen seriellen Schnittstellen können als Bediener-Schnittstelle über ein Terminal oder zur Kommunikation mit externen Baugruppen z.B. WLAN-Modul ESP8266 verwendet werden.
0x20 gibt den Interrupt für den Empfang eines Zeichen auf Rx frei. Zusätzlich muß noch in der Interupttabelle die eigene Interruptroutine für USART1 ein eingetragen werden.
//USART1: Konfiguration der GPIO Pin : PA9=TX und PA10=RX
USART_InitTypeDef USART_InitTypeDef1={115200,USART_WordLength_8b,USART_StopBits_1,
USART_Parity_No,(USART_Mode_Rx | USART_Mode_Tx | 0x20 /*Interrupt RX*/),
USART_HardwareFlowControl_None};
GPIO_InitTypeDef GPIO_InitStructure;
//USART1: Konfiguration von GPIO Pin: PC9 als Output
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_Init(USART1,&USART_InitTypeDef);
USART_Cmd(USART1,ENABLE);
NVIC_EnableIRQ(USART1_IRQn);
Auszug aus der Interrupttabelle (hier: startup_stm32f10x_md.c)
...
MSer_InterruptProgramm,
// USART1_IRQHandler, // 37: USART1
...
SPI1 .. SPI2:
Wie die asynchronen Schnittstellen ist mit den synchronen Schnittstellen die Ansteuerung von Anzeigen und externen Bausteinen z.B. Analog-Digital-Wandler sehr gut möglich.
GPIO_StructInit(&GPIO_InitStruct);
SPI_InitTypeDef SPI_InitStructure;
//SPI: DatenLeitung für ein LCD-Display PB1=Daten/Cmd PB0= Reset
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
//SPI: Konfiguration des GPIO PA4 als Ausgang: SS(CS) über Software gesteuert
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
//SPI: Konfiguration der GPIO Pins PA5=SCK, PA6=MISO und PA7=MOSI
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
SPI_I2S_DeInit(SPI1);
NVIC_DisableIRQ(SPI1_IRQn);
SPI_StructInit(&SPI_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_CalculateCRC(SPI1,DISABLE);
WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BS4);
SPI_SSOutputCmd(SPI1,ENABLE);
SPI_Cmd(SPI1, ENABLE);
SPI_InitStructure.SPI_FirstBit = SPI1->DR;
//Schreibe einen Daten auf die SPI-Schnittstelle
void SPI_WrData8(char x)
{
unsigned short v;
WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BR4);
v = 1;
v |= 3;
if ((SPI1->SR & SPI_SR_TXE) == 0x02) SPI1->DR = x;
while(SPI1->SR & SPI_I2S_FLAG_BSY);
v = 1;
v |= 3;
v = SPI2->DR;
WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BS4);
return;
}
GPIO-Pin PA4 wird als \CS-Signal verwendet, aber muss beim STM32 über Software
gesteuert werden. V=1 und V|= 3 dient als Delay, da die Signale \CS und SCK sonst zeitlich nicht synchron ablaufen. Die Verzögerung ist abhängig vom CPU-Takt und dem SPI-Takt SCK!
GPIO:
Standard I/O-Leitung z.B. für die Ansteuerung einer Leichtdiode oder eines Relais.
//Blaue LED: Konfiguration von GPIO Pin: PC13 als Output
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
Die einzelnen Bits können über die folgende Funktionsaufrufe gesteuert werden:
TogglePA0 ^= 0x01;
if (TogglePA0 == 1) WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BS0);
if (TogglePA0 == 0) WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BR0);
I2C1 .. I2C2:
Der von Philips entwickelte serielle Datenbus kann als Master oder Slave programmiert werden. Hier wird der Controller als Master gesetzt und liest Daten aus dem SHT21-Sensor. Es wird kein Interrupt verwendet.
//I2C1: Konfiguration der GPIO Pin : PB6=SCL und PB7=SDA
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0xe0;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
Messung starten und Daten auslesen:
long I2C_SHT21_WrFunction(char func)
{
I2CWait = 20;
while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)) && (I2CWait));
if (I2CWait <= 0) return I2CWait;
// Sende STRAT
I2C_GenerateSTART(I2C1, ENABLE);
I2CWait = 20;
while((!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) && (I2CWait));
if (I2CWait <= 0) return I2CWait;
// Sende Adresse
I2C_Send7bitAddress(I2C1,0x80,I2C_Direction_Transmitter);
I2CWait = 20;
while((!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (I2CWait));
if (I2CWait <= 0) return I2CWait;
I2C_SendData(I2C1, (uint8_t)(func));
// Sende STOP
I2C_GenerateSTOP(I2C1, ENABLE);
return 1;
}
long I2C_SHT21_RdValue(long * pValue)
{
int NumByteToRead = 3;
int n = 0;
char d[4];
I2CWait = 20;
while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)) && (I2CWait));
if (I2CWait <= 0) return I2CWait;
// Sende START
I2C_GenerateSTART(I2C1, ENABLE);
I2CWait = 20;
while((!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) && (I2CWait));
if (I2CWait <= 0) return I2CWait;
// Sende Adresse
I2C_Send7bitAddress(I2C1,0x80, I2C_Direction_Receiver);
I2CWait = 20;
while((!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) && (I2CWait));
if (I2CWait <= 0) return I2CWait;
// While-Schleife Datenbytes lesen
while(NumByteToRead)
{
if(NumByteToRead == 1)
{
I2C_AcknowledgeConfig(I2C1, DISABLE);
// Sende STOP
I2C_GenerateSTOP(I2C1, ENABLE);
}
I2CWait = 20;
if((I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) && (I2CWait))
{
// Lese ein Byte aus dem SHT21
d[n] = I2C_ReceiveData(I2C1);
n++;
NumByteToRead--;
}
if (I2CWait <= 0) return I2CWait;
}
I2C_AcknowledgeConfig(I2C1, ENABLE);
if (pValue != NULL) *pValue = (d[0] << 8) + d[1];
return 1;
}
I2CWait wird in der SysTick-Interruptroutine alle 20msec um eins verringert!
ADC:
Mit den integrierten Analog-Digital-Wandlern können analoge Signal von 0 .. 3,3V digitalisiert werden und mit Controller beispielsweise verrechnet und angezeigt werden.
Das Beispiel startet den ADC1 und wartet auf das EOC-Flag des Wandlers.
//ADC1 CH1: Konfiguration von GPIO Pin: PA1 als analogen Eingang
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
void ADC_InitADC1SingleConvert(unsigned int cha)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = DISABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd (ADC1,ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_Cmd (ADC1,ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
ADC_RegularChannelConfig(ADC1,cha,1,ADC_SampleTime_71Cycles5);
return;
}
unsigned short ADC_ADC1SingleConvert(unsigned int cha)
{
unsigned short value;
ADC_RegularChannelConfig(ADC1,cha, 1,ADC_SampleTime_71Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
value = ADC_GetConversionValue(ADC1);
ADC_ClearFlag(ADC1,ADC_FLAG_EOC);
return value;
}
IWDG:
Der 'Independent Watchdog' ist sehr einfach für die Überwachung des Programmablaufs einzusetzen. Eine Freigabe
im RCC-Register ist nicht notwendig. Er wird einmalig während der Systeminitialisierung gestartet, muss dann aber
in der while-Schleife im Main-Programms zurückgesetzt werden. Erfolgt dies nicht z.B. durch einen Ablauffehler im
Programm löst der Controller ein Reset aus!
//Initialisierung
void Sys_InitWatchdog()
{
IWDG->KR = 0x5555;
IWDG->PR = 0x04;
IWDG->RLR = 1250;
IWDG->KR = 0xcccc;
return;
}
//Auszug aus dem Main-Programm
Sys_InitWatchdog();
while(1)
{
IWDG->KR = 0x5555;
IWDG->KR = 0xaaaa;
....
....
Wichtig: Für die Verwendung der peripheren Funktionsblöcke ist die Aktivierung der internen Takte
vor dem Einrichten der GPIO-Pins und der Initialisierung zwingend notwendig.
RCC->APB1ENR = RCC_APB1Periph_I2C1 | RCC_APB1Periph_SPI2 |
RCC_APB1Periph_USART2 | RCC_APB1Periph_USART3;
RCC->APB2ENR = RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO |
RCC_APB2Periph_USART1 | RCC_APB2Periph_SPI1 | RCC_APB2Periph_ADC1;
|